@t3lnet/sceneforge 1.0.12 → 1.0.14
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 +81 -2
- package/context/template-loader.ts +20 -2
- package/context/templates/base/cli-reference.md +7 -1
- package/dist/index.cjs +74 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +74 -6
- package/dist/index.js.map +1 -1
- package/dist/templates/base/actions-reference.md +299 -0
- package/dist/templates/base/cli-reference.md +272 -0
- package/dist/templates/base/project-overview.md +58 -0
- package/dist/templates/base/selectors-guide.md +233 -0
- package/dist/templates/base/yaml-schema.md +210 -0
- package/dist/templates/skills/balance-timing.md +136 -0
- package/dist/templates/skills/debug-selector.md +193 -0
- package/dist/templates/skills/generate-actions.md +94 -0
- package/dist/templates/skills/optimize-demo.md +218 -0
- package/dist/templates/skills/review-demo-yaml.md +164 -0
- package/dist/templates/skills/write-step-script.md +136 -0
- package/dist/templates/stages/stage1-actions.md +236 -0
- package/dist/templates/stages/stage2-scripts.md +197 -0
- package/dist/templates/stages/stage3-balancing.md +229 -0
- package/dist/templates/stages/stage4-rebalancing.md +250 -0
- package/package.json +2 -2
|
@@ -27,6 +27,10 @@ import {
|
|
|
27
27
|
|
|
28
28
|
export type Stage = "actions" | "scripts" | "balance" | "rebalance" | "all";
|
|
29
29
|
|
|
30
|
+
// Markers for identifying SceneForge content in existing files
|
|
31
|
+
const SCENEFORGE_START_MARKER = "<!-- SCENEFORGE_CONTEXT_START -->";
|
|
32
|
+
const SCENEFORGE_END_MARKER = "<!-- SCENEFORGE_CONTEXT_END -->";
|
|
33
|
+
|
|
30
34
|
export interface ContextBuilderOptions {
|
|
31
35
|
target: TargetTool | "all";
|
|
32
36
|
stage: Stage;
|
|
@@ -50,6 +54,59 @@ export interface PreviewResult {
|
|
|
50
54
|
content: string;
|
|
51
55
|
}
|
|
52
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Wrap content with SceneForge markers for identification.
|
|
59
|
+
*/
|
|
60
|
+
function wrapWithMarkers(content: string): string {
|
|
61
|
+
return `${SCENEFORGE_START_MARKER}\n${content}\n${SCENEFORGE_END_MARKER}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Merge SceneForge content into an existing file.
|
|
66
|
+
* - If file doesn't exist, returns wrapped content
|
|
67
|
+
* - If file exists without markers, appends wrapped content
|
|
68
|
+
* - If file exists with markers, replaces the marked section
|
|
69
|
+
*/
|
|
70
|
+
async function mergeWithExisting(
|
|
71
|
+
filePath: string,
|
|
72
|
+
newContent: string
|
|
73
|
+
): Promise<{ content: string; merged: boolean }> {
|
|
74
|
+
const wrappedContent = wrapWithMarkers(newContent);
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const existingContent = await fs.readFile(filePath, "utf-8");
|
|
78
|
+
|
|
79
|
+
// Check if file already has SceneForge markers
|
|
80
|
+
const startIndex = existingContent.indexOf(SCENEFORGE_START_MARKER);
|
|
81
|
+
const endIndex = existingContent.indexOf(SCENEFORGE_END_MARKER);
|
|
82
|
+
|
|
83
|
+
if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) {
|
|
84
|
+
// Replace existing SceneForge section
|
|
85
|
+
const beforeSection = existingContent.slice(0, startIndex);
|
|
86
|
+
const afterSection = existingContent.slice(
|
|
87
|
+
endIndex + SCENEFORGE_END_MARKER.length
|
|
88
|
+
);
|
|
89
|
+
return {
|
|
90
|
+
content: beforeSection + wrappedContent + afterSection,
|
|
91
|
+
merged: true,
|
|
92
|
+
};
|
|
93
|
+
} else {
|
|
94
|
+
// Append SceneForge section to existing content
|
|
95
|
+
const separator = existingContent.trim().endsWith("-->") ? "\n\n" : "\n\n---\n\n";
|
|
96
|
+
return {
|
|
97
|
+
content: existingContent.trimEnd() + separator + wrappedContent + "\n",
|
|
98
|
+
merged: true,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
// File doesn't exist, return wrapped content for new file
|
|
103
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
104
|
+
return { content: wrappedContent, merged: false };
|
|
105
|
+
}
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
53
110
|
/**
|
|
54
111
|
* Build context content for a specific tool and stage.
|
|
55
112
|
*/
|
|
@@ -124,14 +181,25 @@ export async function deployContext(
|
|
|
124
181
|
// Ensure directory exists
|
|
125
182
|
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
|
|
126
183
|
|
|
184
|
+
// Merge with existing content or create new
|
|
185
|
+
const { content: finalContent, merged } = await mergeWithExisting(
|
|
186
|
+
absolutePath,
|
|
187
|
+
content
|
|
188
|
+
);
|
|
189
|
+
|
|
127
190
|
// Write file
|
|
128
|
-
await fs.writeFile(absolutePath,
|
|
191
|
+
await fs.writeFile(absolutePath, finalContent, "utf-8");
|
|
129
192
|
|
|
130
193
|
results.push({
|
|
131
194
|
tool,
|
|
132
195
|
filePath: absolutePath,
|
|
133
196
|
created: true,
|
|
197
|
+
skipped: false,
|
|
134
198
|
});
|
|
199
|
+
|
|
200
|
+
if (merged) {
|
|
201
|
+
console.log(` [merged] ${path.relative(outputDir, absolutePath)}`);
|
|
202
|
+
}
|
|
135
203
|
} catch (error) {
|
|
136
204
|
results.push({
|
|
137
205
|
tool,
|
|
@@ -152,15 +220,26 @@ export async function deployContext(
|
|
|
152
220
|
// Ensure directory exists
|
|
153
221
|
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
|
|
154
222
|
|
|
223
|
+
// Merge with existing content or create new
|
|
224
|
+
const { content: finalContent, merged } = await mergeWithExisting(
|
|
225
|
+
absolutePath,
|
|
226
|
+
content
|
|
227
|
+
);
|
|
228
|
+
|
|
155
229
|
// Write file
|
|
156
|
-
await fs.writeFile(absolutePath,
|
|
230
|
+
await fs.writeFile(absolutePath, finalContent, "utf-8");
|
|
157
231
|
|
|
158
232
|
results.push({
|
|
159
233
|
tool,
|
|
160
234
|
filePath: absolutePath,
|
|
161
235
|
stage: stg,
|
|
162
236
|
created: true,
|
|
237
|
+
skipped: false,
|
|
163
238
|
});
|
|
239
|
+
|
|
240
|
+
if (merged) {
|
|
241
|
+
console.log(` [merged] ${path.relative(outputDir, absolutePath)}`);
|
|
242
|
+
}
|
|
164
243
|
} catch (error) {
|
|
165
244
|
results.push({
|
|
166
245
|
tool,
|
|
@@ -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
|
|
|
@@ -182,12 +182,18 @@ sceneforge context <subcommand> [options]
|
|
|
182
182
|
```
|
|
183
183
|
|
|
184
184
|
**Subcommands:**
|
|
185
|
-
- `deploy` - Deploy context files
|
|
185
|
+
- `deploy` - Deploy context files (additive - preserves existing content)
|
|
186
186
|
- `list` - List deployed context
|
|
187
187
|
- `remove` - Remove context files
|
|
188
188
|
- `preview` - Preview context content
|
|
189
189
|
- `skill` - Manage skills
|
|
190
190
|
|
|
191
|
+
**Additive Deployment:**
|
|
192
|
+
SceneForge uses markers to identify its content in instruction files:
|
|
193
|
+
- `<!-- SCENEFORGE_CONTEXT_START -->` and `<!-- SCENEFORGE_CONTEXT_END -->`
|
|
194
|
+
- Existing content outside these markers is preserved
|
|
195
|
+
- Re-running deploy updates only the SceneForge section
|
|
196
|
+
|
|
191
197
|
See dedicated context documentation for details.
|
|
192
198
|
|
|
193
199
|
## Output Structure
|
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) {
|
|
@@ -2671,6 +2687,42 @@ function isValidFormat(format) {
|
|
|
2671
2687
|
// context/context-builder.ts
|
|
2672
2688
|
var fs6 = __toESM(require("fs/promises"), 1);
|
|
2673
2689
|
var path6 = __toESM(require("path"), 1);
|
|
2690
|
+
var SCENEFORGE_START_MARKER = "<!-- SCENEFORGE_CONTEXT_START -->";
|
|
2691
|
+
var SCENEFORGE_END_MARKER = "<!-- SCENEFORGE_CONTEXT_END -->";
|
|
2692
|
+
function wrapWithMarkers(content) {
|
|
2693
|
+
return `${SCENEFORGE_START_MARKER}
|
|
2694
|
+
${content}
|
|
2695
|
+
${SCENEFORGE_END_MARKER}`;
|
|
2696
|
+
}
|
|
2697
|
+
async function mergeWithExisting(filePath, newContent) {
|
|
2698
|
+
const wrappedContent = wrapWithMarkers(newContent);
|
|
2699
|
+
try {
|
|
2700
|
+
const existingContent = await fs6.readFile(filePath, "utf-8");
|
|
2701
|
+
const startIndex = existingContent.indexOf(SCENEFORGE_START_MARKER);
|
|
2702
|
+
const endIndex = existingContent.indexOf(SCENEFORGE_END_MARKER);
|
|
2703
|
+
if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) {
|
|
2704
|
+
const beforeSection = existingContent.slice(0, startIndex);
|
|
2705
|
+
const afterSection = existingContent.slice(
|
|
2706
|
+
endIndex + SCENEFORGE_END_MARKER.length
|
|
2707
|
+
);
|
|
2708
|
+
return {
|
|
2709
|
+
content: beforeSection + wrappedContent + afterSection,
|
|
2710
|
+
merged: true
|
|
2711
|
+
};
|
|
2712
|
+
} else {
|
|
2713
|
+
const separator = existingContent.trim().endsWith("-->") ? "\n\n" : "\n\n---\n\n";
|
|
2714
|
+
return {
|
|
2715
|
+
content: existingContent.trimEnd() + separator + wrappedContent + "\n",
|
|
2716
|
+
merged: true
|
|
2717
|
+
};
|
|
2718
|
+
}
|
|
2719
|
+
} catch (error) {
|
|
2720
|
+
if (error.code === "ENOENT") {
|
|
2721
|
+
return { content: wrappedContent, merged: false };
|
|
2722
|
+
}
|
|
2723
|
+
throw error;
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2674
2726
|
async function buildContext(tool, stage, variables) {
|
|
2675
2727
|
const templates = [];
|
|
2676
2728
|
const baseTemplates = await loadTemplatesByCategory("base");
|
|
@@ -2708,12 +2760,20 @@ async function deployContext(options) {
|
|
|
2708
2760
|
const filePath = getOutputPath(tool, format, outputDir);
|
|
2709
2761
|
const absolutePath = path6.resolve(filePath);
|
|
2710
2762
|
await fs6.mkdir(path6.dirname(absolutePath), { recursive: true });
|
|
2711
|
-
|
|
2763
|
+
const { content: finalContent, merged } = await mergeWithExisting(
|
|
2764
|
+
absolutePath,
|
|
2765
|
+
content
|
|
2766
|
+
);
|
|
2767
|
+
await fs6.writeFile(absolutePath, finalContent, "utf-8");
|
|
2712
2768
|
results.push({
|
|
2713
2769
|
tool,
|
|
2714
2770
|
filePath: absolutePath,
|
|
2715
|
-
created: true
|
|
2771
|
+
created: true,
|
|
2772
|
+
skipped: false
|
|
2716
2773
|
});
|
|
2774
|
+
if (merged) {
|
|
2775
|
+
console.log(` [merged] ${path6.relative(outputDir, absolutePath)}`);
|
|
2776
|
+
}
|
|
2717
2777
|
} catch (error) {
|
|
2718
2778
|
results.push({
|
|
2719
2779
|
tool,
|
|
@@ -2730,13 +2790,21 @@ async function deployContext(options) {
|
|
|
2730
2790
|
const filePath = getOutputPath(tool, format, outputDir, stageName);
|
|
2731
2791
|
const absolutePath = path6.resolve(filePath);
|
|
2732
2792
|
await fs6.mkdir(path6.dirname(absolutePath), { recursive: true });
|
|
2733
|
-
|
|
2793
|
+
const { content: finalContent, merged } = await mergeWithExisting(
|
|
2794
|
+
absolutePath,
|
|
2795
|
+
content
|
|
2796
|
+
);
|
|
2797
|
+
await fs6.writeFile(absolutePath, finalContent, "utf-8");
|
|
2734
2798
|
results.push({
|
|
2735
2799
|
tool,
|
|
2736
2800
|
filePath: absolutePath,
|
|
2737
2801
|
stage: stg,
|
|
2738
|
-
created: true
|
|
2802
|
+
created: true,
|
|
2803
|
+
skipped: false
|
|
2739
2804
|
});
|
|
2805
|
+
if (merged) {
|
|
2806
|
+
console.log(` [merged] ${path6.relative(outputDir, absolutePath)}`);
|
|
2807
|
+
}
|
|
2740
2808
|
} catch (error) {
|
|
2741
2809
|
results.push({
|
|
2742
2810
|
tool,
|