Track and measure agent readiness
Coding agents such as Claude Code, Cursor, GitHub Copilot, and Codex now write most of the code that lands in a repository. The pull requests they open are only as safe as the repository they work in. A repository that states how the project works, how to verify a change, and which standards to follow lets you bring those agents into your software factory and keeps review on the change itself.
This guide turns those checks into catalog data. The scorecard grades each repository in three levels: agents have context, agents can act, and agents are safe.
We will:
- Ingest readiness signals from each GitHub repository.
- Grade repositories with an AI Readiness scorecard.
- Run Continuously fix readiness gaps, which asks Claude Code to open a pull request when a developer requests it and when a file-based rule drops from passed to not passed.
- Track readiness over time and across teams.
Common use cases
- Identify and fix agent readiness gaps in your repositories so coding agents perform well and stay within your standards.
- Track agent readiness over time and across teams and domains.
Prerequisites
This guide assumes the following:
- You have a Port account and have completed the onboarding process.
- Port's GitHub Ocean integration is installed, and repository entities already sync into a
githubRepositoryblueprint. If your mapping writes repositories toserviceinstead, add the same properties there and replace everygithubRepositoryreference in this guide. - You completed the Claude Code backend setup in Trigger AI coding assistants from Port.
- GitHub teams sync into a
githubTeamblueprint, and the repository blueprint can relate to it. The default GitHub mapping createsgithubTeamandgithubOrganization.
To author one ruleset and roll it out to many repositories, use Standardize AI coding rules across repositories. This guide scores whatever is already in each repo.
How the score works
Port scorecards use ordered levels. A repository reaches a level only when it passes every rule at that level and every level below it. A missing README keeps the repository at Basic, even if later rules pass.
| Level | Color | What it means |
|---|---|---|
| Basic | Light gray | The repository misses at least one context rule. |
| Agents Have Context | Yellow | README, an agent instruction file, CODEOWNERS, and the repository URL are present. |
| Agents Can Act | Blue | The repo can build, test, lint, and accept a pull request without guesswork. |
| Agents Are Safe | Green | Instruction files forbid unsafe patterns, and branch protection gates changes. |
AGENTS.md and CLAUDE.md both satisfy the instruction-file rule, so the same scorecard works for Claude Code, Codex, and Cursor.
Add readiness properties
Add the properties below to your existing repository blueprint. Skip any key that is already there.
- Go to the Data model page in Port.
- Find and select the Repository blueprint (
githubRepository). - Click the
...button, then choose Edit blueprint, then{...} Edit JSON. - Under
schema.properties, add the properties in the next two sections. - Click Save.
File and content properties
Boolean properties record that a file exists. Markdown properties store the file body so you can open it from the catalog.
Agent readiness properties (click to expand)
"readme": {
"title": "README",
"type": "string",
"format": "markdown"
},
"url": {
"title": "Repository URL",
"type": "string",
"format": "url"
},
"defaultBranch": {
"title": "Default branch",
"type": "string"
},
"visibility": {
"title": "Visibility",
"type": "string"
},
"language": {
"title": "Language",
"type": "string"
},
"ai_instructions": {
"title": "AI Instructions",
"description": "Rules and contribution guidelines for AI coding agents",
"type": "string",
"format": "markdown"
},
"claude_md": {
"title": "CLAUDE.md",
"description": "Ingested CLAUDE.md content from the repository root",
"type": "string",
"format": "markdown"
},
"has_agents_md": {
"title": "Has AGENTS.md",
"description": "AGENTS.md exists at the repo root.",
"type": "boolean"
},
"has_claude_md": {
"title": "Has CLAUDE.md",
"description": "CLAUDE.md exists at the repo root.",
"type": "boolean"
},
"has_codeowners": {
"title": "Has CODEOWNERS",
"description": "CODEOWNERS exists at the root, in .github/, or in docs/.",
"type": "boolean"
},
"has_devcontainer": {
"title": "Has devcontainer",
"description": ".devcontainer/devcontainer.json exists.",
"type": "boolean"
},
"has_env_example": {
"title": "Has .env example",
"description": "An environment variable template exists (.env.example, .env.sample, or .env.template).",
"type": "boolean"
},
"has_pre_commit": {
"title": "Has pre-commit hooks",
"description": "pre-commit config or Husky pre-commit hook exists.",
"type": "boolean"
},
"has_dependency_updates": {
"title": "Has dependency updates",
"description": "Dependabot or Renovate config exists.",
"type": "boolean"
},
"has_lint_config": {
"title": "Has lint config",
"description": "A linter config exists (ESLint, Ruff, or golangci-lint).",
"type": "boolean"
},
"has_test_config": {
"title": "Has test config",
"description": "A test runner config exists (Jest, Vitest, pytest, or tox).",
"type": "boolean"
},
"has_pr_template": {
"title": "Has PR template",
"description": "A pull request template exists.",
"type": "boolean"
},
"has_api_spec": {
"title": "Has API spec",
"description": "A machine-readable API spec exists (OpenAPI or Swagger).",
"type": "boolean"
},
"has_agent_security_guardrails": {
"title": "Has agent security guardrails",
"description": "Agent instruction files mention security topics (OWASP, injection, authentication, input validation).",
"type": "boolean"
},
"has_secret_guardrails": {
"title": "Has secret handling guardrails",
"description": "Agent instruction files tell agents not to hardcode or expose secrets or credentials.",
"type": "boolean"
},
"has_parameterized_query_guardrail": {
"title": "Has parameterized query guardrail",
"description": "Agent instruction files require parameterized queries or prepared statements.",
"type": "boolean"
}
Branch protection signals
Add these two booleans as well. They come from the branch kind in the same GitHub integration, not from the repository file mapping. A repository reaches Agents Are Safe only when both are true, together with the other Safe rules. Delete those two rules from the scorecard if the GitHub App cannot read branch protection.
Branch protection properties (click to expand)
"is_protected": {
"title": "Default branch protected",
"description": "Branch protection is enabled on the default branch.",
"type": "boolean"
},
"require_code_owner_review": {
"title": "Requires code owner review",
"description": "Branch protection requires code owner review.",
"type": "boolean"
}
Map the two GitHub fields from the default branch. Set enableMergeEntity: true at the root of the mapping so this resource updates the same repository entity as the file mapping, and the identifier matches .full_name (org/repo).
Branch protection mapping (click to expand)
- kind: branch
selector:
query: "true"
defaultBranchOnly: true
protectionRules: true
port:
entity:
mappings:
identifier: .__organization + "/" + .__repository
blueprint: '"githubRepository"'
properties:
require_code_owner_review: >-
.__protection_rules.required_pull_request_reviews.require_code_owner_reviews
is_protected: >-
.__protection_rules != null and (.__protection_rules | length) > 0
protectionRules: true is required. If the GitHub App cannot read branch protection, .__protection_rules is empty and both properties stay false. See Repositories and branch protection rules.
Ingest readiness signals
The repository mapping reads specific paths with includedFiles and turns each one into a property. A boolean is true when at least one of its paths has content. Guardrail booleans are true when the combined instruction files match a pattern, so a rule in AGENTS.md, CLAUDE.md, a Cursor security rule, or a Copilot instruction file all count.
| Signal | Property | Paths or check |
|---|---|---|
| README | readme | README.md |
| Agent instructions | has_agents_md, ai_instructions | AGENTS.md |
| Claude instructions | has_claude_md, claude_md | CLAUDE.md |
| CODEOWNERS | has_codeowners | CODEOWNERS, .github/CODEOWNERS, docs/CODEOWNERS |
| Devcontainer | has_devcontainer | .devcontainer/devcontainer.json |
| Environment template | has_env_example | .env.example, .env.sample, .env.template |
| Pre-commit | has_pre_commit | .pre-commit-config.yaml, .husky/pre-commit |
| Dependency updates | has_dependency_updates | Dependabot or Renovate config |
| Linter | has_lint_config | ESLint, Ruff, or golangci-lint |
| Test runner | has_test_config | Jest, Vitest, pytest, or tox |
| Pull request template | has_pr_template | .github/ or docs/ pull request template |
| API spec | has_api_spec | OpenAPI or Swagger at the repo root, or api/openapi.yaml |
| Security guardrails | has_agent_security_guardrails | Instruction text matches security, OWASP, injection, authentication, or input validation |
| Secret guardrails | has_secret_guardrails | Instruction text matches secret, credential, or hardcode |
| Parameterized queries | has_parameterized_query_guardrail | Instruction text matches parameterized queries or prepared statements |
The Build and test commands documented rule reads readme directly. It passes when that property contains a concrete command such as npm test, pytest, make test, or go test.
- Go to the Connectors page in Port.
- Open your GitHub Ocean integration.
- Go to the Mapping tab.
- On the existing
repositoryresource that maps togithubRepository, add theincludedFileslist and thepropertiesbelow. Keep the identifier, title, and any properties you already map. - Set
include: [teams]on that resource sogithubTeamsis populated. The dashboard groups by this relation. - Click Save, then Resync.
Repository readiness mapping (click to expand)
- kind: repository
selector:
query: 'true'
includedRelations:
teams: true
includedFiles:
include:
- teams
- AGENTS.md
- .cursor/rules/security/authentication.md
- .cursor/rules/security/command-injection.md
- .cursor/rules/security/database.md
- .cursor/rules/security/file-upload.md
- .cursor/rules/security/input-validation.md
- .cursor/rules/security/owasp-10.md
- .cursor/rules/security/logging.md
- .github/instructions/documentation.instructions.md
- .github/instructions/general-coding.instructions.md
- .github/instructions/language-specific.instructions.md
- CLAUDE.md
- .github/CODEOWNERS
- docs/CODEOWNERS
- .devcontainer/devcontainer.json
- .env.example
- .env.sample
- .env.template
- .pre-commit-config.yaml
- .husky/pre-commit
- .github/dependabot.yml
- .github/dependabot.yaml
- renovate.json
- .github/renovate.json
- .eslintrc.json
- eslint.config.js
- eslint.config.mjs
- ruff.toml
- .ruff.toml
- .golangci.yml
- jest.config.js
- jest.config.ts
- vitest.config.ts
- vitest.config.js
- pytest.ini
- tox.ini
- .github/pull_request_template.md
- .github/PULL_REQUEST_TEMPLATE.md
- docs/pull_request_template.md
- openapi.yaml
- openapi.yml
- openapi.json
- swagger.json
- swagger.yaml
- api/openapi.yaml
port:
entity:
mappings:
identifier: .full_name
blueprint: '"githubRepository"'
title: .name
properties:
visibility: if .private then "private" else "public" end
defaultBranch: .default_branch
readme: .__includedFiles["README.md"]
ai_instructions: .__includedFiles["AGENTS.md"]
claude_md: .__includedFiles["CLAUDE.md"]
url: .html_url
language: if .language then .language else "" end
has_agents_md: ([.__includedFiles["AGENTS.md"]] | map(select(. != null)) | length > 0)
has_claude_md: ([.__includedFiles["CLAUDE.md"]] | map(select(. != null)) | length > 0)
has_codeowners: >-
([.__includedFiles["CODEOWNERS"], .__includedFiles[".github/CODEOWNERS"],
.__includedFiles["docs/CODEOWNERS"]] | map(select(. != null)) | length > 0)
has_devcontainer: ([.__includedFiles[".devcontainer/devcontainer.json"]] | map(select(. != null)) | length > 0)
has_env_example: >-
([.__includedFiles[".env.example"], .__includedFiles[".env.sample"], .__includedFiles[".env.template"]] |
map(select(. != null)) | length > 0)
has_pre_commit: >-
([.__includedFiles[".pre-commit-config.yaml"], .__includedFiles[".husky/pre-commit"]] | map(select(. !=
null)) | length > 0)
has_dependency_updates: >-
([.__includedFiles[".github/dependabot.yml"], .__includedFiles[".github/dependabot.yaml"],
.__includedFiles["renovate.json"], .__includedFiles[".github/renovate.json"]] | map(select(. != null)) |
length > 0)
has_lint_config: >-
([.__includedFiles[".eslintrc.json"], .__includedFiles["eslint.config.js"],
.__includedFiles["eslint.config.mjs"], .__includedFiles["ruff.toml"], .__includedFiles[".ruff.toml"],
.__includedFiles[".golangci.yml"]] | map(select(. != null)) | length > 0)
has_test_config: >-
([.__includedFiles["jest.config.js"], .__includedFiles["jest.config.ts"],
.__includedFiles["vitest.config.ts"], .__includedFiles["vitest.config.js"],
.__includedFiles["pytest.ini"], .__includedFiles["tox.ini"]] | map(select(. != null)) | length > 0)
has_pr_template: >-
([.__includedFiles[".github/pull_request_template.md"],
.__includedFiles[".github/PULL_REQUEST_TEMPLATE.md"], .__includedFiles["docs/pull_request_template.md"]] |
map(select(. != null)) | length > 0)
has_api_spec: >-
([.__includedFiles["openapi.yaml"], .__includedFiles["openapi.yml"], .__includedFiles["openapi.json"],
.__includedFiles["swagger.json"], .__includedFiles["swagger.yaml"], .__includedFiles["api/openapi.yaml"]]
| map(select(. != null)) | length > 0)
has_agent_security_guardrails: >-
(([.__includedFiles["AGENTS.md"], .__includedFiles["CLAUDE.md"],
.__includedFiles[".cursor/rules/security/authentication.md"],
.__includedFiles[".cursor/rules/security/command-injection.md"],
.__includedFiles[".cursor/rules/security/database.md"],
.__includedFiles[".cursor/rules/security/file-upload.md"],
.__includedFiles[".cursor/rules/security/input-validation.md"],
.__includedFiles[".cursor/rules/security/owasp-10.md"],
.__includedFiles[".cursor/rules/security/logging.md"],
.__includedFiles[".github/instructions/general-coding.instructions.md"],
.__includedFiles[".github/instructions/language-specific.instructions.md"]] | map(select(. != null)) |
join("\n")) | test("security|owasp|injection|authentication|input validation"; "i"))
has_secret_guardrails: >-
(([.__includedFiles["AGENTS.md"], .__includedFiles["CLAUDE.md"],
.__includedFiles[".cursor/rules/security/authentication.md"],
.__includedFiles[".cursor/rules/security/command-injection.md"],
.__includedFiles[".cursor/rules/security/database.md"],
.__includedFiles[".cursor/rules/security/file-upload.md"],
.__includedFiles[".cursor/rules/security/input-validation.md"],
.__includedFiles[".cursor/rules/security/owasp-10.md"],
.__includedFiles[".cursor/rules/security/logging.md"],
.__includedFiles[".github/instructions/general-coding.instructions.md"],
.__includedFiles[".github/instructions/language-specific.instructions.md"]] | map(select(. != null)) |
join("\n")) | test("hardcod|hard-code|secret|credential"; "i"))
has_parameterized_query_guardrail: >-
(([.__includedFiles["AGENTS.md"], .__includedFiles["CLAUDE.md"],
.__includedFiles[".cursor/rules/security/authentication.md"],
.__includedFiles[".cursor/rules/security/command-injection.md"],
.__includedFiles[".cursor/rules/security/database.md"],
.__includedFiles[".cursor/rules/security/file-upload.md"],
.__includedFiles[".cursor/rules/security/input-validation.md"],
.__includedFiles[".cursor/rules/security/owasp-10.md"],
.__includedFiles[".cursor/rules/security/logging.md"],
.__includedFiles[".github/instructions/general-coding.instructions.md"],
.__includedFiles[".github/instructions/language-specific.instructions.md"]] | map(select(. != null)) |
join("\n")) | test("parameterized|prepared statement"; "i"))
relations:
organization: .owner.login
githubTeams: '[.__teams[].id | tostring]'
The integration only reads the paths in includedFiles. A linter at frontend/eslint.config.js, or a CODEOWNERS file outside the three paths above, stays invisible to the scorecard. Add that path to includedFiles and to the matching JQ expression if your layout differs.
After the resync, open a repository entity and confirm has_agents_md, has_lint_config, and readme match the files on the default branch.
Create the AI readiness scorecard
Blueprint: Repository (githubRepository)
| Rule | Level | Passes when |
|---|---|---|
| README is present | Agents Have Context | readme is not empty |
| AGENTS.md or CLAUDE.md present | Agents Have Context | has_agents_md or has_claude_md is true |
| CODEOWNERS present | Agents Have Context | has_codeowners is true |
| Repository URL linked in catalog | Agents Have Context | url is not empty |
| Pre-commit hooks configured | Agents Can Act | has_pre_commit is true |
| PR template present | Agents Can Act | has_pr_template is true |
| Environment template present | Agents Can Act | has_env_example is true |
| Test runner configured | Agents Can Act | has_test_config is true |
| Linter configured | Agents Can Act | has_lint_config is true |
| Devcontainer present | Agents Can Act | has_devcontainer is true |
| Dependency updates configured | Agents Can Act | has_dependency_updates is true |
| Build and test commands documented | Agents Can Act | readme contains a concrete build or test command |
| API spec present | Agents Can Act | has_api_spec is true |
| Hardcoded secrets prohibited | Agents Are Safe | has_secret_guardrails is true |
| Agent PRs gated by CI | Agents Are Safe | is_protected is true |
| Parameterized queries enforced | Agents Are Safe | has_parameterized_query_guardrail is true |
| Code owner review required | Agents Are Safe | require_code_owner_review is true |
| Agent security guardrails defined | Agents Are Safe | has_agent_security_guardrails is true |
- Go to the Data model page in Port.
- Search for the Repository blueprint and select it.
- Click the Scorecards tab.
- Click + New Scorecard.
- Click
{...} Edit JSONand paste the configuration below. - Click Save.
AI Readiness scorecard (click to expand)
{
"identifier": "ai_readiness_scorecard",
"title": "AI Readiness",
"levels": [
{
"color": "lightGray",
"title": "Basic"
},
{
"color": "yellow",
"title": "Agents Have Context"
},
{
"color": "blue",
"title": "Agents Can Act"
},
{
"color": "green",
"title": "Agents Are Safe"
}
],
"rules": [
{
"identifier": "readme_present",
"title": "README is present",
"level": "Agents Have Context",
"description": "[Context] README explains what the service does, its endpoints, and how to work with it.",
"query": {
"combinator": "and",
"conditions": [
{
"operator": "isNotEmpty",
"property": "readme"
}
]
}
},
{
"identifier": "agent_instructions_present",
"title": "AGENTS.md or CLAUDE.md present",
"level": "Agents Have Context",
"description": "[Context] An agent instruction file tells agents what they can do, what to avoid, and how to run the service. Either file counts, so the rule works for Claude Code, Codex, and Cursor teams.",
"query": {
"combinator": "or",
"conditions": [
{
"value": true,
"operator": "=",
"property": "has_agents_md"
},
{
"value": true,
"operator": "=",
"property": "has_claude_md"
}
]
}
},
{
"identifier": "codeowners_present",
"title": "CODEOWNERS present",
"level": "Agents Have Context",
"description": "[Context] Ownership is defined so agents and humans know who reviews changes to each area.",
"query": {
"combinator": "and",
"conditions": [
{
"value": true,
"operator": "=",
"property": "has_codeowners"
}
]
}
},
{
"identifier": "repo_url_linked",
"title": "Repository URL linked in catalog",
"level": "Agents Have Context",
"description": "[Context] The repo URL is set in the catalog so agents and humans can navigate to the source.",
"query": {
"combinator": "and",
"conditions": [
{
"operator": "isNotEmpty",
"property": "url"
}
]
}
},
{
"identifier": "pre_commit_present",
"title": "Pre-commit hooks configured",
"level": "Agents Can Act",
"description": "[Act] Hooks run checks locally before a commit, so agent-written changes fail fast.",
"query": {
"combinator": "and",
"conditions": [
{
"value": true,
"operator": "=",
"property": "has_pre_commit"
}
]
}
},
{
"identifier": "pr_template_present",
"title": "PR template present",
"level": "Agents Can Act",
"description": "[Act] A PR template tells agents and humans what a change must include before review.",
"query": {
"combinator": "and",
"conditions": [
{
"value": true,
"operator": "=",
"property": "has_pr_template"
}
]
}
},
{
"identifier": "env_example_present",
"title": "Environment template present",
"level": "Agents Can Act",
"description": "[Act] A .env.example documents required environment variables so agents can set up without guessing.",
"query": {
"combinator": "and",
"conditions": [
{
"value": true,
"operator": "=",
"property": "has_env_example"
}
]
}
},
{
"identifier": "test_config_present",
"title": "Test runner configured",
"level": "Agents Can Act",
"description": "[Act] A test runner config exists so agents can run tests and see failures.",
"query": {
"combinator": "and",
"conditions": [
{
"value": true,
"operator": "=",
"property": "has_test_config"
}
]
}
},
{
"identifier": "lint_config_present",
"title": "Linter configured",
"level": "Agents Can Act",
"description": "[Act] A linter config exists so agents get fast feedback on style and obvious errors.",
"query": {
"combinator": "and",
"conditions": [
{
"value": true,
"operator": "=",
"property": "has_lint_config"
}
]
}
},
{
"identifier": "devcontainer_present",
"title": "Devcontainer present",
"level": "Agents Can Act",
"description": "[Act] A devcontainer makes the environment reproducible for developers and agents alike.",
"query": {
"combinator": "and",
"conditions": [
{
"value": true,
"operator": "=",
"property": "has_devcontainer"
}
]
}
},
{
"identifier": "dependency_updates_configured",
"title": "Dependency updates configured",
"level": "Agents Can Act",
"description": "[Act] Dependabot or Renovate keeps dependencies current, which limits drift in agent-generated code.",
"query": {
"combinator": "and",
"conditions": [
{
"value": true,
"operator": "=",
"property": "has_dependency_updates"
}
]
}
},
{
"identifier": "build_test_commands_documented",
"title": "Build and test commands documented",
"level": "Agents Can Act",
"description": "[Act] README contains a concrete build or test command so agents can verify their own work. Matches specific commands, not single words like 'make' or 'pip', which appear in ordinary prose.",
"query": {
"combinator": "or",
"conditions": [
{
"value": "npm test",
"operator": "contains",
"property": "readme"
},
{
"value": "npm run",
"operator": "contains",
"property": "readme"
},
{
"value": "pytest",
"operator": "contains",
"property": "readme"
},
{
"value": "make test",
"operator": "contains",
"property": "readme"
},
{
"value": "make build",
"operator": "contains",
"property": "readme"
},
{
"value": "go test",
"operator": "contains",
"property": "readme"
},
{
"value": "pip install",
"operator": "contains",
"property": "readme"
}
]
}
},
{
"identifier": "api_spec_present",
"title": "API spec present",
"level": "Agents Can Act",
"description": "[Act] A machine-readable API spec gives agents a precise description of the service contract.",
"query": {
"combinator": "and",
"conditions": [
{
"value": true,
"operator": "=",
"property": "has_api_spec"
}
]
}
},
{
"identifier": "no_hardcoded_secrets",
"title": "Hardcoded secrets prohibited",
"level": "Agents Are Safe",
"description": "[Safe] Agent instruction files tell agents not to hardcode API keys, tokens, or credentials. Works for any agent, not only Cursor.",
"query": {
"combinator": "and",
"conditions": [
{
"value": true,
"operator": "=",
"property": "has_secret_guardrails"
}
]
}
},
{
"identifier": "branch_protection_enabled",
"title": "Agent PRs gated by CI",
"level": "Agents Are Safe",
"description": "[Safe] Default branch protection is on, so agent-generated pull requests must pass checks before merge. Set is_protected from the branch protection mapping.",
"query": {
"combinator": "and",
"conditions": [
{
"value": true,
"operator": "=",
"property": "is_protected"
}
]
}
},
{
"identifier": "parameterized_queries_enforced",
"title": "Parameterized queries enforced",
"level": "Agents Are Safe",
"description": "[Safe] Agent instruction files require parameterized queries, which prevents AI-generated SQL injection.",
"query": {
"combinator": "and",
"conditions": [
{
"value": true,
"operator": "=",
"property": "has_parameterized_query_guardrail"
}
]
}
},
{
"identifier": "code_owner_review_gate",
"title": "Code owner review required",
"level": "Agents Are Safe",
"description": "[Safe] Branch protection requires code owner review, so a human reviews agent-generated changes before they ship. Set require_code_owner_review from the branch protection mapping.",
"query": {
"combinator": "and",
"conditions": [
{
"value": true,
"operator": "=",
"property": "require_code_owner_review"
}
]
}
},
{
"identifier": "agent_security_guardrails_present",
"title": "Agent security guardrails defined",
"level": "Agents Are Safe",
"description": "[Safe] Agent instruction files set security standards (OWASP, injection, authentication, input validation) for agent-written code.",
"query": {
"combinator": "and",
"conditions": [
{
"value": true,
"operator": "=",
"property": "has_agent_security_guardrails"
}
]
}
}
]
}
Open a repository entity. The AI Readiness scorecard lists each rule as passed or failed. The level updates after the next evaluation.
Continuously fix readiness gaps
This workflow opens a pull request in two situations:
- A developer runs it from a repository and picks the failing rules to fix.
- A file-based AI Readiness rule changes from Passed to Not passed.
The workflow fixes rules a coding agent can resolve by adding files or instruction text. It leaves out branch protection, code owner review, the repository URL, the test runner, and the API spec. Those still appear on the scorecard.
- Go to the workflows page in Port.
- Click + Workflow.
- Fill out the Create new workflow form. For Name, enter
Continuously fix readiness gaps, then click Confirm. - Click the
{...}button to open the JSON editor and paste the workflow below. - Click Apply changes.
Replace these placeholders before you save:
<YOUR_GITHUB_OCEAN_INTEGRATION_ID>is the identifier on the Connectors page.<GITHUB_ORG>is the GitHub organization that owns the backend workflow file.<GITHUB_REPO>is the repository that contains.github/workflows/claude-backend.yaml.
repo_name is the repository entity identifier (org/name), which matches what claude-backend.yaml expects. The self-service trigger sends the selected repository. The event trigger sends the repository whose rule result changed. See multiple triggers.
The event trigger listens to _rule_result. It runs when result changes from Passed to Not passed, the scorecard title is AI Readiness, and the related rule is one of the file-based rules. Each rule entity identifier is ai_readiness_scorecard_ plus the rule id, for example ai_readiness_scorecard_readme_present.
Continuously fix readiness gaps workflow (click to expand)
{
"identifier": "continuously_fix_readiness_gaps",
"title": "Continuously fix readiness gaps",
"icon": "Github",
"description": "Opens a draft pull request when a developer asks to fix agent readiness gaps, and when a file-based AI Readiness rule drops from passed to not passed.",
"allowAnyoneToViewRuns": true,
"connections": [
{
"sourceIdentifier": "trigger",
"targetIdentifier": "dispatch_claude_code"
},
{
"sourceIdentifier": "scorecard_degraded",
"targetIdentifier": "dispatch_claude_code"
}
],
"nodes": [
{
"identifier": "trigger",
"title": "Fix from a repository",
"config": {
"type": "SELF_SERVE_TRIGGER",
"userInputs": {
"properties": {
"repository": {
"title": "Repository",
"type": "string",
"format": "entity",
"blueprint": "githubRepository"
},
"failing_rules": {
"title": "Rules to fix",
"description": "Select the failing agent readiness rules to remediate. Rules that need repository settings are not listed.",
"type": "array",
"items": {
"type": "string",
"enum": [
"readme_present",
"agent_instructions_present",
"codeowners_present",
"build_test_commands_documented",
"lint_config_present",
"devcontainer_present",
"env_example_present",
"pre_commit_present",
"dependency_updates_configured",
"pr_template_present",
"agent_security_guardrails_present",
"no_hardcoded_secrets",
"parameterized_queries_enforced"
]
},
"minItems": 1
},
"open_as_draft": {
"title": "Open PR as draft",
"type": "boolean",
"default": true
}
},
"required": [
"repository",
"failing_rules"
],
"order": [
"repository",
"failing_rules",
"open_as_draft"
]
},
"published": true,
"permissions": {
"roles": [
"Member"
]
},
"contexts": [
{
"on": "ENTITY",
"userInput": "repository"
}
]
}
},
{
"identifier": "scorecard_degraded",
"title": "On file-based rule failure",
"config": {
"type": "EVENT_TRIGGER",
"event": {
"type": "ENTITY_UPDATED",
"blueprintIdentifier": "_rule_result"
},
"condition": {
"type": "JQ",
"combinator": "and",
"expressions": [
".diff.before.properties.result == \"Passed\"",
".diff.after.properties.result == \"Not passed\"",
".diff.after.properties.scorecard == \"AI Readiness\"",
"([\"ai_readiness_scorecard_readme_present\", \"ai_readiness_scorecard_agent_instructions_present\", \"ai_readiness_scorecard_codeowners_present\", \"ai_readiness_scorecard_build_test_commands_documented\", \"ai_readiness_scorecard_lint_config_present\", \"ai_readiness_scorecard_devcontainer_present\", \"ai_readiness_scorecard_env_example_present\", \"ai_readiness_scorecard_pre_commit_present\", \"ai_readiness_scorecard_dependency_updates_configured\", \"ai_readiness_scorecard_pr_template_present\", \"ai_readiness_scorecard_agent_security_guardrails_present\", \"ai_readiness_scorecard_no_hardcoded_secrets\", \"ai_readiness_scorecard_parameterized_queries_enforced\"] | index(.diff.after.relations.rule)) != null"
]
}
}
},
{
"identifier": "dispatch_claude_code",
"title": "Dispatch Claude Code",
"config": {
"type": "INTEGRATION_ACTION",
"installationId": "<YOUR_GITHUB_OCEAN_INTEGRATION_ID>",
"integrationProvider": "github-ocean",
"integrationInvocationType": "dispatch_workflow",
"integrationActionExecutionProperties": {
"org": "<GITHUB_ORG>",
"repo": "<GITHUB_REPO>",
"workflow": "claude-backend.yaml",
"workflowInputs": {
"command": "{{ \"Fix the following agent readiness gaps:\\n\\n\" + ((.outputs.trigger.failing_rules // [(.outputs.trigger.diff.after.relations.rule // \"\") | sub(\"^ai_readiness_scorecard_\"; \"\")]) | map( if . == \"readme_present\" then \"- Add a README.md.\" elif . == \"agent_instructions_present\" then \"- Add an AGENTS.md.\" elif . == \"codeowners_present\" then \"- Add a CODEOWNERS file.\" elif . == \"build_test_commands_documented\" then \"- Document build/test commands in README.\" elif . == \"lint_config_present\" then \"- Add a linter config.\" elif . == \"devcontainer_present\" then \"- Add a devcontainer.json.\" elif . == \"env_example_present\" then \"- Add a .env.example.\" elif . == \"pre_commit_present\" then \"- Add a pre-commit config.\" elif . == \"dependency_updates_configured\" then \"- Add Dependabot or Renovate config.\" elif . == \"pr_template_present\" then \"- Add a PR template.\" elif . == \"agent_security_guardrails_present\" then \"- Add security guardrails to AGENTS.md.\" elif . == \"no_hardcoded_secrets\" then \"- Add a no-hardcoded-secrets rule to AGENTS.md.\" elif . == \"parameterized_queries_enforced\" then \"- Add a parameterized-queries rule to AGENTS.md.\" else \"- \" + . end ) | join(\"\\n\")) + (if (.outputs.trigger | has(\"open_as_draft\") | not) or .outputs.trigger.open_as_draft then \"\\n\\nOpen the resulting pull request as a draft.\" else \"\" end) }}",
"repo_name": "{{ .outputs.trigger.repository // .outputs.trigger.impactedEntity.identifier }}"
},
"reportWorkflowStatus": true
},
"onFailure": "terminate"
}
}
]
}
The entity context puts Continuously fix readiness gaps on the repository page. Pick one repository whose scorecard is below Agents Can Act, select the failed rules, and run the workflow. When the run succeeds, GitHub shows a pull request on that repository. Merge it, wait for the next GitHub sync, and confirm the boolean properties and the scorecard level move up.
To check the automatic path, remove a mapped file such as AGENTS.md from a repository that was passing that rule. After the next sync, the rule result flips to Not passed and the workflow opens a draft pull request that adds the file back.
Review instruction quality
The scorecard passes when a file exists. A title-only AGENTS.md, or a README that disagrees with it, still leaves a coding agent with bad instructions. This workflow runs when readme, ai_instructions, or claude_md changes and asks Port AI to review three practices drawn from the AGENTS.md convention:
- README and the agent instruction file agree when both state a command, an owner, or a safety rule.
- The instruction file has operating content. An empty file, or a file that is only a heading, leaves the agent without a way to work in the repository.
- The instruction file includes concrete examples, such as the install, test, and lint commands an agent can run.
When the review finds a problem, Claude Code opens a draft pull request with the suggested edits.
- Go to the workflows page in Port.
- Click + Workflow.
- Fill out the Create new workflow form. For Name, enter
Review Agent instruction quality, then click Confirm. - Click the
{...}button to open the JSON editor and paste the workflow below. - Click Apply changes.
Review instruction quality workflow (click to expand)
{
"identifier": "review_agent_instructions",
"title": "Review Agent instruction quality",
"icon": "AI",
"description": "Reviews README and agent instruction files after they change, and opens a draft pull request when they contradict each other, are nearly empty, or lack examples.",
"allowAnyoneToViewRuns": true,
"connections": [
{
"sourceIdentifier": "files_updated",
"targetIdentifier": "review_instructions"
},
{
"sourceIdentifier": "review_instructions",
"targetIdentifier": "needs_fix"
},
{
"sourceIdentifier": "needs_fix",
"targetIdentifier": "dispatch_claude_code",
"sourceOutletIdentifier": "fix"
}
],
"nodes": [
{
"identifier": "files_updated",
"title": "On instruction file change",
"config": {
"type": "EVENT_TRIGGER",
"event": {
"type": "ENTITY_UPDATED",
"blueprintIdentifier": "githubRepository"
},
"condition": {
"type": "JQ",
"combinator": "or",
"expressions": [
"(.diff.before.properties.readme // \"\") != (.diff.after.properties.readme // \"\")",
"(.diff.before.properties.ai_instructions // \"\") != (.diff.after.properties.ai_instructions // \"\")",
"(.diff.before.properties.claude_md // \"\") != (.diff.after.properties.claude_md // \"\")"
]
}
}
},
{
"identifier": "review_instructions",
"title": "Review instruction files",
"config": {
"type": "AI",
"tools": [],
"systemPrompt": "You review repository instruction files for coding agents. Use only the text in the prompt. Report contradictions between README and the agent instruction files, files that are empty or only a heading, and instruction files that lack a concrete install, test, or lint example.",
"userPrompt": "Review these files for repository {{ .outputs.files_updated.diff.after.identifier }}.\n\nREADME:\n{{ .outputs.files_updated.diff.after.properties.readme }}\n\nAGENTS.md:\n{{ .outputs.files_updated.diff.after.properties.ai_instructions }}\n\nCLAUDE.md:\n{{ .outputs.files_updated.diff.after.properties.claude_md }}",
"outputSchema": {
"type": "object",
"properties": {
"needs_fix": { "type": "boolean" },
"summary": { "type": "string" },
"issues": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["needs_fix", "summary", "issues"]
}
}
},
{
"identifier": "needs_fix",
"title": "Issues found",
"config": {
"type": "CONDITION",
"outlets": [
{
"identifier": "fix",
"title": "Needs a pull request",
"expression": "(.outputs.review_instructions.response | fromjson | .needs_fix) == true"
}
]
}
},
{
"identifier": "dispatch_claude_code",
"title": "Dispatch Claude Code",
"config": {
"type": "INTEGRATION_ACTION",
"installationId": "<YOUR_GITHUB_OCEAN_INTEGRATION_ID>",
"integrationProvider": "github-ocean",
"integrationInvocationType": "dispatch_workflow",
"integrationActionExecutionProperties": {
"org": "<GITHUB_ORG>",
"repo": "<GITHUB_REPO>",
"workflow": "claude-backend.yaml",
"workflowInputs": {
"command": "{{ \"Update the README and agent instruction files to resolve this review:\\n\\n\" + (.outputs.review_instructions.response | fromjson | .summary) + \"\\n\\nOpen the resulting pull request as a draft.\" }}",
"repo_name": "{{ .outputs.files_updated.diff.after.identifier }}"
},
"reportWorkflowStatus": true
},
"onFailure": "terminate"
}
}
]
}
Replace the same GitHub placeholders as in the fix workflow before you save. When the review finds no issues, the condition has no matching outlet and the run ends without opening a pull request.
Visualize readiness
We will now create a dashboard to track readiness over time and across teams:
- Repos tracked is the number of GitHub repositories in the catalog.
- Repositories by AI readiness level is how those repositories split across the scorecard levels.
- AI Readiness score trend is the monthly count of passing rule results, with one line per level.
- Most common failing rules shows which AI Readiness rules fail most often.
- Readiness by team lists repositories grouped by the
githubTeamsrelation. - Most agent-ready teams is the Leaderboard plugin from the Plugins library. It ranks
githubTeamentities by their AI readiness pass rate, the percentage of AI Readiness scorecard rule checks passed across their owned repositories.
Add an AI readiness pass rate
A raw count of passed rules favors teams with more repositories. Add a rate on the GitHub Team blueprint so the leaderboard compares teams of different sizes.
- Go to the Data model page in Port.
- Open the GitHub Team blueprint (
githubTeam). - Click the
...button, then choose Edit JSON. - Add these two keys under
aggregationProperties, and the key below undercalculationProperties. Keep any properties that are already there. - Click Save.
Pass rate properties (click to expand)
Add these two keys under aggregationProperties
"ai_readiness_rules_passed": {
"title": "AI Readiness rules passed",
"icon": "Rocket",
"description": "Total AI Readiness scorecard rule checks passed across all repositories owned by this team. A higher number means the team is more agent-ready.",
"type": "number",
"target": "_rule_result",
"calculationSpec": {
"calculationBy": "entities",
"func": "count"
},
"query": {
"combinator": "and",
"rules": [
{
"property": "scorecard",
"operator": "=",
"value": "AI Readiness"
},
{
"property": "result",
"operator": "=",
"value": "Passed"
}
]
}
},
"ai_readiness_rules_tested": {
"title": "AI Readiness rules tested",
"icon": "Properties",
"description": "Total AI Readiness scorecard rule checks evaluated across all repositories owned by this team.",
"type": "number",
"target": "_rule_result",
"calculationSpec": {
"calculationBy": "entities",
"func": "count"
},
"query": {
"combinator": "and",
"rules": [
{
"property": "scorecard",
"operator": "=",
"value": "AI Readiness"
}
]
}
}
Now add this JSON under the calculationProperties
"ai_readiness_pass_rate": {
"title": "AI Readiness pass rate %",
"icon": "LineChart",
"description": "Percentage of AI Readiness scorecard rule checks passed across this team's repositories. This rate normalizes for team size, unlike a raw pass count.",
"type": "number",
"calculation": "if (.properties.ai_readiness_rules_tested // 0) > 0 then ((.properties.ai_readiness_rules_passed // 0) / .properties.ai_readiness_rules_tested * 100 * 100 | round / 100) else null end",
"colorized": true,
"colors": {
"0-49": "red",
"50-79": "yellow",
"80-100": "green"
}
}
The pass rate is passed rules divided by tested rules. The leaderboard sorts teams by ai_readiness_pass_rate.
Install the leaderboard plugin
- Click your profile picture and select Settings.
- Open Plugins and click Library.
- Find Leaderboard and click Add plugin.
- API script
- Manual setup
Get your Port API token
-
Open the Credentials modal.
-
Click Generate API token.
-
Copy the token and store it as an environment variable:
export PORT_ACCESS_TOKEN="YOUR_GENERATED_TOKEN"
If Port is hosted in the EU region, replace api.port.io with api.port-eu.io in the command below.
Create the dashboard with widgets
Save the following JSON as agent_readiness_dashboard.json. pluginId is the Leaderboard plugin identifier shown on the Plugins page after you add it. The library install uses leaderboard-port-plugin.Agent readiness dashboard (click to expand)
Create the page:
curl -s -X POST "https://api.port.io/v1/pages" \
-H "Authorization: Bearer $PORT_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d @agent_readiness_dashboard.json | python3 -m json.tool
- Go to your software catalog.
- Click + and select New dashboard.
- Name it Agent Readiness.
- Description:
Tracks how ready your repositories are for AI coding agents: overall adoption, distribution across AI Readiness scorecard levels, trends over time, and the most common gaps blocking repositories from reaching higher levels. - Click Create.
Repositories by AI readiness level
- Click + Widget and select Pie chart.
- Title:
Repositories by AI readiness level. - Description:
Current distribution of repositories across the AI Readiness scorecard levels: Basic, Agents Have Context, Agents Can Act, and Agents Are Safe. - Blueprint: Repository.
- Breakdown by property: AI Readiness.
- Click Save.
AI Readiness score trend
- Click + Widget and select Line chart.
- Title:
AI Readiness Score Trend. - Description:
Monthly trend of passing AI Readiness rule results, broken down by scorecard level (Basic, Agents Have Context, Agents Can Act, Agents Are Safe), based on when each result last changed. - Time interval: Month. Time range: Last 12 months.
- Add a line. Chart type: Count entities. Blueprint: Scorecard Rule Result.
- Measure time by Result last change. Break down by Level.
- Filter:
scorecardisAI Readiness, andresultisPassed. - Click Save.
Most common failing rules
- Click + Widget and select Bar chart.
- Title:
Most common failing rules. - Description:
The AI Readiness scorecard rules that fail most often across all repositories, highlighting the biggest gaps to close. - Blueprint: Scorecard Rule Result. Break down by Title.
- Filter:
resultisNot passed, andscorecardisAI Readiness. - Click Save.
Most agent-ready teams
- Click + Widget, select Custom, then select Leaderboard.
- Title:
Most Agent-Ready Teams. - Description:
Ranks GitHub teams by their AI Readiness scorecard pass rate, the percentage of rule checks passed across their repositories. The rate is normalized so smaller teams rank fairly with larger ones. - Blueprint: GitHub Team. Sort property:
ai_readiness_pass_rate. Direction: descending. Limit:10. - Click Create.
Related guides
- Standardize AI coding rules across repositories authors the rule files in git and provisions them.
- Trigger AI coding assistants from Port sets up the Claude Code, Copilot, and Gemini backends these workflows dispatch.
- Auto-fix services when scorecards degrade reacts when a scorecard level drops.
- Set up a service production readiness scorecard grades ownership and documentation for production.