@warpgogol/forge 5.1.0 → 5.1.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/AGENTS.md +8 -1
- package/CHANGELOG.md +72 -0
- package/README.md +8 -8
- package/README.uk.md +5 -5
- package/dist/os/compass/handlers/compass-audit-handler.d.ts.map +1 -1
- package/dist/os/compass/handlers/compass-audit-handler.js +3 -3
- package/dist/os/compass/handlers/compass-audit-handler.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 +1 -1
- 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 +1 -1
- package/dist/os/compass/handlers/compass-inventory-handler.js.map +1 -1
- package/dist/os/compass/handlers/compass-inventory.d.ts +1 -1
- package/dist/os/compass/handlers/compass-inventory.d.ts.map +1 -1
- package/dist/os/compass/handlers/compass-inventory.js +20 -5
- package/dist/os/compass/handlers/compass-inventory.js.map +1 -1
- package/dist/os/compass/handlers/compass-migrate-handler.d.ts.map +1 -1
- package/dist/os/compass/handlers/compass-migrate-handler.js +1 -0
- package/dist/os/compass/handlers/compass-migrate-handler.js.map +1 -1
- package/dist/os/compass/handlers/compass-migrate.d.ts +2 -0
- package/dist/os/compass/handlers/compass-migrate.d.ts.map +1 -1
- package/dist/os/compass/handlers/compass-migrate.js +1 -1
- package/dist/os/compass/handlers/compass-migrate.js.map +1 -1
- package/dist/os/compass/handlers/resolve-scan-root.js +1 -1
- package/dist/os/core/core.module.js +2 -2
- package/dist/os/core/core.module.js.map +1 -1
- package/dist/os/core/handlers/pinned-validate.d.ts.map +1 -1
- package/dist/os/core/handlers/pinned-validate.js +26 -6
- package/dist/os/core/handlers/pinned-validate.js.map +1 -1
- package/dist/src/config/forge-config.d.ts +20 -1
- package/dist/src/config/forge-config.d.ts.map +1 -1
- package/dist/src/config/forge-config.js +76 -5
- package/dist/src/config/forge-config.js.map +1 -1
- package/dist/src/onboarding/agents-generate.d.ts.map +1 -1
- package/dist/src/onboarding/agents-generate.js +11 -7
- package/dist/src/onboarding/agents-generate.js.map +1 -1
- package/dist/src/onboarding/create.d.ts.map +1 -1
- package/dist/src/onboarding/create.js +4 -8
- package/dist/src/onboarding/create.js.map +1 -1
- package/dist/src/onboarding/doctor.d.ts.map +1 -1
- package/dist/src/onboarding/doctor.js +65 -0
- package/dist/src/onboarding/doctor.js.map +1 -1
- package/dist/src/onboarding/init.d.ts.map +1 -1
- package/dist/src/onboarding/init.js +11 -0
- package/dist/src/onboarding/init.js.map +1 -1
- package/dist/src/onboarding/nested-agents-templates.d.ts.map +1 -1
- package/dist/src/onboarding/nested-agents-templates.js +5 -2
- package/dist/src/onboarding/nested-agents-templates.js.map +1 -1
- package/dist/src/onboarding/upgrade.d.ts.map +1 -1
- package/dist/src/onboarding/upgrade.js +4 -2
- package/dist/src/onboarding/upgrade.js.map +1 -1
- package/docs/getting-started.md +1 -1
- package/docs/reference/profiles.md +8 -8
- package/os/compass/handlers/compass-audit-handler.ts +3 -0
- package/os/compass/handlers/compass-change-summary-handler.ts +1 -0
- package/os/compass/handlers/compass-inventory-handler.ts +1 -0
- package/os/compass/handlers/compass-inventory.ts +20 -6
- package/os/compass/handlers/compass-migrate-handler.ts +1 -0
- package/os/compass/handlers/compass-migrate.ts +9 -2
- package/os/compass/handlers/resolve-scan-root.ts +1 -1
- package/os/compass/handlers/tests/compass-policy.test.ts +2 -2
- package/os/core/core.module.ts +2 -2
- package/os/core/handlers/pinned-validate.ts +33 -7
- package/package.json +1 -1
- package/profiles/{godot-csharp.yaml → godot-game.yaml} +2 -2
- package/profiles/{knowledge-typescript-turborepo.yaml → knowledge.yaml} +1 -1
- package/profiles/{phaser-turborepo.yaml → phaser-game.yaml} +2 -2
- package/profiles/{typescript-turborepo.yaml → typescript.yaml} +2 -2
- package/src/config/forge-config.ts +83 -5
- package/src/onboarding/agents-generate.ts +16 -7
- package/src/onboarding/create.ts +4 -7
- package/src/onboarding/doctor.ts +70 -0
- package/src/onboarding/init.ts +19 -1
- package/src/onboarding/nested-agents-templates.ts +5 -2
- package/src/onboarding/upgrade.ts +4 -1
- package/src/tests/agents-generate-domain.test.ts +3 -3
- package/src/tests/create.test.ts +4 -4
- package/src/tests/doctor-profile-id-known.test.ts +237 -0
- package/src/tests/package-files.test.ts +53 -1
- package/src/tests/pinned-validate.test.ts +134 -0
- package/src/tests/profile-schema.test.ts +4 -4
- package/src/tests/scaffold-project.test.ts +3 -3
- package/src/tests/stack-profile.test.ts +12 -12
|
@@ -10,8 +10,18 @@ but are missing from the published package.</purpose>
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { test, expect } from "vitest";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
existsSync,
|
|
15
|
+
mkdirSync,
|
|
16
|
+
mkdtempSync,
|
|
17
|
+
readFileSync,
|
|
18
|
+
readdirSync,
|
|
19
|
+
rmSync,
|
|
20
|
+
writeFileSync,
|
|
21
|
+
} from "node:fs";
|
|
22
|
+
import { tmpdir } from "node:os";
|
|
14
23
|
import { join } from "node:path";
|
|
24
|
+
import { resolveForgePackageRoot } from "../config/forge-config.ts";
|
|
15
25
|
|
|
16
26
|
const PACKAGE_ROOT = join(import.meta.dirname, "..", "..");
|
|
17
27
|
|
|
@@ -52,3 +62,45 @@ test("root AGENTS.md templates exist and are readable", () => {
|
|
|
52
62
|
expect(content.length).toBeGreaterThan(0);
|
|
53
63
|
}
|
|
54
64
|
});
|
|
65
|
+
|
|
66
|
+
// Regression: published 5.1.0 resolved templates/profiles via import.meta.dirname,
|
|
67
|
+
// which points at dist/src/onboarding/ in the compiled package — tsc never copies
|
|
68
|
+
// .md assets into dist/, so lookups silently failed. resolveForgePackageRoot must
|
|
69
|
+
// find the package root from both source and compiled layouts.
|
|
70
|
+
test("resolveForgePackageRoot finds package root from compiled dist layout", () => {
|
|
71
|
+
const tmp = mkdtempSync(join(tmpdir(), "forge-pkgroot-"));
|
|
72
|
+
try {
|
|
73
|
+
writeFileSync(join(tmp, "package.json"), JSON.stringify({ name: "@warpgogol/forge" }));
|
|
74
|
+
const distOnboarding = join(tmp, "dist", "src", "onboarding");
|
|
75
|
+
mkdirSync(distOnboarding, { recursive: true });
|
|
76
|
+
const srcTemplates = join(tmp, "src", "onboarding", "templates");
|
|
77
|
+
mkdirSync(srcTemplates, { recursive: true });
|
|
78
|
+
|
|
79
|
+
const root = resolveForgePackageRoot(distOnboarding);
|
|
80
|
+
expect(root).toBe(tmp);
|
|
81
|
+
expect(existsSync(join(root, "src", "onboarding", "templates"))).toBe(true);
|
|
82
|
+
expect(existsSync(join(root, "profiles"))).toBe(false);
|
|
83
|
+
} finally {
|
|
84
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("resolveForgePackageRoot finds package root from source layout", () => {
|
|
89
|
+
const tmp = mkdtempSync(join(tmpdir(), "forge-pkgroot-"));
|
|
90
|
+
try {
|
|
91
|
+
writeFileSync(join(tmp, "package.json"), JSON.stringify({ name: "@warpgogol/forge" }));
|
|
92
|
+
const srcOnboarding = join(tmp, "src", "onboarding");
|
|
93
|
+
mkdirSync(srcOnboarding, { recursive: true });
|
|
94
|
+
|
|
95
|
+
expect(resolveForgePackageRoot(srcOnboarding)).toBe(tmp);
|
|
96
|
+
} finally {
|
|
97
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("resolveForgePackageRoot resolves the real package root from src/onboarding", () => {
|
|
102
|
+
const root = resolveForgePackageRoot(join(PACKAGE_ROOT, "src", "onboarding"));
|
|
103
|
+
expect(root).toBe(PACKAGE_ROOT);
|
|
104
|
+
expect(existsSync(join(root, "src", "onboarding", "templates"))).toBe(true);
|
|
105
|
+
expect(existsSync(join(root, "profiles"))).toBe(true);
|
|
106
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { test, expect, describe } from "vitest";
|
|
2
|
+
import { mkdtemp, rm, mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { execFile } from "node:child_process";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { runPinnedValidate } from "../../os/core/handlers/pinned-validate.ts";
|
|
8
|
+
import type { ForgeCommandInput, ForgeRuntimeContext } from "../../src/types.ts";
|
|
9
|
+
|
|
10
|
+
const execFileAsync = promisify(execFile);
|
|
11
|
+
|
|
12
|
+
async function makeTempDir(): Promise<string> {
|
|
13
|
+
return mkdtemp(join(tmpdir(), "forge-pinned-validate-test-"));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function makeContext(workspaceRoot: string): ForgeRuntimeContext {
|
|
17
|
+
return {
|
|
18
|
+
workspaceRoot,
|
|
19
|
+
logger: {
|
|
20
|
+
section: () => {},
|
|
21
|
+
info: () => {},
|
|
22
|
+
warn: () => {},
|
|
23
|
+
error: () => {},
|
|
24
|
+
success: () => {},
|
|
25
|
+
},
|
|
26
|
+
dryRun: false,
|
|
27
|
+
outputFormat: "json",
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function makeInput(): ForgeCommandInput {
|
|
32
|
+
return { argv: [], flags: {} };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function git(dir: string, args: string[]): Promise<void> {
|
|
36
|
+
await execFileAsync("git", args, { cwd: dir });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Scaffold a git repo with a committed pinned.yaml protecting docs/rfcs/
|
|
41
|
+
* and one committed RFC file. Returns the repo dir.
|
|
42
|
+
*/
|
|
43
|
+
async function makeRepo(): Promise<string> {
|
|
44
|
+
const dir = await makeTempDir();
|
|
45
|
+
await git(dir, ["init"]);
|
|
46
|
+
await git(dir, ["config", "user.email", "test@example.com"]);
|
|
47
|
+
await git(dir, ["config", "user.name", "Test"]);
|
|
48
|
+
await mkdir(join(dir, ".forge"), { recursive: true });
|
|
49
|
+
await writeFile(
|
|
50
|
+
join(dir, ".forge", "pinned.yaml"),
|
|
51
|
+
"pinned:\n - path: docs/rfcs/\n mode: protect\n reason: RFC directory\n",
|
|
52
|
+
);
|
|
53
|
+
await mkdir(join(dir, "docs", "rfcs"), { recursive: true });
|
|
54
|
+
await writeFile(join(dir, "docs", "rfcs", "rfc-0001.md"), "# RFC 1\n");
|
|
55
|
+
await git(dir, ["add", "-A"]);
|
|
56
|
+
await git(dir, ["commit", "-m", "init"]);
|
|
57
|
+
return dir;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
describe("runPinnedValidate", () => {
|
|
61
|
+
test("passes for intra-directory move inside a pinned dir (docs.archive)", async () => {
|
|
62
|
+
const dir = await makeRepo();
|
|
63
|
+
try {
|
|
64
|
+
// Simulate docs.archive: docs/rfcs/rfc-0001.md → docs/rfcs/archive/implemented/rfc-0001.md
|
|
65
|
+
await mkdir(join(dir, "docs", "rfcs", "archive", "implemented"), { recursive: true });
|
|
66
|
+
await git(dir, ["mv", "docs/rfcs/rfc-0001.md", "docs/rfcs/archive/implemented/rfc-0001.md"]);
|
|
67
|
+
|
|
68
|
+
const result = await runPinnedValidate(makeInput(), makeContext(dir));
|
|
69
|
+
expect(result.data?.status).toBe("pass");
|
|
70
|
+
expect(result.data?.violations).toHaveLength(0);
|
|
71
|
+
expect(result.exitCode).toBe(0);
|
|
72
|
+
} finally {
|
|
73
|
+
await rm(dir, { recursive: true, force: true });
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("flags move out of a pinned dir", async () => {
|
|
78
|
+
const dir = await makeRepo();
|
|
79
|
+
try {
|
|
80
|
+
await mkdir(join(dir, "docs", "other"), { recursive: true });
|
|
81
|
+
await git(dir, ["mv", "docs/rfcs/rfc-0001.md", "docs/other/rfc-0001.md"]);
|
|
82
|
+
|
|
83
|
+
const result = await runPinnedValidate(makeInput(), makeContext(dir));
|
|
84
|
+
expect(result.data?.status).toBe("fail");
|
|
85
|
+
expect(result.data?.violations.length).toBeGreaterThan(0);
|
|
86
|
+
expect(
|
|
87
|
+
result.data?.violations.some(
|
|
88
|
+
(v) => v.path === "docs/rfcs/rfc-0001.md" && v.operation === "move",
|
|
89
|
+
),
|
|
90
|
+
).toBe(true);
|
|
91
|
+
} finally {
|
|
92
|
+
await rm(dir, { recursive: true, force: true });
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("flags move into a pinned dir", async () => {
|
|
97
|
+
const dir = await makeRepo();
|
|
98
|
+
try {
|
|
99
|
+
// Source must be committed first — otherwise git records the staged
|
|
100
|
+
// result as A (add), not R (rename).
|
|
101
|
+
await writeFile(join(dir, "incoming.md"), "# incoming\n");
|
|
102
|
+
await git(dir, ["add", "incoming.md"]);
|
|
103
|
+
await git(dir, ["commit", "-m", "add incoming"]);
|
|
104
|
+
await git(dir, ["mv", "incoming.md", "docs/rfcs/incoming.md"]);
|
|
105
|
+
|
|
106
|
+
const result = await runPinnedValidate(makeInput(), makeContext(dir));
|
|
107
|
+
expect(result.data?.status).toBe("fail");
|
|
108
|
+
expect(
|
|
109
|
+
result.data?.violations.some(
|
|
110
|
+
(v) => v.path === "docs/rfcs/incoming.md" && v.operation === "move",
|
|
111
|
+
),
|
|
112
|
+
).toBe(true);
|
|
113
|
+
} finally {
|
|
114
|
+
await rm(dir, { recursive: true, force: true });
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("flags delete of a pinned file", async () => {
|
|
119
|
+
const dir = await makeRepo();
|
|
120
|
+
try {
|
|
121
|
+
await git(dir, ["rm", "docs/rfcs/rfc-0001.md"]);
|
|
122
|
+
|
|
123
|
+
const result = await runPinnedValidate(makeInput(), makeContext(dir));
|
|
124
|
+
expect(result.data?.status).toBe("fail");
|
|
125
|
+
expect(
|
|
126
|
+
result.data?.violations.some(
|
|
127
|
+
(v) => v.path === "docs/rfcs/rfc-0001.md" && v.operation === "delete",
|
|
128
|
+
),
|
|
129
|
+
).toBe(true);
|
|
130
|
+
} finally {
|
|
131
|
+
await rm(dir, { recursive: true, force: true });
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -162,16 +162,16 @@ test("all shipped profiles parse without changes", () => {
|
|
|
162
162
|
expect(profile.id).toBeDefined();
|
|
163
163
|
expect(profile.workspace.dirs.length).toBeGreaterThan(0);
|
|
164
164
|
// Domain fields should be undefined for existing software-domain profiles
|
|
165
|
-
if (profile.id !== "godot-
|
|
165
|
+
if (profile.id !== "godot-game") {
|
|
166
166
|
expect(profile.domain).toBeUndefined();
|
|
167
167
|
expect(profile.register).toBeUndefined();
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
});
|
|
171
171
|
|
|
172
|
-
test("shipped phaser-
|
|
173
|
-
const profile = loadStackProfile(join(FORGE_ROOT, "profiles", "phaser-
|
|
174
|
-
expect(profile.id).toBe("phaser-
|
|
172
|
+
test("shipped phaser-game profile loads via loadStackProfile", () => {
|
|
173
|
+
const profile = loadStackProfile(join(FORGE_ROOT, "profiles", "phaser-game.yaml"));
|
|
174
|
+
expect(profile.id).toBe("phaser-game");
|
|
175
175
|
});
|
|
176
176
|
|
|
177
177
|
test("UNIVERSAL_TERMINOLOGY_KEYS contains the 7 documented keys", () => {
|
|
@@ -73,7 +73,7 @@ test("forge.scaffold fails on missing --profile", async () => {
|
|
|
73
73
|
|
|
74
74
|
test("forge.scaffold derives name from folder when --name omitted", async () => {
|
|
75
75
|
const result = await runScaffoldProject(
|
|
76
|
-
{ argv: [], flags: { profile: "phaser-
|
|
76
|
+
{ argv: [], flags: { profile: "phaser-game" } },
|
|
77
77
|
{ ...makeContext(tempDir), forgeRoot: FORGE_ROOT },
|
|
78
78
|
);
|
|
79
79
|
// Name is now derived from the folder name (consistent with forge.create)
|
|
@@ -89,7 +89,7 @@ test("forge.scaffold derives name from folder when --name omitted", async () =>
|
|
|
89
89
|
|
|
90
90
|
test("forge.scaffold fails on non-kebab-case name", async () => {
|
|
91
91
|
const result = await runScaffoldProject(
|
|
92
|
-
{ argv: [], flags: { profile: "phaser-
|
|
92
|
+
{ argv: [], flags: { profile: "phaser-game", name: "MySite" } },
|
|
93
93
|
{ ...makeContext(tempDir), forgeRoot: FORGE_ROOT },
|
|
94
94
|
);
|
|
95
95
|
expect(result.exitCode).toBe(1);
|
|
@@ -129,7 +129,7 @@ test("forge.init --from detects stack from existing project", async () => {
|
|
|
129
129
|
const result = runInit({ flags: { from: fakeProject } }, { workspaceRoot: WORKSPACE_ROOT });
|
|
130
130
|
expect(result.status).toBe("pass");
|
|
131
131
|
expect(result.detection).toBeDefined();
|
|
132
|
-
expect(result.detection?.profile).toBe("phaser-
|
|
132
|
+
expect(result.detection?.profile).toBe("phaser-game");
|
|
133
133
|
});
|
|
134
134
|
|
|
135
135
|
test("forge.init --from reports null when stack undetectable", async () => {
|
|
@@ -139,31 +139,31 @@ test("detectStack matches on exact filename", async () => {
|
|
|
139
139
|
expect(match?.id).toBe("ts");
|
|
140
140
|
});
|
|
141
141
|
|
|
142
|
-
test("shipped phaser-
|
|
143
|
-
const profile = loadStackProfile(join(FORGE_ROOT, "profiles", "phaser-
|
|
144
|
-
expect(profile.id).toBe("phaser-
|
|
142
|
+
test("shipped phaser-game profile validates", () => {
|
|
143
|
+
const profile = loadStackProfile(join(FORGE_ROOT, "profiles", "phaser-game.yaml"));
|
|
144
|
+
expect(profile.id).toBe("phaser-game");
|
|
145
145
|
expect(profile.workspace.dirs.length).toBeGreaterThan(0);
|
|
146
146
|
});
|
|
147
147
|
|
|
148
|
-
test("shipped godot-
|
|
149
|
-
const profile = loadStackProfile(join(FORGE_ROOT, "profiles", "godot-
|
|
150
|
-
expect(profile.id).toBe("godot-
|
|
148
|
+
test("shipped godot-game profile validates", () => {
|
|
149
|
+
const profile = loadStackProfile(join(FORGE_ROOT, "profiles", "godot-game.yaml"));
|
|
150
|
+
expect(profile.id).toBe("godot-game");
|
|
151
151
|
expect(profile.workspace.dirs.length).toBeGreaterThan(0);
|
|
152
152
|
});
|
|
153
153
|
|
|
154
154
|
test("listStackProfiles finds all shipped profiles", () => {
|
|
155
155
|
const profiles = listStackProfiles(FORGE_ROOT);
|
|
156
156
|
const ids = profiles.map((p) => p.id).sort();
|
|
157
|
-
expect(ids).toContain("phaser-
|
|
158
|
-
expect(ids).toContain("godot-
|
|
157
|
+
expect(ids).toContain("phaser-game");
|
|
158
|
+
expect(ids).toContain("godot-game");
|
|
159
159
|
expect(ids).toContain("forge-shell");
|
|
160
160
|
});
|
|
161
161
|
|
|
162
162
|
test("all shipped profiles include @warpgogol/forge in install steps or package.json template", () => {
|
|
163
163
|
const profiles = listStackProfiles(FORGE_ROOT);
|
|
164
164
|
for (const profile of profiles) {
|
|
165
|
-
// knowledge
|
|
166
|
-
if (profile.id === "knowledge
|
|
165
|
+
// knowledge uses @warpgogol/werkstatt-knowledge plugin, not forge
|
|
166
|
+
if (profile.id === "knowledge") continue;
|
|
167
167
|
const hasForgeInInstall = profile.install.some((cmd) => cmd.includes("@warpgogol/forge"));
|
|
168
168
|
const pkgFile = profile.workspace.files.find((f) => f.path === "package.json");
|
|
169
169
|
const hasForgeInPkg = pkgFile?.content.includes("@warpgogol/forge") ?? false;
|
|
@@ -174,8 +174,8 @@ test("all shipped profiles include @warpgogol/forge in install steps or package.
|
|
|
174
174
|
test("all shipped profiles include operator-profile.md in .gitignore content", () => {
|
|
175
175
|
const profiles = listStackProfiles(FORGE_ROOT);
|
|
176
176
|
for (const profile of profiles) {
|
|
177
|
-
// knowledge
|
|
178
|
-
if (profile.id === "knowledge
|
|
177
|
+
// knowledge uses a different plugin and gitignore template
|
|
178
|
+
if (profile.id === "knowledge") continue;
|
|
179
179
|
const gitignoreFile = profile.workspace.files.find((f) => f.path === ".gitignore");
|
|
180
180
|
expect(gitignoreFile).toBeDefined();
|
|
181
181
|
expect(gitignoreFile?.content).toContain("operator-profile.md");
|