From 825c040c8a7f7fb58a23fb059defd336f7437d64 Mon Sep 17 00:00:00 2001 From: hawkeyexl Date: Thu, 26 Feb 2026 11:29:37 -0800 Subject: [PATCH 1/4] Enhance Doc Detective GitHub Action with new inputs and integration features - Add 'integrations' and 'prompt' inputs to workflows for better customization. - Update 'issue_body' description to include new prompt functionality. - Implement integration handling in the main action logic to notify relevant integrations in issue creation. - Create a new job in the test workflow to demonstrate integration functionality. - Update dependencies in package-lock.json for improved stability and performance. --- .github/workflows/local.yml | 10 ++ .github/workflows/main.yml | 10 ++ .github/workflows/test.yml | 20 ++++ action.yml | 21 +++- dist/index.js | 118 ++++++++++++++++----- index.js | 55 +++++++++- package-lock.json | 197 +++++++++++++++++++----------------- 7 files changed, 307 insertions(+), 124 deletions(-) diff --git a/.github/workflows/local.yml b/.github/workflows/local.yml index 6334650..2df3b58 100644 --- a/.github/workflows/local.yml +++ b/.github/workflows/local.yml @@ -38,6 +38,14 @@ on: description: issue_body required: false default: 'A Doc Detective run ($RUN_URL) failed with the following results:$RESULTS' + integrations: + description: integrations + required: false + default: '' + prompt: + description: prompt + required: false + default: '' permissions: contents: write @@ -59,3 +67,5 @@ jobs: id: dd with: config: ./artifacts/.doc-detective.json + integrations: ${{ github.event.inputs.integrations }} + prompt: ${{ github.event.inputs.prompt }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3bf2cd0..43b7462 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -42,6 +42,14 @@ on: description: issue_body required: false default: 'A Doc Detective run ($RUN_URL) failed with the following results:$RESULTS' + integrations: + description: integrations + required: false + default: '' + prompt: + description: prompt + required: false + default: '' permissions: contents: write @@ -71,3 +79,5 @@ jobs: exit_on_fail: ${{ github.event.inputs.exit_on_fail }} create_issue_on_fail: ${{ github.event.inputs.create_issue_on_fail }} issue_body: ${{ github.event.inputs.issue_body }} + integrations: ${{ github.event.inputs.integrations }} + prompt: ${{ github.event.inputs.prompt }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3284aa6..d26831b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -44,3 +44,23 @@ jobs: config: ./artifacts/.doc-detective.json input: ./artifacts/doc-content-inline-tests-fail.md exit_on_fail: true + + fail-tests-with-integrations: + runs-on: ubuntu-latest + name: Fail tests with integrations + permissions: + contents: read + issues: write + steps: + - uses: actions/checkout@v4 + - name: Run Doc Detective + uses: ./ + id: dd + continue-on-error: true + with: + config: ./artifacts/.doc-detective.json + input: ./artifacts/doc-content-inline-tests-fail.md + exit_on_fail: true + create_issue_on_fail: true + integrations: doc-sentinel,claude + prompt: "Investigate the failures and suggest fixes" diff --git a/action.yml b/action.yml index c498a80..c7ab2e1 100644 --- a/action.yml +++ b/action.yml @@ -59,7 +59,7 @@ inputs: required: false default: "Doc Detective Failure" issue_body: - description: The body of the created GitHub issue. `$RESULTS` inserts the results object. `$RUN_URL` inserts the URL of the workflow that created the issue. Only valid if "create_issue_on_fail" is set to "true". + description: The body of the created GitHub issue. `$RESULTS` inserts the results object. `$RUN_URL` inserts the URL of the workflow that created the issue. `$PROMPT` inserts the prompt text. Only valid if "create_issue_on_fail" is set to "true". required: false default: "A Doc Detective run ($RUN_URL) failed with the following results:$RESULTS" issue_labels: @@ -70,6 +70,25 @@ inputs: description: Comma-separated list of GitHub usernames to assign to the GitHub issue. Only valid if "create_issue_on_fail" is set to "true". required: false default: "" + integrations: + description: >- + Comma-separated list of integrations to notify in the created GitHub issue. + When specified, matching integrations are triggered via mentions or commands + in a collapsible "Integrations" section appended to the issue body. + Supported values: "doc-sentinel", "promptless", "dosu", "claude", "opencode", "copilot". + The "copilot" integration auto-assigns the issue to Copilot instead of adding to the accordion. + Invalid values are ignored with a warning. + Only valid if "create_issue_on_fail" is set to "true". + required: false + default: "" + prompt: + description: >- + The prompt text passed to integrations and available in issue bodies. + In integration strings, `$PROMPT` is replaced with this value (e.g., "@claude "). + Also available as `$PROMPT` in the `issue_body` template alongside `$RUN_URL` and `$RESULTS`. + Only valid if "create_issue_on_fail" is set to "true". + required: false + default: "Investigate the causes of the failures reported in this Doc Detective test output and suggest fixes" token: description: The GitHub token to use for creating issues. Defaults to the token already available to the GitHub Action workflow. Only set this if you want to override the default token. required: false diff --git a/dist/index.js b/dist/index.js index 6831655..abfc310 100644 --- a/dist/index.js +++ b/dist/index.js @@ -2759,7 +2759,7 @@ class HttpClient { } const usingSsl = parsedUrl.protocol === 'https:'; proxyAgent = new undici_1.ProxyAgent(Object.assign({ uri: proxyUrl.href, pipelining: !this._keepAlive ? 0 : 1 }, ((proxyUrl.username || proxyUrl.password) && { - token: `${proxyUrl.username}:${proxyUrl.password}` + token: `Basic ${Buffer.from(`${proxyUrl.username}:${proxyUrl.password}`).toString('base64')}` }))); this._proxyAgentDispatcher = proxyAgent; if (usingSsl && this._ignoreSslError) { @@ -2873,11 +2873,11 @@ function getProxyUrl(reqUrl) { })(); if (proxyVar) { try { - return new URL(proxyVar); + return new DecodedURL(proxyVar); } catch (_a) { if (!proxyVar.startsWith('http://') && !proxyVar.startsWith('https://')) - return new URL(`http://${proxyVar}`); + return new DecodedURL(`http://${proxyVar}`); } } else { @@ -2936,6 +2936,19 @@ function isLoopbackAddress(host) { hostLower.startsWith('[::1]') || hostLower.startsWith('[0:0:0:0:0:0:0:1]')); } +class DecodedURL extends URL { + constructor(url, base) { + super(url, base); + this._decodedUsername = decodeURIComponent(super.username); + this._decodedPassword = decodeURIComponent(super.password); + } + get username() { + return this._decodedUsername; + } + get password() { + return this._decodedPassword; + } +} //# sourceMappingURL=proxy.js.map /***/ }), @@ -3545,11 +3558,11 @@ var __copyProps = (to, from, except, desc) => { var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // pkg/dist-src/index.js -var dist_src_exports = {}; -__export(dist_src_exports, { +var index_exports = {}; +__export(index_exports, { Octokit: () => Octokit }); -module.exports = __toCommonJS(dist_src_exports); +module.exports = __toCommonJS(index_exports); var import_universal_user_agent = __nccwpck_require__(3843); var import_before_after_hook = __nccwpck_require__(2732); var import_request = __nccwpck_require__(8636); @@ -3557,13 +3570,28 @@ var import_graphql = __nccwpck_require__(7); var import_auth_token = __nccwpck_require__(7864); // pkg/dist-src/version.js -var VERSION = "5.1.0"; +var VERSION = "5.2.2"; // pkg/dist-src/index.js var noop = () => { }; var consoleWarn = console.warn.bind(console); var consoleError = console.error.bind(console); +function createLogger(logger = {}) { + if (typeof logger.debug !== "function") { + logger.debug = noop; + } + if (typeof logger.info !== "function") { + logger.info = noop; + } + if (typeof logger.warn !== "function") { + logger.warn = consoleWarn; + } + if (typeof logger.error !== "function") { + logger.error = consoleError; + } + return logger; +} var userAgentTrail = `octokit-core.js/${VERSION} ${(0, import_universal_user_agent.getUserAgent)()}`; var Octokit = class { static { @@ -3637,15 +3665,7 @@ var Octokit = class { } this.request = import_request.request.defaults(requestDefaults); this.graphql = (0, import_graphql.withCustomRequest)(this.request).defaults(requestDefaults); - this.log = Object.assign( - { - debug: noop, - info: noop, - warn: consoleWarn, - error: consoleError - }, - options.log - ); + this.log = createLogger(options.log); this.hook = hook; if (!options.authStrategy) { if (!options.auth) { @@ -4098,18 +4118,18 @@ var __copyProps = (to, from, except, desc) => { var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // pkg/dist-src/index.js -var dist_src_exports = {}; -__export(dist_src_exports, { +var index_exports = {}; +__export(index_exports, { GraphqlResponseError: () => GraphqlResponseError, graphql: () => graphql2, withCustomRequest: () => withCustomRequest }); -module.exports = __toCommonJS(dist_src_exports); +module.exports = __toCommonJS(index_exports); var import_request3 = __nccwpck_require__(8636); var import_universal_user_agent = __nccwpck_require__(3843); // pkg/dist-src/version.js -var VERSION = "7.0.2"; +var VERSION = "7.1.1"; // pkg/dist-src/with-defaults.js var import_request2 = __nccwpck_require__(8636); @@ -4157,8 +4177,7 @@ function graphql(request2, query, options) { ); } for (const key in options) { - if (!FORBIDDEN_VARIABLE_OPTIONS.includes(key)) - continue; + if (!FORBIDDEN_VARIABLE_OPTIONS.includes(key)) continue; return Promise.reject( new Error( `[@octokit/graphql] "${key}" cannot be used as variable name` @@ -31825,6 +31844,47 @@ const { execSync } = __nccwpck_require__(5317); const meta = { dist_interface: "github-actions" }; process.env["DOC_DETECTIVE_META"] = JSON.stringify(meta); +const INTEGRATION_MAP = { + "doc-sentinel": "\n/cc @reem-sab", + "promptless": "\n@Promptless $PROMPT", + "dosu": "\n@dosu $PROMPT", + "claude": "\n@claude $PROMPT", + "opencode": "\n/opencode $PROMPT", +}; + +// All valid integration names (INTEGRATION_MAP keys + special-case integrations) +const VALID_INTEGRATIONS = new Set([...Object.keys(INTEGRATION_MAP), "copilot"]); + +function parseIntegrations(integrationsInput) { + if (!integrationsInput || !integrationsInput.trim()) return []; + + const requested = integrationsInput.split(",").map((s) => s.trim().toLowerCase()).filter((s) => s.length > 0); + const valid = []; + + for (const name of requested) { + if (VALID_INTEGRATIONS.has(name)) { + valid.push(name); + } else { + core.warning( + `Unknown integration "${name}". Supported integrations: ${[...VALID_INTEGRATIONS].join(", ")}` + ); + } + } + + return valid; +} + +function buildIntegrationsAccordion(integrations, prompt) { + const accordionEntries = integrations + .filter((name) => name !== "copilot") + .map((name) => INTEGRATION_MAP[name].replace("$PROMPT", prompt)) + .filter(Boolean); + + if (accordionEntries.length === 0) return ""; + + return `\n\n
\nIntegrations\n${accordionEntries.join("\n")}\n
`; +} + const repoOwner = github.context.repo.owner; const repoName = github.context.repo.repo; const runId = process.env.GITHUB_RUN_ID; @@ -31968,12 +32028,20 @@ async function main() { async function createIssue(results) { const token = core.getInput("token"); const title = core.getInput("issue_title"); + const prompt = core.getInput("prompt"); + const integrations = parseIntegrations(core.getInput("integrations")); + const integrationsAccordion = buildIntegrationsAccordion(integrations, prompt); const body = core .getInput("issue_body") .replace("$RUN_URL", runURL) - .replace("$RESULTS", `\n\n\`\`\`json\n${results}\n\`\`\``); + .replace("$RESULTS", `\n\n\`\`\`json\n${results}\n\`\`\``) + .replace("$PROMPT", prompt) + + integrationsAccordion; const labels = core.getInput("issue_labels"); - const assignees = core.getInput("issue_assignees"); + const assigneesList = core.getInput("issue_assignees").split(",").filter((s) => s.length > 0); + if (integrations.includes("copilot")) { + assigneesList.push("copilot-swe-agent"); + } const octokit = github.getOctokit(token); @@ -31983,7 +32051,7 @@ async function createIssue(results) { title, body, labels: labels.split(","), - assignees: assignees.split(","), + assignees: assigneesList, }); core.info(`Issue created: ${issue.data.html_url}`); diff --git a/index.js b/index.js index a712f78..8721bd1 100644 --- a/index.js +++ b/index.js @@ -8,6 +8,47 @@ const { execSync } = require("child_process"); const meta = { dist_interface: "github-actions" }; process.env["DOC_DETECTIVE_META"] = JSON.stringify(meta); +const INTEGRATION_MAP = { + "doc-sentinel": "\n/cc @reem-sab", + "promptless": "\n@Promptless $PROMPT", + "dosu": "\n@dosu $PROMPT", + "claude": "\n@claude $PROMPT", + "opencode": "\n/opencode $PROMPT", +}; + +// All valid integration names (INTEGRATION_MAP keys + special-case integrations) +const VALID_INTEGRATIONS = new Set([...Object.keys(INTEGRATION_MAP), "copilot"]); + +function parseIntegrations(integrationsInput) { + if (!integrationsInput || !integrationsInput.trim()) return []; + + const requested = integrationsInput.split(",").map((s) => s.trim().toLowerCase()).filter((s) => s.length > 0); + const valid = []; + + for (const name of requested) { + if (VALID_INTEGRATIONS.has(name)) { + valid.push(name); + } else { + core.warning( + `Unknown integration "${name}". Supported integrations: ${[...VALID_INTEGRATIONS].join(", ")}` + ); + } + } + + return valid; +} + +function buildIntegrationsAccordion(integrations, prompt) { + const accordionEntries = integrations + .filter((name) => name !== "copilot") + .map((name) => INTEGRATION_MAP[name].replace("$PROMPT", prompt)) + .filter(Boolean); + + if (accordionEntries.length === 0) return ""; + + return `\n\n
\nIntegrations\n${accordionEntries.join("\n")}\n
`; +} + const repoOwner = github.context.repo.owner; const repoName = github.context.repo.repo; const runId = process.env.GITHUB_RUN_ID; @@ -151,12 +192,20 @@ async function main() { async function createIssue(results) { const token = core.getInput("token"); const title = core.getInput("issue_title"); + const prompt = core.getInput("prompt"); + const integrations = parseIntegrations(core.getInput("integrations")); + const integrationsAccordion = buildIntegrationsAccordion(integrations, prompt); const body = core .getInput("issue_body") .replace("$RUN_URL", runURL) - .replace("$RESULTS", `\n\n\`\`\`json\n${results}\n\`\`\``); + .replace("$RESULTS", `\n\n\`\`\`json\n${results}\n\`\`\``) + .replace("$PROMPT", prompt) + + integrationsAccordion; const labels = core.getInput("issue_labels"); - const assignees = core.getInput("issue_assignees"); + const assigneesList = core.getInput("issue_assignees").split(",").filter((s) => s.length > 0); + if (integrations.includes("copilot")) { + assigneesList.push("copilot-swe-agent"); + } const octokit = github.getOctokit(token); @@ -166,7 +215,7 @@ async function createIssue(results) { title, body, labels: labels.split(","), - assignees: assignees.split(","), + assignees: assigneesList, }); core.info(`Issue created: ${issue.data.html_url}`); diff --git a/package-lock.json b/package-lock.json index 93a9fe6..ce7aa36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz", "integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==", + "license": "MIT", "dependencies": { "@actions/exec": "^1.1.1", "@actions/http-client": "^2.0.1" @@ -31,6 +32,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz", "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==", + "license": "MIT", "dependencies": { "@actions/io": "^1.0.1" } @@ -51,9 +53,10 @@ } }, "node_modules/@actions/http-client": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.1.tgz", - "integrity": "sha512-KhC/cZsq7f8I4LfZSJKgCvEwfkE8o1538VoBeoGzokVLLnbFDEAdFD3UhoMklxo2un9NJVBdANOresx7vTHlHw==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz", + "integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==", + "license": "MIT", "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" @@ -62,12 +65,14 @@ "node_modules/@actions/io": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz", - "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==" + "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==", + "license": "MIT" }, "node_modules/@fastify/busboy": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "license": "MIT", "engines": { "node": ">=14" } @@ -76,20 +81,22 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", + "license": "MIT", "engines": { "node": ">= 18" } }, "node_modules/@octokit/core": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.1.0.tgz", - "integrity": "sha512-BDa2VAMLSh3otEiaMJ/3Y36GU4qf6GI+VivQ/P41NC6GHcdxpKlqV0ikSZ5gdQsmS3ojXeRx5vasgNTinF0Q4g==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.2.tgz", + "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", + "license": "MIT", "dependencies": { "@octokit/auth-token": "^4.0.0", - "@octokit/graphql": "^7.0.0", - "@octokit/request": "^8.0.2", - "@octokit/request-error": "^5.0.0", - "@octokit/types": "^12.0.0", + "@octokit/graphql": "^7.1.0", + "@octokit/request": "^8.4.1", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.0.0", "before-after-hook": "^2.2.0", "universal-user-agent": "^6.0.0" }, @@ -110,28 +117,14 @@ "node": ">= 18" } }, - "node_modules/@octokit/endpoint/node_modules/@octokit/openapi-types": { - "version": "24.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", - "license": "MIT" - }, - "node_modules/@octokit/endpoint/node_modules/@octokit/types": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", - "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^24.2.0" - } - }, "node_modules/@octokit/graphql": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.0.2.tgz", - "integrity": "sha512-OJ2iGMtj5Tg3s6RaXH22cJcxXRi7Y3EBqbHTBRq+PQAqfaS8f/236fUrWhfSn8P4jovyzqucxme7/vWSSZBX2Q==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz", + "integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==", + "license": "MIT", "dependencies": { - "@octokit/request": "^8.0.1", - "@octokit/types": "^12.0.0", + "@octokit/request": "^8.4.1", + "@octokit/types": "^13.0.0", "universal-user-agent": "^6.0.0" }, "engines": { @@ -139,9 +132,10 @@ } }, "node_modules/@octokit/openapi-types": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", - "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==" + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" }, "node_modules/@octokit/plugin-paginate-rest": { "version": "9.2.2", @@ -158,10 +152,26 @@ "@octokit/core": "5" } }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, "node_modules/@octokit/plugin-rest-endpoint-methods": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz", "integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==", + "license": "MIT", "dependencies": { "@octokit/types": "^12.6.0" }, @@ -172,6 +182,21 @@ "@octokit/core": "5" } }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, "node_modules/@octokit/request": { "version": "8.4.1", "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz", @@ -201,28 +226,7 @@ "node": ">= 18" } }, - "node_modules/@octokit/request-error/node_modules/@octokit/openapi-types": { - "version": "24.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", - "license": "MIT" - }, - "node_modules/@octokit/request-error/node_modules/@octokit/types": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", - "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^24.2.0" - } - }, - "node_modules/@octokit/request/node_modules/@octokit/openapi-types": { - "version": "24.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", - "license": "MIT" - }, - "node_modules/@octokit/request/node_modules/@octokit/types": { + "node_modules/@octokit/types": { "version": "13.10.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", @@ -231,18 +235,10 @@ "@octokit/openapi-types": "^24.2.0" } }, - "node_modules/@octokit/types": { - "version": "12.6.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", - "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", - "dependencies": { - "@octokit/openapi-types": "^20.0.0" - } - }, "node_modules/@vercel/ncc": { - "version": "0.38.3", - "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.38.3.tgz", - "integrity": "sha512-rnK6hJBS6mwc+Bkab+PGPs9OiS0i/3kdTO+CkI8V0/VrW3vmz7O2Pxjw/owOlmo6PKEIxRSeZKv/kuL9itnpYA==", + "version": "0.38.4", + "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.38.4.tgz", + "integrity": "sha512-8LwjnlP39s08C08J5NstzriPvW1SP8Zfpp1BvC2sI35kPeZnHfxVkCwu4/+Wodgnd60UtT1n8K8zw+Mp7J9JmQ==", "dev": true, "license": "MIT", "bin": { @@ -263,15 +259,20 @@ } }, "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } }, "node_modules/before-after-hook": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", - "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", + "license": "Apache-2.0" }, "node_modules/binary-extensions": { "version": "2.3.0", @@ -286,13 +287,16 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", "dev": true, + "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/braces": { @@ -331,12 +335,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -358,7 +356,8 @@ "node_modules/deprecation": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", - "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "license": "ISC" }, "node_modules/fill-range": { "version": "7.1.1", @@ -442,15 +441,19 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/ms": { @@ -461,16 +464,16 @@ "license": "MIT" }, "node_modules/nodemon": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", - "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz", + "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==", "dev": true, "license": "MIT", "dependencies": { "chokidar": "^3.5.2", "debug": "^4", "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", + "minimatch": "^10.2.1", "pstree.remy": "^1.1.8", "semver": "^7.5.3", "simple-update-notifier": "^2.0.0", @@ -502,6 +505,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", "dependencies": { "wrappy": "1" } @@ -598,6 +602,7 @@ "version": "0.0.6", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "license": "MIT", "engines": { "node": ">=0.6.11 <=0.7.0 || >=0.7.3" } @@ -623,12 +628,14 @@ "node_modules/universal-user-agent": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", - "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==" + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", + "license": "ISC" }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" } } } From 857d7804de0f6d070252957671bc7bafa5b2c80e Mon Sep 17 00:00:00 2001 From: hawkeyexl Date: Thu, 26 Feb 2026 11:38:10 -0800 Subject: [PATCH 2/4] Update action.yml to enhance integration inputs --- action.yml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/action.yml b/action.yml index c7ab2e1..6d3dd66 100644 --- a/action.yml +++ b/action.yml @@ -72,23 +72,22 @@ inputs: default: "" integrations: description: >- - Comma-separated list of integrations to notify in the created GitHub issue. - When specified, matching integrations are triggered via mentions or commands - in a collapsible "Integrations" section appended to the issue body. - Supported values: "doc-sentinel", "promptless", "dosu", "claude", "opencode", "copilot". + Comma-separated list of integrations to notify in the created GitHub issue. When specified, matching integrations are triggered via mentions or commands in a collapsible "Integrations" section appended to the issue body. + + Supported values: "doc-sentinel", "promptless", "dosu", "claude", "opencode", "copilot". Invalid values are ignored with a warning. + The "copilot" integration auto-assigns the issue to Copilot instead of adding to the accordion. - Invalid values are ignored with a warning. + Only valid if "create_issue_on_fail" is set to "true". required: false default: "" prompt: description: >- - The prompt text passed to integrations and available in issue bodies. - In integration strings, `$PROMPT` is replaced with this value (e.g., "@claude "). - Also available as `$PROMPT` in the `issue_body` template alongside `$RUN_URL` and `$RESULTS`. + The prompt passed to integrations. For example, if `integrations` is set to `claude`, the issue includes "@claude "). Also available as `$PROMPT` in the `issue_body` template. + Only valid if "create_issue_on_fail" is set to "true". required: false - default: "Investigate the causes of the failures reported in this Doc Detective test output and suggest fixes" + default: "Investigate potential causes of the failures reported in this Doc Detective test output and suggest fixes." token: description: The GitHub token to use for creating issues. Defaults to the token already available to the GitHub Action workflow. Only set this if you want to override the default token. required: false From b64a363d718d277f0f9c17bbd6cdedc0870da87f Mon Sep 17 00:00:00 2001 From: hawkeyexl Date: Thu, 26 Feb 2026 11:45:00 -0800 Subject: [PATCH 3/4] Address review feedback: sanitize inputs, use replaceAll, add fault tolerance - Use replaceAll() for placeholder substitution in issue body templates - Sanitize labels and assignees with trim + filter(Boolean) - Add deduplication guard for copilot-swe-agent assignee - Retry issue creation without assignees on 422 validation errors - Guard fail-tests-with-integrations job to only run on workflow_dispatch - Remove empty prompt defaults from workflow_dispatch inputs - Fix typo in action.yml prompt description Co-Authored-By: Claude Opus 4.6 --- .github/workflows/local.yml | 1 - .github/workflows/main.yml | 1 - .github/workflows/test.yml | 1 + action.yml | 2 +- dist/index.js | 45 +++++++++++++++++++++++++------------ index.js | 45 +++++++++++++++++++++++++------------ 6 files changed, 64 insertions(+), 31 deletions(-) diff --git a/.github/workflows/local.yml b/.github/workflows/local.yml index 2df3b58..b44a625 100644 --- a/.github/workflows/local.yml +++ b/.github/workflows/local.yml @@ -45,7 +45,6 @@ on: prompt: description: prompt required: false - default: '' permissions: contents: write diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 43b7462..6ada020 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -49,7 +49,6 @@ on: prompt: description: prompt required: false - default: '' permissions: contents: write diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d26831b..71e7716 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -46,6 +46,7 @@ jobs: exit_on_fail: true fail-tests-with-integrations: + if: github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest name: Fail tests with integrations permissions: diff --git a/action.yml b/action.yml index 6d3dd66..9106820 100644 --- a/action.yml +++ b/action.yml @@ -83,7 +83,7 @@ inputs: default: "" prompt: description: >- - The prompt passed to integrations. For example, if `integrations` is set to `claude`, the issue includes "@claude "). Also available as `$PROMPT` in the `issue_body` template. + The prompt passed to integrations. For example, if `integrations` is set to `claude`, the issue includes "@claude ". Also available as `$PROMPT` in the `issue_body` template. Only valid if "create_issue_on_fail" is set to "true". required: false diff --git a/dist/index.js b/dist/index.js index abfc310..fd7e837 100644 --- a/dist/index.js +++ b/dist/index.js @@ -32033,26 +32033,43 @@ async function createIssue(results) { const integrationsAccordion = buildIntegrationsAccordion(integrations, prompt); const body = core .getInput("issue_body") - .replace("$RUN_URL", runURL) - .replace("$RESULTS", `\n\n\`\`\`json\n${results}\n\`\`\``) - .replace("$PROMPT", prompt) + .replaceAll("$RUN_URL", runURL) + .replaceAll("$RESULTS", `\n\n\`\`\`json\n${results}\n\`\`\``) + .replaceAll("$PROMPT", prompt) + integrationsAccordion; - const labels = core.getInput("issue_labels"); - const assigneesList = core.getInput("issue_assignees").split(",").filter((s) => s.length > 0); - if (integrations.includes("copilot")) { + const labelsList = core.getInput("issue_labels").split(",").map((s) => s.trim()).filter(Boolean); + const assigneesList = core.getInput("issue_assignees").split(",").map((s) => s.trim()).filter(Boolean); + if (integrations.includes("copilot") && !assigneesList.includes("copilot-swe-agent")) { assigneesList.push("copilot-swe-agent"); } const octokit = github.getOctokit(token); - const issue = await octokit.rest.issues.create({ - owner: github.context.repo.owner, - repo: github.context.repo.repo, - title, - body, - labels: labels.split(","), - assignees: assigneesList, - }); + // Try creating the issue; if assignees cause a 422, retry without them + let issue; + try { + issue = await octokit.rest.issues.create({ + owner: github.context.repo.owner, + repo: github.context.repo.repo, + title, + body, + labels: labelsList, + assignees: assigneesList, + }); + } catch (error) { + if (error.status === 422) { + core.warning(`Issue creation failed with assignees (${assigneesList.join(", ")}). Retrying without assignees.`); + issue = await octokit.rest.issues.create({ + owner: github.context.repo.owner, + repo: github.context.repo.repo, + title, + body, + labels: labelsList, + }); + } else { + throw error; + } + } core.info(`Issue created: ${issue.data.html_url}`); core.setOutput("issueUrl", issue.data.html_url); diff --git a/index.js b/index.js index 8721bd1..addbb7a 100644 --- a/index.js +++ b/index.js @@ -197,26 +197,43 @@ async function createIssue(results) { const integrationsAccordion = buildIntegrationsAccordion(integrations, prompt); const body = core .getInput("issue_body") - .replace("$RUN_URL", runURL) - .replace("$RESULTS", `\n\n\`\`\`json\n${results}\n\`\`\``) - .replace("$PROMPT", prompt) + .replaceAll("$RUN_URL", runURL) + .replaceAll("$RESULTS", `\n\n\`\`\`json\n${results}\n\`\`\``) + .replaceAll("$PROMPT", prompt) + integrationsAccordion; - const labels = core.getInput("issue_labels"); - const assigneesList = core.getInput("issue_assignees").split(",").filter((s) => s.length > 0); - if (integrations.includes("copilot")) { + const labelsList = core.getInput("issue_labels").split(",").map((s) => s.trim()).filter(Boolean); + const assigneesList = core.getInput("issue_assignees").split(",").map((s) => s.trim()).filter(Boolean); + if (integrations.includes("copilot") && !assigneesList.includes("copilot-swe-agent")) { assigneesList.push("copilot-swe-agent"); } const octokit = github.getOctokit(token); - const issue = await octokit.rest.issues.create({ - owner: github.context.repo.owner, - repo: github.context.repo.repo, - title, - body, - labels: labels.split(","), - assignees: assigneesList, - }); + // Try creating the issue; if assignees cause a 422, retry without them + let issue; + try { + issue = await octokit.rest.issues.create({ + owner: github.context.repo.owner, + repo: github.context.repo.repo, + title, + body, + labels: labelsList, + assignees: assigneesList, + }); + } catch (error) { + if (error.status === 422) { + core.warning(`Issue creation failed with assignees (${assigneesList.join(", ")}). Retrying without assignees.`); + issue = await octokit.rest.issues.create({ + owner: github.context.repo.owner, + repo: github.context.repo.repo, + title, + body, + labels: labelsList, + }); + } else { + throw error; + } + } core.info(`Issue created: ${issue.data.html_url}`); core.setOutput("issueUrl", issue.data.html_url); From 8336da65d6cd364a258d57c536fd472c06bfabe3 Mon Sep 17 00:00:00 2001 From: hawkeyexl Date: Thu, 26 Feb 2026 11:45:50 -0800 Subject: [PATCH 4/4] Update Promptless casing --- dist/index.js | 2 +- index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/index.js b/dist/index.js index fd7e837..237a6e8 100644 --- a/dist/index.js +++ b/dist/index.js @@ -31846,7 +31846,7 @@ process.env["DOC_DETECTIVE_META"] = JSON.stringify(meta); const INTEGRATION_MAP = { "doc-sentinel": "\n/cc @reem-sab", - "promptless": "\n@Promptless $PROMPT", + "promptless": "\n@promptless $PROMPT", "dosu": "\n@dosu $PROMPT", "claude": "\n@claude $PROMPT", "opencode": "\n/opencode $PROMPT", diff --git a/index.js b/index.js index addbb7a..f41f3fa 100644 --- a/index.js +++ b/index.js @@ -10,7 +10,7 @@ process.env["DOC_DETECTIVE_META"] = JSON.stringify(meta); const INTEGRATION_MAP = { "doc-sentinel": "\n/cc @reem-sab", - "promptless": "\n@Promptless $PROMPT", + "promptless": "\n@promptless $PROMPT", "dosu": "\n@dosu $PROMPT", "claude": "\n@claude $PROMPT", "opencode": "\n/opencode $PROMPT",