@wooojin/forgen 0.4.9 → 0.4.11
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/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +46 -0
- package/README.md +33 -1
- package/assets/claude/agents/forgen-verify.md +65 -0
- package/assets/claude/workflows/compound-extract.js +136 -0
- package/assets/claude/workflows/evidence-gate-audit.js +107 -0
- package/assets/shared/hook-registry.json +1 -0
- package/dist/checks/_shared/meta-guard-dispatch.d.ts +38 -0
- package/dist/checks/_shared/meta-guard-dispatch.js +80 -0
- package/dist/checks/_shared/text-sanitizer.js +15 -0
- package/dist/cli.js +65 -2
- package/dist/core/changelog-cli.d.ts +7 -0
- package/dist/core/changelog-cli.js +100 -0
- package/dist/core/doctor.d.ts +3 -0
- package/dist/core/doctor.js +75 -1
- package/dist/core/effort-advisory.d.ts +23 -0
- package/dist/core/effort-advisory.js +29 -0
- package/dist/core/explain-cli.d.ts +6 -0
- package/dist/core/explain-cli.js +99 -0
- package/dist/core/git-stats.d.ts +23 -0
- package/dist/core/git-stats.js +50 -0
- package/dist/core/health-cli.d.ts +23 -0
- package/dist/core/health-cli.js +86 -0
- package/dist/core/inspect-cli.js +60 -1
- package/dist/core/probe-workflow-cli.d.ts +72 -0
- package/dist/core/probe-workflow-cli.js +282 -0
- package/dist/core/regress-map-cli.d.ts +7 -0
- package/dist/core/regress-map-cli.js +59 -0
- package/dist/core/stats-cli.d.ts +22 -9
- package/dist/core/stats-cli.js +149 -0
- package/dist/core/watch-cli.d.ts +7 -0
- package/dist/core/watch-cli.js +185 -0
- package/dist/core/workflows-cli.d.ts +26 -0
- package/dist/core/workflows-cli.js +120 -0
- package/dist/engine/compound-export.d.ts +12 -0
- package/dist/engine/compound-export.js +136 -14
- package/dist/engine/compound-extractor.d.ts +12 -43
- package/dist/engine/compound-extractor.js +27 -756
- package/dist/engine/extraction-diff.d.ts +11 -0
- package/dist/engine/extraction-diff.js +105 -0
- package/dist/engine/extraction-gates.d.ts +37 -0
- package/dist/engine/extraction-gates.js +100 -0
- package/dist/engine/extraction-git.d.ts +20 -0
- package/dist/engine/extraction-git.js +75 -0
- package/dist/engine/extraction-persistence.d.ts +27 -0
- package/dist/engine/extraction-persistence.js +140 -0
- package/dist/engine/extraction-session.d.ts +26 -0
- package/dist/engine/extraction-session.js +230 -0
- package/dist/engine/lifecycle/types.d.ts +1 -1
- package/dist/engine/meta-learning/matcher-weight-loader.d.ts +16 -0
- package/dist/engine/meta-learning/matcher-weight-loader.js +45 -0
- package/dist/engine/precision-guards.d.ts +14 -0
- package/dist/engine/precision-guards.js +39 -0
- package/dist/engine/ranking-pipeline.d.ts +45 -0
- package/dist/engine/ranking-pipeline.js +66 -0
- package/dist/engine/relevance-scorer.d.ts +43 -0
- package/dist/engine/relevance-scorer.js +81 -0
- package/dist/engine/scoring-algorithms.d.ts +31 -0
- package/dist/engine/scoring-algorithms.js +109 -0
- package/dist/engine/solution-matcher-eval.d.ts +97 -0
- package/dist/engine/solution-matcher-eval.js +122 -0
- package/dist/engine/solution-matcher.d.ts +21 -380
- package/dist/engine/solution-matcher.js +27 -828
- package/dist/fgx.d.ts +4 -1
- package/dist/fgx.js +46 -10
- package/dist/hooks/notepad-injector.js +7 -0
- package/dist/hooks/post-tool-use.js +8 -1
- package/dist/hooks/shared/preflight-check.d.ts +15 -0
- package/dist/hooks/shared/preflight-check.js +51 -0
- package/dist/hooks/stop-guard.js +19 -60
- package/dist/hooks/subagent-stop-guard.d.ts +23 -0
- package/dist/hooks/subagent-stop-guard.js +158 -0
- package/dist/hooks/subagent-tracker.d.ts +36 -3
- package/dist/hooks/subagent-tracker.js +86 -39
- package/dist/store/evidence-store.js +9 -2
- package/hooks/hooks.json +6 -1
- package/package.json +7 -7
- package/plugin.json +1 -1
- package/scripts/postinstall.js +10 -7
|
@@ -152,14 +152,116 @@ export function importKnowledge(archivePath) {
|
|
|
152
152
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Selective export — filter to specific categories.
|
|
157
|
+
* `--only solutions,rules` exports only those two directories.
|
|
158
|
+
*/
|
|
159
|
+
export function exportKnowledgeSelective(categories, outputPath) {
|
|
160
|
+
const date = new Date().toISOString().split('T')[0];
|
|
161
|
+
const resolved = outputPath ?? path.join(process.cwd(), `forgen-knowledge-${date}.tar.gz`);
|
|
162
|
+
const validCategories = categories.filter(c => KNOWLEDGE_DIRS.includes(c));
|
|
163
|
+
if (validCategories.length === 0) {
|
|
164
|
+
throw new Error(`No valid categories. Choose from: ${KNOWLEDGE_DIRS.join(', ')}`);
|
|
165
|
+
}
|
|
166
|
+
const counts = {};
|
|
167
|
+
const existingDirs = [];
|
|
168
|
+
for (const name of validCategories) {
|
|
169
|
+
const dir = path.join(ME_DIR, name);
|
|
170
|
+
const count = countFiles(dir);
|
|
171
|
+
counts[name] = count;
|
|
172
|
+
if (fs.existsSync(dir)) {
|
|
173
|
+
existingDirs.push(name);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const totalFiles = Object.values(counts).reduce((a, b) => a + b, 0);
|
|
177
|
+
if (existingDirs.length === 0) {
|
|
178
|
+
throw new Error('No knowledge directories found to export.');
|
|
179
|
+
}
|
|
180
|
+
const outDir = path.dirname(resolved);
|
|
181
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
182
|
+
execFileSync('tar', ['czf', resolved, ...existingDirs], {
|
|
183
|
+
cwd: ME_DIR,
|
|
184
|
+
timeout: 30000,
|
|
185
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
186
|
+
});
|
|
187
|
+
return { outputPath: resolved, counts, totalFiles };
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Import with merge mode — updates existing files instead of skipping them.
|
|
191
|
+
* Newer files in the archive overwrite older local files.
|
|
192
|
+
*/
|
|
193
|
+
export function importKnowledgeMerge(archivePath) {
|
|
194
|
+
if (!fs.existsSync(archivePath)) {
|
|
195
|
+
throw new Error(`Archive not found: ${archivePath}`);
|
|
196
|
+
}
|
|
197
|
+
const listOutput = execFileSync('tar', ['tzf', archivePath], {
|
|
198
|
+
timeout: 30000,
|
|
199
|
+
encoding: 'utf-8',
|
|
200
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
201
|
+
});
|
|
202
|
+
const archiveFiles = listOutput
|
|
203
|
+
.split('\n')
|
|
204
|
+
.map(f => f.trim())
|
|
205
|
+
.filter(f => f && !f.endsWith('/'));
|
|
206
|
+
const tmpDir = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'forgen-merge-'));
|
|
207
|
+
try {
|
|
208
|
+
execFileSync('tar', ['xzf', archivePath, '-C', tmpDir], {
|
|
209
|
+
timeout: 30000,
|
|
210
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
211
|
+
});
|
|
212
|
+
const result = { imported: 0, skipped: 0, merged: 0, details: [] };
|
|
213
|
+
const meDirCanon = path.resolve(ME_DIR) + path.sep;
|
|
214
|
+
const tmpDirCanon = path.resolve(tmpDir) + path.sep;
|
|
215
|
+
for (const relFile of archiveFiles) {
|
|
216
|
+
const srcPath = path.resolve(path.join(tmpDir, relFile));
|
|
217
|
+
const destPath = path.resolve(path.join(ME_DIR, relFile));
|
|
218
|
+
if (!isPathInside(meDirCanon, destPath) || !isPathInside(tmpDirCanon, srcPath)) {
|
|
219
|
+
result.skipped++;
|
|
220
|
+
result.details.push({ file: relFile, action: 'skipped' });
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (fs.existsSync(destPath)) {
|
|
224
|
+
const srcMtime = fs.statSync(srcPath).mtimeMs;
|
|
225
|
+
const destMtime = fs.statSync(destPath).mtimeMs;
|
|
226
|
+
if (srcMtime > destMtime) {
|
|
227
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
228
|
+
fs.copyFileSync(srcPath, destPath);
|
|
229
|
+
result.merged++;
|
|
230
|
+
result.details.push({ file: relFile, action: 'imported' });
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
result.skipped++;
|
|
234
|
+
result.details.push({ file: relFile, action: 'skipped' });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
239
|
+
fs.copyFileSync(srcPath, destPath);
|
|
240
|
+
result.imported++;
|
|
241
|
+
result.details.push({ file: relFile, action: 'imported' });
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return result;
|
|
245
|
+
}
|
|
246
|
+
finally {
|
|
247
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
155
250
|
/** CLI handler: forgen compound export */
|
|
156
251
|
export async function handleExport(args) {
|
|
157
252
|
const outputIdx = args.indexOf('--output');
|
|
158
253
|
const outputPath = outputIdx !== -1 ? args[outputIdx + 1] : undefined;
|
|
254
|
+
const onlyIdx = args.indexOf('--only');
|
|
255
|
+
const onlyCategories = onlyIdx !== -1 ? (args[onlyIdx + 1] ?? '').split(',').filter(Boolean) : [];
|
|
159
256
|
try {
|
|
160
|
-
const result =
|
|
257
|
+
const result = onlyCategories.length > 0
|
|
258
|
+
? exportKnowledgeSelective(onlyCategories, outputPath)
|
|
259
|
+
: exportKnowledge(outputPath);
|
|
161
260
|
console.log('\n Compound Knowledge Export\n');
|
|
162
261
|
console.log(` Output: ${result.outputPath}`);
|
|
262
|
+
if (onlyCategories.length > 0) {
|
|
263
|
+
console.log(` Filter: ${onlyCategories.join(', ')}`);
|
|
264
|
+
}
|
|
163
265
|
console.log();
|
|
164
266
|
for (const [category, count] of Object.entries(result.counts)) {
|
|
165
267
|
console.log(` ${category}: ${count} files`);
|
|
@@ -173,23 +275,43 @@ export async function handleExport(args) {
|
|
|
173
275
|
}
|
|
174
276
|
/** CLI handler: forgen compound import */
|
|
175
277
|
export async function handleImport(args) {
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
278
|
+
const filteredArgs = args.filter(a => !a.startsWith('--'));
|
|
279
|
+
const archivePath = filteredArgs[0];
|
|
280
|
+
const mergeMode = args.includes('--merge');
|
|
281
|
+
if (!archivePath) {
|
|
282
|
+
console.log(' Usage: forgen compound import <path-to-archive> [--merge]\n');
|
|
283
|
+
console.log(' --merge Update existing files if archive version is newer');
|
|
179
284
|
return;
|
|
180
285
|
}
|
|
181
286
|
try {
|
|
182
287
|
const resolved = path.resolve(archivePath);
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
console.log();
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
288
|
+
if (mergeMode) {
|
|
289
|
+
const result = importKnowledgeMerge(resolved);
|
|
290
|
+
console.log('\n Compound Knowledge Import (merge mode)\n');
|
|
291
|
+
console.log(` Archive: ${resolved}`);
|
|
292
|
+
console.log(` New: ${result.imported} files`);
|
|
293
|
+
console.log(` Merged: ${result.merged} files (newer version overwrote local)`);
|
|
294
|
+
console.log(` Skipped: ${result.skipped} files (local is same or newer)`);
|
|
295
|
+
if (result.details.length > 0 && result.details.length <= 20) {
|
|
296
|
+
console.log();
|
|
297
|
+
for (const d of result.details) {
|
|
298
|
+
const icon = d.action === 'imported' ? '+' : '-';
|
|
299
|
+
console.log(` ${icon} ${d.file}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
const result = importKnowledge(resolved);
|
|
305
|
+
console.log('\n Compound Knowledge Import\n');
|
|
306
|
+
console.log(` Archive: ${resolved}`);
|
|
307
|
+
console.log(` Imported: ${result.imported} new files`);
|
|
308
|
+
console.log(` Skipped: ${result.skipped} existing files`);
|
|
309
|
+
if (result.details.length > 0 && result.details.length <= 20) {
|
|
310
|
+
console.log();
|
|
311
|
+
for (const d of result.details) {
|
|
312
|
+
const icon = d.action === 'imported' ? '+' : '-';
|
|
313
|
+
console.log(` ${icon} ${d.file}`);
|
|
314
|
+
}
|
|
193
315
|
}
|
|
194
316
|
}
|
|
195
317
|
console.log();
|
|
@@ -1,54 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Forgen — Compound Knowledge Extractor
|
|
2
|
+
* Forgen — Compound Knowledge Extractor (facade)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Orchestrates extraction pipeline: git analysis → quality gates →
|
|
5
|
+
* pattern extraction → persistence. Re-exports from decomposed modules.
|
|
6
6
|
*
|
|
7
|
-
* Module
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* - Lines 275-395: extractFromSessionContext — prompt/write history analysis (actions, hotspots, tech)
|
|
14
|
-
* - Lines 396-475: saveExtractedSolution, updateReExtractedCounter — solution persistence
|
|
15
|
-
* - Lines 477-555: runExtraction — main entry point orchestrating gates + extraction + state
|
|
16
|
-
* - Lines 557-634: processExtractionResults, isExtractionPaused, pauseExtraction, resumeExtraction
|
|
7
|
+
* Module layout (post-decomposition):
|
|
8
|
+
* extraction-git.ts — getNewCommits, getCommitMessages, getGitDiff, getDiffStats
|
|
9
|
+
* extraction-gates.ts — gate0-gate3, gateTrivial, evaluateExtractedSolution, ExtractedSolution
|
|
10
|
+
* extraction-diff.ts — extractFromDiff, findCommonPrefix
|
|
11
|
+
* extraction-session.ts — extractFromSessionContext, loadClaudeProjectSessionContext
|
|
12
|
+
* extraction-persistence.ts — saveExtractedSolution, updateReExtractedCounter, LastExtraction
|
|
17
13
|
*/
|
|
18
|
-
import type {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
type: SolutionType;
|
|
22
|
-
tags: string[];
|
|
23
|
-
identifiers: string[];
|
|
24
|
-
context: string;
|
|
25
|
-
content: string;
|
|
26
|
-
}
|
|
27
|
-
interface WriteContextEntry {
|
|
28
|
-
filePath: string;
|
|
29
|
-
contentSnippet: string;
|
|
30
|
-
fileExtension: string;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Load Claude session prompts + writes correlated to `cwd`.
|
|
34
|
-
*
|
|
35
|
-
* Exported primarily for test assertions (the `claude-session-context`
|
|
36
|
-
* tests need to verify that correlation picks the right project's
|
|
37
|
-
* sessions and ignores unrelated ones). Before C4 the tests could
|
|
38
|
-
* observe this indirectly via the now-removed `recurring-task-pattern`
|
|
39
|
-
* extractor; now they check this loader directly. Not intended for
|
|
40
|
-
* production callers outside the extractor pipeline.
|
|
41
|
-
*/
|
|
42
|
-
export declare function loadClaudeProjectSessionContext(cwd: string, lastExtractedAt: string): {
|
|
43
|
-
prompts: string[];
|
|
44
|
-
writes: WriteContextEntry[];
|
|
45
|
-
};
|
|
14
|
+
import type { ExtractedSolution } from './extraction-gates.js';
|
|
15
|
+
export { loadClaudeProjectSessionContext } from './extraction-session.js';
|
|
16
|
+
export type { ExtractedSolution } from './extraction-gates.js';
|
|
46
17
|
export declare function previewExtraction(cwd: string): Promise<{
|
|
47
18
|
preview: ExtractedSolution[];
|
|
48
19
|
skipped: string[];
|
|
49
20
|
reason?: string;
|
|
50
21
|
}>;
|
|
51
|
-
/** Main extraction function — called from SessionStart or CLI */
|
|
52
22
|
export declare function runExtraction(cwd: string, sessionId: string): Promise<{
|
|
53
23
|
extracted: string[];
|
|
54
24
|
skipped: string[];
|
|
@@ -65,4 +35,3 @@ export declare function isExtractionPaused(): boolean;
|
|
|
65
35
|
export declare function pauseExtraction(): void;
|
|
66
36
|
/** Resume auto-extraction */
|
|
67
37
|
export declare function resumeExtraction(): void;
|
|
68
|
-
export {};
|