mirror of
https://github.com/Z3Prover/z3
synced 2025-10-01 13:39:28 +00:00
update improvers
This commit is contained in:
parent
7268136bb6
commit
2364ea42ba
12 changed files with 295 additions and 228 deletions
83
.github/workflows/ask.lock.yml
generated
vendored
83
.github/workflows/ask.lock.yml
generated
vendored
|
@ -2,7 +2,7 @@
|
||||||
# To update this file, edit the corresponding .md file and run:
|
# To update this file, edit the corresponding .md file and run:
|
||||||
# gh aw compile
|
# gh aw compile
|
||||||
#
|
#
|
||||||
# Effective stop-time: 2025-09-19 10:32:53
|
# Effective stop-time: 2025-09-19 12:19:14
|
||||||
|
|
||||||
name: "Question Answering Researcher"
|
name: "Question Answering Researcher"
|
||||||
on:
|
on:
|
||||||
|
@ -594,7 +594,7 @@ jobs:
|
||||||
main();
|
main();
|
||||||
- name: Setup Safe Outputs Collector MCP
|
- name: Setup Safe Outputs Collector MCP
|
||||||
env:
|
env:
|
||||||
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true}}"
|
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true}}"
|
||||||
run: |
|
run: |
|
||||||
mkdir -p /tmp/safe-outputs
|
mkdir -p /tmp/safe-outputs
|
||||||
cat > /tmp/safe-outputs/mcp-server.cjs << 'EOF'
|
cat > /tmp/safe-outputs/mcp-server.cjs << 'EOF'
|
||||||
|
@ -747,7 +747,7 @@ jobs:
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "add-issue-comment",
|
name: "add-comment",
|
||||||
description: "Add a comment to a GitHub issue or pull request",
|
description: "Add a comment to a GitHub issue or pull request",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
|
@ -854,7 +854,7 @@ jobs:
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "add-issue-labels",
|
name: "add-labels",
|
||||||
description: "Add labels to a GitHub issue or pull request",
|
description: "Add labels to a GitHub issue or pull request",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
|
@ -1028,7 +1028,7 @@ jobs:
|
||||||
- name: Setup MCPs
|
- name: Setup MCPs
|
||||||
env:
|
env:
|
||||||
GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }}
|
GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }}
|
||||||
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true}}"
|
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true}}"
|
||||||
run: |
|
run: |
|
||||||
mkdir -p /tmp/mcp-config
|
mkdir -p /tmp/mcp-config
|
||||||
cat > /tmp/mcp-config/mcp-servers.json << 'EOF'
|
cat > /tmp/mcp-config/mcp-servers.json << 'EOF'
|
||||||
|
@ -1066,7 +1066,7 @@ jobs:
|
||||||
WORKFLOW_NAME="Question Answering Researcher"
|
WORKFLOW_NAME="Question Answering Researcher"
|
||||||
|
|
||||||
# Check stop-time limit
|
# Check stop-time limit
|
||||||
STOP_TIME="2025-09-19 10:32:53"
|
STOP_TIME="2025-09-19 12:19:14"
|
||||||
echo "Checking stop-time limit: $STOP_TIME"
|
echo "Checking stop-time limit: $STOP_TIME"
|
||||||
|
|
||||||
# Convert stop time to epoch seconds
|
# Convert stop time to epoch seconds
|
||||||
|
@ -1157,7 +1157,7 @@ jobs:
|
||||||
|
|
||||||
**Adding a Comment to an Issue or Pull Request**
|
**Adding a Comment to an Issue or Pull Request**
|
||||||
|
|
||||||
To add a comment to an issue or pull request, use the add-issue-comments tool from the safe-outputs MCP
|
To add a comment to an issue or pull request, use the add-comments tool from the safe-outputs MCP
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
- name: Print prompt to step summary
|
- name: Print prompt to step summary
|
||||||
|
@ -1327,7 +1327,7 @@ jobs:
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v8
|
||||||
env:
|
env:
|
||||||
GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }}
|
GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }}
|
||||||
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true}}"
|
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true}}"
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
async function main() {
|
async function main() {
|
||||||
|
@ -1360,15 +1360,12 @@ jobs:
|
||||||
let sanitized = content;
|
let sanitized = content;
|
||||||
// Neutralize @mentions to prevent unintended notifications
|
// Neutralize @mentions to prevent unintended notifications
|
||||||
sanitized = neutralizeMentions(sanitized);
|
sanitized = neutralizeMentions(sanitized);
|
||||||
|
// Remove XML comments to prevent content hiding
|
||||||
|
sanitized = removeXmlComments(sanitized);
|
||||||
|
// Remove ANSI escape sequences BEFORE removing control characters
|
||||||
|
sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, "");
|
||||||
// Remove control characters (except newlines and tabs)
|
// Remove control characters (except newlines and tabs)
|
||||||
sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
|
sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
|
||||||
// XML character escaping
|
|
||||||
sanitized = sanitized
|
|
||||||
.replace(/&/g, "&") // Must be first to avoid double-escaping
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.replace(/"/g, """)
|
|
||||||
.replace(/'/g, "'");
|
|
||||||
// URI filtering - replace non-https protocols with "(redacted)"
|
// URI filtering - replace non-https protocols with "(redacted)"
|
||||||
sanitized = sanitizeUrlProtocols(sanitized);
|
sanitized = sanitizeUrlProtocols(sanitized);
|
||||||
// Domain filtering for HTTPS URIs
|
// Domain filtering for HTTPS URIs
|
||||||
|
@ -1388,8 +1385,7 @@ jobs:
|
||||||
lines.slice(0, maxLines).join("\n") +
|
lines.slice(0, maxLines).join("\n") +
|
||||||
"\n[Content truncated due to line count]";
|
"\n[Content truncated due to line count]";
|
||||||
}
|
}
|
||||||
// Remove ANSI escape sequences
|
// ANSI escape sequences already removed earlier in the function
|
||||||
sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, "");
|
|
||||||
// Neutralize common bot trigger phrases
|
// Neutralize common bot trigger phrases
|
||||||
sanitized = neutralizeBotTriggers(sanitized);
|
sanitized = neutralizeBotTriggers(sanitized);
|
||||||
// Trim excessive whitespace
|
// Trim excessive whitespace
|
||||||
|
@ -1401,10 +1397,12 @@ jobs:
|
||||||
*/
|
*/
|
||||||
function sanitizeUrlDomains(s) {
|
function sanitizeUrlDomains(s) {
|
||||||
return s.replace(
|
return s.replace(
|
||||||
/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f]+)/gi,
|
/\bhttps:\/\/[^\s\])}'"<>&\x00-\x1f,;]+/gi,
|
||||||
(match, domain) => {
|
(match) => {
|
||||||
|
// Extract just the URL part after https://
|
||||||
|
const urlAfterProtocol = match.slice(8); // Remove 'https://'
|
||||||
// Extract the hostname part (before first slash, colon, or other delimiter)
|
// Extract the hostname part (before first slash, colon, or other delimiter)
|
||||||
const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase();
|
const hostname = urlAfterProtocol.split(/[\/:\?#]/)[0].toLowerCase();
|
||||||
// Check if this domain or any parent domain is in the allowlist
|
// Check if this domain or any parent domain is in the allowlist
|
||||||
const isAllowed = allowedDomains.some(allowedDomain => {
|
const isAllowed = allowedDomains.some(allowedDomain => {
|
||||||
const normalizedAllowed = allowedDomain.toLowerCase();
|
const normalizedAllowed = allowedDomain.toLowerCase();
|
||||||
|
@ -1423,9 +1421,10 @@ jobs:
|
||||||
* @returns {string} The string with non-https protocols redacted
|
* @returns {string} The string with non-https protocols redacted
|
||||||
*/
|
*/
|
||||||
function sanitizeUrlProtocols(s) {
|
function sanitizeUrlProtocols(s) {
|
||||||
// Match both protocol:// and protocol: patterns
|
// Match protocol:// patterns (URLs) and standalone protocol: patterns that look like URLs
|
||||||
|
// Avoid matching command line flags like -v:10 or z3 -memory:high
|
||||||
return s.replace(
|
return s.replace(
|
||||||
/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi,
|
/\b(\w+):\/\/[^\s\])}'"<>&\x00-\x1f]+/gi,
|
||||||
(match, protocol) => {
|
(match, protocol) => {
|
||||||
// Allow https (case insensitive), redact everything else
|
// Allow https (case insensitive), redact everything else
|
||||||
return protocol.toLowerCase() === "https" ? match : "(redacted)";
|
return protocol.toLowerCase() === "https" ? match : "(redacted)";
|
||||||
|
@ -1444,6 +1443,16 @@ jobs:
|
||||||
(_m, p1, p2) => `${p1}\`@${p2}\``
|
(_m, p1, p2) => `${p1}\`@${p2}\``
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Removes XML comments to prevent content hiding
|
||||||
|
* @param {string} s - The string to process
|
||||||
|
* @returns {string} The string with XML comments removed
|
||||||
|
*/
|
||||||
|
function removeXmlComments(s) {
|
||||||
|
// Remove XML/HTML comments including malformed ones that might be used to hide content
|
||||||
|
// Matches: <!-- ... --> and <!--- ... --> and <!--- ... --!> variations
|
||||||
|
return s.replace(/<!--[\s\S]*?-->/g, "").replace(/<!--[\s\S]*?--!>/g, "");
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Neutralizes bot trigger phrases by wrapping them in backticks
|
* Neutralizes bot trigger phrases by wrapping them in backticks
|
||||||
* @param {string} s - The string to process
|
* @param {string} s - The string to process
|
||||||
|
@ -1477,13 +1486,13 @@ jobs:
|
||||||
switch (itemType) {
|
switch (itemType) {
|
||||||
case "create-issue":
|
case "create-issue":
|
||||||
return 1; // Only one issue allowed
|
return 1; // Only one issue allowed
|
||||||
case "add-issue-comment":
|
case "add-comment":
|
||||||
return 1; // Only one comment allowed
|
return 1; // Only one comment allowed
|
||||||
case "create-pull-request":
|
case "create-pull-request":
|
||||||
return 1; // Only one pull request allowed
|
return 1; // Only one pull request allowed
|
||||||
case "create-pull-request-review-comment":
|
case "create-pull-request-review-comment":
|
||||||
return 10; // Default to 10 review comments allowed
|
return 10; // Default to 10 review comments allowed
|
||||||
case "add-issue-labels":
|
case "add-labels":
|
||||||
return 5; // Only one labels operation allowed
|
return 5; // Only one labels operation allowed
|
||||||
case "update-issue":
|
case "update-issue":
|
||||||
return 1; // Only one issue update allowed
|
return 1; // Only one issue update allowed
|
||||||
|
@ -1698,10 +1707,10 @@ jobs:
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "add-issue-comment":
|
case "add-comment":
|
||||||
if (!item.body || typeof item.body !== "string") {
|
if (!item.body || typeof item.body !== "string") {
|
||||||
errors.push(
|
errors.push(
|
||||||
`Line ${i + 1}: add-issue-comment requires a 'body' string field`
|
`Line ${i + 1}: add-comment requires a 'body' string field`
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1736,10 +1745,10 @@ jobs:
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "add-issue-labels":
|
case "add-labels":
|
||||||
if (!item.labels || !Array.isArray(item.labels)) {
|
if (!item.labels || !Array.isArray(item.labels)) {
|
||||||
errors.push(
|
errors.push(
|
||||||
`Line ${i + 1}: add-issue-labels requires a 'labels' array field`
|
`Line ${i + 1}: add-labels requires a 'labels' array field`
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1749,7 +1758,7 @@ jobs:
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
errors.push(
|
errors.push(
|
||||||
`Line ${i + 1}: add-issue-labels labels array must contain only strings`
|
`Line ${i + 1}: add-labels labels array must contain only strings`
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -2671,11 +2680,11 @@ jobs:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
outputs:
|
outputs:
|
||||||
comment_id: ${{ steps.create_comment.outputs.comment_id }}
|
comment_id: ${{ steps.add_comment.outputs.comment_id }}
|
||||||
comment_url: ${{ steps.create_comment.outputs.comment_url }}
|
comment_url: ${{ steps.add_comment.outputs.comment_url }}
|
||||||
steps:
|
steps:
|
||||||
- name: Add Issue Comment
|
- name: Add Issue Comment
|
||||||
id: create_comment
|
id: add_comment
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v8
|
||||||
env:
|
env:
|
||||||
GITHUB_AW_AGENT_OUTPUT: ${{ needs.question-answering-researcher.outputs.output }}
|
GITHUB_AW_AGENT_OUTPUT: ${{ needs.question-answering-researcher.outputs.output }}
|
||||||
|
@ -2709,15 +2718,15 @@ jobs:
|
||||||
core.info("No valid items found in agent output");
|
core.info("No valid items found in agent output");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Find all add-issue-comment items
|
// Find all add-comment items
|
||||||
const commentItems = validatedOutput.items.filter(
|
const commentItems = validatedOutput.items.filter(
|
||||||
/** @param {any} item */ item => item.type === "add-issue-comment"
|
/** @param {any} item */ item => item.type === "add-comment"
|
||||||
);
|
);
|
||||||
if (commentItems.length === 0) {
|
if (commentItems.length === 0) {
|
||||||
core.info("No add-issue-comment items found in agent output");
|
core.info("No add-comment items found in agent output");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
core.info(`Found ${commentItems.length} add-issue-comment item(s)`);
|
core.info(`Found ${commentItems.length} add-comment item(s)`);
|
||||||
// If in staged mode, emit step summary instead of creating comments
|
// If in staged mode, emit step summary instead of creating comments
|
||||||
if (isStaged) {
|
if (isStaged) {
|
||||||
let summaryContent = "## 🎭 Staged Mode: Add Comments Preview\n\n";
|
let summaryContent = "## 🎭 Staged Mode: Add Comments Preview\n\n";
|
||||||
|
@ -2761,7 +2770,7 @@ jobs:
|
||||||
for (let i = 0; i < commentItems.length; i++) {
|
for (let i = 0; i < commentItems.length; i++) {
|
||||||
const commentItem = commentItems[i];
|
const commentItem = commentItems[i];
|
||||||
core.info(
|
core.info(
|
||||||
`Processing add-issue-comment item ${i + 1}/${commentItems.length}: bodyLength=${commentItem.body.length}`
|
`Processing add-comment item ${i + 1}/${commentItems.length}: bodyLength=${commentItem.body.length}`
|
||||||
);
|
);
|
||||||
// Determine the issue/PR number and comment endpoint for this comment
|
// Determine the issue/PR number and comment endpoint for this comment
|
||||||
let issueNumber;
|
let issueNumber;
|
||||||
|
|
2
.github/workflows/ask.md
vendored
2
.github/workflows/ask.md
vendored
|
@ -11,7 +11,7 @@ permissions: read-all
|
||||||
network: defaults
|
network: defaults
|
||||||
|
|
||||||
safe-outputs:
|
safe-outputs:
|
||||||
add-issue-comment:
|
add-comment:
|
||||||
|
|
||||||
tools:
|
tools:
|
||||||
web-fetch:
|
web-fetch:
|
||||||
|
|
83
.github/workflows/ci-doctor.lock.yml
generated
vendored
83
.github/workflows/ci-doctor.lock.yml
generated
vendored
|
@ -2,7 +2,7 @@
|
||||||
# To update this file, edit the corresponding .md file and run:
|
# To update this file, edit the corresponding .md file and run:
|
||||||
# gh aw compile
|
# gh aw compile
|
||||||
#
|
#
|
||||||
# Effective stop-time: 2025-09-19 10:32:53
|
# Effective stop-time: 2025-09-19 12:19:14
|
||||||
|
|
||||||
name: "CI Failure Doctor"
|
name: "CI Failure Doctor"
|
||||||
"on":
|
"on":
|
||||||
|
@ -75,7 +75,7 @@ jobs:
|
||||||
main();
|
main();
|
||||||
- name: Setup Safe Outputs Collector MCP
|
- name: Setup Safe Outputs Collector MCP
|
||||||
env:
|
env:
|
||||||
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true},\"create-issue\":true}"
|
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true},\"create-issue\":true}"
|
||||||
run: |
|
run: |
|
||||||
mkdir -p /tmp/safe-outputs
|
mkdir -p /tmp/safe-outputs
|
||||||
cat > /tmp/safe-outputs/mcp-server.cjs << 'EOF'
|
cat > /tmp/safe-outputs/mcp-server.cjs << 'EOF'
|
||||||
|
@ -228,7 +228,7 @@ jobs:
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "add-issue-comment",
|
name: "add-comment",
|
||||||
description: "Add a comment to a GitHub issue or pull request",
|
description: "Add a comment to a GitHub issue or pull request",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
|
@ -335,7 +335,7 @@ jobs:
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "add-issue-labels",
|
name: "add-labels",
|
||||||
description: "Add labels to a GitHub issue or pull request",
|
description: "Add labels to a GitHub issue or pull request",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
|
@ -509,7 +509,7 @@ jobs:
|
||||||
- name: Setup MCPs
|
- name: Setup MCPs
|
||||||
env:
|
env:
|
||||||
GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }}
|
GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }}
|
||||||
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true},\"create-issue\":true}"
|
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true},\"create-issue\":true}"
|
||||||
run: |
|
run: |
|
||||||
mkdir -p /tmp/mcp-config
|
mkdir -p /tmp/mcp-config
|
||||||
cat > /tmp/mcp-config/mcp-servers.json << 'EOF'
|
cat > /tmp/mcp-config/mcp-servers.json << 'EOF'
|
||||||
|
@ -547,7 +547,7 @@ jobs:
|
||||||
WORKFLOW_NAME="CI Failure Doctor"
|
WORKFLOW_NAME="CI Failure Doctor"
|
||||||
|
|
||||||
# Check stop-time limit
|
# Check stop-time limit
|
||||||
STOP_TIME="2025-09-19 10:32:53"
|
STOP_TIME="2025-09-19 12:19:14"
|
||||||
echo "Checking stop-time limit: $STOP_TIME"
|
echo "Checking stop-time limit: $STOP_TIME"
|
||||||
|
|
||||||
# Convert stop time to epoch seconds
|
# Convert stop time to epoch seconds
|
||||||
|
@ -770,7 +770,7 @@ jobs:
|
||||||
|
|
||||||
**Adding a Comment to an Issue or Pull Request**
|
**Adding a Comment to an Issue or Pull Request**
|
||||||
|
|
||||||
To add a comment to an issue or pull request, use the add-issue-comments tool from the safe-outputs MCP
|
To add a comment to an issue or pull request, use the add-comments tool from the safe-outputs MCP
|
||||||
|
|
||||||
**Creating an Issue**
|
**Creating an Issue**
|
||||||
|
|
||||||
|
@ -941,7 +941,7 @@ jobs:
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v8
|
||||||
env:
|
env:
|
||||||
GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }}
|
GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }}
|
||||||
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true},\"create-issue\":true}"
|
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true},\"create-issue\":true}"
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
async function main() {
|
async function main() {
|
||||||
|
@ -974,15 +974,12 @@ jobs:
|
||||||
let sanitized = content;
|
let sanitized = content;
|
||||||
// Neutralize @mentions to prevent unintended notifications
|
// Neutralize @mentions to prevent unintended notifications
|
||||||
sanitized = neutralizeMentions(sanitized);
|
sanitized = neutralizeMentions(sanitized);
|
||||||
|
// Remove XML comments to prevent content hiding
|
||||||
|
sanitized = removeXmlComments(sanitized);
|
||||||
|
// Remove ANSI escape sequences BEFORE removing control characters
|
||||||
|
sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, "");
|
||||||
// Remove control characters (except newlines and tabs)
|
// Remove control characters (except newlines and tabs)
|
||||||
sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
|
sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
|
||||||
// XML character escaping
|
|
||||||
sanitized = sanitized
|
|
||||||
.replace(/&/g, "&") // Must be first to avoid double-escaping
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.replace(/"/g, """)
|
|
||||||
.replace(/'/g, "'");
|
|
||||||
// URI filtering - replace non-https protocols with "(redacted)"
|
// URI filtering - replace non-https protocols with "(redacted)"
|
||||||
sanitized = sanitizeUrlProtocols(sanitized);
|
sanitized = sanitizeUrlProtocols(sanitized);
|
||||||
// Domain filtering for HTTPS URIs
|
// Domain filtering for HTTPS URIs
|
||||||
|
@ -1002,8 +999,7 @@ jobs:
|
||||||
lines.slice(0, maxLines).join("\n") +
|
lines.slice(0, maxLines).join("\n") +
|
||||||
"\n[Content truncated due to line count]";
|
"\n[Content truncated due to line count]";
|
||||||
}
|
}
|
||||||
// Remove ANSI escape sequences
|
// ANSI escape sequences already removed earlier in the function
|
||||||
sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, "");
|
|
||||||
// Neutralize common bot trigger phrases
|
// Neutralize common bot trigger phrases
|
||||||
sanitized = neutralizeBotTriggers(sanitized);
|
sanitized = neutralizeBotTriggers(sanitized);
|
||||||
// Trim excessive whitespace
|
// Trim excessive whitespace
|
||||||
|
@ -1015,10 +1011,12 @@ jobs:
|
||||||
*/
|
*/
|
||||||
function sanitizeUrlDomains(s) {
|
function sanitizeUrlDomains(s) {
|
||||||
return s.replace(
|
return s.replace(
|
||||||
/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f]+)/gi,
|
/\bhttps:\/\/[^\s\])}'"<>&\x00-\x1f,;]+/gi,
|
||||||
(match, domain) => {
|
(match) => {
|
||||||
|
// Extract just the URL part after https://
|
||||||
|
const urlAfterProtocol = match.slice(8); // Remove 'https://'
|
||||||
// Extract the hostname part (before first slash, colon, or other delimiter)
|
// Extract the hostname part (before first slash, colon, or other delimiter)
|
||||||
const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase();
|
const hostname = urlAfterProtocol.split(/[\/:\?#]/)[0].toLowerCase();
|
||||||
// Check if this domain or any parent domain is in the allowlist
|
// Check if this domain or any parent domain is in the allowlist
|
||||||
const isAllowed = allowedDomains.some(allowedDomain => {
|
const isAllowed = allowedDomains.some(allowedDomain => {
|
||||||
const normalizedAllowed = allowedDomain.toLowerCase();
|
const normalizedAllowed = allowedDomain.toLowerCase();
|
||||||
|
@ -1037,9 +1035,10 @@ jobs:
|
||||||
* @returns {string} The string with non-https protocols redacted
|
* @returns {string} The string with non-https protocols redacted
|
||||||
*/
|
*/
|
||||||
function sanitizeUrlProtocols(s) {
|
function sanitizeUrlProtocols(s) {
|
||||||
// Match both protocol:// and protocol: patterns
|
// Match protocol:// patterns (URLs) and standalone protocol: patterns that look like URLs
|
||||||
|
// Avoid matching command line flags like -v:10 or z3 -memory:high
|
||||||
return s.replace(
|
return s.replace(
|
||||||
/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi,
|
/\b(\w+):\/\/[^\s\])}'"<>&\x00-\x1f]+/gi,
|
||||||
(match, protocol) => {
|
(match, protocol) => {
|
||||||
// Allow https (case insensitive), redact everything else
|
// Allow https (case insensitive), redact everything else
|
||||||
return protocol.toLowerCase() === "https" ? match : "(redacted)";
|
return protocol.toLowerCase() === "https" ? match : "(redacted)";
|
||||||
|
@ -1058,6 +1057,16 @@ jobs:
|
||||||
(_m, p1, p2) => `${p1}\`@${p2}\``
|
(_m, p1, p2) => `${p1}\`@${p2}\``
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Removes XML comments to prevent content hiding
|
||||||
|
* @param {string} s - The string to process
|
||||||
|
* @returns {string} The string with XML comments removed
|
||||||
|
*/
|
||||||
|
function removeXmlComments(s) {
|
||||||
|
// Remove XML/HTML comments including malformed ones that might be used to hide content
|
||||||
|
// Matches: <!-- ... --> and <!--- ... --> and <!--- ... --!> variations
|
||||||
|
return s.replace(/<!--[\s\S]*?-->/g, "").replace(/<!--[\s\S]*?--!>/g, "");
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Neutralizes bot trigger phrases by wrapping them in backticks
|
* Neutralizes bot trigger phrases by wrapping them in backticks
|
||||||
* @param {string} s - The string to process
|
* @param {string} s - The string to process
|
||||||
|
@ -1091,13 +1100,13 @@ jobs:
|
||||||
switch (itemType) {
|
switch (itemType) {
|
||||||
case "create-issue":
|
case "create-issue":
|
||||||
return 1; // Only one issue allowed
|
return 1; // Only one issue allowed
|
||||||
case "add-issue-comment":
|
case "add-comment":
|
||||||
return 1; // Only one comment allowed
|
return 1; // Only one comment allowed
|
||||||
case "create-pull-request":
|
case "create-pull-request":
|
||||||
return 1; // Only one pull request allowed
|
return 1; // Only one pull request allowed
|
||||||
case "create-pull-request-review-comment":
|
case "create-pull-request-review-comment":
|
||||||
return 10; // Default to 10 review comments allowed
|
return 10; // Default to 10 review comments allowed
|
||||||
case "add-issue-labels":
|
case "add-labels":
|
||||||
return 5; // Only one labels operation allowed
|
return 5; // Only one labels operation allowed
|
||||||
case "update-issue":
|
case "update-issue":
|
||||||
return 1; // Only one issue update allowed
|
return 1; // Only one issue update allowed
|
||||||
|
@ -1312,10 +1321,10 @@ jobs:
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "add-issue-comment":
|
case "add-comment":
|
||||||
if (!item.body || typeof item.body !== "string") {
|
if (!item.body || typeof item.body !== "string") {
|
||||||
errors.push(
|
errors.push(
|
||||||
`Line ${i + 1}: add-issue-comment requires a 'body' string field`
|
`Line ${i + 1}: add-comment requires a 'body' string field`
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1350,10 +1359,10 @@ jobs:
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "add-issue-labels":
|
case "add-labels":
|
||||||
if (!item.labels || !Array.isArray(item.labels)) {
|
if (!item.labels || !Array.isArray(item.labels)) {
|
||||||
errors.push(
|
errors.push(
|
||||||
`Line ${i + 1}: add-issue-labels requires a 'labels' array field`
|
`Line ${i + 1}: add-labels requires a 'labels' array field`
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1363,7 +1372,7 @@ jobs:
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
errors.push(
|
errors.push(
|
||||||
`Line ${i + 1}: add-issue-labels labels array must contain only strings`
|
`Line ${i + 1}: add-labels labels array must contain only strings`
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -2480,11 +2489,11 @@ jobs:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
outputs:
|
outputs:
|
||||||
comment_id: ${{ steps.create_comment.outputs.comment_id }}
|
comment_id: ${{ steps.add_comment.outputs.comment_id }}
|
||||||
comment_url: ${{ steps.create_comment.outputs.comment_url }}
|
comment_url: ${{ steps.add_comment.outputs.comment_url }}
|
||||||
steps:
|
steps:
|
||||||
- name: Add Issue Comment
|
- name: Add Issue Comment
|
||||||
id: create_comment
|
id: add_comment
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v8
|
||||||
env:
|
env:
|
||||||
GITHUB_AW_AGENT_OUTPUT: ${{ needs.ci-failure-doctor.outputs.output }}
|
GITHUB_AW_AGENT_OUTPUT: ${{ needs.ci-failure-doctor.outputs.output }}
|
||||||
|
@ -2518,15 +2527,15 @@ jobs:
|
||||||
core.info("No valid items found in agent output");
|
core.info("No valid items found in agent output");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Find all add-issue-comment items
|
// Find all add-comment items
|
||||||
const commentItems = validatedOutput.items.filter(
|
const commentItems = validatedOutput.items.filter(
|
||||||
/** @param {any} item */ item => item.type === "add-issue-comment"
|
/** @param {any} item */ item => item.type === "add-comment"
|
||||||
);
|
);
|
||||||
if (commentItems.length === 0) {
|
if (commentItems.length === 0) {
|
||||||
core.info("No add-issue-comment items found in agent output");
|
core.info("No add-comment items found in agent output");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
core.info(`Found ${commentItems.length} add-issue-comment item(s)`);
|
core.info(`Found ${commentItems.length} add-comment item(s)`);
|
||||||
// If in staged mode, emit step summary instead of creating comments
|
// If in staged mode, emit step summary instead of creating comments
|
||||||
if (isStaged) {
|
if (isStaged) {
|
||||||
let summaryContent = "## 🎭 Staged Mode: Add Comments Preview\n\n";
|
let summaryContent = "## 🎭 Staged Mode: Add Comments Preview\n\n";
|
||||||
|
@ -2570,7 +2579,7 @@ jobs:
|
||||||
for (let i = 0; i < commentItems.length; i++) {
|
for (let i = 0; i < commentItems.length; i++) {
|
||||||
const commentItem = commentItems[i];
|
const commentItem = commentItems[i];
|
||||||
core.info(
|
core.info(
|
||||||
`Processing add-issue-comment item ${i + 1}/${commentItems.length}: bodyLength=${commentItem.body.length}`
|
`Processing add-comment item ${i + 1}/${commentItems.length}: bodyLength=${commentItem.body.length}`
|
||||||
);
|
);
|
||||||
// Determine the issue/PR number and comment endpoint for this comment
|
// Determine the issue/PR number and comment endpoint for this comment
|
||||||
let issueNumber;
|
let issueNumber;
|
||||||
|
|
2
.github/workflows/ci-doctor.md
vendored
2
.github/workflows/ci-doctor.md
vendored
|
@ -18,7 +18,7 @@ network: defaults
|
||||||
safe-outputs:
|
safe-outputs:
|
||||||
create-issue:
|
create-issue:
|
||||||
title-prefix: "${{ github.workflow }}"
|
title-prefix: "${{ github.workflow }}"
|
||||||
add-issue-comment:
|
add-comment:
|
||||||
|
|
||||||
tools:
|
tools:
|
||||||
web-fetch:
|
web-fetch:
|
||||||
|
|
86
.github/workflows/daily-backlog-burner.lock.yml
generated
vendored
86
.github/workflows/daily-backlog-burner.lock.yml
generated
vendored
|
@ -2,7 +2,7 @@
|
||||||
# To update this file, edit the corresponding .md file and run:
|
# To update this file, edit the corresponding .md file and run:
|
||||||
# gh aw compile
|
# gh aw compile
|
||||||
#
|
#
|
||||||
# Effective stop-time: 2025-09-19 10:32:53
|
# Effective stop-time: 2025-09-19 12:19:14
|
||||||
|
|
||||||
name: "Daily Backlog Burner"
|
name: "Daily Backlog Burner"
|
||||||
"on":
|
"on":
|
||||||
|
@ -55,7 +55,7 @@ jobs:
|
||||||
main();
|
main();
|
||||||
- name: Setup Safe Outputs Collector MCP
|
- name: Setup Safe Outputs Collector MCP
|
||||||
env:
|
env:
|
||||||
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true}"
|
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true}"
|
||||||
run: |
|
run: |
|
||||||
mkdir -p /tmp/safe-outputs
|
mkdir -p /tmp/safe-outputs
|
||||||
cat > /tmp/safe-outputs/mcp-server.cjs << 'EOF'
|
cat > /tmp/safe-outputs/mcp-server.cjs << 'EOF'
|
||||||
|
@ -208,7 +208,7 @@ jobs:
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "add-issue-comment",
|
name: "add-comment",
|
||||||
description: "Add a comment to a GitHub issue or pull request",
|
description: "Add a comment to a GitHub issue or pull request",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
|
@ -315,7 +315,7 @@ jobs:
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "add-issue-labels",
|
name: "add-labels",
|
||||||
description: "Add labels to a GitHub issue or pull request",
|
description: "Add labels to a GitHub issue or pull request",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
|
@ -489,7 +489,7 @@ jobs:
|
||||||
- name: Setup MCPs
|
- name: Setup MCPs
|
||||||
env:
|
env:
|
||||||
GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }}
|
GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }}
|
||||||
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true}"
|
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true}"
|
||||||
run: |
|
run: |
|
||||||
mkdir -p /tmp/mcp-config
|
mkdir -p /tmp/mcp-config
|
||||||
cat > /tmp/mcp-config/mcp-servers.json << 'EOF'
|
cat > /tmp/mcp-config/mcp-servers.json << 'EOF'
|
||||||
|
@ -527,7 +527,7 @@ jobs:
|
||||||
WORKFLOW_NAME="Daily Backlog Burner"
|
WORKFLOW_NAME="Daily Backlog Burner"
|
||||||
|
|
||||||
# Check stop-time limit
|
# Check stop-time limit
|
||||||
STOP_TIME="2025-09-19 10:32:53"
|
STOP_TIME="2025-09-19 12:19:14"
|
||||||
echo "Checking stop-time limit: $STOP_TIME"
|
echo "Checking stop-time limit: $STOP_TIME"
|
||||||
|
|
||||||
# Convert stop time to epoch seconds
|
# Convert stop time to epoch seconds
|
||||||
|
@ -668,7 +668,7 @@ jobs:
|
||||||
|
|
||||||
**Adding a Comment to an Issue or Pull Request**
|
**Adding a Comment to an Issue or Pull Request**
|
||||||
|
|
||||||
To add a comment to an issue or pull request, use the add-issue-comments tool from the safe-outputs MCP
|
To add a comment to an issue or pull request, use the add-comments tool from the safe-outputs MCP
|
||||||
|
|
||||||
**Creating an Issue**
|
**Creating an Issue**
|
||||||
|
|
||||||
|
@ -854,7 +854,7 @@ jobs:
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v8
|
||||||
env:
|
env:
|
||||||
GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }}
|
GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }}
|
||||||
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true}"
|
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true}"
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
async function main() {
|
async function main() {
|
||||||
|
@ -887,15 +887,12 @@ jobs:
|
||||||
let sanitized = content;
|
let sanitized = content;
|
||||||
// Neutralize @mentions to prevent unintended notifications
|
// Neutralize @mentions to prevent unintended notifications
|
||||||
sanitized = neutralizeMentions(sanitized);
|
sanitized = neutralizeMentions(sanitized);
|
||||||
|
// Remove XML comments to prevent content hiding
|
||||||
|
sanitized = removeXmlComments(sanitized);
|
||||||
|
// Remove ANSI escape sequences BEFORE removing control characters
|
||||||
|
sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, "");
|
||||||
// Remove control characters (except newlines and tabs)
|
// Remove control characters (except newlines and tabs)
|
||||||
sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
|
sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
|
||||||
// XML character escaping
|
|
||||||
sanitized = sanitized
|
|
||||||
.replace(/&/g, "&") // Must be first to avoid double-escaping
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.replace(/"/g, """)
|
|
||||||
.replace(/'/g, "'");
|
|
||||||
// URI filtering - replace non-https protocols with "(redacted)"
|
// URI filtering - replace non-https protocols with "(redacted)"
|
||||||
sanitized = sanitizeUrlProtocols(sanitized);
|
sanitized = sanitizeUrlProtocols(sanitized);
|
||||||
// Domain filtering for HTTPS URIs
|
// Domain filtering for HTTPS URIs
|
||||||
|
@ -915,8 +912,7 @@ jobs:
|
||||||
lines.slice(0, maxLines).join("\n") +
|
lines.slice(0, maxLines).join("\n") +
|
||||||
"\n[Content truncated due to line count]";
|
"\n[Content truncated due to line count]";
|
||||||
}
|
}
|
||||||
// Remove ANSI escape sequences
|
// ANSI escape sequences already removed earlier in the function
|
||||||
sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, "");
|
|
||||||
// Neutralize common bot trigger phrases
|
// Neutralize common bot trigger phrases
|
||||||
sanitized = neutralizeBotTriggers(sanitized);
|
sanitized = neutralizeBotTriggers(sanitized);
|
||||||
// Trim excessive whitespace
|
// Trim excessive whitespace
|
||||||
|
@ -928,10 +924,12 @@ jobs:
|
||||||
*/
|
*/
|
||||||
function sanitizeUrlDomains(s) {
|
function sanitizeUrlDomains(s) {
|
||||||
return s.replace(
|
return s.replace(
|
||||||
/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f]+)/gi,
|
/\bhttps:\/\/[^\s\])}'"<>&\x00-\x1f,;]+/gi,
|
||||||
(match, domain) => {
|
(match) => {
|
||||||
|
// Extract just the URL part after https://
|
||||||
|
const urlAfterProtocol = match.slice(8); // Remove 'https://'
|
||||||
// Extract the hostname part (before first slash, colon, or other delimiter)
|
// Extract the hostname part (before first slash, colon, or other delimiter)
|
||||||
const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase();
|
const hostname = urlAfterProtocol.split(/[\/:\?#]/)[0].toLowerCase();
|
||||||
// Check if this domain or any parent domain is in the allowlist
|
// Check if this domain or any parent domain is in the allowlist
|
||||||
const isAllowed = allowedDomains.some(allowedDomain => {
|
const isAllowed = allowedDomains.some(allowedDomain => {
|
||||||
const normalizedAllowed = allowedDomain.toLowerCase();
|
const normalizedAllowed = allowedDomain.toLowerCase();
|
||||||
|
@ -950,9 +948,10 @@ jobs:
|
||||||
* @returns {string} The string with non-https protocols redacted
|
* @returns {string} The string with non-https protocols redacted
|
||||||
*/
|
*/
|
||||||
function sanitizeUrlProtocols(s) {
|
function sanitizeUrlProtocols(s) {
|
||||||
// Match both protocol:// and protocol: patterns
|
// Match protocol:// patterns (URLs) and standalone protocol: patterns that look like URLs
|
||||||
|
// Avoid matching command line flags like -v:10 or z3 -memory:high
|
||||||
return s.replace(
|
return s.replace(
|
||||||
/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi,
|
/\b(\w+):\/\/[^\s\])}'"<>&\x00-\x1f]+/gi,
|
||||||
(match, protocol) => {
|
(match, protocol) => {
|
||||||
// Allow https (case insensitive), redact everything else
|
// Allow https (case insensitive), redact everything else
|
||||||
return protocol.toLowerCase() === "https" ? match : "(redacted)";
|
return protocol.toLowerCase() === "https" ? match : "(redacted)";
|
||||||
|
@ -971,6 +970,16 @@ jobs:
|
||||||
(_m, p1, p2) => `${p1}\`@${p2}\``
|
(_m, p1, p2) => `${p1}\`@${p2}\``
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Removes XML comments to prevent content hiding
|
||||||
|
* @param {string} s - The string to process
|
||||||
|
* @returns {string} The string with XML comments removed
|
||||||
|
*/
|
||||||
|
function removeXmlComments(s) {
|
||||||
|
// Remove XML/HTML comments including malformed ones that might be used to hide content
|
||||||
|
// Matches: <!-- ... --> and <!--- ... --> and <!--- ... --!> variations
|
||||||
|
return s.replace(/<!--[\s\S]*?-->/g, "").replace(/<!--[\s\S]*?--!>/g, "");
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Neutralizes bot trigger phrases by wrapping them in backticks
|
* Neutralizes bot trigger phrases by wrapping them in backticks
|
||||||
* @param {string} s - The string to process
|
* @param {string} s - The string to process
|
||||||
|
@ -1004,13 +1013,13 @@ jobs:
|
||||||
switch (itemType) {
|
switch (itemType) {
|
||||||
case "create-issue":
|
case "create-issue":
|
||||||
return 1; // Only one issue allowed
|
return 1; // Only one issue allowed
|
||||||
case "add-issue-comment":
|
case "add-comment":
|
||||||
return 1; // Only one comment allowed
|
return 1; // Only one comment allowed
|
||||||
case "create-pull-request":
|
case "create-pull-request":
|
||||||
return 1; // Only one pull request allowed
|
return 1; // Only one pull request allowed
|
||||||
case "create-pull-request-review-comment":
|
case "create-pull-request-review-comment":
|
||||||
return 10; // Default to 10 review comments allowed
|
return 10; // Default to 10 review comments allowed
|
||||||
case "add-issue-labels":
|
case "add-labels":
|
||||||
return 5; // Only one labels operation allowed
|
return 5; // Only one labels operation allowed
|
||||||
case "update-issue":
|
case "update-issue":
|
||||||
return 1; // Only one issue update allowed
|
return 1; // Only one issue update allowed
|
||||||
|
@ -1225,10 +1234,10 @@ jobs:
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "add-issue-comment":
|
case "add-comment":
|
||||||
if (!item.body || typeof item.body !== "string") {
|
if (!item.body || typeof item.body !== "string") {
|
||||||
errors.push(
|
errors.push(
|
||||||
`Line ${i + 1}: add-issue-comment requires a 'body' string field`
|
`Line ${i + 1}: add-comment requires a 'body' string field`
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1263,10 +1272,10 @@ jobs:
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "add-issue-labels":
|
case "add-labels":
|
||||||
if (!item.labels || !Array.isArray(item.labels)) {
|
if (!item.labels || !Array.isArray(item.labels)) {
|
||||||
errors.push(
|
errors.push(
|
||||||
`Line ${i + 1}: add-issue-labels requires a 'labels' array field`
|
`Line ${i + 1}: add-labels requires a 'labels' array field`
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1276,7 +1285,7 @@ jobs:
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
errors.push(
|
errors.push(
|
||||||
`Line ${i + 1}: add-issue-labels labels array must contain only strings`
|
`Line ${i + 1}: add-labels labels array must contain only strings`
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -2406,6 +2415,7 @@ jobs:
|
||||||
GITHUB_AW_AGENT_OUTPUT: ${{ needs.daily-backlog-burner.outputs.output }}
|
GITHUB_AW_AGENT_OUTPUT: ${{ needs.daily-backlog-burner.outputs.output }}
|
||||||
GITHUB_AW_ISSUE_TITLE_PREFIX: "${{ github.workflow }}"
|
GITHUB_AW_ISSUE_TITLE_PREFIX: "${{ github.workflow }}"
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ secrets.DSYME_GH_TOKEN}}
|
||||||
script: |
|
script: |
|
||||||
async function main() {
|
async function main() {
|
||||||
// Check if we're in staged mode
|
// Check if we're in staged mode
|
||||||
|
@ -2595,16 +2605,17 @@ jobs:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
outputs:
|
outputs:
|
||||||
comment_id: ${{ steps.create_comment.outputs.comment_id }}
|
comment_id: ${{ steps.add_comment.outputs.comment_id }}
|
||||||
comment_url: ${{ steps.create_comment.outputs.comment_url }}
|
comment_url: ${{ steps.add_comment.outputs.comment_url }}
|
||||||
steps:
|
steps:
|
||||||
- name: Add Issue Comment
|
- name: Add Issue Comment
|
||||||
id: create_comment
|
id: add_comment
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v8
|
||||||
env:
|
env:
|
||||||
GITHUB_AW_AGENT_OUTPUT: ${{ needs.daily-backlog-burner.outputs.output }}
|
GITHUB_AW_AGENT_OUTPUT: ${{ needs.daily-backlog-burner.outputs.output }}
|
||||||
GITHUB_AW_COMMENT_TARGET: "*"
|
GITHUB_AW_COMMENT_TARGET: "*"
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ secrets.DSYME_GH_TOKEN}}
|
||||||
script: |
|
script: |
|
||||||
async function main() {
|
async function main() {
|
||||||
// Check if we're in staged mode
|
// Check if we're in staged mode
|
||||||
|
@ -2634,15 +2645,15 @@ jobs:
|
||||||
core.info("No valid items found in agent output");
|
core.info("No valid items found in agent output");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Find all add-issue-comment items
|
// Find all add-comment items
|
||||||
const commentItems = validatedOutput.items.filter(
|
const commentItems = validatedOutput.items.filter(
|
||||||
/** @param {any} item */ item => item.type === "add-issue-comment"
|
/** @param {any} item */ item => item.type === "add-comment"
|
||||||
);
|
);
|
||||||
if (commentItems.length === 0) {
|
if (commentItems.length === 0) {
|
||||||
core.info("No add-issue-comment items found in agent output");
|
core.info("No add-comment items found in agent output");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
core.info(`Found ${commentItems.length} add-issue-comment item(s)`);
|
core.info(`Found ${commentItems.length} add-comment item(s)`);
|
||||||
// If in staged mode, emit step summary instead of creating comments
|
// If in staged mode, emit step summary instead of creating comments
|
||||||
if (isStaged) {
|
if (isStaged) {
|
||||||
let summaryContent = "## 🎭 Staged Mode: Add Comments Preview\n\n";
|
let summaryContent = "## 🎭 Staged Mode: Add Comments Preview\n\n";
|
||||||
|
@ -2686,7 +2697,7 @@ jobs:
|
||||||
for (let i = 0; i < commentItems.length; i++) {
|
for (let i = 0; i < commentItems.length; i++) {
|
||||||
const commentItem = commentItems[i];
|
const commentItem = commentItems[i];
|
||||||
core.info(
|
core.info(
|
||||||
`Processing add-issue-comment item ${i + 1}/${commentItems.length}: bodyLength=${commentItem.body.length}`
|
`Processing add-comment item ${i + 1}/${commentItems.length}: bodyLength=${commentItem.body.length}`
|
||||||
);
|
);
|
||||||
// Determine the issue/PR number and comment endpoint for this comment
|
// Determine the issue/PR number and comment endpoint for this comment
|
||||||
let issueNumber;
|
let issueNumber;
|
||||||
|
@ -2827,6 +2838,7 @@ jobs:
|
||||||
GITHUB_AW_PR_DRAFT: "true"
|
GITHUB_AW_PR_DRAFT: "true"
|
||||||
GITHUB_AW_PR_IF_NO_CHANGES: "warn"
|
GITHUB_AW_PR_IF_NO_CHANGES: "warn"
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ secrets.DSYME_GH_TOKEN}}
|
||||||
script: |
|
script: |
|
||||||
/** @type {typeof import("fs")} */
|
/** @type {typeof import("fs")} */
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
|
3
.github/workflows/daily-backlog-burner.md
vendored
3
.github/workflows/daily-backlog-burner.md
vendored
|
@ -14,11 +14,12 @@ safe-outputs:
|
||||||
create-issue:
|
create-issue:
|
||||||
title-prefix: "${{ github.workflow }}"
|
title-prefix: "${{ github.workflow }}"
|
||||||
max: 3
|
max: 3
|
||||||
add-issue-comment:
|
add-comment:
|
||||||
target: "*" # all issues and PRs
|
target: "*" # all issues and PRs
|
||||||
max: 3
|
max: 3
|
||||||
create-pull-request:
|
create-pull-request:
|
||||||
draft: true
|
draft: true
|
||||||
|
github-token: ${{ secrets.DSYME_GH_TOKEN}}
|
||||||
|
|
||||||
tools:
|
tools:
|
||||||
web-fetch:
|
web-fetch:
|
||||||
|
|
86
.github/workflows/daily-perf-improver.lock.yml
generated
vendored
86
.github/workflows/daily-perf-improver.lock.yml
generated
vendored
|
@ -2,7 +2,7 @@
|
||||||
# To update this file, edit the corresponding .md file and run:
|
# To update this file, edit the corresponding .md file and run:
|
||||||
# gh aw compile
|
# gh aw compile
|
||||||
#
|
#
|
||||||
# Effective stop-time: 2025-09-19 10:32:53
|
# Effective stop-time: 2025-09-19 12:19:14
|
||||||
|
|
||||||
name: "Daily Perf Improver"
|
name: "Daily Perf Improver"
|
||||||
"on":
|
"on":
|
||||||
|
@ -69,7 +69,7 @@ jobs:
|
||||||
main();
|
main();
|
||||||
- name: Setup Safe Outputs Collector MCP
|
- name: Setup Safe Outputs Collector MCP
|
||||||
env:
|
env:
|
||||||
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true}"
|
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true}"
|
||||||
run: |
|
run: |
|
||||||
mkdir -p /tmp/safe-outputs
|
mkdir -p /tmp/safe-outputs
|
||||||
cat > /tmp/safe-outputs/mcp-server.cjs << 'EOF'
|
cat > /tmp/safe-outputs/mcp-server.cjs << 'EOF'
|
||||||
|
@ -222,7 +222,7 @@ jobs:
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "add-issue-comment",
|
name: "add-comment",
|
||||||
description: "Add a comment to a GitHub issue or pull request",
|
description: "Add a comment to a GitHub issue or pull request",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
|
@ -329,7 +329,7 @@ jobs:
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "add-issue-labels",
|
name: "add-labels",
|
||||||
description: "Add labels to a GitHub issue or pull request",
|
description: "Add labels to a GitHub issue or pull request",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
|
@ -503,7 +503,7 @@ jobs:
|
||||||
- name: Setup MCPs
|
- name: Setup MCPs
|
||||||
env:
|
env:
|
||||||
GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }}
|
GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }}
|
||||||
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true}"
|
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true}"
|
||||||
run: |
|
run: |
|
||||||
mkdir -p /tmp/mcp-config
|
mkdir -p /tmp/mcp-config
|
||||||
cat > /tmp/mcp-config/mcp-servers.json << 'EOF'
|
cat > /tmp/mcp-config/mcp-servers.json << 'EOF'
|
||||||
|
@ -541,7 +541,7 @@ jobs:
|
||||||
WORKFLOW_NAME="Daily Perf Improver"
|
WORKFLOW_NAME="Daily Perf Improver"
|
||||||
|
|
||||||
# Check stop-time limit
|
# Check stop-time limit
|
||||||
STOP_TIME="2025-09-19 10:32:53"
|
STOP_TIME="2025-09-19 12:19:14"
|
||||||
echo "Checking stop-time limit: $STOP_TIME"
|
echo "Checking stop-time limit: $STOP_TIME"
|
||||||
|
|
||||||
# Convert stop time to epoch seconds
|
# Convert stop time to epoch seconds
|
||||||
|
@ -743,7 +743,7 @@ jobs:
|
||||||
|
|
||||||
**Adding a Comment to an Issue or Pull Request**
|
**Adding a Comment to an Issue or Pull Request**
|
||||||
|
|
||||||
To add a comment to an issue or pull request, use the add-issue-comments tool from the safe-outputs MCP
|
To add a comment to an issue or pull request, use the add-comments tool from the safe-outputs MCP
|
||||||
|
|
||||||
**Creating an Issue**
|
**Creating an Issue**
|
||||||
|
|
||||||
|
@ -929,7 +929,7 @@ jobs:
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v8
|
||||||
env:
|
env:
|
||||||
GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }}
|
GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }}
|
||||||
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true}"
|
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true}"
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
async function main() {
|
async function main() {
|
||||||
|
@ -962,15 +962,12 @@ jobs:
|
||||||
let sanitized = content;
|
let sanitized = content;
|
||||||
// Neutralize @mentions to prevent unintended notifications
|
// Neutralize @mentions to prevent unintended notifications
|
||||||
sanitized = neutralizeMentions(sanitized);
|
sanitized = neutralizeMentions(sanitized);
|
||||||
|
// Remove XML comments to prevent content hiding
|
||||||
|
sanitized = removeXmlComments(sanitized);
|
||||||
|
// Remove ANSI escape sequences BEFORE removing control characters
|
||||||
|
sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, "");
|
||||||
// Remove control characters (except newlines and tabs)
|
// Remove control characters (except newlines and tabs)
|
||||||
sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
|
sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
|
||||||
// XML character escaping
|
|
||||||
sanitized = sanitized
|
|
||||||
.replace(/&/g, "&") // Must be first to avoid double-escaping
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.replace(/"/g, """)
|
|
||||||
.replace(/'/g, "'");
|
|
||||||
// URI filtering - replace non-https protocols with "(redacted)"
|
// URI filtering - replace non-https protocols with "(redacted)"
|
||||||
sanitized = sanitizeUrlProtocols(sanitized);
|
sanitized = sanitizeUrlProtocols(sanitized);
|
||||||
// Domain filtering for HTTPS URIs
|
// Domain filtering for HTTPS URIs
|
||||||
|
@ -990,8 +987,7 @@ jobs:
|
||||||
lines.slice(0, maxLines).join("\n") +
|
lines.slice(0, maxLines).join("\n") +
|
||||||
"\n[Content truncated due to line count]";
|
"\n[Content truncated due to line count]";
|
||||||
}
|
}
|
||||||
// Remove ANSI escape sequences
|
// ANSI escape sequences already removed earlier in the function
|
||||||
sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, "");
|
|
||||||
// Neutralize common bot trigger phrases
|
// Neutralize common bot trigger phrases
|
||||||
sanitized = neutralizeBotTriggers(sanitized);
|
sanitized = neutralizeBotTriggers(sanitized);
|
||||||
// Trim excessive whitespace
|
// Trim excessive whitespace
|
||||||
|
@ -1003,10 +999,12 @@ jobs:
|
||||||
*/
|
*/
|
||||||
function sanitizeUrlDomains(s) {
|
function sanitizeUrlDomains(s) {
|
||||||
return s.replace(
|
return s.replace(
|
||||||
/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f]+)/gi,
|
/\bhttps:\/\/[^\s\])}'"<>&\x00-\x1f,;]+/gi,
|
||||||
(match, domain) => {
|
(match) => {
|
||||||
|
// Extract just the URL part after https://
|
||||||
|
const urlAfterProtocol = match.slice(8); // Remove 'https://'
|
||||||
// Extract the hostname part (before first slash, colon, or other delimiter)
|
// Extract the hostname part (before first slash, colon, or other delimiter)
|
||||||
const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase();
|
const hostname = urlAfterProtocol.split(/[\/:\?#]/)[0].toLowerCase();
|
||||||
// Check if this domain or any parent domain is in the allowlist
|
// Check if this domain or any parent domain is in the allowlist
|
||||||
const isAllowed = allowedDomains.some(allowedDomain => {
|
const isAllowed = allowedDomains.some(allowedDomain => {
|
||||||
const normalizedAllowed = allowedDomain.toLowerCase();
|
const normalizedAllowed = allowedDomain.toLowerCase();
|
||||||
|
@ -1025,9 +1023,10 @@ jobs:
|
||||||
* @returns {string} The string with non-https protocols redacted
|
* @returns {string} The string with non-https protocols redacted
|
||||||
*/
|
*/
|
||||||
function sanitizeUrlProtocols(s) {
|
function sanitizeUrlProtocols(s) {
|
||||||
// Match both protocol:// and protocol: patterns
|
// Match protocol:// patterns (URLs) and standalone protocol: patterns that look like URLs
|
||||||
|
// Avoid matching command line flags like -v:10 or z3 -memory:high
|
||||||
return s.replace(
|
return s.replace(
|
||||||
/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi,
|
/\b(\w+):\/\/[^\s\])}'"<>&\x00-\x1f]+/gi,
|
||||||
(match, protocol) => {
|
(match, protocol) => {
|
||||||
// Allow https (case insensitive), redact everything else
|
// Allow https (case insensitive), redact everything else
|
||||||
return protocol.toLowerCase() === "https" ? match : "(redacted)";
|
return protocol.toLowerCase() === "https" ? match : "(redacted)";
|
||||||
|
@ -1046,6 +1045,16 @@ jobs:
|
||||||
(_m, p1, p2) => `${p1}\`@${p2}\``
|
(_m, p1, p2) => `${p1}\`@${p2}\``
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Removes XML comments to prevent content hiding
|
||||||
|
* @param {string} s - The string to process
|
||||||
|
* @returns {string} The string with XML comments removed
|
||||||
|
*/
|
||||||
|
function removeXmlComments(s) {
|
||||||
|
// Remove XML/HTML comments including malformed ones that might be used to hide content
|
||||||
|
// Matches: <!-- ... --> and <!--- ... --> and <!--- ... --!> variations
|
||||||
|
return s.replace(/<!--[\s\S]*?-->/g, "").replace(/<!--[\s\S]*?--!>/g, "");
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Neutralizes bot trigger phrases by wrapping them in backticks
|
* Neutralizes bot trigger phrases by wrapping them in backticks
|
||||||
* @param {string} s - The string to process
|
* @param {string} s - The string to process
|
||||||
|
@ -1079,13 +1088,13 @@ jobs:
|
||||||
switch (itemType) {
|
switch (itemType) {
|
||||||
case "create-issue":
|
case "create-issue":
|
||||||
return 1; // Only one issue allowed
|
return 1; // Only one issue allowed
|
||||||
case "add-issue-comment":
|
case "add-comment":
|
||||||
return 1; // Only one comment allowed
|
return 1; // Only one comment allowed
|
||||||
case "create-pull-request":
|
case "create-pull-request":
|
||||||
return 1; // Only one pull request allowed
|
return 1; // Only one pull request allowed
|
||||||
case "create-pull-request-review-comment":
|
case "create-pull-request-review-comment":
|
||||||
return 10; // Default to 10 review comments allowed
|
return 10; // Default to 10 review comments allowed
|
||||||
case "add-issue-labels":
|
case "add-labels":
|
||||||
return 5; // Only one labels operation allowed
|
return 5; // Only one labels operation allowed
|
||||||
case "update-issue":
|
case "update-issue":
|
||||||
return 1; // Only one issue update allowed
|
return 1; // Only one issue update allowed
|
||||||
|
@ -1300,10 +1309,10 @@ jobs:
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "add-issue-comment":
|
case "add-comment":
|
||||||
if (!item.body || typeof item.body !== "string") {
|
if (!item.body || typeof item.body !== "string") {
|
||||||
errors.push(
|
errors.push(
|
||||||
`Line ${i + 1}: add-issue-comment requires a 'body' string field`
|
`Line ${i + 1}: add-comment requires a 'body' string field`
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1338,10 +1347,10 @@ jobs:
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "add-issue-labels":
|
case "add-labels":
|
||||||
if (!item.labels || !Array.isArray(item.labels)) {
|
if (!item.labels || !Array.isArray(item.labels)) {
|
||||||
errors.push(
|
errors.push(
|
||||||
`Line ${i + 1}: add-issue-labels requires a 'labels' array field`
|
`Line ${i + 1}: add-labels requires a 'labels' array field`
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1351,7 +1360,7 @@ jobs:
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
errors.push(
|
errors.push(
|
||||||
`Line ${i + 1}: add-issue-labels labels array must contain only strings`
|
`Line ${i + 1}: add-labels labels array must contain only strings`
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -2481,6 +2490,7 @@ jobs:
|
||||||
GITHUB_AW_AGENT_OUTPUT: ${{ needs.daily-perf-improver.outputs.output }}
|
GITHUB_AW_AGENT_OUTPUT: ${{ needs.daily-perf-improver.outputs.output }}
|
||||||
GITHUB_AW_ISSUE_TITLE_PREFIX: "${{ github.workflow }}"
|
GITHUB_AW_ISSUE_TITLE_PREFIX: "${{ github.workflow }}"
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ secrets.DSYME_GH_TOKEN}}
|
||||||
script: |
|
script: |
|
||||||
async function main() {
|
async function main() {
|
||||||
// Check if we're in staged mode
|
// Check if we're in staged mode
|
||||||
|
@ -2670,16 +2680,17 @@ jobs:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
outputs:
|
outputs:
|
||||||
comment_id: ${{ steps.create_comment.outputs.comment_id }}
|
comment_id: ${{ steps.add_comment.outputs.comment_id }}
|
||||||
comment_url: ${{ steps.create_comment.outputs.comment_url }}
|
comment_url: ${{ steps.add_comment.outputs.comment_url }}
|
||||||
steps:
|
steps:
|
||||||
- name: Add Issue Comment
|
- name: Add Issue Comment
|
||||||
id: create_comment
|
id: add_comment
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v8
|
||||||
env:
|
env:
|
||||||
GITHUB_AW_AGENT_OUTPUT: ${{ needs.daily-perf-improver.outputs.output }}
|
GITHUB_AW_AGENT_OUTPUT: ${{ needs.daily-perf-improver.outputs.output }}
|
||||||
GITHUB_AW_COMMENT_TARGET: "*"
|
GITHUB_AW_COMMENT_TARGET: "*"
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ secrets.DSYME_GH_TOKEN}}
|
||||||
script: |
|
script: |
|
||||||
async function main() {
|
async function main() {
|
||||||
// Check if we're in staged mode
|
// Check if we're in staged mode
|
||||||
|
@ -2709,15 +2720,15 @@ jobs:
|
||||||
core.info("No valid items found in agent output");
|
core.info("No valid items found in agent output");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Find all add-issue-comment items
|
// Find all add-comment items
|
||||||
const commentItems = validatedOutput.items.filter(
|
const commentItems = validatedOutput.items.filter(
|
||||||
/** @param {any} item */ item => item.type === "add-issue-comment"
|
/** @param {any} item */ item => item.type === "add-comment"
|
||||||
);
|
);
|
||||||
if (commentItems.length === 0) {
|
if (commentItems.length === 0) {
|
||||||
core.info("No add-issue-comment items found in agent output");
|
core.info("No add-comment items found in agent output");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
core.info(`Found ${commentItems.length} add-issue-comment item(s)`);
|
core.info(`Found ${commentItems.length} add-comment item(s)`);
|
||||||
// If in staged mode, emit step summary instead of creating comments
|
// If in staged mode, emit step summary instead of creating comments
|
||||||
if (isStaged) {
|
if (isStaged) {
|
||||||
let summaryContent = "## 🎭 Staged Mode: Add Comments Preview\n\n";
|
let summaryContent = "## 🎭 Staged Mode: Add Comments Preview\n\n";
|
||||||
|
@ -2761,7 +2772,7 @@ jobs:
|
||||||
for (let i = 0; i < commentItems.length; i++) {
|
for (let i = 0; i < commentItems.length; i++) {
|
||||||
const commentItem = commentItems[i];
|
const commentItem = commentItems[i];
|
||||||
core.info(
|
core.info(
|
||||||
`Processing add-issue-comment item ${i + 1}/${commentItems.length}: bodyLength=${commentItem.body.length}`
|
`Processing add-comment item ${i + 1}/${commentItems.length}: bodyLength=${commentItem.body.length}`
|
||||||
);
|
);
|
||||||
// Determine the issue/PR number and comment endpoint for this comment
|
// Determine the issue/PR number and comment endpoint for this comment
|
||||||
let issueNumber;
|
let issueNumber;
|
||||||
|
@ -2902,6 +2913,7 @@ jobs:
|
||||||
GITHUB_AW_PR_DRAFT: "true"
|
GITHUB_AW_PR_DRAFT: "true"
|
||||||
GITHUB_AW_PR_IF_NO_CHANGES: "warn"
|
GITHUB_AW_PR_IF_NO_CHANGES: "warn"
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ secrets.DSYME_GH_TOKEN}}
|
||||||
script: |
|
script: |
|
||||||
/** @type {typeof import("fs")} */
|
/** @type {typeof import("fs")} */
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
|
3
.github/workflows/daily-perf-improver.md
vendored
3
.github/workflows/daily-perf-improver.md
vendored
|
@ -16,10 +16,11 @@ safe-outputs:
|
||||||
create-issue:
|
create-issue:
|
||||||
title-prefix: "${{ github.workflow }}"
|
title-prefix: "${{ github.workflow }}"
|
||||||
max: 5
|
max: 5
|
||||||
add-issue-comment:
|
add-comment:
|
||||||
target: "*" # can add a comment to any one single issue or pull request
|
target: "*" # can add a comment to any one single issue or pull request
|
||||||
create-pull-request:
|
create-pull-request:
|
||||||
draft: true
|
draft: true
|
||||||
|
github-token: ${{ secrets.DSYME_GH_TOKEN}}
|
||||||
|
|
||||||
tools:
|
tools:
|
||||||
web-fetch:
|
web-fetch:
|
||||||
|
|
87
.github/workflows/daily-test-improver.lock.yml
generated
vendored
87
.github/workflows/daily-test-improver.lock.yml
generated
vendored
|
@ -2,7 +2,7 @@
|
||||||
# To update this file, edit the corresponding .md file and run:
|
# To update this file, edit the corresponding .md file and run:
|
||||||
# gh aw compile
|
# gh aw compile
|
||||||
#
|
#
|
||||||
# Effective stop-time: 2025-09-19 10:32:53
|
# Effective stop-time: 2025-09-19 12:19:15
|
||||||
|
|
||||||
name: "Daily Test Coverage Improver"
|
name: "Daily Test Coverage Improver"
|
||||||
"on":
|
"on":
|
||||||
|
@ -69,7 +69,7 @@ jobs:
|
||||||
main();
|
main();
|
||||||
- name: Setup Safe Outputs Collector MCP
|
- name: Setup Safe Outputs Collector MCP
|
||||||
env:
|
env:
|
||||||
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true,\"update-issue\":true}"
|
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true,\"update-issue\":true}"
|
||||||
run: |
|
run: |
|
||||||
mkdir -p /tmp/safe-outputs
|
mkdir -p /tmp/safe-outputs
|
||||||
cat > /tmp/safe-outputs/mcp-server.cjs << 'EOF'
|
cat > /tmp/safe-outputs/mcp-server.cjs << 'EOF'
|
||||||
|
@ -222,7 +222,7 @@ jobs:
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "add-issue-comment",
|
name: "add-comment",
|
||||||
description: "Add a comment to a GitHub issue or pull request",
|
description: "Add a comment to a GitHub issue or pull request",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
|
@ -329,7 +329,7 @@ jobs:
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "add-issue-labels",
|
name: "add-labels",
|
||||||
description: "Add labels to a GitHub issue or pull request",
|
description: "Add labels to a GitHub issue or pull request",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
|
@ -503,7 +503,7 @@ jobs:
|
||||||
- name: Setup MCPs
|
- name: Setup MCPs
|
||||||
env:
|
env:
|
||||||
GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }}
|
GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }}
|
||||||
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true,\"update-issue\":true}"
|
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true,\"update-issue\":true}"
|
||||||
run: |
|
run: |
|
||||||
mkdir -p /tmp/mcp-config
|
mkdir -p /tmp/mcp-config
|
||||||
cat > /tmp/mcp-config/mcp-servers.json << 'EOF'
|
cat > /tmp/mcp-config/mcp-servers.json << 'EOF'
|
||||||
|
@ -541,7 +541,7 @@ jobs:
|
||||||
WORKFLOW_NAME="Daily Test Coverage Improver"
|
WORKFLOW_NAME="Daily Test Coverage Improver"
|
||||||
|
|
||||||
# Check stop-time limit
|
# Check stop-time limit
|
||||||
STOP_TIME="2025-09-19 10:32:53"
|
STOP_TIME="2025-09-19 12:19:15"
|
||||||
echo "Checking stop-time limit: $STOP_TIME"
|
echo "Checking stop-time limit: $STOP_TIME"
|
||||||
|
|
||||||
# Convert stop time to epoch seconds
|
# Convert stop time to epoch seconds
|
||||||
|
@ -714,7 +714,7 @@ jobs:
|
||||||
|
|
||||||
**Adding a Comment to an Issue or Pull Request**
|
**Adding a Comment to an Issue or Pull Request**
|
||||||
|
|
||||||
To add a comment to an issue or pull request, use the add-issue-comments tool from the safe-outputs MCP
|
To add a comment to an issue or pull request, use the add-comments tool from the safe-outputs MCP
|
||||||
|
|
||||||
**Creating an Issue**
|
**Creating an Issue**
|
||||||
|
|
||||||
|
@ -904,7 +904,7 @@ jobs:
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v8
|
||||||
env:
|
env:
|
||||||
GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }}
|
GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }}
|
||||||
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true,\"update-issue\":true}"
|
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true,\"update-issue\":true}"
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
async function main() {
|
async function main() {
|
||||||
|
@ -937,15 +937,12 @@ jobs:
|
||||||
let sanitized = content;
|
let sanitized = content;
|
||||||
// Neutralize @mentions to prevent unintended notifications
|
// Neutralize @mentions to prevent unintended notifications
|
||||||
sanitized = neutralizeMentions(sanitized);
|
sanitized = neutralizeMentions(sanitized);
|
||||||
|
// Remove XML comments to prevent content hiding
|
||||||
|
sanitized = removeXmlComments(sanitized);
|
||||||
|
// Remove ANSI escape sequences BEFORE removing control characters
|
||||||
|
sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, "");
|
||||||
// Remove control characters (except newlines and tabs)
|
// Remove control characters (except newlines and tabs)
|
||||||
sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
|
sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
|
||||||
// XML character escaping
|
|
||||||
sanitized = sanitized
|
|
||||||
.replace(/&/g, "&") // Must be first to avoid double-escaping
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.replace(/"/g, """)
|
|
||||||
.replace(/'/g, "'");
|
|
||||||
// URI filtering - replace non-https protocols with "(redacted)"
|
// URI filtering - replace non-https protocols with "(redacted)"
|
||||||
sanitized = sanitizeUrlProtocols(sanitized);
|
sanitized = sanitizeUrlProtocols(sanitized);
|
||||||
// Domain filtering for HTTPS URIs
|
// Domain filtering for HTTPS URIs
|
||||||
|
@ -965,8 +962,7 @@ jobs:
|
||||||
lines.slice(0, maxLines).join("\n") +
|
lines.slice(0, maxLines).join("\n") +
|
||||||
"\n[Content truncated due to line count]";
|
"\n[Content truncated due to line count]";
|
||||||
}
|
}
|
||||||
// Remove ANSI escape sequences
|
// ANSI escape sequences already removed earlier in the function
|
||||||
sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, "");
|
|
||||||
// Neutralize common bot trigger phrases
|
// Neutralize common bot trigger phrases
|
||||||
sanitized = neutralizeBotTriggers(sanitized);
|
sanitized = neutralizeBotTriggers(sanitized);
|
||||||
// Trim excessive whitespace
|
// Trim excessive whitespace
|
||||||
|
@ -978,10 +974,12 @@ jobs:
|
||||||
*/
|
*/
|
||||||
function sanitizeUrlDomains(s) {
|
function sanitizeUrlDomains(s) {
|
||||||
return s.replace(
|
return s.replace(
|
||||||
/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f]+)/gi,
|
/\bhttps:\/\/[^\s\])}'"<>&\x00-\x1f,;]+/gi,
|
||||||
(match, domain) => {
|
(match) => {
|
||||||
|
// Extract just the URL part after https://
|
||||||
|
const urlAfterProtocol = match.slice(8); // Remove 'https://'
|
||||||
// Extract the hostname part (before first slash, colon, or other delimiter)
|
// Extract the hostname part (before first slash, colon, or other delimiter)
|
||||||
const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase();
|
const hostname = urlAfterProtocol.split(/[\/:\?#]/)[0].toLowerCase();
|
||||||
// Check if this domain or any parent domain is in the allowlist
|
// Check if this domain or any parent domain is in the allowlist
|
||||||
const isAllowed = allowedDomains.some(allowedDomain => {
|
const isAllowed = allowedDomains.some(allowedDomain => {
|
||||||
const normalizedAllowed = allowedDomain.toLowerCase();
|
const normalizedAllowed = allowedDomain.toLowerCase();
|
||||||
|
@ -1000,9 +998,10 @@ jobs:
|
||||||
* @returns {string} The string with non-https protocols redacted
|
* @returns {string} The string with non-https protocols redacted
|
||||||
*/
|
*/
|
||||||
function sanitizeUrlProtocols(s) {
|
function sanitizeUrlProtocols(s) {
|
||||||
// Match both protocol:// and protocol: patterns
|
// Match protocol:// patterns (URLs) and standalone protocol: patterns that look like URLs
|
||||||
|
// Avoid matching command line flags like -v:10 or z3 -memory:high
|
||||||
return s.replace(
|
return s.replace(
|
||||||
/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi,
|
/\b(\w+):\/\/[^\s\])}'"<>&\x00-\x1f]+/gi,
|
||||||
(match, protocol) => {
|
(match, protocol) => {
|
||||||
// Allow https (case insensitive), redact everything else
|
// Allow https (case insensitive), redact everything else
|
||||||
return protocol.toLowerCase() === "https" ? match : "(redacted)";
|
return protocol.toLowerCase() === "https" ? match : "(redacted)";
|
||||||
|
@ -1021,6 +1020,16 @@ jobs:
|
||||||
(_m, p1, p2) => `${p1}\`@${p2}\``
|
(_m, p1, p2) => `${p1}\`@${p2}\``
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Removes XML comments to prevent content hiding
|
||||||
|
* @param {string} s - The string to process
|
||||||
|
* @returns {string} The string with XML comments removed
|
||||||
|
*/
|
||||||
|
function removeXmlComments(s) {
|
||||||
|
// Remove XML/HTML comments including malformed ones that might be used to hide content
|
||||||
|
// Matches: <!-- ... --> and <!--- ... --> and <!--- ... --!> variations
|
||||||
|
return s.replace(/<!--[\s\S]*?-->/g, "").replace(/<!--[\s\S]*?--!>/g, "");
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Neutralizes bot trigger phrases by wrapping them in backticks
|
* Neutralizes bot trigger phrases by wrapping them in backticks
|
||||||
* @param {string} s - The string to process
|
* @param {string} s - The string to process
|
||||||
|
@ -1054,13 +1063,13 @@ jobs:
|
||||||
switch (itemType) {
|
switch (itemType) {
|
||||||
case "create-issue":
|
case "create-issue":
|
||||||
return 1; // Only one issue allowed
|
return 1; // Only one issue allowed
|
||||||
case "add-issue-comment":
|
case "add-comment":
|
||||||
return 1; // Only one comment allowed
|
return 1; // Only one comment allowed
|
||||||
case "create-pull-request":
|
case "create-pull-request":
|
||||||
return 1; // Only one pull request allowed
|
return 1; // Only one pull request allowed
|
||||||
case "create-pull-request-review-comment":
|
case "create-pull-request-review-comment":
|
||||||
return 10; // Default to 10 review comments allowed
|
return 10; // Default to 10 review comments allowed
|
||||||
case "add-issue-labels":
|
case "add-labels":
|
||||||
return 5; // Only one labels operation allowed
|
return 5; // Only one labels operation allowed
|
||||||
case "update-issue":
|
case "update-issue":
|
||||||
return 1; // Only one issue update allowed
|
return 1; // Only one issue update allowed
|
||||||
|
@ -1275,10 +1284,10 @@ jobs:
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "add-issue-comment":
|
case "add-comment":
|
||||||
if (!item.body || typeof item.body !== "string") {
|
if (!item.body || typeof item.body !== "string") {
|
||||||
errors.push(
|
errors.push(
|
||||||
`Line ${i + 1}: add-issue-comment requires a 'body' string field`
|
`Line ${i + 1}: add-comment requires a 'body' string field`
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1313,10 +1322,10 @@ jobs:
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "add-issue-labels":
|
case "add-labels":
|
||||||
if (!item.labels || !Array.isArray(item.labels)) {
|
if (!item.labels || !Array.isArray(item.labels)) {
|
||||||
errors.push(
|
errors.push(
|
||||||
`Line ${i + 1}: add-issue-labels requires a 'labels' array field`
|
`Line ${i + 1}: add-labels requires a 'labels' array field`
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1326,7 +1335,7 @@ jobs:
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
errors.push(
|
errors.push(
|
||||||
`Line ${i + 1}: add-issue-labels labels array must contain only strings`
|
`Line ${i + 1}: add-labels labels array must contain only strings`
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -2456,6 +2465,7 @@ jobs:
|
||||||
GITHUB_AW_AGENT_OUTPUT: ${{ needs.daily-test-coverage-improver.outputs.output }}
|
GITHUB_AW_AGENT_OUTPUT: ${{ needs.daily-test-coverage-improver.outputs.output }}
|
||||||
GITHUB_AW_ISSUE_TITLE_PREFIX: "${{ github.workflow }}"
|
GITHUB_AW_ISSUE_TITLE_PREFIX: "${{ github.workflow }}"
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ secrets.DSYME_GH_TOKEN}}
|
||||||
script: |
|
script: |
|
||||||
async function main() {
|
async function main() {
|
||||||
// Check if we're in staged mode
|
// Check if we're in staged mode
|
||||||
|
@ -2645,16 +2655,17 @@ jobs:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
outputs:
|
outputs:
|
||||||
comment_id: ${{ steps.create_comment.outputs.comment_id }}
|
comment_id: ${{ steps.add_comment.outputs.comment_id }}
|
||||||
comment_url: ${{ steps.create_comment.outputs.comment_url }}
|
comment_url: ${{ steps.add_comment.outputs.comment_url }}
|
||||||
steps:
|
steps:
|
||||||
- name: Add Issue Comment
|
- name: Add Issue Comment
|
||||||
id: create_comment
|
id: add_comment
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v8
|
||||||
env:
|
env:
|
||||||
GITHUB_AW_AGENT_OUTPUT: ${{ needs.daily-test-coverage-improver.outputs.output }}
|
GITHUB_AW_AGENT_OUTPUT: ${{ needs.daily-test-coverage-improver.outputs.output }}
|
||||||
GITHUB_AW_COMMENT_TARGET: "*"
|
GITHUB_AW_COMMENT_TARGET: "*"
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ secrets.DSYME_GH_TOKEN}}
|
||||||
script: |
|
script: |
|
||||||
async function main() {
|
async function main() {
|
||||||
// Check if we're in staged mode
|
// Check if we're in staged mode
|
||||||
|
@ -2684,15 +2695,15 @@ jobs:
|
||||||
core.info("No valid items found in agent output");
|
core.info("No valid items found in agent output");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Find all add-issue-comment items
|
// Find all add-comment items
|
||||||
const commentItems = validatedOutput.items.filter(
|
const commentItems = validatedOutput.items.filter(
|
||||||
/** @param {any} item */ item => item.type === "add-issue-comment"
|
/** @param {any} item */ item => item.type === "add-comment"
|
||||||
);
|
);
|
||||||
if (commentItems.length === 0) {
|
if (commentItems.length === 0) {
|
||||||
core.info("No add-issue-comment items found in agent output");
|
core.info("No add-comment items found in agent output");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
core.info(`Found ${commentItems.length} add-issue-comment item(s)`);
|
core.info(`Found ${commentItems.length} add-comment item(s)`);
|
||||||
// If in staged mode, emit step summary instead of creating comments
|
// If in staged mode, emit step summary instead of creating comments
|
||||||
if (isStaged) {
|
if (isStaged) {
|
||||||
let summaryContent = "## 🎭 Staged Mode: Add Comments Preview\n\n";
|
let summaryContent = "## 🎭 Staged Mode: Add Comments Preview\n\n";
|
||||||
|
@ -2736,7 +2747,7 @@ jobs:
|
||||||
for (let i = 0; i < commentItems.length; i++) {
|
for (let i = 0; i < commentItems.length; i++) {
|
||||||
const commentItem = commentItems[i];
|
const commentItem = commentItems[i];
|
||||||
core.info(
|
core.info(
|
||||||
`Processing add-issue-comment item ${i + 1}/${commentItems.length}: bodyLength=${commentItem.body.length}`
|
`Processing add-comment item ${i + 1}/${commentItems.length}: bodyLength=${commentItem.body.length}`
|
||||||
);
|
);
|
||||||
// Determine the issue/PR number and comment endpoint for this comment
|
// Determine the issue/PR number and comment endpoint for this comment
|
||||||
let issueNumber;
|
let issueNumber;
|
||||||
|
@ -2877,6 +2888,7 @@ jobs:
|
||||||
GITHUB_AW_PR_DRAFT: "true"
|
GITHUB_AW_PR_DRAFT: "true"
|
||||||
GITHUB_AW_PR_IF_NO_CHANGES: "warn"
|
GITHUB_AW_PR_IF_NO_CHANGES: "warn"
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ secrets.DSYME_GH_TOKEN}}
|
||||||
script: |
|
script: |
|
||||||
/** @type {typeof import("fs")} */
|
/** @type {typeof import("fs")} */
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
@ -3197,6 +3209,7 @@ jobs:
|
||||||
GITHUB_AW_UPDATE_BODY: true
|
GITHUB_AW_UPDATE_BODY: true
|
||||||
GITHUB_AW_UPDATE_TARGET: "*"
|
GITHUB_AW_UPDATE_TARGET: "*"
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ secrets.DSYME_GH_TOKEN}}
|
||||||
script: |
|
script: |
|
||||||
async function main() {
|
async function main() {
|
||||||
// Check if we're in staged mode
|
// Check if we're in staged mode
|
||||||
|
|
3
.github/workflows/daily-test-improver.md
vendored
3
.github/workflows/daily-test-improver.md
vendored
|
@ -19,10 +19,11 @@ safe-outputs:
|
||||||
target: "*" # one single issue
|
target: "*" # one single issue
|
||||||
body: # can update the issue title/body only
|
body: # can update the issue title/body only
|
||||||
title: # can update the issue title/body only
|
title: # can update the issue title/body only
|
||||||
add-issue-comment:
|
add-comment:
|
||||||
target: "*" # can add a comment to any one single issue or pull request
|
target: "*" # can add a comment to any one single issue or pull request
|
||||||
create-pull-request: # can create a pull request
|
create-pull-request: # can create a pull request
|
||||||
draft: true
|
draft: true
|
||||||
|
github-token: ${{ secrets.DSYME_GH_TOKEN}}
|
||||||
|
|
||||||
tools:
|
tools:
|
||||||
web-fetch:
|
web-fetch:
|
||||||
|
|
83
.github/workflows/pr-fix.lock.yml
generated
vendored
83
.github/workflows/pr-fix.lock.yml
generated
vendored
|
@ -2,7 +2,7 @@
|
||||||
# To update this file, edit the corresponding .md file and run:
|
# To update this file, edit the corresponding .md file and run:
|
||||||
# gh aw compile
|
# gh aw compile
|
||||||
#
|
#
|
||||||
# Effective stop-time: 2025-09-19 10:32:53
|
# Effective stop-time: 2025-09-19 12:19:15
|
||||||
|
|
||||||
name: "PR Fix"
|
name: "PR Fix"
|
||||||
on:
|
on:
|
||||||
|
@ -599,7 +599,7 @@ jobs:
|
||||||
main();
|
main();
|
||||||
- name: Setup Safe Outputs Collector MCP
|
- name: Setup Safe Outputs Collector MCP
|
||||||
env:
|
env:
|
||||||
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true},\"create-issue\":true,\"push-to-pr-branch\":{\"enabled\":true}}"
|
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true},\"create-issue\":true,\"push-to-pr-branch\":{\"enabled\":true}}"
|
||||||
run: |
|
run: |
|
||||||
mkdir -p /tmp/safe-outputs
|
mkdir -p /tmp/safe-outputs
|
||||||
cat > /tmp/safe-outputs/mcp-server.cjs << 'EOF'
|
cat > /tmp/safe-outputs/mcp-server.cjs << 'EOF'
|
||||||
|
@ -752,7 +752,7 @@ jobs:
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "add-issue-comment",
|
name: "add-comment",
|
||||||
description: "Add a comment to a GitHub issue or pull request",
|
description: "Add a comment to a GitHub issue or pull request",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
|
@ -859,7 +859,7 @@ jobs:
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "add-issue-labels",
|
name: "add-labels",
|
||||||
description: "Add labels to a GitHub issue or pull request",
|
description: "Add labels to a GitHub issue or pull request",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
|
@ -1033,7 +1033,7 @@ jobs:
|
||||||
- name: Setup MCPs
|
- name: Setup MCPs
|
||||||
env:
|
env:
|
||||||
GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }}
|
GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }}
|
||||||
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true},\"create-issue\":true,\"push-to-pr-branch\":{\"enabled\":true}}"
|
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true},\"create-issue\":true,\"push-to-pr-branch\":{\"enabled\":true}}"
|
||||||
run: |
|
run: |
|
||||||
mkdir -p /tmp/mcp-config
|
mkdir -p /tmp/mcp-config
|
||||||
cat > /tmp/mcp-config/mcp-servers.json << 'EOF'
|
cat > /tmp/mcp-config/mcp-servers.json << 'EOF'
|
||||||
|
@ -1071,7 +1071,7 @@ jobs:
|
||||||
WORKFLOW_NAME="PR Fix"
|
WORKFLOW_NAME="PR Fix"
|
||||||
|
|
||||||
# Check stop-time limit
|
# Check stop-time limit
|
||||||
STOP_TIME="2025-09-19 10:32:53"
|
STOP_TIME="2025-09-19 12:19:15"
|
||||||
echo "Checking stop-time limit: $STOP_TIME"
|
echo "Checking stop-time limit: $STOP_TIME"
|
||||||
|
|
||||||
# Convert stop time to epoch seconds
|
# Convert stop time to epoch seconds
|
||||||
|
@ -1174,7 +1174,7 @@ jobs:
|
||||||
|
|
||||||
**Adding a Comment to an Issue or Pull Request**
|
**Adding a Comment to an Issue or Pull Request**
|
||||||
|
|
||||||
To add a comment to an issue or pull request, use the add-issue-comments tool from the safe-outputs MCP
|
To add a comment to an issue or pull request, use the add-comments tool from the safe-outputs MCP
|
||||||
|
|
||||||
**Creating an Issue**
|
**Creating an Issue**
|
||||||
|
|
||||||
|
@ -1358,7 +1358,7 @@ jobs:
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v8
|
||||||
env:
|
env:
|
||||||
GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }}
|
GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }}
|
||||||
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true},\"create-issue\":true,\"push-to-pr-branch\":{\"enabled\":true}}"
|
GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true},\"create-issue\":true,\"push-to-pr-branch\":{\"enabled\":true}}"
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
async function main() {
|
async function main() {
|
||||||
|
@ -1391,15 +1391,12 @@ jobs:
|
||||||
let sanitized = content;
|
let sanitized = content;
|
||||||
// Neutralize @mentions to prevent unintended notifications
|
// Neutralize @mentions to prevent unintended notifications
|
||||||
sanitized = neutralizeMentions(sanitized);
|
sanitized = neutralizeMentions(sanitized);
|
||||||
|
// Remove XML comments to prevent content hiding
|
||||||
|
sanitized = removeXmlComments(sanitized);
|
||||||
|
// Remove ANSI escape sequences BEFORE removing control characters
|
||||||
|
sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, "");
|
||||||
// Remove control characters (except newlines and tabs)
|
// Remove control characters (except newlines and tabs)
|
||||||
sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
|
sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
|
||||||
// XML character escaping
|
|
||||||
sanitized = sanitized
|
|
||||||
.replace(/&/g, "&") // Must be first to avoid double-escaping
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.replace(/"/g, """)
|
|
||||||
.replace(/'/g, "'");
|
|
||||||
// URI filtering - replace non-https protocols with "(redacted)"
|
// URI filtering - replace non-https protocols with "(redacted)"
|
||||||
sanitized = sanitizeUrlProtocols(sanitized);
|
sanitized = sanitizeUrlProtocols(sanitized);
|
||||||
// Domain filtering for HTTPS URIs
|
// Domain filtering for HTTPS URIs
|
||||||
|
@ -1419,8 +1416,7 @@ jobs:
|
||||||
lines.slice(0, maxLines).join("\n") +
|
lines.slice(0, maxLines).join("\n") +
|
||||||
"\n[Content truncated due to line count]";
|
"\n[Content truncated due to line count]";
|
||||||
}
|
}
|
||||||
// Remove ANSI escape sequences
|
// ANSI escape sequences already removed earlier in the function
|
||||||
sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, "");
|
|
||||||
// Neutralize common bot trigger phrases
|
// Neutralize common bot trigger phrases
|
||||||
sanitized = neutralizeBotTriggers(sanitized);
|
sanitized = neutralizeBotTriggers(sanitized);
|
||||||
// Trim excessive whitespace
|
// Trim excessive whitespace
|
||||||
|
@ -1432,10 +1428,12 @@ jobs:
|
||||||
*/
|
*/
|
||||||
function sanitizeUrlDomains(s) {
|
function sanitizeUrlDomains(s) {
|
||||||
return s.replace(
|
return s.replace(
|
||||||
/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f]+)/gi,
|
/\bhttps:\/\/[^\s\])}'"<>&\x00-\x1f,;]+/gi,
|
||||||
(match, domain) => {
|
(match) => {
|
||||||
|
// Extract just the URL part after https://
|
||||||
|
const urlAfterProtocol = match.slice(8); // Remove 'https://'
|
||||||
// Extract the hostname part (before first slash, colon, or other delimiter)
|
// Extract the hostname part (before first slash, colon, or other delimiter)
|
||||||
const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase();
|
const hostname = urlAfterProtocol.split(/[\/:\?#]/)[0].toLowerCase();
|
||||||
// Check if this domain or any parent domain is in the allowlist
|
// Check if this domain or any parent domain is in the allowlist
|
||||||
const isAllowed = allowedDomains.some(allowedDomain => {
|
const isAllowed = allowedDomains.some(allowedDomain => {
|
||||||
const normalizedAllowed = allowedDomain.toLowerCase();
|
const normalizedAllowed = allowedDomain.toLowerCase();
|
||||||
|
@ -1454,9 +1452,10 @@ jobs:
|
||||||
* @returns {string} The string with non-https protocols redacted
|
* @returns {string} The string with non-https protocols redacted
|
||||||
*/
|
*/
|
||||||
function sanitizeUrlProtocols(s) {
|
function sanitizeUrlProtocols(s) {
|
||||||
// Match both protocol:// and protocol: patterns
|
// Match protocol:// patterns (URLs) and standalone protocol: patterns that look like URLs
|
||||||
|
// Avoid matching command line flags like -v:10 or z3 -memory:high
|
||||||
return s.replace(
|
return s.replace(
|
||||||
/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi,
|
/\b(\w+):\/\/[^\s\])}'"<>&\x00-\x1f]+/gi,
|
||||||
(match, protocol) => {
|
(match, protocol) => {
|
||||||
// Allow https (case insensitive), redact everything else
|
// Allow https (case insensitive), redact everything else
|
||||||
return protocol.toLowerCase() === "https" ? match : "(redacted)";
|
return protocol.toLowerCase() === "https" ? match : "(redacted)";
|
||||||
|
@ -1475,6 +1474,16 @@ jobs:
|
||||||
(_m, p1, p2) => `${p1}\`@${p2}\``
|
(_m, p1, p2) => `${p1}\`@${p2}\``
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Removes XML comments to prevent content hiding
|
||||||
|
* @param {string} s - The string to process
|
||||||
|
* @returns {string} The string with XML comments removed
|
||||||
|
*/
|
||||||
|
function removeXmlComments(s) {
|
||||||
|
// Remove XML/HTML comments including malformed ones that might be used to hide content
|
||||||
|
// Matches: <!-- ... --> and <!--- ... --> and <!--- ... --!> variations
|
||||||
|
return s.replace(/<!--[\s\S]*?-->/g, "").replace(/<!--[\s\S]*?--!>/g, "");
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Neutralizes bot trigger phrases by wrapping them in backticks
|
* Neutralizes bot trigger phrases by wrapping them in backticks
|
||||||
* @param {string} s - The string to process
|
* @param {string} s - The string to process
|
||||||
|
@ -1508,13 +1517,13 @@ jobs:
|
||||||
switch (itemType) {
|
switch (itemType) {
|
||||||
case "create-issue":
|
case "create-issue":
|
||||||
return 1; // Only one issue allowed
|
return 1; // Only one issue allowed
|
||||||
case "add-issue-comment":
|
case "add-comment":
|
||||||
return 1; // Only one comment allowed
|
return 1; // Only one comment allowed
|
||||||
case "create-pull-request":
|
case "create-pull-request":
|
||||||
return 1; // Only one pull request allowed
|
return 1; // Only one pull request allowed
|
||||||
case "create-pull-request-review-comment":
|
case "create-pull-request-review-comment":
|
||||||
return 10; // Default to 10 review comments allowed
|
return 10; // Default to 10 review comments allowed
|
||||||
case "add-issue-labels":
|
case "add-labels":
|
||||||
return 5; // Only one labels operation allowed
|
return 5; // Only one labels operation allowed
|
||||||
case "update-issue":
|
case "update-issue":
|
||||||
return 1; // Only one issue update allowed
|
return 1; // Only one issue update allowed
|
||||||
|
@ -1729,10 +1738,10 @@ jobs:
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "add-issue-comment":
|
case "add-comment":
|
||||||
if (!item.body || typeof item.body !== "string") {
|
if (!item.body || typeof item.body !== "string") {
|
||||||
errors.push(
|
errors.push(
|
||||||
`Line ${i + 1}: add-issue-comment requires a 'body' string field`
|
`Line ${i + 1}: add-comment requires a 'body' string field`
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1767,10 +1776,10 @@ jobs:
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "add-issue-labels":
|
case "add-labels":
|
||||||
if (!item.labels || !Array.isArray(item.labels)) {
|
if (!item.labels || !Array.isArray(item.labels)) {
|
||||||
errors.push(
|
errors.push(
|
||||||
`Line ${i + 1}: add-issue-labels requires a 'labels' array field`
|
`Line ${i + 1}: add-labels requires a 'labels' array field`
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1780,7 +1789,7 @@ jobs:
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
errors.push(
|
errors.push(
|
||||||
`Line ${i + 1}: add-issue-labels labels array must contain only strings`
|
`Line ${i + 1}: add-labels labels array must contain only strings`
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -3019,11 +3028,11 @@ jobs:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
outputs:
|
outputs:
|
||||||
comment_id: ${{ steps.create_comment.outputs.comment_id }}
|
comment_id: ${{ steps.add_comment.outputs.comment_id }}
|
||||||
comment_url: ${{ steps.create_comment.outputs.comment_url }}
|
comment_url: ${{ steps.add_comment.outputs.comment_url }}
|
||||||
steps:
|
steps:
|
||||||
- name: Add Issue Comment
|
- name: Add Issue Comment
|
||||||
id: create_comment
|
id: add_comment
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v8
|
||||||
env:
|
env:
|
||||||
GITHUB_AW_AGENT_OUTPUT: ${{ needs.pr-fix.outputs.output }}
|
GITHUB_AW_AGENT_OUTPUT: ${{ needs.pr-fix.outputs.output }}
|
||||||
|
@ -3058,15 +3067,15 @@ jobs:
|
||||||
core.info("No valid items found in agent output");
|
core.info("No valid items found in agent output");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Find all add-issue-comment items
|
// Find all add-comment items
|
||||||
const commentItems = validatedOutput.items.filter(
|
const commentItems = validatedOutput.items.filter(
|
||||||
/** @param {any} item */ item => item.type === "add-issue-comment"
|
/** @param {any} item */ item => item.type === "add-comment"
|
||||||
);
|
);
|
||||||
if (commentItems.length === 0) {
|
if (commentItems.length === 0) {
|
||||||
core.info("No add-issue-comment items found in agent output");
|
core.info("No add-comment items found in agent output");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
core.info(`Found ${commentItems.length} add-issue-comment item(s)`);
|
core.info(`Found ${commentItems.length} add-comment item(s)`);
|
||||||
// If in staged mode, emit step summary instead of creating comments
|
// If in staged mode, emit step summary instead of creating comments
|
||||||
if (isStaged) {
|
if (isStaged) {
|
||||||
let summaryContent = "## 🎭 Staged Mode: Add Comments Preview\n\n";
|
let summaryContent = "## 🎭 Staged Mode: Add Comments Preview\n\n";
|
||||||
|
@ -3110,7 +3119,7 @@ jobs:
|
||||||
for (let i = 0; i < commentItems.length; i++) {
|
for (let i = 0; i < commentItems.length; i++) {
|
||||||
const commentItem = commentItems[i];
|
const commentItem = commentItems[i];
|
||||||
core.info(
|
core.info(
|
||||||
`Processing add-issue-comment item ${i + 1}/${commentItems.length}: bodyLength=${commentItem.body.length}`
|
`Processing add-comment item ${i + 1}/${commentItems.length}: bodyLength=${commentItem.body.length}`
|
||||||
);
|
);
|
||||||
// Determine the issue/PR number and comment endpoint for this comment
|
// Determine the issue/PR number and comment endpoint for this comment
|
||||||
let issueNumber;
|
let issueNumber;
|
||||||
|
|
2
.github/workflows/pr-fix.md
vendored
2
.github/workflows/pr-fix.md
vendored
|
@ -14,7 +14,7 @@ safe-outputs:
|
||||||
push-to-pr-branch:
|
push-to-pr-branch:
|
||||||
create-issue:
|
create-issue:
|
||||||
title-prefix: "${{ github.workflow }}"
|
title-prefix: "${{ github.workflow }}"
|
||||||
add-issue-comment:
|
add-comment:
|
||||||
github-token: ${{ secrets.DSYME_GH_TOKEN}}
|
github-token: ${{ secrets.DSYME_GH_TOKEN}}
|
||||||
|
|
||||||
tools:
|
tools:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue