@t3lnet/sceneforge 1.0.13 → 1.0.15
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/context/context-builder.ts +56 -0
- package/context/template-loader.ts +20 -2
- package/context/tests/context-builder.test.ts +15 -2
- package/dist/index.cjs +65 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +65 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- /package/dist/templates/{templates/base → base}/actions-reference.md +0 -0
- /package/dist/templates/{templates/base → base}/cli-reference.md +0 -0
- /package/dist/templates/{templates/base → base}/project-overview.md +0 -0
- /package/dist/templates/{templates/base → base}/selectors-guide.md +0 -0
- /package/dist/templates/{templates/base → base}/yaml-schema.md +0 -0
- /package/dist/templates/{templates/skills → skills}/balance-timing.md +0 -0
- /package/dist/templates/{templates/skills → skills}/debug-selector.md +0 -0
- /package/dist/templates/{templates/skills → skills}/generate-actions.md +0 -0
- /package/dist/templates/{templates/skills → skills}/optimize-demo.md +0 -0
- /package/dist/templates/{templates/skills → skills}/review-demo-yaml.md +0 -0
- /package/dist/templates/{templates/skills → skills}/write-step-script.md +0 -0
- /package/dist/templates/{templates/stages → stages}/stage1-actions.md +0 -0
- /package/dist/templates/{templates/stages → stages}/stage2-scripts.md +0 -0
- /package/dist/templates/{templates/stages → stages}/stage3-balancing.md +0 -0
- /package/dist/templates/{templates/stages → stages}/stage4-rebalancing.md +0 -0
|
@@ -107,6 +107,42 @@ async function mergeWithExisting(
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
async function writeSplitPointer(
|
|
111
|
+
tool: TargetTool,
|
|
112
|
+
stageNames: Stage[],
|
|
113
|
+
outputDir: string
|
|
114
|
+
): Promise<{ filePath: string; merged: boolean }> {
|
|
115
|
+
const config = getToolConfig(tool);
|
|
116
|
+
const stageLines = stageNames.map((stage) => {
|
|
117
|
+
const friendly = formatStageName(stage);
|
|
118
|
+
const stageFile = `${config.splitFilePrefix}${getStageFileName(stage)}${config.fileExtension}`;
|
|
119
|
+
const relativePath = path.posix.join(config.splitDir, stageFile);
|
|
120
|
+
return `- ${friendly}: ${relativePath}`;
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const pointerBody = [
|
|
124
|
+
"SceneForge context for this tool is split across the following stage files:",
|
|
125
|
+
"",
|
|
126
|
+
...stageLines,
|
|
127
|
+
"",
|
|
128
|
+
`Open the stage file that matches the work you're doing, or run "npx sceneforge context preview --target ${tool} --stage <stage>" to inspect a specific stage.`,
|
|
129
|
+
].join("\n");
|
|
130
|
+
|
|
131
|
+
const formattedPointer = formatForTool(tool, pointerBody, {
|
|
132
|
+
includeToolHeader: true,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const combinedPath = path.join(outputDir, config.combinedFile);
|
|
136
|
+
await fs.mkdir(path.dirname(combinedPath), { recursive: true });
|
|
137
|
+
const { content: finalContent, merged } = await mergeWithExisting(
|
|
138
|
+
combinedPath,
|
|
139
|
+
formattedPointer
|
|
140
|
+
);
|
|
141
|
+
await fs.writeFile(combinedPath, finalContent, "utf-8");
|
|
142
|
+
|
|
143
|
+
return { filePath: combinedPath, merged };
|
|
144
|
+
}
|
|
145
|
+
|
|
110
146
|
/**
|
|
111
147
|
* Build context content for a specific tool and stage.
|
|
112
148
|
*/
|
|
@@ -210,6 +246,7 @@ export async function deployContext(
|
|
|
210
246
|
}
|
|
211
247
|
} else {
|
|
212
248
|
// Generate split files for each stage
|
|
249
|
+
const deployedStages: Stage[] = [];
|
|
213
250
|
for (const stg of stages) {
|
|
214
251
|
try {
|
|
215
252
|
const content = await buildContext(tool, stg, variables);
|
|
@@ -240,6 +277,7 @@ export async function deployContext(
|
|
|
240
277
|
if (merged) {
|
|
241
278
|
console.log(` [merged] ${path.relative(outputDir, absolutePath)}`);
|
|
242
279
|
}
|
|
280
|
+
deployedStages.push(stg);
|
|
243
281
|
} catch (error) {
|
|
244
282
|
results.push({
|
|
245
283
|
tool,
|
|
@@ -250,6 +288,24 @@ export async function deployContext(
|
|
|
250
288
|
});
|
|
251
289
|
}
|
|
252
290
|
}
|
|
291
|
+
if (deployedStages.length > 0) {
|
|
292
|
+
const pointerResult = await writeSplitPointer(
|
|
293
|
+
tool,
|
|
294
|
+
deployedStages,
|
|
295
|
+
outputDir
|
|
296
|
+
);
|
|
297
|
+
results.push({
|
|
298
|
+
tool,
|
|
299
|
+
filePath: pointerResult.filePath,
|
|
300
|
+
created: true,
|
|
301
|
+
skipped: false,
|
|
302
|
+
});
|
|
303
|
+
if (pointerResult.merged) {
|
|
304
|
+
console.log(
|
|
305
|
+
` [merged] ${path.relative(outputDir, pointerResult.filePath)}`
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
253
309
|
}
|
|
254
310
|
}
|
|
255
311
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Loads markdown templates and supports variable interpolation.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { existsSync } from "fs";
|
|
6
7
|
import * as fs from "fs/promises";
|
|
7
8
|
import * as path from "path";
|
|
8
9
|
import { fileURLToPath } from "url";
|
|
@@ -24,7 +25,22 @@ export interface LoadedTemplate {
|
|
|
24
25
|
* Get the templates directory path.
|
|
25
26
|
*/
|
|
26
27
|
function getTemplatesDir(): string {
|
|
27
|
-
|
|
28
|
+
const candidates = [
|
|
29
|
+
// Standard dist layout: dist/templates/{base,stages,skills}
|
|
30
|
+
path.join(__dirname, "templates"),
|
|
31
|
+
// Nested layout if templates were copied into an existing dist/templates
|
|
32
|
+
path.join(__dirname, "templates", "templates"),
|
|
33
|
+
// Source layout when templates are shipped under context/templates
|
|
34
|
+
path.join(__dirname, "..", "context", "templates"),
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
for (const candidate of candidates) {
|
|
38
|
+
if (existsSync(path.join(candidate, "base"))) {
|
|
39
|
+
return candidate;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return candidates[0];
|
|
28
44
|
}
|
|
29
45
|
|
|
30
46
|
/**
|
|
@@ -68,7 +84,9 @@ export async function loadTemplatesByCategory(
|
|
|
68
84
|
|
|
69
85
|
return templates;
|
|
70
86
|
} catch (error) {
|
|
71
|
-
throw new Error(
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Failed to load templates from ${category} in ${templatesDir}: ${error}`
|
|
89
|
+
);
|
|
72
90
|
}
|
|
73
91
|
}
|
|
74
92
|
|
|
@@ -118,7 +118,7 @@ describe("context-builder", () => {
|
|
|
118
118
|
outputDir: tempDir,
|
|
119
119
|
});
|
|
120
120
|
|
|
121
|
-
expect(results.length).toBe(
|
|
121
|
+
expect(results.length).toBe(2);
|
|
122
122
|
expect(results[0].created).toBe(true);
|
|
123
123
|
|
|
124
124
|
const splitDir = path.join(tempDir, ".claude/rules");
|
|
@@ -134,9 +134,22 @@ describe("context-builder", () => {
|
|
|
134
134
|
outputDir: tempDir,
|
|
135
135
|
});
|
|
136
136
|
|
|
137
|
-
expect(results.length).toBe(
|
|
137
|
+
expect(results.length).toBe(5);
|
|
138
138
|
expect(results.every((r) => r.created)).toBe(true);
|
|
139
139
|
});
|
|
140
|
+
|
|
141
|
+
it("updates combined file when split files are deployed", async () => {
|
|
142
|
+
await deployContext({
|
|
143
|
+
target: "claude",
|
|
144
|
+
stage: "actions",
|
|
145
|
+
format: "split",
|
|
146
|
+
outputDir: tempDir,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const claudeFile = await fs.readFile(path.join(tempDir, "CLAUDE.md"), "utf-8");
|
|
150
|
+
expect(claudeFile).toContain("split across the following stage files");
|
|
151
|
+
expect(claudeFile).toContain(".claude/rules/stage1-actions.md");
|
|
152
|
+
});
|
|
140
153
|
});
|
|
141
154
|
|
|
142
155
|
describe("listDeployedContext", () => {
|
package/dist/index.cjs
CHANGED
|
@@ -2483,6 +2483,7 @@ async function discoverDemos(demoDir) {
|
|
|
2483
2483
|
}
|
|
2484
2484
|
|
|
2485
2485
|
// context/template-loader.ts
|
|
2486
|
+
var import_fs = require("fs");
|
|
2486
2487
|
var fs5 = __toESM(require("fs/promises"), 1);
|
|
2487
2488
|
var path5 = __toESM(require("path"), 1);
|
|
2488
2489
|
var import_url = require("url");
|
|
@@ -2490,7 +2491,20 @@ var import_meta = {};
|
|
|
2490
2491
|
var __filename = (0, import_url.fileURLToPath)(import_meta.url);
|
|
2491
2492
|
var __dirname = path5.dirname(__filename);
|
|
2492
2493
|
function getTemplatesDir() {
|
|
2493
|
-
|
|
2494
|
+
const candidates = [
|
|
2495
|
+
// Standard dist layout: dist/templates/{base,stages,skills}
|
|
2496
|
+
path5.join(__dirname, "templates"),
|
|
2497
|
+
// Nested layout if templates were copied into an existing dist/templates
|
|
2498
|
+
path5.join(__dirname, "templates", "templates"),
|
|
2499
|
+
// Source layout when templates are shipped under context/templates
|
|
2500
|
+
path5.join(__dirname, "..", "context", "templates")
|
|
2501
|
+
];
|
|
2502
|
+
for (const candidate of candidates) {
|
|
2503
|
+
if ((0, import_fs.existsSync)(path5.join(candidate, "base"))) {
|
|
2504
|
+
return candidate;
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
return candidates[0];
|
|
2494
2508
|
}
|
|
2495
2509
|
async function loadTemplate(category, name) {
|
|
2496
2510
|
const templatesDir = getTemplatesDir();
|
|
@@ -2517,7 +2531,9 @@ async function loadTemplatesByCategory(category) {
|
|
|
2517
2531
|
}
|
|
2518
2532
|
return templates;
|
|
2519
2533
|
} catch (error) {
|
|
2520
|
-
throw new Error(
|
|
2534
|
+
throw new Error(
|
|
2535
|
+
`Failed to load templates from ${category} in ${templatesDir}: ${error}`
|
|
2536
|
+
);
|
|
2521
2537
|
}
|
|
2522
2538
|
}
|
|
2523
2539
|
function interpolateVariables(content, variables) {
|
|
@@ -2707,6 +2723,33 @@ async function mergeWithExisting(filePath, newContent) {
|
|
|
2707
2723
|
throw error;
|
|
2708
2724
|
}
|
|
2709
2725
|
}
|
|
2726
|
+
async function writeSplitPointer(tool, stageNames, outputDir) {
|
|
2727
|
+
const config = getToolConfig(tool);
|
|
2728
|
+
const stageLines = stageNames.map((stage) => {
|
|
2729
|
+
const friendly = formatStageName(stage);
|
|
2730
|
+
const stageFile = `${config.splitFilePrefix}${getStageFileName(stage)}${config.fileExtension}`;
|
|
2731
|
+
const relativePath = path6.posix.join(config.splitDir, stageFile);
|
|
2732
|
+
return `- ${friendly}: ${relativePath}`;
|
|
2733
|
+
});
|
|
2734
|
+
const pointerBody = [
|
|
2735
|
+
"SceneForge context for this tool is split across the following stage files:",
|
|
2736
|
+
"",
|
|
2737
|
+
...stageLines,
|
|
2738
|
+
"",
|
|
2739
|
+
`Open the stage file that matches the work you're doing, or run "npx sceneforge context preview --target ${tool} --stage <stage>" to inspect a specific stage.`
|
|
2740
|
+
].join("\n");
|
|
2741
|
+
const formattedPointer = formatForTool(tool, pointerBody, {
|
|
2742
|
+
includeToolHeader: true
|
|
2743
|
+
});
|
|
2744
|
+
const combinedPath = path6.join(outputDir, config.combinedFile);
|
|
2745
|
+
await fs6.mkdir(path6.dirname(combinedPath), { recursive: true });
|
|
2746
|
+
const { content: finalContent, merged } = await mergeWithExisting(
|
|
2747
|
+
combinedPath,
|
|
2748
|
+
formattedPointer
|
|
2749
|
+
);
|
|
2750
|
+
await fs6.writeFile(combinedPath, finalContent, "utf-8");
|
|
2751
|
+
return { filePath: combinedPath, merged };
|
|
2752
|
+
}
|
|
2710
2753
|
async function buildContext(tool, stage, variables) {
|
|
2711
2754
|
const templates = [];
|
|
2712
2755
|
const baseTemplates = await loadTemplatesByCategory("base");
|
|
@@ -2767,6 +2810,7 @@ async function deployContext(options) {
|
|
|
2767
2810
|
});
|
|
2768
2811
|
}
|
|
2769
2812
|
} else {
|
|
2813
|
+
const deployedStages = [];
|
|
2770
2814
|
for (const stg of stages) {
|
|
2771
2815
|
try {
|
|
2772
2816
|
const content = await buildContext(tool, stg, variables);
|
|
@@ -2789,6 +2833,7 @@ async function deployContext(options) {
|
|
|
2789
2833
|
if (merged) {
|
|
2790
2834
|
console.log(` [merged] ${path6.relative(outputDir, absolutePath)}`);
|
|
2791
2835
|
}
|
|
2836
|
+
deployedStages.push(stg);
|
|
2792
2837
|
} catch (error) {
|
|
2793
2838
|
results.push({
|
|
2794
2839
|
tool,
|
|
@@ -2799,6 +2844,24 @@ async function deployContext(options) {
|
|
|
2799
2844
|
});
|
|
2800
2845
|
}
|
|
2801
2846
|
}
|
|
2847
|
+
if (deployedStages.length > 0) {
|
|
2848
|
+
const pointerResult = await writeSplitPointer(
|
|
2849
|
+
tool,
|
|
2850
|
+
deployedStages,
|
|
2851
|
+
outputDir
|
|
2852
|
+
);
|
|
2853
|
+
results.push({
|
|
2854
|
+
tool,
|
|
2855
|
+
filePath: pointerResult.filePath,
|
|
2856
|
+
created: true,
|
|
2857
|
+
skipped: false
|
|
2858
|
+
});
|
|
2859
|
+
if (pointerResult.merged) {
|
|
2860
|
+
console.log(
|
|
2861
|
+
` [merged] ${path6.relative(outputDir, pointerResult.filePath)}`
|
|
2862
|
+
);
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2802
2865
|
}
|
|
2803
2866
|
}
|
|
2804
2867
|
return results;
|