From ff6b6e1ee85a56839111977c0669cbe2864b3eb9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 14:35:16 +0000 Subject: [PATCH 1/5] Initial plan From 29d9db3e99f7e165588c8885590a6b716a97933c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 14:51:35 +0000 Subject: [PATCH 2/5] Fix agenti ingestion JS to handle fragmented JSON Add logic to detect and reconstruct fragmented JSON where individual fields are written on separate lines instead of complete JSON objects. This handles the case where agent output contains lines like: "field": "value", "field2": "value2", instead of a single JSON object on one line. The fix adds: - isJsonFragment() to detect individual JSON field lines - reconstructFragmentedJson() to combine fragments into complete objects - Preprocessing step before main NDJSON parsing - Test case to verify reconstruction works correctly All existing tests pass. Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/js/collect_ndjson_output.cjs | 77 ++++++++++++++++++- .../js/collect_ndjson_output.test.cjs | 48 ++++++++++++ 2 files changed, 124 insertions(+), 1 deletion(-) diff --git a/pkg/workflow/js/collect_ndjson_output.cjs b/pkg/workflow/js/collect_ndjson_output.cjs index 8ce7d994935..722b322313f 100644 --- a/pkg/workflow/js/collect_ndjson_output.cjs +++ b/pkg/workflow/js/collect_ndjson_output.cjs @@ -156,6 +156,77 @@ async function main() { normalizedItem, }; } + /** + * Detect if a line looks like a fragment of a JSON object (e.g., `"field": "value",`) + * This should match individual fields from a pretty-printed JSON object, not incomplete JSON. + * @param {string} line - The line to check + * @returns {boolean} True if the line appears to be a JSON fragment + */ + function isJsonFragment(line) { + const trimmed = line.trim(); + // Must start with a quoted field name + if (!trimmed.startsWith('"')) return false; + // Must not be a complete JSON object (starting with { or {") + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + // Must not end with a closing brace (which would indicate it's just missing opening brace) + if (trimmed.endsWith('}')) return false; + // Look for pattern: "fieldname": value,? (with optional trailing comma) + // This matches individual fields extracted from a pretty-printed object + return /^"[^"]+"\s*:\s*.+,?\s*$/.test(trimmed); + } + + /** + * Reconstruct fragmented JSON lines into complete JSON objects + * @param {string[]} lines - Array of lines + * @returns {string[]} Array of lines with fragments reconstructed + */ + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + + while (i < lines.length) { + const line = lines[i].trim(); + + // Skip empty lines + if (line === "") { + i++; + continue; + } + + // Check if this line starts a sequence of JSON fragments + if (isJsonFragment(line)) { + // Collect consecutive JSON fragments + const fragments = []; + let j = i; + + while (j < lines.length && isJsonFragment(lines[j].trim())) { + let fragment = lines[j].trim(); + // Remove trailing comma if present + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + + if (fragments.length > 0) { + // Reconstruct the JSON object + const reconstructed = '{' + fragments.join(',') + '}'; + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + i = j; + continue; + } + } + + // Not a fragment, keep as-is + result.push(line); + i++; + } + + return result; + } + function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -211,7 +282,11 @@ async function main() { core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + + // Reconstruct any fragmented JSON (individual fields on separate lines) + lines = reconstructFragmentedJson(lines); + const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/pkg/workflow/js/collect_ndjson_output.test.cjs b/pkg/workflow/js/collect_ndjson_output.test.cjs index 03f574bbd7b..89199744022 100644 --- a/pkg/workflow/js/collect_ndjson_output.test.cjs +++ b/pkg/workflow/js/collect_ndjson_output.test.cjs @@ -922,6 +922,54 @@ Line 3"} expect(parsedOutput.errors.some(error => error.includes("JSON parsing failed"))).toBe(true); }); + it("should reconstruct fragmented JSON (individual fields on separate lines)", async () => { + const testFile = "/tmp/gh-aw/test-ndjson-output.txt"; + // This simulates the bug where individual JSON fields are written as separate lines + // Each line contains a single field from a pretty-printed JSON object + const ndjsonContent = `"type": "create_discussion", +"title": "Test Discussion", +"body": "This is the body", +"category": "general" +{"type": "noop", "message": "This is valid"}`; + + fs.writeFileSync(testFile, ndjsonContent); + process.env.GH_AW_SAFE_OUTPUTS = testFile; + const __config = '{"create_discussion": true, "noop": true}'; + const configPath = "/tmp/gh-aw/safeoutputs/config.json"; + fs.mkdirSync("/tmp/gh-aw/safeoutputs", { recursive: true }); + fs.writeFileSync(configPath, __config); + + await eval(`(async () => { ${collectScript} })()`); + + const setOutputCalls = mockCore.setOutput.mock.calls; + const outputCall = setOutputCalls.find(call => call[0] === "output"); + expect(outputCall).toBeDefined(); + + const parsedOutput = JSON.parse(outputCall[1]); + + // Both items should be successfully parsed after reconstruction + expect(parsedOutput.items).toHaveLength(2); + + // First item should be the reconstructed create_discussion + expect(parsedOutput.items[0].type).toBe("create_discussion"); + expect(parsedOutput.items[0].title).toBe("Test Discussion"); + expect(parsedOutput.items[0].body).toBe("This is the body"); + expect(parsedOutput.items[0].category).toBe("general"); + + // Second item should be the valid noop + expect(parsedOutput.items[1].type).toBe("noop"); + expect(parsedOutput.items[1].message).toBe("This is valid"); + + // Should have no errors since reconstruction was successful + expect(parsedOutput.errors).toHaveLength(0); + + // Verify that info log shows reconstruction happened + const infoCall = mockCore.info.mock.calls.find(call => + String(call[0]).includes("Reconstructed") && String(call[0]).includes("JSON fragments") + ); + expect(infoCall).toBeDefined(); + }); + it("should still report error if repair fails completely", async () => { const testFile = "/tmp/gh-aw/test-ndjson-output.txt"; const ndjsonContent = `{completely broken json with no hope: of repair [[[}}}`; From d4fb9660c76ecb42b7cd2bffad2344e6c5080cff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 15:07:13 +0000 Subject: [PATCH 3/5] Improve fragmented JSON reconstruction with validation Address code review feedback: - Use more restrictive regex pattern for detecting JSON fragments - Add validation that reconstructed JSON is valid before using it - Add graceful fallback if reconstruction fails - Add test for incomplete fragment reconstruction The improved implementation: - Uses specific regex for JSON value types (string, number, boolean, null, array, object) - Validates reconstructed JSON with JSON.parse before accepting it - Falls back to original fragments if validation fails - Logs warning when reconstruction fails Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/js/collect_ndjson_output.cjs | 33 ++++++++++++++-- .../js/collect_ndjson_output.test.cjs | 38 +++++++++++++++++++ 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/pkg/workflow/js/collect_ndjson_output.cjs b/pkg/workflow/js/collect_ndjson_output.cjs index 722b322313f..e3586c9764a 100644 --- a/pkg/workflow/js/collect_ndjson_output.cjs +++ b/pkg/workflow/js/collect_ndjson_output.cjs @@ -171,8 +171,24 @@ async function main() { // Must not end with a closing brace (which would indicate it's just missing opening brace) if (trimmed.endsWith('}')) return false; // Look for pattern: "fieldname": value,? (with optional trailing comma) - // This matches individual fields extracted from a pretty-printed object - return /^"[^"]+"\s*:\s*.+,?\s*$/.test(trimmed); + // Use more restrictive pattern to avoid matching malformed values + // Value can be: string, number, boolean, null, array, or object + return /^"[^"]+"\s*:\s*(?:"[^"\\]*(?:\\.[^"\\]*)*"|true|false|null|[+-]?\d+\.?\d*(?:[eE][+-]?\d+)?|\[.*\]|\{.*\})\s*,?\s*$/.test(trimmed); + } + + /** + * Validate that a reconstructed JSON string is valid + * @param {string} jsonStr - The JSON string to validate + * @returns {boolean} True if valid, false otherwise + */ + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + // Must be an object (not array, string, number, etc.) + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } } /** @@ -212,8 +228,17 @@ async function main() { if (fragments.length > 0) { // Reconstruct the JSON object const reconstructed = '{' + fragments.join(',') + '}'; - core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); - result.push(reconstructed); + + // Validate the reconstructed JSON before adding it + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + // If validation fails, keep the original fragments as separate lines + // They'll be processed normally and produce appropriate errors + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + fragments.forEach(frag => result.push(frag)); + } i = j; continue; } diff --git a/pkg/workflow/js/collect_ndjson_output.test.cjs b/pkg/workflow/js/collect_ndjson_output.test.cjs index 89199744022..61861a43924 100644 --- a/pkg/workflow/js/collect_ndjson_output.test.cjs +++ b/pkg/workflow/js/collect_ndjson_output.test.cjs @@ -970,6 +970,44 @@ Line 3"} expect(infoCall).toBeDefined(); }); + it("should handle invalid fragment reconstruction gracefully", async () => { + const testFile = "/tmp/gh-aw/test-ndjson-output.txt"; + // These look like valid fragments but can't be reconstructed because they're missing required fields + // (e.g., create_discussion requires both title and body) + const ndjsonContent = `"type": "create_discussion", +"category": "general" +{"type": "noop", "message": "This is valid"}`; + + fs.writeFileSync(testFile, ndjsonContent); + process.env.GH_AW_SAFE_OUTPUTS = testFile; + const __config = '{"create_discussion": true, "noop": true}'; + const configPath = "/tmp/gh-aw/safeoutputs/config.json"; + fs.mkdirSync("/tmp/gh-aw/safeoutputs", { recursive: true }); + fs.writeFileSync(configPath, __config); + + await eval(`(async () => { ${collectScript} })()`); + + const setOutputCalls = mockCore.setOutput.mock.calls; + const outputCall = setOutputCalls.find(call => call[0] === "output"); + expect(outputCall).toBeDefined(); + + const parsedOutput = JSON.parse(outputCall[1]); + + // Valid noop should be parsed + // Reconstructed create_discussion will have errors (missing required fields) + expect(parsedOutput.items).toHaveLength(1); + expect(parsedOutput.items[0].type).toBe("noop"); + + // Should have errors for the incomplete create_discussion + expect(parsedOutput.errors.length).toBeGreaterThan(0); + + // Verify that reconstruction happened + const infoCall = mockCore.info.mock.calls.find(call => + String(call[0]).includes("Reconstructed") && String(call[0]).includes("JSON fragments") + ); + expect(infoCall).toBeDefined(); + }); + it("should still report error if repair fails completely", async () => { const testFile = "/tmp/gh-aw/test-ndjson-output.txt"; const ndjsonContent = `{completely broken json with no hope: of repair [[[}}}`; From cb8b9b1ebc23f753875537cbc31c6f489d0a5a00 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 15:09:54 +0000 Subject: [PATCH 4/5] Refactor fragment detection regex for maintainability Address final code review feedback: - Break down complex regex into named patterns for each value type - Preserve original fragments (with commas) when reconstruction fails - Use simpler patterns for arrays/objects (nested structures validated by JSON.parse) Improvements: - Separate patterns for string, number, boolean, null, array, object - Clear comments explaining each pattern - Original fragments preserved exactly when validation fails - Simpler array/object patterns prevent false matches on incomplete structures Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/js/collect_ndjson_output.cjs | 36 ++++++++++++++++------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/pkg/workflow/js/collect_ndjson_output.cjs b/pkg/workflow/js/collect_ndjson_output.cjs index e3586c9764a..5a64ae4b651 100644 --- a/pkg/workflow/js/collect_ndjson_output.cjs +++ b/pkg/workflow/js/collect_ndjson_output.cjs @@ -170,10 +170,23 @@ async function main() { if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; // Must not end with a closing brace (which would indicate it's just missing opening brace) if (trimmed.endsWith('}')) return false; - // Look for pattern: "fieldname": value,? (with optional trailing comma) - // Use more restrictive pattern to avoid matching malformed values - // Value can be: string, number, boolean, null, array, or object - return /^"[^"]+"\s*:\s*(?:"[^"\\]*(?:\\.[^"\\]*)*"|true|false|null|[+-]?\d+\.?\d*(?:[eE][+-]?\d+)?|\[.*\]|\{.*\})\s*,?\s*$/.test(trimmed); + + // Define patterns for each JSON value type + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; // Escaped string + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; // Number (int, float, scientific) + const booleanPattern = 'true|false'; // Boolean + const nullPattern = 'null'; // Null + // For arrays and objects, use simple balanced bracket detection + // More complex nested structures will be caught by JSON.parse validation + const arrayPattern = '\\[[^\\[\\]]*\\]'; // Simple array (no nesting) + const objectPattern = '\\{[^{}]*\\}'; // Simple object (no nesting) + + // Combine patterns for any valid JSON value + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + + // Match pattern: "fieldname": value,? + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); } /** @@ -211,13 +224,17 @@ async function main() { // Check if this line starts a sequence of JSON fragments if (isJsonFragment(line)) { - // Collect consecutive JSON fragments + // Collect consecutive JSON fragments (keeping originals for fallback) const fragments = []; + const originalFragments = []; let j = i; while (j < lines.length && isJsonFragment(lines[j].trim())) { - let fragment = lines[j].trim(); - // Remove trailing comma if present + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + + // Remove trailing comma for reconstruction + let fragment = originalFragment; if (fragment.endsWith(',')) { fragment = fragment.slice(0, -1); } @@ -234,10 +251,9 @@ async function main() { core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); result.push(reconstructed); } else { - // If validation fails, keep the original fragments as separate lines - // They'll be processed normally and produce appropriate errors + // If validation fails, keep the original fragments exactly as they were core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); - fragments.forEach(frag => result.push(frag)); + originalFragments.forEach(frag => result.push(frag)); } i = j; continue; From 581417efbeb32597f043f229c60326a13b9e6465 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 16:01:25 +0000 Subject: [PATCH 5/5] Never fail step on validation errors, only log warnings Per @pelikhan's request: ignore invalid lines but do not fail the step. Changes: - Remove setFailed() call when there are no valid items - Always log validation errors as warnings - Always set output even when items array is empty - Update all tests to reflect new behavior With this change, the workflow will continue even if all agent outputs have validation errors, making it more resilient to malformed inputs. Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/ai-triage-campaign.lock.yml | 67 ++++- .github/workflows/archie.lock.yml | 67 ++++- .github/workflows/artifacts-summary.lock.yml | 67 ++++- .github/workflows/audit-workflows.lock.yml | 67 ++++- .github/workflows/blog-auditor.lock.yml | 67 ++++- .github/workflows/brave.lock.yml | 67 ++++- .../breaking-change-checker.lock.yml | 67 ++++- .github/workflows/changeset.lock.yml | 67 ++++- .github/workflows/ci-doctor.lock.yml | 67 ++++- .../cli-consistency-checker.lock.yml | 67 ++++- .../workflows/cli-version-checker.lock.yml | 67 ++++- .github/workflows/cloclo.lock.yml | 67 ++++- .../workflows/close-old-discussions.lock.yml | 67 ++++- .../commit-changes-analyzer.lock.yml | 67 ++++- .../workflows/copilot-agent-analysis.lock.yml | 67 ++++- .../copilot-pr-merged-report.lock.yml | 67 ++++- .../copilot-pr-nlp-analysis.lock.yml | 67 ++++- .../copilot-pr-prompt-analysis.lock.yml | 67 ++++- .../copilot-session-insights.lock.yml | 67 ++++- .github/workflows/craft.lock.yml | 67 ++++- .../daily-assign-issue-to-user.lock.yml | 67 ++++- .github/workflows/daily-code-metrics.lock.yml | 67 ++++- .../daily-copilot-token-report.lock.yml | 67 ++++- .github/workflows/daily-doc-updater.lock.yml | 67 ++++- .github/workflows/daily-fact.lock.yml | 67 ++++- .github/workflows/daily-file-diet.lock.yml | 67 ++++- .../workflows/daily-firewall-report.lock.yml | 67 ++++- .../workflows/daily-issues-report.lock.yml | 67 ++++- .../daily-malicious-code-scan.lock.yml | 67 ++++- .../daily-multi-device-docs-tester.lock.yml | 67 ++++- .github/workflows/daily-news.lock.yml | 67 ++++- .../daily-performance-summary.lock.yml | 67 ++++- .../workflows/daily-repo-chronicle.lock.yml | 67 ++++- .github/workflows/daily-team-status.lock.yml | 67 ++++- .../workflows/daily-workflow-updater.lock.yml | 67 ++++- .github/workflows/deep-report.lock.yml | 67 ++++- .../workflows/dependabot-go-checker.lock.yml | 67 ++++- .github/workflows/dev-hawk.lock.yml | 67 ++++- .../developer-docs-consolidator.lock.yml | 67 ++++- .github/workflows/dictation-prompt.lock.yml | 67 ++++- .github/workflows/docs-noob-tester.lock.yml | 67 ++++- .../duplicate-code-detector.lock.yml | 67 ++++- .../example-workflow-analyzer.lock.yml | 67 ++++- .../github-mcp-structural-analysis.lock.yml | 67 ++++- .../github-mcp-tools-report.lock.yml | 67 ++++- .../workflows/glossary-maintainer.lock.yml | 67 ++++- .github/workflows/go-fan.lock.yml | 67 ++++- .github/workflows/go-logger.lock.yml | 67 ++++- .../workflows/go-pattern-detector.lock.yml | 67 ++++- .github/workflows/grumpy-reviewer.lock.yml | 67 ++++- .../workflows/instructions-janitor.lock.yml | 67 ++++- .github/workflows/issue-arborist.lock.yml | 67 ++++- .github/workflows/issue-classifier.lock.yml | 67 ++++- .github/workflows/issue-monster.lock.yml | 67 ++++- .github/workflows/issue-triage-agent.lock.yml | 67 ++++- .github/workflows/lockfile-stats.lock.yml | 67 ++++- .github/workflows/mcp-inspector.lock.yml | 67 ++++- .github/workflows/mergefest.lock.yml | 67 ++++- .../workflows/notion-issue-summary.lock.yml | 67 ++++- .github/workflows/org-health-report.lock.yml | 67 ++++- .github/workflows/pdf-summary.lock.yml | 67 ++++- .github/workflows/plan.lock.yml | 67 ++++- .github/workflows/poem-bot.lock.yml | 67 ++++- .../workflows/pr-nitpick-reviewer.lock.yml | 67 ++++- .../prompt-clustering-analysis.lock.yml | 67 ++++- .github/workflows/python-data-charts.lock.yml | 67 ++++- .github/workflows/q.lock.yml | 67 ++++- .github/workflows/release.lock.yml | 67 ++++- .github/workflows/repo-tree-map.lock.yml | 67 ++++- .../repository-quality-improver.lock.yml | 67 ++++- .github/workflows/research.lock.yml | 67 ++++- .github/workflows/safe-output-health.lock.yml | 67 ++++- .../schema-consistency-checker.lock.yml | 67 ++++- .github/workflows/scout.lock.yml | 67 ++++- .github/workflows/security-fix-pr.lock.yml | 67 ++++- .../semantic-function-refactor.lock.yml | 67 ++++- .github/workflows/smoke-claude.lock.yml | 67 ++++- .github/workflows/smoke-codex.lock.yml | 67 ++++- .../smoke-copilot-no-firewall.lock.yml | 67 ++++- .../smoke-copilot-playwright.lock.yml | 67 ++++- .../smoke-copilot-safe-inputs.lock.yml | 67 ++++- .github/workflows/smoke-copilot.lock.yml | 67 ++++- .github/workflows/smoke-detector.lock.yml | 67 ++++- .github/workflows/smoke-srt.lock.yml | 67 ++++- .github/workflows/spec-kit-execute.lock.yml | 67 ++++- .github/workflows/spec-kit-executor.lock.yml | 67 ++++- .github/workflows/speckit-dispatcher.lock.yml | 67 ++++- .../workflows/stale-repo-identifier.lock.yml | 67 ++++- .../workflows/static-analysis-report.lock.yml | 67 ++++- .github/workflows/super-linter.lock.yml | 67 ++++- .../workflows/technical-doc-writer.lock.yml | 67 ++++- .../test-discussion-expires.lock.yml | 67 ++++- .../workflows/test-python-safe-input.lock.yml | 67 ++++- .github/workflows/tidy.lock.yml | 67 ++++- .github/workflows/typist.lock.yml | 67 ++++- .github/workflows/unbloat-docs.lock.yml | 67 ++++- .github/workflows/video-analyzer.lock.yml | 67 ++++- .../workflows/weekly-issue-summary.lock.yml | 67 ++++- pkg/workflow/js/collect_ndjson_output.cjs | 5 +- .../js/collect_ndjson_output.test.cjs | 243 ++++++++++-------- 100 files changed, 6610 insertions(+), 204 deletions(-) diff --git a/.github/workflows/ai-triage-campaign.lock.yml b/.github/workflows/ai-triage-campaign.lock.yml index ecd8306cba7..3ab9bc4b20b 100644 --- a/.github/workflows/ai-triage-campaign.lock.yml +++ b/.github/workflows/ai-triage-campaign.lock.yml @@ -3173,6 +3173,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3224,7 +3288,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/archie.lock.yml b/.github/workflows/archie.lock.yml index 489df76b36a..00d60564c53 100644 --- a/.github/workflows/archie.lock.yml +++ b/.github/workflows/archie.lock.yml @@ -4834,6 +4834,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4885,7 +4949,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/artifacts-summary.lock.yml b/.github/workflows/artifacts-summary.lock.yml index c26115730d2..ac50831f3d0 100644 --- a/.github/workflows/artifacts-summary.lock.yml +++ b/.github/workflows/artifacts-summary.lock.yml @@ -3331,6 +3331,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3382,7 +3446,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/audit-workflows.lock.yml b/.github/workflows/audit-workflows.lock.yml index ba9364ca0c6..e07e928ec66 100644 --- a/.github/workflows/audit-workflows.lock.yml +++ b/.github/workflows/audit-workflows.lock.yml @@ -4888,6 +4888,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4939,7 +5003,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/blog-auditor.lock.yml b/.github/workflows/blog-auditor.lock.yml index 1ea6a51beb2..953b4ab198c 100644 --- a/.github/workflows/blog-auditor.lock.yml +++ b/.github/workflows/blog-auditor.lock.yml @@ -3950,6 +3950,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4001,7 +4065,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/brave.lock.yml b/.github/workflows/brave.lock.yml index 671146ea212..fd53b40a75a 100644 --- a/.github/workflows/brave.lock.yml +++ b/.github/workflows/brave.lock.yml @@ -4624,6 +4624,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4675,7 +4739,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/breaking-change-checker.lock.yml b/.github/workflows/breaking-change-checker.lock.yml index b6ed001dd7b..781ab78e5b2 100644 --- a/.github/workflows/breaking-change-checker.lock.yml +++ b/.github/workflows/breaking-change-checker.lock.yml @@ -3415,6 +3415,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3466,7 +3530,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/changeset.lock.yml b/.github/workflows/changeset.lock.yml index b696c5a651a..c08e640a1ee 100644 --- a/.github/workflows/changeset.lock.yml +++ b/.github/workflows/changeset.lock.yml @@ -4304,6 +4304,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4355,7 +4419,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/ci-doctor.lock.yml b/.github/workflows/ci-doctor.lock.yml index fcdc2d06b37..537da8bc633 100644 --- a/.github/workflows/ci-doctor.lock.yml +++ b/.github/workflows/ci-doctor.lock.yml @@ -4110,6 +4110,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4161,7 +4225,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/cli-consistency-checker.lock.yml b/.github/workflows/cli-consistency-checker.lock.yml index 3497b07bb07..d0e86fc83e1 100644 --- a/.github/workflows/cli-consistency-checker.lock.yml +++ b/.github/workflows/cli-consistency-checker.lock.yml @@ -3412,6 +3412,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3463,7 +3527,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/cli-version-checker.lock.yml b/.github/workflows/cli-version-checker.lock.yml index 8668410f23a..73e5fdfeaf9 100644 --- a/.github/workflows/cli-version-checker.lock.yml +++ b/.github/workflows/cli-version-checker.lock.yml @@ -3899,6 +3899,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3950,7 +4014,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/cloclo.lock.yml b/.github/workflows/cloclo.lock.yml index a3fd36c2acf..9253c335388 100644 --- a/.github/workflows/cloclo.lock.yml +++ b/.github/workflows/cloclo.lock.yml @@ -5366,6 +5366,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -5417,7 +5481,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/close-old-discussions.lock.yml b/.github/workflows/close-old-discussions.lock.yml index 1d4111670ad..1aab90047b3 100644 --- a/.github/workflows/close-old-discussions.lock.yml +++ b/.github/workflows/close-old-discussions.lock.yml @@ -3509,6 +3509,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3560,7 +3624,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/commit-changes-analyzer.lock.yml b/.github/workflows/commit-changes-analyzer.lock.yml index 695e43071cd..b10e8b85bfa 100644 --- a/.github/workflows/commit-changes-analyzer.lock.yml +++ b/.github/workflows/commit-changes-analyzer.lock.yml @@ -3831,6 +3831,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3882,7 +3946,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/copilot-agent-analysis.lock.yml b/.github/workflows/copilot-agent-analysis.lock.yml index 870a8f2f4b6..c4953eed02e 100644 --- a/.github/workflows/copilot-agent-analysis.lock.yml +++ b/.github/workflows/copilot-agent-analysis.lock.yml @@ -4575,6 +4575,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4626,7 +4690,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/copilot-pr-merged-report.lock.yml b/.github/workflows/copilot-pr-merged-report.lock.yml index a6fce2e689f..6be5340e8d3 100644 --- a/.github/workflows/copilot-pr-merged-report.lock.yml +++ b/.github/workflows/copilot-pr-merged-report.lock.yml @@ -4846,6 +4846,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4897,7 +4961,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/copilot-pr-nlp-analysis.lock.yml b/.github/workflows/copilot-pr-nlp-analysis.lock.yml index c68a4094ccd..032eadb853e 100644 --- a/.github/workflows/copilot-pr-nlp-analysis.lock.yml +++ b/.github/workflows/copilot-pr-nlp-analysis.lock.yml @@ -4952,6 +4952,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -5003,7 +5067,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/copilot-pr-prompt-analysis.lock.yml b/.github/workflows/copilot-pr-prompt-analysis.lock.yml index 827c2e99919..0e77b413809 100644 --- a/.github/workflows/copilot-pr-prompt-analysis.lock.yml +++ b/.github/workflows/copilot-pr-prompt-analysis.lock.yml @@ -3974,6 +3974,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4025,7 +4089,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/copilot-session-insights.lock.yml b/.github/workflows/copilot-session-insights.lock.yml index 1e580db7208..30129c9aedb 100644 --- a/.github/workflows/copilot-session-insights.lock.yml +++ b/.github/workflows/copilot-session-insights.lock.yml @@ -5985,6 +5985,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -6036,7 +6100,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/craft.lock.yml b/.github/workflows/craft.lock.yml index 23260e94362..4f3f8c0e060 100644 --- a/.github/workflows/craft.lock.yml +++ b/.github/workflows/craft.lock.yml @@ -4968,6 +4968,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -5019,7 +5083,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/daily-assign-issue-to-user.lock.yml b/.github/workflows/daily-assign-issue-to-user.lock.yml index 8ba15e8a7f9..a18a8d89ec9 100644 --- a/.github/workflows/daily-assign-issue-to-user.lock.yml +++ b/.github/workflows/daily-assign-issue-to-user.lock.yml @@ -3609,6 +3609,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3660,7 +3724,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/daily-code-metrics.lock.yml b/.github/workflows/daily-code-metrics.lock.yml index 47a13654d22..a5c22faaa36 100644 --- a/.github/workflows/daily-code-metrics.lock.yml +++ b/.github/workflows/daily-code-metrics.lock.yml @@ -5026,6 +5026,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -5077,7 +5141,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/daily-copilot-token-report.lock.yml b/.github/workflows/daily-copilot-token-report.lock.yml index 09d3e0bd737..b46dd768c9e 100644 --- a/.github/workflows/daily-copilot-token-report.lock.yml +++ b/.github/workflows/daily-copilot-token-report.lock.yml @@ -5117,6 +5117,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -5168,7 +5232,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/daily-doc-updater.lock.yml b/.github/workflows/daily-doc-updater.lock.yml index 0862fdfc5fa..854447e9fbc 100644 --- a/.github/workflows/daily-doc-updater.lock.yml +++ b/.github/workflows/daily-doc-updater.lock.yml @@ -3622,6 +3622,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3673,7 +3737,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/daily-fact.lock.yml b/.github/workflows/daily-fact.lock.yml index eebc4a37dfa..13fa760b389 100644 --- a/.github/workflows/daily-fact.lock.yml +++ b/.github/workflows/daily-fact.lock.yml @@ -3704,6 +3704,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3755,7 +3819,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/daily-file-diet.lock.yml b/.github/workflows/daily-file-diet.lock.yml index 05ac63aadc6..c287d04da2c 100644 --- a/.github/workflows/daily-file-diet.lock.yml +++ b/.github/workflows/daily-file-diet.lock.yml @@ -3658,6 +3658,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3709,7 +3773,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/daily-firewall-report.lock.yml b/.github/workflows/daily-firewall-report.lock.yml index 63bd431d0f8..5f92a73eb47 100644 --- a/.github/workflows/daily-firewall-report.lock.yml +++ b/.github/workflows/daily-firewall-report.lock.yml @@ -4399,6 +4399,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4450,7 +4514,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/daily-issues-report.lock.yml b/.github/workflows/daily-issues-report.lock.yml index d93193d972b..89b17b4cf84 100644 --- a/.github/workflows/daily-issues-report.lock.yml +++ b/.github/workflows/daily-issues-report.lock.yml @@ -5241,6 +5241,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -5292,7 +5356,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/daily-malicious-code-scan.lock.yml b/.github/workflows/daily-malicious-code-scan.lock.yml index 4088808b2f8..4db9195852e 100644 --- a/.github/workflows/daily-malicious-code-scan.lock.yml +++ b/.github/workflows/daily-malicious-code-scan.lock.yml @@ -3645,6 +3645,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3696,7 +3760,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/daily-multi-device-docs-tester.lock.yml b/.github/workflows/daily-multi-device-docs-tester.lock.yml index ca3a5d11aca..d0e7c609014 100644 --- a/.github/workflows/daily-multi-device-docs-tester.lock.yml +++ b/.github/workflows/daily-multi-device-docs-tester.lock.yml @@ -3533,6 +3533,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3584,7 +3648,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/daily-news.lock.yml b/.github/workflows/daily-news.lock.yml index b77fcca3eda..a39a2432222 100644 --- a/.github/workflows/daily-news.lock.yml +++ b/.github/workflows/daily-news.lock.yml @@ -4876,6 +4876,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4927,7 +4991,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/daily-performance-summary.lock.yml b/.github/workflows/daily-performance-summary.lock.yml index a0c2cc9d133..1a4ae5f00a7 100644 --- a/.github/workflows/daily-performance-summary.lock.yml +++ b/.github/workflows/daily-performance-summary.lock.yml @@ -6470,6 +6470,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -6521,7 +6585,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/daily-repo-chronicle.lock.yml b/.github/workflows/daily-repo-chronicle.lock.yml index c916e171965..4c6a517046e 100644 --- a/.github/workflows/daily-repo-chronicle.lock.yml +++ b/.github/workflows/daily-repo-chronicle.lock.yml @@ -4550,6 +4550,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4601,7 +4665,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/daily-team-status.lock.yml b/.github/workflows/daily-team-status.lock.yml index e593f5b6e8b..42bac64d3e8 100644 --- a/.github/workflows/daily-team-status.lock.yml +++ b/.github/workflows/daily-team-status.lock.yml @@ -3174,6 +3174,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3225,7 +3289,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/daily-workflow-updater.lock.yml b/.github/workflows/daily-workflow-updater.lock.yml index 06e6eb4dba3..9dda200b7ef 100644 --- a/.github/workflows/daily-workflow-updater.lock.yml +++ b/.github/workflows/daily-workflow-updater.lock.yml @@ -3337,6 +3337,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3388,7 +3452,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/deep-report.lock.yml b/.github/workflows/deep-report.lock.yml index 6c305f62746..cca1ac4a8c2 100644 --- a/.github/workflows/deep-report.lock.yml +++ b/.github/workflows/deep-report.lock.yml @@ -4120,6 +4120,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4171,7 +4235,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/dependabot-go-checker.lock.yml b/.github/workflows/dependabot-go-checker.lock.yml index aa85eb6c476..07333b229bb 100644 --- a/.github/workflows/dependabot-go-checker.lock.yml +++ b/.github/workflows/dependabot-go-checker.lock.yml @@ -3945,6 +3945,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3996,7 +4060,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/dev-hawk.lock.yml b/.github/workflows/dev-hawk.lock.yml index f2c17712260..af43741c885 100644 --- a/.github/workflows/dev-hawk.lock.yml +++ b/.github/workflows/dev-hawk.lock.yml @@ -3878,6 +3878,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3929,7 +3993,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/developer-docs-consolidator.lock.yml b/.github/workflows/developer-docs-consolidator.lock.yml index 16f70af5e20..64c5bef54b2 100644 --- a/.github/workflows/developer-docs-consolidator.lock.yml +++ b/.github/workflows/developer-docs-consolidator.lock.yml @@ -4777,6 +4777,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4828,7 +4892,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/dictation-prompt.lock.yml b/.github/workflows/dictation-prompt.lock.yml index 8932f788239..94952034ca7 100644 --- a/.github/workflows/dictation-prompt.lock.yml +++ b/.github/workflows/dictation-prompt.lock.yml @@ -3285,6 +3285,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3336,7 +3400,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/docs-noob-tester.lock.yml b/.github/workflows/docs-noob-tester.lock.yml index 756c259084a..f84fe161759 100644 --- a/.github/workflows/docs-noob-tester.lock.yml +++ b/.github/workflows/docs-noob-tester.lock.yml @@ -3423,6 +3423,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3474,7 +3538,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/duplicate-code-detector.lock.yml b/.github/workflows/duplicate-code-detector.lock.yml index 4c786dadcce..3ff491ceaf5 100644 --- a/.github/workflows/duplicate-code-detector.lock.yml +++ b/.github/workflows/duplicate-code-detector.lock.yml @@ -3493,6 +3493,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3544,7 +3608,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/example-workflow-analyzer.lock.yml b/.github/workflows/example-workflow-analyzer.lock.yml index accdabf6156..89fac3d1d8f 100644 --- a/.github/workflows/example-workflow-analyzer.lock.yml +++ b/.github/workflows/example-workflow-analyzer.lock.yml @@ -3338,6 +3338,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3389,7 +3453,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/github-mcp-structural-analysis.lock.yml b/.github/workflows/github-mcp-structural-analysis.lock.yml index 9600776396d..8a60bf22a2e 100644 --- a/.github/workflows/github-mcp-structural-analysis.lock.yml +++ b/.github/workflows/github-mcp-structural-analysis.lock.yml @@ -4704,6 +4704,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4755,7 +4819,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/github-mcp-tools-report.lock.yml b/.github/workflows/github-mcp-tools-report.lock.yml index 77a9ca02736..acd084c1013 100644 --- a/.github/workflows/github-mcp-tools-report.lock.yml +++ b/.github/workflows/github-mcp-tools-report.lock.yml @@ -4480,6 +4480,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4531,7 +4595,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/glossary-maintainer.lock.yml b/.github/workflows/glossary-maintainer.lock.yml index 4d8cf0989be..e879e1d9997 100644 --- a/.github/workflows/glossary-maintainer.lock.yml +++ b/.github/workflows/glossary-maintainer.lock.yml @@ -4443,6 +4443,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4494,7 +4558,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/go-fan.lock.yml b/.github/workflows/go-fan.lock.yml index c00231951e9..b136b8aa5a3 100644 --- a/.github/workflows/go-fan.lock.yml +++ b/.github/workflows/go-fan.lock.yml @@ -4021,6 +4021,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4072,7 +4136,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/go-logger.lock.yml b/.github/workflows/go-logger.lock.yml index e522f4251af..d5bce853277 100644 --- a/.github/workflows/go-logger.lock.yml +++ b/.github/workflows/go-logger.lock.yml @@ -3786,6 +3786,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3837,7 +3901,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/go-pattern-detector.lock.yml b/.github/workflows/go-pattern-detector.lock.yml index 96d0c6d7494..a90f559409b 100644 --- a/.github/workflows/go-pattern-detector.lock.yml +++ b/.github/workflows/go-pattern-detector.lock.yml @@ -3537,6 +3537,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3588,7 +3652,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/grumpy-reviewer.lock.yml b/.github/workflows/grumpy-reviewer.lock.yml index daaa7adcd63..3275f9e16c4 100644 --- a/.github/workflows/grumpy-reviewer.lock.yml +++ b/.github/workflows/grumpy-reviewer.lock.yml @@ -4773,6 +4773,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4824,7 +4888,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/instructions-janitor.lock.yml b/.github/workflows/instructions-janitor.lock.yml index c50075d976c..b16f1c825a9 100644 --- a/.github/workflows/instructions-janitor.lock.yml +++ b/.github/workflows/instructions-janitor.lock.yml @@ -3551,6 +3551,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3602,7 +3666,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/issue-arborist.lock.yml b/.github/workflows/issue-arborist.lock.yml index f06c5a707a7..c197e0b0583 100644 --- a/.github/workflows/issue-arborist.lock.yml +++ b/.github/workflows/issue-arborist.lock.yml @@ -3502,6 +3502,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3553,7 +3617,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/issue-classifier.lock.yml b/.github/workflows/issue-classifier.lock.yml index ea90a843178..3f6fc01d358 100644 --- a/.github/workflows/issue-classifier.lock.yml +++ b/.github/workflows/issue-classifier.lock.yml @@ -4349,6 +4349,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4400,7 +4464,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/issue-monster.lock.yml b/.github/workflows/issue-monster.lock.yml index 2fe30a29e4a..f8f00d727a4 100644 --- a/.github/workflows/issue-monster.lock.yml +++ b/.github/workflows/issue-monster.lock.yml @@ -4045,6 +4045,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4096,7 +4160,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/issue-triage-agent.lock.yml b/.github/workflows/issue-triage-agent.lock.yml index bbfdb15005b..e9db314cfc0 100644 --- a/.github/workflows/issue-triage-agent.lock.yml +++ b/.github/workflows/issue-triage-agent.lock.yml @@ -3517,6 +3517,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3568,7 +3632,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/lockfile-stats.lock.yml b/.github/workflows/lockfile-stats.lock.yml index 2b76db9ef40..43e4a3ba596 100644 --- a/.github/workflows/lockfile-stats.lock.yml +++ b/.github/workflows/lockfile-stats.lock.yml @@ -4063,6 +4063,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4114,7 +4178,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/mcp-inspector.lock.yml b/.github/workflows/mcp-inspector.lock.yml index e891d4257cb..014874ec8c5 100644 --- a/.github/workflows/mcp-inspector.lock.yml +++ b/.github/workflows/mcp-inspector.lock.yml @@ -3953,6 +3953,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4004,7 +4068,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/mergefest.lock.yml b/.github/workflows/mergefest.lock.yml index 9799770269e..97bf5c2a184 100644 --- a/.github/workflows/mergefest.lock.yml +++ b/.github/workflows/mergefest.lock.yml @@ -4118,6 +4118,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4169,7 +4233,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/notion-issue-summary.lock.yml b/.github/workflows/notion-issue-summary.lock.yml index 865b8616952..46ffb349fdc 100644 --- a/.github/workflows/notion-issue-summary.lock.yml +++ b/.github/workflows/notion-issue-summary.lock.yml @@ -3020,6 +3020,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3071,7 +3135,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/org-health-report.lock.yml b/.github/workflows/org-health-report.lock.yml index f7520296329..3a7236bbe85 100644 --- a/.github/workflows/org-health-report.lock.yml +++ b/.github/workflows/org-health-report.lock.yml @@ -4814,6 +4814,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4865,7 +4929,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/pdf-summary.lock.yml b/.github/workflows/pdf-summary.lock.yml index 964b01f05a4..ae75dede1a4 100644 --- a/.github/workflows/pdf-summary.lock.yml +++ b/.github/workflows/pdf-summary.lock.yml @@ -4798,6 +4798,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4849,7 +4913,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/plan.lock.yml b/.github/workflows/plan.lock.yml index eadf27d6021..f53edc94a1d 100644 --- a/.github/workflows/plan.lock.yml +++ b/.github/workflows/plan.lock.yml @@ -4132,6 +4132,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4183,7 +4247,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/poem-bot.lock.yml b/.github/workflows/poem-bot.lock.yml index 4ce91f446df..d88521743ac 100644 --- a/.github/workflows/poem-bot.lock.yml +++ b/.github/workflows/poem-bot.lock.yml @@ -5850,6 +5850,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -5901,7 +5965,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/pr-nitpick-reviewer.lock.yml b/.github/workflows/pr-nitpick-reviewer.lock.yml index 51dcb12f250..bac76aaf894 100644 --- a/.github/workflows/pr-nitpick-reviewer.lock.yml +++ b/.github/workflows/pr-nitpick-reviewer.lock.yml @@ -5118,6 +5118,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -5169,7 +5233,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/prompt-clustering-analysis.lock.yml b/.github/workflows/prompt-clustering-analysis.lock.yml index 8b3518ca78c..a190b8db3e8 100644 --- a/.github/workflows/prompt-clustering-analysis.lock.yml +++ b/.github/workflows/prompt-clustering-analysis.lock.yml @@ -5339,6 +5339,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -5390,7 +5454,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/python-data-charts.lock.yml b/.github/workflows/python-data-charts.lock.yml index 278588e8eff..24a3b167992 100644 --- a/.github/workflows/python-data-charts.lock.yml +++ b/.github/workflows/python-data-charts.lock.yml @@ -5182,6 +5182,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -5233,7 +5297,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/q.lock.yml b/.github/workflows/q.lock.yml index 65a801a175f..51093962f91 100644 --- a/.github/workflows/q.lock.yml +++ b/.github/workflows/q.lock.yml @@ -5380,6 +5380,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -5431,7 +5495,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/release.lock.yml b/.github/workflows/release.lock.yml index 12df687d64e..76ce56f8e3c 100644 --- a/.github/workflows/release.lock.yml +++ b/.github/workflows/release.lock.yml @@ -3478,6 +3478,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3529,7 +3593,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/repo-tree-map.lock.yml b/.github/workflows/repo-tree-map.lock.yml index e957b6e23aa..35858ebf84d 100644 --- a/.github/workflows/repo-tree-map.lock.yml +++ b/.github/workflows/repo-tree-map.lock.yml @@ -3358,6 +3358,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3409,7 +3473,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/repository-quality-improver.lock.yml b/.github/workflows/repository-quality-improver.lock.yml index 665b4cf35bf..6d2dd9c840e 100644 --- a/.github/workflows/repository-quality-improver.lock.yml +++ b/.github/workflows/repository-quality-improver.lock.yml @@ -4396,6 +4396,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4447,7 +4511,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/research.lock.yml b/.github/workflows/research.lock.yml index e29080e8b7a..6040a9fd780 100644 --- a/.github/workflows/research.lock.yml +++ b/.github/workflows/research.lock.yml @@ -3273,6 +3273,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3324,7 +3388,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/safe-output-health.lock.yml b/.github/workflows/safe-output-health.lock.yml index bac63dcb7d4..8ed349ca86d 100644 --- a/.github/workflows/safe-output-health.lock.yml +++ b/.github/workflows/safe-output-health.lock.yml @@ -4360,6 +4360,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4411,7 +4475,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/schema-consistency-checker.lock.yml b/.github/workflows/schema-consistency-checker.lock.yml index f5b19eceb93..4308d26cbf6 100644 --- a/.github/workflows/schema-consistency-checker.lock.yml +++ b/.github/workflows/schema-consistency-checker.lock.yml @@ -4008,6 +4008,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4059,7 +4123,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/scout.lock.yml b/.github/workflows/scout.lock.yml index 472dda4ca8f..aaa3056b830 100644 --- a/.github/workflows/scout.lock.yml +++ b/.github/workflows/scout.lock.yml @@ -5414,6 +5414,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -5465,7 +5529,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/security-fix-pr.lock.yml b/.github/workflows/security-fix-pr.lock.yml index eef6f3116f0..5cea893bb89 100644 --- a/.github/workflows/security-fix-pr.lock.yml +++ b/.github/workflows/security-fix-pr.lock.yml @@ -3558,6 +3558,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3609,7 +3673,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/semantic-function-refactor.lock.yml b/.github/workflows/semantic-function-refactor.lock.yml index dbc5a30587f..d1560bfbc25 100644 --- a/.github/workflows/semantic-function-refactor.lock.yml +++ b/.github/workflows/semantic-function-refactor.lock.yml @@ -4396,6 +4396,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4447,7 +4511,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index 7d20cb943fa..8475b99a3d2 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -5295,6 +5295,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -5346,7 +5410,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index 0f196f3126b..954f8990acc 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -4846,6 +4846,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4897,7 +4961,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/smoke-copilot-no-firewall.lock.yml b/.github/workflows/smoke-copilot-no-firewall.lock.yml index 0fd645f390d..e4d49532c1d 100644 --- a/.github/workflows/smoke-copilot-no-firewall.lock.yml +++ b/.github/workflows/smoke-copilot-no-firewall.lock.yml @@ -6275,6 +6275,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -6326,7 +6390,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/smoke-copilot-playwright.lock.yml b/.github/workflows/smoke-copilot-playwright.lock.yml index ee6b61dcdf1..afdf5033a19 100644 --- a/.github/workflows/smoke-copilot-playwright.lock.yml +++ b/.github/workflows/smoke-copilot-playwright.lock.yml @@ -6259,6 +6259,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -6310,7 +6374,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/smoke-copilot-safe-inputs.lock.yml b/.github/workflows/smoke-copilot-safe-inputs.lock.yml index e688af5980c..4fb9a0297f5 100644 --- a/.github/workflows/smoke-copilot-safe-inputs.lock.yml +++ b/.github/workflows/smoke-copilot-safe-inputs.lock.yml @@ -5984,6 +5984,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -6035,7 +6099,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 9e9b9fbc2e3..6e7dcee1103 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -6161,6 +6161,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -6212,7 +6276,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/smoke-detector.lock.yml b/.github/workflows/smoke-detector.lock.yml index 1174b272b4d..78abd970d25 100644 --- a/.github/workflows/smoke-detector.lock.yml +++ b/.github/workflows/smoke-detector.lock.yml @@ -5038,6 +5038,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -5089,7 +5153,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/smoke-srt.lock.yml b/.github/workflows/smoke-srt.lock.yml index c3dd5ea3713..d1369d76471 100644 --- a/.github/workflows/smoke-srt.lock.yml +++ b/.github/workflows/smoke-srt.lock.yml @@ -3165,6 +3165,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3216,7 +3280,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/spec-kit-execute.lock.yml b/.github/workflows/spec-kit-execute.lock.yml index ad651dda29b..3aa52508221 100644 --- a/.github/workflows/spec-kit-execute.lock.yml +++ b/.github/workflows/spec-kit-execute.lock.yml @@ -3887,6 +3887,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3938,7 +4002,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/spec-kit-executor.lock.yml b/.github/workflows/spec-kit-executor.lock.yml index d2b0167d47a..3e22fabbff1 100644 --- a/.github/workflows/spec-kit-executor.lock.yml +++ b/.github/workflows/spec-kit-executor.lock.yml @@ -3577,6 +3577,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3628,7 +3692,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/speckit-dispatcher.lock.yml b/.github/workflows/speckit-dispatcher.lock.yml index 17937ba83ab..bec13ed6a5a 100644 --- a/.github/workflows/speckit-dispatcher.lock.yml +++ b/.github/workflows/speckit-dispatcher.lock.yml @@ -5296,6 +5296,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -5347,7 +5411,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/stale-repo-identifier.lock.yml b/.github/workflows/stale-repo-identifier.lock.yml index c3dbf3ebb9d..1a64c01084a 100644 --- a/.github/workflows/stale-repo-identifier.lock.yml +++ b/.github/workflows/stale-repo-identifier.lock.yml @@ -5050,6 +5050,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -5101,7 +5165,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/static-analysis-report.lock.yml b/.github/workflows/static-analysis-report.lock.yml index 7c74d2566f7..8a25e622cda 100644 --- a/.github/workflows/static-analysis-report.lock.yml +++ b/.github/workflows/static-analysis-report.lock.yml @@ -4099,6 +4099,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4150,7 +4214,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/super-linter.lock.yml b/.github/workflows/super-linter.lock.yml index aaa2a000f75..2c1f725aaba 100644 --- a/.github/workflows/super-linter.lock.yml +++ b/.github/workflows/super-linter.lock.yml @@ -3574,6 +3574,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3625,7 +3689,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/technical-doc-writer.lock.yml b/.github/workflows/technical-doc-writer.lock.yml index 70a06082440..8f48993ab76 100644 --- a/.github/workflows/technical-doc-writer.lock.yml +++ b/.github/workflows/technical-doc-writer.lock.yml @@ -4631,6 +4631,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4682,7 +4746,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/test-discussion-expires.lock.yml b/.github/workflows/test-discussion-expires.lock.yml index 32fc7db1a8b..9a0d691829b 100644 --- a/.github/workflows/test-discussion-expires.lock.yml +++ b/.github/workflows/test-discussion-expires.lock.yml @@ -2952,6 +2952,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3003,7 +3067,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/test-python-safe-input.lock.yml b/.github/workflows/test-python-safe-input.lock.yml index dc9f0f2e0d1..6b66815e914 100644 --- a/.github/workflows/test-python-safe-input.lock.yml +++ b/.github/workflows/test-python-safe-input.lock.yml @@ -4567,6 +4567,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4618,7 +4682,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/tidy.lock.yml b/.github/workflows/tidy.lock.yml index ae2d55d3f4f..03da54ebfd7 100644 --- a/.github/workflows/tidy.lock.yml +++ b/.github/workflows/tidy.lock.yml @@ -3691,6 +3691,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3742,7 +3806,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/typist.lock.yml b/.github/workflows/typist.lock.yml index cf2cf1fef13..0df7769f8d0 100644 --- a/.github/workflows/typist.lock.yml +++ b/.github/workflows/typist.lock.yml @@ -4427,6 +4427,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4478,7 +4542,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/unbloat-docs.lock.yml b/.github/workflows/unbloat-docs.lock.yml index 8b3818543c9..78f23caeaf4 100644 --- a/.github/workflows/unbloat-docs.lock.yml +++ b/.github/workflows/unbloat-docs.lock.yml @@ -5160,6 +5160,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -5211,7 +5275,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/video-analyzer.lock.yml b/.github/workflows/video-analyzer.lock.yml index e9ae84d7d72..e9f8bbc295f 100644 --- a/.github/workflows/video-analyzer.lock.yml +++ b/.github/workflows/video-analyzer.lock.yml @@ -3615,6 +3615,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -3666,7 +3730,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/.github/workflows/weekly-issue-summary.lock.yml b/.github/workflows/weekly-issue-summary.lock.yml index 59a39e8f802..083545278ce 100644 --- a/.github/workflows/weekly-issue-summary.lock.yml +++ b/.github/workflows/weekly-issue-summary.lock.yml @@ -4407,6 +4407,70 @@ jobs: normalizedItem, }; } + function isJsonFragment(line) { + const trimmed = line.trim(); + if (!trimmed.startsWith('"')) return false; + if (trimmed.startsWith('{"') || trimmed.startsWith('{')) return false; + if (trimmed.endsWith('}')) return false; + const stringPattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + const numberPattern = '[+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?'; + const booleanPattern = 'true|false'; + const nullPattern = 'null'; + const arrayPattern = '\\[[^\\[\\]]*\\]'; + const objectPattern = '\\{[^{}]*\\}'; + const valuePattern = `(?:${stringPattern}|${numberPattern}|${booleanPattern}|${nullPattern}|${arrayPattern}|${objectPattern})`; + const fragmentPattern = new RegExp(`^"[^"]+\"\\s*:\\s*${valuePattern}\\s*,?\\s*$`); + return fragmentPattern.test(trimmed); + } + function isValidReconstructedJson(jsonStr) { + try { + const parsed = JSON.parse(jsonStr); + return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); + } catch (e) { + return false; + } + } + function reconstructFragmentedJson(lines) { + const result = []; + let i = 0; + while (i < lines.length) { + const line = lines[i].trim(); + if (line === "") { + i++; + continue; + } + if (isJsonFragment(line)) { + const fragments = []; + const originalFragments = []; + let j = i; + while (j < lines.length && isJsonFragment(lines[j].trim())) { + const originalFragment = lines[j].trim(); + originalFragments.push(originalFragment); + let fragment = originalFragment; + if (fragment.endsWith(',')) { + fragment = fragment.slice(0, -1); + } + fragments.push(fragment); + j++; + } + if (fragments.length > 0) { + const reconstructed = '{' + fragments.join(',') + '}'; + if (isValidReconstructedJson(reconstructed)) { + core.info(`Reconstructed ${fragments.length} JSON fragments into single object (lines ${i + 1}-${j})`); + result.push(reconstructed); + } else { + core.warning(`Failed to reconstruct JSON fragments (lines ${i + 1}-${j}) - keeping original lines`); + originalFragments.forEach(frag => result.push(frag)); + } + i = j; + continue; + } + } + result.push(line); + i++; + } + return result; + } function parseJsonWithRepair(jsonStr) { try { return JSON.parse(jsonStr); @@ -4458,7 +4522,8 @@ jobs: core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } - const lines = outputContent.trim().split("\n"); + let lines = outputContent.trim().split("\n"); + lines = reconstructFragmentedJson(lines); const parsedItems = []; const errors = []; for (let i = 0; i < lines.length; i++) { diff --git a/pkg/workflow/js/collect_ndjson_output.cjs b/pkg/workflow/js/collect_ndjson_output.cjs index 5a64ae4b651..70c6d19848a 100644 --- a/pkg/workflow/js/collect_ndjson_output.cjs +++ b/pkg/workflow/js/collect_ndjson_output.cjs @@ -398,10 +398,7 @@ async function main() { if (errors.length > 0) { core.warning("Validation errors found:"); errors.forEach(error => core.warning(` - ${error}`)); - if (parsedItems.length === 0) { - core.setFailed(errors.map(e => ` - ${e}`).join("\n")); - return; - } + // Continue processing even if there are no valid items - don't fail the step } for (const itemType of Object.keys(expectedOutputTypes)) { const minRequired = getMinRequiredForType(itemType, expectedOutputTypes); diff --git a/pkg/workflow/js/collect_ndjson_output.test.cjs b/pkg/workflow/js/collect_ndjson_output.test.cjs index 61861a43924..15093f65980 100644 --- a/pkg/workflow/js/collect_ndjson_output.test.cjs +++ b/pkg/workflow/js/collect_ndjson_output.test.cjs @@ -336,16 +336,19 @@ describe("collect_ndjson_output.cjs", () => { await eval(`(async () => { ${collectScript} })()`); - // Since there are errors and no valid items, setFailed should be called - expect(mockCore.setFailed).toHaveBeenCalledTimes(1); - const failedMessage = mockCore.setFailed.mock.calls[0][0]; - expect(failedMessage).toContain("requires a 'body' field (string)"); - expect(failedMessage).toContain("requires a 'title' field (string)"); - - // setOutput should not be called because of early return + // With the new behavior, we don't fail the step even when all items have validation errors + expect(mockCore.setFailed).not.toHaveBeenCalled(); + + // Warnings should be logged for validation errors + const warningCalls = mockCore.warning.mock.calls; + const errorMessages = warningCalls.map(call => call[0]).join(" "); + expect(errorMessages).toContain("requires a 'body' field (string)"); + expect(errorMessages).toContain("requires a 'title' field (string)"); + + // setOutput should still be called even when there are no valid items const setOutputCalls = mockCore.setOutput.mock.calls; const outputCall = setOutputCalls.find(call => call[0] === "output"); - expect(outputCall).toBeUndefined(); + expect(outputCall).toBeDefined(); }); it("should validate required fields for add-labels type", async () => { @@ -1021,15 +1024,18 @@ Line 3"} await eval(`(async () => { ${collectScript} })()`); - // Since there are errors and no valid items, setFailed should be called - expect(mockCore.setFailed).toHaveBeenCalledTimes(1); - const failedMessage = mockCore.setFailed.mock.calls[0][0]; - expect(failedMessage).toContain("JSON parsing failed"); + // With the new behavior, we don't fail the step even when all items have validation errors + expect(mockCore.setFailed).not.toHaveBeenCalled(); + + // Warnings should be logged for validation errors + const warningCalls = mockCore.warning.mock.calls; + const errorMessages = warningCalls.map(call => call[0]).join(" "); + expect(errorMessages).toContain("JSON parsing failed"); - // setOutput should not be called because of early return + // setOutput should still be called even when there are no valid items const setOutputCalls = mockCore.setOutput.mock.calls; const outputCall = setOutputCalls.find(call => call[0] === "output"); - expect(outputCall).toBeUndefined(); + expect(outputCall).toBeDefined(); }); it("should preserve valid JSON without modification", async () => { @@ -1116,21 +1122,22 @@ Line 3"} await eval(`(async () => { ${collectScript} })()`); - // Check if repair succeeded by looking at mock calls + // Output should always be set now, even if there are no valid items const setOutputCalls = mockCore.setOutput.mock.calls; const outputCall = setOutputCalls.find(call => call[0] === "output"); + expect(outputCall).toBeDefined(); - if (outputCall) { + const parsedOutput = JSON.parse(outputCall[1]); + if (parsedOutput.items.length > 0) { // Repair succeeded - const parsedOutput = JSON.parse(outputCall[1]); expect(parsedOutput.items[0].type).toBe("add_labels"); expect(parsedOutput.items[0].labels).toEqual(["bug", "feature"]); expect(parsedOutput.errors).toHaveLength(0); } else { - // Repair failed, should have called setFailed - expect(mockCore.setFailed).toHaveBeenCalledTimes(1); - const failedMessage = mockCore.setFailed.mock.calls[0][0]; - expect(failedMessage).toContain("JSON parsing failed"); + // Repair failed - check that warnings were logged + expect(mockCore.setFailed).not.toHaveBeenCalled(); + const warningCalls = mockCore.warning.mock.calls; + expect(warningCalls.length).toBeGreaterThan(0); } }); @@ -1442,15 +1449,18 @@ Line 3"} await eval(`(async () => { ${collectScript} })()`); - // Since this JSON is too malformed to repair and results in no valid items, setFailed should be called - expect(mockCore.setFailed).toHaveBeenCalledTimes(1); - const failedMessage = mockCore.setFailed.mock.calls[0][0]; - expect(failedMessage).toContain("JSON parsing failed"); + // With the new behavior, we don't fail the step even when JSON is malformed + expect(mockCore.setFailed).not.toHaveBeenCalled(); + + // Warnings should be logged for parsing errors + const warningCalls = mockCore.warning.mock.calls; + const errorMessages = warningCalls.map(call => call[0]).join(" "); + expect(errorMessages).toContain("JSON parsing failed"); - // setOutput should not be called because of early return + // setOutput should still be called even when there are no valid items const setOutputCalls = mockCore.setOutput.mock.calls; const outputCall = setOutputCalls.find(call => call[0] === "output"); - expect(outputCall).toBeUndefined(); + expect(outputCall).toBeDefined(); }); it("should repair very long strings with multiple issues", async () => { @@ -1573,15 +1583,18 @@ Line 3"} await eval(`(async () => { ${collectScript} })()`); - // Since this JSON is fundamentally broken and results in no valid items, setFailed should be called - expect(mockCore.setFailed).toHaveBeenCalledTimes(1); - const failedMessage = mockCore.setFailed.mock.calls[0][0]; - expect(failedMessage).toContain("JSON parsing failed"); + // With the new behavior, we don't fail the step even when JSON is malformed + expect(mockCore.setFailed).not.toHaveBeenCalled(); + + // Warnings should be logged for parsing errors + const warningCalls = mockCore.warning.mock.calls; + const errorMessages = warningCalls.map(call => call[0]).join(" "); + expect(errorMessages).toContain("JSON parsing failed"); - // setOutput should not be called because of early return + // setOutput should still be called even when there are no valid items const setOutputCalls = mockCore.setOutput.mock.calls; const outputCall = setOutputCalls.find(call => call[0] === "output"); - expect(outputCall).toBeUndefined(); + expect(outputCall).toBeDefined(); }); it("should handle repair of JSON with missing property separators", async () => { @@ -1597,15 +1610,18 @@ Line 3"} await eval(`(async () => { ${collectScript} })()`); - // Since this JSON likely fails to repair and results in no valid items, setFailed should be called - expect(mockCore.setFailed).toHaveBeenCalledTimes(1); - const failedMessage = mockCore.setFailed.mock.calls[0][0]; - expect(failedMessage).toContain("JSON parsing failed"); + // With the new behavior, we don't fail the step even when JSON is malformed + expect(mockCore.setFailed).not.toHaveBeenCalled(); + + // Warnings should be logged for parsing errors + const warningCalls = mockCore.warning.mock.calls; + const errorMessages = warningCalls.map(call => call[0]).join(" "); + expect(errorMessages).toContain("JSON parsing failed"); - // setOutput should not be called because of early return + // setOutput should still be called even when there are no valid items const setOutputCalls = mockCore.setOutput.mock.calls; const outputCall = setOutputCalls.find(call => call[0] === "output"); - expect(outputCall).toBeUndefined(); + expect(outputCall).toBeDefined(); }); it("should repair arrays with mixed bracket types in complex structures", async () => { @@ -1645,21 +1661,22 @@ Line 3"} await eval(`(async () => { ${collectScript} })()`); - // Check if repair succeeded by looking at mock calls + // Output should always be set now, even if there are no valid items const setOutputCalls = mockCore.setOutput.mock.calls; const outputCall = setOutputCalls.find(call => call[0] === "output"); + expect(outputCall).toBeDefined(); - if (outputCall) { + const parsedOutput = JSON.parse(outputCall[1]); + if (parsedOutput.items.length > 0) { // Repair succeeded - const parsedOutput = JSON.parse(outputCall[1]); expect(parsedOutput.items[0].type).toBe("create_issue"); expect(parsedOutput.items[0].title).toBe("Test"); expect(parsedOutput.errors).toHaveLength(0); } else { - // Repair failed, should have called setFailed - expect(mockCore.setFailed).toHaveBeenCalledTimes(1); - const failedMessage = mockCore.setFailed.mock.calls[0][0]; - expect(failedMessage).toContain("JSON parsing failed"); + // Repair failed - check that warnings were logged + expect(mockCore.setFailed).not.toHaveBeenCalled(); + const warningCalls = mockCore.warning.mock.calls; + expect(warningCalls.length).toBeGreaterThan(0); } }); @@ -1866,17 +1883,18 @@ Line 3"} await eval(`(async () => { ${collectScript} })()`); // Since there are errors and no valid items, setFailed should be called - expect(mockCore.setFailed).toHaveBeenCalledTimes(1); - const failedMessage = mockCore.setFailed.mock.calls[0][0]; - expect(failedMessage).toContain("create_code_scanning_alert requires a 'file' field (string)"); - expect(failedMessage).toContain("create_code_scanning_alert 'line' is required"); - expect(failedMessage).toContain("create_code_scanning_alert requires a 'severity' field (string)"); - expect(failedMessage).toContain("create_code_scanning_alert requires a 'message' field (string)"); + expect(mockCore.setFailed).not.toHaveBeenCalled(); + const warningCalls = mockCore.warning.mock.calls; + const errorMessages = warningCalls.map(call => call[0]).join(" "); + expect(errorMessages).toContain("create_code_scanning_alert requires a 'file' field (string)"); + expect(errorMessages).toContain("create_code_scanning_alert 'line' is required"); + expect(errorMessages).toContain("create_code_scanning_alert requires a 'severity' field (string)"); + expect(errorMessages).toContain("create_code_scanning_alert requires a 'message' field (string)"); // setOutput should not be called because of early return const setOutputCalls = mockCore.setOutput.mock.calls; const outputCall = setOutputCalls.find(call => call[0] === "output"); - expect(outputCall).toBeUndefined(); + expect(outputCall).toBeDefined(); }); it("should reject code scanning alert entries with invalid field types", async () => { @@ -1896,17 +1914,18 @@ Line 3"} await eval(`(async () => { ${collectScript} })()`); // Since there are errors and no valid items, setFailed should be called - expect(mockCore.setFailed).toHaveBeenCalledTimes(1); - const failedMessage = mockCore.setFailed.mock.calls[0][0]; - expect(failedMessage).toContain("create_code_scanning_alert requires a 'file' field (string)"); - expect(failedMessage).toContain("create_code_scanning_alert 'line' is required"); - expect(failedMessage).toContain("create_code_scanning_alert requires a 'severity' field (string)"); - expect(failedMessage).toContain("create_code_scanning_alert requires a 'message' field (string)"); + expect(mockCore.setFailed).not.toHaveBeenCalled(); + const warningCalls = mockCore.warning.mock.calls; + const errorMessages = warningCalls.map(call => call[0]).join(" "); + expect(errorMessages).toContain("create_code_scanning_alert requires a 'file' field (string)"); + expect(errorMessages).toContain("create_code_scanning_alert 'line' is required"); + expect(errorMessages).toContain("create_code_scanning_alert requires a 'severity' field (string)"); + expect(errorMessages).toContain("create_code_scanning_alert requires a 'message' field (string)"); // setOutput should not be called because of early return const setOutputCalls = mockCore.setOutput.mock.calls; const outputCall = setOutputCalls.find(call => call[0] === "output"); - expect(outputCall).toBeUndefined(); + expect(outputCall).toBeDefined(); }); it("should reject code scanning alert entries with invalid severity levels", async () => { @@ -1923,15 +1942,18 @@ Line 3"} await eval(`(async () => { ${collectScript} })()`); - // Since there are errors and no valid items, setFailed should be called - expect(mockCore.setFailed).toHaveBeenCalledTimes(1); - const failedMessage = mockCore.setFailed.mock.calls[0][0]; - expect(failedMessage).toContain("create_code_scanning_alert 'severity' must be one of: error, warning, info, note"); + // With the new behavior, we don't fail the step even when all items have validation errors + expect(mockCore.setFailed).not.toHaveBeenCalled(); + + // Warnings should be logged for validation errors + const warningCalls = mockCore.warning.mock.calls; + const errorMessages = warningCalls.map(call => call[0]).join(" "); + expect(errorMessages).toContain("create_code_scanning_alert 'severity' must be one of: error, warning, info, note"); - // setOutput should not be called because of early return + // setOutput should still be called even when there are no valid items const setOutputCalls = mockCore.setOutput.mock.calls; const outputCall = setOutputCalls.find(call => call[0] === "output"); - expect(outputCall).toBeUndefined(); + expect(outputCall).toBeDefined(); }); it("should reject code scanning alert entries with invalid optional fields", async () => { @@ -1950,18 +1972,19 @@ Line 3"} await eval(`(async () => { ${collectScript} })()`); // Since there are errors and no valid items, setFailed should be called - expect(mockCore.setFailed).toHaveBeenCalledTimes(1); - const failedMessage = mockCore.setFailed.mock.calls[0][0]; - expect(failedMessage).toContain("create_code_scanning_alert 'column' must be a valid positive integer (got: invalid)"); - expect(failedMessage).toContain("create_code_scanning_alert 'ruleIdSuffix' must be a string"); - expect(failedMessage).toContain( + expect(mockCore.setFailed).not.toHaveBeenCalled(); + const warningCalls = mockCore.warning.mock.calls; + const errorMessages = warningCalls.map(call => call[0]).join(" "); + expect(errorMessages).toContain("create_code_scanning_alert 'column' must be a valid positive integer (got: invalid)"); + expect(errorMessages).toContain("create_code_scanning_alert 'ruleIdSuffix' must be a string"); + expect(errorMessages).toContain( "create_code_scanning_alert 'ruleIdSuffix' must contain only alphanumeric characters, hyphens, and underscores" ); // setOutput should not be called because of early return const setOutputCalls = mockCore.setOutput.mock.calls; const outputCall = setOutputCalls.find(call => call[0] === "output"); - expect(outputCall).toBeUndefined(); + expect(outputCall).toBeDefined(); }); it("should handle mixed valid and invalid code scanning alert entries", async () => { @@ -2010,18 +2033,19 @@ Line 3"} await eval(`(async () => { ${collectScript} })()`); // Since there are errors and no valid items, setFailed should be called - expect(mockCore.setFailed).toHaveBeenCalledTimes(1); - const failedMessage = mockCore.setFailed.mock.calls[0][0]; - expect(failedMessage).toContain("create_code_scanning_alert 'line' must be a valid positive integer (got: invalid)"); - expect(failedMessage).toContain("create_code_scanning_alert 'line' must be a valid positive integer (got: 0)"); - expect(failedMessage).toContain("create_code_scanning_alert 'line' must be a valid positive integer (got: -5)"); - expect(failedMessage).toContain("create_code_scanning_alert 'column' must be a valid positive integer (got: abc)"); - expect(failedMessage).toContain("create_code_scanning_alert 'column' must be a valid positive integer (got: 0)"); + expect(mockCore.setFailed).not.toHaveBeenCalled(); + const warningCalls = mockCore.warning.mock.calls; + const errorMessages = warningCalls.map(call => call[0]).join(" "); + expect(errorMessages).toContain("create_code_scanning_alert 'line' must be a valid positive integer (got: invalid)"); + expect(errorMessages).toContain("create_code_scanning_alert 'line' must be a valid positive integer (got: 0)"); + expect(errorMessages).toContain("create_code_scanning_alert 'line' must be a valid positive integer (got: -5)"); + expect(errorMessages).toContain("create_code_scanning_alert 'column' must be a valid positive integer (got: abc)"); + expect(errorMessages).toContain("create_code_scanning_alert 'column' must be a valid positive integer (got: 0)"); // setOutput should not be called because of early return const setOutputCalls = mockCore.setOutput.mock.calls; const outputCall = setOutputCalls.find(call => call[0] === "output"); - expect(outputCall).toBeUndefined(); + expect(outputCall).toBeDefined(); }); }); @@ -2580,10 +2604,13 @@ Line 3"} await eval(`(async () => { ${collectScript} })()`); - // When there are only errors and no valid items, setFailed is called instead of setOutput - expect(mockCore.setFailed).toHaveBeenCalled(); - const failedCall = mockCore.setFailed.mock.calls[0][0]; - expect(failedCall).toContain("noop requires a 'message' field (string)"); + // With the new behavior, we don't fail the step even when all items have validation errors + expect(mockCore.setFailed).not.toHaveBeenCalled(); + + // Warnings should be logged for validation errors + const warningCalls = mockCore.warning.mock.calls; + const errorMessages = warningCalls.map(call => call[0]).join(" "); + expect(errorMessages).toContain("noop requires a 'message' field (string)"); }); it("should reject noop with non-string message", async () => { @@ -2601,10 +2628,13 @@ Line 3"} await eval(`(async () => { ${collectScript} })()`); - // When there are only errors and no valid items, setFailed is called instead of setOutput - expect(mockCore.setFailed).toHaveBeenCalled(); - const failedCall = mockCore.setFailed.mock.calls[0][0]; - expect(failedCall).toContain("noop requires a 'message' field (string)"); + // With the new behavior, we don't fail the step even when all items have validation errors + expect(mockCore.setFailed).not.toHaveBeenCalled(); + + // Warnings should be logged for validation errors + const warningCalls = mockCore.warning.mock.calls; + const errorMessages = warningCalls.map(call => call[0]).join(" "); + expect(errorMessages).toContain("noop requires a 'message' field (string)"); }); it("should sanitize noop message content", async () => { @@ -2751,15 +2781,18 @@ Line 3"} await eval(`(async () => { ${collectScript} })()`); - // Since there are errors and no valid items, setFailed should be called - expect(mockCore.setFailed).toHaveBeenCalledTimes(1); - const failedMessage = mockCore.setFailed.mock.calls[0][0]; - expect(failedMessage).toContain("assign_to_agent 'issue_number' is required"); + // With the new behavior, we don't fail the step even when all items have validation errors + expect(mockCore.setFailed).not.toHaveBeenCalled(); + + // Warnings should be logged for validation errors + const warningCalls = mockCore.warning.mock.calls; + const errorMessages = warningCalls.map(call => call[0]).join(" "); + expect(errorMessages).toContain("assign_to_agent 'issue_number' is required"); - // setOutput should not be called because of early return + // setOutput should still be called even when there are no valid items const setOutputCalls = mockCore.setOutput.mock.calls; const outputCall = setOutputCalls.find(call => call[0] === "output"); - expect(outputCall).toBeUndefined(); + expect(outputCall).toBeDefined(); }); }); @@ -2877,10 +2910,13 @@ Line 3"} await eval(`(async () => { ${collectScript} })()`); - // When all items fail validation, setFailed is called and output is not set - const setFailedCalls = mockCore.setFailed.mock.calls; - expect(setFailedCalls.length).toBeGreaterThan(0); - expect(setFailedCalls[0][0]).toContain("must be a positive integer or temporary ID"); + // With the new behavior, we don't fail the step even when validation fails + expect(mockCore.setFailed).not.toHaveBeenCalled(); + + // Warnings should be logged for validation errors + const warningCalls = mockCore.warning.mock.calls; + const errorMessages = warningCalls.map(call => call[0]).join(" "); + expect(errorMessages).toContain("must be a positive integer or temporary ID"); }); it("should reject same temporary ID for parent and sub", async () => { @@ -2893,10 +2929,13 @@ Line 3"} await eval(`(async () => { ${collectScript} })()`); - // When all items fail validation, setFailed is called and output is not set - const setFailedCalls = mockCore.setFailed.mock.calls; - expect(setFailedCalls.length).toBeGreaterThan(0); - expect(setFailedCalls[0][0]).toContain("must be different"); + // With the new behavior, we don't fail the step even when validation fails + expect(mockCore.setFailed).not.toHaveBeenCalled(); + + // Warnings should be logged for validation errors + const warningCalls = mockCore.warning.mock.calls; + const errorMessages = warningCalls.map(call => call[0]).join(" "); + expect(errorMessages).toContain("must be different"); }); }); });