From 315bc1a7648058957f201e50e761ec12a58adfdc Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Tue, 21 Jul 2026 18:48:47 -0700 Subject: [PATCH] FN-8463: cover root-level File Scope files Extend File Scope regression coverage for root-level configuration and solution files. - Cover .NET and ecosystem-neutral root-level file entries - Verify task-store and public store validation exports share classification - Add a patch changeset for the File Scope validation fix Files changed: .changeset/fn-8463-file-scope-root-extensions.md | 7 +++ .../__tests__/file-scope-classification.test.ts | 64 +++++++++++++++++++++- 2 files changed, 69 insertions(+), 2 deletions(-) Fusion-Task-Id: FN-8463 Fusion-Task-Lineage: 149f0066-8ec5-4f9d-b5be-29f158a274cc Co-authored-by: Fusion (runfusion.ai) --- .../fn-8463-file-scope-root-extensions.md | 7 ++ .../file-scope-classification.test.ts | 64 ++++++++++++++++++- 2 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 .changeset/fn-8463-file-scope-root-extensions.md diff --git a/.changeset/fn-8463-file-scope-root-extensions.md b/.changeset/fn-8463-file-scope-root-extensions.md new file mode 100644 index 0000000000..e91ae4d6a5 --- /dev/null +++ b/.changeset/fn-8463-file-scope-root-extensions.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Accept root-level File Scope files with extensions such as global.json and solution files. +category: fix +dev: isValidFileScopeEntry no longer requires a slash; letter-leading final extensions share create/update validation with classification. Regression coverage tracks GitHub #2389. diff --git a/packages/core/src/__tests__/file-scope-classification.test.ts b/packages/core/src/__tests__/file-scope-classification.test.ts index 6225a7fc7e..5716c9c95c 100644 --- a/packages/core/src/__tests__/file-scope-classification.test.ts +++ b/packages/core/src/__tests__/file-scope-classification.test.ts @@ -9,6 +9,11 @@ * FNXC:FileScopeClassification 2026-07-21-18:05: * Also covers fenced repro snippets (GitHub #2389) whose escaped backticks produced * tokens like `global.json\` and blocked import via InvalidFileScopeError. + * + * FNXC:FileScopeClassification 2026-07-21-19:00: + * The regression invariant covers ecosystem-neutral root files, not only the four-path + * .NET repro. Classification, create/update validation, and the public store re-export + * must agree while duplicate, read-only, and invalid tokens remain excluded from writes. */ import { describe, expect, it } from "vitest"; import { @@ -17,18 +22,27 @@ import { isValidFileScopeEntry, } from "../file-scope-classification.js"; import { - isValidFileScopeEntry as storeIsValidFileScopeEntry, + isValidFileScopeEntry as storeFileScopeIsValidFileScopeEntry, validateFileScopeInPromptContent, } from "../task-store/file-scope.js"; +import { isValidFileScopeEntry as storeIsValidFileScopeEntry } from "../store.js"; import { buildBootstrapPrompt } from "../mesh-task-replication.js"; describe("isValidFileScopeEntry", () => { it("accepts root-level repo files with letter-leading extensions", () => { const roots = [ "global.json", + "Directory.Build.props", + "Directory.Build.targets", "Directory.Packages.props", + "nuget.config", + "NuGet.config", + ".editorconfig", "MyApp.slnx", "MyApp.sln", + "Cargo.toml", + "go.mod", + "pyproject.toml", "tsconfig.json", "package.json", "pnpm-lock.yaml", @@ -38,6 +52,7 @@ describe("isValidFileScopeEntry", () => { ]; for (const path of roots) { expect(isValidFileScopeEntry(path), path).toBe(true); + expect(storeFileScopeIsValidFileScopeEntry(path), `task-store:${path}`).toBe(true); expect(storeIsValidFileScopeEntry(path), `store:${path}`).toBe(true); } }); @@ -82,7 +97,8 @@ describe("isValidFileScopeEntry", () => { } }); - it("keeps create/update validation and classification on the same function", () => { + it("keeps create/update validation and store exports on the same function", () => { + expect(storeFileScopeIsValidFileScopeEntry).toBe(isValidFileScopeEntry); expect(storeIsValidFileScopeEntry).toBe(isValidFileScopeEntry); }); }); @@ -109,6 +125,28 @@ describe("extractEffectiveWriteScopeFromPrompt / validateFileScopeInPromptConten ]); }); + it("retains the broader .NET root set in effective scope", () => { + const rootPaths = [ + ".editorconfig", + "Directory.Build.props", + "Directory.Build.targets", + "Directory.Packages.props", + "global.json", + "nuget.config", + "MyApp.slnx", + ]; + const broaderPrompt = `## File Scope +${rootPaths.map((path) => `- \`${path}\` (new)`).join("\n")} +- \`src/MyApp/Program.cs\` (new) +`; + + expect(extractEffectiveWriteScopeFromPrompt(broaderPrompt)).toEqual([ + ...rootPaths, + "src/MyApp/Program.cs", + ]); + expect(validateFileScopeInPromptContent(broaderPrompt).invalid).toEqual([]); + }); + it("passes create/update File Scope validation for root-level extension paths", () => { const { valid, invalid } = validateFileScopeInPromptContent(prompt); expect(invalid).toEqual([]); @@ -120,6 +158,28 @@ describe("extractEffectiveWriteScopeFromPrompt / validateFileScopeInPromptConten ]); }); + it("omits duplicate, read-only, conditional, and invalid tokens from write scope", () => { + const classified = `## File Scope +- \`global.json\` +- \`global.json\` + +Read-only context: +- \`Directory.Build.props\` + +Files changed: +- \`src/MyApp/Program.cs\` + +Only if changed: +- \`.changeset/fix.md\` + +## Steps +`; + expect(extractEffectiveWriteScopeFromPrompt(classified)).toEqual([ + "global.json", + "src/MyApp/Program.cs", + ]); + }); + it("still rejects git-ref tokens inside File Scope on create/update validation", () => { const bad = `## File Scope - \`packages/core/src/store.ts\`