@warpgogol/forge 5.2.3 → 5.2.4

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.
Files changed (51) hide show
  1. package/AGENTS.md +2 -0
  2. package/dist/os/core/core.module.d.ts.map +1 -1
  3. package/dist/os/core/core.module.js +14 -0
  4. package/dist/os/core/core.module.js.map +1 -1
  5. package/dist/src/config/forge-config.d.ts +9 -0
  6. package/dist/src/config/forge-config.d.ts.map +1 -1
  7. package/dist/src/config/forge-config.js +7 -0
  8. package/dist/src/config/forge-config.js.map +1 -1
  9. package/dist/src/onboarding/agents-generate.d.ts +2 -0
  10. package/dist/src/onboarding/agents-generate.d.ts.map +1 -1
  11. package/dist/src/onboarding/agents-generate.js +14 -18
  12. package/dist/src/onboarding/agents-generate.js.map +1 -1
  13. package/dist/src/onboarding/doctor.d.ts +6 -0
  14. package/dist/src/onboarding/doctor.d.ts.map +1 -1
  15. package/dist/src/onboarding/doctor.js +117 -8
  16. package/dist/src/onboarding/doctor.js.map +1 -1
  17. package/dist/src/onboarding/memory-compact.d.ts +22 -0
  18. package/dist/src/onboarding/memory-compact.d.ts.map +1 -0
  19. package/dist/src/onboarding/memory-compact.js +147 -0
  20. package/dist/src/onboarding/memory-compact.js.map +1 -0
  21. package/dist/src/onboarding/memory-scaffold.d.ts +1 -0
  22. package/dist/src/onboarding/memory-scaffold.d.ts.map +1 -1
  23. package/dist/src/onboarding/memory-scaffold.js +1 -1
  24. package/dist/src/onboarding/memory-scaffold.js.map +1 -1
  25. package/dist/src/onboarding/nested-agents-generate.d.ts.map +1 -1
  26. package/dist/src/onboarding/nested-agents-generate.js +1 -1
  27. package/dist/src/onboarding/nested-agents-generate.js.map +1 -1
  28. package/dist/src/onboarding/workspace-discovery.d.ts +1 -1
  29. package/dist/src/onboarding/workspace-discovery.d.ts.map +1 -1
  30. package/dist/src/onboarding/workspace-discovery.js +14 -2
  31. package/dist/src/onboarding/workspace-discovery.js.map +1 -1
  32. package/docs/reference/forge-yaml.md +12 -0
  33. package/os/core/core.module.ts +15 -0
  34. package/package.json +1 -1
  35. package/profiles/forge-shell.yaml +3 -0
  36. package/profiles/godot-game.yaml +3 -0
  37. package/profiles/phaser-game.yaml +3 -0
  38. package/profiles/site-workshop.yaml +567 -564
  39. package/src/config/forge-config.ts +11 -0
  40. package/src/onboarding/agents-generate.ts +18 -18
  41. package/src/onboarding/doctor.ts +134 -9
  42. package/src/onboarding/memory-compact.ts +188 -0
  43. package/src/onboarding/memory-scaffold.ts +1 -1
  44. package/src/onboarding/nested-agents-generate.ts +5 -1
  45. package/src/onboarding/workspace-discovery.ts +15 -1
  46. package/src/tests/agents-generate.test.ts +26 -3
  47. package/src/tests/doctor-dist-freshness.test.ts +110 -0
  48. package/src/tests/doctor-fix.test.ts +145 -0
  49. package/src/tests/doctor-nested-agents.test.ts +108 -0
  50. package/src/tests/memory-compact.test.ts +143 -0
  51. package/src/tests/workspace-discovery.test.ts +28 -0
@@ -0,0 +1,110 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Unit tests for the dist-freshness doctor check — warns when a forge
4
+ source checkout's dist/ is older than the newest src/ file (RFC-1151).</purpose>
5
+ </MODULE_CONTRACT>
6
+ <CHANGE_SUMMARY>
7
+ <item>RFC-1151: initial dist-freshness tests.</item>
8
+ </CHANGE_SUMMARY>
9
+ */
10
+
11
+ import { test, expect, beforeEach, afterEach } from "vitest";
12
+ import { mkdtemp, rm, mkdir, writeFile, utimes } from "node:fs/promises";
13
+ import { tmpdir } from "node:os";
14
+ import { join } from "node:path";
15
+ import { runDoctor } from "../onboarding/doctor.ts";
16
+ import type { ForgeCommandInput, ForgeRuntimeContext } from "../types.ts";
17
+
18
+ let tempDir: string;
19
+
20
+ beforeEach(async () => {
21
+ tempDir = await mkdtemp(join(tmpdir(), "doctor-dist-test-"));
22
+ });
23
+
24
+ afterEach(async () => {
25
+ await rm(tempDir, { recursive: true, force: true });
26
+ });
27
+
28
+ function makeContext(): ForgeRuntimeContext {
29
+ return {
30
+ workspaceRoot: tempDir,
31
+ // forgeRoot falls back to <workspaceRoot>/packages/forge when unresolvable
32
+ forgeRoot: join(tempDir, "packages", "forge"),
33
+ logger: {
34
+ section: () => {},
35
+ success: () => {},
36
+ warn: () => {},
37
+ error: () => {},
38
+ info: () => {},
39
+ },
40
+ dryRun: false,
41
+ outputFormat: "json",
42
+ };
43
+ }
44
+
45
+ const FORGE_YAML = `schema: "forge/config@1"
46
+ project:
47
+ name: test-project
48
+ stack: []
49
+ packageManager: pnpm
50
+ paths:
51
+ rfcsDir: docs/rfcs
52
+ adrsDir: docs/adrs
53
+ plansDir: docs/plans
54
+ auditsDir: docs/audits
55
+ specsDir: docs/specs
56
+ skillsDir: .agents/skills
57
+ sessionsDir: docs/sessions
58
+ `;
59
+
60
+ async function makeForgeCheckout(srcMtime: Date, distMtime: Date): Promise<void> {
61
+ const forgeRoot = join(tempDir, "packages", "forge");
62
+ await mkdir(join(forgeRoot, "src"), { recursive: true });
63
+ await mkdir(join(forgeRoot, "dist"), { recursive: true });
64
+ await writeFile(join(forgeRoot, "package.json"), JSON.stringify({ name: "@warpgogol/forge" }));
65
+ await writeFile(join(forgeRoot, "src", "index.ts"), "export {};\n");
66
+ await writeFile(join(forgeRoot, "dist", "index.js"), "export {};\n");
67
+ await utimes(join(forgeRoot, "src", "index.ts"), srcMtime, srcMtime);
68
+ await utimes(join(forgeRoot, "dist"), distMtime, distMtime);
69
+ }
70
+
71
+ test("dist-freshness warns when dist is older than newest src file", async () => {
72
+ await writeFile(join(tempDir, "forge.yaml"), FORGE_YAML, "utf8");
73
+ const old = new Date("2026-01-01T00:00:00Z");
74
+ const recent = new Date("2026-09-01T00:00:00Z");
75
+ await makeForgeCheckout(recent, old);
76
+
77
+ const input: ForgeCommandInput = { argv: [], flags: {} };
78
+ const result = await runDoctor(input, makeContext());
79
+
80
+ const check = result.data!.checks.find((c) => c.name === "dist-freshness");
81
+ expect(check).toBeDefined();
82
+ expect(check!.status).toBe("warn");
83
+ expect(check!.message).toContain("pnpm run build");
84
+ });
85
+
86
+ test("dist-freshness passes when dist is absent (npm consumer layout)", async () => {
87
+ await writeFile(join(tempDir, "forge.yaml"), FORGE_YAML, "utf8");
88
+ // No packages/forge at all — forgeRoot fallback dir doesn't exist
89
+ const input: ForgeCommandInput = { argv: [], flags: {} };
90
+ const result = await runDoctor(input, makeContext());
91
+
92
+ const check = result.data!.checks.find((c) => c.name === "dist-freshness");
93
+ expect(check).toBeDefined();
94
+ expect(check!.status).toBe("pass");
95
+ });
96
+
97
+ test("dist-freshness passes when dist is newer than src", async () => {
98
+ await writeFile(join(tempDir, "forge.yaml"), FORGE_YAML, "utf8");
99
+ const old = new Date("2026-01-01T00:00:00Z");
100
+ const recent = new Date("2026-09-01T00:00:00Z");
101
+ await makeForgeCheckout(old, recent);
102
+
103
+ const input: ForgeCommandInput = { argv: [], flags: {} };
104
+ const result = await runDoctor(input, makeContext());
105
+
106
+ const check = result.data!.checks.find((c) => c.name === "dist-freshness");
107
+ expect(check).toBeDefined();
108
+ expect(check!.status).toBe("pass");
109
+ expect(check!.message).toContain("fresh");
110
+ });
@@ -0,0 +1,145 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Unit tests for forge doctor --fix — safe remediations for warn/fail
4
+ checks: nested-AGENTS.md regeneration and memory-layer compaction (RFC-1151).</purpose>
5
+ </MODULE_CONTRACT>
6
+ <CHANGE_SUMMARY>
7
+ <item>RFC-1151: initial doctor --fix tests.</item>
8
+ </CHANGE_SUMMARY>
9
+ */
10
+
11
+ import { test, expect, beforeEach, afterEach } from "vitest";
12
+ import { mkdtemp, rm, mkdir, writeFile, readFile } from "node:fs/promises";
13
+ import { tmpdir } from "node:os";
14
+ import { join } from "node:path";
15
+ import { runDoctor } from "../onboarding/doctor.ts";
16
+ import type { ForgeCommandInput, ForgeRuntimeContext } from "../types.ts";
17
+
18
+ let tempDir: string;
19
+
20
+ beforeEach(async () => {
21
+ tempDir = await mkdtemp(join(tmpdir(), "doctor-fix-test-"));
22
+ });
23
+
24
+ afterEach(async () => {
25
+ await rm(tempDir, { recursive: true, force: true });
26
+ });
27
+
28
+ function makeContext(dryRun = false): ForgeRuntimeContext {
29
+ return {
30
+ workspaceRoot: tempDir,
31
+ logger: {
32
+ section: () => {},
33
+ success: () => {},
34
+ warn: () => {},
35
+ error: () => {},
36
+ info: () => {},
37
+ },
38
+ dryRun,
39
+ outputFormat: "json",
40
+ };
41
+ }
42
+
43
+ const FORGE_YAML = `schema: "forge/config@1"
44
+ project:
45
+ name: test-project
46
+ stack: []
47
+ packageManager: pnpm
48
+ paths:
49
+ rfcsDir: docs/rfcs
50
+ adrsDir: docs/adrs
51
+ plansDir: docs/plans
52
+ auditsDir: docs/audits
53
+ specsDir: docs/specs
54
+ skillsDir: .agents/skills
55
+ sessionsDir: docs/sessions
56
+ `;
57
+
58
+ const GENERATED_MARKER =
59
+ "<!--\n GENERATED. Do not change this line unless the file contains project specific changes.\n-->\n";
60
+
61
+ async function makeStaleWorkspace(): Promise<void> {
62
+ await mkdir(join(tempDir, "packages", "my-pkg"), { recursive: true });
63
+ await writeFile(
64
+ join(tempDir, "packages", "my-pkg", "package.json"),
65
+ JSON.stringify({ name: "@test/my-pkg" }),
66
+ );
67
+ // Generated marker + outdated content → stale
68
+ await writeFile(
69
+ join(tempDir, "packages", "my-pkg", "AGENTS.md"),
70
+ `${GENERATED_MARKER}# Old content\n`,
71
+ "utf8",
72
+ );
73
+ }
74
+
75
+ test("doctor --fix regenerates stale nested AGENTS.md and subsequent run passes", async () => {
76
+ await writeFile(join(tempDir, "forge.yaml"), FORGE_YAML, "utf8");
77
+ await makeStaleWorkspace();
78
+
79
+ const fixInput: ForgeCommandInput = { argv: [], flags: { fix: true } };
80
+ const fixResult = await runDoctor(fixInput, makeContext());
81
+
82
+ const fixEntry = fixResult.data!.fixes?.find((f) => f.check === "nested-AGENTS.md");
83
+ expect(fixEntry).toBeDefined();
84
+ expect(fixEntry!.action).toBe("fixed");
85
+
86
+ const regenerated = await readFile(
87
+ join(tempDir, "packages", "my-pkg", "AGENTS.md"),
88
+ "utf8",
89
+ );
90
+ expect(regenerated).toContain("`@test/my-pkg` — Agent Guide");
91
+ expect(regenerated).not.toContain("Old content");
92
+
93
+ // Subsequent doctor run: nested-AGENTS.md no longer reports this file stale
94
+ const verifyResult = await runDoctor({ argv: [], flags: {} }, makeContext());
95
+ const check = verifyResult.data!.checks.find((c) => c.name === "nested-AGENTS.md");
96
+ expect(check!.message).not.toContain("my-pkg/AGENTS.md stale");
97
+ });
98
+
99
+ test("doctor --fix does not modify hand-written nested AGENTS.md", async () => {
100
+ await writeFile(join(tempDir, "forge.yaml"), FORGE_YAML, "utf8");
101
+ await mkdir(join(tempDir, "packages", "my-pkg"), { recursive: true });
102
+ await writeFile(
103
+ join(tempDir, "packages", "my-pkg", "package.json"),
104
+ JSON.stringify({ name: "@test/my-pkg" }),
105
+ );
106
+ const handwritten = "# Custom\nNo marker.\n";
107
+ await writeFile(join(tempDir, "packages", "my-pkg", "AGENTS.md"), handwritten, "utf8");
108
+
109
+ const result = await runDoctor({ argv: [], flags: { fix: true } }, makeContext());
110
+ expect(result.data!.fixes).toBeDefined();
111
+
112
+ const after = await readFile(join(tempDir, "packages", "my-pkg", "AGENTS.md"), "utf8");
113
+ expect(after).toBe(handwritten);
114
+ });
115
+
116
+ test("doctor --fix compacts over-budget MEMORY.md", async () => {
117
+ await writeFile(join(tempDir, "forge.yaml"), FORGE_YAML, "utf8");
118
+ await mkdir(join(tempDir, ".agents", "memory"), { recursive: true });
119
+ const env = Array.from({ length: 30 }, (_, i) => `- env ${i} ${"x".repeat(200)}`).join("\n");
120
+ await writeFile(
121
+ join(tempDir, ".agents", "memory", "MEMORY.md"),
122
+ `# Project Memory\n\n## Current focus\n\n- keep\n\n## Environment notes\n\n${env}\n`,
123
+ "utf8",
124
+ );
125
+
126
+ const result = await runDoctor({ argv: [], flags: { fix: true } }, makeContext());
127
+ const fixEntry = result.data!.fixes?.find((f) => f.check === "memory-layer");
128
+ expect(fixEntry).toBeDefined();
129
+ expect(fixEntry!.action).toBe("fixed");
130
+
131
+ const after = await readFile(join(tempDir, ".agents", "memory", "MEMORY.md"), "utf8");
132
+ expect(after.length).toBeLessThanOrEqual(4096);
133
+ expect(after).toContain("- keep");
134
+ });
135
+
136
+ test("doctor without --fix produces no fixes array and does not mutate", async () => {
137
+ await writeFile(join(tempDir, "forge.yaml"), FORGE_YAML, "utf8");
138
+ await makeStaleWorkspace();
139
+
140
+ const result = await runDoctor({ argv: [], flags: {} }, makeContext());
141
+ expect(result.data!.fixes).toBeUndefined();
142
+
143
+ const after = await readFile(join(tempDir, "packages", "my-pkg", "AGENTS.md"), "utf8");
144
+ expect(after).toContain("Old content");
145
+ });
@@ -0,0 +1,108 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Unit tests for the nested-AGENTS.md doctor check — consumer-declared
4
+ workspace discovery exclusions via bindings.workspaces.skipDirs (RFC-1150).</purpose>
5
+ </MODULE_CONTRACT>
6
+ <CHANGE_SUMMARY>
7
+ <item>RFC-1150: initial nested-AGENTS.md skipDirs tests.</item>
8
+ </CHANGE_SUMMARY>
9
+ */
10
+
11
+ import { test, expect, beforeEach, afterEach } from "vitest";
12
+ import { mkdtemp, rm, mkdir, writeFile } from "node:fs/promises";
13
+ import { tmpdir } from "node:os";
14
+ import { join } from "node:path";
15
+ import { runDoctor } from "../onboarding/doctor.ts";
16
+ import type { ForgeCommandInput, ForgeRuntimeContext } from "../types.ts";
17
+
18
+ let tempDir: string;
19
+
20
+ beforeEach(async () => {
21
+ tempDir = await mkdtemp(join(tmpdir(), "doctor-nested-test-"));
22
+ });
23
+
24
+ afterEach(async () => {
25
+ await rm(tempDir, { recursive: true, force: true });
26
+ });
27
+
28
+ function makeContext(): ForgeRuntimeContext {
29
+ return {
30
+ workspaceRoot: tempDir,
31
+ logger: {
32
+ section: () => {},
33
+ success: () => {},
34
+ warn: () => {},
35
+ error: () => {},
36
+ info: () => {},
37
+ },
38
+ dryRun: false,
39
+ outputFormat: "json",
40
+ };
41
+ }
42
+
43
+ function forgeYaml(skipDirs: string[]): string {
44
+ const skip = skipDirs.length > 0 ? `\n workspaces:\n skipDirs: [${skipDirs.join(", ")}]` : "";
45
+ return `schema: "forge/config@1"
46
+ project:
47
+ name: test-project
48
+ stack: []
49
+ packageManager: pnpm
50
+ paths:
51
+ rfcsDir: docs/rfcs
52
+ adrsDir: docs/adrs
53
+ plansDir: docs/plans
54
+ auditsDir: docs/audits
55
+ specsDir: docs/specs
56
+ skillsDir: .agents/skills
57
+ sessionsDir: docs/sessions
58
+ bindings:
59
+ schema: forge/bindings@1
60
+ commands:
61
+ validateRfc: null
62
+ validateAdr: null
63
+ implementStamp: null
64
+ typecheck: null
65
+ test: null
66
+ scopedBuild: null
67
+ specValidate: null
68
+ sessionSave: null
69
+ paths:
70
+ invariantsFile: null
71
+ compassDocs: []
72
+ reviewsDir: null
73
+ handoffsDir: null
74
+ sessionsDir: null${skip}
75
+ `;
76
+ }
77
+
78
+ test("nested-AGENTS.md check excludes dirs declared in workspaces.skipDirs", async () => {
79
+ await writeFile(join(tempDir, "forge.yaml"), forgeYaml(["builds"]), "utf8");
80
+ // Artifact dir that would otherwise be discovered as a workspace
81
+ await mkdir(join(tempDir, "builds", "out"), { recursive: true });
82
+ await writeFile(join(tempDir, "builds", "out", "package.json"), "{}");
83
+ // Real workspace without AGENTS.md — must still be reported missing
84
+ await mkdir(join(tempDir, "packages", "real-pkg"), { recursive: true });
85
+ await writeFile(join(tempDir, "packages", "real-pkg", "package.json"), "{}");
86
+
87
+ const input: ForgeCommandInput = { argv: [], flags: {} };
88
+ const result = await runDoctor(input, makeContext());
89
+
90
+ const check = result.data!.checks.find((c) => c.name === "nested-AGENTS.md");
91
+ expect(check).toBeDefined();
92
+ // missing-only reports pass (warn requires stale>0) — assert on message
93
+ expect(check!.message).toContain("packages/real-pkg");
94
+ expect(check!.message).not.toContain("builds");
95
+ });
96
+
97
+ test("nested-AGENTS.md check reports artifact dir as missing without skipDirs", async () => {
98
+ await writeFile(join(tempDir, "forge.yaml"), forgeYaml([]), "utf8");
99
+ await mkdir(join(tempDir, "builds", "out"), { recursive: true });
100
+ await writeFile(join(tempDir, "builds", "out", "package.json"), "{}");
101
+
102
+ const input: ForgeCommandInput = { argv: [], flags: {} };
103
+ const result = await runDoctor(input, makeContext());
104
+
105
+ const check = result.data!.checks.find((c) => c.name === "nested-AGENTS.md");
106
+ expect(check).toBeDefined();
107
+ expect(check!.message).toContain("builds");
108
+ });
@@ -0,0 +1,143 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Unit tests for forge memory.compact — mechanical MEMORY.md budget
4
+ enforcement via oldest-first Environment notes truncation (RFC-1151).</purpose>
5
+ </MODULE_CONTRACT>
6
+ <CHANGE_SUMMARY>
7
+ <item>RFC-1151: initial memory.compact tests.</item>
8
+ </CHANGE_SUMMARY>
9
+ */
10
+
11
+ import { test, expect, beforeEach, afterEach } from "vitest";
12
+ import { mkdtemp, rm, mkdir, writeFile, readFile } from "node:fs/promises";
13
+ import { tmpdir } from "node:os";
14
+ import { join } from "node:path";
15
+ import { runMemoryCompact, compactMemoryMd } from "../onboarding/memory-compact.ts";
16
+ import type { ForgeRuntimeContext } from "../types.ts";
17
+
18
+ let tempDir: string;
19
+
20
+ beforeEach(async () => {
21
+ tempDir = await mkdtemp(join(tmpdir(), "memory-compact-test-"));
22
+ await mkdir(join(tempDir, ".agents", "memory"), { recursive: true });
23
+ });
24
+
25
+ afterEach(async () => {
26
+ await rm(tempDir, { recursive: true, force: true });
27
+ });
28
+
29
+ function makeContext(dryRun = false): ForgeRuntimeContext {
30
+ return {
31
+ workspaceRoot: tempDir,
32
+ logger: {
33
+ section: () => {},
34
+ success: () => {},
35
+ warn: () => {},
36
+ error: () => {},
37
+ info: () => {},
38
+ },
39
+ dryRun,
40
+ outputFormat: "json",
41
+ };
42
+ }
43
+
44
+ function memoryMd(envBullets: number, bulletSize = 200): string {
45
+ const env = Array.from(
46
+ { length: envBullets },
47
+ (_, i) => `- env note ${i} ${"x".repeat(bulletSize)}`,
48
+ ).join("\n");
49
+ return [
50
+ "# Project Memory",
51
+ "",
52
+ "## Current focus",
53
+ "",
54
+ "- keep me",
55
+ "",
56
+ "## Decisions in flight",
57
+ "",
58
+ "- keep me too",
59
+ "",
60
+ "## Environment notes",
61
+ "",
62
+ env,
63
+ "",
64
+ ].join("\n");
65
+ }
66
+
67
+ test("compactMemoryMd removes oldest Environment notes bullets until within budget", () => {
68
+ const content = memoryMd(20);
69
+ const budget = 2000;
70
+ const { lines, removedLines, fitsBudget } = compactMemoryMd(content, budget);
71
+
72
+ expect(fitsBudget).toBe(true);
73
+ expect(removedLines.length).toBeGreaterThan(0);
74
+ // Oldest-first: env note 0 removed before env note 19
75
+ expect(removedLines[0]).toContain("env note 0");
76
+ expect(lines.join("\n").length).toBeLessThanOrEqual(budget);
77
+ // Protected sections untouched
78
+ expect(lines.join("\n")).toContain("- keep me");
79
+ expect(lines.join("\n")).toContain("- keep me too");
80
+ });
81
+
82
+ test("compactMemoryMd returns empty removedLines when already within budget", () => {
83
+ const content = memoryMd(2, 10);
84
+ const { removedLines, fitsBudget } = compactMemoryMd(content, 4096);
85
+ expect(removedLines).toHaveLength(0);
86
+ expect(fitsBudget).toBe(true);
87
+ });
88
+
89
+ test("runMemoryCompact writes compacted file and reports removed count", async () => {
90
+ const content = memoryMd(30);
91
+ await writeFile(join(tempDir, ".agents", "memory", "MEMORY.md"), content, "utf8");
92
+
93
+ const result = await runMemoryCompact({ argv: [], flags: {} }, makeContext());
94
+ expect(result.exitCode).toBe(0);
95
+ expect(result.data?.status).toBe("pass");
96
+ expect(result.data?.removed).toBeGreaterThan(0);
97
+ expect(result.data?.remainingChars).toBeLessThanOrEqual(result.data!.budget);
98
+
99
+ const after = await readFile(join(tempDir, ".agents", "memory", "MEMORY.md"), "utf8");
100
+ expect(after.length).toBeLessThanOrEqual(result.data!.budget);
101
+ expect(after).toContain("- keep me");
102
+ });
103
+
104
+ test("runMemoryCompact dryRun reports without writing", async () => {
105
+ const content = memoryMd(30);
106
+ await writeFile(join(tempDir, ".agents", "memory", "MEMORY.md"), content, "utf8");
107
+
108
+ const result = await runMemoryCompact({ argv: [], flags: {} }, makeContext(true));
109
+ expect(result.exitCode).toBe(0);
110
+ expect(result.data?.removed).toBeGreaterThan(0);
111
+ expect(result.data?.removedLines?.length).toBe(result.data?.removed);
112
+
113
+ const after = await readFile(join(tempDir, ".agents", "memory", "MEMORY.md"), "utf8");
114
+ expect(after).toBe(content);
115
+ });
116
+
117
+ test("runMemoryCompact passes when MEMORY.md absent", async () => {
118
+ const result = await runMemoryCompact({ argv: [], flags: {} }, makeContext());
119
+ expect(result.exitCode).toBe(0);
120
+ expect(result.data?.removed).toBe(0);
121
+ });
122
+
123
+ test("runMemoryCompact fails when protected sections alone exceed budget", async () => {
124
+ // Huge Current focus — Environment notes empty, nothing removable
125
+ const content = [
126
+ "# Project Memory",
127
+ "",
128
+ "## Current focus",
129
+ "",
130
+ `- ${"y".repeat(6000)}`,
131
+ "",
132
+ "## Environment notes",
133
+ "",
134
+ "- small note",
135
+ "",
136
+ ].join("\n");
137
+ await writeFile(join(tempDir, ".agents", "memory", "MEMORY.md"), content, "utf8");
138
+
139
+ const result = await runMemoryCompact({ argv: [], flags: {} }, makeContext());
140
+ expect(result.exitCode).toBe(1);
141
+ expect(result.data?.status).toBe("fail");
142
+ expect(result.data?.errors[0]).toContain("manual editorial compaction");
143
+ });
@@ -104,6 +104,34 @@ test("discoverWorkspaces skips node_modules, .git, dist, .turbo, .cache, .agents
104
104
  expect(workspaces).toHaveLength(0);
105
105
  });
106
106
 
107
+ test("discoverWorkspaces skips missions, test-fixtures, .stryker-tmp", async () => {
108
+ // Pipeline-managed workpieces/archives, fixture workshops, and mutation-testing
109
+ // sandboxes are not project workspaces — their AGENTS.md is owned elsewhere.
110
+ for (const skip of ["missions", "test-fixtures", ".stryker-tmp"]) {
111
+ await mkdir(join(tempDir, skip, "sub"), { recursive: true });
112
+ await writeFile(join(tempDir, skip, "sub", "package.json"), "{}");
113
+ }
114
+
115
+ const workspaces = discoverWorkspaces(tempDir);
116
+ expect(workspaces).toHaveLength(0);
117
+ });
118
+
119
+ // RFC-1150: consumer-declared exclusions (bindings.workspaces.skipDirs)
120
+
121
+ test("discoverWorkspaces honors extraSkipDirs for consumer artifact dirs", async () => {
122
+ await mkdir(join(tempDir, "builds", "out"), { recursive: true });
123
+ await writeFile(join(tempDir, "builds", "out", "package.json"), "{}");
124
+ await mkdir(join(tempDir, "packages", "real-pkg"), { recursive: true });
125
+ await writeFile(join(tempDir, "packages", "real-pkg", "package.json"), "{}");
126
+
127
+ const without = discoverWorkspaces(tempDir);
128
+ expect(without.map((w) => w.path)).toContain(join("builds", "out"));
129
+
130
+ const withSkip = discoverWorkspaces(tempDir, undefined, ["builds"]);
131
+ expect(withSkip.map((w) => w.path)).not.toContain(join("builds", "out"));
132
+ expect(withSkip.map((w) => w.path)).toContain(join("packages", "real-pkg"));
133
+ });
134
+
107
135
  test("discoverWorkspaces detects generated AGENTS.md", async () => {
108
136
  await mkdir(join(tempDir, "packages", "my-pkg"), { recursive: true });
109
137
  await writeFile(join(tempDir, "packages", "my-pkg", "package.json"), "{}");