@t3lnet/sceneforge 1.0.34 → 1.0.35
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 +117 -4
- package/context/index.ts +2 -0
- package/context/tests/context-builder.test.ts +9 -5
- package/dist/index.cjs +76 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +76 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -113,6 +113,12 @@ async function writeSplitPointer(
|
|
|
113
113
|
outputDir: string
|
|
114
114
|
): Promise<{ filePath: string; merged: boolean }> {
|
|
115
115
|
const config = getToolConfig(tool);
|
|
116
|
+
|
|
117
|
+
// Base reference file
|
|
118
|
+
const baseFile = `${config.splitFilePrefix}base${config.fileExtension}`;
|
|
119
|
+
const baseRelativePath = path.posix.join(config.splitDir, baseFile);
|
|
120
|
+
|
|
121
|
+
// Stage files
|
|
116
122
|
const stageLines = stageNames.map((stage) => {
|
|
117
123
|
const friendly = formatStageName(stage);
|
|
118
124
|
const stageFile = `${config.splitFilePrefix}${getStageFileName(stage)}${config.fileExtension}`;
|
|
@@ -121,11 +127,17 @@ async function writeSplitPointer(
|
|
|
121
127
|
});
|
|
122
128
|
|
|
123
129
|
const pointerBody = [
|
|
124
|
-
"SceneForge context for this tool is split
|
|
130
|
+
"SceneForge context for this tool is split into modular files:",
|
|
125
131
|
"",
|
|
132
|
+
"**Reference Documentation:**",
|
|
133
|
+
`- Base Reference (YAML schema, CLI, selectors): ${baseRelativePath}`,
|
|
134
|
+
"",
|
|
135
|
+
"**Stage-Specific Instructions:**",
|
|
126
136
|
...stageLines,
|
|
127
137
|
"",
|
|
128
|
-
`Open the stage file that matches the work you're doing
|
|
138
|
+
`Open the stage file that matches the work you're doing. The base reference contains shared documentation used across all stages.`,
|
|
139
|
+
"",
|
|
140
|
+
`Run "npx sceneforge context preview --target ${tool} --stage <stage>" to inspect a specific stage.`,
|
|
129
141
|
].join("\n");
|
|
130
142
|
|
|
131
143
|
const formattedPointer = formatForTool(tool, pointerBody, {
|
|
@@ -145,6 +157,8 @@ async function writeSplitPointer(
|
|
|
145
157
|
|
|
146
158
|
/**
|
|
147
159
|
* Build context content for a specific tool and stage.
|
|
160
|
+
* For combined format, includes both base and stage templates.
|
|
161
|
+
* For split format, use buildBaseContext and buildStageOnlyContext instead.
|
|
148
162
|
*/
|
|
149
163
|
export async function buildContext(
|
|
150
164
|
tool: TargetTool,
|
|
@@ -187,6 +201,58 @@ export async function buildContext(
|
|
|
187
201
|
return formatForTool(tool, content, { stage: stageName });
|
|
188
202
|
}
|
|
189
203
|
|
|
204
|
+
/**
|
|
205
|
+
* Build base context content only (for split format).
|
|
206
|
+
* Contains reference documentation shared across all stages.
|
|
207
|
+
*/
|
|
208
|
+
export async function buildBaseContext(
|
|
209
|
+
tool: TargetTool,
|
|
210
|
+
variables?: TemplateVariables
|
|
211
|
+
): Promise<string> {
|
|
212
|
+
const baseTemplates = await loadTemplatesByCategory("base");
|
|
213
|
+
|
|
214
|
+
let content = composeTemplates(baseTemplates, {
|
|
215
|
+
separator: "\n\n---\n\n",
|
|
216
|
+
includeHeaders: false,
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
if (variables) {
|
|
220
|
+
content = interpolateVariables(content, variables);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return formatForTool(tool, content, { stage: "Base Reference" });
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Build stage-only context content (for split format).
|
|
228
|
+
* Contains only stage-specific instructions without base templates.
|
|
229
|
+
*/
|
|
230
|
+
export async function buildStageOnlyContext(
|
|
231
|
+
tool: TargetTool,
|
|
232
|
+
stage: Stage,
|
|
233
|
+
variables?: TemplateVariables
|
|
234
|
+
): Promise<string> {
|
|
235
|
+
if (stage === "all") {
|
|
236
|
+
throw new Error("buildStageOnlyContext does not support 'all' stage");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const stageFileName = getStageFileName(stage);
|
|
240
|
+
const stageTemplate = await loadTemplate("stages", stageFileName);
|
|
241
|
+
|
|
242
|
+
let content = stageTemplate.content;
|
|
243
|
+
|
|
244
|
+
if (variables) {
|
|
245
|
+
content = interpolateVariables(content, variables);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Add a note pointing to base reference
|
|
249
|
+
const baseNote = `> **Note:** This file contains stage-specific instructions. See \`base.md\` in this directory for reference documentation (YAML schema, CLI commands, selectors guide, etc.).\n\n`;
|
|
250
|
+
|
|
251
|
+
return formatForTool(tool, baseNote + content, {
|
|
252
|
+
stage: formatStageName(stage),
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
190
256
|
/**
|
|
191
257
|
* Deploy context files to the target directory.
|
|
192
258
|
*/
|
|
@@ -245,11 +311,58 @@ export async function deployContext(
|
|
|
245
311
|
});
|
|
246
312
|
}
|
|
247
313
|
} else {
|
|
248
|
-
// Generate split files
|
|
314
|
+
// Generate split files: one base file + individual stage files
|
|
315
|
+
const config = getToolConfig(tool);
|
|
316
|
+
|
|
317
|
+
// First, deploy the base reference file
|
|
318
|
+
try {
|
|
319
|
+
const baseContent = await buildBaseContext(tool, variables);
|
|
320
|
+
const baseFilePath = path.join(
|
|
321
|
+
outputDir,
|
|
322
|
+
config.splitDir,
|
|
323
|
+
`${config.splitFilePrefix}base${config.fileExtension}`
|
|
324
|
+
);
|
|
325
|
+
const absoluteBasePath = path.resolve(baseFilePath);
|
|
326
|
+
|
|
327
|
+
await fs.mkdir(path.dirname(absoluteBasePath), { recursive: true });
|
|
328
|
+
|
|
329
|
+
const { content: finalBaseContent, merged: baseMerged } =
|
|
330
|
+
await mergeWithExisting(absoluteBasePath, baseContent);
|
|
331
|
+
|
|
332
|
+
await fs.writeFile(absoluteBasePath, finalBaseContent, "utf-8");
|
|
333
|
+
|
|
334
|
+
results.push({
|
|
335
|
+
tool,
|
|
336
|
+
filePath: absoluteBasePath,
|
|
337
|
+
stage: "base",
|
|
338
|
+
created: true,
|
|
339
|
+
skipped: false,
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
if (baseMerged) {
|
|
343
|
+
console.log(
|
|
344
|
+
` [merged] ${path.relative(outputDir, absoluteBasePath)}`
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
} catch (error) {
|
|
348
|
+
results.push({
|
|
349
|
+
tool,
|
|
350
|
+
filePath: path.join(
|
|
351
|
+
outputDir,
|
|
352
|
+
config.splitDir,
|
|
353
|
+
`${config.splitFilePrefix}base${config.fileExtension}`
|
|
354
|
+
),
|
|
355
|
+
stage: "base",
|
|
356
|
+
created: false,
|
|
357
|
+
error: error instanceof Error ? error.message : String(error),
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Then deploy individual stage files (without base content)
|
|
249
362
|
const deployedStages: Stage[] = [];
|
|
250
363
|
for (const stg of stages) {
|
|
251
364
|
try {
|
|
252
|
-
const content = await
|
|
365
|
+
const content = await buildStageOnlyContext(tool, stg, variables);
|
|
253
366
|
const stageName = getStageFileName(stg);
|
|
254
367
|
const filePath = getOutputPath(tool, format, outputDir, stageName);
|
|
255
368
|
const absolutePath = path.resolve(filePath);
|
package/context/index.ts
CHANGED
|
@@ -118,12 +118,14 @@ describe("context-builder", () => {
|
|
|
118
118
|
outputDir: tempDir,
|
|
119
119
|
});
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
expect(results
|
|
121
|
+
// Split format now creates: base.md + stage file + pointer file
|
|
122
|
+
expect(results.length).toBe(3);
|
|
123
|
+
expect(results.every((r) => r.created)).toBe(true);
|
|
123
124
|
|
|
124
125
|
const splitDir = path.join(tempDir, ".claude/rules");
|
|
125
126
|
const files = await fs.readdir(splitDir);
|
|
126
|
-
expect(files
|
|
127
|
+
expect(files).toContain("base.md");
|
|
128
|
+
expect(files).toContain("stage1-actions.md");
|
|
127
129
|
});
|
|
128
130
|
|
|
129
131
|
it("deploys multiple stages in split format", async () => {
|
|
@@ -134,7 +136,8 @@ describe("context-builder", () => {
|
|
|
134
136
|
outputDir: tempDir,
|
|
135
137
|
});
|
|
136
138
|
|
|
137
|
-
|
|
139
|
+
// Split format now creates: base.md + 4 stage files + pointer file = 6
|
|
140
|
+
expect(results.length).toBe(6);
|
|
138
141
|
expect(results.every((r) => r.created)).toBe(true);
|
|
139
142
|
});
|
|
140
143
|
|
|
@@ -147,7 +150,8 @@ describe("context-builder", () => {
|
|
|
147
150
|
});
|
|
148
151
|
|
|
149
152
|
const claudeFile = await fs.readFile(path.join(tempDir, "CLAUDE.md"), "utf-8");
|
|
150
|
-
expect(claudeFile).toContain("split
|
|
153
|
+
expect(claudeFile).toContain("split into modular files");
|
|
154
|
+
expect(claudeFile).toContain("Base Reference");
|
|
151
155
|
expect(claudeFile).toContain(".claude/rules/stage1-actions.md");
|
|
152
156
|
});
|
|
153
157
|
});
|
package/dist/index.cjs
CHANGED
|
@@ -2728,6 +2728,8 @@ async function mergeWithExisting(filePath, newContent) {
|
|
|
2728
2728
|
}
|
|
2729
2729
|
async function writeSplitPointer(tool, stageNames, outputDir) {
|
|
2730
2730
|
const config = getToolConfig(tool);
|
|
2731
|
+
const baseFile = `${config.splitFilePrefix}base${config.fileExtension}`;
|
|
2732
|
+
const baseRelativePath = path6.posix.join(config.splitDir, baseFile);
|
|
2731
2733
|
const stageLines = stageNames.map((stage) => {
|
|
2732
2734
|
const friendly = formatStageName(stage);
|
|
2733
2735
|
const stageFile = `${config.splitFilePrefix}${getStageFileName(stage)}${config.fileExtension}`;
|
|
@@ -2735,11 +2737,17 @@ async function writeSplitPointer(tool, stageNames, outputDir) {
|
|
|
2735
2737
|
return `- ${friendly}: ${relativePath}`;
|
|
2736
2738
|
});
|
|
2737
2739
|
const pointerBody = [
|
|
2738
|
-
"SceneForge context for this tool is split
|
|
2740
|
+
"SceneForge context for this tool is split into modular files:",
|
|
2739
2741
|
"",
|
|
2742
|
+
"**Reference Documentation:**",
|
|
2743
|
+
`- Base Reference (YAML schema, CLI, selectors): ${baseRelativePath}`,
|
|
2744
|
+
"",
|
|
2745
|
+
"**Stage-Specific Instructions:**",
|
|
2740
2746
|
...stageLines,
|
|
2741
2747
|
"",
|
|
2742
|
-
`Open the stage file that matches the work you're doing
|
|
2748
|
+
`Open the stage file that matches the work you're doing. The base reference contains shared documentation used across all stages.`,
|
|
2749
|
+
"",
|
|
2750
|
+
`Run "npx sceneforge context preview --target ${tool} --stage <stage>" to inspect a specific stage.`
|
|
2743
2751
|
].join("\n");
|
|
2744
2752
|
const formattedPointer = formatForTool(tool, pointerBody, {
|
|
2745
2753
|
includeToolHeader: true
|
|
@@ -2778,6 +2786,34 @@ async function buildContext(tool, stage, variables) {
|
|
|
2778
2786
|
const stageName = stage === "all" ? void 0 : formatStageName(stage);
|
|
2779
2787
|
return formatForTool(tool, content, { stage: stageName });
|
|
2780
2788
|
}
|
|
2789
|
+
async function buildBaseContext(tool, variables) {
|
|
2790
|
+
const baseTemplates = await loadTemplatesByCategory("base");
|
|
2791
|
+
let content = composeTemplates(baseTemplates, {
|
|
2792
|
+
separator: "\n\n---\n\n",
|
|
2793
|
+
includeHeaders: false
|
|
2794
|
+
});
|
|
2795
|
+
if (variables) {
|
|
2796
|
+
content = interpolateVariables(content, variables);
|
|
2797
|
+
}
|
|
2798
|
+
return formatForTool(tool, content, { stage: "Base Reference" });
|
|
2799
|
+
}
|
|
2800
|
+
async function buildStageOnlyContext(tool, stage, variables) {
|
|
2801
|
+
if (stage === "all") {
|
|
2802
|
+
throw new Error("buildStageOnlyContext does not support 'all' stage");
|
|
2803
|
+
}
|
|
2804
|
+
const stageFileName = getStageFileName(stage);
|
|
2805
|
+
const stageTemplate = await loadTemplate("stages", stageFileName);
|
|
2806
|
+
let content = stageTemplate.content;
|
|
2807
|
+
if (variables) {
|
|
2808
|
+
content = interpolateVariables(content, variables);
|
|
2809
|
+
}
|
|
2810
|
+
const baseNote = `> **Note:** This file contains stage-specific instructions. See \`base.md\` in this directory for reference documentation (YAML schema, CLI commands, selectors guide, etc.).
|
|
2811
|
+
|
|
2812
|
+
`;
|
|
2813
|
+
return formatForTool(tool, baseNote + content, {
|
|
2814
|
+
stage: formatStageName(stage)
|
|
2815
|
+
});
|
|
2816
|
+
}
|
|
2781
2817
|
async function deployContext(options) {
|
|
2782
2818
|
const { target, stage, format, outputDir, variables } = options;
|
|
2783
2819
|
const results = [];
|
|
@@ -2813,10 +2849,47 @@ async function deployContext(options) {
|
|
|
2813
2849
|
});
|
|
2814
2850
|
}
|
|
2815
2851
|
} else {
|
|
2852
|
+
const config = getToolConfig(tool);
|
|
2853
|
+
try {
|
|
2854
|
+
const baseContent = await buildBaseContext(tool, variables);
|
|
2855
|
+
const baseFilePath = path6.join(
|
|
2856
|
+
outputDir,
|
|
2857
|
+
config.splitDir,
|
|
2858
|
+
`${config.splitFilePrefix}base${config.fileExtension}`
|
|
2859
|
+
);
|
|
2860
|
+
const absoluteBasePath = path6.resolve(baseFilePath);
|
|
2861
|
+
await fs6.mkdir(path6.dirname(absoluteBasePath), { recursive: true });
|
|
2862
|
+
const { content: finalBaseContent, merged: baseMerged } = await mergeWithExisting(absoluteBasePath, baseContent);
|
|
2863
|
+
await fs6.writeFile(absoluteBasePath, finalBaseContent, "utf-8");
|
|
2864
|
+
results.push({
|
|
2865
|
+
tool,
|
|
2866
|
+
filePath: absoluteBasePath,
|
|
2867
|
+
stage: "base",
|
|
2868
|
+
created: true,
|
|
2869
|
+
skipped: false
|
|
2870
|
+
});
|
|
2871
|
+
if (baseMerged) {
|
|
2872
|
+
console.log(
|
|
2873
|
+
` [merged] ${path6.relative(outputDir, absoluteBasePath)}`
|
|
2874
|
+
);
|
|
2875
|
+
}
|
|
2876
|
+
} catch (error) {
|
|
2877
|
+
results.push({
|
|
2878
|
+
tool,
|
|
2879
|
+
filePath: path6.join(
|
|
2880
|
+
outputDir,
|
|
2881
|
+
config.splitDir,
|
|
2882
|
+
`${config.splitFilePrefix}base${config.fileExtension}`
|
|
2883
|
+
),
|
|
2884
|
+
stage: "base",
|
|
2885
|
+
created: false,
|
|
2886
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2887
|
+
});
|
|
2888
|
+
}
|
|
2816
2889
|
const deployedStages = [];
|
|
2817
2890
|
for (const stg of stages) {
|
|
2818
2891
|
try {
|
|
2819
|
-
const content = await
|
|
2892
|
+
const content = await buildStageOnlyContext(tool, stg, variables);
|
|
2820
2893
|
const stageName = getStageFileName(stg);
|
|
2821
2894
|
const filePath = getOutputPath(tool, format, outputDir, stageName);
|
|
2822
2895
|
const absolutePath = path6.resolve(filePath);
|