@wooojin/forgen 0.2.1 → 0.3.1
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.
- package/CHANGELOG.md +76 -0
- package/README.ko.md +25 -14
- package/README.md +61 -17
- package/agents/analyst.md +48 -4
- package/agents/architect.md +39 -4
- package/agents/code-reviewer.md +107 -77
- package/agents/critic.md +47 -4
- package/agents/debugger.md +46 -4
- package/agents/designer.md +40 -4
- package/agents/executor.md +112 -30
- package/agents/explore.md +45 -5
- package/agents/git-master.md +48 -4
- package/agents/planner.md +121 -18
- package/agents/solution-evolver.md +115 -0
- package/agents/test-engineer.md +58 -4
- package/agents/verifier.md +92 -77
- package/commands/architecture-decision.md +127 -258
- package/commands/calibrate.md +225 -0
- package/commands/code-review.md +163 -178
- package/commands/compound.md +127 -68
- package/commands/deep-interview.md +212 -110
- package/commands/docker.md +68 -178
- package/commands/forge-loop.md +215 -0
- package/commands/learn.md +231 -0
- package/commands/retro.md +215 -0
- package/commands/ship.md +277 -0
- package/dist/cli.js +25 -9
- package/dist/core/auto-compound-runner.js +14 -0
- package/dist/core/config-injector.d.ts +2 -1
- package/dist/core/config-injector.js +2 -1
- package/dist/core/dashboard.d.ts +17 -0
- package/dist/core/dashboard.js +158 -2
- package/dist/core/harness.d.ts +6 -1
- package/dist/core/harness.js +75 -19
- package/dist/core/paths.d.ts +31 -1
- package/dist/core/paths.js +43 -2
- package/dist/core/spawn.d.ts +3 -2
- package/dist/core/spawn.js +27 -8
- package/dist/core/types.d.ts +34 -0
- package/dist/engine/compound-lifecycle.d.ts +4 -3
- package/dist/engine/compound-lifecycle.js +91 -46
- package/dist/engine/learn-cli.d.ts +1 -0
- package/dist/engine/learn-cli.js +182 -0
- package/dist/engine/meta-learning/adaptive-thresholds.d.ts +20 -0
- package/dist/engine/meta-learning/adaptive-thresholds.js +126 -0
- package/dist/engine/meta-learning/extraction-tuner.d.ts +15 -0
- package/dist/engine/meta-learning/extraction-tuner.js +99 -0
- package/dist/engine/meta-learning/matcher-weight-tuner.d.ts +21 -0
- package/dist/engine/meta-learning/matcher-weight-tuner.js +151 -0
- package/dist/engine/meta-learning/runner.d.ts +14 -0
- package/dist/engine/meta-learning/runner.js +90 -0
- package/dist/engine/meta-learning/scope-promoter.d.ts +21 -0
- package/dist/engine/meta-learning/scope-promoter.js +84 -0
- package/dist/engine/meta-learning/session-quality-scorer.d.ts +61 -0
- package/dist/engine/meta-learning/session-quality-scorer.js +166 -0
- package/dist/engine/meta-learning/types.d.ts +114 -0
- package/dist/engine/meta-learning/types.js +43 -0
- package/dist/engine/solution-candidate.d.ts +30 -0
- package/dist/engine/solution-candidate.js +124 -0
- package/dist/engine/solution-fitness.d.ts +52 -0
- package/dist/engine/solution-fitness.js +95 -0
- package/dist/engine/solution-fixup.d.ts +30 -0
- package/dist/engine/solution-fixup.js +116 -0
- package/dist/engine/solution-format.d.ts +10 -2
- package/dist/engine/solution-format.js +287 -57
- package/dist/engine/solution-index.d.ts +1 -1
- package/dist/engine/solution-index.js +10 -0
- package/dist/engine/solution-matcher.d.ts +7 -1
- package/dist/engine/solution-matcher.js +137 -37
- package/dist/engine/solution-outcomes.d.ts +70 -0
- package/dist/engine/solution-outcomes.js +242 -0
- package/dist/engine/solution-quarantine.d.ts +36 -0
- package/dist/engine/solution-quarantine.js +172 -0
- package/dist/engine/solution-weakness.d.ts +45 -0
- package/dist/engine/solution-weakness.js +225 -0
- package/dist/engine/solution-writer.d.ts +5 -0
- package/dist/engine/solution-writer.js +18 -0
- package/dist/fgx.js +12 -8
- package/dist/hooks/context-guard.d.ts +5 -0
- package/dist/hooks/context-guard.js +118 -2
- package/dist/hooks/hooks-generator.d.ts +3 -0
- package/dist/hooks/hooks-generator.js +23 -6
- package/dist/hooks/keyword-detector.js +16 -100
- package/dist/hooks/post-tool-failure.js +7 -0
- package/dist/hooks/skill-injector.d.ts +4 -3
- package/dist/hooks/skill-injector.js +6 -4
- package/dist/hooks/solution-injector.js +20 -0
- package/dist/host/codex-adapter.d.ts +10 -0
- package/dist/host/codex-adapter.js +154 -0
- package/dist/mcp/solution-reader.d.ts +5 -5
- package/dist/mcp/solution-reader.js +34 -24
- package/dist/mcp/tools.js +8 -0
- package/dist/services/session.d.ts +19 -0
- package/dist/services/session.js +62 -0
- package/hooks/hooks.json +2 -2
- package/package.json +2 -1
- package/skills/architecture-decision/SKILL.md +113 -257
- package/skills/calibrate/SKILL.md +207 -0
- package/skills/code-review/SKILL.md +151 -178
- package/skills/compound/SKILL.md +126 -68
- package/skills/deep-interview/SKILL.md +210 -110
- package/skills/docker/SKILL.md +57 -179
- package/skills/forge-loop/SKILL.md +198 -0
- package/skills/learn/SKILL.md +216 -0
- package/skills/retro/SKILL.md +199 -0
- package/skills/ship/SKILL.md +259 -0
- package/agents/code-simplifier.md +0 -197
- package/agents/performance-reviewer.md +0 -172
- package/agents/qa-tester.md +0 -158
- package/agents/refactoring-expert.md +0 -168
- package/agents/scientist.md +0 -144
- package/agents/security-reviewer.md +0 -137
- package/agents/writer.md +0 -184
- package/commands/api-design.md +0 -268
- package/commands/ci-cd.md +0 -270
- package/commands/database.md +0 -263
- package/commands/debug-detective.md +0 -99
- package/commands/documentation.md +0 -276
- package/commands/ecomode.md +0 -51
- package/commands/frontend.md +0 -271
- package/commands/git-master.md +0 -90
- package/commands/incident-response.md +0 -292
- package/commands/migrate.md +0 -101
- package/commands/performance.md +0 -288
- package/commands/refactor.md +0 -105
- package/commands/security-review.md +0 -288
- package/commands/specify.md +0 -128
- package/commands/tdd.md +0 -183
- package/commands/testing-strategy.md +0 -265
- package/skills/api-design/SKILL.md +0 -262
- package/skills/ci-cd/SKILL.md +0 -264
- package/skills/database/SKILL.md +0 -257
- package/skills/debug-detective/SKILL.md +0 -95
- package/skills/documentation/SKILL.md +0 -270
- package/skills/ecomode/SKILL.md +0 -46
- package/skills/frontend/SKILL.md +0 -265
- package/skills/git-master/SKILL.md +0 -86
- package/skills/incident-response/SKILL.md +0 -286
- package/skills/migrate/SKILL.md +0 -96
- package/skills/performance/SKILL.md +0 -282
- package/skills/refactor/SKILL.md +0 -100
- package/skills/security-review/SKILL.md +0 -282
- package/skills/specify/SKILL.md +0 -122
- package/skills/tdd/SKILL.md +0 -178
- package/skills/testing-strategy/SKILL.md +0 -260
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import yaml from 'js-yaml';
|
|
4
|
+
import { DEFAULT_EVIDENCE } from './solution-format.js';
|
|
5
|
+
import { diagnoseFromRawContent } from './solution-quarantine.js';
|
|
6
|
+
import { createLogger } from '../core/logger.js';
|
|
7
|
+
const log = createLogger('solution-fixup');
|
|
8
|
+
/**
|
|
9
|
+
* Attempt to repair known-safe frontmatter defects.
|
|
10
|
+
*
|
|
11
|
+
* Handled defects (pre-0.3.1 schema drift, observed on 5 auto-extracted
|
|
12
|
+
* solutions from 2026-04-10):
|
|
13
|
+
* - `extractedBy` missing → add `extractedBy: auto`
|
|
14
|
+
* - `evidence` block missing → add `DEFAULT_EVIDENCE`
|
|
15
|
+
*
|
|
16
|
+
* All other validation errors (bad scope, non-numeric confidence, etc.)
|
|
17
|
+
* are surfaced in `remaining_errors` and the file is left untouched —
|
|
18
|
+
* those require human judgement, not a mechanical default.
|
|
19
|
+
*
|
|
20
|
+
* `dryRun: true` (default) reports what would change without writing.
|
|
21
|
+
*/
|
|
22
|
+
export function fixupSolutions(solutionsDir, opts = {}) {
|
|
23
|
+
const dryRun = opts.dryRun !== false;
|
|
24
|
+
const result = { scanned: 0, fixed: 0, untouched: 0, unfixable: 0, reports: [] };
|
|
25
|
+
if (!fs.existsSync(solutionsDir))
|
|
26
|
+
return result;
|
|
27
|
+
const files = fs.readdirSync(solutionsDir).filter((f) => f.endsWith('.md'));
|
|
28
|
+
for (const file of files) {
|
|
29
|
+
const filePath = path.join(solutionsDir, file);
|
|
30
|
+
result.scanned++;
|
|
31
|
+
let content;
|
|
32
|
+
try {
|
|
33
|
+
content = fs.readFileSync(filePath, 'utf-8');
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
result.unfixable++;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const errors = diagnoseFromRawContent(content);
|
|
40
|
+
if (errors.length === 0) {
|
|
41
|
+
result.untouched++;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const fix = tryFix(content, errors);
|
|
45
|
+
result.reports.push({
|
|
46
|
+
path: filePath,
|
|
47
|
+
changed: fix.changed,
|
|
48
|
+
added: fix.added,
|
|
49
|
+
remaining_errors: fix.remaining,
|
|
50
|
+
});
|
|
51
|
+
if (fix.changed && fix.remaining.length === 0) {
|
|
52
|
+
if (!dryRun) {
|
|
53
|
+
try {
|
|
54
|
+
fs.writeFileSync(filePath, fix.content);
|
|
55
|
+
log.debug(`fixed: ${filePath} (${fix.added.join(', ')})`);
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
log.debug(`write failed: ${filePath}: ${e instanceof Error ? e.message : String(e)}`);
|
|
59
|
+
result.unfixable++;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
result.fixed++;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
result.unfixable++;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
function tryFix(content, initialErrors) {
|
|
72
|
+
const trimmed = content.trimStart();
|
|
73
|
+
const added = [];
|
|
74
|
+
if (!trimmed.startsWith('---')) {
|
|
75
|
+
return { changed: false, added, remaining: initialErrors, content };
|
|
76
|
+
}
|
|
77
|
+
const endIdx = trimmed.indexOf('---', 3);
|
|
78
|
+
if (endIdx === -1) {
|
|
79
|
+
return { changed: false, added, remaining: initialErrors, content };
|
|
80
|
+
}
|
|
81
|
+
const leadingWs = content.slice(0, content.length - trimmed.length);
|
|
82
|
+
const fmRaw = trimmed.slice(3, endIdx);
|
|
83
|
+
const body = trimmed.slice(endIdx + 3);
|
|
84
|
+
let fm;
|
|
85
|
+
try {
|
|
86
|
+
const parsed = yaml.load(fmRaw, { schema: yaml.JSON_SCHEMA });
|
|
87
|
+
if (parsed == null || typeof parsed !== 'object') {
|
|
88
|
+
return { changed: false, added, remaining: initialErrors, content };
|
|
89
|
+
}
|
|
90
|
+
fm = parsed;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return { changed: false, added, remaining: initialErrors, content };
|
|
94
|
+
}
|
|
95
|
+
if (fm.extractedBy !== 'auto' && fm.extractedBy !== 'manual') {
|
|
96
|
+
fm.extractedBy = 'auto';
|
|
97
|
+
added.push('extractedBy: auto');
|
|
98
|
+
}
|
|
99
|
+
if (fm.evidence == null || typeof fm.evidence !== 'object') {
|
|
100
|
+
fm.evidence = { ...DEFAULT_EVIDENCE };
|
|
101
|
+
added.push('evidence: default');
|
|
102
|
+
}
|
|
103
|
+
if (fm.supersedes === undefined) {
|
|
104
|
+
fm.supersedes = null;
|
|
105
|
+
added.push('supersedes: null');
|
|
106
|
+
}
|
|
107
|
+
const newFmRaw = yaml.dump(fm, { lineWidth: 120, noRefs: true, sortKeys: false });
|
|
108
|
+
const rebuilt = `${leadingWs}---\n${newFmRaw}---${body}`;
|
|
109
|
+
const remaining = diagnoseFromRawContent(rebuilt);
|
|
110
|
+
return {
|
|
111
|
+
changed: added.length > 0,
|
|
112
|
+
added,
|
|
113
|
+
remaining,
|
|
114
|
+
content: rebuilt,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
@@ -13,7 +13,7 @@ export interface SolutionFrontmatter {
|
|
|
13
13
|
status: SolutionStatus;
|
|
14
14
|
confidence: number;
|
|
15
15
|
type: SolutionType;
|
|
16
|
-
scope: 'me' | 'team' | 'project';
|
|
16
|
+
scope: 'me' | 'team' | 'project' | 'universal';
|
|
17
17
|
tags: string[];
|
|
18
18
|
identifiers: string[];
|
|
19
19
|
evidence: SolutionEvidence;
|
|
@@ -33,7 +33,7 @@ export interface SolutionIndexEntry {
|
|
|
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
|
* Pre-expanded tag set, computed at index build time via the term normalizer.
|
|
@@ -60,6 +60,14 @@ export declare const DEFAULT_EVIDENCE: SolutionEvidence;
|
|
|
60
60
|
export declare function slugify(text: string): string;
|
|
61
61
|
/** Runtime type guard for SolutionFrontmatter */
|
|
62
62
|
export declare function validateFrontmatter(fm: unknown): fm is SolutionFrontmatter;
|
|
63
|
+
/**
|
|
64
|
+
* Return a list of validation errors for a parsed frontmatter object.
|
|
65
|
+
*
|
|
66
|
+
* Empty array = valid. Non-empty = each entry describes one missing/wrong
|
|
67
|
+
* field. Callers that only need a boolean should use `validateFrontmatter`.
|
|
68
|
+
* Slow path (quarantine logging) uses this to produce actionable diagnostics.
|
|
69
|
+
*/
|
|
70
|
+
export declare function diagnoseFrontmatter(fm: unknown): string[];
|
|
63
71
|
/** Parse YAML frontmatter from solution file content */
|
|
64
72
|
export declare function parseFrontmatterOnly(content: string): SolutionFrontmatter | null;
|
|
65
73
|
/** Parse a full V3 solution file into its components */
|
|
@@ -1,9 +1,26 @@
|
|
|
1
1
|
import yaml from 'js-yaml';
|
|
2
2
|
export const DEFAULT_EVIDENCE = {
|
|
3
|
-
injected: 0,
|
|
3
|
+
injected: 0,
|
|
4
|
+
reflected: 0,
|
|
5
|
+
negative: 0,
|
|
6
|
+
sessions: 0,
|
|
7
|
+
reExtracted: 0,
|
|
4
8
|
};
|
|
5
|
-
const VALID_STATUSES = [
|
|
6
|
-
|
|
9
|
+
const VALID_STATUSES = [
|
|
10
|
+
'experiment',
|
|
11
|
+
'candidate',
|
|
12
|
+
'verified',
|
|
13
|
+
'mature',
|
|
14
|
+
'retired',
|
|
15
|
+
];
|
|
16
|
+
const VALID_TYPES = [
|
|
17
|
+
'pattern',
|
|
18
|
+
'solution',
|
|
19
|
+
'decision',
|
|
20
|
+
'troubleshoot',
|
|
21
|
+
'anti-pattern',
|
|
22
|
+
'convention',
|
|
23
|
+
];
|
|
7
24
|
// ── Helpers ──
|
|
8
25
|
export function slugify(text) {
|
|
9
26
|
const slug = text
|
|
@@ -18,43 +35,58 @@ export function slugify(text) {
|
|
|
18
35
|
// ── Validation ──
|
|
19
36
|
/** Runtime type guard for SolutionFrontmatter */
|
|
20
37
|
export function validateFrontmatter(fm) {
|
|
21
|
-
|
|
22
|
-
|
|
38
|
+
return diagnoseFrontmatter(fm).length === 0;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Return a list of validation errors for a parsed frontmatter object.
|
|
42
|
+
*
|
|
43
|
+
* Empty array = valid. Non-empty = each entry describes one missing/wrong
|
|
44
|
+
* field. Callers that only need a boolean should use `validateFrontmatter`.
|
|
45
|
+
* Slow path (quarantine logging) uses this to produce actionable diagnostics.
|
|
46
|
+
*/
|
|
47
|
+
export function diagnoseFrontmatter(fm) {
|
|
48
|
+
const errors = [];
|
|
49
|
+
if (fm == null || typeof fm !== 'object') {
|
|
50
|
+
errors.push('frontmatter is not an object');
|
|
51
|
+
return errors;
|
|
52
|
+
}
|
|
23
53
|
const o = fm;
|
|
24
54
|
if (typeof o.name !== 'string')
|
|
25
|
-
|
|
55
|
+
errors.push('name: must be string');
|
|
26
56
|
if (typeof o.version !== 'number' || o.version <= 0)
|
|
27
|
-
|
|
57
|
+
errors.push('version: must be positive number');
|
|
28
58
|
if (typeof o.status !== 'string' || !VALID_STATUSES.includes(o.status))
|
|
29
|
-
|
|
59
|
+
errors.push(`status: must be one of ${VALID_STATUSES.join('|')}`);
|
|
30
60
|
if (typeof o.confidence !== 'number' || o.confidence < 0 || o.confidence > 1)
|
|
31
|
-
|
|
61
|
+
errors.push('confidence: must be number in [0,1]');
|
|
32
62
|
if (typeof o.type !== 'string' || !VALID_TYPES.includes(o.type))
|
|
33
|
-
|
|
34
|
-
if (o.scope !== 'me' && o.scope !== 'team' && o.scope !== 'project')
|
|
35
|
-
|
|
63
|
+
errors.push(`type: must be one of ${VALID_TYPES.join('|')}`);
|
|
64
|
+
if (o.scope !== 'me' && o.scope !== 'team' && o.scope !== 'project' && o.scope !== 'universal')
|
|
65
|
+
errors.push('scope: must be me|team|project|universal');
|
|
36
66
|
if (!Array.isArray(o.tags) || !o.tags.every((t) => typeof t === 'string'))
|
|
37
|
-
|
|
67
|
+
errors.push('tags: must be string[]');
|
|
38
68
|
if (!Array.isArray(o.identifiers) || !o.identifiers.every((t) => typeof t === 'string'))
|
|
39
|
-
|
|
69
|
+
errors.push('identifiers: must be string[]');
|
|
40
70
|
if (typeof o.created !== 'string')
|
|
41
|
-
|
|
71
|
+
errors.push('created: must be string');
|
|
42
72
|
if (typeof o.updated !== 'string')
|
|
43
|
-
|
|
73
|
+
errors.push('updated: must be string');
|
|
44
74
|
if (o.supersedes !== null && typeof o.supersedes !== 'string')
|
|
45
|
-
|
|
75
|
+
errors.push('supersedes: must be string or null');
|
|
46
76
|
if (o.extractedBy !== 'auto' && o.extractedBy !== 'manual')
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return false;
|
|
51
|
-
const ev = o.evidence;
|
|
52
|
-
const evFields = ['injected', 'reflected', 'negative', 'sessions', 'reExtracted'];
|
|
53
|
-
for (const f of evFields) {
|
|
54
|
-
if (typeof ev[f] !== 'number')
|
|
55
|
-
return false;
|
|
77
|
+
errors.push('extractedBy: missing or not auto|manual');
|
|
78
|
+
if (o.evidence == null || typeof o.evidence !== 'object') {
|
|
79
|
+
errors.push('evidence: block missing');
|
|
56
80
|
}
|
|
57
|
-
|
|
81
|
+
else {
|
|
82
|
+
const ev = o.evidence;
|
|
83
|
+
const evFields = ['injected', 'reflected', 'negative', 'sessions', 'reExtracted'];
|
|
84
|
+
for (const f of evFields) {
|
|
85
|
+
if (typeof ev[f] !== 'number')
|
|
86
|
+
errors.push(`evidence.${f}: must be number`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return errors;
|
|
58
90
|
}
|
|
59
91
|
// ── Parsing ──
|
|
60
92
|
/** Parse YAML frontmatter from solution file content */
|
|
@@ -122,7 +154,11 @@ export function parseSolutionV3(content) {
|
|
|
122
154
|
// ── Serialization ──
|
|
123
155
|
/** Serialize a SolutionV3 to a markdown string with YAML frontmatter */
|
|
124
156
|
export function serializeSolutionV3(solution) {
|
|
125
|
-
const yamlStr = yaml.dump(solution.frontmatter, {
|
|
157
|
+
const yamlStr = yaml.dump(solution.frontmatter, {
|
|
158
|
+
lineWidth: -1,
|
|
159
|
+
quotingType: '"',
|
|
160
|
+
schema: yaml.JSON_SCHEMA,
|
|
161
|
+
});
|
|
126
162
|
return `---\n${yamlStr}---\n\n## Context\n${solution.context}\n\n## Content\n${solution.content}\n`;
|
|
127
163
|
}
|
|
128
164
|
// ── Format Detection ──
|
|
@@ -149,34 +185,207 @@ export function isV1Format(content) {
|
|
|
149
185
|
/** 한국어 불용어 — 태그로 의미 없는 일반 단어 */
|
|
150
186
|
const KO_STOPWORDS = new Set([
|
|
151
187
|
// 일반 불용어
|
|
152
|
-
'적용',
|
|
153
|
-
'
|
|
154
|
-
'
|
|
155
|
-
'
|
|
156
|
-
'
|
|
157
|
-
'
|
|
158
|
-
'
|
|
159
|
-
'
|
|
188
|
+
'적용',
|
|
189
|
+
'패턴',
|
|
190
|
+
'모든',
|
|
191
|
+
'같은',
|
|
192
|
+
'발견',
|
|
193
|
+
'다른',
|
|
194
|
+
'사용',
|
|
195
|
+
'경우',
|
|
196
|
+
'위해',
|
|
197
|
+
'통해',
|
|
198
|
+
'대한',
|
|
199
|
+
'이후',
|
|
200
|
+
'때문',
|
|
201
|
+
'하는',
|
|
202
|
+
'있는',
|
|
203
|
+
'없는',
|
|
204
|
+
'되는',
|
|
205
|
+
'관련',
|
|
206
|
+
'해야',
|
|
207
|
+
'하고',
|
|
208
|
+
'있다',
|
|
209
|
+
'없다',
|
|
210
|
+
'한다',
|
|
211
|
+
'이런',
|
|
212
|
+
'그런',
|
|
213
|
+
'저런',
|
|
214
|
+
'매우',
|
|
215
|
+
'항상',
|
|
216
|
+
'모두',
|
|
217
|
+
'각각',
|
|
218
|
+
'대해',
|
|
219
|
+
'여러',
|
|
220
|
+
'시작',
|
|
221
|
+
'그것',
|
|
222
|
+
'이것',
|
|
223
|
+
'저것',
|
|
224
|
+
'아주',
|
|
225
|
+
'정말',
|
|
226
|
+
'너무',
|
|
227
|
+
'많이',
|
|
228
|
+
'자주',
|
|
229
|
+
'가장',
|
|
230
|
+
'먼저',
|
|
231
|
+
'이미',
|
|
232
|
+
'아직',
|
|
233
|
+
'그냥',
|
|
234
|
+
'바로',
|
|
235
|
+
'다시',
|
|
236
|
+
'함께',
|
|
237
|
+
'위한',
|
|
238
|
+
'따라',
|
|
239
|
+
'부분',
|
|
240
|
+
'전체',
|
|
241
|
+
'방법',
|
|
242
|
+
'내용',
|
|
243
|
+
'결과',
|
|
244
|
+
'문제',
|
|
245
|
+
'시점',
|
|
246
|
+
'설정',
|
|
247
|
+
'작업',
|
|
248
|
+
'확인',
|
|
249
|
+
'수행',
|
|
250
|
+
'처리',
|
|
251
|
+
'기본',
|
|
252
|
+
'추가',
|
|
253
|
+
'변경',
|
|
254
|
+
'제거',
|
|
255
|
+
'포함',
|
|
256
|
+
'생성',
|
|
257
|
+
'실행',
|
|
258
|
+
'완료',
|
|
259
|
+
'필요',
|
|
160
260
|
// 조사/어미/접속사 — Jaccard 분모 희석 방지
|
|
161
|
-
'에서',
|
|
162
|
-
'
|
|
163
|
-
'
|
|
164
|
-
'
|
|
165
|
-
'
|
|
166
|
-
'
|
|
261
|
+
'에서',
|
|
262
|
+
'으로',
|
|
263
|
+
'에게',
|
|
264
|
+
'에는',
|
|
265
|
+
'에도',
|
|
266
|
+
'까지',
|
|
267
|
+
'부터',
|
|
268
|
+
'보다',
|
|
269
|
+
'처럼',
|
|
270
|
+
'만큼',
|
|
271
|
+
'대로',
|
|
272
|
+
'밖에',
|
|
273
|
+
'뿐만',
|
|
274
|
+
'이나',
|
|
275
|
+
'이고',
|
|
276
|
+
'이면',
|
|
277
|
+
'이라',
|
|
278
|
+
'인데',
|
|
279
|
+
'했는데',
|
|
280
|
+
'됐는데',
|
|
281
|
+
'있으면',
|
|
282
|
+
'없으면',
|
|
283
|
+
'하면',
|
|
284
|
+
'되면',
|
|
285
|
+
'하지',
|
|
286
|
+
'되지',
|
|
287
|
+
'하며',
|
|
288
|
+
'되며',
|
|
289
|
+
'에서의',
|
|
290
|
+
'으로의',
|
|
291
|
+
'라는',
|
|
292
|
+
'라고',
|
|
293
|
+
'이라고',
|
|
294
|
+
'때문에',
|
|
295
|
+
'아니라',
|
|
296
|
+
'하지만',
|
|
297
|
+
'그러나',
|
|
298
|
+
'그래서',
|
|
299
|
+
'따라서',
|
|
300
|
+
'그리고',
|
|
301
|
+
'그러면',
|
|
302
|
+
'만약',
|
|
303
|
+
'비록',
|
|
304
|
+
'하여',
|
|
305
|
+
'않고',
|
|
306
|
+
'않은',
|
|
307
|
+
'않는',
|
|
308
|
+
'해서',
|
|
309
|
+
'해도',
|
|
310
|
+
'해야',
|
|
167
311
|
// 일반 동사/형용사 어간 — 의미 없는 고빈도 단어
|
|
168
|
-
'가능',
|
|
169
|
-
'
|
|
170
|
-
'
|
|
312
|
+
'가능',
|
|
313
|
+
'상태',
|
|
314
|
+
'이유',
|
|
315
|
+
'방지',
|
|
316
|
+
'의존',
|
|
317
|
+
'의존성',
|
|
318
|
+
'즉시',
|
|
319
|
+
'원칙',
|
|
320
|
+
'근거',
|
|
321
|
+
'수정',
|
|
322
|
+
'제안',
|
|
323
|
+
'기능',
|
|
324
|
+
'구현',
|
|
325
|
+
'구조',
|
|
326
|
+
'단계',
|
|
327
|
+
'목적',
|
|
328
|
+
'상황',
|
|
329
|
+
'조건',
|
|
330
|
+
'규칙',
|
|
331
|
+
'동작',
|
|
332
|
+
'활성',
|
|
333
|
+
'비활성',
|
|
334
|
+
'원래',
|
|
335
|
+
'현재',
|
|
336
|
+
'이전',
|
|
337
|
+
'다음',
|
|
338
|
+
'최종',
|
|
171
339
|
]);
|
|
172
340
|
/** 영어 불용어 */
|
|
173
341
|
const EN_STOPWORDS = new Set([
|
|
174
|
-
'the',
|
|
175
|
-
'
|
|
176
|
-
'
|
|
177
|
-
'
|
|
178
|
-
'
|
|
179
|
-
'
|
|
342
|
+
'the',
|
|
343
|
+
'and',
|
|
344
|
+
'for',
|
|
345
|
+
'that',
|
|
346
|
+
'this',
|
|
347
|
+
'with',
|
|
348
|
+
'from',
|
|
349
|
+
'are',
|
|
350
|
+
'was',
|
|
351
|
+
'were',
|
|
352
|
+
'been',
|
|
353
|
+
'have',
|
|
354
|
+
'has',
|
|
355
|
+
'had',
|
|
356
|
+
'not',
|
|
357
|
+
'but',
|
|
358
|
+
'all',
|
|
359
|
+
'can',
|
|
360
|
+
'will',
|
|
361
|
+
'use',
|
|
362
|
+
'used',
|
|
363
|
+
'using',
|
|
364
|
+
'when',
|
|
365
|
+
'each',
|
|
366
|
+
'which',
|
|
367
|
+
'their',
|
|
368
|
+
'also',
|
|
369
|
+
'into',
|
|
370
|
+
'more',
|
|
371
|
+
'some',
|
|
372
|
+
'than',
|
|
373
|
+
'other',
|
|
374
|
+
'should',
|
|
375
|
+
'would',
|
|
376
|
+
'could',
|
|
377
|
+
'about',
|
|
378
|
+
'after',
|
|
379
|
+
'before',
|
|
380
|
+
'between',
|
|
381
|
+
'does',
|
|
382
|
+
'only',
|
|
383
|
+
'across',
|
|
384
|
+
'just',
|
|
385
|
+
'detected',
|
|
386
|
+
'based',
|
|
387
|
+
'sessions',
|
|
388
|
+
'prompts',
|
|
180
389
|
]);
|
|
181
390
|
/** 한국어 일반 조사/어미 — strip 대상 (긴 것부터 매칭)
|
|
182
391
|
*
|
|
@@ -189,9 +398,32 @@ const EN_STOPWORDS = new Set([
|
|
|
189
398
|
* term-matcher의 `KO_VERBAL_SUFFIXES`에 따로 둔다.
|
|
190
399
|
*/
|
|
191
400
|
export const KO_SUFFIXES = [
|
|
192
|
-
'했습니다',
|
|
193
|
-
'
|
|
194
|
-
'
|
|
401
|
+
'했습니다',
|
|
402
|
+
'있습니다',
|
|
403
|
+
'합니다',
|
|
404
|
+
'입니다',
|
|
405
|
+
'됩니다',
|
|
406
|
+
'에서',
|
|
407
|
+
'까지',
|
|
408
|
+
'으로',
|
|
409
|
+
'하는',
|
|
410
|
+
'하고',
|
|
411
|
+
'했다',
|
|
412
|
+
'된다',
|
|
413
|
+
'한다',
|
|
414
|
+
'을',
|
|
415
|
+
'를',
|
|
416
|
+
'이',
|
|
417
|
+
'가',
|
|
418
|
+
'은',
|
|
419
|
+
'는',
|
|
420
|
+
'의',
|
|
421
|
+
'에',
|
|
422
|
+
'와',
|
|
423
|
+
'과',
|
|
424
|
+
'도',
|
|
425
|
+
'만',
|
|
426
|
+
'로',
|
|
195
427
|
];
|
|
196
428
|
export function stripKoSuffix(word) {
|
|
197
429
|
for (const suffix of KO_SUFFIXES) {
|
|
@@ -220,9 +452,7 @@ const MAX_TAGS = 8;
|
|
|
220
452
|
* a fresh `ROUND3_BASELINE` measurement on every downstream PR.
|
|
221
453
|
*/
|
|
222
454
|
export function extractTags(text) {
|
|
223
|
-
const cleaned = text
|
|
224
|
-
.toLowerCase()
|
|
225
|
-
.replace(/[^가-힣a-z0-9\s]/g, ' ');
|
|
455
|
+
const cleaned = text.toLowerCase().replace(/[^가-힣a-z0-9\s]/g, ' ');
|
|
226
456
|
const words = cleaned.split(/\s+/).filter(Boolean);
|
|
227
457
|
const freq = new Map();
|
|
228
458
|
for (const w of words) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { SolutionIndexEntry } from './solution-format.js';
|
|
2
2
|
export interface SolutionDirConfig {
|
|
3
3
|
dir: string;
|
|
4
|
-
scope: 'me' | 'team' | 'project';
|
|
4
|
+
scope: 'me' | 'team' | 'project' | 'universal';
|
|
5
5
|
}
|
|
6
6
|
export interface SolutionIndex {
|
|
7
7
|
entries: SolutionIndexEntry[];
|
|
@@ -5,6 +5,7 @@ import { defaultNormalizer } from './term-normalizer.js';
|
|
|
5
5
|
import { withFileLockSync } from '../hooks/shared/file-lock.js';
|
|
6
6
|
import { atomicWriteText } from '../hooks/shared/atomic-write.js';
|
|
7
7
|
import { createLogger } from '../core/logger.js';
|
|
8
|
+
import { recordQuarantine, diagnoseFromRawContent } from './solution-quarantine.js';
|
|
8
9
|
const log = createLogger('solution-index');
|
|
9
10
|
/**
|
|
10
11
|
* Cache keyed by an order-preserving directory signature.
|
|
@@ -155,6 +156,15 @@ function buildIndex(dirs) {
|
|
|
155
156
|
const fm = parseFrontmatterOnly(content);
|
|
156
157
|
if (!fm) {
|
|
157
158
|
droppedMalformed++;
|
|
159
|
+
// Slow-path diagnosis: re-parse YAML to produce actionable errors,
|
|
160
|
+
// then persist to ~/.forgen/state/solution-quarantine.jsonl so the
|
|
161
|
+
// file is visible to `forgen doctor` instead of silently dead.
|
|
162
|
+
// Best-effort: quarantine writes must never throw.
|
|
163
|
+
try {
|
|
164
|
+
const errors = diagnoseFromRawContent(content);
|
|
165
|
+
recordQuarantine(filePath, errors);
|
|
166
|
+
}
|
|
167
|
+
catch { /* ignore */ }
|
|
158
168
|
log.debug(`dropped (malformed frontmatter): ${filePath}`);
|
|
159
169
|
continue;
|
|
160
170
|
}
|
|
@@ -32,7 +32,7 @@ export declare function tagWeight(tag: string): number;
|
|
|
32
32
|
export interface SolutionMatch {
|
|
33
33
|
name: string;
|
|
34
34
|
path: string;
|
|
35
|
-
scope: 'me' | 'team' | 'project';
|
|
35
|
+
scope: 'me' | 'team' | 'project' | 'universal';
|
|
36
36
|
relevance: number;
|
|
37
37
|
summary: string;
|
|
38
38
|
status: SolutionStatus;
|
|
@@ -65,6 +65,12 @@ export interface CalculateRelevanceOptions {
|
|
|
65
65
|
solutionTagsExpanded?: string[];
|
|
66
66
|
/** Average document (solution) tag count for BM25 normalization. Defaults to 6. */
|
|
67
67
|
avgDocLength?: number;
|
|
68
|
+
/** Meta-learning: dynamic ensemble weights (sum must equal 1.0). Defaults to {tfidf:0.5, bm25:0.3, bigram:0.2}. */
|
|
69
|
+
ensembleWeights?: {
|
|
70
|
+
tfidf: number;
|
|
71
|
+
bm25: number;
|
|
72
|
+
bigram: number;
|
|
73
|
+
};
|
|
68
74
|
}
|
|
69
75
|
export declare function calculateRelevance(promptTags: string[], solutionTags: string[], confidence: number, options?: CalculateRelevanceOptions): {
|
|
70
76
|
relevance: number;
|