原始 JSON 报告 机器可读
{
"data": {
"repo": {
"topics": [
"audit-log",
"audit-trail",
"logging",
"cakephp",
"cakephp-plugin",
"php"
],
"is_fork": false,
"size_kb": 757,
"has_wiki": true,
"homepage": "https://dereuromark.github.io/cakephp-audit-stash/",
"languages": {
"PHP": 876741
},
"pushed_at": "2026-06-28T10:52:52Z",
"created_at": "2025-10-15T20:48:45Z",
"owner_type": "User",
"updated_at": "2026-06-28T10:52:54Z",
"description": "Flexible and rock solid audit log tracking for CakePHP",
"is_archived": false,
"is_disabled": false,
"license_spdx": "MIT",
"default_branch": "master",
"license_spdx_raw": "MIT",
"primary_language": "PHP",
"significant_languages": [
"PHP"
]
},
"owner": {
"blog": "https://www.dereuromark.de/",
"name": "Mark Scherer",
"type": "User",
"login": "dereuromark",
"company": "Available as (Cake)PHP expert",
"location": "Berlin",
"followers": 750,
"avatar_url": "https://avatars.githubusercontent.com/u/39854?v=4",
"created_at": "2008-12-11T16:33:44Z",
"is_verified": null,
"public_repos": 123,
"account_age_days": 6437
},
"license": {
"state": "standard",
"spdx_id": "MIT",
"raw_spdx": "MIT",
"file_present": true,
"scorecard_found": true,
"profile_has_license": true
},
"activity": {
"releases": [
{
"tag": "2.1.0",
"kind": "minor",
"published_at": "2026-06-18T15:47:13Z"
},
{
"tag": "2.0.1",
"kind": "patch",
"published_at": "2026-05-25T23:53:39Z"
},
{
"tag": "2.0.0",
"kind": "major",
"published_at": "2026-05-04T11:59:30Z"
},
{
"tag": "1.2.3",
"kind": "patch",
"published_at": "2026-05-03T19:14:38Z"
},
{
"tag": "1.2.2",
"kind": "patch",
"published_at": "2026-05-03T15:30:40Z"
},
{
"tag": "1.2.1",
"kind": "patch",
"published_at": "2026-05-03T07:39:32Z"
},
{
"tag": "1.2.0",
"kind": "minor",
"published_at": "2026-04-27T13:02:27Z"
},
{
"tag": "1.1.1",
"kind": "patch",
"published_at": "2026-04-17T16:36:20Z"
},
{
"tag": "1.1.0",
"kind": "minor",
"published_at": "2026-04-17T11:56:52Z"
},
{
"tag": "1.0.1",
"kind": "patch",
"published_at": "2026-03-28T14:33:36Z"
},
{
"tag": "1.0.0",
"kind": "major",
"published_at": "2026-03-20T13:45:39Z"
},
{
"tag": "0.3.12",
"kind": "patch",
"published_at": "2026-03-19T05:44:07Z"
},
{
"tag": "0.3.11",
"kind": "patch",
"published_at": "2026-03-19T04:37:21Z"
},
{
"tag": "0.3.10",
"kind": "patch",
"published_at": "2026-03-16T07:27:05Z"
},
{
"tag": "0.3.9",
"kind": "patch",
"published_at": "2026-03-15T05:23:35Z"
},
{
"tag": "0.3.8",
"kind": "patch",
"published_at": "2026-03-07T14:06:29Z"
},
{
"tag": "0.3.7",
"kind": "patch",
"published_at": "2026-02-18T19:27:20Z"
},
{
"tag": "0.3.6",
"kind": "patch",
"published_at": "2026-01-31T06:59:16Z"
},
{
"tag": "0.3.5",
"kind": "patch",
"published_at": "2026-01-22T17:08:39Z"
},
{
"tag": "0.3.4",
"kind": "patch",
"published_at": "2025-12-09T05:05:24Z"
},
{
"tag": "0.3.3",
"kind": "patch",
"published_at": "2025-12-07T07:30:34Z"
},
{
"tag": "0.3.2",
"kind": "patch",
"published_at": "2025-12-05T06:21:44Z"
},
{
"tag": "0.3.1",
"kind": "patch",
"published_at": "2025-11-24T22:36:39Z"
},
{
"tag": "0.3.0",
"kind": "minor",
"published_at": "2025-10-29T02:33:57Z"
},
{
"tag": "0.2.0",
"kind": "minor",
"published_at": "2025-10-17T22:58:16Z"
},
{
"tag": "0.1.0",
"kind": "minor",
"published_at": "2025-10-15T21:42:07Z"
}
],
"recent_commits": [
{
"oid": "86a1550a95a43d83c8252b62ceeda6f9f03c8288",
"body": null,
"is_bot": false,
"headline": "Fix PHPStan on CakePHP 5.4",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-06-28T10:52:50Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "0147c985b379d7d99c7b1339c029a980cb6dbba2",
"body": "actions/cache@v1-v4 declare Node 20, which GitHub now deprecates and\nforce-runs on Node 24. v5 is Node 24 native and removes the warning.",
"is_bot": false,
"headline": "CI: bump actions/cache to v5 (Node 24)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-06-28T09:40:30Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "b36e54a62a3f5142627bf2403d6ea91c0e2b9bab",
"body": "Add support for sebastian/diff 9.x",
"is_bot": false,
"headline": "Merge pull request #79 from dereuromark/sebastian-diff-9-support",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-06-18T15:33:51Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "c34b4f73b4eb6298e84b6e000fe33a50700df8ad",
"body": null,
"is_bot": false,
"headline": "Add support for sebastian/diff 9.x",
"author_name": "Mark",
"author_login": "dereuromark",
"committed_at": "2026-06-18T15:28:48Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "ae6142d27b6014d07e5fa8aab6e2f5ddebf648c5",
"body": null,
"is_bot": false,
"headline": "Fix PHPCS issues after sniffer update (#78)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-27T16:24:53Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "de23bb35a08daac4199c7af7c0ef1622941aaae2",
"body": null,
"is_bot": false,
"headline": "Restore hidden checkbox field in revert preview",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-26T23:58:54Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "0c0df8d5f33b78d296eeea7bf88391e5902649d6",
"body": null,
"is_bot": false,
"headline": "Add sort direction indicators to admin tables",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-26T21:13:38Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "e2724d50caf63831317d64be186454e4500c7e00",
"body": "* Fix N^2 audit duplication on bulk deleteMany\n\nCakePHP shares a single options object (and therefore one `_auditQueue`)\nacross every entity of a `deleteMany()`, then dispatches\nModel.afterDeleteCommit once per entity. afterCommit() flushed the whole\nshared queue on every dispatch but never cleared \n[…]\nr parent under the same transaction id. Single saves and the afterSave\nstrategy are unaffected.\n\nCovered by integration tests for bulk inserts, audited associations, and\nrollback (no phantom records).",
"is_bot": false,
"headline": "Fix saveMany() logging no audit records (#77)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-25T23:35:42Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "3e8e6d6173e9141616a051c82f0450a5d5e2c6cf",
"body": null,
"is_bot": false,
"headline": "Fix audit-stash static analysis follow-up (#74)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-25T14:23:52Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "86b9003bb371d5e71e83037eabdae9f4f31c1471",
"body": "* Apply conservative Rector cleanup\n\n* Run PHPCS cleanup\n\n* Preserve explicit ForbiddenException handling\n\n* Restore safe NDJSON encoding\n\n* Restore compact() for matching view vars\n\n* Fix operator precedence review issues\n\n* Restore compact() for matching view vars\n\n* Fix cleanup branch static anal\n[…]\nestore parent setUp call\n\n* Restore compact in audit-stash\n\n* Fix audit-stash review follow-ups\n\n* Restore entity property access\n\n* Fix inline var annotation spacing\n\n* Use typed guard for delete log",
"is_bot": false,
"headline": "Apply conservative Rector cleanup (#73)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-25T14:10:07Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "0b4fb3c0416d7c89a06b435e8fe569b8b93e6e8a",
"body": "Point readers to https://toolbox.dereuromark.de/dbml so they can convert this\nDBML snapshot into the migration or raw SQL they need for their own app.",
"is_bot": false,
"headline": "Link the DBML converter in the schema snapshot",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-23T15:53:59Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "594a7c4d5acb9ebc882cac311200f675b7339633",
"body": "Hand-maintained DBML snapshot of the plugin's migration schema under\nresources/schema/schema.dbml — paste into dbdiagram.io to view/diagram.\nMirrors the snapshot already shipped by cakephp-workflow.",
"is_bot": false,
"headline": "Add DBML database schema snapshot",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-23T12:53:55Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "593f70827fa53aa544ddbc9f14fe10e6125bc937",
"body": "* Make audit_logs.primary_key type configurable via Polymorphic.type\n\n* Address review: document Polymorphic.type as a config array and clarify it affects fresh installs",
"is_bot": false,
"headline": "Make audit_logs.primary_key type configurable via Polymorphic.type (#71)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-22T23:04:23Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "853bcaccf7120aed96d35b7df1ac581f28c87ec5",
"body": null,
"is_bot": false,
"headline": "Move CONTRIBUTING.md to .github/ and refresh",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-22T18:51:05Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "25aa29ade6b8ad8c7073d002e5c221991976be15",
"body": "…ary_keys (#70)\n\n* Align audit_logs primary_key signedness with Migrations.unsigned_primary_keys\n\nThe primary_key column stores the polymorphic reference to the audited record and was hardcoded as unsigned.\n\nColumns referencing primary keys must use the same signedness as the application's primary k\n[…]\ngned when unset) so new installs get a matching column. The separate follow-up migration is removed; existing installs are intentionally left untouched. The table's own auto-increment id is unchanged.",
"is_bot": false,
"headline": "Align audit_logs primary_key signedness with Migrations.unsigned_prim…",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-22T18:29:23Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a2b3abd74e3da133f81a57f119cceb67afdb4789",
"body": "- Set cleanUrls so doc pages drop the .html suffix (works on GitHub Pages)\n- Bump deploy-docs actions to the Node 24 majors ahead of the Node 20\n runner removal (checkout v6, setup-node v6, configure-pages v6,\n upload-pages-artifact v5, deploy-pages v5)",
"is_bot": false,
"headline": "Enable clean docs URLs and update deploy actions to Node 24",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-21T17:20:49Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "e587c257f6d0e658f648eeb9abfc14bb7536b587",
"body": null,
"is_bot": false,
"headline": "Replace date function with CakePHP DateTime (#69)",
"author_name": "Kevin Pfeifer",
"author_login": "LordSimal",
"committed_at": "2026-05-16T15:34:49Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "52b9bccf39efa5ed665babff6fe4513ac836bf98",
"body": "The behavior hooks per-entity ORM model events\n(Model.beforeSave / afterSave / afterSaveCommit). Anything that doesn't\ngo through Table::save() or Table::delete() — updateAll, deleteAll, the\nquery() builder's update/delete, raw Connection::execute() — is\nsilently un-audited. This is correct CakePHP \n[…]\nparagraph IMPORTANT note to the AuditLogBehavior\nclass-level docblock so it surfaces in IDE tooltips and on any code\nreview of a bulk-write change against an audited table.\n\nNo code changes; doc-only.",
"is_bot": false,
"headline": "Document operations that bypass the audit listener (#68)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-12T13:35:51Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "af72db51a94116b9770b482fd6ee47e25346475a",
"body": "ChainVerifier walked the table in chunks of N rows using\n`WHERE id > $lastId`, with no upper bound. Two concurrent-write\nscenarios produced false-broken reports:\n\n1. A new audit row inserted mid-stream gets its prev_hash from the\n in-flight last row; if the verifier crosses chunk boundaries while\n\n[…]\n.beforeFind listener to insert a third hash-bearing row between\nthe snapshot query and the chunk walk. With the fix the row is not\nchecked (rowsChecked == 2); without the fix it is (rowsChecked == 3).",
"is_bot": false,
"headline": "Snapshot upper id bound at start of chain verification (#67)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-12T13:20:16Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "70a9a591c9bb30e2a8c9c7cbf957d8a811c768c2",
"body": "The page-header pattern (title + action buttons in\n`d-flex justify-content-between align-items-center`) pushed buttons\noff-viewport on phones because no wrap was allowed. Wrap on viewports\n< sm so buttons fall to a new row instead of being clipped.",
"is_bot": false,
"headline": "Make page-header row wrap on phones so action buttons stay on-screen",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-05-12T08:29:37Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "de44d7f3b73fb88a5d522c04ff2991cb96caa0b1",
"body": "… (#66)\n\n* Fix related-changes finder for irregular plural and -ies source names\n\n`AuditLogsTable::buildForeignKeyName()` derived the child foreign key\nfrom the parent source by stripping a trailing `s` from the\nsnake-cased name. That produced:\n\n- Categories -> categorie_id (should be category_id)\n[…]\nestBuildForeignKeyNameViaPascalCase` continues to pass\nbecause UserProfiles -> user_profiles -> user_profile is correct under\nboth implementations.\n\n* Fix docblock tag order for the extends annotation",
"is_bot": false,
"headline": "Fix related-changes finder for irregular plural and -ies source names…",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-11T23:08:13Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a5ebe0a8cb297eeaf4a26cef80d0c383e234323d",
"body": "The default Bootstrap 5 placeholder color (--bs-secondary-color, ~#757575)\nis dark enough to look like real field values. Override to the gray-500\ntoken so empty fields read clearly as placeholders.",
"is_bot": false,
"headline": "Lighten form placeholder color so it's not mistaken for real content",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-05-11T22:01:56Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "6559e31b9acb6e3893887240f6eb815fd617efc3",
"body": null,
"is_bot": false,
"headline": "Fix CS.",
"author_name": "Mark",
"author_login": "dereuromark",
"committed_at": "2026-05-09T18:46:50Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "fea94b45bce7d04d4d8e12d7249f2ff1db422bf4",
"body": "Packagist already shows the GitHub repo under Source; the homepage\nfield is more useful pointing at the dedicated docs site.",
"is_bot": false,
"headline": "Point composer.json homepage at the VitePress docs site",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-05-09T13:42:20Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "dbc41b2354a3ce03c02410103c065600017e646d",
"body": "* Add configurable Back-to-App link to admin header\n\nReads AuditStash.adminBackUrl (any URL — string, array, or anything\n$this->Url->build() accepts). Optional AuditStash.adminBackLabel\noverrides the default \"Back to App\" text.\n\nWhen set, an outline arrow-left button is rendered in the right group\nof the header — left of the Bouncer cross-link if present, before the\nclock. When unset (default), the header looks the same as before.\n\n* Document adminBackUrl + adminBackLabel",
"is_bot": false,
"headline": "Add configurable Back-to-App link to admin header (#65)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-04T12:25:02Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "5771f1cdf15d797fe899d0660f0f4d3c77a419d5",
"body": "The general .pagination .page-link rule sets color: var(--audit-primary).\nThe active rule then sets background-color: var(--audit-primary) without\noverriding the text color, leaving the current-page number rendered as\npurple text on a purple background — unreadable.\n\nOverride the text color to #fff on the active item.",
"is_bot": false,
"headline": "Fix unreadable active page number in pagination (#64)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-04T11:57:59Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "8ec58cd5bf41a793182ccf5f0a63627c971d4d72",
"body": null,
"is_bot": false,
"headline": "Add CONTRIBUTING.md",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-05-04T02:46:08Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "ae658f134d2626d83d081ec5181c44364fa18e9b",
"body": "Rolling back via VARCHAR(7) silently truncates (MySQL non-strict) or\nhard-rejects (MySQL strict, Postgres) any custom event type longer\nthan 7 chars — exactly the values this migration was added to\nsupport (Audit::log() facade types like 'user.login').\n\nA wider column is forward-compatible, so the safe rollback is to\nleave it at VARCHAR(64) — i.e. an intentional no-op down().",
"is_bot": false,
"headline": "Fix WidenTypeColumn::down() — drop the destructive type-column shrink",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-05-04T01:40:52Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "99f9a3c6904bcf94cd35100706cded45f22f9f36",
"body": "…havior",
"is_bot": false,
"headline": "Document revert.enabled and revert.auditReverts now that they gate be…",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-05-04T01:08:56Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "336904804765fca8b52f0b1f6a6620370b6a8526",
"body": "Both keys were documented in config/app.example.php but never read by\nany source file — RevertService ran unconditionally and unconditionally\ncreated the revert audit row.\n\n- AuditStash.revert.enabled: when false, revertFull/revertPartial/\n restoreDeleted throw RuntimeException at the entry point, before any\n DB work. Defaults to true (the historic behavior).\n- AuditStash.revert.auditReverts: when false, createRevertAudit() bails\n out without inserting the type=revert row. Defaults to true.",
"is_bot": false,
"headline": "Wire revert.enabled and revert.auditReverts (#63)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-04T01:07:13Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "47bf7b7cbcba7e45a9877a8cbc7d25ad9d65d538",
"body": "Add two new pages:\n- features/revert.md — Revert/Restore feature (RevertService API, admin\n routes, audit-of-revert behavior, restore caveats).\n- guide/view-helper.md — AuditHelper API across diff rendering, formatting,\n badges, tables, and revert/restore action buttons, with a worked example\n of\n[…]\nalongside beforeLog.\n- guide/testing.md documents Audit::setPersister() for swapping the\n persister in unit tests.\n\nWire new pages into the guide and features sidebars and the section\noverview pages.",
"is_bot": false,
"headline": "Document remaining undocumented features and config",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-05-04T01:00:42Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "5d4f51555e69b7bd3a6a0b527e50363948242f67",
"body": "VitePress treats absolute paths as internal site links and fails\nthe build on dead links. The /src/Persister/TablePersister.php\nreference now points at the GitHub blob URL instead.",
"is_bot": false,
"headline": "Fix VitePress build: link TablePersister source via GitHub URL",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-05-04T00:47:24Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "c77c97f062f2b01cdbdd0708bcf9bddca22a0eaa",
"body": null,
"is_bot": false,
"headline": "Restore CakePHP version-map paragraph as README intro",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-05-04T00:42:26Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "7e96c39294e9d78e1bfc3873fd303482c286db86",
"body": "- Reduce VitePress hero halo (blur 42px → 72px, opacity 0.55 light /\n 0.45 dark) so the orange shield no longer dominates the landing.\n- Commit docs/package-lock.json (required by deploy-docs workflow\n cache-dependency-path).\n- Fix stale docs/tamper-evidence.md reference in\n AuditLogBehaviorTest after move to docs/features/.",
"is_bot": false,
"headline": "Polish docs site: soften hero glow, lock npm deps, fix stale path",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-05-04T00:41:39Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "73fec29e6d2f4d62bf6738a73c5689147f110a76",
"body": "Was: today (since midnight) vs full-day yesterday → at 00:01am every\ndashboard showed -100% because today had barely started while yesterday\nwas a full day's activity. Same noise on the active-users tile.\n\nNow: yesterday's window is yesterday-midnight through yesterday at\n`now()`'s H:i:s — apples to apples through the day. At 9am we compare\n9 hours vs 9 hours. The midnight cliff is gone.",
"is_bot": false,
"headline": "Compare today vs yesterday up to the SAME time-of-day",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-05-04T00:32:42Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "df3451af398f389df3d3b21215a4934395ac79a7",
"body": "Mirrors the Queue admin layout: brand on the left, cross-link button in\nthe middle (when present), server-time clock on the far right.",
"is_bot": false,
"headline": "Reorder header so server clock is the last element",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-05-04T00:29:35Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "7e9d306807dbda32e2e46c0011f1189be3f0de1e",
"body": null,
"is_bot": false,
"headline": "Delete .phive/phars.xml",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-04T00:22:35Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "53a3c82ecb92da9a1f04f87b1b53b14f41fcd241",
"body": null,
"is_bot": false,
"headline": "Add CakePHP version map note to README",
"author_name": "Mark",
"author_login": null,
"committed_at": "2026-05-04T00:15:37Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "42ec849d0eee99d620d710c81a6f249a970a6dee",
"body": "Convert __() / __n() calls in src/ and templates/ to __d('audit_stash', ...) /\n__dn('audit_stash', ...) so user-facing strings live in their own translation\ndomain instead of leaking into the host app's default domain.\n\nAdd resources/locales/audit_stash.pot generated via cake i18n extract so\ntranslators have a stable POT to base language files on.",
"is_bot": false,
"headline": "Use plugin-specific i18n domain for translations (#62)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-03T23:58:10Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "e7665b972e1560661ed3c24e25b26cd32a1bcd7e",
"body": "* Add server clock to admin header + debug-only Reset button\n\n- Header: a server-time clock (\\`date('Y-m-d H:i:s')\\`) sits between the\n brand and the Bouncer cross-link, mirroring the Queue admin header.\n- Sidebar: a \"Debug\" section appears when Configure::read('debug') is\n truthy with a Reset (tr\n[…]\nng new\n AuditStashController::reset() action TRUNCATEs audit_logs, refusing\n to run unless debug is on so a misconfigured route can't wipe\n production logs.\n\n* phpcs: add missing @throws on reset()",
"is_bot": false,
"headline": "Add admin header clock + debug-only Reset (truncate) button (#61)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-03T23:29:36Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "610793210834b82772b10d904dae57741f33c476",
"body": "Single source of truth for the index / export filter set. Previously\nthe controller had its own private applyBaseFilters helper plus four\ninlined finder calls, and the export action only ran the base filters\nhalf — so changed_field, field_name+value, and bulk_filter were\nsilently dropped from any ex\n[…]\nhan the preview the user just confirmed.\n\nThe new findForFilters finder is documented as the entry point and\ncalls the existing single-purpose finders internally; their public\ncontracts are unchanged.",
"is_bot": false,
"headline": "Move audit-log filter logic into AuditLogsTable::findForFilters",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-05-03T21:06:40Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "6b19cbb4c67d87b1122379c7379dd9afb4a7a8e3",
"body": "The export form silently injected a 30-day created-at floor whenever\ndate_from / date_to were empty. Result: a user filtering the index by\nsource / primary_key / transaction_key sees N rows, clicks Export, and\nthe form reports \"nothing to export\" because the rows are older than 30\ndays. The floor ex\n[…]\nlower\nevery month — when the caller has already pinned the query, it stops\nbeing load-protective and just hides rows the index shows. The hard\ncap remains the safety net for accidentally huge exports.",
"is_bot": false,
"headline": "Skip default 30-day export floor when other narrowing filters are set",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-05-03T20:54:58Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "e2b60bac59900150269c51ede2e053d9bfa83d85",
"body": "Match the cakephp-queue posture: the plugin no longer serves any admin\naction unless `AuditStash.adminAccess` is explicitly set to a Closure.\nA missing config key, a non-Closure value, a Closure that returns\nanything other than literal `true`, or a Closure that throws all yield\na 403.\n\nAudit logs co\n[…]\nfor users who want to delegate fully to host AppController\nauth: pass `'adminAccess' => fn() => true`. That's an explicit \"I trust\nthe upstream guard\" choice rather than an accidental forgotten guard.",
"is_bot": false,
"headline": "Make AuditStash.adminAccess required (deny-by-default)",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-05-03T20:17:42Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "339da30d164eb9917c0d291ed94b7f96b7bc9071",
"body": "Followup to the plugin-prefix discussion (#60): docs/viewer.md gains\na copy-pasteable callable that resolves both `Plugin.Table` and\n`Table` source forms to the CakePHP-conventional Admin-prefix URL\nwithout needing a per-table mapping. Cross-links to the usage.md\nsection that explains why the source column can be either form.\n\nPure docs, no code change.",
"is_bot": false,
"headline": "Document a plugin-aware linkRecord callable",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-03T20:05:07Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "858c540b4fb5475737963655ba1546eea6476903",
"body": "The coverage report row for a plugin table (e.g. `Comments.Comments`)\nwas correctly counting events under both the dotted and short alias\n(the AuditLogBehavior persists `getRegistryAlias()`, which can be\neither form depending on how the table was loaded), but the 'View\nactivity' link was always buil\n[…]\nource), and the messy both-aliases-\npopulated state where the dotted form wins and the short-alias rows\nsurface separately as 'empirical' so the operator can decide whether\nto backfill or delete them.",
"is_bot": false,
"headline": "Fix coverage report linking the wrong source for plugin tables (#60)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-03T19:57:51Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "9e87edf0c931d42c2c6ac88a8388ca0eafd5b6f1",
"body": "When no `filter` query param is set, the controller hands the template\nan empty string for `$filter`. The chip closure compared it directly to\nthe chip key, so the All chip — whose key is the literal string 'all' —\nnever matched and never rendered as active. The page looked like\n\"nothing is selected\" even though All was the implicit current view.\n\nTreat an empty `$filter` as the All state in the closure so the\nimplicit-default and the click-target stay in sync.",
"is_bot": false,
"headline": "Highlight the All chip on /admin/audit-stash/coverage",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-05-03T19:52:08Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "2689e52da955b336f39d962d85c9c99bc7893155",
"body": "A handful of cosmetic + dead-code issues found while sweeping the admin\ntemplates for custom-event handling.\n\nStale entity property references (column rename never propagated):\n- revert_preview.php and restore.php both branched on `->user`, which\n isn't a column on AuditLog (it was renamed to user_\n[…]\n a CRUD diff.\n\nOptional fields in the email alert (HTML + text):\n- \"Primary Key:\" row is hidden for custom events that aren't scoped\n to a record, instead of showing an empty value next to the label.",
"is_bot": false,
"headline": "Polish admin UI: stale ->user refs, button styles, empty payload copy",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-05-03T19:50:13Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "69d4fa579d5e9fbd9f269ae52c37b55b1e836ce9",
"body": "Followup to the export-page PR. Three nav entry points still pointed\nat the legacy direct-stream URLs and bypassed the new form (with its\nrow-count estimate, format picker, and hard-cap pre-flight):\n\n- templates/element/AuditStash/sidebar.php had separate 'Export CSV'\n and 'Export JSON' entries hit\n[…]\nrent view would\nexport, just with one extra (worthwhile) click to see the count.\n\nStreaming CSV / JSON / NDJSON download URLs themselves are unchanged\nand still work for direct linking / scripted use.",
"is_bot": false,
"headline": "Update sidebar / mobile-nav / index export links to the form page (#59)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-03T19:43:55Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "4e0ca1424abb1ff7dae5e1436b8630de0dfe1ece",
"body": "…#58)\n\nThe previous export action was an inline 'Export CSV' / 'Export JSON'\nbutton on the index page that hard-coded LIMIT 10000, buffered the\nentire result set with toArray(), and built the response body in\nmemory (php://temp + stream_get_contents for CSV; json_encode the\nfull array for JSON). At \n[…]\nphp gets a new 'export' block documenting the\nthree knobs (hardCap, defaultDays, batchSize). Routes now register\nndjson alongside csv / json extensions.\n\nphpunit: 364 (was 351), phpstan + phpcs clean.",
"is_bot": false,
"headline": "Move export to its own page, stream the output, cap result-set size (…",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-03T19:40:19Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "7293c8b2aca96a7535371e67d7a93c09ad1013eb",
"body": "Custom events emitted via Audit::log() without a primaryKey land in\naudit_logs with primary_key NULL. The index template still rendered the\nTimeline and Related action buttons for those rows, but both controller\nactions hard-require source AND primary_key and redirect with a flash\nerror otherwise — \n[…]\nrd-scoped rows. Same fix applied to the View Timeline\nbutton on the entry detail page.\n\nThe View action and the index \"New\" badge already handled the null\ncase correctly, so no further changes needed.",
"is_bot": false,
"headline": "Hide Timeline / Related buttons for primary-key-less rows",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-05-03T19:37:59Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "4df000227a0bcf64d9f2f9f71919c68b8b34ff39",
"body": "* Add native Slack, Teams and Discord notification channels\n\nAdds three platform-native chat channels so users no longer have to\nwrite a custom Channel class to get a readable Slack/Teams/Discord\nmessage — drop in 'class' => SlackChannel::class and the alert renders\nwith the platform's expected card\n[…]\nrnsFalseAndLogsWhenPayloadCannotBeJsonEncoded\n (uses an invalid UTF-8 payload + a stub PSR-3 logger to assert the\n channel doesn't issue an HTTP request and emits an \"encoding failed\"\n log record).",
"is_bot": false,
"headline": "Add native Slack and Discord notification channels (#56)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-03T19:24:12Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "01d75f387528bbbfb067a3b2a60cbc3a1a8bef4f",
"body": "* Add lifecycle event hooks to AuditMonitor\n\nChannels are the happy-path delivery mechanism, but they are a closed\nset — anything beyond the bundled platforms (Mattermost, PagerDuty,\ncustom incident store, per-context suppression, alert mutation,\nforwarding rule exceptions to Sentry) currently requi\n[…]\nDocs reflect the simpler shape: one paragraph explaining the\nlog-based rule-failure path with a Sentry config snippet, instead of\na fourth event recipe.\n\nphpunit: 345 (was 343), phpstan + phpcs clean.",
"is_bot": false,
"headline": "Add lifecycle event hooks to AuditMonitor (#57)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-03T19:05:29Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "192a58c63ddb70a4413d5639153e01cdd2c4e4c0",
"body": null,
"is_bot": false,
"headline": "Cleanup.",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-05-03T18:31:57Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "d32d9b9ab695e8c441b311df307af96e1a90d4b1",
"body": "* Add forensic capture, sensitive-field rule, test assertions trait\n\nThree independent observability additions, all opt-in / additive — no\nexisting call sites change behavior.\n\nEnvironmentMetadata: new `capture` constructor argument lets users\nopt into request-derived meta fields (`user_agent`, `ref\n[…]\nthe\nevent-hook path needs a persister decorator because BaseEvent has no\npublic setter for changed/original. Also points at the existing\n'sensitive' behavior config for the related PII-redaction case.",
"is_bot": false,
"headline": "Add forensic capture, sensitive-field rule, test assertions trait (#55)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-03T18:08:37Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "225bcd33d19023bf51d73144469bad528e2b56fc",
"body": "* Add admin dashboard and coverage report\n\nThe plugin now ships an at-a-glance dashboard at the Admin root and a\ncoverage report that surfaces which Tables have AuditLog behavior\nattached vs which don't.\n\nDashboard (/admin/audit-stash, configurable via AuditStash.routePath):\n- KPI cards: events toda\n[…]\nash.coverage.hidePlugins` and `AuditStash.coverage.hideTables`\n documented with default empty arrays + behavior notes.\n\n* Coverage legend: collapsible details with definition list\n\n* Plugin excludes.",
"is_bot": false,
"headline": "Add admin dashboard and coverage report (#54)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-03T17:44:36Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "569d19bde82286cc5ae1ed7466ef6243c6537db5",
"body": "README + viewer.md: RequestMetadata constructor takes userId / userDisplay,\nnot user. Removed an orphaned 'Compound User Format' section that\ndescribed an id:displayName string trick and a userSeparator config —\nneither exists in src/, both were left over from before the user_id /\nuser_display split\n[…]\n but nothing in src\nactually read it — setting persisterConfig.hashChain silently did\nnothing. Now applied via setConfig() on the lazily-built persister\nwhen the global config is an array. Test added.",
"is_bot": false,
"headline": "Fix doc bugs flagged in correctness audit (#53)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-03T16:19:50Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "2c871be2b1df5d304fe432e055103141f0a434a2",
"body": "* Add custom action event support\n\nLets applications log audit events that don't map to entity CRUD —\n\"user.login\", \"report.exported\", \"permission.granted\", etc. — through\na static facade that flows through the existing persister, hash chain,\nand viewer.\n\nPublic API:\n\n use AuditStash\\Audit;\n\n \n[…]\nleft out: VARCHAR(64) is a TablePersister schema\ndetail, not a property of the event, and the Elasticsearch\npersister has no such limit. The DB driver still raises a clear\n'Data too long' on overflow.",
"is_bot": false,
"headline": "Add custom action event support (#48)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-03T16:01:10Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "d13d375150f41e6144d70567e5056a5b97e900de",
"body": null,
"is_bot": false,
"headline": "Delete config/Migrations/schema-dump-default.lock",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-03T15:45:06Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "259e9974491d8499ed1d1d5a2d76b171749ed7b8",
"body": "PR #46 switched the four Paginator nav links (first/prev/next/last) from\nescape => false to escapeTitle => false. PaginatorHelper only honors the\nescape option — escapeTitle is silently ignored — so the title fell back\nto the default escaped path and the FontAwesome <i> tags rendered as\nliteral HTML\n[…]\nthose four to escape => false. PaginatorHelper does not have\nan escapeTitle option (verified in cakephp/cakephp PaginatorHelper:\nprev/next/first/last/sort all use escape, with no narrower equivalent).",
"is_bot": false,
"headline": "Fix pagination icons rendering as raw text in admin (#52)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-03T15:43:49Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "62afd538631f8ca952fa4e105f1585b91149ead1",
"body": "When the table's displayField points at a column that resolves to a\nbacked enum, unit enum, or any Stringable value object, casting the\nentity value to string would throw 'Object of class ... could not be\nconverted to string' from afterSave/afterDelete (and the cascade\ndelete branch).\n\nBacked enums \n[…]\n scalar value, unit enums to their\ncase name, Stringable instances via __toString. Anything else that\nisn't scalar yields null so the audit row still gets written instead\nof blowing up the whole save.",
"is_bot": false,
"headline": "Convert enum/Stringable display field values for audit log (#51)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-03T15:28:36Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a8fd84c12623c8b5e50a35e49ef7811add694f66",
"body": "…ttack (#47)\n\nCloses four injection / unsafe-deserialization surfaces in the admin namespace.\n\nLIKE wildcard / DoS via user_id filter\n AuditLogsController builds a LIKE %value% clause from the user_id query\n string in three sites (index, applyBaseFilters, export). Without escaping,\n a value conta\n[…]\nes\n hook for subclasses that need to whitelist additional types.\n\nTests: six regression cases across AuditLogsControllerTest and\nAuditLogsTableTest; suite remains green at 289 tests / 892 assertions.",
"is_bot": false,
"headline": "Harden admin filters against LIKE/JSON/ES injection and unserialize a…",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-05-02T14:04:30Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "0f283e6e937fc758ad88f294897b45f809b02cbc",
"body": "Using 'escape' => false also disables HTML escaping of attributes (URL, classes, title), not just the title text. The narrower 'escapeTitle' => false keeps attribute escaping on while still allowing the icon HTML in the title.",
"is_bot": false,
"headline": "Use escapeTitle instead of escape in pagination element (#46)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-04-30T23:09:35Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "754bda68f9d7e828a180e65ad0f5312420e35594",
"body": null,
"is_bot": false,
"headline": "Document AuditStash.accessCheck in app.example.php",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-04-30T15:49:32Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "b562574daeeec91ee20e62e59bab46084cada027",
"body": "Optional gate for the /admin/audit-logs viewer. Set\nAuditStash.accessCheck to a Closure that receives the current request\nand returns literal true to grant access; anything else (non-Closure,\nreturns false, returns a truthy non-bool, throws) yields a 403.\n\nUnset = no-op — the host AppController's au\n[…]\n via Cake\\Log\\Log; ForbiddenException raised\ninside the Closure is respected as-is so callers can short-circuit\nwith their own message.\n\n8 new test cases in AuditLogsControllerTest cover all branches.",
"is_bot": false,
"headline": "Add AuditStash.accessCheck Closure gate (opt-in defense-in-depth) (#45)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-04-30T13:44:12Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "c6515d04ab78bac46c253e30b8ad1cf67a5507c1",
"body": null,
"is_bot": false,
"headline": "cs badge.",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-04-28T15:45:16Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "67e4224b58cea487f9234cb6796557ffdea122b1",
"body": "* Capture audit event timestamps with microsecond precision\n\nThe BaseEvent constructor formatted the timestamp with DateTime::ATOM,\nwhich truncates at seconds. Persisters that re-parsed the string into\na DateTime therefore lost any sub-second resolution before persistence,\neven on backends (Elastics\n[…]\nmp() is unchanged, and the\nserialized representation remains a string, so existing implementors,\nqueues, and persisters keep working.\n\n* Document opting into sub-second precision on the created column",
"is_bot": false,
"headline": "Capture audit event timestamps with microsecond precision (#44)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-04-27T13:02:18Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "6e8284ee2e42a45f9810db8736b72a81fb2a6685",
"body": "- Add nonce attribute to 2 inline <script> blocks (layout + revert_preview)\n- Move Form->button 'confirm' option to form-level data-confirm-message\n on the revert form to avoid the inline onclick that CSP would block\n- Update layout delegate to use form[data-confirm-message] (the\n postButton-compatible hook) instead of the legacy form[data-confirm]\n\nThis lets the admin UI work under a strict script-src CSP\n(e.g. \"script-src 'self' 'nonce-X'\") without needing\n'unsafe-inline' or 'unsafe-hashes'.",
"is_bot": false,
"headline": "Refactor admin UI to be CSP-compatible (#42)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-04-22T23:51:27Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "dfeefb569f12ca1ed29c2fd9feb5e17d7f49f849",
"body": "The transaction -> transaction_key change was applied both retroactively\nto the original CreateAuditLogs migration and as a new rename migration.\nThat created a schema divergence: installs created before this change\nran the rename, while fresh installs would have the final column name\nalready and fa\n[…]\nAuditLogs to its original shipped form (transaction\ncolumn + index) so the RenameTransactionToTransactionKey migration is\nthe single source of truth for the rename on both fresh and existing\ninstalls.",
"is_bot": false,
"headline": "Revert retroactive schema edit to CreateAuditLogs (#41)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-04-17T16:35:33Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "e7ddf6cd2a7c8b7f1ac376bcfc3c3625c4979478",
"body": "* Add optional SHA-256 hash chain for tamper-evident audit logs\n\nIntroduces an opt-in hash chain over persisted audit rows, giving\nAuditStash the integrity guarantees required for GoBD (DE), SOX (US),\nHIPAA, and similar regulated environments. Disabled by default —\nexisting installs see no change un\n[…]\n- Document hashChain persister option in config/app.example.php\n\n* Fix CS violation in app.example.php\n\nRemove fully qualified class reference to avoid\nSlevomatCodingStandard violation in config file.",
"is_bot": false,
"headline": "Optional SHA-256 hash chain for tamper-evident audit logs (#38)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-04-16T02:50:37Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "d915cd2db1a44c5746c475f3e90df67eca700d32",
"body": "The column name \"transaction\" is a SQL reserved keyword which causes\nsyntax errors on SQLite and can be problematic on other database engines.\nRenaming to \"transaction_key\" avoids the reserved word conflict entirely.\n\nIncludes a migration for existing installations to rename the column.",
"is_bot": false,
"headline": "Rename transaction column to transaction_key (#39)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-04-16T02:44:00Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "b1496f4f96808af6a1e441d6d4de46f9eef0a7e5",
"body": null,
"is_bot": false,
"headline": "Fix pre element overflow in standalone layout",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-04-02T14:57:21Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "c8d2d101101d538a1ccdd68bb8e2ee98d44f06d1",
"body": null,
"is_bot": false,
"headline": "Add standalone-ui keyword.",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-03-28T16:11:20Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "9ef1a726a051054b194947f6e794f65187ea94ad",
"body": "Sets explicit Bootstrap 5 templates to avoid app templates affecting\nthe standalone admin layout.",
"is_bot": false,
"headline": "Add standalone pagination element to prevent style leakage.",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-03-28T14:11:43Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "62bad139d18cfeb15c05ace529a419fdc63144e9",
"body": "Extract flash rendering logic into flash/flash.php element for cleaner\nlayout code and easier maintenance.",
"is_bot": false,
"headline": "Refactor flash rendering to use dedicated element.",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-03-28T13:45:39Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "d59e0c969fa9cb79d189356b69a47d5105a95225",
"body": "Use inline flash rendering with consume() to avoid style leakage from\napp layouts and ensure messages are properly consumed from session.",
"is_bot": false,
"headline": "Fix flash message rendering in standalone layout.",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-03-28T13:40:11Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "a152684a9c1e471e3a4abec265b7e3a7b93d5b75",
"body": "Instead of hardcoding 'plugin' => 'AuditStash' and 'prefix' => 'Admin',\nretrieve these values from the current request. This allows the admin\nlayout to work with custom prefixes or no prefix at all.\n\nFixes #36",
"is_bot": false,
"headline": "Use dynamic plugin and prefix in admin layout templates (#37)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-03-22T16:48:10Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "24d0264bf05dc30380da8a60284cdb4202b12e47",
"body": "* Add self-contained Bootstrap 5 admin layout and template elements\n\nSimilar to cakephp-queue and cakephp-queue-scheduler, this adds proper own\ntemplating with a self-contained admin interface that doesn't depend on\nthe host application's styling.\n\nChanges:\n- Add templates/layout/audit_stash.php - i\n[…]\nckr initialization\n- Fix date range queries to include full day (00:00:00 to 23:59:59)\n- Update Flatpickr to target .datepicker class\n\n* Disable autocomplete on date inputs to prevent picker conflicts",
"is_bot": false,
"headline": "Add self-contained Bootstrap 5 admin layout and template elements (#35)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-03-19T05:44:25Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "af6ca01fcfcfd3585164ca1bdea08da9dbdde964",
"body": "Date comparisons now include time component:\n- date_from: appends ' 00:00:00' for start of day\n- date_to: appends ' 23:59:59' for end of day\n\nThis ensures records created during the day are properly included\nin date range queries.",
"is_bot": false,
"headline": "Fix date range filtering to include full day",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-03-19T05:19:10Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "48591baa25c1ee4960c67b741f44b06f53feb1ff",
"body": "…template\n\nSame issue as timeline.php - these fields may already be arrays due to\nthe JSON column type configuration in AuditLogsTable.",
"is_bot": false,
"headline": "Fix additional json_decode() TypeErrors in RevertService and restore …",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-03-19T05:06:40Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "dc4aadf21796f259747dae8bf9d1b947dde22fa7",
"body": "The AuditLogsTable sets 'json' column types for changed, original, and\nmeta fields, so CakePHP automatically decodes them. The template was\ncalling json_decode() on already-decoded arrays, causing TypeErrors.\n\nFixes the issue by checking if the value is a string before decoding.",
"is_bot": false,
"headline": "Fix json_decode() TypeError when entity fields are already decoded",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-03-19T05:03:43Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "6a41404293f6505cd6abf997eb724593aa4515f1",
"body": "- Update AuditLog entity PHPDoc to reflect reality: these JSON fields\n can be either string (from database) or array (when set programmatically)\n- Add is_string() checks before json_decode() in all affected locations\n- Fixes test failures caused by previous commit",
"is_bot": false,
"headline": "Fix entity types: original/changed/meta can be string or array",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-03-19T04:51:02Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "206d312ba104e6b5a1aa393003b89802d7548640",
"body": "- Remove unused AuditLog imports (FQCN used in annotations instead)\n- Move @var annotations to variable assignment line per convention\n- Use array<\\FQCN> syntax for proper type hints",
"is_bot": false,
"headline": "Fix phpcs: use FQCN in @var annotations, move to variable assignment",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-03-19T04:47:49Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "4ce3058f315df4d6b39f71632238783616f8b81b",
"body": "- Add AuditLog entity import and @var annotations before foreach loops\n to resolve property access errors on array|EntityInterface union types\n- Remove redundant is_string() check in AuditLogsTable (always true after null check)\n- Add null checks for $log->changed in StateReconstructorService",
"is_bot": false,
"headline": "Fix PHPStan errors with entity type annotations",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-03-19T04:44:39Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "394de8a618c617177bd0bbf85e31908a863e7937",
"body": "Add both data-toggle (BS4) and data-bs-toggle (BS5) attributes\nto support both Bootstrap versions.",
"is_bot": false,
"headline": "Fix collapse toggle for Bootstrap 4 compatibility.",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-03-19T04:31:09Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "19487d175c0d05f387459ccdee3a8516762b679f",
"body": "* Add GDPR compliance helpers (anonymize/delete user audit logs)\n\n- Add GdprService with methods: anonymize, delete, export, findByUser, getStats\n- Add GdprCommand (bin/cake audit_stash gdpr) with actions:\n - stats: View user's audit log statistics\n - anonymize: Anonymize PII while preserving audi\n[…]\neduce memory usage for users with large audit histories\n- Fix table name casing in docs to match CakePHP registry aliases\n (PascalCase: Articles, Comments, Users)\n\n* Fix PHPStan errors in GdprService",
"is_bot": false,
"headline": "Add GDPR compliance helpers (anonymize/delete user audit logs) (#34)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-03-18T21:37:25Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "698b6bc88f24a58cfbcf4576af9ce139517b3ad0",
"body": "…ssions (#33)\n\nFirefox sends \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\"\nas Accept header for form submissions. The application/xml part was incorrectly\ntriggering API detection.\n\nNow only considers application/xml as API request when text/html and\napplication/xhtml+xml are NOT present in the Accept header.\n\nFixes #32",
"is_bot": false,
"headline": "Fix request source incorrectly detected as API for Firefox form submi…",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-03-16T07:26:12Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "28aa003a3bb92c19f2f25bff59d3a58181f17fb8",
"body": null,
"is_bot": false,
"headline": "Enable phpcs cache for faster subsequent runs",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-03-15T15:36:25Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "dd3db2af6ee2edad2085e3b0491dbf1140350e67",
"body": "The JSON column types cause automatic decoding to arrays.\nExport methods now handle both cases:\n- CSV: encode arrays to JSON strings\n- JSON: use arrays directly or decode strings",
"is_bot": false,
"headline": "Fix export methods to handle both array and string JSON values (#31)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-03-14T03:55:57Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "709573c202d7c5d2f3f615d0a326877fe5b2c90e",
"body": "* Add CleanupCommand for retention policy enforcement\n\nImplements configurable audit log cleanup with:\n- Default retention period (configurable via AuditStash.retention.default)\n- Per-table retention periods (AuditStash.retention.tables.{name})\n- Command line override with --retention option\n- Table\n[…]\nest for global cleanup with per-table config warning\n\n* Fix --dry-run short flag and retention exception message (#30)\n\n* Initial plan\n\n* Fix --dry-run short flag and exception message trailing period",
"is_bot": false,
"headline": "Add CleanupCommand for retention policy enforcement (#25)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-03-13T18:31:32Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "feeb3e28224c49de565944c92892962df4787b10",
"body": "* Add EnvironmentMetadata documentation and clarify retention policies",
"is_bot": false,
"headline": "Add EnvironmentMetadata documentation (#29)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-03-13T16:16:32Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "f4874d61db6922f24e7c89a4894bf4014c9582da",
"body": "* Add change filtering to ignore insignificant changes\n\nAdds configurable filters to skip logging changes that are not significant:\n- ignoreEmpty: Skip if no changes after filtering (default: true)\n- ignoreTimestampOnly: Skip if only timestamp fields changed (default: false)\n- ignoreFields: Skip if \n[…]\nlast_seen', 'login_count'],\n 'ignoreWhitespace' => true,\n ]);\n\nThe ChangeFilter class can also be used independently for custom filtering logic.\n\n* Add documentation for change filtering options",
"is_bot": false,
"headline": "Add change filtering to ignore insignificant changes (#27)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-03-13T16:11:15Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "9b86adf3f42ab691da189f1122075bc2d771e6a5",
"body": "- Replace bulk_only checkbox with bulk_filter select dropdown\n- Add findNonBulkChanges finder to filter for non-bulk changes\n- Update controller to handle 'yes', 'no', and empty filter values\n- Add test for findNonBulkChanges finder\n\nThis allows users to filter for:\n- All changes (default)\n- Bulk changes only (transactions with 5+ records)\n- Non-bulk changes only (excluding bulk transactions)",
"is_bot": false,
"headline": "Change bulk filter from checkbox to select with yes/no/all options (#28)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-03-13T16:10:44Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "a9e40f53ea6a22f217248af8ffae971405f43b9b",
"body": "Adds a new metadata collector that enriches audit logs with:\n- request_source: 'web', 'cli', 'api', or custom (e.g., 'queue')\n- Optional extra metadata (server name, deployment, etc.)\n\nAuto-detection:\n- CLI: Detected via PHP_SAPI\n- API: Detected via Accept/Content-Type headers, /api/* URL, or auth h\n[…]\nr::instance()->on(new EnvironmentMetadata());\n EventManager::instance()->on(new EnvironmentMetadata('queue'));\n EventManager::instance()->on(new EnvironmentMetadata(null, ['server' => hostname()]));",
"is_bot": false,
"headline": "Add EnvironmentMetadata for request source tracking (#26)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-03-13T15:56:19Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "23003f4ebdb8459be07a0fab5d1f86d4a357bd58",
"body": null,
"is_bot": false,
"headline": "Make advanced filter layout consistent - help text above inputs",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-03-13T15:30:58Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "f9797c7eea17acdbf4e0f406b841d914a04647ce",
"body": null,
"is_bot": false,
"headline": "Remove duplicate help text from changed_field filter",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-03-13T15:30:08Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "8500c123500aa2f02fb6b33215dd75fb517251b5",
"body": "- Move basic filters (source, type, user, dates, etc.) to top\n- Add collapsible \"Advanced Filters\" section for field-level queries\n- Group Field Name + Field Value together with help text clarifying\n both are required for value search\n- Add descriptive help text for each advanced filter\n- Auto-expand advanced section when those filters are active",
"is_bot": false,
"headline": "Improve filter UI with collapsible advanced filters section",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-03-13T15:28:51Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "b1f52a2ea51962d7cc81d7bcf545a065edd61504",
"body": "The JSON_CONTAINS_PATH function returns NULL for NULL columns,\nwhich doesn't filter records properly. Explicitly compare to 1\nto ensure only records with the key present are returned.",
"is_bot": false,
"headline": "Fix MySQL JSON_CONTAINS_PATH filter returning all records",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-03-13T15:26:11Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "31c150ea64236dd7b7a32bbe3f22e597ed51c94b",
"body": "* Add advanced filtering and querying features for audit log viewer\n\n- Add JsonQueryHelper for DB-agnostic JSON queries (MySQL/PostgreSQL)\n- Add custom finders: findByChangedField, findByChangedFieldValue,\n findRelatedChanges, findBulkChanges, findBulkChangeStats\n- Update index with filters for cha\n[…]\nneed to bind\n the comparison value with the correct type (integer vs string)\n- Simplify bulk stats test assertions to check key existence\n rather than values, since MAX() return type handling varies",
"is_bot": false,
"headline": "Add advanced filtering and querying features for audit log viewer (#24)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-03-13T15:23:14Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "ab45aa6380f7b914e9169f50ab9a9cd21ceae81c",
"body": "The Table schema defines JSON columns which CakePHP auto-decodes to arrays.\nHowever, the helper methods expected raw JSON strings.\n\nUpdated methods to accept `array|string|null`:\n- changeSummary()\n- diff()\n- diffInline()\n- metadata()\n- fieldValuesTable()\n\nFixes #22",
"is_bot": false,
"headline": "Fix AuditHelper methods to accept both array and string (#23)",
"author_name": "Mark Scherer",
"author_login": "dereuromark",
"committed_at": "2026-03-13T15:06:30Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "4dee54caf56c409f823ae783c84d004abe806118",
"body": null,
"is_bot": false,
"headline": "Improve composer.json metadata",
"author_name": "mscherer",
"author_login": "dereuromark",
"committed_at": "2026-03-13T14:51:21Z",
"body_truncated": false,
"is_coding_agent": false
}
],
"releases_count": 26,
"commits_last_year": 194,
"latest_release_at": "2026-06-18T15:47:13Z",
"latest_release_tag": "2.1.0",
"releases_from_tags": false,
"days_since_last_push": 29,
"active_weeks_last_year": 27,
"days_since_latest_release": 39,
"mean_days_between_releases": 9.1
},
"community": {
"has_readme": true,
"has_license": true,
"has_description": true,
"has_contributing": true,
"health_percentage": 57,
"has_issue_template": false,
"has_code_of_conduct": false,
"has_pull_request_template": false
},
"ecosystem": {
"packages": [
{
"name": "dereuromark/cakephp-audit-stash",
"exists": true,
"license": "MIT",
"keywords": [
"logging",
"plugin",
"cakephp",
"Audit",
"admin-panel",
"tracking",
"history",
"standalone-ui"
],
"ecosystem": "packagist",
"matches_repo": true,
"registry_url": "https://packagist.org/packages/dereuromark/cakephp-audit-stash",
"is_deprecated": false,
"latest_version": "2.1.0",
"repository_url": "https://github.com/dereuromark/cakephp-audit-stash",
"versions_count": 26,
"total_downloads": 28731,
"dependents_count": 4,
"deprecation_note": null,
"maintainers_count": null,
"monthly_downloads": 6814,
"first_published_at": null,
"latest_published_at": "2026-06-18T15:33:51Z",
"latest_version_yanked": null,
"days_since_latest_publish": 39
}
]
},
"popularity": {
"forks": 2,
"stars": 8,
"watchers": 1,
"fork_history": {
"days": [
{
"date": "2026-04-17",
"count": 1
},
{
"date": "2026-05-16",
"count": 1
}
],
"complete": true,
"collected": 2,
"total_forks": 2
},
"star_history": null,
"open_issues_and_prs": 1
},
"ai_readiness": {
"has_nix": false,
"example_dirs": [],
"has_llms_txt": false,
"has_dockerfile": false,
"has_mcp_signal": false,
"bootstrap_files": [],
"api_schema_files": [],
"has_devcontainer": false,
"typecheck_configs": [],
"toolchain_manifests": [],
"largest_source_bytes": 40814,
"source_files_sampled": 157,
"oversized_source_files": 0,
"agent_instruction_files": [],
"agent_instruction_max_bytes": null
},
"dependencies": {
"manifests": [
"composer.json",
"docs/package.json"
],
"advisories": {
"error": null,
"scope": null,
"source": null,
"findings": [],
"collected": false,
"malicious": [],
"truncated": false,
"by_severity": {},
"advisory_count": 0,
"affected_count": 0,
"assessed_count": 0,
"malicious_count": 0,
"assessed_package": null,
"unassessed_count": 0,
"direct_affected_count": 0
},
"ecosystems": [
"npm",
"packagist"
],
"dependencies": [
{
"name": "cakephp/orm",
"manifest": "composer.json",
"ecosystem": "packagist",
"version_constraint": "^5.3.0"
},
{
"name": "sebastian/diff",
"manifest": "composer.json",
"ecosystem": "packagist",
"version_constraint": "^6.0 || ^7.0 || ^8.0 || ^9.0"
}
],
"all_dependencies": {
"error": "GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository",
"source": null,
"packages": [],
"collected": false,
"truncated": false,
"total_count": null,
"direct_count": null,
"indirect_count": null
}
},
"maintainership": {
"issues": {
"open_prs": 0,
"merged_prs": 65,
"open_issues": 1,
"closed_ratio": 0.917,
"closed_issues": 11,
"closed_unmerged_prs": 3
},
"bus_factor": 1,
"bot_contributors": 0,
"top_contributors": [
{
"type": "User",
"login": "dereuromark",
"commits": 194,
"avatar_url": "https://avatars.githubusercontent.com/u/39854?v=4"
},
{
"type": "User",
"login": "lorenzo",
"commits": 107,
"avatar_url": "https://avatars.githubusercontent.com/u/37621?v=4"
},
{
"type": "User",
"login": "cnizzardini",
"commits": 11,
"avatar_url": "https://avatars.githubusercontent.com/u/171294?v=4"
},
{
"type": "User",
"login": "jippi",
"commits": 9,
"avatar_url": "https://avatars.githubusercontent.com/u/22841?v=4"
},
{
"type": "User",
"login": "nicosp",
"commits": 8,
"avatar_url": "https://avatars.githubusercontent.com/u/4110066?v=4"
},
{
"type": "User",
"login": "bravo-kernel",
"commits": 6,
"avatar_url": "https://avatars.githubusercontent.com/u/230500?v=4"
},
{
"type": "User",
"login": "swiffer",
"commits": 6,
"avatar_url": "https://avatars.githubusercontent.com/u/2990373?v=4"
},
{
"type": "User",
"login": "amayer5125",
"commits": 3,
"avatar_url": "https://avatars.githubusercontent.com/u/3212673?v=4"
},
{
"type": "User",
"login": "aceat64",
"commits": 2,
"avatar_url": "https://avatars.githubusercontent.com/u/348675?v=4"
},
{
"type": "User",
"login": "blamh",
"commits": 1,
"avatar_url": "https://avatars.githubusercontent.com/u/114339?v=4"
}
],
"contributors_sampled": 18,
"top_contributor_share": 0.546
},
"quality_signals": {
"has_ci": true,
"has_tests": true,
"ci_workflows": [
"ci.yml",
"deploy-docs.yml"
],
"has_docs_dir": true,
"linter_configs": [
"phpcs.xml",
"phpstan.neon"
],
"has_editorconfig": true,
"has_linter_config": true,
"has_precommit_config": false
},
"security_signals": {
"lockfiles": [
"package-lock.json"
],
"scorecard": {
"checks": [
{
"name": "Binary-Artifacts",
"score": 10,
"reason": "no binaries found in the repo",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#binary-artifacts"
},
{
"name": "Branch-Protection",
"score": 3,
"reason": "branch protection is not maximal on development and all release branches",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#branch-protection"
},
{
"name": "CI-Tests",
"score": 10,
"reason": "15 out of 15 merged PRs checked by a CI test -- score normalized to 10",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#ci-tests"
},
{
"name": "CII-Best-Practices",
"score": 0,
"reason": "no effort to earn an OpenSSF best practices badge detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#cii-best-practices"
},
{
"name": "Code-Review",
"score": 0,
"reason": "Found 2/29 approved changesets -- score normalized to 0",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review"
},
{
"name": "Contributors",
"score": 10,
"reason": "project has 11 contributing companies or organizations",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#contributors"
},
{
"name": "Dangerous-Workflow",
"score": 10,
"reason": "no dangerous workflow patterns detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dangerous-workflow"
},
{
"name": "Dependency-Update-Tool",
"score": 0,
"reason": "no update tool detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dependency-update-tool"
},
{
"name": "Fuzzing",
"score": 0,
"reason": "project is not fuzzed",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#fuzzing"
},
{
"name": "License",
"score": 10,
"reason": "license file detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#license"
},
{
"name": "Maintained",
"score": 10,
"reason": "30 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 10",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#maintained"
},
{
"name": "Packaging",
"score": null,
"reason": "packaging workflow not detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging"
},
{
"name": "Pinned-Dependencies",
"score": 1,
"reason": "dependency not pinned by hash detected -- score normalized to 1",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#pinned-dependencies"
},
{
"name": "SAST",
"score": 0,
"reason": "SAST tool is not run on all commits -- score normalized to 0",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#sast"
},
{
"name": "Security-Policy",
"score": 0,
"reason": "security policy file not detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#security-policy"
},
{
"name": "Signed-Releases",
"score": null,
"reason": "no releases found",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#signed-releases"
},
{
"name": "Token-Permissions",
"score": 0,
"reason": "detected GitHub workflow tokens with excessive permissions",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#token-permissions"
},
{
"name": "Vulnerabilities",
"score": 5,
"reason": "5 existing vulnerabilities detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
}
],
"commit": "86a1550a95a43d83c8252b62ceeda6f9f03c8288",
"ran_at": "2026-07-28T09:55:23Z",
"aggregate_score": 4.2,
"scorecard_version": "v5.5.0"
},
"has_codeql_workflow": false,
"has_security_policy": false,
"has_dependabot_config": false
},
"contribution_flow": {
"collected": true,
"ci_last_run_at": "2026-06-28T10:53:56Z",
"oldest_open_prs": [],
"last_merged_pr_at": "2026-06-28T10:52:51Z",
"ci_last_conclusion": "SUCCESS",
"oldest_open_issues": [
{
"number": 8,
"created_at": "2025-12-04T17:31:28Z",
"last_comment_at": null,
"last_comment_author": null
}
]
}
},
"config": {
"disabled_metrics": [],
"disabled_categories": [],
"disabled_components": {}
},
"source": {
"url": "https://github.com/dereuromark/cakephp-audit-stash",
"host": "github.com",
"name": "cakephp-audit-stash",
"owner": "dereuromark"
},
"metrics": {
"overall": {
"key": "overall",
"band": "moderate",
"name": "Overall health",
"note": null,
"notes": [],
"value": 69,
"inputs": {
"security": 42,
"vitality": 86,
"community": 44,
"governance": 70,
"engineering": 94
},
"components": []
},
"categories": [
{
"key": "vitality",
"band": "excellent",
"name": "Vitality",
"value": 86,
"weight": 0.22,
"metrics": [
{
"key": "development_activity",
"band": "good",
"name": "Development activity",
"note": null,
"notes": [],
"value": 76,
"inputs": {
"commits_last_year": 194,
"human_commit_share": 1,
"days_since_last_push": 29,
"active_weeks_last_year": 27
},
"components": [
{
"key": "push_recency",
"name": "Push recency",
"detail": "last push 29 days ago",
"points": 28.8,
"status": "partial",
"details": [
{
"code": "push_recency",
"params": {
"days": 29
}
}
],
"max_points": 36
},
{
"key": "commit_cadence",
"name": "Commit cadence",
"detail": "27/52 weeks with commits",
"points": 18.7,
"status": "partial",
"details": [
{
"code": "commit_cadence_weeks",
"params": {
"weeks": 27
}
}
],
"max_points": 36
},
{
"key": "commit_volume",
"name": "Commit volume",
"detail": "194 commits in the last year",
"points": 18,
"status": "met",
"details": [
{
"code": "commits_last_year",
"params": {
"count": 194
}
}
],
"max_points": 18
},
{
"key": "openssf_scorecard_maintained",
"name": "OpenSSF Scorecard: Maintained",
"detail": "30 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 10",
"points": 10,
"status": "met",
"details": [],
"max_points": 10
}
]
},
{
"key": "release_discipline",
"band": "excellent",
"name": "Release discipline",
"note": "Excluded from scoring (no data or not applicable): OpenSSF Scorecard: Signed-Releases. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"openssf_scorecard_signed_releases"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 100,
"inputs": {
"releases_count": 26,
"latest_release_tag": "2.1.0",
"releases_from_tags": false,
"days_since_latest_release": 39,
"mean_days_between_releases": 9.1
},
"components": [
{
"key": "ships_releases",
"name": "Ships releases",
"detail": "26 releases published",
"points": 27,
"status": "met",
"details": [
{
"code": "releases_published",
"params": {
"count": 26
}
}
],
"max_points": 27
},
{
"key": "release_recency",
"name": "Release recency",
"detail": "latest release 39 days ago",
"points": 36,
"status": "met",
"details": [
{
"code": "release_recency",
"params": {
"days": 39
}
}
],
"max_points": 36
},
{
"key": "release_cadence",
"name": "Release cadence",
"detail": "a release every ~9.1 days",
"points": 27,
"status": "met",
"details": [
{
"code": "release_cadence",
"params": {
"gap": 9.1
}
}
],
"max_points": 27
},
{
"key": "openssf_scorecard_signed_releases",
"name": "OpenSSF Scorecard: Signed-Releases",
"detail": "no releases found",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 10
}
]
},
{
"key": "abandonment",
"band": "excellent",
"name": "Abandonment",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"cap": null,
"state": "maintained",
"guards": [],
"signals": [],
"red_flag": false,
"multiplier_pct": 100,
"declared_reason": null,
"unverified_reason": null,
"unanswered_open_prs": null,
"unanswered_open_issues": null,
"days_since_last_merged_pr": null,
"days_since_last_human_commit": 29,
"days_since_last_human_commit_is_floor": false
},
"components": [
{
"key": "project_is_still_maintained",
"name": "Project is still maintained",
"detail": "last human commit 29 days ago",
"points": 100,
"status": "met",
"details": [
{
"code": "abandonment_maintained",
"params": {
"days": 29
}
}
],
"max_points": 100
}
]
}
],
"description": "Is the project alive — is code being written and are releases shipping?"
},
{
"key": "community",
"band": "at_risk",
"name": "Community & Adoption",
"value": 44,
"weight": 0.18,
"metrics": [
{
"key": "popularity",
"band": "critical",
"name": "Popularity & adoption",
"note": null,
"notes": [],
"value": 14,
"inputs": {
"forks": 2,
"stars": 8,
"watchers": 1,
"growth_state": "unverified",
"growth_factor_pct": 100,
"growth_unverified_reason": "no_history"
},
"components": [
{
"key": "stars",
"name": "Stars",
"detail": "8 stars",
"points": 13.7,
"status": "partial",
"details": [
{
"code": "stars",
"params": {
"count": 8
}
}
],
"max_points": 60
},
{
"key": "forks",
"name": "Forks",
"detail": "2 forks",
"points": 0,
"status": "missed",
"details": [
{
"code": "forks",
"params": {
"count": 2
}
}
],
"max_points": 25
},
{
"key": "watchers",
"name": "Watchers",
"detail": "1 watchers",
"points": 0,
"status": "missed",
"details": [
{
"code": "watchers",
"params": {
"count": 1
}
}
],
"max_points": 15
}
]
},
{
"key": "community_health",
"band": "good",
"name": "Community health",
"note": null,
"notes": [],
"value": 70,
"inputs": {
"has_readme": true,
"has_license": true,
"has_contributing": true,
"has_issue_template": false,
"has_code_of_conduct": false,
"has_pull_request_template": false
},
"components": [
{
"key": "readme",
"name": "README",
"detail": null,
"points": 22.5,
"status": "met",
"details": [],
"max_points": 22.5
},
{
"key": "license",
"name": "License",
"detail": "recognized license (MIT)",
"points": 22.5,
"status": "met",
"details": [
{
"code": "license_standard",
"params": {}
},
{
"code": "license_spdx",
"params": {
"spdx": "MIT"
}
}
],
"max_points": 22.5
},
{
"key": "contributing_guide",
"name": "CONTRIBUTING guide",
"detail": null,
"points": 18,
"status": "met",
"details": [],
"max_points": 18
},
{
"key": "code_of_conduct",
"name": "Code of conduct",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 13.5
},
{
"key": "issue_template",
"name": "Issue template",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.2
},
{
"key": "pr_template",
"name": "PR template",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 6.3
}
]
},
{
"key": "ecosystem_adoption",
"band": "moderate",
"name": "Ecosystem adoption (downloads)",
"note": null,
"notes": [],
"value": 56,
"inputs": {
"packages": [
"dereuromark/cakephp-audit-stash"
],
"dependents": 4,
"ecosystems": "packagist",
"total_downloads": 28731,
"monthly_downloads": 6814
},
"components": [
{
"key": "monthly_downloads",
"name": "Monthly downloads",
"detail": "6,814 downloads/month across packagist",
"points": 51.1,
"status": "partial",
"details": [
{
"code": "downloads_monthly",
"params": {
"count": 6814,
"ecosystems": "packagist"
}
}
],
"max_points": 80
},
{
"key": "registry_dependents",
"name": "Registry dependents",
"detail": "4 packages depend on it",
"points": 4.7,
"status": "partial",
"details": [
{
"code": "registry_dependents",
"params": {
"count": 4
}
}
],
"max_points": 20
}
]
}
],
"description": "Does the project have users, downloads, attention, and a welcoming setup for contributors?"
},
{
"key": "governance",
"band": "good",
"name": "Sustainability & Governance",
"value": 70,
"weight": 0.24,
"metrics": [
{
"key": "maintainer_resilience",
"band": "at_risk",
"name": "Maintainer resilience (bus factor)",
"note": null,
"notes": [],
"value": 43,
"inputs": {
"bus_factor": 1,
"contributors_sampled": 18,
"top_contributor_share": 0.546
},
"components": [
{
"key": "bus_factor",
"name": "Bus factor",
"detail": "1 contributor(s) cover half of all commits",
"points": 9,
"status": "partial",
"details": [
{
"code": "bus_factor",
"params": {
"count": 1
}
}
],
"max_points": 54
},
{
"key": "commit_distribution",
"name": "Commit distribution",
"detail": "top contributor authored 55% of commits",
"points": 10.2,
"status": "partial",
"details": [
{
"code": "top_contributor_share",
"params": {
"share": 55
}
}
],
"max_points": 22.5
},
{
"key": "contributor_breadth",
"name": "Contributor breadth",
"detail": "18 contributors",
"points": 13.5,
"status": "met",
"details": [
{
"code": "contributors_sampled",
"params": {
"count": 18
}
}
],
"max_points": 13.5
},
{
"key": "openssf_scorecard_contributors",
"name": "OpenSSF Scorecard: Contributors",
"detail": "project has 11 contributing companies or organizations",
"points": 10,
"status": "met",
"details": [],
"max_points": 10
}
]
},
{
"key": "responsiveness",
"band": "good",
"name": "Issue & PR responsiveness",
"note": null,
"notes": [],
"value": 80,
"inputs": {
"merged_prs": 65,
"open_issues": 1,
"closed_issues": 11,
"issue_closed_ratio": 0.917,
"closed_unmerged_prs": 3
},
"components": [
{
"key": "issue_resolution",
"name": "Issue resolution",
"detail": "92% of issues closed",
"points": 42.9,
"status": "partial",
"details": [
{
"code": "issues_closed_share",
"params": {
"share": 92
}
}
],
"max_points": 46.75
},
{
"key": "pr_acceptance",
"name": "PR acceptance",
"detail": "65/68 decided PRs merged",
"points": 36.6,
"status": "partial",
"details": [
{
"code": "decided_prs_merged",
"params": {
"merged": 65,
"decided": 68
}
}
],
"max_points": 38.25
},
{
"key": "openssf_scorecard_code_review",
"name": "OpenSSF Scorecard: Code-Review",
"detail": "Found 2/29 approved changesets -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 15
}
]
},
{
"key": "stewardship",
"band": "good",
"name": "Ownership & stewardship",
"note": "Excluded from scoring (no data or not applicable): Verified domain. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"verified_domain"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 70,
"inputs": {
"followers": 750,
"owner_type": "User",
"is_verified": null,
"owner_login": "dereuromark",
"public_repos": 123,
"account_age_days": 6437
},
"components": [
{
"key": "ownership_backing",
"name": "Ownership backing",
"detail": "personal (user) account",
"points": 10,
"status": "partial",
"details": [
{
"code": "owner_personal",
"params": {}
}
],
"max_points": 30
},
{
"key": "verified_domain",
"name": "Verified domain",
"detail": "not applicable to user accounts",
"points": 0,
"status": "excluded",
"details": [
{
"code": "not_applicable_to_user_accounts",
"params": {}
}
],
"max_points": 20
},
{
"key": "owner_reach",
"name": "Owner reach",
"detail": "750 followers of dereuromark",
"points": 20.7,
"status": "partial",
"details": [
{
"code": "owner_followers",
"params": {
"count": 750,
"login": "dereuromark"
}
}
],
"max_points": 25
},
{
"key": "track_record",
"name": "Track record",
"detail": "123 public repos, account ~17 yr old",
"points": 25,
"status": "met",
"details": [
{
"code": "public_repos",
"params": {
"count": 123
}
},
{
"code": "account_age_years",
"params": {
"years": 17
}
}
],
"max_points": 25
}
]
},
{
"key": "package_maintenance",
"band": "excellent",
"name": "Package maintenance",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"packages": [
"dereuromark/cakephp-audit-stash"
],
"ecosystems": "packagist",
"any_deprecated": false,
"min_days_since_publish": 39
},
"components": [
{
"key": "published_resolvable",
"name": "Published & resolvable",
"detail": "1 package(s) on packagist",
"points": 25,
"status": "met",
"details": [
{
"code": "packages_published",
"params": {
"count": 1,
"ecosystems": "packagist"
}
}
],
"max_points": 25
},
{
"key": "publish_recency",
"name": "Publish recency",
"detail": "latest publish 39 days ago",
"points": 35,
"status": "met",
"details": [
{
"code": "publish_recency",
"params": {
"days": 39
}
}
],
"max_points": 35
},
{
"key": "version_history",
"name": "Version history",
"detail": "26 published versions",
"points": 20,
"status": "met",
"details": [
{
"code": "published_versions",
"params": {
"count": 26
}
}
],
"max_points": 20
},
{
"key": "not_deprecated",
"name": "Not deprecated",
"detail": "active, not deprecated or yanked",
"points": 20,
"status": "met",
"details": [
{
"code": "package_not_deprecated",
"params": {}
}
],
"max_points": 20
}
]
}
],
"description": "Will the project survive its people — bus factor, responsiveness, who backs it, and package upkeep?"
},
{
"key": "engineering",
"band": "excellent",
"name": "Engineering Quality",
"value": 94,
"weight": 0.2,
"metrics": [
{
"key": "engineering_practices",
"band": "excellent",
"name": "Engineering practices",
"note": null,
"notes": [],
"value": 90,
"inputs": {
"has_ci": true,
"has_tests": true,
"has_editorconfig": true,
"has_linter_config": true,
"has_precommit_config": false
},
"components": [
{
"key": "ci_workflows",
"name": "CI workflows",
"detail": "2 workflow(s)",
"points": 24,
"status": "met",
"details": [
{
"code": "ci_workflows",
"params": {
"count": 2
}
}
],
"max_points": 24
},
{
"key": "tests_present",
"name": "Tests present",
"detail": null,
"points": 24,
"status": "met",
"details": [],
"max_points": 24
},
{
"key": "linter_config",
"name": "Linter config",
"detail": "phpcs.xml, phpstan.neon",
"points": 16,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "phpcs.xml, phpstan.neon"
}
}
],
"max_points": 16
},
{
"key": "pre_commit_hooks",
"name": "Pre-commit hooks",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 9.6
},
{
"key": "editorconfig",
"name": ".editorconfig",
"detail": null,
"points": 6.4,
"status": "met",
"details": [],
"max_points": 6.4
},
{
"key": "openssf_scorecard_ci_tests",
"name": "OpenSSF Scorecard: CI-Tests",
"detail": "15 out of 15 merged PRs checked by a CI test -- score normalized to 10",
"points": 20,
"status": "met",
"details": [],
"max_points": 20
}
]
},
{
"key": "documentation",
"band": "excellent",
"name": "Documentation",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"topics": [
"audit-log",
"audit-trail",
"logging",
"cakephp",
"cakephp-plugin",
"php"
],
"has_wiki": true,
"homepage": "https://dereuromark.github.io/cakephp-audit-stash/",
"has_readme": true,
"has_docs_dir": true,
"has_description": true
},
"components": [
{
"key": "readme",
"name": "README",
"detail": null,
"points": 30,
"status": "met",
"details": [],
"max_points": 30
},
{
"key": "documentation_directory",
"name": "Documentation directory",
"detail": null,
"points": 25,
"status": "met",
"details": [],
"max_points": 25
},
{
"key": "documentation_homepage_site",
"name": "Documentation / homepage site",
"detail": "https://dereuromark.github.io/cakephp-audit-stash/",
"points": 15,
"status": "met",
"details": [],
"max_points": 15
},
{
"key": "repository_description",
"name": "Repository description",
"detail": null,
"points": 10,
"status": "met",
"details": [],
"max_points": 10
},
{
"key": "topics",
"name": "Topics",
"detail": "6 topics",
"points": 10,
"status": "met",
"details": [
{
"code": "topics_count",
"params": {
"count": 6
}
}
],
"max_points": 10
},
{
"key": "wiki",
"name": "Wiki",
"detail": null,
"points": 10,
"status": "met",
"details": [],
"max_points": 10
}
]
}
],
"description": "Are baseline engineering and documentation practices in place?"
},
{
"key": "security",
"band": "at_risk",
"name": "Security",
"value": 42,
"weight": 0.16,
"metrics": [
{
"key": "security_posture",
"band": "at_risk",
"name": "Security posture",
"note": "Excluded from scoring (no data or not applicable): Packaging, Signed-Releases. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"packaging",
"signed_releases"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 42,
"inputs": {
"source": "openssf_scorecard",
"checks_evaluated": 16,
"scorecard_version": "v5.5.0",
"checks_inconclusive": 2,
"scorecard_aggregate": 4.2
},
"components": [
{
"key": "binary_artifacts",
"name": "Binary-Artifacts",
"detail": "no binaries found in the repo",
"points": 7.5,
"status": "met",
"details": [],
"max_points": 7.5
},
{
"key": "branch_protection",
"name": "Branch-Protection",
"detail": "branch protection is not maximal on development and all release branches",
"points": 2.2,
"status": "partial",
"details": [],
"max_points": 7.5
},
{
"key": "ci_tests",
"name": "CI-Tests",
"detail": "15 out of 15 merged PRs checked by a CI test -- score normalized to 10",
"points": 2.5,
"status": "met",
"details": [],
"max_points": 2.5
},
{
"key": "cii_best_practices",
"name": "CII-Best-Practices",
"detail": "no effort to earn an OpenSSF best practices badge detected",
"points": 0,
"status": "missed",
"details": [],
"max_points": 2.5
},
{
"key": "code_review",
"name": "Code-Review",
"detail": "Found 2/29 approved changesets -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "contributors",
"name": "Contributors",
"detail": "project has 11 contributing companies or organizations",
"points": 2.5,
"status": "met",
"details": [],
"max_points": 2.5
},
{
"key": "dangerous_workflow",
"name": "Dangerous-Workflow",
"detail": "no dangerous workflow patterns detected",
"points": 10,
"status": "met",
"details": [],
"max_points": 10
},
{
"key": "dependency_update_tool",
"name": "Dependency-Update-Tool",
"detail": "no update tool detected",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "fuzzing",
"name": "Fuzzing",
"detail": "project is not fuzzed",
"points": 0,
"status": "missed",
"details": [],
"max_points": 5
},
{
"key": "license",
"name": "License",
"detail": "license file detected",
"points": 2.5,
"status": "met",
"details": [],
"max_points": 2.5
},
{
"key": "maintained",
"name": "Maintained",
"detail": "30 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 10",
"points": 7.5,
"status": "met",
"details": [],
"max_points": 7.5
},
{
"key": "packaging",
"name": "Packaging",
"detail": "packaging workflow not detected",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 5
},
{
"key": "pinned_dependencies",
"name": "Pinned-Dependencies",
"detail": "dependency not pinned by hash detected -- score normalized to 1",
"points": 0.5,
"status": "partial",
"details": [],
"max_points": 5
},
{
"key": "sast",
"name": "SAST",
"detail": "SAST tool is not run on all commits -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 5
},
{
"key": "security_policy",
"name": "Security-Policy",
"detail": "security policy file not detected",
"points": 0,
"status": "missed",
"details": [],
"max_points": 5
},
{
"key": "signed_releases",
"name": "Signed-Releases",
"detail": "no releases found",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 7.5
},
{
"key": "token_permissions",
"name": "Token-Permissions",
"detail": "detected GitHub workflow tokens with excessive permissions",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "vulnerabilities",
"name": "Vulnerabilities",
"detail": "5 existing vulnerabilities detected",
"points": 3.8,
"status": "partial",
"details": [],
"max_points": 7.5
}
]
},
{
"key": "high_risk_jurisdiction_exposure",
"band": "excellent",
"name": "High-Risk Jurisdiction Exposure",
"note": "Only high-confidence self-published location evidence affects this multiplier. Ambiguous matches are review-only; country evidence is not proof of nationality, citizenship, legal registration, malicious intent, or sanctions status.",
"notes": [
{
"code": "jurisdiction_evidence_limits",
"params": {}
}
],
"value": 100,
"inputs": {
"meaning": "self-published location evidence; not nationality or citizenship",
"red_flag": false,
"exposures": [],
"policy_countries": [
"Russia",
"Iran",
"North Korea"
],
"review_only_matches": 0,
"assessed_self_published_locations": 17
},
"components": [
{
"key": "policy_exposure_multiplier",
"name": "Policy exposure multiplier",
"detail": "no confirmed policy-scope location match",
"points": 100,
"status": "met",
"details": [
{
"code": "jurisdiction_no_match",
"params": {}
}
],
"max_points": 100
}
]
}
],
"description": "Are visible security and supply-chain practices strong, with no malicious dependency and no unresolved high-risk jurisdiction exposure?"
},
{
"key": "ai_readiness",
"band": "at_risk",
"name": "AI Readiness",
"value": 45,
"weight": 0,
"metrics": [
{
"key": "ai_agent_context",
"band": "at_risk",
"name": "Agent context & guidance",
"note": null,
"notes": [],
"value": 40,
"inputs": {
"has_llms_txt": false,
"legible_history_share": 0.79,
"agent_instruction_files": [],
"agent_instruction_max_bytes": null
},
"components": [
{
"key": "agent_instructions",
"name": "Agent instructions",
"detail": "no CLAUDE.md / AGENTS.md / editor rules",
"points": 0,
"status": "missed",
"details": [
{
"code": "no_agent_instructions",
"params": {}
}
],
"max_points": 45
},
{
"key": "machine_readable_docs_llms_txt",
"name": "Machine-readable docs (llms.txt)",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 15
},
{
"key": "legible_commit_history",
"name": "Legible commit history",
"detail": "79 of 100 human commits state their intent (structured subject or explanatory body)",
"points": 40,
"status": "met",
"details": [
{
"code": "legible_history",
"params": {
"legible": 79,
"sampled": 100
}
}
],
"max_points": 40
}
]
},
{
"key": "ai_verify_loop",
"band": "at_risk",
"name": "Verify loop (build / test / typecheck)",
"note": null,
"notes": [],
"value": 44,
"inputs": {
"has_nix": false,
"has_tests": true,
"lockfiles": [
"package-lock.json"
],
"has_dockerfile": false,
"typed_language": false,
"bootstrap_files": [],
"has_devcontainer": false,
"has_linter_config": true,
"typecheck_configs": [],
"agent_commit_share": 0,
"toolchain_manifests": [],
"dependency_bot_commit_share": 0
},
"components": [
{
"key": "one_command_bootstrap",
"name": "One-command bootstrap",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 18
},
{
"key": "automated_tests",
"name": "Automated tests",
"detail": null,
"points": 22,
"status": "met",
"details": [],
"max_points": 22
},
{
"key": "lint_format_config",
"name": "Lint / format config",
"detail": "phpcs.xml, phpstan.neon",
"points": 11,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "phpcs.xml, phpstan.neon"
}
}
],
"max_points": 11
},
{
"key": "static_type_checking",
"name": "Static type checking",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 11
},
{
"key": "reproducible_environment",
"name": "Reproducible environment",
"detail": "lockfile",
"points": 10,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "lockfile"
}
}
],
"max_points": 10
},
{
"key": "demonstrated_agent_practice",
"name": "Demonstrated agent practice",
"detail": "no agent-authored commits among the last 100",
"points": 0,
"status": "missed",
"details": [
{
"code": "no_agent_authored_commits",
"params": {
"sampled": 100
}
}
],
"max_points": 10
},
{
"key": "automated_maintenance",
"name": "Automated maintenance",
"detail": "no automated dependency updates observed",
"points": 0,
"status": "missed",
"details": [
{
"code": "no_dependency_automation",
"params": {}
}
],
"max_points": 8
},
{
"key": "openssf_scorecard_pinned_dependencies",
"name": "OpenSSF Scorecard: Pinned-Dependencies",
"detail": "dependency not pinned by hash detected -- score normalized to 1",
"points": 1,
"status": "partial",
"details": [],
"max_points": 10
}
]
},
{
"key": "ai_code_legibility",
"band": "moderate",
"name": "Code legibility for models",
"note": null,
"notes": [],
"value": 55,
"inputs": {
"primary_language": "PHP",
"largest_source_bytes": 40814,
"source_files_sampled": 157,
"oversized_source_files": 0
},
"components": [
{
"key": "type_checkable_code",
"name": "Type-checkable code",
"detail": "PHP without a type-check config",
"points": 0,
"status": "missed",
"details": [
{
"code": "no_typecheck_config_language",
"params": {
"language": "PHP"
}
}
],
"max_points": 45
},
{
"key": "manageable_file_sizes",
"name": "Manageable file sizes",
"detail": "0/157 source files over 60KB",
"points": 55,
"status": "met",
"details": [
{
"code": "oversized_source_files",
"params": {
"kb": 60,
"sampled": 157,
"oversized": 0
}
}
],
"max_points": 55
}
]
}
],
"description": "How well is the repo equipped to be developed and maintained with AI coding agents? An independent, experimental badge — weight 0.0, so it is surfaced on its own and does not affect the overall health score."
}
],
"metrics_version": "1.13.0"
},
"warnings": [
"Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token",
"GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository"
],
"report_type": "repository",
"generated_at": "2026-07-28T09:55:41.694632Z",
"schema_version": "0.27.0",
"badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/d/dereuromark/cakephp-audit-stash.svg",
"full_name": "dereuromark/cakephp-audit-stash",
"license_state": "standard",
"license_spdx": "MIT"
}