)]}'
{
  "log": [
    {
      "commit": "d0886443234c1206bb46aa0d37fddcbc21b1b4b2",
      "tree": "5b1b6176cb7f0b836707613e848e1013cbf0c38e",
      "parents": [
        "83b457eacd8a121367422d3d97fb13ea24b00e56"
      ],
      "author": {
        "name": "vbhanuchander-lang",
        "email": "v.bhanuchander@gmail.com",
        "time": "Sat Aug 15 02:01:17 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Aug 15 14:01:17 2026 +0800"
      },
      "message": "fix(org): match user and account emails case-insensitively (#9051)\n\nConnectUserAccountsExact keyed its lookup map on the raw email address, so\nthe comparison was case-sensitive. A user imported from users.csv as\nTyrone.Cummings@corp.com was not linked to an account recorded as\ntyrone.cummings@corp.com, and the account was left unlinked with no\nwarning: the subtask still reported success.\n\nThat matters because the two sides come from different systems. A git\nconfig, a provider profile and a hand-maintained CSV routinely disagree on\nthe capitalisation of the same address, and email addresses are treated\ncase-insensitively everywhere else. Once an account is unlinked, everything\nattributing through it stops counting towards that user, and the scan is\nrestricted to accounts not already in user_accounts, so a later run does\nnot revisit it.\n\nFolds case on both sides of the email comparison. Name matching is left\nexactly as it was, since names are not case-insensitive in the same way and\nloosening them could create links that are simply wrong.\n\nThe org e2e fixtures gain account a11, whose email differs from user U006\u0027s\nonly in case, with a name and login that deliberately match nothing. It\nfails before this change (10 rows against 11 expected) and passes after."
    },
    {
      "commit": "83b457eacd8a121367422d3d97fb13ea24b00e56",
      "tree": "17e89ef3a817d3fe42eb1b8a24b940077feacd1e",
      "parents": [
        "092b8953ce3fdbaace1215ba01dc5ee450eff9a7"
      ],
      "author": {
        "name": "Francisc Munteanu",
        "email": "fmuntean@redhat.com",
        "time": "Fri Aug 14 09:38:21 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Aug 14 15:38:21 2026 +0800"
      },
      "message": "feat(gitlab,bitbucket): warn when adding a repo already scoped on another connection (#9047)\n\n* feat(gitlab,bitbucket): extend scope-duplicate warning to GitLab and Bitbucket\n\nAdd scope-duplicates API endpoints for GitLab and Bitbucket, mirroring the\nexisting GitHub implementation, and generalize the config-ui data-scope-remote\ncomponent and scope API client so the duplicate-connection warning works for\nall three plugins.\n\nCo-Authored-By: Cursor Agent \u003cnoreply@cursor.com\u003e\nCo-authored-by: Cursor \u003ccursoragent@cursor.com\u003e\n\n* assert specific error message\n\n---------\n\nCo-authored-by: Cursor Agent \u003cnoreply@cursor.com\u003e\nCo-authored-by: Cursor \u003ccursoragent@cursor.com\u003e"
    },
    {
      "commit": "092b8953ce3fdbaace1215ba01dc5ee450eff9a7",
      "tree": "663e996a1cd68457c3dbd9a5400b7f38b94ebbe7",
      "parents": [
        "b24379c46a9a4b6e2da589f3fd4a14705fafed8b"
      ],
      "author": {
        "name": "Dirk",
        "email": "DoDiDev@proton.me",
        "time": "Fri Aug 14 02:40:19 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Aug 14 08:40:19 2026 +0800"
      },
      "message": "fix(gitlab): add missing commit_updated_at column used by MR incremental filter (#9046)\n\n#8959 changed `GetMergeRequestsIterator` to filter merge requests by\n`GREATEST(gmr.gitlab_updated_at, COALESCE(gmr.commit_updated_at, gmr.gitlab_updated_at))`,\nbut `commit_updated_at` does not exist on `_tool_gitlab_merge_requests`:\nthere is neither a model field nor a migration script, and nothing ever\nwrites the value.\n\nAs a result every incremental \"Collect MR Notes\" run (the only caller that\npasses a stateful collector; the MR commit collector passes nil) aborts with:\n\n    Error 1054 (42S22): Unknown column \u0027gmr.commit_updated_at\u0027 in \u0027where clause\u0027\n\nThis makes the intended behaviour actually work instead of reverting it:\n\n* add `CommitUpdatedAt` to `GitlabMergeRequest` plus a migration script\n* maintain the column in the MR commit extractor, setting it to the latest\n  authored date of the MR\u0027s commits, so MRs that received new commits without\n  their own `updated_at` being bumped (e.g. force-pushes) are picked up again\n* the collector filter is unchanged and now resolves against a real column\n\nVerified on MySQL 8 and PostgreSQL 14 (extractor UPDATE and the GREATEST\nfilter produce identical results on both)."
    },
    {
      "commit": "b24379c46a9a4b6e2da589f3fd4a14705fafed8b",
      "tree": "3d13d3e4ae3d263c5eb0f36e4652a3c5ef142e9b",
      "parents": [
        "739771bd2f0c9cca5c9cad6801c96ba6c3c86975"
      ],
      "author": {
        "name": "Bramhanand Lingala",
        "email": "bramhanand.lingala11@gmail.com",
        "time": "Fri Aug 14 05:50:29 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Aug 14 08:20:29 2026 +0800"
      },
      "message": "feat(dora): exclude bot/automation accounts from PR Pickup Time calcu… (#9000)\n\n* feat(dora): exclude bot/automation accounts from PR Pickup Time calculation\n\n* test: add is_bot column to account e2e snapshot fixtures"
    },
    {
      "commit": "739771bd2f0c9cca5c9cad6801c96ba6c3c86975",
      "tree": "86aaa81b5275a8c662ee5255969df9ab89535a57",
      "parents": [
        "7e90d3628305a3ad4a711621be4fdfe17584e8b1"
      ],
      "author": {
        "name": "vbhanuchander-lang",
        "email": "v.bhanuchander@gmail.com",
        "time": "Wed Aug 12 09:53:01 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Aug 12 21:53:01 2026 +0800"
      },
      "message": "feat(github): collect GitHub issue fields and map them onto domain issues (#9038)\n\n* feat(github): collect GitHub issue fields and map them onto domain issues\n\nGitHub issue fields are organization-level structured issue metadata that\nwent generally available on 2026-07-02. They are typed, mutually exclusive\nwithin a field, and shared across every repository in the organization —\nwhich is what teams currently approximate with `type:`-style labels.\n\nCollects issue field values and lets a scope config map a field onto an\nissue column, where it takes precedence over the existing label regexes.\n\n- New table `_tool_github_issue_field_values`, one row per issue per field,\n  carrying a queryable text form of the value alongside the original JSON.\n- New subtasks Collect/Extract Issue Field Values, both disabled by default\n  so existing pipelines are unaffected until a mapping is configured.\n- New scope config keys issueFieldPriority, issueFieldSeverity,\n  issueFieldComponent, issueFieldStoryPoint and issueFieldDueDate, each\n  holding a field *name*.\n\nThe mapping is applied in the issue convertor rather than written back into\n`_tool_github_issues`: the collector iterates that table to build its request\nURLs, so a subtask that both read and wrote it was a cycle in the subtask\ngraph. Converting instead also keeps the tool layer as raw GitHub truth and\navoids adding columns there.\n\nA 404 from the field-values endpoint is treated as \"no field values\" so an\norganization that has never configured issue fields, or a token that cannot\nsee them, does not fail the whole task.\n\n* test(github): add an e2e dataflow test for issue field values\n\nCovers extraction and the scope config mapping end to end against a real\ndatabase, which the unit tests could not reach.\n\nExtraction asserts the value normalisation per data type: a single_select\nresolving to its option name and colour, an integral number rendering as\n\"5\" rather than \"5.0\", a fractional number keeping its precision, a\nmulti_select joining option names while keeping the raw JSON array, and a\nnull value producing an empty value.\n\nConversion asserts the mapping reaches the domain issue -- priority,\ncomponent, story point and due date -- and, for the case that matters,\nthat an unparseable value is skipped with a warning rather than failing\nthe task or writing a wrong value: issue #7 carries \"soon\" in a date\nfield and a null priority, and comes out with neither set while the other\nissues are untouched.\n\n* fix(github): register the issue field values table in GetTablesInfo\n\nTest_GetPluginTablesInfo compares the plugin\u0027s declared tables against the\nones its migrations create, and the new table was missing from the list:\n\n  table_info_test.go:121: The following tables are not returned by the\n  TablesInfo method\n      _tool_github_issue_field_values\n\nAdds GithubIssueFieldValue to Github.GetTablesInfo(). Verified inside the\nmericodev/lake-builder image the unit-test job uses, since the plugins\npackage needs libgit2 to build."
    },
    {
      "commit": "7e90d3628305a3ad4a711621be4fdfe17584e8b1",
      "tree": "d8c45ef5a31215fc5ea268f06c2020723335d97b",
      "parents": [
        "79b262bca4e9e90a65de825a0fba479770859ec4"
      ],
      "author": {
        "name": "vbhanuchander-lang",
        "email": "v.bhanuchander@gmail.com",
        "time": "Wed Aug 12 04:53:15 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Aug 12 16:53:15 2026 +0800"
      },
      "message": "fix(jira): stop the changelog convertor hiding unconverted items forever (#9042)\n\nThe incremental filter on the changelog convertor used created_at:\n\n  _tool_jira_issue_changelog_items.created_at \u003e\u003d ?\n\ncreated_at is stamped when the row is first inserted and never moves again.\nSo a changelog item that was collected during one window but not converted\nin that window can never be selected by any later incremental run -- the\ntimestamp it is filtered on is permanently in the past. Nothing errors; the\nrows simply stay in the tool layer.\n\nThat matches the report in #8834: a partial, silent shortfall in\nissue_changelogs from the same sync run, persisting across runs, varying by\nproject. It also explains why the reporter\u0027s check looked clean -- they\ninspected _devlake_collector_latest_state, which is the collector\u0027s state,\nnot the convertor\u0027s.\n\nSwitched to updated_at, which the extractor\u0027s upsert refreshes\n(OnConflict{UpdateAll: true}), so a re-collected item is reconsidered. Of\nthe 36 convertors in the code base, this was the only one filtering on\ncreated_at; the other 35 already use updated_at.\n\nThis does not widen the board filter, so it does not carry the cost klesh\nraised against moving board_id into the join: no board task converts\nanything outside its own board.\n\nSeparately, the board filter is now reported rather than silent. After a\nsuccessful conversion the subtask counts collected changelog items whose\nissue is not on this board and logs the number with the reason. That is one\naggregate query per board task, and it turns an unexplained shortfall into\na logged figure. Diagnostic failures are logged, never propagated.\n\nTests: an e2e dataflow test driving the convertor with a changelog for an\nissue on no board, asserting it is excluded, that nothing is attached to\nissue id 0, and that in-scope changelogs still convert; plus a regression\nguard on the filter column, since reverting it is a one-token change that\nsilently restores permanent data loss."
    },
    {
      "commit": "79b262bca4e9e90a65de825a0fba479770859ec4",
      "tree": "04278ab8c2e6a66687b80fa41892b16132071c49",
      "parents": [
        "4b75a1ca479dcd0f607f8c52135f52577be10f44"
      ],
      "author": {
        "name": "Dirk",
        "email": "DoDiDev@proton.me",
        "time": "Wed Aug 12 10:51:31 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Aug 12 16:51:31 2026 +0800"
      },
      "message": "build(deps): bump mockery v2.53.6 → v3.7.2 (#9029)\n\n* build(deps): mockery v2→v3\n\nUpgrade the mock generator from mockery v2.53.6 to v3.7.2 at every install\nsite (backend/Makefile, backend/Dockerfile, backend/Dockerfile.local,\ndevops/docker/lake-builder/Dockerfile).\n\nmockery v3 dropped the CLI flags used by the `mock` target and is configured\nvia YAML instead, so two config files are added:\n\n- backend/.mockery.core.yml\n- backend/.mockery.helpers.yml\n\nThey reproduce the exact layout produced by the previous v2 invocations\n(--recursive --keeptree --dir\u003d./\u003ctree\u003e --output\u003d./mocks/\u003ctree\u003e\n--unroll-variadic\u003dfalse --name\u003d\u0027.*\u0027): backend/mocks/\u003csrc-dir\u003e/\u003cInterface\u003e.go,\npackage `mocks`, un-prefixed mock struct names. Existing test imports such as\n`mockdal \"github.com/apache/incubator-devlake/mocks/core/dal\"` therefore keep\nworking unchanged.\n\nTwo configs (instead of one) are required because `helpers/unithelper` imports\nthe generated `mocks/core/...` packages: unlike v2, v3 type-checks sources via\ngo/packages, so the core mocks must exist before the helpers tree can be\nloaded. The `mock` target runs them in that order.\n\nNote: v3 only generates mocks for interfaces, no longer for function types.\nThe affected mocks (e.g. plugin.ApiAsyncCallback, api.DataConvertHandler,\nerrors.Option) were not used by any test. `backend/mocks/` is gitignored, so\nthere is no generated-code churn in this diff.\n\nValidation: `make mock`, `go build ./...` and `scripts/unit-test-go.sh`\n(60 packages) all pass.\n\nSigned-off-by: DoDiODev \u003cDoDiDev@proton.me\u003e\n\n* ci: bootstrap mockery 3 in lint workflow\n\nSigned-off-by: DoDiODev \u003cDoDiDev@proton.me\u003e\n\n* ci: bootstrap mockery 3 in unit-test workflow\n\nSigned-off-by: DoDiODev \u003cDoDiDev@proton.me\u003e\n\n---------\n\nSigned-off-by: DoDiODev \u003cDoDiDev@proton.me\u003e"
    },
    {
      "commit": "4b75a1ca479dcd0f607f8c52135f52577be10f44",
      "tree": "0f9cf42c04be3436f076fb63cb15cca7ad711bc8",
      "parents": [
        "62612a82f76473cb008052d7f7d049cb10b86026"
      ],
      "author": {
        "name": "bujjibabukatta",
        "email": "bujjibabukatta6@gmail.com",
        "time": "Wed Aug 12 04:50:45 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Aug 12 16:50:45 2026 +0800"
      },
      "message": "fix(gitlab): use diff_stats instead of changes_count for correct MR s… (#8928)\n\n* fix(gitlab): use diff_stats instead of changes_count for correct MR size (fixes #8888)\n\n* fix(gitlab): handle missing diff_stats for older GitLab instances\n\n* fix(gitlab): add strconv import and license header; fix else-if syntax\n\n* fix(gitlab): use nullable additions/deletions to distinguish missing diff_stats from zero\n\n---------\n\nCo-authored-by: Klesh Wong \u003cklesh@qq.com\u003e"
    },
    {
      "commit": "62612a82f76473cb008052d7f7d049cb10b86026",
      "tree": "3c49de2b0fa314e11a6e5cb64746e930669a5027",
      "parents": [
        "ea8c25c6c2b8ffb2361e19d17d849ba306a1029f"
      ],
      "author": {
        "name": "Dirk",
        "email": "DoDiDev@proton.me",
        "time": "Wed Aug 12 10:21:10 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Aug 12 16:21:10 2026 +0800"
      },
      "message": "build(deps): refresh container images and frontend dependencies (#9044)\n\n* build(deps): refresh container images\n\nBump the pinned container images used by the dev/test stacks and the\nshipped Dockerfiles to their current patch releases:\n\n- Grafana 13.0.2 -\u003e 13.1.3 (grafana/Dockerfile)\n- PostgreSQL 18.1 -\u003e 18.4 (docker-compose-dev-postgresql.yml,\n  backend/test/e2e/remote/docker-compose.test.yml)\n- MySQL 8.4.10 -\u003e 8.4.11, LTS line (docker-compose-dev-mysql.yml,\n  docker-compose.datasources.yml, .devcontainer/docker-compose.yml,\n  devops/deployment/temporal/docker-compose-temporal.yml,\n  backend/test/e2e/remote/docker-compose.test.yml)\n- nginx-unprivileged 1.31.2 -\u003e 1.31.3 (config-ui/Dockerfile)\n\nEvery occurrence of a given pin is raised together so the stacks stay\nconsistent. The Grafana bump stays within the 13.x line and requires no\ndashboard JSON changes. Release artefacts under devops/releases/ are\nhistorical and intentionally left untouched.\n\nSigned-off-by: DoDiODev \u003cDoDiDev@proton.me\u003e\n\n* build(deps): bump pinned Node.js to 24.19.0\n\nUpdate the volta Node pin in config-ui/package.json from 24.17.0 to\n24.19.0, the current 24.x LTS (\"Krypton\") release. The Docker build stays\non the floating node:24-bookworm-slim tag, so this only aligns local\ndevelopment environments with the release actually used in CI/images.\n\nSigned-off-by: DoDiODev \u003cDoDiDev@proton.me\u003e\n\n* build(deps): refresh frontend dependencies\n\nUpdate the pinned config-ui dependencies to their current releases and\nregenerate yarn.lock:\n\n- antd 6.4.5 -\u003e 6.6.0\n- @ant-design/icons 6.2.5 -\u003e 6.3.2\n- cron-parser 5.6.0 -\u003e 5.8.1\n- react-router-dom 7.18.0 -\u003e 7.18.2\n- styled-components 6.4.2 -\u003e 6.5.1\n- eslint 10.5.0 -\u003e 10.8.1\n- prettier 3.8.4 -\u003e 3.9.6\n- vite 8.1.0 -\u003e 8.2.1\n- vitest 4.1.9 -\u003e 4.1.10\n\nAll target versions stay within their current major line and keep their\ndeclared engines/peerDependencies compatible with the pinned Node 24 and\nReact 19 versions. TypeScript is deliberately left at 6.0.3, since 7.x is\na major upgrade that deserves its own change.\n\nValidated locally with yarn install, yarn build, yarn test and yarn lint.\n\nSigned-off-by: DoDiODev \u003cDoDiDev@proton.me\u003e\n\n---------\n\nSigned-off-by: DoDiODev \u003cDoDiDev@proton.me\u003e"
    },
    {
      "commit": "ea8c25c6c2b8ffb2361e19d17d849ba306a1029f",
      "tree": "28fc88563f27451fe2a929cdc14a1ab417969d88",
      "parents": [
        "b005e6ffb66ea098a4e2a4d3a6761684637750f0"
      ],
      "author": {
        "name": "Francisc Munteanu",
        "email": "fmuntean@redhat.com",
        "time": "Wed Aug 12 10:20:14 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Aug 12 16:20:14 2026 +0800"
      },
      "message": "fix(gitlab): map WorkInProgress to IsDraft in MR converter (#8982)\n\nWorkInProgress was extracted from the GitLab API and stored in\n_tool_gitlab_merge_requests but never forwarded to code.PullRequest.IsDraft\nduring conversion. Draft/WIP MRs were indistinguishable from non-draft MRs\nat the domain layer.\n- Add IsDraft: gitlabMr.WorkInProgress to the converter struct\n- Update e2e snapshot: MRs 145012495 and 158698019 now have is_draft\u003d1"
    },
    {
      "commit": "b005e6ffb66ea098a4e2a4d3a6761684637750f0",
      "tree": "644c6b32f91b4e5e7cb3e31d5073e9f9946b13d3",
      "parents": [
        "82cab8b0bc911ed4f0a68dd9b652bf3dacde7334"
      ],
      "author": {
        "name": "bujjibabukatta",
        "email": "bujjibabukatta6@gmail.com",
        "time": "Wed Aug 12 04:19:05 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Aug 12 16:19:05 2026 +0800"
      },
      "message": "fix(gitlab): always re-collect MR commits regardless of MR updated_at… (#8959)\n\n* fix(gitlab): always re-collect MR commits regardless of MR updated_at to prevent missing commits in incremental runs\n\n* fix(gitlab): correct if-brace syntax in GetMergeRequestsIterator"
    },
    {
      "commit": "82cab8b0bc911ed4f0a68dd9b652bf3dacde7334",
      "tree": "0d8d5e5ec245d13503196b573d371121ae722642",
      "parents": [
        "7df8ba0c8bd720f2479bcf19e527efb369e10a94"
      ],
      "author": {
        "name": "Klesh Wong",
        "email": "klesh@qq.com",
        "time": "Tue Aug 11 21:53:12 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Aug 11 21:53:12 2026 +0800"
      },
      "message": "refactor(api): rename GetURIStringPointer to ResolveRequestURI and reject absolute relativePath (#9043)\n\nurl.URL.ResolveReference silently discards the base URL when the reference\nis absolute or protocol-relative, which would make the request target the\nwrong host without any error. Validate relativePath up front and document\nthe resolution semantics on the renamed function.\n\nCo-authored-by: Louis.z \u003clouis.s4372121@gmail.com\u003e"
    },
    {
      "commit": "7df8ba0c8bd720f2479bcf19e527efb369e10a94",
      "tree": "2d87eed12cde5fc0ed64435de01c01c8f09f2a37",
      "parents": [
        "d323c1697d04c199150dfdc85f6280dfa6b14762"
      ],
      "author": {
        "name": "Dirk",
        "email": "DoDiDev@proton.me",
        "time": "Tue Aug 11 09:21:16 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Aug 11 15:21:16 2026 +0800"
      },
      "message": "build(deps): bump libgit2 1.3 → 1.5.0 and git2go v33 → v34 (#9028)\n\n* build(deps): libgit2 1.3→1.5 / git2go v33→v34\n\nBump git2go binding to v34.0.0 (highest importable module; the v35.0.0 tag has a broken go.mod module path) paired with libgit2 1.5.0 across install sites. git2go Go API is unchanged between v33 and v34, so no gitextractor code changes are needed.\n\nSigned-off-by: DoDiODev \u003cDoDiDev@proton.me\u003e\n\n* ci: fix lake-builder image build (golang:1.26-bullseye does not exist)\n\nThe libgit2 cross-build stage was based on `golang:1.26-bullseye`, a tag that\nwas never published (Go dropped bullseye variants), so the image build failed\nwith \"docker.io/library/golang:1.26-bullseye: not found\". The stage does not\nuse Go at all, so it is now plain `debian:bullseye` with wget/ca-certificates\nadded explicitly.\n\n* ci: disable libgit2 test build in lake-builder image\n\nThe libgit2 cross-build failed at the cmake step with \"Could not find a python\ninterpreter, which is needed to build the tests\". The upstream golang base\nimage happened to ship python; the plain debian:bullseye base does not, and we\nnever build the libgit2 tests anyway. Pass -DBUILD_TESTS\u003dOFF.\n\n* ci: bootstrap libgit2 1.5 in Go workflows\n\nSigned-off-by: DoDiODev \u003cDoDiDev@proton.me\u003e\n\n---------\n\nSigned-off-by: DoDiODev \u003cDoDiDev@proton.me\u003e"
    },
    {
      "commit": "d323c1697d04c199150dfdc85f6280dfa6b14762",
      "tree": "7081a02678652bd63b6e4957fb8cbf7a8c21d50b",
      "parents": [
        "361455ef07317141b3f72d5628d2298d1dd05a7d"
      ],
      "author": {
        "name": "Francisc Munteanu",
        "email": "fmuntean@redhat.com",
        "time": "Sat Aug 08 07:19:53 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Aug 08 13:19:53 2026 +0800"
      },
      "message": "feat(github): warn when adding a repo already scoped on another connection (#9034)\n\nAdd a scoped GET /plugins/github/scope-duplicates API and a dismissible\nConfig-UI Alert so users are informed before creating cross-connection\nduplicate domain records (#8947).\n\nCo-authored-by: Cursor \u003cnoreply@cursor.com\u003e\nCo-authored-by: Cursor \u003ccursoragent@cursor.com\u003e"
    },
    {
      "commit": "361455ef07317141b3f72d5628d2298d1dd05a7d",
      "tree": "65560345d504a1d810d8ff95b41b046763663323",
      "parents": [
        "6ea77c9265dc4b040347effe425ad21f3a74df6d"
      ],
      "author": {
        "name": "Bramhanand Lingala",
        "email": "bramhanand.lingala11@gmail.com",
        "time": "Fri Aug 07 18:01:57 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Aug 07 20:31:57 2026 +0800"
      },
      "message": "fix(sonarqube): correct aggregate coverage formula in Grafana dashboards (#8997)"
    },
    {
      "commit": "6ea77c9265dc4b040347effe425ad21f3a74df6d",
      "tree": "c2180ea6f050301d60e6592e7e5b48bfe1e3bd70",
      "parents": [
        "b7e5efaf15a0f9b554d87054b571fdfe70504002"
      ],
      "author": {
        "name": "Francisc Munteanu",
        "email": "fmuntean@redhat.com",
        "time": "Thu Aug 06 09:54:57 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Aug 06 15:54:57 2026 +0800"
      },
      "message": "fix(core): replace constraints.Unsigned with local interface in archived base (#9032)\n\nRecent golang.org/x/exp requires Go 1.23+, which breaks golangci-lint\ntypecheck on older CI. Inline the Unsigned constraint to drop the import.\n\nCo-authored-by: Cursor \u003ccursoragent@cursor.com\u003e"
    },
    {
      "commit": "b7e5efaf15a0f9b554d87054b571fdfe70504002",
      "tree": "fea3a66bbf774c46152f5b86f2ca8345205dab28",
      "parents": [
        "352f3b5efefb649fbf2d82287d3170fb34c214de"
      ],
      "author": {
        "name": "Dirk",
        "email": "DoDiDev@proton.me",
        "time": "Mon Aug 03 15:43:29 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Aug 03 21:43:29 2026 +0800"
      },
      "message": "chore(python): bump Python runtime from 3.9 to 3.11 (#9005)\n\nBump the Python runtime used across the project from 3.9 to 3.11:\n\n- backend/Dockerfile: server image base python:3.9 -\u003e python:3.11\n- backend/python/uv.sh: default uv venv python 3.9 -\u003e 3.11\n- pydevlake / azuredevops / fakeplugin pyproject.toml: python\n  constraint \"~3.9\" -\u003e \"\u003e\u003d3.11,\u003c3.12\"\n- .devcontainer/devcontainer.json: python feature 3.9 -\u003e 3.11\n- backend/python/DevelopmentSetup.md: docs reference python3.11\n\nThis is a pure runtime bump. No library versions are changed and the\nexisting Pydantic v1 code keeps working on 3.11 (verified with\nbuild-pydevlake and unit-test-python). Library upgrades (Pydantic v2,\nSQLModel, ...) and the corresponding poetry.lock regeneration are done\nin a follow-up PR.\n\nSigned-off-by: DoDiODev \u003cDoDiDev@proton.me\u003e"
    },
    {
      "commit": "352f3b5efefb649fbf2d82287d3170fb34c214de",
      "tree": "1b9263ab2e2d3bdfbc5fcf0400571d2b7aa9eefb",
      "parents": [
        "5eee167f3556145ac9199367098e3aafa31698af"
      ],
      "author": {
        "name": "Dirk",
        "email": "DoDiDev@proton.me",
        "time": "Mon Aug 03 15:22:48 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Aug 03 21:22:48 2026 +0800"
      },
      "message": "fix(jira): add missing _raw_data_* columns to _tool_jira_sprint_reports (#9015)\n\n* fix(jira): add missing _raw_data_* columns to _tool_jira_sprint_reports\n\nThe Sprint Report migration 20260722_add_sprint_report_table.go creates\n_tool_jira_sprint_reports from a struct that does not embed\ncommon.NoPKModel, while the runtime model models.JiraSprintReport does.\nThe columns _raw_data_params / _raw_data_table / _raw_data_id /\n_raw_data_remark (plus created_at, updated_at) were therefore never\ncreated, so the ApiExtractor cleanup query\n\n    WHERE _raw_data_table \u003d ? AND _raw_data_params \u003d ?\n\nmade the extractSprintReport subtask fail with\n\"Error 1054 (42S22): Unknown column \u0027_raw_data_table\u0027 in \u0027where clause\u0027\".\n\nAdd a new, additive migration that re-runs AutoMigrateTables on a struct\nembedding archived.NoPKModel. The original migration is left untouched:\nmigration scripts are append-only, and editing it would not repair\ndatabases that already recorded its version.\n\nAdd two schema-drift regression guards that run the REAL migration\nscripts instead of AutoMigrate-ing the runtime model, which would hide\nthis class of drift:\n\n  * plugins/jira/e2e/migration_schema_test.go - Jira-specific guard.\n  * plugins/schema_e2e/migration_schema_test.go - cross-plugin guard for\n    every built-in Go plugin, including TestAllGoPluginsListed so the\n    guard stays complete when a new plugin is added.\n\nBoth guards run the migrations against a dedicated, empty database\ncreated by the new helper e2ehelper.NewIsolatedMigrationDb: the shared\ne2e database is polluted by the other e2e tests, which AutoMigrate\ntables without recording anything in _devlake_migration_history, so\nrunning the real scripts against it fails with errors such as\n\"Table \u0027cicd_pipeline_commits\u0027 already exists\".\n\nThe cross-plugin guard immediately uncovered three pre-existing drifts\nof the same class, each fixed with its own additive migration:\n\n  * _tool_taiga_scope_configs      - missing type_mappings\n  * _tool_teambition_scope_configs - missing id, created_at, updated_at\n  * _tool_testmo_scope_configs     - missing connection_id, name\n\nThe teambition table has no primary key at all, and its missing `id` is\nan auto-increment primary key, which AutoMigrate cannot append to an\nexisting table (MySQL: \"Incorrect table definition; there can be only\none auto column and it must be defined as a key\"). That column is\ntherefore added with explicit DDL, which also keeps the ids of existing\nrows and the sequence/counter in sync on both MySQL and PostgreSQL.\n\nFinally, exclude plugins/schema_e2e from scripts/build-plugins.sh: it is\nnot a plugin and has no main package, which broke `make build-plugin`\nwith \"-buildmode\u003dplugin requires exactly one main package\".\n\nSigned-off-by: DoDiODev \u003cDoDiDev@proton.me\u003e\n\n* fix(gh-copilot): add missing AI credit usage breakdown columns\n\nThe cross-plugin schema-drift guard added by this PR caught a fourth\noccurrence of the same bug class, introduced by #9019:\n\n  _tool_copilot_enterprise_ai_credit_usage\n  _tool_copilot_org_ai_credit_usage\n  _tool_copilot_user_ai_credit_usage\n\nall lack gross_quantity, discount_quantity, net_quantity, price_per_unit,\ngross_amount, discount_amount and net_amount, while the runtime models\nmodels.GhCopilot{Enterprise,Org,User}AiCreditUsage declare them inline. Writing\na record therefore fails with \"Unknown column \u0027gross_quantity\u0027 in \u0027field list\u0027\".\n\nRoot cause: 20260708_add_ai_credit_usage_metrics.go declares those seven columns\nthrough an anonymous embedded struct whose TYPE NAME IS UNEXPORTED\n\n    creditUsageBreakdown20260708 `gorm:\"embedded\"`\n\nand GORM\u0027s schema parser skips anonymous fields of unexported types, so\nAutoMigrate never created the columns.\n\nAdd a new, additive migration that AutoMigrates the missing columns. It only\nadds absent columns, so it is a no-op on databases that already have them and\nsafe on populated tables. The original script is left untouched: migration\nscripts are append-only and its version is already recorded in\n_devlake_migration_history.\n\nVerified with the cross-plugin guard against a fresh database on MySQL 8.4.10\nand PostgreSQL 17.2: 44/44 plugins pass (was 43/44 with gh-copilot failing on\n21 missing columns).\n\nSigned-off-by: DoDiODev \u003cDoDiDev@proton.me\u003e\n\n* test(schema): guard the migration upgrade path on populated tables\n\nReview feedback on #9015: TestMigrationSchemaMatchesModels proves the END STATE\nof a fresh migration run matches the runtime models, but every table it inspects\nis empty, so it never exercises the upgrade path of a repair migration on a\ndatabase that already holds rows -- which is the only situation those migrations\nexist for.\n\nAdd TestMigrationUpgradePathOnPopulatedTables, which for every repair migration\nin this PR\n\n  1. recreates the table exactly as the buggy migration left it,\n  2. inserts rows,\n  3. runs ONLY that repair script,\n  4. asserts the columns were added, the rows survived, the table has a primary\n     key and auto-increment ids were backfilled (plus that a subsequent INSERT\n     still works, i.e. the sequence/counter is in sync).\n\nStep 4 covers what a column-presence check cannot see. Negative test, with the\nexplicit AUTO_INCREMENT DDL in the teambition script replaced by a plain\nAutoMigrate:\n\n  MySQL      -\u003e FAIL, migration errors out (Error 1075)\n  PostgreSQL -\u003e FAIL, \"table has no primary key after ...\" (AutoMigrate happily\n                adds `bigserial` without a key, so this is invisible to the\n                column-only guard)\n\nCovered: _tool_jira_sprint_reports, _tool_taiga_scope_configs,\n_tool_teambition_scope_configs, _tool_testmo_scope_configs and the three\n_tool_copilot_*_ai_credit_usage tables.\n\nThe scripts are looked up through each plugin\u0027s own MigrationScripts() by\nversion, so the test fails if one is removed or renumbered.\n\nVerified on MySQL 8.4.10 and PostgreSQL 17.2: 51/51 subtests pass\n(44 plugins + 7 upgrade-path cases).\n\nSigned-off-by: DoDiODev \u003cDoDiDev@proton.me\u003e\n\n---------\n\nSigned-off-by: DoDiODev \u003cDoDiDev@proton.me\u003e"
    },
    {
      "commit": "5eee167f3556145ac9199367098e3aafa31698af",
      "tree": "66f617ce0c0a0736f7be449dc1e18a302647bfce",
      "parents": [
        "e3a42717bdba6761bd43c38fadbf6938a0fcd7e7"
      ],
      "author": {
        "name": "vbhanuchander-lang",
        "email": "v.bhanuchander@gmail.com",
        "time": "Mon Aug 03 02:54:58 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Aug 03 14:54:58 2026 +0800"
      },
      "message": "fix(customize): skip rows with a missing raw record instead of ending the scan (#9027)\n\nThe extractor cursor LEFT JOINs the raw table, so a domain row whose\n_raw_data_id points at a raw record that has since been deleted comes back with\na NULL data column. That value fell through to the default branch of the type\nswitch, which returned nil and ended the whole scan.\n\nNothing surfaced: the subtask reported success, and every row ordered after the\nfirst orphan silently kept whatever custom field values it already had. The\nreporter saw 1,382 orphaned rows out of 30,287 leave roughly 28,000 rows\nunpopulated.\n\nSkip the row and carry on. extractCustomizedFields now returns how many rows it\nskipped for that reason, and the caller logs a warning naming the table and the\ncount, so the condition is visible instead of silent.\n\nAlso return rows.Err() at the end of the scan. An error raised while iterating\nthe cursor was previously discarded, which is the same class of silent partial\nprocessing.\n\nSigned-off-by: Bhanu Chander Vallabaneni \u003cv.bhanuchander@gmail.com\u003e"
    },
    {
      "commit": "e3a42717bdba6761bd43c38fadbf6938a0fcd7e7",
      "tree": "38b0b2d0fb4d3d195dd46e5461ef4f49f70720ef",
      "parents": [
        "14a4e5bcdfbf21e85b05c6386757b02d8e770fb1"
      ],
      "author": {
        "name": "vbhanuchander-lang",
        "email": "v.bhanuchander@gmail.com",
        "time": "Mon Aug 03 02:50:16 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Aug 03 14:50:16 2026 +0800"
      },
      "message": "feat(jira): add JIRA_SKIP_UNPARSEABLE_ISSUES to survive unparseable issue pages (#9026)\n\n* feat(jira): add JIRA_SKIP_UNPARSEABLE_ISSUES to survive unparseable issue pages\n\nA page that comes back with a successful status but a body that is not the\nexpected JSON - a truncated payload, or an HTML error page substituted by a\nproxy - fails the whole collectIssues subtask. On a large Jira instance that\ndiscards an otherwise complete sync because of a single bad page, typically\nduring deep pagination with changelog expansion.\n\nAdd JIRA_SKIP_UNPARSEABLE_ISSUES, read through the same\ntaskCtx.GetConfigReader().GetBool() pattern that JIRA_JQL_AUTO_FULL_REFRESH\nalready uses in this plugin. It defaults to false, preserving the current\nbehaviour of surfacing the error and failing the subtask. When enabled, the\noffending page is logged with its URL and body size and skipped, and collection\ncontinues.\n\nOnly parse failures on otherwise-successful responses are affected. Non-2xx\nstatuses never reach the response parser and keep their existing retry\nbehaviour.\n\nThe V2 (api/2/search) and V3 (api/3/search/jql) response parsers were byte\nidentical, so both now share a single parseIssuesResponse helper rather than\ncarrying two copies of the change. A skipped page yields an empty non-nil\nslice, so the collector records a page with no rows instead of treating the\nresult as absent.\n\nSigned-off-by: Bhanu Chander Vallabaneni \u003cv.bhanuchander@gmail.com\u003e\n\n* docs(jira): document JIRA_SKIP_UNPARSEABLE_ISSUES in env.example\n\nRequested in review on #9026.\n\nSigned-off-by: Bhanu Chander Vallabaneni \u003cv.bhanuchander@gmail.com\u003e\n\n---------\n\nSigned-off-by: Bhanu Chander Vallabaneni \u003cv.bhanuchander@gmail.com\u003e"
    },
    {
      "commit": "14a4e5bcdfbf21e85b05c6386757b02d8e770fb1",
      "tree": "8a0c1c20943bc45f55e7a67820af7ae69ad62391",
      "parents": [
        "705280d08ccc6a19f6b84ac2e17812a8e24a71d1"
      ],
      "author": {
        "name": "Kashif Shoukat",
        "email": "kashif.shoukat.ext@bbc.co.uk",
        "time": "Thu Jul 30 16:19:02 2026 +0000"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jul 31 00:19:02 2026 +0800"
      },
      "message": "feat(jira): add Atlassian API Gateway authentication mode (#9024)\n\n- Add JIRA_GATEWAY_REGEX and a third connection mode \u0027Jira Cloud (Atlassian\n  API Gateway)\u0027 in the connection UI\n- Auto-build endpoint from Cloud ID input: the user types only the Cloud ID\n  and the full gateway URL is constructed automatically\n- Force authMethod\u003dAccessToken for gateway connections; existing BasicAuth\n  path for standard cloud and server connections is unchanged\n- Detect gateway endpoints on edit/load and pre-fill the Cloud ID field\n- Suppress misleading /rest/ hint in testConnection() for api.atlassian.com\n  endpoints (a 404 there means a bad Cloud ID, not a missing /rest/ path)\n- Add unit tests for gateway and standard-cloud connection flows\n- Add frontend test suite for regex patterns and Cloud ID builder\n\nNo migration script required. No new DB columns.\nAll collectors, extractors, converters and domain models unchanged."
    },
    {
      "commit": "705280d08ccc6a19f6b84ac2e17812a8e24a71d1",
      "tree": "5860974939022f5c02b902af1f217fb958ef1750",
      "parents": [
        "0194a41b107a37b7f1a0db2607b1ee4542e5a58b"
      ],
      "author": {
        "name": "Pankaj Chaudhari",
        "email": "91079596+PankajChaudhari-Atos@users.noreply.github.com",
        "time": "Thu Jul 30 21:45:58 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jul 31 00:15:58 2026 +0800"
      },
      "message": "feat(gh-copilot): add AI credit usage billing metrics and fix report-body double-read (#9019)\n\n* feat(gh-copilot): add AI credit usage billing metrics\n\nAdds collection and extraction of GitHub Copilot AI credit usage\nbilling metrics at org, user, and enterprise levels, including new\nmodels, tasks (ai_credit_collector/extractor), a migration for the\nnew billing tables, and e2e snapshot fixtures.\n\nPorts the work from apache/devlake#8980.\n\nCo-authored-by: Andrei Savu \u003c54935810+AndreiS-gh@users.noreply.github.com\u003e\nSigned-off-by: Pankaj Chaudhari \u003cpankaj.chaudhari@atos.net\u003e\n\n* fix(gh-copilot): parse report metadata from already-read body\n\nThe user and enterprise Copilot metrics collectors read the report\nmetadata HTTP response body with io.ReadAll, then called\nparseReportMetadataResponse(res, ...) which read res.Body a second time.\nSince the body was already consumed, the second read returned empty,\nparseReportMetadata logged \"Report metadata response was empty,\nskipping\" and returned nil, so the collectors produced zero records.\n\nParse the metadata from the body already read instead. The organization\ncollector was unaffected because it reads the body only once inline.\n\nFixes empty _raw_copilot_user_metrics and _raw_copilot_enterprise_metrics\n(and downstream _tool_copilot_user_daily_metrics) while org metrics work.\n\nSigned-off-by: Pankaj Chaudhari \u003cpankaj.chaudhari@atos.net\u003e\n\n* fix(gh-copilot): resolve migration-linter and golangci-lint failures\n\n- migrationscripts: inline snapshot structs instead of importing models pkg\n- user_metrics: fix gofmt struct-tag alignment\n- ai_credit_collector/extractor: fix ASF license header (capital The)\n- report_download_helper: remove unused readReportMetadataBody/parseReportMetadataResponse\n- metrics_collector_test: retarget tests to parseReportMetadata/ignoreNoContent\n\n---------\n\nSigned-off-by: Pankaj Chaudhari \u003cpankaj.chaudhari@atos.net\u003e\nCo-authored-by: Andrei Savu \u003c54935810+AndreiS-gh@users.noreply.github.com\u003e"
    },
    {
      "commit": "0194a41b107a37b7f1a0db2607b1ee4542e5a58b",
      "tree": "f956ca2d4596d020d096ea70d719a387f097e0d9",
      "parents": [
        "2d4834ca3ff73ea91b4d270313c19665b02cae2b"
      ],
      "author": {
        "name": "Sebastian Machuca",
        "email": "serroba@gmail.com",
        "time": "Fri Jul 31 02:14:49 2026 +1000"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jul 31 00:14:49 2026 +0800"
      },
      "message": "feat(incidentio): add incident.io data source plugin (#9023)\n\nAdds a datasource plugin for incident.io (https://incident.io), an incident\nmanagement platform, following the structure of the rootly plugin:\n\n- Connection with Bearer-token auth against https://api.incident.io/\n- Scopes are incident types (v1/incident_types); remote-scope listing included\n- Collects incidents (v2/incidents, cursor pagination), extracts to\n  _tool_incidentio_* tables (test/tutorial-mode incidents excluded), converts\n  to ticket domain issues with type INCIDENT for DORA CFR/MTTR correlation\n- Timestamps mapped from incident_timestamp_values: \u0027Declared at\u0027 drives the\n  created date, \u0027Resolved at\u0027 (falling back to \u0027Closed at\u0027) the resolution\n  date; retrospective incidents keep their resolution date even when it\n  precedes the declaration\n- Unit tests, e2e snapshot tests, config-ui registration, Grafana dashboard\n\nValidated end-to-end against a production incident.io workspace via the\nin-memory test server (50 incidents, field-level match with reference data).\n\nCloses #9018\n\nCo-authored-by: Claude Fable 5 \u003cnoreply@anthropic.com\u003e"
    },
    {
      "commit": "2d4834ca3ff73ea91b4d270313c19665b02cae2b",
      "tree": "11cd1084a5520867ae2f99057288940e1c680eda",
      "parents": [
        "2cfe6308cdf18de6ca9667c7aa46af394b4ff0a1"
      ],
      "author": {
        "name": "Pablo BC",
        "email": "pabbalca@gmail.com",
        "time": "Thu Jul 30 18:13:59 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jul 31 00:13:59 2026 +0800"
      },
      "message": "fix(org): resolve API handlers at request time to survive late plugin init (#9022)\n\nRoute registration can evaluate ApiResources() before InitPlugins() has\nrun (InitPlugins is only invoked from pipelineServiceInit, which is\ndelayed e.g. by a pending DB migration at boot). The org plugin returned\nbound method values on p.handlers, capturing a nil receiver permanently\nand making every org endpoint panic with a nil pointer dereference.\n\nResolve p.handlers at request time via method expressions and return a\nclean error while the plugin is not initialized yet.\n\nCloses #9021"
    },
    {
      "commit": "2cfe6308cdf18de6ca9667c7aa46af394b4ff0a1",
      "tree": "6c99456682f4a7550f6a41ea6411c54b8cb3faff",
      "parents": [
        "e217c670a1b0d495e02bf76aadb576474aff24d3"
      ],
      "author": {
        "name": "TOGEP",
        "email": "38622278+TOGEP@users.noreply.github.com",
        "time": "Fri Jul 31 01:13:36 2026 +0900"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jul 31 00:13:36 2026 +0800"
      },
      "message": "fix(config-ui): improve copy text contrast in dark mode (#9020)"
    },
    {
      "commit": "e217c670a1b0d495e02bf76aadb576474aff24d3",
      "tree": "6c70b96a7a0426e00494f45c79ca942aca2e7da0",
      "parents": [
        "73fa07d97e68c3b8ba0684a233e01abec99a3819"
      ],
      "author": {
        "name": "bujjibabukatta",
        "email": "bujjibabukatta6@gmail.com",
        "time": "Thu Jul 30 12:12:59 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jul 31 00:12:59 2026 +0800"
      },
      "message": "fix(dashboards): fix string team/user ID filtering in MySQL dashboards (#8926)"
    },
    {
      "commit": "73fa07d97e68c3b8ba0684a233e01abec99a3819",
      "tree": "fca2219410e86ba2d21f4cce7afc100784235e98",
      "parents": [
        "2127e8492e82c4364afa75acc38d2271bb41b7f0"
      ],
      "author": {
        "name": "Evan Lynch",
        "email": "elynch303@users.noreply.github.com",
        "time": "Thu Jul 30 12:11:49 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jul 31 00:11:49 2026 +0800"
      },
      "message": "feat(clickup): add ClickUp data-source plugin (folder-scope boards, sprints, DORA) (#9009)\n\n* feat(clickup): scaffold ClickUp data-source plugin (task→issue MVP)\n\nAdd a new DevLake data-source plugin for ClickUp, mirroring the linear\n(structure) and trello (REST client) tracker plugins.\n\n- models: ClickUpConnection (token auth, no Bearer prefix), ClickUpList\n  scope (keyed by list id), ClickUpTask, ClickUpUser, ClickUpTaskComment\n  (stub), ClickUpScopeConfig (status + type mapping)\n- migrationscripts: register + addInitTables creating all _tool_clickup_*\n  tables via migrationhelper.AutoMigrateTables\n- tasks: task/user collectors (page-based + member REST), extractors,\n  convertors to ticket.Issue/BoardIssue/IssueAssignee, crossdomain.Account\n  and ticket.Board; shared status/type/time helpers\n- api: connection CRUD + test-connection (GET /team), scope, scope-config,\n  hierarchical remote-scopes (Team→Space→Folder→List), proxy, blueprint v200\n- impl: all required plugin interfaces incl. GetTablesInfo listing every\n  model, dependency-ordered SubTaskMetas and PrepareTaskData\n- register clickup in plugins/table_info_test.go\n\ngo build ./plugins/clickup/... and go vet pass.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) \u003cnoreply@anthropic.com\u003e\n\n* feat(clickup): add ClickUp connector with folder-scope boards, sprints, and DORA\n\nAdd a new ClickUp data-source plugin so ClickUp workspaces feed DevLake\u0027s\nissue-tracking and DORA/velocity metrics, modeled on the Jira/Linear connectors.\n\nBackend:\n- Folder is the data-source scope (ClickUpFolder ~\u003d a Jira board); lists are\n  child entities collected every sync, so rolling/archived sprint lists never\n  need re-scoping.\n- Sprint lists (name-matched) -\u003e ticket.Sprint + board_sprints; start/end\n  parsed from the list name (M/D vs D/M disambiguated by the \u003e12 rule).\n- Tasks -\u003e issues + board_issues; sprint-list tasks also -\u003e sprint_issues.\n- Story points from ClickUp native sprint points (or a configured custom field).\n- Scope config: sprint-name pattern, story-point field, force-issue-type\n  (flag a folder INCIDENT for DORA CFR/MTTR), status/type overrides.\n- remote-scopes walks Workspace -\u003e Space -\u003e Folder (folders selectable).\n- migration 20260722 adds _tool_clickup_folders + sprint/story-point columns.\n- Unit tests for sprint name/date parsing and status/type/story-point mapping.\n\nConfig UI:\n- ClickUp connection registration (personal-token auth) with a Beta pill.\n- Boards data-scope picker + scope-config transformation panel.\n- getPluginScopeId(clickup) -\u003e folderId so scope delete and per-scope project\n  selection target one scope; fix a pre-existing Connections-page chunk() split\n  that silently dropped plugins.\n\nDocs \u0026 dashboards:\n- Plugin README (connection, folder scope, sprints, story points, incidents).\n- Branded ClickUp Grafana dashboard (grafana/dashboards/ClickUp.json).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) \u003cnoreply@anthropic.com\u003e\nSigned-off-by: Evan Lynch \u003ce.lynch303@gmail.com\u003e\n\n* feat(clickup): classify issues by list name (bug/incident list patterns)\n\nClickUp frequently carries no per-task type, so teams group bugs and\nincidents in dedicated lists (e.g. \"QA Bugs\", \"Incident Backlog\")\nrather than tagging each task. Add scope-config BugListPattern and\nIncidentListPattern regexes matched against a list\u0027s name; tasks in a\nmatching list are classified BUG / INCIDENT (INCIDENT wins when both\nmatch). Precedence in the task convertor: forced DefaultIssueType \u003e\nlist-name pattern \u003e per-task type detection \u003e REQUIREMENT default.\n\n- models: BugListPattern / IncidentListPattern on ClickUpScopeConfig\n- migration 20260723 adds the two columns (additive AutoMigrate)\n- task convertor: loadListTypeOverrides + pure listTypeFor helper\n- unit test for listTypeFor (precedence, case-insensitivity, nil regexes)\n- config-ui transformation panel: two list-pattern inputs\n\nCo-Authored-By: Claude Opus 4.8 (1M context) \u003cnoreply@anthropic.com\u003e\nSigned-off-by: Evan Lynch \u003ce.lynch303@gmail.com\u003e\n\n---------\n\nSigned-off-by: Evan Lynch \u003ce.lynch303@gmail.com\u003e\nCo-authored-by: Evan Lynch \u003celynch@freedomeledger.net\u003e\nCo-authored-by: Claude Opus 4.8 (1M context) \u003cnoreply@anthropic.com\u003e"
    },
    {
      "commit": "2127e8492e82c4364afa75acc38d2271bb41b7f0",
      "tree": "c7c4cbccdf426c6488f18a79f5449304cf916876",
      "parents": [
        "7f7cf09b08eb2c3cce9efff1af448c53fbf9b63f"
      ],
      "author": {
        "name": "Dirk",
        "email": "DoDiDev@proton.me",
        "time": "Thu Jul 30 09:22:43 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Jul 30 15:22:43 2026 +0800"
      },
      "message": "chore(config-ui): modernize frontend toolchain and framework stack (#9006)\n\n* chore(config-ui): modernize frontend toolchain and framework stack\n\nUpgrade the config-ui frontend to current supported versions as one coupled\nchange so the build stays green:\n\n- Node 18 -\u003e 24, Yarn 3.4.1 -\u003e 4.17.0 (Corepack, vendored release, .yarnrc.yml),\n  nginx base image bump in config-ui/Dockerfile\n- Vite 8, TypeScript config update, ESLint flat config (eslint.config.mjs)\n  replacing .eslintrc.js/.eslintignore, Vitest setup replacing .file-headerrc\n- React 18 -\u003e 19 and antd 5 -\u003e 6 with the resulting ref/hook/type fixes and a\n  miller-columns-select compatibility patch\n- Add main-entry and miller-columns-select tests\n\nThe q-dev connection-field files are touched only for the mandatory React 19\nuseRef(undefined) fix and Prettier formatting; no q-dev logic changes.\n\nVerified on Node 24.18.0 / Yarn 4.17.0: install --immutable clean, tsc --noEmit,\neslint . (0 errors), yarn build (Vite 8), yarn test (4/4) all pass.\n\nSigned-off-by: DoDiODev \u003cDoDiDev@proton.me\u003e\n\n* ci(config-ui): bump lint workflow Node to 24\nThe modernized config-ui toolchain (ESLint 10.5.0) uses util.styleText in\nits stylish formatter, which is only available on Node \u003e\u003d 20.12. The lint\nworkflow still pinned Node 18, causing \u0027TypeError: util.styleText is not a\nfunction\u0027. Align CI with the volta-pinned Node 24.17.0.\n\n---------\n\nSigned-off-by: DoDiODev \u003cDoDiDev@proton.me\u003e"
    },
    {
      "commit": "7f7cf09b08eb2c3cce9efff1af448c53fbf9b63f",
      "tree": "049f4a5dba5c58d067e4a64be285f7abc203e653",
      "parents": [
        "21ea61c300e029cb1684a3f70863ce6ebabaa555"
      ],
      "author": {
        "name": "Frederic Hübner",
        "email": "frederic_huebner@web.de",
        "time": "Mon Jul 27 08:48:30 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Jul 27 14:48:30 2026 +0800"
      },
      "message": "fix(sonarqube): tolerate missing migration indexes (#9012)\n\nCo-authored-by: Frederic Hübner \u003cfrederichuebner@ip-192-168-1-223.eu-central-1.compute.internal\u003e"
    },
    {
      "commit": "21ea61c300e029cb1684a3f70863ce6ebabaa555",
      "tree": "2f6a2f852ba299398bf1efb953ef07344b39727c",
      "parents": [
        "20179fa520d03df65dd58d7295421a3d832fe0d5"
      ],
      "author": {
        "name": "Bramhanand Lingala",
        "email": "bramhanand.lingala11@gmail.com",
        "time": "Mon Jul 27 02:29:53 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Jul 27 14:29:53 2026 +0800"
      },
      "message": "feat(jira): persist Sprint Report snapshot for accurate committed/completed velocity (#8967) (#9010)"
    },
    {
      "commit": "20179fa520d03df65dd58d7295421a3d832fe0d5",
      "tree": "0c86dcf9d1de277bbbf6627549f0ce92f528ea9b",
      "parents": [
        "c4a15bf894a7108c61df49798ff13d43329fd7e2"
      ],
      "author": {
        "name": "Christophe Kamphaus",
        "email": "christophe.kamphaus@gmail.com",
        "time": "Mon Jul 27 08:14:21 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Jul 27 14:14:21 2026 +0800"
      },
      "message": "Fix webhook authorization when AUTH_ENABLED\u003dtrue (#9008)\n\n* test(webhook): add test for webhook api key with activated auth\n\n* fix(webhook): fix webhook authorization"
    },
    {
      "commit": "c4a15bf894a7108c61df49798ff13d43329fd7e2",
      "tree": "025a6465f97a7ef5e2ac5bdeaee69f7d35683e4d",
      "parents": [
        "b70ac93a8468d5292cccb3826c41cf27c4a59253"
      ],
      "author": {
        "name": "Bramhanand Lingala",
        "email": "bramhanand.lingala11@gmail.com",
        "time": "Sun Jul 26 08:35:08 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Jul 26 20:35:08 2026 +0800"
      },
      "message": "fix(bitbucket): paginate remote-scopes API to return all repositories (#8999) (#9004)"
    },
    {
      "commit": "b70ac93a8468d5292cccb3826c41cf27c4a59253",
      "tree": "5e94297ed5ab78ab64e9599d6ba8f7bf750de01d",
      "parents": [
        "9e8ca1b8ddf6970e26dbb63c4cf7860eb12c792a"
      ],
      "author": {
        "name": "Dirk",
        "email": "DoDiDev@proton.me",
        "time": "Sun Jul 26 13:14:47 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Jul 26 19:14:47 2026 +0800"
      },
      "message": "chore(docker): bump PostgreSQL to 18.1 and MySQL to 8.4.10 (#9003)\n\nBump database engine images across the dev, devcontainer, temporal deployment and remote e2e compose files: PostgreSQL 17.2/14.2 to 18.1 and MySQL 8/8.0.26 to 8.4.10.\n\nFor PostgreSQL 18 the remote e2e volume is mounted at /var/lib/postgresql (parent of the new versioned data dir) instead of /var/lib/postgresql/data, and the Grafana datasource provisioning sets postgresVersion to 1800.\n\nGrafana database env parametrization is intentionally out of scope.\n\nSigned-off-by: DoDiODev \u003cDoDiDev@proton.me\u003e"
    },
    {
      "commit": "9e8ca1b8ddf6970e26dbb63c4cf7860eb12c792a",
      "tree": "1359db5983a5c7e1ab8156efaec3e8acd20e47eb",
      "parents": [
        "27c771a89723c7eef0df54cfad5927c8ed4a09fe"
      ],
      "author": {
        "name": "Dirk",
        "email": "DoDiDev@proton.me",
        "time": "Mon Jul 20 09:25:47 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Jul 20 15:25:47 2026 +0800"
      },
      "message": "feat(grafana): upgrade dashboards and provisioning for Grafana 13 (#8992)\n\nUpdate the Grafana SQL converter dependency to sqlglot 30.12.0.\n\nUpgrade Grafana from 11.6.2 through 12.4.5 to 13.0.2 and harden dashboard provisioning, home-dashboard resolution, legacy-volume handling, and datasource cleanup.\n\nAlign dashboard datasource references and plugin metadata with Grafana 13, and remove duplicate AI Model ROI dashboards.\n\nKeep postgresVersion at 1400; the PostgreSQL 18 server-version bump remains scoped to its dedicated change.\n\nSigned-off-by: DoDiODev \u003cDoDiDev@proton.me\u003e"
    },
    {
      "commit": "27c771a89723c7eef0df54cfad5927c8ed4a09fe",
      "tree": "892c31fed9a284b9cb2b16eeb50ad64b4ee8f23b",
      "parents": [
        "b4b53b8a6da9e528237b6f0d1c638a8bd331c0b5"
      ],
      "author": {
        "name": "Dirk",
        "email": "DoDiDev@proton.me",
        "time": "Sun Jul 19 05:25:05 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Jul 19 11:25:05 2026 +0800"
      },
      "message": "fix(jira): change _tool_jira_issues.fix_versions column to text (#8988)\n\nThe `fix_versions` field on the jira issue model is declared as\n`gorm:\"type:text\"`, but the original migration\n(20250619_add_fix_versions_to_issue) created the column as\n`varchar(255)`. Installations that ran the earlier migration therefore\nhave a `varchar(255)` column that no longer matches the model and\ntruncates long fix version lists.\n\nThis migration alters `_tool_jira_issues.fix_versions` to `text` so the\ndatabase schema matches the model definition and can store values longer\nthan 255 characters.\n\nSigned-off-by: DoDiODev \u003cDoDiDev@proton.me\u003e\nCo-authored-by: Klesh Wong \u003cklesh@qq.com\u003e"
    },
    {
      "commit": "b4b53b8a6da9e528237b6f0d1c638a8bd331c0b5",
      "tree": "be5784682b982add2c7f488b967a97b7a93252c2",
      "parents": [
        "b34d761c378920efbcd4094c543b4ac6b3cf1c76"
      ],
      "author": {
        "name": "Dirk",
        "email": "DoDiDev@proton.me",
        "time": "Sun Jul 19 05:24:46 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Jul 19 11:24:46 2026 +0800"
      },
      "message": "fix(domain): migrate issue component to text (#8987)\n\nSigned-off-by: DoDiODev \u003cDoDiDev@proton.me\u003e\nCo-authored-by: Klesh Wong \u003cklesh@qq.com\u003e"
    },
    {
      "commit": "b34d761c378920efbcd4094c543b4ac6b3cf1c76",
      "tree": "c597c8429727e5c7177415c8975830d5658229ea",
      "parents": [
        "060af3a9cbd476068e6ff90d8170f6522843c277"
      ],
      "author": {
        "name": "Dirk",
        "email": "DoDiDev@proton.me",
        "time": "Sun Jul 19 05:02:14 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Jul 19 11:02:14 2026 +0800"
      },
      "message": "fix(sonarqube): support long issue code block components (#8986)\n\nSigned-off-by: DoDiODev \u003cDoDiDev@proton.me\u003e"
    },
    {
      "commit": "060af3a9cbd476068e6ff90d8170f6522843c277",
      "tree": "ea3b7eb6f7baf6ef572c11843aa7b496b0e0e2f3",
      "parents": [
        "eaf86ce57c38eb90f6c00ee5ccc086bf62da1dfb"
      ],
      "author": {
        "name": "Bramhanand Lingala",
        "email": "bramhanand.lingala11@gmail.com",
        "time": "Sun Jul 19 08:32:05 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Jul 19 11:02:05 2026 +0800"
      },
      "message": "fix(config-ui): explicitly set initialValues for plugins missing it (#8995)\n\nCo-authored-by: Klesh Wong \u003cklesh@qq.com\u003e"
    },
    {
      "commit": "eaf86ce57c38eb90f6c00ee5ccc086bf62da1dfb",
      "tree": "1f6bb84aba9486c5eded4499f0012c5ea0fe6136",
      "parents": [
        "1cbd8357e87cbc25c5566f58f3070640e85f294f"
      ],
      "author": {
        "name": "Dirk",
        "email": "DoDiDev@proton.me",
        "time": "Wed Jul 15 11:06:59 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Jul 15 17:06:59 2026 +0800"
      },
      "message": "build: pin swag CLI and oauth2-proxy, ignore build artifacts in docker context (#8989)\n\nMake the backend container build inputs reproducible and shrink the\nDocker build context:\n\n- Align the swag CLI pin to v1.16.6 across backend/Makefile,\n  backend/Dockerfile and backend/Dockerfile.local so local, CI and\n  image builds generate Swagger docs with the same tool version.\n- Pin the oauth2-proxy dev auth proxy image to v7.15.3-amd64 in both\n  docker-compose-dev-mysql.yml and docker-compose-dev-postgresql.yml.\n- Add backend/.dockerignore to keep regenerated build artifacts\n  (bin/, mocks/, logs/, Python venv/build output, coverage files and\n  OS junk) out of the build context. Source under python/ is retained.\n\nNo functional/runtime code changes.\n\nSigned-off-by: DoDiODev \u003cDoDiDev@proton.me\u003e"
    },
    {
      "commit": "1cbd8357e87cbc25c5566f58f3070640e85f294f",
      "tree": "a08802dfe42392c63e54f86537349b22cca55605",
      "parents": [
        "3a4333eb6ff431bb9e790cbe0c52e606d3a8f77c"
      ],
      "author": {
        "name": "Joshua Smith",
        "email": "jbsmith7741@gmail.com",
        "time": "Wed Jul 15 02:53:49 2026 -0600"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Jul 15 16:53:49 2026 +0800"
      },
      "message": "feat(sonarqube): collect historical project metrics and Grafana trends (#8983)\n\n* feat(sonarqube): add project-level metrics history collection\n\nAdd collector, extractor, and convertor subtasks that pull historical\nmetric snapshots from the SonarQube measures/search_history and\nproject_analyses/search APIs into a new cq_project_metrics_history\ndomain table, enabling trend analysis for coverage, bugs, code smells,\ncomplexity, vulnerabilities, and other quality indicators over time.\n\nSigned-off-by: Joshua Smith \u003cjbsmith7741@gmail.com\u003e\n\n* fix(sonarqube): Require user token at test\n\n  * Reject Global and Project analysis tokens on SonarQube Server before authentication validate runs.\n  * Skip prefix validation for SonarCloud endpoints where token prefixes differ.\n\n* feat(grafana): add historical trends to SonarQube dashboards\n\n  * Add collapsed \"Historical Trends\" row with coverage, bugs, vulnerabilities, code smells, and duplication time-series panels to both Server and Cloud dashboards\n  * Panels query cq_project_metrics_history and honor the Grafana time picker via $__timeFilter\n\nSigned-off-by: Joshua Smith \u003cjbsmith7741@gmail.com\u003e\n\n* style(sonarqube): Fix gofmt formatting in code quality files\n\n  * Align struct fields and ApiCollectorArgs literals to satisfy golangci-lint gofmt checks.\n  * Updates cq_project_metrics_history and SonarQube collector tasks flagged in CI.\n\n---------\n\nSigned-off-by: Joshua Smith \u003cjbsmith7741@gmail.com\u003e"
    },
    {
      "commit": "3a4333eb6ff431bb9e790cbe0c52e606d3a8f77c",
      "tree": "13da9e0211cbb45fd8fe313f4123d3f52fe63288",
      "parents": [
        "082a40242db3244ab9d24598d3b9ee4b3e799207"
      ],
      "author": {
        "name": "bujjibabukatta",
        "email": "bujjibabukatta6@gmail.com",
        "time": "Wed Jul 15 04:39:27 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Jul 15 16:39:27 2026 +0800"
      },
      "message": "fix(python): add missing composite primary keys in domain_layer/code.py (#8957)"
    },
    {
      "commit": "082a40242db3244ab9d24598d3b9ee4b3e799207",
      "tree": "a0d380b57980a20b9b3d4faa34671fba0c4241de",
      "parents": [
        "f93895f32dc8846de2cdc503824fa4ea03b02dbb"
      ],
      "author": {
        "name": "bujjibabukatta",
        "email": "bujjibabukatta6@gmail.com",
        "time": "Wed Jul 15 04:20:13 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Jul 15 16:20:13 2026 +0800"
      },
      "message": "fix(dora): use two-phase deployment lookup to prevent first-deploymen… (#8942)\n\n* fix(dora): use two-phase deployment lookup to prevent first-deployment over-mapping in lead time calculator\n\n* test(dora): add e2e coverage for Phase 1 direct deployment match (issue #8790)\n\n* test(dora): add direct-match deployment case to change lead time e2e test\n\n* fix(dora): use straight double quotes in doc comment to satisfy gofmt\n\n---------\n\nCo-authored-by: Bujjibabukatta \u003cbujjubabukatta6@gmail.com\u003e\nCo-authored-by: Klesh Wong \u003cklesh@qq.com\u003e"
    },
    {
      "commit": "f93895f32dc8846de2cdc503824fa4ea03b02dbb",
      "tree": "dc051c82daa09cafadcb6d937d0544c95fa24381",
      "parents": [
        "52278b0a2b0e4b83c8edc3e0ad50f95d57172ecf"
      ],
      "author": {
        "name": "bujjibabukatta",
        "email": "bujjibabukatta6@gmail.com",
        "time": "Tue Jul 14 00:11:57 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Jul 13 20:41:57 2026 +0200"
      },
      "message": "fix(gh-copilot): preserve hyphenated enterprise slugs (#8935)\n\nCo-authored-by: Bujjibabukatta \u003cbujjubabukatta6@gmail.com\u003e\nCo-authored-by: Eldrick Wega \u003celdrick.wega@outlook.com\u003e\nCo-authored-by: Eldrick Wega \u003cewega@github.com\u003e"
    },
    {
      "commit": "52278b0a2b0e4b83c8edc3e0ad50f95d57172ecf",
      "tree": "6625f06e51faf4370a582cc1754143eb60610983",
      "parents": [
        "50f664e495d9b1c752fbc237af9c5d8b4cfeae76"
      ],
      "author": {
        "name": "Kenneth Wong",
        "email": "KennethWKZ@users.noreply.github.com",
        "time": "Sat Jul 11 19:56:02 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Jul 11 19:56:02 2026 +0800"
      },
      "message": "fix(bitbucket): migrate off cross-workspace APIs removed by CHANGE-2770 (#8916)\n\n* fix(bitbucket): migrate off cross-workspace APIs removed by CHANGE-2770\n\n- listBitbucketWorkspaces: replace GET /user/permissions/workspaces with the supported GET /workspaces (lists the current user\u0027s workspaces). Workspace slug/name now parsed from the top level of each value instead of a nested \"workspace\" object.\n- searchBitbucketRepos: replace cross-workspace GET /repositories?role\u003dmember with per-workspace GET /repositories/{workspace}, enumerating the user\u0027s workspaces first and aggregating up to PageSize matches.\n- models: flatten GroupResponse to match the /workspaces response shape.\n\n* fix(bitbucket): list workspaces via supported /user/workspaces (CHANGE-2770)\n\nGET /2.0/workspaces is also deprecated, not just /user/permissions/workspaces.\nThe supported replacement for enumerating the current user\u0027s workspaces is\nGET /2.0/user/workspaces, which nests slug/name under a workspace object.\n\n- remote_api.go: listBitbucketWorkspaces / listAllBitbucketWorkspaces now call\n  /user/workspaces with workspace.* fields and read via GroupId()/GroupName().\n- models/repo.go: re-nest GroupResponse under WorkspaceItem to match the\n  /user/workspaces response shape.\n\nRepo listing stays on the supported GET /2.0/repositories/{workspace}.\n\n* fix(bitbucket): drop sort/fields on /user/workspaces (400 Invalid field name)\n\nGET /2.0/user/workspaces rejects sort\u003dworkspace.slug with HTTP 400 \u0027Invalid\nfield name\u0027. The bare paginated call returns the nested workspace objects we\nparse, so drop the sort/fields query params in both listBitbucketWorkspaces\nand listAllBitbucketWorkspaces.\n\n* fix(bitbucket): ignore 410 Gone on sunset issues API\n\nBitbucket has sunset the issue tracker/wiki API; the issues endpoint now\nreturns 410 Gone instead of 404. ignoreHTTPStatus404 only mapped 404 to\nErrIgnoreAndContinue, so a 410 fell through to nil, DoAsync retried 3x and\nfailed the whole pipeline (\"Collect Issues ended unexpectedly\").\n\nTreat 410 like 404 (ignore and continue). Shared helper also covers issue\ncomments and pr commits. Adds a table test for the status-code mapping."
    },
    {
      "commit": "50f664e495d9b1c752fbc237af9c5d8b4cfeae76",
      "tree": "31bced7c6e585d63f6374d5766be3bd86544da87",
      "parents": [
        "e7ac139f910aac5bc3d05126f37e8c5226a5b2ab"
      ],
      "author": {
        "name": "Mauricio Apuril",
        "email": "mauricio.apuril@fortyau.com",
        "time": "Thu Jul 09 22:37:28 2026 -0300"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jul 10 09:37:28 2026 +0800"
      },
      "message": "feat(auth): add OIDC allowlist restrictions (#8984)"
    },
    {
      "commit": "e7ac139f910aac5bc3d05126f37e8c5226a5b2ab",
      "tree": "9ad50be53a289d566c70f853d3f245af04d39c53",
      "parents": [
        "cd7bebf64db3a2e886a511cd405174978946c71d"
      ],
      "author": {
        "name": "Klesh Wong",
        "email": "klesh@qq.com",
        "time": "Fri Jul 10 09:30:25 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jul 10 09:30:25 2026 +0800"
      },
      "message": "refactor: deprecating dbt plugin (#8971)\n\nCo-authored-by: Louis.z \u003clouis.s4372121@gmail.com\u003e"
    },
    {
      "commit": "cd7bebf64db3a2e886a511cd405174978946c71d",
      "tree": "8d22321458c03a4b01591d0d6b6cd9421afcf206",
      "parents": [
        "ee919fa6623c00d0191ddc8a35353a8254c42f2b"
      ],
      "author": {
        "name": "Jon Vaughan",
        "email": "vaughanie1978@gmail.com",
        "time": "Thu Jul 09 10:00:46 2026 +0100"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Jul 09 17:00:46 2026 +0800"
      },
      "message": "feat(jira): Allow extra JQL when querying JIRA board (#8972)\n\n* feat(jira): Allow extra JQL when querying JIRA board\n\n* fix: revert code so we just do extrajql, no dynamic dev lake project  name\n\n* fix: remove reference to devlakeprojectname as a template option"
    },
    {
      "commit": "ee919fa6623c00d0191ddc8a35353a8254c42f2b",
      "tree": "bfadb23af783fff63a9ac4e7cd15498c7e77c88e",
      "parents": [
        "7a51debd6fb2184160fadbae7fcd8e7eafa9bdf6"
      ],
      "author": {
        "name": "kirillNevedrov",
        "email": "kirill.nevedrov@gmail.com",
        "time": "Thu Jul 09 09:22:37 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Jul 09 15:22:37 2026 +0800"
      },
      "message": "fix: docker file path in build-grafana-image target (#8979)"
    },
    {
      "commit": "7a51debd6fb2184160fadbae7fcd8e7eafa9bdf6",
      "tree": "86b8ff746ff3336600d9d1e1b3ce8c8cbf34f8a0",
      "parents": [
        "edb9b0d99e4aba6fe10714e265f9cbc7f19ae410"
      ],
      "author": {
        "name": "Salim Shaikh",
        "email": "43460517+salim8898@users.noreply.github.com",
        "time": "Thu Jul 09 10:22:26 2026 +0300"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Jul 09 15:22:26 2026 +0800"
      },
      "message": "feat:allow claude organization support (#8978)"
    },
    {
      "commit": "edb9b0d99e4aba6fe10714e265f9cbc7f19ae410",
      "tree": "de3a6e475908c9a04a6ccfbc64c6acce40211f12",
      "parents": [
        "15e34bb80b5b3e35c77a88122761d507b23363e6"
      ],
      "author": {
        "name": "TOGEP",
        "email": "38622278+TOGEP@users.noreply.github.com",
        "time": "Thu Jul 09 16:07:25 2026 +0900"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Jul 09 15:07:25 2026 +0800"
      },
      "message": "fix(opsgenie): align access-rights error with docs (#8976)"
    },
    {
      "commit": "15e34bb80b5b3e35c77a88122761d507b23363e6",
      "tree": "c9eff3856f91cb9921f099ad9ea007bd27d345a9",
      "parents": [
        "6f2ee9d1556b59ba3682e80e3020d65dd646b07c"
      ],
      "author": {
        "name": "Joshua Smith",
        "email": "jbsmith7741@gmail.com",
        "time": "Thu Jul 02 06:53:54 2026 -0600"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Jul 02 20:53:54 2026 +0800"
      },
      "message": "fix(jira): collect issues via search API to avoid kanban sub-filter exclusion (#8931)\n\n* feat(jira): collect issues via search API to avoid kanban sub-filter exclusion\n\n  * Switch from board Agile API to search API with saved filter JQL so\n    resolved issues (e.g. released fixVersions) are no longer silently\n    excluded by kanban sub-filters\n  * Add sub_query field to boards model and migration to track kanban\n    sub-filter changes across syncs\n  * Support JIRA Cloud v3 search/jql endpoint alongside v2 for Server\n\n* fix(jira,config-ui): correct SubQuery parsing and connection form crashes\n\n* Fix Jira board SubQuery struct to match nested API response shape\n* Prevent click event from being stored as plugin state in connection list\n* Default initialValues to empty object when plugin config omits it\n\n* feat(jira): Add BoardConfiguration unmarshal tests\n\n  * Verify SubQuery parsing for kanban boards with, without, and empty sub-filter objects\n  * Include a test using a full Jira Cloud response payload to validate all fields"
    },
    {
      "commit": "6f2ee9d1556b59ba3682e80e3020d65dd646b07c",
      "tree": "19104a999fe4d80c0cf5ca6f0b49b675a0da3a6e",
      "parents": [
        "c43696ab5c67e509b7119ac0ccb15ec2ff64adee"
      ],
      "author": {
        "name": "Eldrick Wega",
        "email": "ewega@github.com",
        "time": "Thu Jul 02 15:34:56 2026 +0300"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Jul 02 20:34:56 2026 +0800"
      },
      "message": "Add Copilot team adoption dashboards (#8966)\n\nCo-authored-by: Copilot \u003c223556219+Copilot@users.noreply.github.com\u003e"
    },
    {
      "commit": "c43696ab5c67e509b7119ac0ccb15ec2ff64adee",
      "tree": "e354a2d3ef47f1bd88fe23b853fc1156750a962c",
      "parents": [
        "0e96ba1112404cae1eddbfe26c2528e83a032be8"
      ],
      "author": {
        "name": "bujjibabukatta",
        "email": "bujjibabukatta6@gmail.com",
        "time": "Tue Jun 30 15:07:15 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Jun 30 17:37:15 2026 +0800"
      },
      "message": "fix(azuredevops): treat 204 No Content as graceful skip in timeline collector (#8958)"
    },
    {
      "commit": "0e96ba1112404cae1eddbfe26c2528e83a032be8",
      "tree": "d9d325161a4c5073080f94fac5485eb8f63c2d5a",
      "parents": [
        "88c14a2b2cf17c5a180d5391f7f4f3f7d52c5f1e"
      ],
      "author": {
        "name": "kirillNevedrov",
        "email": "kirill.nevedrov@gmail.com",
        "time": "Tue Jun 30 09:18:16 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Jun 30 15:18:16 2026 +0800"
      },
      "message": "fix: recover create a new connection action (#8952)"
    },
    {
      "commit": "88c14a2b2cf17c5a180d5391f7f4f3f7d52c5f1e",
      "tree": "13aa7ead39f636c3c288ca6472e57812a893c07e",
      "parents": [
        "bcfdfb69d7a7509bff344c9100880e749c086f4f"
      ],
      "author": {
        "name": "Richard Boisvert",
        "email": "rboisvert@devolutions.net",
        "time": "Thu Jun 25 02:24:44 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Jun 25 14:24:44 2026 +0800"
      },
      "message": "fix(authentication): feature should be disabled by default (#8951)\n\nContrary to what the PR for the feature mentioned, the setting was enabled by default, instead of disabled.\n\nFollow up to https://github.com/apache/devlake/pull/8854"
    },
    {
      "commit": "bcfdfb69d7a7509bff344c9100880e749c086f4f",
      "tree": "0bcf8f84dbea3a9f965a40261f3cbcca8b80dd26",
      "parents": [
        "f2de2dcb19cdb2af5b2613a2b939f50a33bfd209"
      ],
      "author": {
        "name": "Joshua Smith",
        "email": "jbsmith7741@gmail.com",
        "time": "Wed Jun 24 23:56:04 2026 -0600"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Jun 25 13:56:04 2026 +0800"
      },
      "message": "fix(circleci): skip HTTP 500 on workflow/job collectors and filter by time range (#8956)\n\n* Skip 500 (corrupt CircleCI Server records) alongside 404 in AfterResponse hook so a single bad pipeline does not abort the entire subtask\n  * Apply SyncPolicy.TimeAfter to workflow and job DB iterators on full sync to avoid calling the API for every historical tool-layer row\n\nSigned-off-by: Joshua Smith \u003cjbsmith7741@gmail.com\u003e"
    },
    {
      "commit": "f2de2dcb19cdb2af5b2613a2b939f50a33bfd209",
      "tree": "a78a5872df2876cc861cf3448dc2bdc03526e172",
      "parents": [
        "57b2641eb922052d859c5b7530c5b2f218fe3faf"
      ],
      "author": {
        "name": "bujjibabukatta",
        "email": "bujjibabukatta6@gmail.com",
        "time": "Mon Jun 22 08:58:04 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Jun 22 11:28:04 2026 +0800"
      },
      "message": "feat(webhook): allow closing incidents via request body instead of url (#8939)"
    },
    {
      "commit": "57b2641eb922052d859c5b7530c5b2f218fe3faf",
      "tree": "0c73f4317571a6a4e3e5019284a0232e3d3d8090",
      "parents": [
        "48e71f50a268504ce1b0267c72f974718015b609"
      ],
      "author": {
        "name": "bujjibabukatta",
        "email": "bujjibabukatta6@gmail.com",
        "time": "Fri Jun 19 15:28:38 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jun 19 17:58:38 2026 +0800"
      },
      "message": "fix(security): update nginx-unprivileged to 1.30.3 (#8940)"
    },
    {
      "commit": "48e71f50a268504ce1b0267c72f974718015b609",
      "tree": "8c651c670003ea7e3ee8a5e3f747ccc796186dcd",
      "parents": [
        "b9b0373d2462c0c2bbab631d29297b9aa242f093"
      ],
      "author": {
        "name": "Joshua Smith",
        "email": "jbsmith7741@gmail.com",
        "time": "Thu Jun 18 20:34:17 2026 -0600"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jun 19 10:34:17 2026 +0800"
      },
      "message": "fix(server): remove auth from proceed-db-migration endpoint (#8930)\n\n* fix(server): remove auth from proceed-db-migration endpoint\n\n  * Auth tables may not exist when migration is pending, causing a bootstrap deadlock\n  * Restores pre-auth-hardening behavior for the idempotent migration endpoint\n\nSigned-off-by: Joshua Smith \u003cjbsmith7741@gmail.com\u003e\n\n* fix(server): add proceed-db-migration to publicPaths\n\nSigned-off-by: Joshua Smith \u003cjbsmith7741@gmail.com\u003e\n\n---------\n\nSigned-off-by: Joshua Smith \u003cjbsmith7741@gmail.com\u003e"
    },
    {
      "commit": "b9b0373d2462c0c2bbab631d29297b9aa242f093",
      "tree": "bbd5dcc62f8291793e456173d6fcf2161a941b63",
      "parents": [
        "46c5c5106cfd103614b7da0bc39c611e4481b7c0"
      ],
      "author": {
        "name": "Jon Vaughan",
        "email": "vaughanie1978@gmail.com",
        "time": "Thu Jun 18 06:59:00 2026 +0100"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Jun 18 13:59:00 2026 +0800"
      },
      "message": "feat(webhook): Issues and PullRequests new endpoint by projectName (#8915)\n\n* feat(webhook): Issues and PullRequests new endpoint by projectName\n\n* ci: fix invalid push\n\n* ci: fix lint issues\n\n* ci: add missing lint."
    },
    {
      "commit": "46c5c5106cfd103614b7da0bc39c611e4481b7c0",
      "tree": "c3b14a0f4c7ffaaa43c8c2d95d32af7916718165",
      "parents": [
        "55911c8a7134f3d48e32d3ce0b4567d376da267f"
      ],
      "author": {
        "name": "bujjibabukatta",
        "email": "bujjibabukatta6@gmail.com",
        "time": "Tue Jun 16 20:53:42 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Jun 16 23:23:42 2026 +0800"
      },
      "message": "fix(grafana): use account email instead of user email in Work Logs Code Commits panel (#8925)\n\nCo-authored-by: Louis.z \u003clouis.s4372121@gmail.com\u003e"
    },
    {
      "commit": "55911c8a7134f3d48e32d3ce0b4567d376da267f",
      "tree": "8def7d7bbd694b6989d009fb062f62e6aca8caba",
      "parents": [
        "50010e093547cd73642c56fc03553e1cabc91b5f"
      ],
      "author": {
        "name": "Klesh Wong",
        "email": "klesh@qq.com",
        "time": "Tue Jun 16 23:03:10 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Jun 16 23:03:10 2026 +0800"
      },
      "message": "docs: Update README.md to remove incubator wording (#8936)"
    },
    {
      "commit": "50010e093547cd73642c56fc03553e1cabc91b5f",
      "tree": "2a8a765dac86bfbc07af2aed7650daad2f6cc195",
      "parents": [
        "33f7d340fa7a56071137aaf710f941ece6f4c182"
      ],
      "author": {
        "name": "bujjibabukatta",
        "email": "bujjibabukatta6@gmail.com",
        "time": "Fri Jun 12 19:27:03 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jun 12 21:57:03 2026 +0800"
      },
      "message": "fix(jira): rename \"Jira Server\" to \"Jira Data Center\" in UI labels (#8903)\n\n* fix(jira): rename Jira Server to Jira Data Center in UI labels\n\n* fix(jira): show both Jira Server and Jira Data Center labels"
    },
    {
      "commit": "33f7d340fa7a56071137aaf710f941ece6f4c182",
      "tree": "d1608cfcab39355f2d614d496ef76746faff6148",
      "parents": [
        "cec4286c035bc06ef6761efb82c4c5816f1f0725"
      ],
      "author": {
        "name": "bujjibabukatta",
        "email": "bujjibabukatta6@gmail.com",
        "time": "Fri Jun 12 19:22:21 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jun 12 21:52:21 2026 +0800"
      },
      "message": "fix(ui): guard against empty plugin prop on connection creation page (#8918)"
    },
    {
      "commit": "cec4286c035bc06ef6761efb82c4c5816f1f0725",
      "tree": "c2102a88ba1c02f57964598d2c0c2c34080ced7f",
      "parents": [
        "65cbe82b01a1b0d3735dbf06d4abb711bc180d45"
      ],
      "author": {
        "name": "Eduardo Rodrigues",
        "email": "2961314+eduardoarantes@users.noreply.github.com",
        "time": "Fri Jun 12 23:48:47 2026 +1000"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jun 12 21:48:47 2026 +0800"
      },
      "message": "feat: add Linear (linear.app) data source plugin (#8900)\n\n* feat(linear): add tool-layer models and init migration\n\nAdd the Linear plugin\u0027s tool-layer data models (connection, team scope,\nscope config, account, issue, comment, issue label, workflow state, cycle,\nissue history) and the initial schema migration with archived snapshots.\n\nThe connection authenticates with a personal API key passed verbatim in the\nAuthorization header (Linear uses no Bearer prefix).\n\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* feat(linear): add plugin skeleton, connection API and GraphQL client\n\nWire the Linear plugin entry point and implement all required plugin\ninterfaces (meta, init, task, api, model, source, migration, blueprint v200,\ncloseable). Add connection/scope/scope-config CRUD via the data-source helper,\na test-connection endpoint that runs a GraphQL viewer query, and a rate-limited\nasync GraphQL client that injects the API key via a bare Authorization header.\n\nSubTaskMetas is intentionally empty; collectors are added per entity in\nfollowing commits.\n\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* feat(linear): collect, extract and convert users to accounts\n\nAdd the users GraphQL collector (paginated), extractor to\n_tool_linear_accounts, and convertor to the domain crossdomain.Account\ntable, wired as the first three subtasks. Includes an e2e dataflow test\nwith raw fixtures and verified snapshots.\n\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* feat(linear): collect and extract workflow states\n\nAdd the team-scoped workflow states GraphQL collector and extractor into\n_tool_linear_workflow_states. These states (backlog/unstarted/started/\ncompleted/canceled) drive deterministic issue status mapping. Includes an\ne2e test covering all five state types.\n\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* feat(linear): collect, extract and convert issues\n\nAdd the team-scoped issues GraphQL collector (incremental via updatedAt\nordering, inline labels), extractor to _tool_linear_issues and\n_tool_linear_issue_labels, and convertor to domain ticket.Issue and\nticket.BoardIssue.\n\nStatus maps deterministically from Linear\u0027s WorkflowState.type\n(backlog/unstarted-\u003eTODO, started-\u003eIN_PROGRESS, completed/canceled-\u003eDONE);\npriority maps to its label; lead time falls back to resolution minus\ncreation. Includes an e2e test spanning all state types, unassigned issues,\nissues without a cycle, and multi-label issues.\n\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* feat(linear): collect, extract and convert issue comments\n\nAdd a per-issue comments GraphQL collector (driven by an input iterator over\ncollected issues, with pagination), an extractor that recovers the owning\nissue id from the raw input column, and a convertor to domain\nticket.IssueComment. Includes an e2e dataflow test.\n\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* feat(linear): convert issue labels to domain layer\n\nAdd the convertor from _tool_linear_issue_labels (populated inline by the\nissue extractor) into the domain ticket.IssueLabel table. Includes an e2e\ntest covering issues with multiple labels and with none.\n\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* feat(linear): collect cycles and convert to sprints\n\nAdd the team-scoped cycles GraphQL collector and extractor, plus convertors\nproducing domain ticket.Sprint and ticket.BoardSprint (status derived from\ncompletedAt), and ticket.SprintIssue linking issues to their cycle. Includes\nan e2e dataflow test covering closed/active cycles and issues with/without a\ncycle.\n\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* feat(linear): collect issue history and convert to changelogs\n\nAdd a per-issue history GraphQL collector (input iterator over issues, with\npagination), an extractor capturing state transitions including state types,\nand a convertor to domain ticket.IssueChangelogs with mapped from/to status\nvalues. Lead time is already derived from the issue\u0027s native\nstartedAt/completedAt. Includes an e2e test of a full\nbacklog-\u003estarted-\u003ecompleted lifecycle.\n\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* test(linear): add blueprint v200 scope generation tests\n\nCover makeScopesV200: a team scope with the ticket entity produces the\nexpected domain board scope id, and a scope without the ticket entity\nproduces none.\n\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* docs(linear): add plugin README\n\nDocument the Linear plugin: supported entities, tool/domain mapping tables,\ndeterministic status mapping, priority/type/lead-time handling, API-key auth,\nconnection/scope/pipeline setup examples, rate limiting, and the roadmap\n(OAuth, label-based type mapping, config-ui integration).\n\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* fix(linear): guard lead-time fallback against resolution before creation\n\nA resolution timestamp (completedAt/canceledAt) earlier than createdAt —\nfrom clock skew or migrated/imported issues — produced a negative duration\nthat, cast to uint, yields platform-dependent garbage (0 on arm64, ~1.8e19\non amd64). Skip the fallback unless the resolution is after creation so lead\ntime stays unset instead.\n\nAdds an isolated e2e dataflow test with a fixture whose canceledAt precedes\ncreatedAt, asserting lead_time_minutes is empty.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) \u003cnoreply@anthropic.com\u003e\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* fix(linear): map Linear triage state type to TODO\n\nThe WorkflowState.type \u0027triage\u0027 (the inbox state issues land in before being\naccepted) previously fell through to OTHER, contradicting the documented total\nmapping and silently mislabeling triage issues. Map it to TODO; keep OTHER as\nthe fallback for genuinely unrecognized types so unexpected API values surface.\n\nAdds a unit test covering every documented state type plus triage and an\nunknown value.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) \u003cnoreply@anthropic.com\u003e\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* refactor(linear): remove unused GraphqlInlineAccount struct\n\nThe struct was documented as the shared inline-user shape but was never\nreferenced; each collector declares its own inline user struct. Removing it\navoids misleading a maintainer into editing a type nothing reads.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) \u003cnoreply@anthropic.com\u003e\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* perf(linear): raise issue collector page size to 100\n\nEvery other Linear collector uses a page size of 100; issues used 50, which\ndoubled the number of issue-page round-trips and the iterator size that drives\nthe per-issue comment/history collectors. Linear permits first: 250.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) \u003cnoreply@anthropic.com\u003e\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* feat(linear): populate issue assignee/creator names and issue_assignees\n\nThe issue convertor set only assignee_id/creator_id, leaving the denormalized\nassignee_name/creator_name columns blank and writing no issue_assignees rows,\nso dashboards reading those columns or joining through issue_assignees showed\nblank names. Preload account display names (matching the account convertor\u0027s\ndisplayName-then-name rule) and emit an IssueAssignee per assigned issue.\n\nThe issue dataflow test now loads accounts before conversion and asserts the\nnames plus issue_assignees; the lead-time test flushes accounts to stay\norder-independent on the shared test DB.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) \u003cnoreply@anthropic.com\u003e\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* fix(linear): clear stale sprint_issues when issues leave their cycle\n\nSprint membership is derived from each issue\u0027s cycle_id, and the batch divider\nonly deletes outdated rows when it produces at least one row of the type. When\nevery issue is moved out of its cycle the convertor emits nothing, so the\ndivider never fires and prior sprint_issues rows linger, leaving issues shown\nin sprints they no longer belong to. Delete the team\u0027s sprint_issues up front\nso the result is correct regardless of how many issues remain in a cycle.\n\nAdds a two-run e2e test that empties every issue\u0027s cycle and asserts\nsprint_issues is empty afterward.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) \u003cnoreply@anthropic.com\u003e\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* feat(linear): derive issue lead time from state-transition history\n\nThe LinearIssue.LeadTimeMinutes field was never populated, so lead time always\nfell back to the coarse createdAt -\u003e resolutionDate span. Derive it instead from\nthe recorded history: the span from an issue\u0027s first transition into an\nin-progress state to its first transition into a done state thereafter (active\ncycle time), which is the value that genuinely requires history. ConvertIssues\nstill seeds the fallback; ConvertIssueHistory now overrides it when the\ntransitions exist, and issues lacking them keep the fallback.\n\nAdds an e2e test asserting issue-1 (started 05-02, completed 05-03) resolves to\n1440 minutes from history rather than its 2880-minute created-\u003eresolved span.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) \u003cnoreply@anthropic.com\u003e\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* feat(linear): add remote-scopes endpoints to enumerate teams\n\nOther first-party ticket plugins (jira, asana, github) expose\nconnections/:connectionId/remote-scopes so the config UI can browse and select\nscopes from the API. Linear had none, forcing users to hand-craft a PUT /scopes\nwith raw team UUIDs they had no in-product way to discover. Wire the standard\nDsRemoteApiProxyHelper + DsRemoteApiScopeListHelper and a lister that queries\nthe GraphQL teams connection (flat list, cursor-paginated) through the\nconnection\u0027s authenticated client.\n\nAdds unit tests for the response-\u003escope-entry mapping, the pagination cursor,\nand the route registration.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) \u003cnoreply@anthropic.com\u003e\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* perf(linear): make comment and history collection incremental\n\nBoth child collectors used a plain GraphqlCollector and swept every issue in\nthe team on every run, issuing one request per issue with no since filter -\ntens of thousands of requests per run on a large team against Linear\u0027s ~1500\nreq/hour budget. Switch them to a stateful collector and restrict the driving\ncursor to issues updated since the last successful collection, so steady-state\nruns scale with the change delta rather than the whole backlog. A full sync\n(since \u003d\u003d nil) still sweeps every issue.\n\nAdds a unit test for the incremental cursor-clause builder.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) \u003cnoreply@anthropic.com\u003e\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* fix(linear): filter issues server-side by updatedAt for incremental sync\n\nIncremental collection relied on the issues query returning newest-first and a\nclient-side early-stop, but the query pinned no sort direction (Linear\u0027s orderBy\nis a scalar enum with no direction operand). If the server default were\nascending, the early-stop would fire on the first (oldest) row and collect\nalmost nothing. Pass a server-side IssueFilter { updatedAt: { gt: since } }\ninstead and drop the early-stop, so correctness no longer depends on an\nundocumented default ordering. A full sync passes an empty filter (match all).\n\nAdds a unit test pinning the filter\u0027s JSON shape to Linear\u0027s IssueFilter input.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) \u003cnoreply@anthropic.com\u003e\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* refactor(linear): drop dead LeadTimeMinutes tool-layer field\n\nThe _tool_linear_issues.lead_time_minutes column was never populated (the\ncollector never requested it and no extractor set it). Now that lead time is\nderived into the domain ticket.Issue directly -- from state-transition history\nwhen available, otherwise the createdAt-\u003eresolutionDate fallback in the issue\nconvertor -- the tool-layer field is pure dead weight. Remove it from the model,\nthe init migration\u0027s archived model, the convertor, and the extractor snapshot.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) \u003cnoreply@anthropic.com\u003e\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* feat(config-ui): register Linear plugin\n\nAdds the Linear data source to config-ui so it appears in the connection\npicker: connection form (endpoint + personal API key + proxy + rate limit),\na flat Teams data-scope backed by the plugin\u0027s remote-scopes endpoint, and the\nLinear logo. No scope-config transformation — Linear\u0027s status mapping is\ndeterministic. Wired into the plugin registry.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) \u003cnoreply@anthropic.com\u003e\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* fix(config-ui): map Linear scope id to teamId\n\ngetPluginScopeId fell through to the default (scope.id) for Linear, but a\nLinearTeam scope is keyed by teamId and has no id field — so the blueprint\nreferenced an undefined scopeId and patching failed with \u0027LinearTeam not found\u0027.\nAdd a linear case returning scope.teamId.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) \u003cnoreply@anthropic.com\u003e\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* feat(linear): add Grafana dashboard\n\nAdds grafana/dashboards/Linear.json (cloned from the Asana ticket-dashboard\ntemplate) so Linear ships a per-tool dashboard like every other ticket plugin.\nIts board picker is scoped to Linear (boards id like \u0027linear%\u0027); the 13 panels\n(throughput, lead/cycle time, status distribution, delivery rate, sprints) read\nthe shared domain tables. Auto-loaded via Grafana file provisioning.\n\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* fix(linear): convert team scope to a domain board\n\nboard_issues and sprint_issues referenced a board_id (boardIdGen over\nLinearTeam), but nothing ever created the ticket.Board row itself, so the\ndomain boards table stayed empty. Board-scoped dashboards (whose board picker\nis \u0027boards where id like linear%\u0027) and any board join therefore returned no\ndata. Add a ConvertTeams subtask that converts the team scope in\n_tool_linear_teams into a ticket.Board keyed identically to those references.\n\nAdds an e2e test asserting the board is produced with the matching id.\n\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* fix(linear): widen issue title/url columns to avoid truncation\n\n_tool_linear_issues.title and .url were varchar(255), but Linear titles can\nexceed 255 chars (and the issue URL embeds a title slug), so extraction failed\nwith \u0027Error 1406: Data too long for column title\u0027. Drop the varchar limit so\nboth are longtext, matching the domain issues.title and jira\u0027s tool summary.\n\nAdds an e2e test extracting a 300-char title without truncation.\n\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* fix(linear): recover owning issue id for comments and history\n\nThe GraphQL collector stores the query variables (which carry issueId) in the\nraw row\u0027s input column, but the comment and history extractors parsed it as\n{\"Id\":...} (SimpleLinearIssue.Id), so the owning issue id came out empty and\nthe convertor joins produced zero domain comments/changelogs on real data. The\ne2e fixtures hand-wrote {\"Id\":...}, masking it. Parse issueId (with an Id\nfallback) and update the fixtures to the real collector shape.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) \u003cnoreply@anthropic.com\u003e\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* test: flush LinearIssueLabel before issue extraction in e2e tests\n\nThe issue extractor writes both _tool_linear_issues and\n_tool_linear_issue_labels, but comment_test, cycle_test and\nissue_history_test only flushed LinearIssue before running\nExtractIssuesMeta. On a clean database (as in CI) the table\n_tool_linear_issue_labels was never auto-migrated, so the extractor\u0027s\nDELETE on that table panicked and aborted the whole package. Flush\nLinearIssueLabel too, matching the other linear e2e tests and the jira\nplugin convention.\n\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* test: register linear plugin in Test_GetPluginTablesInfo\n\nThe plugin count check failed in CI (actual 41 vs tested 40) because the\nlinear plugin was not listed in table_info_test.go. Add its import and\nFeedIn call so every Go plugin is covered.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) \u003cnoreply@anthropic.com\u003e\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n* feat(linear): map issues to ticket types via label-based scope config\n\nLinear has no native issue type, so the issue convertor previously\nhardcoded every issue to REQUIREMENT. Add optional regex fields to the\nLinear scope config (issueTypeIncident/Bug/Requirement) matched against\nan issue\u0027s label names, with precedence INCIDENT \u003e BUG \u003e REQUIREMENT and\na REQUIREMENT default when nothing matches. This lets DORA classify\nLinear bugs as incidents for change-failure-rate / time-to-restore.\n\nAlso fix two gaps that made scope configs unusable for Linear:\n- thread the scope\u0027s ScopeConfigId into the pipeline task options so the\n  convertor can load the config at runtime\n- register the standard scope-config/:scopeConfigId/projects route that\n  every other plugin serves (config-ui 404\u0027d on the scope config page)\n\nTested: new e2e TestLinearIssueIncidentMapping (Bug label -\u003e INCIDENT,\nother issues REQUIREMENT) plus a blueprint plan test asserting\nScopeConfigId is passed through; full plugins/linear suite green.\n\nCo-Authored-By: Claude Opus 4.8 \u003cnoreply@anthropic.com\u003e\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\n\n---------\n\nSigned-off-by: Eduardo Rodrigues \u003c2961314+eduardoarantes@users.noreply.github.com\u003e\nCo-authored-by: Claude Opus 4.8 (1M context) \u003cnoreply@anthropic.com\u003e"
    },
    {
      "commit": "65cbe82b01a1b0d3735dbf06d4abb711bc180d45",
      "tree": "44ad2c5ba38c54ac4cd9e29f8f3129a47e5750fa",
      "parents": [
        "f4888416e400c245c55b4d5643529a64fc3d86f7"
      ],
      "author": {
        "name": "bujjibabukatta",
        "email": "bujjibabukatta6@gmail.com",
        "time": "Fri Jun 12 19:01:18 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jun 12 21:31:18 2026 +0800"
      },
      "message": "fix(webhook): set resolution_date and lead_time_minutes on incident close (#8919)"
    },
    {
      "commit": "f4888416e400c245c55b4d5643529a64fc3d86f7",
      "tree": "c5f2764702d5b1847ede134e341ffe2c740655c5",
      "parents": [
        "61d66a1339d35709868105cd85153c60b824f290"
      ],
      "author": {
        "name": "Juan Antonio Osorio",
        "email": "ozz@stacklok.com",
        "time": "Fri Jun 12 16:21:22 2026 +0300"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jun 12 21:21:22 2026 +0800"
      },
      "message": "fix(github): create domain accounts for non-committer authors (#8886) (#8894)\n\n* fix(github): create domain accounts for non-committer authors (#8886)\n\nConvertAccounts sourced the domain `accounts` table FROM _tool_github_accounts,\nwhich is only populated for users we collected full profiles for (effectively,\ncommitters). Issue and PR authors who never committed were written into\n_tool_github_repo_accounts but never converted, so issues.creator_id and\npull_requests.author_id pointed at accounts rows that didn\u0027t exist.\n\nSource ConvertAccounts FROM _tool_github_repo_accounts LEFT JOIN\n_tool_github_accounts instead, so every user the repo references gets a domain\naccount, enriched with profile detail when we have it and login-only otherwise.\nThe domain id uses the same generator the issue/PR convertors use, so the FKs\nline up. Also emit a repo_account for a PR\u0027s merged_by user so\npull_requests.merged_by_id resolves too.\n\nThe query stays MySQL/PostgreSQL-agnostic (COALESCE, no backtick quoting,\nparameterized via the dal) and mirrors the join already in\naccount_org_collector.go.\n\nAdds the non-committer orphan case to the e2e fixture plus a referential-\nintegrity assertion in TestAccountDataFlow. Verified on both MySQL and\nPostgreSQL.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) \u003cnoreply@anthropic.com\u003e\n\n* fix(github): carry _raw_data provenance and guard zero account ids\n\nReview feedback on #8894: the rewritten ConvertAccounts dropped the\n_raw_data fields from converted accounts. Select them with COALESCE,\npreferring the enriched _tool_github_accounts row and falling back to\nthe _tool_github_repo_accounts row for non-committers. The e2e fixture\nnow carries the _raw_data columns like every other tool fixture, and\nthe regenerated snapshot pins the pre-existing provenance for enriched\naccounts plus the issue-extractor provenance for the non-committer\ncase.\n\nAlso guard PR author and merged-by id generation against zero ids: an\nunmerged PR or a deleted user otherwise yields the domain id\ngithub:GithubAccount:\u003cconn\u003e:0, the same orphan-FK shape this PR fixes.\nissue_convertor already guards its AuthorId the same way.\n\nVerified with the full github e2e suite on both MySQL and PostgreSQL.\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n\n---------\n\nCo-authored-by: Claude Opus 4.8 (1M context) \u003cnoreply@anthropic.com\u003e"
    },
    {
      "commit": "61d66a1339d35709868105cd85153c60b824f290",
      "tree": "bc872eef74113d80d637ec777c508647e608bd43",
      "parents": [
        "144c2b7f87ad358ebdb5a6accf732541a4a76687"
      ],
      "author": {
        "name": "Kevin Sanjula",
        "email": "kevinxsanjula@gmail.com",
        "time": "Fri Jun 12 10:33:43 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jun 12 13:03:43 2026 +0800"
      },
      "message": "Fix/graphql client token refresh (#8791)\n\n* fix(github-graphql): prevent panic in graphql rate limit polling goroutine\n\nReplace panic in GraphqlAsyncClient rate-limit polling goroutine with graceful error handling.\n\nPreviously, any error while fetching rate limit (e.g., transient network issues or 401 responses) would trigger a panic inside a background goroutine, crashing the entire DevLake process.\n\nNow, errors are logged and the client retries in the next cycle while retaining the last known rate limit.\n\nDesign decisions:\n- Avoid panic in background goroutines: rate-limit polling is non-critical and should not bring down the entire pipeline.\n- Use last known rateRemaining on runtime failures instead of resetting or blocking, ensuring continued progress with eventual consistency.\n- Retry via existing polling mechanism instead of immediate retry to prevent tight retry loops and unnecessary API pressure.\n- Introduce a default fallback (5000) only for initial rate-limit fetch failures, since no prior state exists at startup.\n- Separate handling of initial vs runtime failures:\n  - Initial failure → fallback to default (5000)\n  - Runtime failure → retain previous value\n\nFixes #8788 (bug 1)\n\n* fix(github-graphql): reuse ApiClient transport for GraphQL to enable token refresh\n\nReplace oauth2.StaticTokenSource-based HTTP client with the underlying http.Client from ApiAsyncClient.\n\nPreviously, the GraphQL client constructed its own HTTP client using StaticTokenSource, which froze the access token at task start time. This caused GitHub App installation tokens (which expire after ~1 hour) to become invalid during long-running pipelines, leading to persistent 401 errors.\n\nNow, the GraphQL client reuses apiClient.GetClient(), which is already configured with RefreshRoundTripper and TokenProvider. This enables automatic token refresh on 401 responses, aligning GraphQL behavior with the REST client.\n\nDesign decisions:\n- Reuse transport layer instead of duplicating authentication logic to ensure consistency across REST and GraphQL clients.\n- Avoid StaticTokenSource, as it prevents token refresh and breaks long-running pipelines.\n- Leverage existing RefreshRoundTripper for transparent token rotation without modifying GraphQL query logic.\n- Keep protocol-specific logic (GraphQL vs REST) separate while sharing the underlying HTTP transport.\n\nThis ensures GraphQL pipelines using GitHub App authentication can run beyond token expiry without failure.\n\nFixes #8788 (bug 2)\n\n* refactor(github): extract shared authenticated http client from api client\n\n- moved token provider and refresh round tripper setup into a reusable helper\n- introduced CreateAuthenticatedHttpClient to centralize auth + transport logic\n- updated CreateApiClient to use shared http client instead of inline setup\n\nRationale:\n- decouples authentication (transport layer) from REST-specific client logic\n- enables reuse for GraphQL client without duplicating token refresh logic\n- aligns architecture with separation of concerns (http transport vs api clients)\n\n* feat(github-graphql): introduce graphql client with shared auth and integrate into task flow\n\n- added CreateGraphqlClient to encapsulate graphql client construction\n- reused CreateAuthenticatedHttpClient from github/tasks to inject token refresh via RoundTripper\n- replaced manual graphql client setup in PrepareTaskData with new factory function\n- preserved existing rate limit handling via getRateRemaining callback\n- preserved query cost calculation using SetGetRateCost\n\nTechnical details:\n- graphql client now uses http transport with TokenProvider and RefreshRoundTripper\n- removes dependency on oauth2 client and avoids token expiration issues\n- decouples graphql client from REST ApiClient by avoiding reuse of apiClient.GetClient()\n- maintains compatibility with github.com and enterprise graphql endpoints\n\nNote:\n- shared auth logic remains in github/tasks and is imported with alias to avoid package name collision\n- introduces cross-plugin dependency (github_graphql → github/tasks) as a pragmatic tradeoff to avoid duplication\n\n* feat(github): support static token transport for GraphQL and REST clients\n\nadd StaticRoundTripper for PAT authentication and use it in the shared http client.\n\nsince the same client is used by both REST and GraphQL, auth handling must distinguish\nbetween refreshable tokens and static tokens. avoid applying refresh/retry logic to PAT.\n\nensures correct behavior across clients and prevents unnecessary retries for static auth.\n\n* feat(github-graphql): introduce hierarchical fallback for GraphQL rate limit\n\nImplement a layered fallback mechanism for GraphQL rate limiting:\n\n1. Dynamic rate limit from provider (getRateRemaining)\n2. Per-client override (WithFallbackRateLimit)\n3. Config override (GRAPHQL_RATE_LIMIT)\n4. Default fallback (1000)\n\nAlso moved GitHub-specific fallback (5000) via WithFallbackRateLimit\nto the Graphql client.\n\n* feat(github-graphql): Add graphql rate limit to .env example\n\n* fix(github): Fix leaked debug statement\n\n* fix(github-graphql): reuse http.Client proxy, auth configurations\n\nReused `http.Client` inside the apiClient returned by `CreateApiClient` method, so keeping the proxy and auth configurations the same.That also keep the centralized management of logic.\n\n* fix(helpers): fix the priority order of fallback rate limit\n\nPriority order fixed for fallback rate limit, priority order is:\n1.Env variable\n2.Value set with `WithFallbackRateLimit`\n3.default value in the code\nThis all works only when the `getRateRemaining` fails: hence the fallback\n\n* fix(github): StaticRoundTripper now owns token splitting and rotation for AccessToken connections\n\nPreviously, connection.Token (comma-separated PATs) was injected as-is\ninto the Authorization header, sending \"Bearer tok1,tok2,tok3\" instead\nof a single rotated token.\n\nStaticRoundTripper now splits the raw token string on comma and rotates\nthrough tokens round-robin using an atomic counter.\n\nFor REST: StaticRoundTripper operates at transport level and always\noverwrites the Authorization header set by SetupAuthentication.\nSetupAuthentication is retained because conn.tokens is still required\nby GetTokensCount() for rate limit calculation — but its header write\nis superseded by StaticRoundTripper on every request.\n\nFor GraphQL: SetupAuthentication is never called by the graphql client,\nso StaticRoundTripper is the only auth mechanism on this path — without\nthis fix, GraphQL requests were sent with the full unsplit token string.\n\n* refactor(github-graphql): Downgrade fetch failure logs from Warn to Info\n\n* fix(helper): use inline func type for GraphqlClientOption to avoid mock cycle\n\nReplace exported GraphqlClientOption type with inline func(*GraphqlAsyncClient)\nin CreateAsyncGraphqlClient signature. The named type caused mockery to generate\na mock file (GraphqlClientOption.go) that created an import cycle in tests.\n\n* style(github): fix linting\n\n* fix(github): token rotation start from index\n\n* fix(helper): prevent graphql deadlock when rate limit fetch keeps failing\n\n---------\n\nCo-authored-by: Klesh Wong \u003cklesh@qq.com\u003e"
    },
    {
      "commit": "144c2b7f87ad358ebdb5a6accf732541a4a76687",
      "tree": "3cfb36a9e8f78904fddb374406241d86ac761592",
      "parents": [
        "a9650e4ca045155604dea563152123042be68da2"
      ],
      "author": {
        "name": "Volodymyr Zahorniak",
        "email": "7808206+zahorniak@users.noreply.github.com",
        "time": "Tue Jun 09 05:33:13 2026 +0300"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Jun 09 10:33:13 2026 +0800"
      },
      "message": "fix(plugin-circleci): populate workflow id for unfinished-job collection (#8907) (#8912)\n\n* refactor(plugin-circleci): extract unfinished-jobs input clauses into a helper\n\nSigned-off-by: Volodymyr Zahorniak \u003cv.zahorniak@gmail.com\u003e\n\n* fix(plugin-circleci): populate workflow id for unfinished-job collection (#8907)\n\nThe collectJobs \u0027unfinished details\u0027 collector built its URL from\n\u0027/v2/workflow/{{ .Input.Id }}/job\u0027 but its iterator selected \u0027DISTINCT\nworkflow_id\u0027 into a models.CircleciJob, leaving .Id empty and producing\n\u0027/v2/workflow//job\u0027 (HTTP 500) whenever a job was running/queued/on_hold.\nAlias the projection to \u0027workflow_id AS id\u0027 so .Id carries the workflow id,\nmirroring the new-records collector. Adds an e2e regression test.\n\nSigned-off-by: Volodymyr Zahorniak \u003cv.zahorniak@gmail.com\u003e\n\n---------\n\nSigned-off-by: Volodymyr Zahorniak \u003cv.zahorniak@gmail.com\u003e\nCo-authored-by: Klesh Wong \u003cklesh@qq.com\u003e"
    },
    {
      "commit": "a9650e4ca045155604dea563152123042be68da2",
      "tree": "d4aa310c483f901b00cacb6d35b1bf2d698eb6f1",
      "parents": [
        "9e659abdbe122735f07303100f794487fda32900"
      ],
      "author": {
        "name": "Jarek",
        "email": "74616204+jaroslawgajewski@users.noreply.github.com",
        "time": "Tue Jun 09 03:30:19 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Jun 09 09:30:19 2026 +0800"
      },
      "message": "feat(gh-copilot): close Copilot metrics parity gaps (#8889)\n\n* feat(gh-copilot): close API gaps for per-user metrics, teams, CLI, code review, and PR fields\n\nAdd missing GitHub Copilot Metrics API fields to achieve full API parity:\n\nEnterprise/Org metrics:\n- CLI active user counts and CLI breakdown (sessions, requests, tokens)\n- Code review user counts (daily/weekly/monthly × active/passive)\n- Chat panel mode breakdown (agent/ask/custom/edit/plan/unknown)\n- Expanded PR metrics (merged, merge time, suggestions, Copilot impact)\n\nPer-user metrics:\n- used_cli, used_copilot_code_review_active/passive boolean flags\n- CLI breakdown per user (sessions, requests, tokens)\n\nUser-team mapping (new):\n- New collector/extractor for user-teams-1-day endpoint\n- Enables team-level metrics via JOIN with per-user tables\n\nSeat assignments:\n- Team assignment fields (assigning_team id/name/slug)\n- User detail fields (name, email)\n\nIncludes migration script 20260527 and comprehensive docs in\nCOPILOT_METRICS_GAPS.md.\n\nCo-authored-by: Copilot \u003c223556219+Copilot@users.noreply.github.com\u003e\n\n* feat(copilot-metrics): remove outdated metrics gaps documentation and implement new user-team mapping and metrics enhancements\n\nSigned-off-by: Jarek \u003cjaroslaw.gajewski@atos.net\u003e\n\n---------\n\nSigned-off-by: Jarek \u003cjaroslaw.gajewski@atos.net\u003e\nCo-authored-by: Copilot \u003c223556219+Copilot@users.noreply.github.com\u003e"
    },
    {
      "commit": "9e659abdbe122735f07303100f794487fda32900",
      "tree": "466e23e691eaa45e3d856b233f525b69aced8c2d",
      "parents": [
        "8a3af1f97f47c40b244e9fb374256cd4224ec090"
      ],
      "author": {
        "name": "Andrea Carmisciano",
        "email": "andrea.carmisciano@gmail.com",
        "time": "Sat Jun 06 12:26:46 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Jun 06 18:26:46 2026 +0800"
      },
      "message": "feat(tempo): add Jira Tempo Timesheets plugin (#8884)\n\n* feat(tempo): add Jira Tempo Timesheets plugin\n\nAdds plugin for ingesting worklogs from Jira Tempo (Tempo Timesheets) API v4.\n\nBackend:\n- Plugin entry point (impl/impl.go)\n- Tempo API client with Bearer token auth (tasks/api_client.go)\n- Data collectors, extractors, and converters for worklogs and teams\n- Connection management API endpoints (api/)\n- Database migrations for _tool_tempo_worklogs, _tool_tempo_teams\n- E2E tests with CSV fixtures\n\nConfig-UI:\n- Connection configuration UI (config.tsx)\n- Plugin registration in index.ts\n\nDependency: requires Jira plugin for issue ID mapping.\n\nCloses #8883\n\n* fix(tempo): resolve CI failures on tempo plugin PR\n\n- fix Apache license headers in 5 files (missing/typo content)\n- move migration script to archived models pattern (forbidden import on plugins/tempo/models)\n- register tempo in plugins/table_info_test.go\n- drop unused validator var/import in api/init.go\n- fix malformed issue_worklogs.csv snapshot (header/body column mismatch)\n\n* fix(tempo): align e2e team fixtures with extractor expectations\n\nThe team extractor queries _raw_tempo_api_teams with the params serialized\nfrom TempoApiParams{ConnectionId, TeamId}, expecting the raw fixture rows\nto store each team as its own record with a single-object data payload.\nThe previous fixture wrapped both teams into a JSON array in the data\ncolumn and used only ConnectionId in params, causing TestTeamDataFlow to\nfind zero matching rows.\n\nAlso extend the _tool_tempo_teams snapshot with the _raw_data_* columns\nthat the test verifies via ColumnWithRawData.\n\n---------\n\nCo-authored-by: Fix Bot \u003cfix@example.com\u003e"
    },
    {
      "commit": "8a3af1f97f47c40b244e9fb374256cd4224ec090",
      "tree": "f4fff1a8d33140b3bb51466fcb69e5c4bd1612a9",
      "parents": [
        "ecb905d7af6df3ac349705f63a3ea9b2f8b7b69e"
      ],
      "author": {
        "name": "Tamás Laczkó-Albert",
        "email": "76678478+la-tamas@users.noreply.github.com",
        "time": "Fri Jun 05 17:51:08 2026 +0300"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jun 05 17:51:08 2026 +0300"
      },
      "message": "fix: Copilot plugin - add backfill time window to ensure data consistency (#8811)\n\n* fix: Add backfill time window to ensure data consistency\n\nchore: Add backfill time window to ensure data consistency\n(cherry picked from commit 52a9555b782860f9c8ba9db409bd56e0c8f58272)\n\n* fix(q_dev): prevent data duplication in user_report and user_data tables (#8737)\n\n* fix(q_dev): prevent data duplication in user_report and user_data tables\n\nReplace auto-increment ID with composite primary keys so that\nCreateOrUpdate can properly deduplicate rows on re-extraction.\n\n- user_report PK: (connection_id, scope_id, user_id, date, client_type)\n- user_data PK: (connection_id, scope_id, user_id, date)\n- Switch db.Create() to db.CreateOrUpdate() in s3_data_extractor\n- Migration drops old tables, rebuilds with new PKs, resets s3_file_meta\n  processed flag to trigger re-extraction\n\n* fix(q_dev): gofmt archived user_data_v2 model\n\n* feat(github): Extend exclusion of file extensions to github plugin (#8719)\n\n* feat(github): extend PR size exclusion for specified file extension to github plugin\n\n* fix: register migration script\n\n* fix: move PR size to \u0027Additional settings\u0027 and change so the comma doesn\u0027t get removed while typing\n\n* fix: linting\n\n* fix(doc): update expired Slack invite links in README (#8739)\n\nThe Slack invite links in README.md were expired and returning\n\"This link is no longer active.\" Updated both occurrences (badge\nand community section) to match the current link on the official\nDevLake website.\n\nCloses #8738\n\nCo-authored-by: Spiff Azeta \u003cspiffazeta@gmail.com\u003e\n\n* docs: add gh-devlake CLI to Getting Started installation options (#8733)\n\nAdds gh-devlake as a third installation method alongside Docker Compose\nand Helm. gh-devlake is a GitHub CLI extension that automates DevLake\ndeployment, configuration, and monitoring from the terminal.\n\nCloses #8732\n\n* fix(gitlab): add missing repos scope in project_mapping (#8743)\n\nGitLab\u0027s makeScopeV200 did not create a repos scope when\nscopeConfig.Entities was empty or only contained CROSS. This\ncaused project_mapping to have no table\u003d\u0027repos\u0027 row, breaking\ndownstream DORA metrics, PR-issue linking, and all PR dashboard\npanels that join on project_mapping.\n\nThe fix aligns GitLab with the GitHub plugin by:\n1. Defaulting empty entities to plugin.DOMAIN_TYPES\n2. Adding DOMAIN_TYPE_CROSS to the repo scope condition\n\nCloses #8742\n\nCo-authored-by: Spiff Azeta \u003cspiffazeta@gmail.com\u003e\n\n* fix(grafana): update dashboard descriptions to list all supported data sources (#8741)\n\nSeveral dashboard introduction panels hardcoded \"GitHub and Jira\" as\nrequired data sources, even though the underlying queries use generic\ndomain layer tables that work with any supported Git tool or issue\ntracker. Updated to list all supported sources following the pattern\nalready used by DORA and WorkLogs dashboards.\n\nCloses #8740\n\nCo-authored-by: Spiff Azeta \u003cspiffazeta@gmail.com\u003e\n\n* fix: modify cicd_deployments name from varchar to text (#8724)\n\n* fix: modify cicd_deployments name from varchar to text\n\n* fix: update the year\n\n* fix(q_dev): replace MariaDB-specific IF NOT EXISTS syntax with DAL methods for MySQL 8.x compatibility (#8745)\n\n* fix(azuredevops): default empty entities and add CROSS to repo scope in makeScopeV200 (#8751)\n\nWhen scopeConfig.Entities is empty (common when no entities are\nexplicitly selected in the UI), makeScopeV200 produced zero scopes,\nleaving project_mapping with no rows. Additionally, the repo scope\ncondition did not check for DOMAIN_TYPE_CROSS, so selecting only\nCROSS would not create a repo scope, breaking DORA metrics.\n\nThis adds the same fixes applied to GitLab in #8743.\n\nCloses #8749\n\n* fix(bitbucket): default empty entities to all domain types in makeScopesV200 (#8750)\n\nWhen scopeConfig.Entities is empty (common when no entities are\nexplicitly selected in the UI), makeScopesV200 produced zero scopes,\nleaving project_mapping with no repo rows. This adds the same\nempty-entities default applied to GitLab in #8743.\n\nCloses #8748\n\n* feat(circleci): add server version requirement and endpoint help text (#8757)\n\nUpdate CircleCI connection form to indicate Server v4.x+ requirement\nand provide guidance for server endpoint configuration.\n\nSigned-off-by: Joshua Smith \u003cjbsmith7741@gmail.com\u003e\n\n* feat(asana): add Asana plugin for project and task collection (#8758)\n\nAdd a new Asana plugin that integrates with Asana\u0027s REST API to collect\nprojects, sections, tasks, subtasks, stories (comments), tags, and users,\nmapping them to DevLake\u0027s ticket/board domain model.\n\nBackend:\n- Plugin implementation with all required interfaces (PluginMeta,\n  PluginTask, PluginModel, PluginMigration, PluginSource, PluginApi,\n  DataSourcePluginBlueprintV200)\n- Collectors, extractors, and converters for projects, sections, tasks,\n  subtasks, stories, tags, and users\n- Remote API scope picker (Workspaces -\u003e Teams/Portfolios -\u003e Projects)\n- Scope config with issue-type regex transformation rules\n- Migration scripts for schema evolution\n- E2E tests with CSV fixtures for project and task data flows\n\nConfig UI:\n- Plugin registration with connection form (PAT auth, endpoint, proxy)\n- Scope config transformation form for issue-type mapping\n- Dashboard URL integration for onboarding flow\n\nGrafana:\n- Asana dashboard with task metrics and visualizations\n\nMade-with: Cursor\n\n* feat: GitHub App token refresh (#8746)\n\n* feat(github): auto-refresh GitHub App installation tokens\n\nAdd transport-level token refresh for GitHub App (AppKey) connections.\nGitHub App installation tokens expire after ~1 hour; this adds proactive\nrefresh (before expiry) and reactive refresh (on 401) using the existing\nTokenProvider/RefreshRoundTripper infrastructure.\n\nNew files:\n- app_installation_refresh.go: refresh logic + DB persistence\n- refresh_api_client.go: minimal ApiClient for token refresh POST\n- cmd/test_refresh/main.go: manual test script for real GitHub Apps\n\nModified:\n- connection.go: export GetInstallationAccessToken, parse ExpiresAt\n- token_provider.go: add refreshFn for pluggable refresh strategies\n- round_tripper.go: document dual Authorization header interaction\n- api_client.go: wire AppKey connections into refresh infrastructure\n- Tests updated for new constructors and AppKey refresh flow\n\n* feat(github): add diagnostic logging to GitHub App token refresh\n\nAdd structured logging at key decision points for token refresh:\n- Token provider creation (connection ID, installation ID, expiry)\n- Round tripper installation (connection ID, auth method)\n- Proactive refresh trigger (near-expiry detection)\n- Refresh start/success/failure (old/new token prefixes, expiry times)\n- DB persistence success/failure\n- Reactive 401 refresh and skip-due-to-concurrent-refresh\n\nAll logs route through the DevLake logger to pipeline log files.\n\n* fix(github): prevent deadlock and fix token persistence in App token refresh\n\nDeadlock fix: NewAppInstallationTokenProvider now captures client.Transport\n(the base transport) before wrapping with RefreshRoundTripper. The refresh\nfunction uses newRefreshApiClientWithTransport(baseTransport) to POST for\nnew installation tokens, bypassing the RefreshRoundTripper entirely.\n\nToken persistence fix: PersistEncryptedTokenColumns() manually encrypts\ntokens via plugin.Encrypt() then writes ciphertext via dal.UpdateColumns\nwith conn.TableName() (a string) as the first argument. Passing the table\nname string makes GORM use Table() instead of Model(), preventing the\nencdec serializer from corrupting the in-memory token value. The encryption\nsecret is threaded from taskCtx.GetConfig(ENCRYPTION_SECRET) through\nCreateApiClient to TokenProvider to persist functions.\n\nAlso persists the initial App token at startup for DB consistency, and\nadds TestProactiveRefreshNoDeadlock with a real RSA key to verify the\ndeadlock scenario is resolved.\n\n* fix(grafana): update dashboard descriptions to list all supported data sources (#8741)\n\nSeveral dashboard introduction panels hardcoded \"GitHub and Jira\" as\nrequired data sources, even though the underlying queries use generic\ndomain layer tables that work with any supported Git tool or issue\ntracker. Updated to list all supported sources following the pattern\nalready used by DORA and WorkLogs dashboards.\n\nCloses #8740\n\nCo-authored-by: Spiff Azeta \u003cspiffazeta@gmail.com\u003e\n\n* fix: modify cicd_deployments name from varchar to text (#8724)\n\n* fix: modify cicd_deployments name from varchar to text\n\n* fix: update the year\n\n* fix(q_dev): replace MariaDB-specific IF NOT EXISTS syntax with DAL methods for MySQL 8.x compatibility (#8745)\n\n* fix(azuredevops): default empty entities and add CROSS to repo scope in makeScopeV200 (#8751)\n\nWhen scopeConfig.Entities is empty (common when no entities are\nexplicitly selected in the UI), makeScopeV200 produced zero scopes,\nleaving project_mapping with no rows. Additionally, the repo scope\ncondition did not check for DOMAIN_TYPE_CROSS, so selecting only\nCROSS would not create a repo scope, breaking DORA metrics.\n\nThis adds the same fixes applied to GitLab in #8743.\n\nCloses #8749\n\n* fix(bitbucket): default empty entities to all domain types in makeScopesV200 (#8750)\n\nWhen scopeConfig.Entities is empty (common when no entities are\nexplicitly selected in the UI), makeScopesV200 produced zero scopes,\nleaving project_mapping with no repo rows. This adds the same\nempty-entities default applied to GitLab in #8743.\n\nCloses #8748\n\n* fix(github): remove unused refresh client constructor and update tests\n\n---------\n\nCo-authored-by: Spiff Azeta \u003c35563797+spiffaz@users.noreply.github.com\u003e\nCo-authored-by: Spiff Azeta \u003cspiffazeta@gmail.com\u003e\nCo-authored-by: Dan Crews \u003ccrewsd@gmail.com\u003e\nCo-authored-by: Tomoya Kawaguchi \u003c68677002+yamoyamoto@users.noreply.github.com\u003e\n\n* fix: cwe89 sql injection (#8762)\n\n* feat(q-dev): add logging data ingestion and enrich Kiro dashboards (#8767)\n\n* feat(q-dev): add logging data ingestion and enrich Kiro dashboards\n\nAdd support for ingesting S3 logging data (GenerateAssistantResponse and\nGenerateCompletions events) into new database tables, and enrich all three\nKiro Grafana dashboards with additional metrics.\n\nChanges:\n- New models: QDevChatLog and QDevCompletionLog for logging event data\n- New extractor: s3_logging_extractor.go parses JSON.gz logging files\n- Updated S3 collector to also handle .json.gz files\n- Added logging S3 prefixes (GenerateAssistantResponse, GenerateCompletions)\n- New dashboard: \"Kiro AI Activity Insights\" with 10 panels including\n  model usage distribution, active hours, conversation depth, feature\n  adoption (Steering/Spec), file type usage, and prompt/response trends\n- Enriched \"Kiro Code Metrics Dashboard\" with DocGeneration, TestGeneration,\n  and Dev (Agentic) metric panels\n- Fixed \"Kiro Usage Dashboard\" per-user table to sort by user_id\n- Migration script for new tables\n\n* fix(q-dev): use separate base path for logging S3 prefixes\n\nLogging data lives under a different S3 prefix (\"logging/\") than user\nreport data (\"user-report/\"). Add LoggingBasePath option (defaults to\n\"logging\") so logging prefixes are constructed correctly.\n\n* fix(q-dev): auto-scan logging path without extra config\n\nKiro exports to two well-known S3 prefixes in the same bucket:\n- user-report/AWSLogs/{accountId}/KiroLogs/ (CSV reports)\n- logging/AWSLogs/{accountId}/KiroLogs/ (interaction logs)\n\nWhen AccountId is set, automatically scan both paths. The \"logging\"\nprefix is hardcoded since it\u0027s a standard Kiro export convention.\nNo additional configuration needed.\n\n* fix(q-dev): update scope tooltip to mention logging data scanning\n\n* fix(q-dev): fix scope ID routing and CSV/JSON file separation\n\nThree fixes:\n1. Use *scopeId (catch-all) route pattern instead of :scopeId so scope\n   IDs containing \"/\" (e.g. \"034362076319/2026\") work in URL paths\n2. CSV extractor now filters for .csv files only, preventing it from\n   trying to parse .json.gz logging files as CSV\n3. Frontend scope API calls now encodeURIComponent(scopeId) for safe\n   URL encoding\n\n* fix(q-dev): resolve *scopeId route conflict with dispatcher pattern\n\nThe catch-all *scopeId route conflicts with *scopeId/latest-sync-state.\nFollow Jenkins/Bitbucket pattern: use a single *scopeId route with a\nGetScopeDispatcher that checks for /latest-sync-state suffix and\ndispatches accordingly. All scope handlers now TrimLeft \"/\" from scopeId.\n\n* fix(q-dev): use URL-safe scope ID format (underscore separator)\n\nScope IDs like \"034362076319/2026\" break URL routing because \"/\" is a\npath separator. Change ID format to \"034362076319_2026\" (underscore)\nwhen AccountId is set. The Prefix field still uses \"/\" for S3 path\nmatching. Revert to standard :scopeId routes since IDs are now safe.\n\nNote: existing scopes need to be recreated after this change.\n\n* fix(q-dev): use NoPKModel instead of Model in archived logging models\n\narchived.Model only has ID+timestamps, missing RawDataOrigin fields\n(_raw_data_params etc.) that common.NoPKModel includes. This caused\n\"Unknown column \u0027_raw_data_params\u0027\" errors at runtime.\n\n* fix(q-dev): fix GROUP BY in per-user table to merge display_name variants\n\nRemove display_name from GROUP BY so same user_id with different\ndisplay_name values gets merged. Use MAX(display_name) in SELECT.\n\n* fix(q-dev): normalize logging user IDs to match CSV short UUID format\n\nLogging data uses \"d-{directoryId}.{UUID}\" format while CSV user-report\nuses plain \"{UUID}\". Strip the \"d-xxx.\" prefix so the same user maps to\none user_id across both data sources.\n\n* fix(q-dev): normalize user IDs in CSV extractors and sort table DESC\n\nApply normalizeUserId to both createUserReportData and\ncreateUserDataWithDisplayName so user_report CSV data also strips\nthe \"d-{directoryId}.\" prefix. Change per-user table sort to\nORDER BY user_id DESC.\n\n* style(q-dev): fix gofmt formatting in chat_log models\n\n* perf(q-dev): parallelize logging S3 downloads and batch DB writes\n\nOptimize logging extractor performance:\n- 10 goroutine workers for parallel S3 file downloads\n- Batch 50 files per DB transaction instead of 1-per-file\n- sync.Map cache for display name resolution (avoid repeated IAM calls)\n- Parse records in memory during download, write all at once\n\nThis should improve throughput from ~1.5 files/sec to ~15+ files/sec\nfor typical logging file sizes.\n\n* fix(q-dev): check tx.Rollback error return to satisfy errcheck lint\n\n* feat(q-dev): add per-user model usage table and models column\n\nAdd \"Per-User Model Usage\" table (panel 11) showing each user\u0027s\nrequest count and avg prompt/response length per model_id. Also add\n\"Models Used\" column to the Per-User Activity table.\n\n* fix(q-dev): remove per-user model usage table, keep models column only\n\n* feat(q-dev): add Kiro Executive Dashboard with cross-source analytics\n\nNew dashboard \"Kiro Executive Dashboard\" with 12 panels covering:\n- KPIs: WAU, credits efficiency, acceptance rate, steering adoption\n- Trends: weekly active users, new vs returning users\n- Adoption funnel: Chat→Inline→CodeFix→Review→DocGen→TestGen→Agentic→Steering→Spec\n- Cost: credits pace vs projected monthly, idle power users\n- Quality: acceptance rate trends, code review findings, test generation\n- Efficiency: per-user productivity table with credits/line ratio\n\nCorrelates data across user_report (credits), user_data (code metrics),\nand chat_log (interaction patterns) for holistic Kiro usage insights.\n\n* fix(q-dev): fix pie charts to show per-row slices instead of single total\n\nSet reduceOptions.values\u003dtrue so Grafana treats each SQL result row as\na separate pie slice. Fixes Model Usage Distribution, File Type Usage,\nKiro Feature Adoption, and Active File Types pie charts.\n\n* fix(q-dev): cast Hour to string for Active Hours bar chart x-axis\n\n* fix(q-dev): fix pie chart single-slice and GROUP BY display_name issues\n\n1. qdev_user_report Panel 4 (Subscription Tier Distribution): set\n   reduceOptions.values\u003dtrue to show per-tier slices\n2. qdev_user_data Panel 6 (User Interactions): remove display_name\n   from GROUP BY, use MAX(display_name) to merge same user\n\n* fix(q-dev): prevent data inflation in user_report JOIN user_data\n\nuser_report has multiple rows per (user_id, date) due to client_type\n(KIRO_IDE, KIRO_CLI), but user_data has only one row per (user_id, date).\nA direct JOIN causes user_data metrics to be counted multiple times.\n\nFix: pre-aggregate user_report by (user_id, date) in a subquery before\njoining, so the JOIN is always 1:1.\n\nAffects: Credits Efficiency stat and User Productivity table.\n\n* feat(qa): add is_invalid field to qa_test_case_executions (#8764)\n\n* feat(qa): add is_invalid field to qa_test_case_executions\n\nAdd is_invalid boolean field to the domain layer qa_test_case_executions\ntable to allow QA teams to flag test executions as invalid due to\nenvironmental issues, flaky tests, false positives, or false negatives.\n\nChanges:\n- Add IsInvalid field to QaTestCaseExecution domain model\n- Create migration script (20260313_add_is_invalid_to_qa_test_case_executions)\n- Register migration in migrationscripts/register.go\n- Update customize service to set default value for is_invalid\n- Update E2E test data to include new column\n\nResolves #8763\n\nCo-Authored-By: Claude Opus 4.6 \u003cnoreply@anthropic.com\u003e\n\n* fix(qa): handle missing is_invalid column in CSV import\n\nFix PostgreSQL compatibility issue when CSV files don\u0027t contain\nthe is_invalid column. The field now defaults to false instead\nof an empty string.\n\nChanges:\n- Update qaTestCaseExecutionHandler to check for empty string values\n- Add E2E test for backward compatibility with CSV files lacking is_invalid\n- Add explicit IsInvalid initialization in Testmo plugin converter\n\nResolves #8763\n\n---------\n\nCo-authored-by: Claude Opus 4.6 \u003cnoreply@anthropic.com\u003e\n\n* feat(linker): link when branch names contain issue keys (#8777)\n\n* feat(linker): branch names containing issue keys\n\n* chore: add testing data\n\n* Add codespell support with configuration and fixes (#8761)\n\n* ci(codespell): add codespell config and GitHub Actions workflow\n\nAdd .codespellrc with skip patterns for generated files, camelCase/PascalCase\nignore-regex, and project-specific word list (convertor, crypted, te, thur).\nAdd GitHub Actions workflow to run codespell on push to main and PRs.\n\nCo-Authored-By: Claude Code 2.1.63 / Claude Opus 4.6 \u003cnoreply@anthropic.com\u003e\nSigned-off-by: Yaroslav Halchenko \u003cdebian@onerussian.com\u003e\n\n* fix(codespell): fix ambiguous typos requiring context review\n\nManual fixes for typos that needed human review to avoid breaking code:\n- Comment/string typos: occured-\u003eoccurred, destory-\u003edestroy, writting-\u003ewriting,\n  retreive-\u003eretrieve, identifer-\u003eidentifier, etc.\n- Struct field comments and documentation corrections\n- Migration script comment fixes (preserving Go identifiers like DataConvertor)\n\nCo-Authored-By: Claude Code 2.1.63 / Claude Opus 4.6 \u003cnoreply@anthropic.com\u003e\nSigned-off-by: Yaroslav Halchenko \u003cdebian@onerussian.com\u003e\n\n* fix(codespell): fix non-ambiguous typos with codespell -w\n\nAutomated fix via `codespell -w` for clear-cut typos across backend, config-ui,\nand grafana dashboards. Examples: sucess-\u003esuccess, occurence-\u003eoccurrence,\nexeucte-\u003eexecute, asynchornous-\u003easynchronous, Grafana panel typos, etc.\n\nCo-Authored-By: Claude Code 2.1.63 / Claude Opus 4.6 \u003cnoreply@anthropic.com\u003e\nSigned-off-by: Yaroslav Halchenko \u003cdebian@onerussian.com\u003e\n\n---------\n\nSigned-off-by: Yaroslav Halchenko \u003cdebian@onerussian.com\u003e\nCo-authored-by: Claude Code 2.1.63 / Claude Opus 4.6 \u003cnoreply@anthropic.com\u003e\n\n* feat(q-dev): enrich logging fields, separate dashboards, add E2E tests (#8786)\n\n* feat(q-dev): enrich logging fields, separate dashboards by data source, add E2E tests\n\n- Add new fields to chat_log: CodeReferenceCount, WebLinkCount, HasFollowupPrompts\n  (from codeReferenceEvents, supplementaryWebLinksEvent, followupPrompts in JSON)\n- Add new fields to completion_log: LeftContextLength, RightContextLength\n  (from leftContext/rightContext in JSON)\n- Update s3_logging_extractor to parse and populate new fields\n- Add migration script 20260319_add_logging_fields\n- Create qdev_feature_metrics dashboard for legacy by_user_analytic data\n- Reorganize qdev_executive dashboard with Row dividers labeling data sources\n  and cross-dashboard navigation links\n- Enrich qdev_logging dashboard with new panels:\n  Chat Trigger Type Distribution, Response Enrichment Breakdown,\n  Completion Context Size Trends, Response Enrichment Trends\n- Fix SQL compatibility with only_full_group_by mode in executive dashboard\n  (Weekly Active Users Trend, New vs Returning Users)\n- Fix Steering Adoption stat panel returning string instead of numeric value\n- Add Playwright E2E test covering full pipeline flow and dashboard verification\n\n* fix: add Apache license headers to e2e files, fix gofmt alignment\n\n* fix: add SQL identifier validation to prevent SQL injection via table/column names (#8769)\n\nAdd ValidateTableName and ValidateColumnName functions in core/dal to ensure\ntable and column names used in dynamic SQL are safe identifiers. Applied to\nscope_service_helper, scope_generic_helper, and customized_fields_extractor.\n\n* feat(q-dev): add Kiro Credits + DORA Correlation dashboard (#8792)\n\nAdd a new Grafana dashboard that correlates Kiro AI usage (credits,\nmessages, active users) with DORA metrics at weekly aggregate level.\n\nPanels include:\n- Pearson\u0027s r correlation between weekly credits and PR cycle time\n- High AI Usage vs Low AI Usage cycle time comparison\n- Weekly credits vs deployment frequency trend\n- Weekly credits vs change failure rate trend\n\nData is joined by week_start between _tool_q_dev_user_report and\nproject_pr_metrics / cicd_deployment_commits.\n\n* feat(q-dev): add AI Cost-Efficiency dashboard (#8793)\n\nAdd a Grafana dashboard showing AI tool cost-efficiency metrics:\n- Credits per merged PR (overall + weekly trend)\n- Credits per production deployment (overall + weekly trend)\n- Credits per issue resolved (overall + weekly trend)\n- Weekly AI activity volume (credits, messages, conversations)\n\nJoins _tool_q_dev_user_report with pull_requests,\ncicd_deployment_commits, and issues by weekly aggregation.\n\n* feat(q-dev): add Multi-AI Tool Comparison dashboard (Copilot vs Kiro) (#8794)\n\nAdd a Grafana dashboard comparing GitHub Copilot and Kiro side by side:\n- Weekly active users comparison\n- Code suggestions \u0026 acceptance events (per tool)\n- LOC accepted comparison (combined time series)\n- Acceptance rate comparison (bar gauge)\n\nTemplate variables for Copilot connection/scope selection.\nData from _tool_copilot_enterprise_daily_metrics vs\n_tool_q_dev_user_report and _tool_q_dev_user_data.\n\n* feat(q-dev): add Kiro AI Model ROI dashboard (#8795)\n\nAdd a Grafana dashboard analyzing per-model performance from chat logs:\n- Model Performance Summary table (requests, share%, avg prompt/response\n  length, response/prompt ratio, steering/spec mode usage)\n- Daily Model Usage Distribution (stacked bar chart)\n- Avg Response Length by Model trend (output quality proxy)\n\nData source: _tool_q_dev_chat_log grouped by model_id.\n\n* feat(q-dev): add Steering \u0026 Spec Mode Adoption dashboard (#8798)\n\nTrack Kiro steering rules and spec mode adoption:\n- User/request adoption rate stats\n- Weekly adoption rate trend\n- Steering impact on prompt/response length\n- Per-user feature adoption table\n\n* feat(q-dev): add Developer AI Productivity Hours dashboard (#8797)\n\nAnalyze when developers are most productive with AI tools:\n- AI Activity by Hour of Day (chat + completions stacked bar)\n- Prompt \u0026 Response Length by Hour (complexity patterns)\n- Feature Usage by Hour (steering/spec mode/plain chat)\n- AI Activity by Day of Week\n\n* feat(q-dev): add Language AI Heatmap dashboard (#8796)\n\nAnalyze AI-assisted coding patterns by programming language:\n- Language Completion Profile table (requests, avg completions,\n  context sizes, users per language)\n- Daily Completions by Language (stacked bar)\n- Active File Types During Chat (donut)\n- Avg Context Size by Language trend (top 5)\n\n* Fix/circleci column names (#8799)\n\n* fix(circleci): rename created_at to created_date in jobs/workflows\nAdd migration to copy created_at -\u003e created_date and update models/converters.\n\n* fix(circleci): update pipeline parsing\n\n* test(circleci): add incremental tests for collectors\n\n* fix(jenkins): scope multi-branch build collection to current project (#8430) (#8781)\n\nThe branch jobs query in collectMultiBranchJobApiBuilds selected all\nWorkflowJob entries across all multi-branch pipelines for a connection,\ncausing builds to be duplicated and misattributed. Filter by\n_raw_data_params to collect only the current project\u0027s branch jobs.\n\nCo-authored-by: Claude Opus 4.6 (1M context) \u003cnoreply@anthropic.com\u003e\n\n* fix: Make gh-copilot plugin database agnostic (#8779)\n\nCo-authored-by: Eldrick Wega \u003celdrick.wega@outlook.com\u003e\n\n* fix(sonarqube): increase cq_issues and cq_file_metrics project_key length to 500 (#8783)\n\nFixes #8331\n\n* feat: added taiga plugin (#8755)\n\n* feat: added taiga plugin\n\n* fix: fixed tests\n\n* feat(gh-copilot): add support for organization daily user metrics (#8747)\n\n* feat(circleci): add server version requirement and endpoint help text (#8757)\n\nUpdate CircleCI connection form to indicate Server v4.x+ requirement\nand provide guidance for server endpoint configuration.\n\nSigned-off-by: Joshua Smith \u003cjbsmith7741@gmail.com\u003e\n\n* fix: fixed test files\n\n---------\n\nSigned-off-by: Joshua Smith \u003cjbsmith7741@gmail.com\u003e\nCo-authored-by: Reece Ward \u003c47779818+ReeceXW@users.noreply.github.com\u003e\nCo-authored-by: Joshua Smith \u003cjbsmith7741@gmail.com\u003e\n\n* fix(docker): pin Poetry to 2.2.1 for Python 3.9 compatibility (#8735)\n\nPoetry 2.3.0 dropped Python 3.9 support. Without cache the installer\nfetches the latest version (currently 2.3.2), which fails on the\npython:3.9-slim-bookworm base image. Pin to 2.2.1, the last release\ncompatible with Python 3.9.\n\nCo-authored-by: Rodrigo Silva \u003crodrigo.silva@bonial.com\u003e\n\n* fix(linker): scope clearHistoryData to current project only (#8814) (#8815)\n\nThe clearHistoryData() function used a LEFT JOIN with project_name\nin the ON clause, causing the subquery to return all PR IDs regardless\nof project. This effectively wiped the entire pull_request_issues table\non every linker run, deleting links from other projects sharing the\nsame repos and links created by the GitHub converter.\n\nFix:\n- Use INNER JOIN + WHERE for proper project scoping\n- Add issue-side subquery scoped to current project\u0027s boards\n- Filter by _raw_data_table/_raw_data_remark to only delete\n  linker-created rows\n\nAdd e2e test for cross-project shared repo scenario.\n\n* fix(circleci): prevent negative values when calculating circleci (#8800)\n\nworkflow duration\n\n* fix: sonarqube: missing api/users/search endpoint (#8813)\n\n* fix(argocd): extract revision from multi-source application revisions[] (#8810)\n\n---------\n\nSigned-off-by: Joshua Smith \u003cjbsmith7741@gmail.com\u003e\nSigned-off-by: Yaroslav Halchenko \u003cdebian@onerussian.com\u003e\nCo-authored-by: tamas.albert \u003ctamas.laczkoalbert@concentrix.com\u003e\nCo-authored-by: Warren Chen \u003cwarren.chen830@gmail.com\u003e\nCo-authored-by: Ema Abitante \u003cema.abitante@gmail.com\u003e\nCo-authored-by: Spiff Azeta \u003c35563797+spiffaz@users.noreply.github.com\u003e\nCo-authored-by: Spiff Azeta \u003cspiffazeta@gmail.com\u003e\nCo-authored-by: Eldrick Wega \u003celdrick.wega@outlook.com\u003e\nCo-authored-by: Dan Crews \u003ccrewsd@gmail.com\u003e\nCo-authored-by: Tomoya Kawaguchi \u003c68677002+yamoyamoto@users.noreply.github.com\u003e\nCo-authored-by: Joshua Smith \u003cjbsmith7741@gmail.com\u003e\nCo-authored-by: jawad khan \u003cjawadkhan444@gmail.com\u003e\nCo-authored-by: Leif Roger Frøysaa \u003cleif.roger.froysaa@akerbp.com\u003e\nCo-authored-by: Klesh Wong \u003cklesh@qq.com\u003e\nCo-authored-by: NaRro \u003ccong.wang@merico.dev\u003e\nCo-authored-by: Claude Opus 4.6 \u003cnoreply@anthropic.com\u003e\nCo-authored-by: Reece Ward \u003c47779818+ReeceXW@users.noreply.github.com\u003e\nCo-authored-by: Yaroslav Halchenko \u003cdebian@onerussian.com\u003e\nCo-authored-by: Chris Pavlicek \u003cvarsis@users.noreply.github.com\u003e\nCo-authored-by: AvivGuiser \u003cavivguiser@gmail.com\u003e\nCo-authored-by: Shayne Clausson \u003cshayne.clausson@extendaretail.com\u003e\nCo-authored-by: irfanuddinahmad \u003c34648393+irfanuddinahmad@users.noreply.github.com\u003e\nCo-authored-by: Rodrigo Silva \u003crodrigoluizscs@gmail.com\u003e\nCo-authored-by: Rodrigo Silva \u003crodrigo.silva@bonial.com\u003e\nCo-authored-by: Daniele M. \u003cgithub@dmoraschi.com\u003e\nCo-authored-by: Pavel Sturc \u003cpsturc@redhat.com\u003e\nCo-authored-by: Anvesh Vemula \u003c39478419+vemulaanvesh@users.noreply.github.com\u003e"
    },
    {
      "commit": "ecb905d7af6df3ac349705f63a3ea9b2f8b7b69e",
      "tree": "c5b98c5a0f7c4e2796ad732142b71b70ceaa477a",
      "parents": [
        "31ea5303835512af67b39221b1cec350b143cf4d"
      ],
      "author": {
        "name": "Klesh Wong",
        "email": "klesh@qq.com",
        "time": "Fri Jun 05 12:15:47 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jun 05 12:15:47 2026 +0800"
      },
      "message": "fix(jenkins): truncate primary_view to fit varchar(255) column (#8899)\n\n* fix(jenkins): truncate primary_view to fit varchar(255) column\n\nFixes Error 1406 (22001): Data too long for column \u0027primary_view\u0027\nby truncating the concatenated value to 255 characters before saving.\n\nCloses https://github.com/apache/devlake/issues/8897\n\n* ci: exclude generated mocks from golangci-lint"
    },
    {
      "commit": "31ea5303835512af67b39221b1cec350b143cf4d",
      "tree": "5e5d15147d7a4908f2b8f3f27d31d96e960fe0e1",
      "parents": [
        "5e9dfb8b8d3915494b468e098efde76696a51fa3"
      ],
      "author": {
        "name": "Fábio Luciano",
        "email": "fabio@naoimporta.com",
        "time": "Fri Jun 05 01:01:26 2026 -0300"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jun 05 12:01:26 2026 +0800"
      },
      "message": "Grafana postgresql support (#8870)\n\n* feat: add support for postgresql to grafana dashboards\n\n* feat: add support for postgresql to grafana dashboards\n\n* fix(grafana): improve mysql to postgresql conversion script\n\n- Disable ambiguous alias pattern that caused _computed suffix duplication\n- Add MySQL FIELD() function to PostgreSQL CASE WHEN conversion\n- Fix adoption_pct and deploy_count column name issues\n\n* chore(grafana): remove archived dashboards\n\nRemove outdated dashboard files from _archive directory\n\n* refactor(grafana): rename mysql dashboard files to kebab-case\n\nStandardize dashboard filenames from CamelCase to kebab-case for consistency\n\n* feat(grafana): regenerate postgresql dashboards with improved conversion\n\n- Fix _computed suffix duplication in column aliases\n- Convert MySQL FIELD() to PostgreSQL CASE WHEN\n- Apply all SQL dialect fixes from updated conversion script\n- Rename dashboard files to kebab-case for consistency\n\n* feat(docker): split mysql and postgresql compose configurations\n\n- Rename docker-compose-dev.yml to docker-compose-dev-postgresql.yml\n- Create separate docker-compose-dev-mysql.yml\n- Use different project names to avoid conflicts\n- Assign unique ports for each stack (MySQL: 3001/8083/4001/4181, PostgreSQL: 3002/8084/4000/4180)\n\n* feat(docker): add mysql development compose configuration\n\nCreate dedicated compose file for MySQL development environment with unique ports and project name\n\n* fix(grafana): disable folder structure from dashboard provisioning\n\nSet foldersFromFilesStructure to false to prevent creating postgresql/mysql subdirectories in Grafana UI\n\n* refactor(grafana): combine RUN commands in Dockerfile\n\nMerge plugin installation and permission setup into single RUN statement\n\n* fix: fixing more dashboards\n\n* fix: fixing dora dashboard\n\n* fix: update license information in convert-mysql-to-postgresql.py\n\n---------\n\nCo-authored-by: Fábio Luciano \u003cfabio.gois@sicoob.com.br\u003e"
    },
    {
      "commit": "5e9dfb8b8d3915494b468e098efde76696a51fa3",
      "tree": "d7681aa4e85b1c4ebaf513af8b638d2dda5ae5aa",
      "parents": [
        "92fbab8c02a73178e4eb639b1f584191f522f61a"
      ],
      "author": {
        "name": "Klesh Wong",
        "email": "klesh@qq.com",
        "time": "Sun May 31 19:26:12 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun May 31 19:26:12 2026 +0800"
      },
      "message": "feat(encryption): implement AES-GCM encryption and decryption with backward compatibility (#8895)\n\nCo-authored-by: Klesh Wong \u003ckleshwong@gmail.com\u003e"
    },
    {
      "commit": "92fbab8c02a73178e4eb639b1f584191f522f61a",
      "tree": "fb8b527a395a01b9312fbb47561077ecad036009",
      "parents": [
        "ad202276ab04c5b545fb0c5515c667b8d97ca61a"
      ],
      "author": {
        "name": "Spiff Azeta",
        "email": "35563797+spiffaz@users.noreply.github.com",
        "time": "Sat May 30 14:31:11 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat May 30 20:31:11 2026 +0800"
      },
      "message": "fix(dora): count failed deployments, not incidents, in Change Failure Rate (#8890)\n\nThe Change Failure Rate panels summed `has_incident`, which was the count of\ndistinct incidents per deployment. A deployment that caused multiple incidents\nwas counted multiple times, inflating CFR above the DORA definition\n(deployments that caused a failure / total deployments).\n\nMake `has_incident` a 0/1 flag so each deployment counts once regardless of how\nmany incidents it caused, and relabel the companion stat from \"incident count\"\nto \"failed deployment count\" so the displayed counts match the rate.\n\nValidated against a production dataset: a 30-day window with 6 incidents across\n4 deployments out of 23 total dropped from 26.1% to the correct 17.4%.\n\nSigned-off-by: Spiff Azeta \u003cspiffazeta@yahoo.com\u003e"
    },
    {
      "commit": "ad202276ab04c5b545fb0c5515c667b8d97ca61a",
      "tree": "cdeab0e9330e5eb645dcf6f093769ed18f611bee",
      "parents": [
        "4ac2c4256a9f015e6d13d43b2e287ab518c8417d"
      ],
      "author": {
        "name": "Klesh Wong",
        "email": "klesh@qq.com",
        "time": "Sat May 30 19:34:08 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat May 30 19:34:08 2026 +0800"
      },
      "message": "feat: require authentication for /proceed-db-migration endpoint if auth was enabled (#8893)\n\n* feat: require authentication for /proceed-db-migration endpoint if auth was enabled\n\n* fix: linting\n\n* fix: disable codespell due to it is blocked by asf runner\n\n---------\n\nCo-authored-by: Klesh Wong \u003ckleshwong@gmail.com\u003e"
    },
    {
      "commit": "4ac2c4256a9f015e6d13d43b2e287ab518c8417d",
      "tree": "256dcc5b204ec0b618b6992d5a6ccacfc6760427",
      "parents": [
        "163540a210c68e07bb0da1f874ab4152a33a1481"
      ],
      "author": {
        "name": "bujjibabukatta",
        "email": "bujjibabukatta6@gmail.com",
        "time": "Sat May 30 01:27:01 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat May 30 13:27:01 2026 +0800"
      },
      "message": "fix(bitbucket): handle API errors in workspace listing and guard empty pipelineId (#8885)\n\n- Add HTTP status code check in listBitbucketWorkspaces, mirroring\n  the existing check in listBitbucketRepos. A non-2xx Bitbucket response\n  was silently unmarshalled as an empty WorkspaceResponse, causing the\n  UI to show No data to select with no error surfaced to the user.\n\n- Guard against calling the pipeline subtasks API with an empty\n  pipelineId in card.tsx and step-4.tsx. The record is initialised\n  with pipelineId\u003d\"\" in step-2 before a pipeline has been triggered,\n  causing the frontend to issue GET /pipelines//subtasks and receive\n  a 400 invalid pipeline ID format error from the backend.\n\nFixes #8868"
    },
    {
      "commit": "163540a210c68e07bb0da1f874ab4152a33a1481",
      "tree": "f8dc1c0f036b48ff0a8be39e39484649228424dc",
      "parents": [
        "048adfb4e3a9c9fb9900cae2a5cad87e7bb71ab6"
      ],
      "author": {
        "name": "Tamás Laczkó-Albert",
        "email": "76678478+la-tamas@users.noreply.github.com",
        "time": "Sat May 30 08:19:22 2026 +0300"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat May 30 13:19:22 2026 +0800"
      },
      "message": "feature: Add claude code metrics data integration (#8808)\n\n* feat: Add claude code metrics data integration\n\n(cherry picked from commit 78fd47d9e7647ff3ac8b886b2ddb45f1df55e2b4)\n(cherry picked from commit fa69cf9ec0fa0db6aa5fb0aec2e5ca2796355ffd)\n\n(upstream) story: XPL-551: Add license headers\n\n(cherry picked from commit a12ece8c1ca65ff27acc14d4630ac3d404d51ce8)\n\n* feat: Fix header validation issues, migrations and tests\n\n* feat: Update plugin table tests\n\n* feat: Update claude_code unit tests\n\n---------\n\nCo-authored-by: tamas.albert \u003ctamas.laczkoalbert@concentrix.com\u003e\nCo-authored-by: Eldrick Wega \u003celdrick.wega@outlook.com\u003e"
    },
    {
      "commit": "048adfb4e3a9c9fb9900cae2a5cad87e7bb71ab6",
      "tree": "7d36ffb6901af5b9922d1c80c38d682d1d89c8e8",
      "parents": [
        "c1b617f60a947a90ea0f1b06f12a70dd66640521"
      ],
      "author": {
        "name": "Brian Feucht",
        "email": "brianfeucht@gmail.com",
        "time": "Fri May 29 20:40:13 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat May 30 11:40:13 2026 +0800"
      },
      "message": "Allow Rootly plugin to collect incidents without service identifiers assigned (#8892)\n\n* fix: map Rootly \u0027completed\u0027 status to DONE in incident converter\n\nThe mapStatus function was missing \u0027completed\u0027 as a terminal status,\ncausing incidents with that status to be written as IN_PROGRESS instead\nof DONE.\n\nCo-authored-by: Copilot \u003c223556219+Copilot@users.noreply.github.com\u003e\n\n* fix: use updated_date as MTTR fallback for Rootly \u0027completed\u0027 incidents\n\nRootly\u0027s \u0027completed\u0027 status represents a fully resolved incident that has\ngone through post-incident review. These incidents may not have a\nresolved_date populated. Fall back to updated_date so MTTR is computed\ncorrectly instead of showing the incident as unresolved.\n\nCo-authored-by: Copilot \u003c223556219+Copilot@users.noreply.github.com\u003e\n\n* fix: remove service filter requirement from Rootly incident collection\n\nAllow collecting all incidents without filtering by service ID.\nPreviously, all incidents required a service association to be collected;\nincidents created without service tags (common in Rootly) were silently\ndropped.\n\nChanges:\n- ValidateTaskOptions: service_id is now optional (no longer required)\n- GetParams: falls back to scope_id \u0027all\u0027 when service_id is empty\n- buildIncidentsQuery: only adds filter[service_ids] when service_id is set\n- extractRootlyIncident: skips the service-membership guard when\n  collecting globally (service_id \u003d \u0027\u0027) to avoid false drops\n\nCo-authored-by: Copilot \u003c223556219+Copilot@users.noreply.github.com\u003e\n\n---------\n\nCo-authored-by: Copilot \u003c223556219+Copilot@users.noreply.github.com\u003e"
    },
    {
      "commit": "c1b617f60a947a90ea0f1b06f12a70dd66640521",
      "tree": "edacd92fadd0aa221d608e12ccf31c71626d6261",
      "parents": [
        "7e9a2aec67695f8ddc7fe4442be8e02b831a7566"
      ],
      "author": {
        "name": "Dan Crews",
        "email": "crewsd@gmail.com",
        "time": "Wed May 20 22:48:20 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu May 21 13:48:20 2026 +0800"
      },
      "message": "chore: upgrade go to 1.26 (#8875)\n\n* chore: upgrade go to 1.26\n\n* ci: update github action versions\n\n* refactor: replace deprecated go APIs\n\n---------\n\nCo-authored-by: Klesh Wong \u003cklesh@qq.com\u003e"
    },
    {
      "commit": "7e9a2aec67695f8ddc7fe4442be8e02b831a7566",
      "tree": "ab2ab614a17fa2dab3f050d7d1dc8c219c9e54ee",
      "parents": [
        "c057341d13cad9ac308581a1c9f49cfcfd49a846"
      ],
      "author": {
        "name": "Klesh Wong",
        "email": "klesh@qq.com",
        "time": "Wed May 20 11:21:39 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed May 20 11:21:39 2026 +0800"
      },
      "message": "fix: require secret for forwarded user auth (#8880)\n\nReject spoofed X-Forwarded-User headers unless a configured shared secret matches the forwarded secret header.\n\nCo-authored-by: Klesh Wong \u003ckleshwong@gmail.com\u003e\nCo-authored-by: Copilot \u003c223556219+Copilot@users.noreply.github.com\u003e"
    },
    {
      "commit": "c057341d13cad9ac308581a1c9f49cfcfd49a846",
      "tree": "f5ec73add7f86cbc2df13cacd3d47fdda3462389",
      "parents": [
        "f03b83df5897831eac527abc8879045d86bb2d8c"
      ],
      "author": {
        "name": "Klesh Wong",
        "email": "klesh@qq.com",
        "time": "Sun May 17 17:15:16 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun May 17 17:15:16 2026 +0800"
      },
      "message": "feat(push-api): implement push API with authentication and validation logic (#8879)\n\nCo-authored-by: Klesh Wong \u003ckleshwong@gmail.com\u003e"
    },
    {
      "commit": "f03b83df5897831eac527abc8879045d86bb2d8c",
      "tree": "dfa61adcabeab14ef7dc46b92c84879b76918a69",
      "parents": [
        "c413ebc6c514f3ce17b40bbf17d0b1cfa09b320c"
      ],
      "author": {
        "name": "Klesh Wong",
        "email": "klesh@qq.com",
        "time": "Sun May 17 17:14:38 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun May 17 17:14:38 2026 +0800"
      },
      "message": "fix: harden dbt pipeline inputs (#8878)\n\n* fix: harden dbt pipeline inputs\n\nCo-authored-by: Copilot \u003c223556219+Copilot@users.noreply.github.com\u003e\n\n* fix(test): disable auth for local e2e server\n\nCo-authored-by: Copilot \u003c223556219+Copilot@users.noreply.github.com\u003e\n\n---------\n\nCo-authored-by: Klesh Wong \u003ckleshwong@gmail.com\u003e\nCo-authored-by: Copilot \u003c223556219+Copilot@users.noreply.github.com\u003e"
    },
    {
      "commit": "c413ebc6c514f3ce17b40bbf17d0b1cfa09b320c",
      "tree": "82df61b23c6a23ae8826ed5e65b0d0d9e9b26d73",
      "parents": [
        "ecb703c8e944f2b0a28cfa144d069484f85f1e6d"
      ],
      "author": {
        "name": "Klesh Wong",
        "email": "klesh@qq.com",
        "time": "Sun May 17 17:13:55 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun May 17 17:13:55 2026 +0800"
      },
      "message": "fix(asana): implement encryption for Asana connection token (#8873)"
    },
    {
      "commit": "ecb703c8e944f2b0a28cfa144d069484f85f1e6d",
      "tree": "a2fa015310d883131201d091576835f9a97f1119",
      "parents": [
        "aff3487a8efcf6c2eac8b8668391716d87d37c72"
      ],
      "author": {
        "name": "Klesh Wong",
        "email": "klesh@qq.com",
        "time": "Sun May 17 17:12:36 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun May 17 17:12:36 2026 +0800"
      },
      "message": "fix: Multiple credential-leak paths in gitextractor (#8872)"
    },
    {
      "commit": "aff3487a8efcf6c2eac8b8668391716d87d37c72",
      "tree": "a6cc71f9c60255fa8253541b1a3ef8e43b9c9c80",
      "parents": [
        "420e494b4ede9a16c4bfb59de099c797bcd7f5f1"
      ],
      "author": {
        "name": "Dan Crews",
        "email": "crewsd@gmail.com",
        "time": "Sat May 16 06:38:08 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat May 16 21:38:08 2026 +0800"
      },
      "message": "feat(rootly): add rootly integration plugin (#8877)\n\n* feat(rootly): scaffold plugin skeleton\n\nAdd the plugin entry point, impl shell with all required plugin\ninterfaces except DataSourcePluginBlueprintV200 (wired in U2),\nconnection model with Bearer auth, service + scope-config + placeholder\nincident/user/assignment models, and the initial migration script. API\nresources and subtasks are intentionally empty and will be populated by\nU2-U5.\n\n* feat(rootly): add API handlers and blueprint v200\n\nWire up connection test, remote scope list/search, scope CRUD, scope\nsync state, and blueprint v200 endpoints. Remote scope listing speaks\nJSON:API and page-based pagination to match the Rootly API shape.\nTestConnection validates the bearer token via GET /users/current.\nimpl.go now wires api.Init and restores DataSourcePluginBlueprintV200\nso the plugin can produce pipeline plans for selected Rootly services.\n\n* feat(rootly): collect and convert services to domain boards\n\nAdd services_collector, services_extractor, and service_converter\nsubtasks. Collector fetches GET /services/{id} for the scoped service,\nunwraps the JSON:API envelope, and populates _tool_rootly_services.\nConverter emits one ticket.Board per service using the standard\ndomain-id generator, feeding into the existing TICKET pipeline.\n\nAlso harden remote-scope pagination termination to key off the page\nwe requested rather than meta.current_page so a response missing that\nfield does not silently truncate the service list.\n\n* feat(rootly): collect and extract incidents with inline users\n\nFinalize the Incident and User models and add role-specific user-id\nfields (creator, started_by, mitigated_by, resolved_by, closed_by)\ndirectly on the Incident row. Add the incidents_collector and\nincidents_extractor subtasks. The collector is single-phase,\nfilter[services]-scoped and filter[updated_at][gt]-incremental; the\nextractor unwraps the JSON:API envelope and pulls inline nested user\nobjects from the incident attributes, emitting deduplicated User rows\nper incident.\n\nDrop the Assignment table entirely. Rootly\u0027s incident data model is\nrole-per-lifecycle-event, not a list of assignees, so PagerDuty\u0027s\nn-assignees shape does not fit. The schema and GetTablesInfo are\nadjusted accordingly.\n\n* feat(rootly): convert incidents to domain issues\n\nAdd incidents_converter. For each tool-layer incident, emit a\nticket.Issue (Type\u003dINCIDENT) with status, priority, lead time, and\nresolution date derived from the corresponding Rootly fields; emit one\nticket.IssueAssignee per distinct role user (creator, started_by,\nmitigated_by, resolved_by, closed_by); and emit a ticket.BoardIssue\nlinking the incident to its service board. This feeds DevLake\u0027s\nexisting DORA pipeline so change-failure rate and MTTR compute\ncorrectly for teams running on Rootly.\n\nUnknown incident statuses fall back to IN_PROGRESS with a warning log\nrather than panicking (a deliberate divergence from the PagerDuty\nplugin, motivated by Rootly\u0027s more volatile status enum). Severity\nmapping accepts case-insensitive sev0-sev4; unknown severities are\npreserved as-is.\n\nGuard computeLeadTime against resolved-before-started timestamps (clock\nskew or backfill anomalies) by returning nil rather than the wraparound\ngarbage a naive uint() cast would produce. Tighten test coverage on the\ndedup, key-fallback, and known-status-warning boundaries flagged during\ncode review.\n\n* test(rootly): add e2e test for extract and convert pipeline\n\nAdd an end-to-end test that drives the extract and convert subtasks\nover a crafted raw-incident fixture and verifies the tool-layer and\ndomain-layer output against snapshot CSVs. Fixtures cover every branch\nof the status and severity mapping tables, the same-user-across-roles\ndedup path, the zero-user case, and the safety-net filter that drops\nincidents whose relationships point at a different service than the\none the task was scoped to.\n\n* feat(rootly): register plugin in backend and config-ui\n\nAdd rootly to the backend plugin startup test and the table-info test\nso CI exercises it alongside the other plugins. Register RootlyConfig\nin the config-ui plugin list so operators can create connections,\nbrowse scopes, and run blueprints through the UI.\n\nThe cloud endpoint default includes the /v1/ API version prefix,\nwhich is what Rootly\u0027s REST API actually expects; without it requests\nland on the marketing site and return 404. The DOC_URL entries point\nat a Configuration/Rootly docs page that still needs to be written.\n\nUse the Rootly wordmark glyph as the plugin icon, rewritten as a\nfill-aware (currentColor) SVG so the config-ui can recolor it for\nselected/unselected states the same way it does every other plugin\nicon.\n\n* fix(rootly): align archived models with live model schemas\n\nThe U1 init migration\u0027s archived Connection, Service, and ScopeConfig\nmodels were missing columns contributed by the live models\u0027 embedded\nhelpers, so AutoMigrate produced tables the live models could not\nread or write. Each gap surfaced as an \"Unknown column\" error from\nMySQL the first time the table was touched:\n\n- Connection was missing endpoint, proxy, rate_limit_per_hour\n  (contributed by helper.RestConnection on the live struct).\n- Service was missing scope_config_id (contributed by common.Scope).\n- ScopeConfig was missing connection_id and name (contributed by\n  common.ScopeConfig, which the archived base type does not include).\n\nFold the missing fields into the archived models so a single init\nmigration produces the correct schema. Since rootly has not been\ndeployed anywhere, keeping one init migration is cleaner than\nchaining follow-up ALTERs; a fresh migrate creates the correct\ntables in one pass.\n\n* fix(rootly): align API contract with real Rootly /v1 responses\n\nThe original plugin shape was built from docs summaries rather than an\nactual response capture and diverged from the Rootly API in ways that\nsilently produced zero incidents. Reconcile every code path with\nground truth from the OpenAPI spec and a captured GET /v1/incidents\nresponse:\n\n- Connection test hits /users/me (Rootly\u0027s real \"who am I\" path); the\n  original /users/current returns 404.\n- Incident list filter is filter[service_ids]\u003d\u003cuuid\u003e, not\n  filter[services]\u003d\u003cuuid\u003e. The latter exists but accepts names and\n  silently matches nothing for a UUID.\n- Role-bearing user fields (user, started_by, mitigated_by,\n  resolved_by, closed_by) and severity are JSON:API response\n  envelopes nested on attributes: {\"data\":{\"id\":...,\"attributes\":...}}.\n  The previous flat NestedUser / SeverityAttrs shapes were reading the\n  wrong paths, so those fields were always empty.\n- Service membership lives on the sibling relationships block as\n  JSON:API id+type pointers, not on attributes. The safety-net\n  scope-filter check now reads from the right place.\n- The incident resource does not have an urgency field. Drop the\n  corresponding column from the model and archived schema.\n\nAlso harden the collector: split the ResponseParser / next-page logic\nso pagination state is captured during parse (rather than re-reading\nthe already-drained response body in GetNextPageCustomData), and add\nlightweight request/response diagnostics gated behind Debug logging.\n\nVerified end-to-end against a live Rootly tenant: 3 of 6 scoped\nservices returned incidents, all 3 extracted and converted into\nticket.Issue rows with creator assignees and board linkage.\n\n* style(rootly): trim narrative comments to match plugin conventions\n\nMatch PagerDuty\u0027s comment density. Keep the few comments that flag\nnon-obvious invariants (archived-base field overrides, 1-based\npagination, deliberate divergence from PagerDuty\u0027s panic-on-unknown\nbehavior, clock-skew guard).\n\n* refactor(rootly): address code review feedback\n\n  Apply fixes from a multi-lens code review pass:\n\n  - API: rename swagger {serviceId} to {scopeId} to match registered\n    route; remove dead Proxy handler.\n  - Models: add Sanitize() on RootlyConn; add RoleUserIds() helper on\n    Incident; index ServiceId; drop unused Url field on User; remove\n    dead RootlyResponse/ApiUserResponse types.\n  - Migrations: mirror live schema in archived models (index on\n    service_id; drop user.url).\n  - Collector: switch pagination to reqData.Pager.Page (avoids\n    divide-by-zero), cap at 10000 pages, extract buildIncidentsQuery\n    as a pure helper, drop unreachable lastPageEmpty branch and unused\n    TotalCount, remove diagnostic logs; add unit test pinning the\n    filter[service_ids] param literal as a regression guard.\n  - Services: preserve ScopeConfigId across re-collections; declare\n    ProductTables on collector and extractor metas.\n  - Extractor: skip emitting User rows with neither name nor email so\n    sibling scope tasks can fill in fuller data; use generic resolve()\n    for SequentialId; type ServiceRef as a named struct.\n  - Converter: consolidate mapStatus to return (mapped, known); use\n    Incident.RoleUserIds() instead of an inline slice.\n  - impl.go: comment justifying services-before-incidents subtask\n    ordering.\n  - e2e: rewrite raw incident fixtures to JSON:API envelope shape;\n    regenerate snapshots (drop urgency column).\n\n* feat(rootly): add rootly dashboard\n\n* chore: run gofmt\n\n* chore: add isBeta flag to rootly plugin"
    },
    {
      "commit": "420e494b4ede9a16c4bfb59de099c797bcd7f5f1",
      "tree": "c55a8a9c0753f8ffa623563374619ff2f5a37ba4",
      "parents": [
        "a6232fbe7ead219a0f8405b949295262f4a0b514"
      ],
      "author": {
        "name": "Fábio Luciano",
        "email": "fabio@naoimporta.com",
        "time": "Tue May 05 06:04:26 2026 -0300"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue May 05 17:04:26 2026 +0800"
      },
      "message": "feat(docker): add Dockerfile.local for local development and fix migrations (#8866)\n\n* fix(migrations): add new fields and improve migration scripts for various plugins\n\n* feat(docker): add Dockerfile.local for local development and update docker-compose configuration\n\n* chore: add license to dockerfile\n\n---------\n\nCo-authored-by: Fábio Luciano \u003cfabio.gois@sicoob.com.br\u003e"
    },
    {
      "commit": "a6232fbe7ead219a0f8405b949295262f4a0b514",
      "tree": "6c503209fd700c45b4118ebc266f7a9a1b56f510",
      "parents": [
        "78cb8d79b77b6d496f7a953f036791f79b62b876"
      ],
      "author": {
        "name": "Aviral Garg",
        "email": "gargaviral99@gmail.com",
        "time": "Tue May 05 11:53:17 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue May 05 14:23:17 2026 +0800"
      },
      "message": "fix(ci): replace Poetry runtime setup with uv (#8830)\n\nCo-authored-by: Klesh Wong \u003cklesh@qq.com\u003e"
    },
    {
      "commit": "78cb8d79b77b6d496f7a953f036791f79b62b876",
      "tree": "c97b47a87cb3266f4783c8f88fc90fd5d72abd96",
      "parents": [
        "e26c61b9297dbc6bcb5e0e1609a8c91b6acdef29"
      ],
      "author": {
        "name": "rohit singh gulia",
        "email": "rg383@njit.edu",
        "time": "Mon May 04 21:53:45 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue May 05 09:53:45 2026 +0800"
      },
      "message": "fix(ipc): increase scanner buffer size to 5MB to handle large outputs (#8841)"
    },
    {
      "commit": "e26c61b9297dbc6bcb5e0e1609a8c91b6acdef29",
      "tree": "a8c00f5d40921c59978f502e89cee5622c79dd42",
      "parents": [
        "ac73e27d4904e48507d18a7f3293905f9f64406c"
      ],
      "author": {
        "name": "Leif Roger Frøysaa",
        "email": "leif.roger.froysaa@akerbp.com",
        "time": "Wed Apr 29 14:35:02 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Apr 29 20:35:02 2026 +0800"
      },
      "message": "feat(github): Add incremental data collection (#8858)\n\n* fix(github): use RFC 3339 format for since\u003d incremental API parameters\n\nThe since\u003d query parameter passed to the GitHub API in four collectors\nwas formatted using Go\u0027s time.Time.String(), which produces a\nhuman-readable string (e.g. \"2024-01-15 10:30:00 +0000 UTC\") rather\nthan the ISO 8601 / RFC 3339 format the GitHub API requires\n(e.g. \"2024-01-15T10:30:00Z\").\n\nThe GitHub API silently ignores malformed date strings, causing these\ncollectors to perform full re-scans on every incremental run despite\nappearing to filter correctly. Fix by using .UTC().Format(time.RFC3339)\nin all four affected collectors:\n- comment_collector.go\n- issue_collector.go\n- commit_collector.go\n- pr_review_comment_collector.go\n\n(cherry picked from commit cf3cb621462d8ae661df5fcb8e1b47c70564cd60)\n\n* feat(github): migrate event extractor to StatefulApiExtractor\n\nSwitch Extract Events from the legacy full-scan NewApiExtractor to\nNewStatefulApiExtractor, which filters _raw_github_api_events by\ncreated_at \u003e\u003d last_run_start on incremental syncs. This table had\n13,828 rows in a representative production run and took ~117s to\nprocess on every run regardless of how few new events were collected.\nAfter this change incremental runs process only newly collected rows.\n\n(cherry picked from commit 84f324f65e8576cd2ff0ca8b74a2588ee2600e12)\n\n* feat(github): migrate PR extractor to StatefulApiExtractor\n\nSwitch Extract Pull Requests from legacy full-scan NewApiExtractor to\nNewStatefulApiExtractor. The _raw_github_api_pull_requests table had\n12,448 rows in a representative production run, taking ~108s on every\nincremental sync. After this change only newly collected PR rows are\nprocessed.\n\nSubtaskConfig captures prType and prComponent regex strings so that a\nscope config change automatically triggers a full re-extract. BeforeExtract\ndeletes GithubPrLabel rows for the current PR before re-inserting them\nin incremental mode, preventing stale labels from persisting when labels\nare removed upstream.\n\n(cherry picked from commit 80dc76d4e5f1ad6c65d48d42f60f50b87c9dad2d)\n\n* feat(github): migrate workflow run and PR commit extractors to StatefulApiExtractor\n\nSwitch Extract Workflow Runs (~7,364 raw rows, ~65s) and Extract PR\nCommits (~6,982 raw rows, ~60s) from legacy full-scan NewApiExtractor\nto NewStatefulApiExtractor. Both extractors are simple mappings with\nno scope-config dependency, so no SubtaskConfig or BeforeExtract needed.\n\n(cherry picked from commit ea4f158a65ad0f7a2c23ba7bbc9932059e2ca408)\n\n* feat(github): migrate remaining high-volume extractors to StatefulApiExtractor\n\nMigrate Extract Jobs (~5,369 rows, ~48s), Extract PR Reviews (~3,073\nrows, ~27s), and Extract PR Review Comments (~1,820 rows, ~16s) from\nlegacy full-scan NewApiExtractor to NewStatefulApiExtractor.\n\nAlso moves prUrlRegex compilation in pr_review_comment_extractor.go\nfrom inside the Extract closure (recompiled on every raw row) to before\nthe extractor is created, eliminating redundant regexp compilation.\n\n(cherry picked from commit 54d601587bc74ebd0f1103345c545571146739b5)\n\n* feat(github): migrate remaining low-volume extractors to StatefulApiExtractor\n\nMigrate the final seven GitHub extractors to NewStatefulApiExtractor:\nissue, comment, account, account_org, milestone, commit, commit_stats.\n\nissue_extractor gains SubtaskConfig (issue classification regex strings)\nso scope config changes trigger automatic full re-extraction, and\nBeforeExtract cleanup for GithubIssueLabel and GithubIssueAssignee rows\nin incremental mode to prevent stale labels/assignees persisting after\nupstream removal.\n\nAll other extractors in this commit are simple migrations with no\nconfig-sensitivity or child record cleanup needed.\n\nWith this commit all 14 GitHub plugin extractors are now incremental.\nCombined with the collector fixes in earlier commits, incremental\ncollection runs that previously took 9+ minutes in the extract phase\nwill now complete in seconds when few or no new records were collected.\n\n(cherry picked from commit 606acea88caef63662733162faa47a6c6d3155cc)\n\n* feat(github): migrate CICD converters to StatefulDataConverter\n\nConvert Workflow Runs and Convert Jobs now use NewStatefulDataConverter,\nskipping records unchanged since last run on incremental pipelines.\nJobs are filtered via JOIN on _tool_github_runs.github_updated_at.\n\n(cherry picked from commit 75a909efccab1d04bfae4058f4674663b00762a6)\n\n* feat(github): migrate PR supporting data converters to StatefulDataConverter\n\nConvert PR Commits, Convert PR Comments, and Convert PR Reviews now use\nNewStatefulDataConverter. Child-of-PR records are filtered incrementally\nvia JOIN on _tool_github_pull_requests.github_updated_at; PR comments\nadditionally filter on their own github_updated_at.\n\n(cherry picked from commit 17cbc2e4caff7349fa2d3389d7cafcc5d0edee71)\n\n* feat(github): migrate PR main and cross converters to StatefulDataConverter\n\nConvert Pull Requests filters by GithubPullRequest.github_updated_at.\nConvert Reviews and Convert PR Issues filter via JOIN on pull_requests\ngithub_updated_at since reviewers and pr_issues have no own timestamp.\n\n(cherry picked from commit 47437ae49d30b967b443c964a4b1baca34344acc)\n\n* feat(github): migrate remaining data converters to StatefulDataConverter\n\nMigrate the last 9 converters from DataConverter to StatefulDataConverter\nso they skip already-processed records on incremental runs:\n\n- issue_convertor: filter on github_updated_at\n- issue_comment_convertor: filter on github_updated_at\n- issue_label_convertor: JOIN to issues, filter on issues.github_updated_at\n- issue_assignee_convertor: JOIN to issues, filter on issues.github_updated_at\n- pr_label_convertor: JOIN to pull_requests, filter on pr.github_updated_at\n- account_convertor: filter on updated_at\n- release_convertor: filter on updated_at\n- repo_convertor: filter on updated_at; retain GithubApiRepo struct (used by pr_extractor)\n- commit_convertor: filter on authored_date\n\n(cherry picked from commit 7bcc9da4676d34d9cd24e41a68765cf85723ac81)\n\n* fix(github): keep incremental commit and PR-issue processing accurate\n\n* fix(github): bootstrap workflow runs incremental window from tool state\n\n* feat(runner): include processed record count in subtask finish logs\n\n* fix(stateful): bootstrap subtask state from collector checkpoints\n\n* fix(github): optimize incremental Convert Jobs query\n\n* fix(github): resolve lint and staticcheck issues in converters\n\n* fix(stateful): tolerate missing collector state table in bootstrap\n\n* fix(github): address CI failures in state bootstrap and run tests\n\n* fix(server): use interface{} for store handler swag annotations (#8859)\n\nSigned-off-by: yamoyamoto \u003cyamo7yamoto@gmail.com\u003e\n\n* fix(github): stabilize e2e state bootstrap and issue assignee join\n\n* fix(stateful): skip collector bootstrap lookup when state table is absent\n\n* test(stateful): use generic scope params in bootstrap tests\n\n* fix(github): keep issue assignee conversion full-sync safe\n\n---------\n\nSigned-off-by: yamoyamoto \u003cyamo7yamoto@gmail.com\u003e\nCo-authored-by: Tomoya Kawaguchi \u003c68677002+yamoyamoto@users.noreply.github.com\u003e"
    },
    {
      "commit": "ac73e27d4904e48507d18a7f3293905f9f64406c",
      "tree": "19d136d9e0f5cc78f0d66c94d434d7efda7dbfae",
      "parents": [
        "3472cc3644f3684b3ae3e6599e359dcc58d2bf5d"
      ],
      "author": {
        "name": "Richard Boisvert",
        "email": "rboisvert@devolutions.net",
        "time": "Wed Apr 29 02:32:01 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Apr 29 14:32:01 2026 +0800"
      },
      "message": "feat(ui): implement dark theme and refactor color usage (#8865)\n\nAdd a system-aware dark theme with a header toggle (light, dark, system) that persists in localStorage. All colors now flow through `src/theme/tokens.ts` into both the antd component library and styled-components, replacing hardcoded hex across `styled.ts` files and inline styles. Light mode is preserved the same as before.\n\nhttps://github.com/apache/incubator-devlake/issues/8863"
    },
    {
      "commit": "3472cc3644f3684b3ae3e6599e359dcc58d2bf5d",
      "tree": "ce55c22bb83f6b21e36a2edbf7315faea3bd1050",
      "parents": [
        "16c5b928c42a277e2c06c1b6082e12dcf58d1443"
      ],
      "author": {
        "name": "Cornelius Schuchardt",
        "email": "cornelius.schuchardt@gmail.com",
        "time": "Wed Apr 29 06:05:08 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Apr 29 12:05:08 2026 +0800"
      },
      "message": "fix(azuredevops_go): handle empty/invalid timeline responses for YAML… (#8839)\n\n* fix(azuredevops_go): handle empty/invalid timeline responses for YAML-broken builds\n\nBuilds that fail due to YAML syntax errors in pipeline definitions return\nan empty or non-JSON body from the Timeline API (200 OK with no usable\ncontent). Previously this caused ParseRawMessageFromRecords to propagate\na hard error that aborted the entire collectApiTimelineRecords subtask.\n\nTwo complementary fixes:\n\n1. Add ignoreInvalidTimelineResponse AfterResponse handler that supersedes\n   ignoreDeletedBuilds: it still skips 404s (deleted builds) and now also\n   skips responses with empty or non-JSON bodies by returning\n   api.ErrIgnoreAndContinue, leaving the rest of the pipeline intact.\n   The body is peeked and then restored via io.NopCloser so the downstream\n   ResponseParser can still read it when the body is valid JSON.\n\n2. Extend the DB query in CollectRecords to exclude builds with\n   result \u003d \u0027none\u0027 in addition to result \u003d \u0027failed\u0027. Builds broken by\n   YAML syntax errors are categorised as \u0027none\u0027 by Azure DevOps (already\n   mapped to RESULT_FAILURE in cicdBuildResultRule), so fetching their\n   timeline is both unnecessary and the primary source of the bad\n   responses. Using NOT IN ? with a []string slice follows the same\n   pattern used in blueprint_helper.go.\n\nUnit tests added for ignoreInvalidTimelineResponse covering: 404,\nempty body, non-JSON body, valid JSON (nil error + body restored),\nand valid empty JSON object.\n\nFixes: #8838\nSigned-off-by: Cornelius Schuchardt \u003ccornelius.schuchardt@beck.de\u003e\n\n* fix(azuredevops_go): remove unused ignoreDeletedBuilds function\n\nThe ignoreDeletedBuilds function was replaced by\nignoreInvalidTimelineResponse\nin ci_cd_timeline_records_collector.go but was left behind in shared.go.\nThis caused the \u0027unused\u0027 linter to fail with exit code 2.\n\n* fix: resolve govet non-constant format string warnings\n\nUse constant format verbs instead of dynamic strings passed directly\nas format arguments to avoid govet printf-family warnings:\n\n- core/migration/linter: fmt.Errorf with string concat replaced by %s verb\n- impls/logruslog: Warn/Error pass pre-formatted message via \"%s\" arg\n  instead of using it directly as the format string\n\n---------\n\nSigned-off-by: Cornelius Schuchardt \u003ccornelius.schuchardt@beck.de\u003e"
    },
    {
      "commit": "16c5b928c42a277e2c06c1b6082e12dcf58d1443",
      "tree": "82bafdb08cd0a3c575d04f7ecd96ea67530c4818",
      "parents": [
        "0e16249cee158c41d0e3e380d3ff43f15e5c9189"
      ],
      "author": {
        "name": "Anton Tarasevich",
        "email": "65176916+antarasevich@users.noreply.github.com",
        "time": "Tue Apr 28 17:19:37 2026 +0300"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Apr 28 10:19:37 2026 -0400"
      },
      "message": "fix(argoCD): avoid duplication of latest deployments (#8862)"
    },
    {
      "commit": "0e16249cee158c41d0e3e380d3ff43f15e5c9189",
      "tree": "76c4f3a6ec1700333959a963b0fb574d83214fad",
      "parents": [
        "49959f61414ba91647f7bb3fb4ba80efea343d07"
      ],
      "author": {
        "name": "Tomoya Kawaguchi",
        "email": "68677002+yamoyamoto@users.noreply.github.com",
        "time": "Tue Apr 28 16:00:14 2026 +0900"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Apr 28 15:00:14 2026 +0800"
      },
      "message": "fix(server): use interface{} for store handler swag annotations (#8859)\n\nSigned-off-by: yamoyamoto \u003cyamo7yamoto@gmail.com\u003e"
    },
    {
      "commit": "49959f61414ba91647f7bb3fb4ba80efea343d07",
      "tree": "7d800037119a97a9ff8829e680810a8b21d0732c",
      "parents": [
        "19ee801d909a160dafa190c4a81f0120a14c5602"
      ],
      "author": {
        "name": "Tomoya Kawaguchi",
        "email": "68677002+yamoyamoto@users.noreply.github.com",
        "time": "Mon Apr 27 22:07:45 2026 +0900"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Apr 27 21:07:45 2026 +0800"
      },
      "message": "fix(github_graphql): keep requestedIssues out of GraphQL query in Collect Issues (#8855)\n\nSigned-off-by: yamoyamoto \u003cyamo7yamoto@gmail.com\u003e"
    },
    {
      "commit": "19ee801d909a160dafa190c4a81f0120a14c5602",
      "tree": "12225ba19237d7245ab0ac1eaea2d128c0c00562",
      "parents": [
        "1d21f199783b24fc8486445337d922d8a58badd3"
      ],
      "author": {
        "name": "Richard Boisvert",
        "email": "rboisvert@devolutions.net",
        "time": "Mon Apr 27 08:29:34 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Apr 27 20:29:34 2026 +0800"
      },
      "message": "feat(auth): implement oidc authentication and session management (#8854)\n\nDevLake had no native OIDC login. The existing protections: nginx Basic auth and /rest/* Bearer tokens, all sit outside the API and don\u0027t give you SSO at the application layer.\n\nThis adds an OIDC login flow behind a default-off AUTH_ENABLED switch. Multiple providers (Entra ID, Google, etc.) can be configured side by side, sessions are revocable server-side, and CSRF is enforced on state-changing requests when authenticating via the session cookie.\n\nWhen AUTH_ENABLED\u003dfalse the system behaves exactly as before; existing Basic auth, oauth2-proxy, and API-key flows are untouched.\n\nRefs: https://github.com/apache/incubator-devlake/discussions/8694\n\nRefs: https://devlake-io.slack.com/archives/C03AJH1HBAT/p1776917914734679"
    },
    {
      "commit": "1d21f199783b24fc8486445337d922d8a58badd3",
      "tree": "fecaa5739891f63202fa6d3f41ee4c28180aa727",
      "parents": [
        "f72868cd3423bdab0416f8611d85bb1d98699772"
      ],
      "author": {
        "name": "Tomoya Kawaguchi",
        "email": "68677002+yamoyamoto@users.noreply.github.com",
        "time": "Sun Apr 26 17:39:09 2026 +0900"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Apr 26 16:39:09 2026 +0800"
      },
      "message": "fix(ci): pin docker/* actions in build.yml to ASF-approved SHAs (#8852)\n\nSigned-off-by: yamoyamoto \u003cyamo7yamoto@gmail.com\u003e"
    },
    {
      "commit": "f72868cd3423bdab0416f8611d85bb1d98699772",
      "tree": "2d97b6783cc49402f6f4245b0fe7baf864e71136",
      "parents": [
        "c8a7711c2ab97a4fe5de9a1ab84027bbe82e3847"
      ],
      "author": {
        "name": "fxgagnon",
        "email": "72470239+fxgagnon@users.noreply.github.com",
        "time": "Sat Apr 25 09:00:34 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Apr 25 21:00:34 2026 +0800"
      },
      "message": "fix: disable API client caching for GitHub App connections (#8850)\n\nGitHub App installation tokens expire after ~1 hour, causing admin APIs\n(remote-scopes, test-connection) to fail silently after token expiry.\n\nThis fix overrides GetHash() to return empty string for GitHub App connections,\ndisabling client caching and ensuring fresh tokens are fetched for each admin request.\n\nPAT connections continue to use default caching behavior.\n\nFixes #8847"
    },
    {
      "commit": "c8a7711c2ab97a4fe5de9a1ab84027bbe82e3847",
      "tree": "fc31f3b7527bd5f4fe725ccd0490816badb744ce",
      "parents": [
        "c5905f75fadf6a6b5aecd686c0118ed3e774d174"
      ],
      "author": {
        "name": "Joshua Smith",
        "email": "jbsmith7741@gmail.com",
        "time": "Sat Apr 25 07:00:25 2026 -0600"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Apr 25 21:00:25 2026 +0800"
      },
      "message": "fix(sonarqube): use filter parameter for project search (#8849)\n\nSonarQube\u0027s components/search_projects endpoint does not accept a bare\nq parameter for text search; it is silently ignored. The correct\napproach is the filter language: filter\u003dquery \u003d \"term\". Sending q\u003dterm\ncaused DevLake to return the full unfiltered project list regardless of\nthe search input.\n\nAlso fix three related pagination bugs in the remote scope search UI:\n- results were replaced instead of accumulated on page 2+\n- getHasMore was inverted (tested loading state instead of hasMore)\n- onScroll could trigger duplicate requests while one was in flight\n- typing a new query did not reset page and hasMore state\n\nFixes #8805\n\nSigned-off-by: Joshua Smith \u003cjbsmith7741@gmail.com\u003e"
    }
  ],
  "next": "c5905f75fadf6a6b5aecd686c0118ed3e774d174"
}
