> For the complete documentation index, see llms.txt.
Skip to main content

Check out Port for yourself ➜ 

Track and measure agent readiness

Implement with AI

Send this guide to your coding agent.

Prerequisite: Install Port MCP

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:

  1. Ingest readiness signals from each GitHub repository.
  2. Grade repositories with an AI Readiness scorecard.
  3. 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.
  4. 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 githubRepository blueprint. If your mapping writes repositories to service instead, add the same properties there and replace every githubRepository reference in this guide.
  • You completed the Claude Code backend setup in Trigger AI coding assistants from Port.
  • GitHub teams sync into a githubTeam blueprint, and the repository blueprint can relate to it. The default GitHub mapping creates githubTeam and githubOrganization.

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.

LevelColorWhat it means
BasicLight grayThe repository misses at least one context rule.
Agents Have ContextYellowREADME, an agent instruction file, CODEOWNERS, and the repository URL are present.
Agents Can ActBlueThe repo can build, test, lint, and accept a pull request without guesswork.
Agents Are SafeGreenInstruction 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.

  1. Go to the Data model page in Port.
  2. Find and select the Repository blueprint (githubRepository).
  3. Click the ... button, then choose Edit blueprint, then {...} Edit JSON.
  4. Under schema.properties, add the properties in the next two sections.
  5. 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.

SignalPropertyPaths or check
READMEreadmeREADME.md
Agent instructionshas_agents_md, ai_instructionsAGENTS.md
Claude instructionshas_claude_md, claude_mdCLAUDE.md
CODEOWNERShas_codeownersCODEOWNERS, .github/CODEOWNERS, docs/CODEOWNERS
Devcontainerhas_devcontainer.devcontainer/devcontainer.json
Environment templatehas_env_example.env.example, .env.sample, .env.template
Pre-commithas_pre_commit.pre-commit-config.yaml, .husky/pre-commit
Dependency updateshas_dependency_updatesDependabot or Renovate config
Linterhas_lint_configESLint, Ruff, or golangci-lint
Test runnerhas_test_configJest, Vitest, pytest, or tox
Pull request templatehas_pr_template.github/ or docs/ pull request template
API spechas_api_specOpenAPI or Swagger at the repo root, or api/openapi.yaml
Security guardrailshas_agent_security_guardrailsInstruction text matches security, OWASP, injection, authentication, or input validation
Secret guardrailshas_secret_guardrailsInstruction text matches secret, credential, or hardcode
Parameterized querieshas_parameterized_query_guardrailInstruction 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.

  1. Go to the Connectors page in Port.
  2. Open your GitHub Ocean integration.
  3. Go to the Mapping tab.
  4. On the existing repository resource that maps to githubRepository, add the includedFiles list and the properties below. Keep the identifier, title, and any properties you already map.
  5. Set include: [teams] on that resource so githubTeams is populated. The dashboard groups by this relation.
  6. 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]'
Exact paths

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)

RuleLevelPasses when
README is presentAgents Have Contextreadme is not empty
AGENTS.md or CLAUDE.md presentAgents Have Contexthas_agents_md or has_claude_md is true
CODEOWNERS presentAgents Have Contexthas_codeowners is true
Repository URL linked in catalogAgents Have Contexturl is not empty
Pre-commit hooks configuredAgents Can Acthas_pre_commit is true
PR template presentAgents Can Acthas_pr_template is true
Environment template presentAgents Can Acthas_env_example is true
Test runner configuredAgents Can Acthas_test_config is true
Linter configuredAgents Can Acthas_lint_config is true
Devcontainer presentAgents Can Acthas_devcontainer is true
Dependency updates configuredAgents Can Acthas_dependency_updates is true
Build and test commands documentedAgents Can Actreadme contains a concrete build or test command
API spec presentAgents Can Acthas_api_spec is true
Hardcoded secrets prohibitedAgents Are Safehas_secret_guardrails is true
Agent PRs gated by CIAgents Are Safeis_protected is true
Parameterized queries enforcedAgents Are Safehas_parameterized_query_guardrail is true
Code owner review requiredAgents Are Saferequire_code_owner_review is true
Agent security guardrails definedAgents Are Safehas_agent_security_guardrails is true
  1. Go to the Data model page in Port.
  2. Search for the Repository blueprint and select it.
  3. Click the Scorecards tab.
  4. Click + New Scorecard.
  5. Click {...} Edit JSON and paste the configuration below.
  6. 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.

  1. Go to the workflows page in Port.
  2. Click + Workflow.
  3. Fill out the Create new workflow form. For Name, enter Continuously fix readiness gaps, then click Confirm.
  4. Click the {...} button to open the JSON editor and paste the workflow below.
  5. 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.

  1. Go to the workflows page in Port.
  2. Click + Workflow.
  3. Fill out the Create new workflow form. For Name, enter Review Agent instruction quality, then click Confirm.
  4. Click the {...} button to open the JSON editor and paste the workflow below.
  5. 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​

AI Readiness Dashboard

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 githubTeams relation.
  • Most agent-ready teams is the Leaderboard plugin from the Plugins library. It ranks githubTeam entities 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.

  1. Go to the Data model page in Port.
  2. Open the GitHub Team blueprint (githubTeam).
  3. Click the ... button, then choose Edit JSON.
  4. Add these two keys under aggregationProperties, and the key below under calculationProperties. Keep any properties that are already there.
  5. 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​

  1. Click your profile picture and select Settings.
  2. Open Plugins and click Library.
  3. Find Leaderboard and click Add plugin.

Get your Port API token

  1. Open the Credentials modal.

  2. Click Generate API token.

  3. Copy the token and store it as an environment variable:

    export PORT_ACCESS_TOKEN="YOUR_GENERATED_TOKEN"
EU region

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)
{
"identifier": "agent_readiness_dashboard",
"title": "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.",
"icon": "AI",
"type": "dashboard",
"sidebar": "catalog",
"widgets": [
{
"id": "dashboard-widget-root",
"type": "dashboard-widget",
"layout": [
{
"height": 400,
"columns": [
{ "id": "kpi_total", "size": 4 },
{ "id": "readiness_by_pillar", "size": 8 }
]
},
{
"height": 400,
"columns": [
{ "id": "ai_readiness_score_trend", "size": 12 }
]
},
{
"height": 400,
"columns": [
{ "id": "failing_rules", "size": 12 }
]
},
{
"height": 500,
"columns": [
{ "id": "readiness_by_team", "size": 6 },
{ "id": "ai_readiness_team_leaderboard", "size": 6 }
]
}
],
"widgets": [
{
"id": "kpi_total",
"type": "entities-number-chart",
"title": "Repos tracked",
"description": "Total number of GitHub repositories currently being tracked for AI Readiness.",
"blueprint": "githubRepository",
"chartType": "countEntities",
"func": "count",
"unit": "none",
"dataset": { "combinator": "and", "rules": [] }
},
{
"id": "readiness_by_pillar",
"type": "entities-pie-chart",
"icon": "Score",
"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": "githubRepository",
"property": "scorecard#ai_readiness_scorecard",
"dataset": { "combinator": "and", "rules": [] }
},
{
"id": "ai_readiness_score_trend",
"type": "multi-line-chart",
"icon": "LineChart",
"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.",
"timeInterval": "month",
"timeRange": { "preset": "last12Months" },
"xAxisTitle": "Month",
"yAxisTitle": "Passing rule results",
"nullValueDisplayMode": "zero",
"lines": [
{
"chartType": "countEntities",
"title": "Passing rule results by level",
"blueprint": "_rule_result",
"func": "count",
"measureTimeBy": "result_last_change",
"breakdownProperty": "level",
"dataset": {
"combinator": "and",
"rules": [
{ "property": "scorecard", "operator": "=", "value": "AI Readiness" },
{ "property": "result", "operator": "=", "value": "Passed" }
]
}
}
]
},
{
"id": "failing_rules",
"type": "bar-chart",
"icon": "Alert",
"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": "_rule_result",
"property": "$title",
"dataset": {
"combinator": "and",
"rules": [
{ "property": "result", "operator": "=", "value": "Not passed" },
{ "property": "scorecard", "operator": "=", "value": "AI Readiness" }
]
}
},
{
"id": "readiness_by_team",
"type": "table-entities-explorer",
"title": "Readiness by team",
"description": "Repository-level AI Readiness scorecard results and supporting signals, grouped by owning GitHub team.",
"blueprint": "githubRepository",
"displayMode": "widget",
"dataset": { "combinator": "and", "rules": [] },
"blueprintConfig": {
"githubRepository": {
"groupSettings": { "groupBy": ["githubTeams"] },
"propertiesSettings": {
"order": ["$title", "ai_readiness_scorecard", "$updatedAt"],
"shown": ["$title", "ai_readiness_scorecard", "readme", "url", "language", "ai_instructions", "claude_md"]
}
}
}
},
{
"id": "ai_readiness_team_leaderboard",
"type": "custom-widget",
"icon": "Rocket",
"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.",
"pluginId": "leaderboard-port-plugin",
"paramValues": {
"blueprint": { "value": "githubTeam", "type": "blueprint" },
"sortProperty": { "value": "ai_readiness_pass_rate", "type": "string" },
"sortDirection": { "value": "desc", "type": "string" },
"limit": { "value": 10, "type": "number" }
}
}
]
}
]
}

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