@warpgogol/forge 2.9.0 → 2.9.2
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/dist/os/compass/compass.module.d.ts.map +1 -1
- package/dist/os/compass/compass.module.js +32 -16
- package/dist/os/compass/compass.module.js.map +1 -1
- package/dist/os/compass/handlers/compass-change-summary-handler.d.ts.map +1 -1
- package/dist/os/compass/handlers/compass-change-summary-handler.js +13 -2
- package/dist/os/compass/handlers/compass-change-summary-handler.js.map +1 -1
- package/dist/os/compass/handlers/compass-inventory-handler.d.ts.map +1 -1
- package/dist/os/compass/handlers/compass-inventory-handler.js +64 -0
- package/dist/os/compass/handlers/compass-inventory-handler.js.map +1 -1
- package/dist/os/compass/handlers/compass-inventory.d.ts.map +1 -1
- package/dist/os/compass/handlers/compass-inventory.js +51 -7
- package/dist/os/compass/handlers/compass-inventory.js.map +1 -1
- package/dist/src/onboarding/create.d.ts.map +1 -1
- package/dist/src/onboarding/create.js +3 -0
- package/dist/src/onboarding/create.js.map +1 -1
- package/dist/src/onboarding/init.d.ts +4 -0
- package/dist/src/onboarding/init.d.ts.map +1 -1
- package/dist/src/onboarding/init.js +6 -0
- package/dist/src/onboarding/init.js.map +1 -1
- package/dist/src/profiles/profile-schema.d.ts +8 -0
- package/dist/src/profiles/profile-schema.d.ts.map +1 -1
- package/dist/src/profiles/profile-schema.js +6 -0
- package/dist/src/profiles/profile-schema.js.map +1 -1
- package/dist/src/profiles/stack-profile.d.ts +4 -0
- package/dist/src/profiles/stack-profile.d.ts.map +1 -1
- package/dist/src/profiles/stack-profile.js +2 -0
- package/dist/src/profiles/stack-profile.js.map +1 -1
- package/os/compass/compass.module.ts +32 -16
- package/os/compass/handlers/compass-change-summary-handler.ts +12 -2
- package/os/compass/handlers/compass-game-extensions.test.ts +154 -0
- package/os/compass/handlers/compass-inventory-handler.ts +65 -0
- package/os/compass/handlers/compass-inventory.ts +56 -7
- package/package.json +3 -5
- package/profiles/godot-csharp.yaml +17 -0
- package/profiles/phaser-turborepo.yaml +14 -0
- package/skills/fo/fo-compass-annotate/reference/comment-styles.md +7 -1
- package/src/onboarding/create.ts +3 -0
- package/src/onboarding/init.ts +10 -0
- package/src/profiles/profile-schema.ts +10 -0
- package/src/profiles/stack-profile.ts +2 -0
- package/src/tests/create.test.ts +30 -0
|
@@ -14,15 +14,51 @@ for Compass source-file inventory.</purpose>
|
|
|
14
14
|
<item>Post-refactor hardening: exclude src/templates generation inputs from authored Compass requirements.</item>
|
|
15
15
|
<item>Post-refactor hardening: detect nested packages/os workspaces before deriving Compass layer and workspace name.</item>
|
|
16
16
|
<item>RFC-0556: moved canonical implementation from @warpgogol/site-kernel to @warpgogol/forge for autonomous mode.</item>
|
|
17
|
+
<item>Game extensions: added .cs, .tscn, .tres, .gd to SOURCE_EXTENSIONS; createCompassInventoryEntries now reads forge.yaml compass.fileExtensions at runtime and merges with hardcoded set.</item>
|
|
17
18
|
</CHANGE_SUMMARY>
|
|
18
19
|
*/
|
|
19
20
|
|
|
20
21
|
import { readdir, readFile } from "node:fs/promises";
|
|
22
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
21
23
|
import { join, relative, resolve } from "node:path";
|
|
24
|
+
import { parse as parseYaml } from "yaml";
|
|
22
25
|
import type { ForgeCommandInput } from "../../../src/types.ts";
|
|
23
26
|
import { hasGeneratedMarker } from "../../../src/utils/generated-marker.ts";
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
function loadCompassExtensionsFromForgeYaml(workspaceRoot: string): Set<string> {
|
|
29
|
+
const forgeYamlPath = join(workspaceRoot, "forge.yaml");
|
|
30
|
+
if (!existsSync(forgeYamlPath)) {
|
|
31
|
+
return new Set();
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const raw = readFileSync(forgeYamlPath, "utf8");
|
|
35
|
+
const parsed = parseYaml(raw) as Record<string, unknown> | null;
|
|
36
|
+
const bindings = parsed?.bindings as Record<string, unknown> | undefined;
|
|
37
|
+
const compass = bindings?.compass as Record<string, unknown> | undefined;
|
|
38
|
+
const extensions = compass?.fileExtensions;
|
|
39
|
+
if (Array.isArray(extensions)) {
|
|
40
|
+
return new Set(extensions.filter((e: unknown) => typeof e === "string"));
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
// forge.yaml not parseable or missing compass section — fall back to hardcoded set
|
|
44
|
+
}
|
|
45
|
+
return new Set();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const SOURCE_EXTENSIONS = new Set([
|
|
49
|
+
".ts",
|
|
50
|
+
".tsx",
|
|
51
|
+
".astro",
|
|
52
|
+
".js",
|
|
53
|
+
".jsx",
|
|
54
|
+
".mjs",
|
|
55
|
+
".mts",
|
|
56
|
+
".css",
|
|
57
|
+
".cs",
|
|
58
|
+
".tscn",
|
|
59
|
+
".tres",
|
|
60
|
+
".gd",
|
|
61
|
+
]);
|
|
26
62
|
const IGNORED_DIRECTORY_NAMES = new Set([
|
|
27
63
|
".git",
|
|
28
64
|
".turbo",
|
|
@@ -113,16 +149,26 @@ function shouldIgnoreDirectory(name: string): boolean {
|
|
|
113
149
|
return false;
|
|
114
150
|
}
|
|
115
151
|
|
|
116
|
-
function hasRelevantExtension(filePath: string): boolean {
|
|
152
|
+
function hasRelevantExtension(filePath: string, extraExtensions?: Set<string>): boolean {
|
|
117
153
|
for (const extension of SOURCE_EXTENSIONS) {
|
|
118
154
|
if (filePath.endsWith(extension)) {
|
|
119
155
|
return true;
|
|
120
156
|
}
|
|
121
157
|
}
|
|
158
|
+
if (extraExtensions) {
|
|
159
|
+
for (const extension of extraExtensions) {
|
|
160
|
+
if (filePath.endsWith(extension)) {
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
122
165
|
return false;
|
|
123
166
|
}
|
|
124
167
|
|
|
125
|
-
async function collectSourceFiles(
|
|
168
|
+
async function collectSourceFiles(
|
|
169
|
+
targetPath: string,
|
|
170
|
+
extraExtensions?: Set<string>,
|
|
171
|
+
): Promise<string[]> {
|
|
126
172
|
let stat;
|
|
127
173
|
try {
|
|
128
174
|
const { stat: fsStat } = await import("node:fs/promises");
|
|
@@ -132,7 +178,7 @@ async function collectSourceFiles(targetPath: string): Promise<string[]> {
|
|
|
132
178
|
}
|
|
133
179
|
|
|
134
180
|
if (stat.isFile()) {
|
|
135
|
-
return hasRelevantExtension(targetPath) ? [targetPath] : [];
|
|
181
|
+
return hasRelevantExtension(targetPath, extraExtensions) ? [targetPath] : [];
|
|
136
182
|
}
|
|
137
183
|
|
|
138
184
|
let entries;
|
|
@@ -161,11 +207,11 @@ async function collectSourceFiles(targetPath: string): Promise<string[]> {
|
|
|
161
207
|
continue;
|
|
162
208
|
}
|
|
163
209
|
|
|
164
|
-
files.push(...(await collectSourceFiles(absolutePath)));
|
|
210
|
+
files.push(...(await collectSourceFiles(absolutePath, extraExtensions)));
|
|
165
211
|
continue;
|
|
166
212
|
}
|
|
167
213
|
|
|
168
|
-
if (entry.isFile() && hasRelevantExtension(absolutePath)) {
|
|
214
|
+
if (entry.isFile() && hasRelevantExtension(absolutePath, extraExtensions)) {
|
|
169
215
|
files.push(absolutePath);
|
|
170
216
|
}
|
|
171
217
|
}
|
|
@@ -469,7 +515,10 @@ export async function createCompassInventoryEntries(
|
|
|
469
515
|
scanRoot?: string,
|
|
470
516
|
): Promise<CompassInventoryEntry[]> {
|
|
471
517
|
const roots = scanRoot ? [scanRoot] : resolveScanRoots(workspaceRoot, input);
|
|
472
|
-
const
|
|
518
|
+
const extraExtensions = loadCompassExtensionsFromForgeYaml(workspaceRoot);
|
|
519
|
+
const files = (
|
|
520
|
+
await Promise.all(roots.map((root) => collectSourceFiles(root, extraExtensions)))
|
|
521
|
+
).flat();
|
|
473
522
|
const entries: CompassInventoryEntry[] = [];
|
|
474
523
|
|
|
475
524
|
for (const filePath of files.sort()) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@warpgogol/forge",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -170,12 +170,10 @@
|
|
|
170
170
|
"yaml": "^2.9.0",
|
|
171
171
|
"zod": "^4.4.3"
|
|
172
172
|
},
|
|
173
|
-
"optionalDependencies": {
|
|
174
|
-
"@warpgogol/werkstatt-shared": "*",
|
|
175
|
-
"@warpgogol/werkstatt": "*"
|
|
176
|
-
},
|
|
177
173
|
"devDependencies": {
|
|
178
174
|
"@types/node": "^24.0.0",
|
|
175
|
+
"@warpgogol/werkstatt-shared": "*",
|
|
176
|
+
"@warpgogol/werkstatt": "1.4.0",
|
|
179
177
|
"eslint": "^10.8.0",
|
|
180
178
|
"fast-check": "^4.9.0",
|
|
181
179
|
"typescript": "^6.0.3",
|
|
@@ -6,6 +6,16 @@ detect:
|
|
|
6
6
|
- project.godot
|
|
7
7
|
domain: game
|
|
8
8
|
register: creative
|
|
9
|
+
compass:
|
|
10
|
+
fileExtensions:
|
|
11
|
+
- .cs
|
|
12
|
+
- .tscn
|
|
13
|
+
- .tres
|
|
14
|
+
- .gd
|
|
15
|
+
- .ts
|
|
16
|
+
- .tsx
|
|
17
|
+
- .js
|
|
18
|
+
- .mjs
|
|
9
19
|
terminology:
|
|
10
20
|
artifact: game
|
|
11
21
|
artifactPlural: games
|
|
@@ -294,6 +304,9 @@ workspace:
|
|
|
294
304
|
- name: Test
|
|
295
305
|
run: pnpm test
|
|
296
306
|
|
|
307
|
+
- name: Compass header validation
|
|
308
|
+
run: pnpm exec werkstatt run compass.validate
|
|
309
|
+
|
|
297
310
|
# Windows CI (optional — uncomment if Windows checks are needed):
|
|
298
311
|
#
|
|
299
312
|
# windows-ci:
|
|
@@ -677,9 +690,13 @@ workspace:
|
|
|
677
690
|
#!/bin/sh
|
|
678
691
|
# Platform-scope pre-commit guard
|
|
679
692
|
# Runs werkstatt autonomy and plugin validation before commits
|
|
693
|
+
# Note: compass.validate reads all source files and may add a few seconds
|
|
694
|
+
# to commit time for large projects. Remove the line below if this is
|
|
695
|
+
# too slow during iterative development.
|
|
680
696
|
echo "Pre-commit: running platform checks..."
|
|
681
697
|
pnpm exec werkstatt run werkstatt.autonomy.validate || exit 1
|
|
682
698
|
pnpm exec werkstatt run werkstatt.plugin.validate || exit 1
|
|
699
|
+
pnpm exec werkstatt run compass.validate || exit 1
|
|
683
700
|
- path: systems-cache/.gitkeep
|
|
684
701
|
content: |
|
|
685
702
|
# Sternsystem cache directory (RFC-0790)
|
|
@@ -5,6 +5,13 @@ detect:
|
|
|
5
5
|
anyOf:
|
|
6
6
|
- phaser.config.*
|
|
7
7
|
- package.json
|
|
8
|
+
compass:
|
|
9
|
+
fileExtensions:
|
|
10
|
+
- .ts
|
|
11
|
+
- .tsx
|
|
12
|
+
- .js
|
|
13
|
+
- .mjs
|
|
14
|
+
- .css
|
|
8
15
|
workspace:
|
|
9
16
|
dirs:
|
|
10
17
|
- apps
|
|
@@ -123,6 +130,9 @@ workspace:
|
|
|
123
130
|
- name: Test
|
|
124
131
|
run: pnpm test
|
|
125
132
|
|
|
133
|
+
- name: Compass header validation
|
|
134
|
+
run: pnpm exec werkstatt run compass.validate
|
|
135
|
+
|
|
126
136
|
# Windows CI (optional — uncomment if Windows checks are needed):
|
|
127
137
|
#
|
|
128
138
|
# windows-ci:
|
|
@@ -283,9 +293,13 @@ workspace:
|
|
|
283
293
|
#!/bin/sh
|
|
284
294
|
# Platform-scope pre-commit guard
|
|
285
295
|
# Runs werkstatt autonomy and plugin validation before commits
|
|
296
|
+
# Note: compass.validate reads all source files and may add a few seconds
|
|
297
|
+
# to commit time for large projects. Remove the line below if this is
|
|
298
|
+
# too slow during iterative development.
|
|
286
299
|
echo "Pre-commit: running platform checks..."
|
|
287
300
|
pnpm exec werkstatt run werkstatt.autonomy.validate || exit 1
|
|
288
301
|
pnpm exec werkstatt run werkstatt.plugin.validate || exit 1
|
|
302
|
+
pnpm exec werkstatt run compass.validate || exit 1
|
|
289
303
|
- path: systems-cache/.gitkeep
|
|
290
304
|
content: |
|
|
291
305
|
# Sternsystem cache directory (RFC-0790)
|
|
@@ -12,6 +12,10 @@ Compass headers are embedded in source file comments. The comment syntax varies
|
|
|
12
12
|
| `.js` | `/* ... */` block comment | `/* <MODULE_CONTRACT> ... </MODULE_CONTRACT> */` |
|
|
13
13
|
| `.mjs` | `/* ... */` block comment | `/* <MODULE_CONTRACT> ... </MODULE_CONTRACT> */` |
|
|
14
14
|
| `.css` | `/* ... */` block comment | `/* <MODULE_CONTRACT> ... </MODULE_CONTRACT> */` |
|
|
15
|
+
| `.cs` | `/* ... */` block comment | `/* <MODULE_CONTRACT> ... </MODULE_CONTRACT> */` |
|
|
16
|
+
| `.tscn` | `; ` line comment | `; <MODULE_CONTRACT>` (each line prefixed with `; `) |
|
|
17
|
+
| `.tres` | `; ` line comment | `; <MODULE_CONTRACT>` (each line prefixed with `; `) |
|
|
18
|
+
| `.gd` | `# ` line comment | `# <MODULE_CONTRACT>` (each line prefixed with `# `) |
|
|
15
19
|
| `.yaml` | `# ` line comment | `# <MODULE_CONTRACT>` (each line prefixed with `# `) |
|
|
16
20
|
| `.yml` | `# ` line comment | `# <MODULE_CONTRACT>` (each line prefixed with `# `) |
|
|
17
21
|
| `.json` | Not supported | JSON files cannot carry comments; skip Compass headers |
|
|
@@ -20,6 +24,8 @@ Compass headers are embedded in source file comments. The comment syntax varies
|
|
|
20
24
|
## Rules
|
|
21
25
|
|
|
22
26
|
- The XML-like tags (`<MODULE_CONTRACT>`, `<CHANGE_SUMMARY>`, `<item>`, etc.) are always preserved as-is inside the comment wrapper.
|
|
23
|
-
- For `.yaml`/`.yml`, each line of the block is prefixed with `# `.
|
|
27
|
+
- For `.yaml`/`.yml` and `.gd`, each line of the block is prefixed with `# `.
|
|
28
|
+
- For `.tscn`/`.tres`, each line of the block is prefixed with `; ` (Godot INI-style comment).
|
|
29
|
+
- For `.cs`, use `/* ... */` block comments (same as TypeScript).
|
|
24
30
|
- For `.json`, Compass headers are not applicable — skip these files.
|
|
25
31
|
- The block is placed at the top of the file, after any license header but before code/imports.
|
package/src/onboarding/create.ts
CHANGED
package/src/onboarding/init.ts
CHANGED
|
@@ -52,6 +52,10 @@ export interface InitDomainFields {
|
|
|
52
52
|
terminology?: Record<string, string>;
|
|
53
53
|
semanticBindings?: Record<string, string | null>;
|
|
54
54
|
profileId?: string;
|
|
55
|
+
compass?: {
|
|
56
|
+
fileExtensions?: string[];
|
|
57
|
+
testPatterns?: string[];
|
|
58
|
+
};
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
export function runInit(
|
|
@@ -150,6 +154,12 @@ export function runInit(
|
|
|
150
154
|
}
|
|
151
155
|
}
|
|
152
156
|
}
|
|
157
|
+
if (domainFields?.compass && config.bindings) {
|
|
158
|
+
config.bindings.compass = {
|
|
159
|
+
...(config.bindings.compass ?? {}),
|
|
160
|
+
...domainFields.compass,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
153
163
|
const yamlContent = stringifyYaml(config);
|
|
154
164
|
fs.writeFileSync(forgeYamlPath, yamlContent, "utf8");
|
|
155
165
|
created.push("forge.yaml");
|
|
@@ -331,6 +331,12 @@ export const stackProfileDomainFieldsSchema = z.object({
|
|
|
331
331
|
templates: z.array(profileTemplateSchema).optional(),
|
|
332
332
|
scriptDir: z.string().min(1).optional(),
|
|
333
333
|
rootAgentsMdTemplate: z.string().optional(),
|
|
334
|
+
compass: z
|
|
335
|
+
.object({
|
|
336
|
+
fileExtensions: z.array(z.string()).optional(),
|
|
337
|
+
testPatterns: z.array(z.string()).optional(),
|
|
338
|
+
})
|
|
339
|
+
.optional(),
|
|
334
340
|
});
|
|
335
341
|
|
|
336
342
|
export interface StackProfileDomainFields {
|
|
@@ -347,4 +353,8 @@ export interface StackProfileDomainFields {
|
|
|
347
353
|
templates?: ProfileTemplate[];
|
|
348
354
|
scriptDir?: string;
|
|
349
355
|
rootAgentsMdTemplate?: string;
|
|
356
|
+
compass?: {
|
|
357
|
+
fileExtensions?: string[];
|
|
358
|
+
testPatterns?: string[];
|
|
359
|
+
};
|
|
350
360
|
}
|
|
@@ -76,6 +76,8 @@ export const stackProfileSchema = z.object({
|
|
|
76
76
|
scriptDir: stackProfileDomainFieldsSchema.shape.scriptDir,
|
|
77
77
|
// Profile-driven root AGENTS.md template
|
|
78
78
|
rootAgentsMdTemplate: stackProfileDomainFieldsSchema.shape.rootAgentsMdTemplate,
|
|
79
|
+
// Profile-driven Compass file extensions
|
|
80
|
+
compass: stackProfileDomainFieldsSchema.shape.compass,
|
|
79
81
|
});
|
|
80
82
|
|
|
81
83
|
export interface ProfileFile {
|
package/src/tests/create.test.ts
CHANGED
|
@@ -287,3 +287,33 @@ test("forge create --in-place derives name from folder name when --name omitted"
|
|
|
287
287
|
const pkgJson = JSON.parse(await readFileAsync(join(namedDir, "package.json"), "utf8"));
|
|
288
288
|
expect(pkgJson.name).toBe("my-derived-project");
|
|
289
289
|
}, 30000);
|
|
290
|
+
|
|
291
|
+
test("forge create --profile godot-csharp writes compass.fileExtensions into forge.yaml", async () => {
|
|
292
|
+
const result = await runCreate(
|
|
293
|
+
{ argv: [], flags: { "in-place": true, profile: "godot-csharp", name: "my-game" } },
|
|
294
|
+
makeContext(tempDir),
|
|
295
|
+
);
|
|
296
|
+
expect(result.exitCode).toBe(0);
|
|
297
|
+
|
|
298
|
+
const { readFile: readFileAsync } = await import("node:fs/promises");
|
|
299
|
+
const forgeYaml = await readFileAsync(join(tempDir, "forge.yaml"), "utf8");
|
|
300
|
+
expect(forgeYaml).toContain("compass:");
|
|
301
|
+
expect(forgeYaml).toContain("fileExtensions:");
|
|
302
|
+
expect(forgeYaml).toContain(".cs");
|
|
303
|
+
expect(forgeYaml).toContain(".tscn");
|
|
304
|
+
expect(forgeYaml).toContain(".tres");
|
|
305
|
+
expect(forgeYaml).toContain(".gd");
|
|
306
|
+
}, 30000);
|
|
307
|
+
|
|
308
|
+
test("forge create --profile phaser-turborepo writes compass.fileExtensions into forge.yaml", async () => {
|
|
309
|
+
const result = await runCreate(
|
|
310
|
+
{ argv: [], flags: { "in-place": true, profile: "phaser-turborepo", name: "my-phaser-game" } },
|
|
311
|
+
makeContext(tempDir),
|
|
312
|
+
);
|
|
313
|
+
expect(result.exitCode).toBe(0);
|
|
314
|
+
|
|
315
|
+
const { readFile: readFileAsync } = await import("node:fs/promises");
|
|
316
|
+
const forgeYaml = await readFileAsync(join(tempDir, "forge.yaml"), "utf8");
|
|
317
|
+
expect(forgeYaml).toContain("compass:");
|
|
318
|
+
expect(forgeYaml).toContain("fileExtensions:");
|
|
319
|
+
}, 30000);
|