diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e92a79e..6151c24 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,6 +49,11 @@ See [skills/README.md](skills/README.md) for the full list. The load-bearing one Deliberate history notes — and explicitly-scoped engine-development examples of patterns banned for analysis — take `` on the preceding line. 4. Descriptions stay ≤1024 characters (Codex hard limit). +5. **No browser call to the Public API**: `tests/test_public_api_browser_guidance.py` fails on a + TypeScript, JavaScript, HTML, Vue or Svelte example that names `api.policyengine.org` or its + Cloud Run addresses, and on a `NEXT_PUBLIC_*`, `VITE_*` or `REACT_APP_*` variable that holds + its address or a credential (see the policyengine-api-access skill). A server-side example + that must name it takes `` on the line before its fence. ## Testing diff --git a/bundles/analysis-tools.json b/bundles/analysis-tools.json index 7192011..12f25cf 100644 --- a/bundles/analysis-tools.json +++ b/bundles/analysis-tools.json @@ -2,7 +2,7 @@ "name": "analysis-tools", "description": "Policy analysis and research - impact studies, dashboards, notebooks, and visualizations", "category": "analysis", - "version": "4.1.0", + "version": "4.2.0", "keywords": [ "analysis", "research", @@ -25,6 +25,7 @@ "./skills/policyengine-canada", "./skills/policyengine-healthcare", "./skills/policyengine-user-guide", + "./skills/policyengine-api-access", "./skills/policyengine-design", "./skills/policyengine-writing", "./skills/policyengine-research-lookup", diff --git a/bundles/api-development.json b/bundles/api-development.json index cdee630..5b5d98f 100644 --- a/bundles/api-development.json +++ b/bundles/api-development.json @@ -2,7 +2,7 @@ "name": "api-development", "description": "API development - Flask endpoints, caching, services, and integration patterns", "category": "development", - "version": "4.1.0", + "version": "4.2.0", "keywords": [ "api", "flask", @@ -18,6 +18,7 @@ "hooks": null, "skills": [ "./skills/policyengine-api", + "./skills/policyengine-api-access", "./skills/policyengine-model-development", "./skills/policyengine-us", "./skills/policyengine-uk", diff --git a/bundles/app-development.json b/bundles/app-development.json index 4645926..4c2bc3d 100644 --- a/bundles/app-development.json +++ b/bundles/app-development.json @@ -2,7 +2,7 @@ "name": "app-development", "description": "React app development - app-v2, standalone tools, Vercel deployment, and PolicyEngine-specific patterns", "category": "development", - "version": "4.1.0", + "version": "4.2.0", "keywords": [ "react", "frontend", @@ -21,6 +21,7 @@ "skills": [ "./skills/policyengine-app", "./skills/policyengine-tools", + "./skills/policyengine-api-access", "./skills/policyengine-model-development", "./skills/policyengine-us", "./skills/policyengine-uk", diff --git a/bundles/complete.json b/bundles/complete.json index 3d73563..16a0619 100644 --- a/bundles/complete.json +++ b/bundles/complete.json @@ -2,7 +2,7 @@ "name": "complete", "description": "Complete PolicyEngine knowledge base - all agents, commands, and skills for users, analysts, and contributors", "category": "complete", - "version": "4.1.0", + "version": "4.2.0", "keywords": [ "complete", "all", @@ -25,6 +25,7 @@ "./skills/policyengine-tools", "./skills/policyengine-app", "./skills/policyengine-api", + "./skills/policyengine-api-access", "./skills/policyengine-design", "./skills/policyengine-standards", "./skills/policyengine-cloud-costs", diff --git a/bundles/country-models.json b/bundles/country-models.json index 9ac521b..fc67d1a 100644 --- a/bundles/country-models.json +++ b/bundles/country-models.json @@ -2,7 +2,7 @@ "name": "country-models", "description": "Complete multi-agent workflow for implementing government benefit programs in PolicyEngine country packages", "category": "development", - "version": "4.1.0", + "version": "4.2.0", "keywords": [ "tax", "benefits", diff --git a/bundles/dashboard-builder.json b/bundles/dashboard-builder.json index cac2260..10cd985 100644 --- a/bundles/dashboard-builder.json +++ b/bundles/dashboard-builder.json @@ -2,7 +2,7 @@ "name": "dashboard-builder", "description": "Orchestrated AI workflow for creating PolicyEngine dashboards from natural-language descriptions", "category": "development", - "version": "4.1.0", + "version": "4.2.0", "keywords": [ "dashboard", "calculator", @@ -21,6 +21,7 @@ "skills": [ "./skills/policyengine-tools", "./skills/policyengine-api", + "./skills/policyengine-api-access", "./skills/policyengine-app", "./skills/policyengine-us", "./skills/policyengine-uk", diff --git a/bundles/data-science.json b/bundles/data-science.json index 8e87e9a..19cbe5d 100644 --- a/bundles/data-science.json +++ b/bundles/data-science.json @@ -2,7 +2,7 @@ "name": "data-science", "description": "Data analysis, survey enhancement, imputation, calibration, and microdata utilities", "category": "data", - "version": "4.1.0", + "version": "4.2.0", "keywords": [ "data", "microdata", diff --git a/bundles/essential.json b/bundles/essential.json index 09dea39..7e196eb 100644 --- a/bundles/essential.json +++ b/bundles/essential.json @@ -2,7 +2,7 @@ "name": "essential", "description": "Essential PolicyEngine knowledge for all users - understanding the platform, using the web app, and basic concepts", "category": "getting-started", - "version": "4.1.0", + "version": "4.2.0", "keywords": [ "beginner", "users", diff --git a/dashboard/src/data/manifest.json b/dashboard/src/data/manifest.json index 89b5f54..f088f21 100644 --- a/dashboard/src/data/manifest.json +++ b/dashboard/src/data/manifest.json @@ -1,7 +1,7 @@ { - "generated_at": "2026-11-07T12:00:17.220492Z", + "generated_at": "2026-11-10T22:02:06.973729Z", "counts": { - "skill": 24, + "skill": 25, "agent": 36, "command": 20, "bundle": 8, @@ -258,7 +258,7 @@ "id": "policyengine", "kind": "skill", "name": "policyengine", - "description": "ALWAYS load this skill before writing any Python that computes taxes, benefits, or policy\nimpacts with PolicyEngine \u2014 household calculations, microsimulation, reform scoring, poverty\nor distributional analysis, state/district/constituency breakdowns.\nTriggers: policyengine, microsimulation, calculate_household, reform impact, budgetary impact,\ncost of a policy, revenue estimate, poverty rate, child poverty, winners and losers, decile,\nGini, inequality, CTC, EITC, SNAP, income tax, universal credit, state-level analysis,\ncongressional district, constituency, Microcosm dataset, MicroSeries, ensure_datasets,\neconomic_impact_analysis, managed_microsimulation.\nNOT for: implementing new variables/parameters inside country models (use\npolicyengine-model-development) or calling the REST API from JS (use policyengine-api).", + "description": "ALWAYS load this skill before writing any Python that computes taxes, benefits, or policy\nimpacts with PolicyEngine \u2014 household calculations, microsimulation, reform scoring, poverty\nor distributional analysis, state/district/constituency breakdowns.\nTriggers: policyengine, microsimulation, calculate_household, reform impact, budgetary impact,\ncost of a policy, revenue estimate, poverty rate, child poverty, winners and losers, decile,\nGini, inequality, CTC, EITC, SNAP, income tax, universal credit, state-level analysis,\ncongressional district, constituency, Microcosm dataset, MicroSeries, ensure_datasets,\neconomic_impact_analysis, managed_microsimulation.\nNOT for: implementing new variables/parameters inside country models (use\npolicyengine-model-development) or calling the REST API from a page or server (use\npolicyengine-api-access).", "category": "analysis", "path": "skills/policyengine/SKILL.md", "tools": [], @@ -303,7 +303,7 @@ "id": "policyengine-api", "kind": "skill", "name": "policyengine-api", - "description": "Load when consuming or developing the PolicyEngine REST APIs \u2014 the v1 Flask API at\napi.policyengine.org (production today) or the v2 alpha microservices in policyengine-api-v2.\nCovers the v1 endpoint surface (household calc, economy-over-baseline polling), the v2\narchitecture (three services, uv/Docker/Cloud Run, OpenAPI client-gen), and when to use REST\nvs the Python package.\nTriggers: PolicyEngine API, api.policyengine.org, REST endpoint, household calculate, economy\nover baseline, v2 API, api-full, api-simulation, api-tagger, OpenAPI client, v2.api.policyengine.org.\nNOT for: Python analysis (use the policyengine skill) or calling the API from the app\nfrontend (policyengine-app).", + "description": "Load when developing the PolicyEngine REST APIs or writing server code against them \u2014 the v1\nFlask API at api.policyengine.org (the Public API, production today, callable only with a\nserver credential) or the v2 alpha microservices in policyengine-api-v2. Covers the v1\nendpoint surface (household calc, economy-over-baseline polling), the v2 architecture (three\nservices, uv/Docker/Cloud Run, OpenAPI client-gen), and when to use REST vs the Python package.\nTriggers: PolicyEngine API, api.policyengine.org, REST endpoint, household calculate, economy\nover baseline, v2 API, api-full, api-simulation, api-tagger, OpenAPI client, v2.api.policyengine.org.\nNOT for: Python analysis (use the policyengine skill), or reaching the API from a dashboard\npage or your own server (policyengine-api-access).", "category": "apps", "path": "skills/policyengine-api/SKILL.md", "tools": [], @@ -339,7 +339,44 @@ "use_instead": [], "registry_notes": "Recommended for the listed scope.", "references": {}, - "body_length": 4633 + "body_length": 5327 + }, + { + "id": "policyengine-api-access", + "kind": "skill", + "name": "policyengine-api-access", + "description": "Load before writing or reviewing code that reaches the PolicyEngine Public API\n(api.policyengine.org) from a dashboard, tool, backend, Modal app, Vercel function or CI\njob. The Public API serves only PolicyEngine's servers: pages call www.policyengine.org's\ndashboard API through @policyengine/api-client, and servers send their own Auth0\nclient-credentials token. Covers the rules, the four dashboard operations, polling, allowed\norigins, cached server tokens, getting a client, local development and previews, asking for a\nnew operation, and 401/403/429/503.\nTriggers: api.policyengine.org, Public API, dashboard API, @policyengine/api-client, household\ncalculate from the browser, economy comparison polling, NEXT_PUBLIC_API_URL, VITE_API_URL,\nAuth0 client credentials, server token, PUBLIC_API_SERVER_CLIENT_IDS, Authentication is\nrequired, CORS error from the API.\nNOT for: Python analysis (policyengine skill) or the Public API's own endpoints and code\n(policyengine-api).", + "category": "apps", + "path": "skills/policyengine-api-access/SKILL.md", + "tools": [], + "model": null, + "triggers": [], + "bundles": [ + "analysis-tools", + "api-development", + "app-development", + "complete", + "dashboard-builder" + ], + "target_repos": [ + "policyengine-app-v2", + "policyengine-api", + "policyengine-app", + "policyengine-us" + ], + "functional_role": null, + "functional_summary": null, + "functional_scope": [], + "functional_supersedes": [], + "registry_status": "recommended", + "registry_owner": "Frontend & product", + "recommended_for": [ + "Load this skill for work involving: Load before writing or reviewing code that reaches the PolicyEngine Public API\n(api.policyengine.org) from a dashboard, tool, backend, Modal app, Vercel function or CI\njob. The Public API serves only PolicyEngine's servers: pages call www.policyengine.org's\ndashboard API through @policyengine/api-client, and servers send their own Auth0\nclient-credentials token. Covers the rules, the four dashboard operations, polling, allowed\norigins, cached server tokens, getting a client, local development and previews, asking for a\nnew operation, and 401/403/429/503.\nTriggers: api.policyengine.org, Public API, dashboard API, @policyengine/api-client, household\ncalculate from the browser, economy comparison polling, NEXT_PUBLIC_API_URL, VITE_API_URL,\nAuth0 client credentials, server token, PUBLIC_API_SERVER_CLIENT_IDS, Authentication is\nrequired, CORS error from the API.\nNOT for: Python analysis (policyengine skill) or the Public API's own endpoints and code\n(policyengine-api)." + ], + "use_instead": [], + "registry_notes": "Recommended for the listed scope.", + "references": {}, + "body_length": 19355 }, { "id": "policyengine-app", @@ -383,7 +420,7 @@ ], "registry_notes": "A newer artifact explicitly supersedes this one. Prefer the replacement.", "references": {}, - "body_length": 6445 + "body_length": 7734 }, { "id": "policyengine-calibration-diagnostics", @@ -572,7 +609,7 @@ "id": "policyengine-data", "kind": "skill", "name": "policyengine-data", - "description": "Load for PolicyEngine's data layer \u2014 how Microcosm builds, calibrates, versions, and names the\nmicrodata behind population runs. Covers the Microcosm stack (Frame kernel, microcosm-fit\nconditional models, microcosm-calibrate weights with opt-in L0 pruning, build/release gates),\nthe certified datasets in policyengine bundles (populace_us_2024 57k-household frozen-support\ndefault, populace_us_2024_acs_local ~1.6M local-area, microcosm_uk_2024_25 private),\nlocal-area runs as one national dataset plus a geography filter, the calibration diagnostics\ndashboard, and where data work goes since PolicyEngine archived policyengine-us-data.\nTriggers: Microcosm, Frame, microcosm-fit, microcosm-calibrate, calibration target, survey\nweights, reweighting, imputation, QRF, quantile loss, L0 sparsity, sparse 57k, dense_no_l0,\nlocal-area data, ACS, FRS, WAS, enhanced microdata, DEFAULT_DATASET.\nNOT for: running simulations (see policyengine) or diagnosing a specific score mismatch (see\npolicyengine-calibration-diagnostics).", + "description": "Load for PolicyEngine's data layer \u2014 how Microcosm builds, calibrates, versions, and names the\nmicrodata behind population runs. Covers the Microcosm stack (Frame kernel, microcosm-fit\nmodels, microcosm-calibrate weights with opt-in L0 pruning, release gates), the certified\ndatasets in policyengine bundles (populace_us_2024 57k-household frozen-support default,\npopulace_us_2024_acs_local ~1.6M local-area, microcosm_uk_2024_25 private), local-area runs as\none national dataset plus a geography filter, the calibration dashboard, and where data work\ngoes since PolicyEngine archived policyengine-us-data.\nTriggers: Microcosm, Frame, microcosm-fit, microcosm-calibrate, microimpute, microcalibrate,\ncalibration target, survey weights, reweighting, imputation, QRF, quantile loss, L0 sparsity,\nsparse 57k, dense_no_l0, local-area data, ACS, FRS, WAS, enhanced microdata, DEFAULT_DATASET.\nNOT for: running simulations (see policyengine) or diagnosing a specific score mismatch (see\npolicyengine-calibration-diagnostics).", "category": "data", "path": "skills/policyengine-data/SKILL.md", "tools": [], @@ -586,8 +623,8 @@ "target_repos": [ "calibration-diagnostics", "policyengine-us-data", + "policyengine-core", "policyengine-us", - "policyengine.py", "microcalibrate", "microimpute", "microunit", @@ -616,7 +653,7 @@ "use_instead": [], "registry_notes": "Recommended for the listed scope.", "references": {}, - "body_length": 21011 + "body_length": 22881 }, { "id": "policyengine-design", @@ -1001,7 +1038,7 @@ "id": "policyengine-tools", "kind": "skill", "name": "policyengine-tools", - "description": "Load when building a standalone PolicyEngine interactive tool, calculator, or dashboard\nthat deploys to Vercel and embeds into policyengine.org as a Next.js multizone.\nCovers the required stack (Next App Router + Tailwind v4 + @policyengine/ui-kit + bun),\nmultizone zone config (basePath/assetPrefix, host rewrites, apps.json), Modal backends\n(gateway + worker + polling), chart patterns, and the dual-mode SEO strategy.\nTriggers: new tool, standalone calculator, dashboard, embed in policyengine.org, multizone,\nbasePath, assetPrefix, appZoneRoutes, Modal backend, scale to zero, vercel --scope\npolicy-engine, ui-kit theme.css, recharts colors.\nNOT for: developing policyengine.org itself (use policyengine-app) or token values\n(policyengine-design).", + "description": "Load when building a standalone PolicyEngine interactive tool, calculator, or dashboard\nthat deploys to Vercel and embeds into policyengine.org as a Next.js multizone.\nCovers the required stack (Next App Router + Tailwind v4 + @policyengine/ui-kit + bun),\nmultizone zone config (basePath/assetPrefix, host rewrites, apps.json), Modal backends\n(gateway + worker + polling), chart patterns, and the dual-mode SEO strategy.\nTriggers: new tool, standalone calculator, dashboard, embed in policyengine.org, multizone,\nbasePath, assetPrefix, appZoneRoutes, Modal backend, scale to zero, vercel --scope\npolicy-engine, ui-kit theme.css, recharts colors.\nNOT for: developing policyengine.org itself (use policyengine-app), token values\n(policyengine-design), or how a page or backend reaches api.policyengine.org\n(policyengine-api-access).", "category": "apps", "path": "skills/policyengine-tools/SKILL.md", "tools": [], @@ -1014,6 +1051,7 @@ ], "target_repos": [ "policyengine-app-v2", + "policyengine-api", "policyengine-app", "policyengine.py", "marriage", @@ -1043,7 +1081,7 @@ "use_instead": [], "registry_notes": "Recommended for the listed scope.", "references": {}, - "body_length": 14583 + "body_length": 15548 }, { "id": "policyengine-uk", @@ -2141,6 +2179,7 @@ "references": { "skill": [ "policyengine-api", + "policyengine-api-access", "policyengine-cloud-costs", "policyengine-tools", "policyengine-uk", @@ -2150,7 +2189,7 @@ "deploy-dashboard" ] }, - "body_length": 22032 + "body_length": 24570 }, { "id": "dashboard-architecture-validator", @@ -2296,11 +2335,12 @@ ], "skill": [ "policyengine-api", + "policyengine-api-access", "policyengine-design", "policyengine-tools" ] }, - "body_length": 6350 + "body_length": 9815 }, { "id": "dashboard-plan-validator", @@ -2397,6 +2437,7 @@ ], "skill": [ "policyengine-api", + "policyengine-api-access", "policyengine-app", "policyengine-cloud-costs", "policyengine-design", @@ -2409,7 +2450,7 @@ "analyze-policy" ] }, - "body_length": 24586 + "body_length": 24997 }, { "id": "dashboard-scaffold", @@ -2455,8 +2496,12 @@ "use_instead": [], "registry_notes": "Agents are usually implementation details of commands; call directly only when maintaining or debugging the workflow.", "references": { + "agent": [ + "backend-builder" + ], "skill": [ "policyengine-api", + "policyengine-api-access", "policyengine-app", "policyengine-cloud-costs", "policyengine-design", @@ -2467,7 +2512,7 @@ "deploy-dashboard" ] }, - "body_length": 25960 + "body_length": 26646 }, { "id": "frontend-builder", @@ -3809,11 +3854,8 @@ "complete" ], "target_repos": [ - "policyengine-api-v2", "policyengine-api", "marriage", - "policyengine-us", - "policyengine-uk", "policyengine-app" ], "functional_role": "workflow:new-tool", @@ -3832,6 +3874,7 @@ "references": { "skill": [ "policyengine-api", + "policyengine-api-access", "policyengine-app", "policyengine-cloud-costs", "policyengine-design", @@ -3842,7 +3885,7 @@ "new-tool" ] }, - "body_length": 15206 + "body_length": 16117 }, { "id": "prior-analysis", @@ -4119,7 +4162,7 @@ "use_instead": [], "registry_notes": "Bundle install profile.", "references": {}, - "body_length": 2135 + "body_length": 2175 }, { "id": "api-development", @@ -4145,7 +4188,7 @@ "use_instead": [], "registry_notes": "Bundle install profile.", "references": {}, - "body_length": 1054 + "body_length": 1094 }, { "id": "app-development", @@ -4171,7 +4214,7 @@ "use_instead": [], "registry_notes": "Bundle install profile.", "references": {}, - "body_length": 1461 + "body_length": 1501 }, { "id": "complete", @@ -4197,7 +4240,7 @@ "use_instead": [], "registry_notes": "Bundle install profile.", "references": {}, - "body_length": 3730 + "body_length": 3770 }, { "id": "country-models", @@ -4249,7 +4292,7 @@ "use_instead": [], "registry_notes": "Bundle install profile.", "references": {}, - "body_length": 1326 + "body_length": 1366 }, { "id": "data-science", @@ -4965,6 +5008,12 @@ "target_kind": "skill", "target": "policyengine-api" }, + { + "source_kind": "command", + "source": "new-tool", + "target_kind": "skill", + "target": "policyengine-api-access" + }, { "source_kind": "command", "source": "new-tool", @@ -5133,6 +5182,12 @@ "target_kind": "skill", "target": "policyengine-api" }, + { + "source_kind": "agent", + "source": "backend-builder", + "target_kind": "skill", + "target": "policyengine-api-access" + }, { "source_kind": "agent", "source": "backend-builder", @@ -5259,6 +5314,12 @@ "target_kind": "skill", "target": "policyengine-api" }, + { + "source_kind": "agent", + "source": "dashboard-integrator", + "target_kind": "skill", + "target": "policyengine-api-access" + }, { "source_kind": "agent", "source": "dashboard-integrator", @@ -5301,6 +5362,12 @@ "target_kind": "skill", "target": "policyengine-api" }, + { + "source_kind": "agent", + "source": "dashboard-planner", + "target_kind": "skill", + "target": "policyengine-api-access" + }, { "source_kind": "agent", "source": "dashboard-planner", @@ -5343,12 +5410,24 @@ "target_kind": "skill", "target": "policyengine-writing" }, + { + "source_kind": "agent", + "source": "dashboard-scaffold", + "target_kind": "agent", + "target": "backend-builder" + }, { "source_kind": "agent", "source": "dashboard-scaffold", "target_kind": "skill", "target": "policyengine-api" }, + { + "source_kind": "agent", + "source": "dashboard-scaffold", + "target_kind": "skill", + "target": "policyengine-api-access" + }, { "source_kind": "agent", "source": "dashboard-scaffold", @@ -6057,6 +6136,12 @@ "target_kind": "skill", "target": "policyengine" }, + { + "source_kind": "bundle", + "source": "analysis-tools", + "target_kind": "skill", + "target": "policyengine-api-access" + }, { "source_kind": "bundle", "source": "analysis-tools", @@ -6213,6 +6298,12 @@ "target_kind": "skill", "target": "policyengine-api" }, + { + "source_kind": "bundle", + "source": "api-development", + "target_kind": "skill", + "target": "policyengine-api-access" + }, { "source_kind": "bundle", "source": "api-development", @@ -6315,6 +6406,12 @@ "target_kind": "skill", "target": "fix-pr" }, + { + "source_kind": "bundle", + "source": "app-development", + "target_kind": "skill", + "target": "policyengine-api-access" + }, { "source_kind": "bundle", "source": "app-development", @@ -6645,6 +6742,12 @@ "target_kind": "skill", "target": "policyengine-api" }, + { + "source_kind": "bundle", + "source": "complete", + "target_kind": "skill", + "target": "policyengine-api-access" + }, { "source_kind": "bundle", "source": "complete", @@ -7107,6 +7210,12 @@ "target_kind": "skill", "target": "policyengine-api" }, + { + "source_kind": "bundle", + "source": "dashboard-builder", + "target_kind": "skill", + "target": "policyengine-api-access" + }, { "source_kind": "bundle", "source": "dashboard-builder", @@ -7256,7 +7365,7 @@ "kind": "command", "id": "implement-structural" }, - "score": 0.6526, + "score": 0.6489, "shared_terms": [ "structural", "backlog", @@ -7277,7 +7386,7 @@ "kind": "command", "id": "generate-content" }, - "score": 0.6298, + "score": 0.6295, "shared_terms": [ "social", "content", @@ -7298,7 +7407,7 @@ "kind": "command", "id": "generate-content" }, - "score": 0.6287, + "score": 0.6283, "shared_terms": [ "social", "content", @@ -7319,7 +7428,7 @@ "kind": "agent", "id": "review-worker" }, - "score": 0.6233, + "score": 0.6191, "shared_terms": [ "your", "assigned", @@ -7328,7 +7437,28 @@ "coordinator", "executes", "task", - "access" + "test" + ] + }, + { + "a": { + "kind": "skill", + "id": "policyengine-api" + }, + "b": { + "kind": "skill", + "id": "policyengine-api-access" + }, + "score": 0.6187, + "shared_terms": [ + "api", + "client", + "public", + "server", + "household", + "org", + "credential", + "country" ] }, { @@ -7340,15 +7470,15 @@ "kind": "skill", "id": "policyengine-uk" }, - "score": 0.6031, + "score": 0.6019, "shared_terms": [ "household", "unit", - "income", "tax", + "income", "poverty", - "age", "person", + "age", "result" ] }, @@ -7361,7 +7491,7 @@ "kind": "command", "id": "prior-scores" }, - "score": 0.5994, + "score": 0.6005, "shared_terms": [ "scorekeepers", "scores", @@ -7382,7 +7512,7 @@ "kind": "skill", "id": "review-program" }, - "score": 0.588, + "score": 0.5874, "shared_terms": [ "review", "canonical", @@ -7403,14 +7533,14 @@ "kind": "command", "id": "audit-multizone" }, - "score": 0.5814, + "score": 0.5812, "shared_terms": [ "zone", "path", "host", "next", - "config", "repo", + "config", "app", "basepath" ] @@ -7424,7 +7554,7 @@ "kind": "skill", "id": "policyengine-us" }, - "score": 0.5677, + "score": 0.5673, "shared_terms": [ "unit", "tax", @@ -7445,7 +7575,7 @@ "kind": "skill", "id": "review-program" }, - "score": 0.5651, + "score": 0.5661, "shared_terms": [ "review", "canonical", @@ -7466,7 +7596,7 @@ "kind": "agent", "id": "content-orchestrator" }, - "score": 0.5387, + "score": 0.5391, "shared_terms": [ "social", "content", @@ -7487,7 +7617,7 @@ "kind": "skill", "id": "policyengine-us" }, - "score": 0.5305, + "score": 0.5307, "shared_terms": [ "unit", "tax", @@ -7508,7 +7638,7 @@ "kind": "skill", "id": "fix-pr" }, - "score": 0.5007, + "score": 0.5006, "shared_terms": [ "review", "claude", @@ -7520,6 +7650,27 @@ "references" ] }, + { + "a": { + "kind": "agent", + "id": "backend-builder" + }, + "b": { + "kind": "agent", + "id": "dashboard-scaffold" + }, + "score": 0.4933, + "shared_terms": [ + "api", + "plan", + "dashboard", + "custom", + "backend", + "modal", + "data", + "pattern" + ] + }, { "a": { "kind": "skill", @@ -7529,7 +7680,7 @@ "kind": "agent", "id": "calibration-diagnostics" }, - "score": 0.4911, + "score": 0.492, "shared_terms": [ "calibration", "signature", @@ -7550,7 +7701,7 @@ "kind": "agent", "id": "review-worker" }, - "score": 0.4801, + "score": 0.4805, "shared_terms": [ "review", "canonical", @@ -7571,7 +7722,7 @@ "kind": "agent", "id": "prior-scores-finder" }, - "score": 0.4749, + "score": 0.4757, "shared_terms": [ "prior", "tier", @@ -7592,14 +7743,14 @@ "kind": "skill", "id": "policyengine-tools" }, - "score": 0.4692, + "score": 0.4755, "shared_terms": [ "app", "next", "bun", "vite", - "host", "multizone", + "host", "router", "vercel" ] @@ -7607,22 +7758,22 @@ { "a": { "kind": "agent", - "id": "dashboard-design-validator" + "id": "edge-case-generator" }, "b": { "kind": "agent", - "id": "dashboard-plan-validator" + "id": "test-creator" }, - "score": 0.4622, + "score": 0.4656, "shared_terms": [ - "grep", - "components", - "include", - "tsx", - "node", - "modules", - "app", - "component" + "test", + "tests", + "cases", + "edge", + "variable", + "month", + "boundary", + "files" ] }, { @@ -7634,7 +7785,7 @@ "kind": "command", "id": "create-new-component" }, - "score": 0.4613, + "score": 0.4602, "shared_terms": [ "component", "kit", @@ -7649,22 +7800,22 @@ { "a": { "kind": "agent", - "id": "edge-case-generator" + "id": "dashboard-design-validator" }, "b": { "kind": "agent", - "id": "test-creator" + "id": "dashboard-plan-validator" }, - "score": 0.4603, + "score": 0.4595, "shared_terms": [ - "test", - "tests", - "cases", - "edge", - "variable", - "month", - "boundary", - "files" + "grep", + "components", + "include", + "tsx", + "node", + "modules", + "app", + "component" ] }, { @@ -7676,7 +7827,7 @@ "kind": "agent", "id": "reform-classifier" }, - "score": 0.4487, + "score": 0.4471, "shared_terms": [ "parameter", "gov", @@ -7684,8 +7835,29 @@ "deployed", "provision", "lag", - "credits", - "parametric" + "parametric", + "credits" + ] + }, + { + "a": { + "kind": "skill", + "id": "policyengine-tools" + }, + "b": { + "kind": "agent", + "id": "dashboard-architecture-validator" + }, + "score": 0.4283, + "shared_terms": [ + "app", + "postcss", + "css", + "config", + "kit", + "tailwind", + "tailwindcss", + "backend" ] }, { @@ -7695,39 +7867,39 @@ }, "b": { "kind": "agent", - "id": "dashboard-scaffold" + "id": "dashboard-integrator" }, - "score": 0.4421, + "score": 0.4217, "shared_terms": [ - "plan", - "custom", - "dashboard", + "api", "data", - "modal", "backend", + "client", "pattern", - "api" + "step", + "dashboard", + "builder" ] }, { "a": { "kind": "skill", - "id": "policyengine-tools" + "id": "policyengine-api-access" }, "b": { "kind": "agent", - "id": "dashboard-architecture-validator" + "id": "dashboard-integrator" }, - "score": 0.4256, + "score": 0.4204, "shared_terms": [ - "app", - "postcss", - "css", - "config", - "kit", - "tailwind", - "tailwindcss", - "next" + "api", + "client", + "household", + "backend", + "dashboard", + "request", + "result", + "through" ] }, { @@ -7739,7 +7911,7 @@ "kind": "agent", "id": "dashboard-design-validator" }, - "score": 0.4108, + "score": 0.4126, "shared_terms": [ "grep", "app", @@ -7760,7 +7932,7 @@ "kind": "agent", "id": "multizone-validator" }, - "score": 0.4107, + "score": 0.4042, "shared_terms": [ "zone", "basepath", @@ -7772,27 +7944,6 @@ "assetprefix" ] }, - { - "a": { - "kind": "agent", - "id": "backend-builder" - }, - "b": { - "kind": "agent", - "id": "dashboard-integrator" - }, - "score": 0.4032, - "shared_terms": [ - "api", - "data", - "client", - "backend", - "step", - "types", - "hooks", - "pattern" - ] - }, { "a": { "kind": "skill", @@ -7802,7 +7953,7 @@ "kind": "command", "id": "create-pr" }, - "score": 0.402, + "score": 0.4031, "shared_terms": [ "echo", "wait", @@ -7823,7 +7974,7 @@ "kind": "command", "id": "prior-scores" }, - "score": 0.4, + "score": 0.4009, "shared_terms": [ "tier", "prior", @@ -7844,7 +7995,7 @@ "kind": "command", "id": "prior-analysis" }, - "score": 0.3972, + "score": 0.3971, "shared_terms": [ "analyses", "prior", @@ -7858,35 +8009,14 @@ }, { "a": { - "kind": "skill", - "id": "policyengine-design" - }, - "b": { "kind": "agent", - "id": "frontend-builder" - }, - "score": 0.3834, - "shared_terms": [ - "kit", - "chart", - "css", - "teal", - "design", - "font", - "colors", - "tailwind" - ] - }, - { - "a": { - "kind": "agent", - "id": "test-creator" + "id": "test-creator" }, "b": { "kind": "command", "id": "write-tests" }, - "score": 0.3815, + "score": 0.3924, "shared_terms": [ "test", "tests", @@ -7894,8 +8024,29 @@ "step", "cases", "file", - "cover", - "unit" + "unit", + "cover" + ] + }, + { + "a": { + "kind": "skill", + "id": "policyengine-design" + }, + "b": { + "kind": "agent", + "id": "frontend-builder" + }, + "score": 0.3833, + "shared_terms": [ + "kit", + "chart", + "css", + "teal", + "design", + "font", + "colors", + "tailwind" ] }, { @@ -7907,7 +8058,7 @@ "kind": "command", "id": "write-tests" }, - "score": 0.3723, + "score": 0.3809, "shared_terms": [ "test", "tests", @@ -7921,65 +8072,86 @@ }, { "a": { + "kind": "skill", + "id": "policyengine-api" + }, + "b": { "kind": "agent", - "id": "reform-classifier" + "id": "dashboard-integrator" + }, + "score": 0.3799, + "shared_terms": [ + "api", + "household", + "client", + "data", + "values", + "dashboard", + "request", + "response" + ] + }, + { + "a": { + "kind": "skill", + "id": "policyengine-api-access" }, "b": { - "kind": "command", - "id": "text-to-reform" + "kind": "agent", + "id": "backend-builder" }, - "score": 0.3706, + "score": 0.3755, "shared_terms": [ - "reform", - "provision", - "deployed", - "structural", - "possible", - "parametric", - "parameter", - "microsim" + "api", + "client", + "dashboard", + "public", + "backend", + "modal", + "json", + "server" ] }, { "a": { "kind": "agent", - "id": "rules-engineer" + "id": "edge-case-generator" }, "b": { - "kind": "agent", - "id": "reference-validator" + "kind": "command", + "id": "write-tests" }, - "score": 0.3697, + "score": 0.3733, "shared_terms": [ - "page", - "href", - "title", - "section", - "pdf", - "manual", - "field", - "yaml" + "test", + "tests", + "edge", + "files", + "cases", + "file", + "existing", + "write" ] }, { "a": { "kind": "agent", - "id": "dashboard-planner" + "id": "reform-classifier" }, "b": { "kind": "command", - "id": "create-dashboard" + "id": "text-to-reform" }, - "score": 0.3673, + "score": 0.37, "shared_terms": [ - "dashboard", - "planner", "reform", - "plan", - "page", - "validation", - "tabs", - "natural" + "provision", + "deployed", + "structural", + "possible", + "parametric", + "parameter", + "microsim" ] }, { @@ -7991,37 +8163,37 @@ "kind": "command", "id": "household-calc" }, - "score": 0.3668, + "score": 0.3654, "shared_terms": [ "household", "income", "microsimulation", "tax", "reform", - "net", "level", + "net", "result" ] }, { "a": { "kind": "agent", - "id": "edge-case-generator" + "id": "rules-engineer" }, "b": { - "kind": "command", - "id": "write-tests" + "kind": "agent", + "id": "reference-validator" }, "score": 0.3653, "shared_terms": [ - "test", - "tests", - "edge", - "files", - "cases", - "file", - "existing", - "write" + "href", + "page", + "title", + "section", + "pdf", + "manual", + "yaml", + "reference" ] }, { @@ -8033,7 +8205,7 @@ "kind": "agent", "id": "review-worker" }, - "score": 0.3607, + "score": 0.3613, "shared_terms": [ "review", "canonical", @@ -8054,7 +8226,7 @@ "kind": "agent", "id": "frontend-builder" }, - "score": 0.3554, + "score": 0.3538, "shared_terms": [ "kit", "components", @@ -8075,7 +8247,7 @@ "kind": "command", "id": "text-to-reform" }, - "score": 0.3533, + "score": 0.3538, "shared_terms": [ "bill", "reform", @@ -8087,6 +8259,48 @@ "arpa" ] }, + { + "a": { + "kind": "skill", + "id": "encode-policy-v2" + }, + "b": { + "kind": "agent", + "id": "model-worker" + }, + "score": 0.3523, + "shared_terms": [ + "encode", + "coordinator", + "contract", + "delegation", + "canonical", + "owned", + "tests", + "own" + ] + }, + { + "a": { + "kind": "agent", + "id": "dashboard-planner" + }, + "b": { + "kind": "command", + "id": "create-dashboard" + }, + "score": 0.3501, + "shared_terms": [ + "dashboard", + "planner", + "plan", + "reform", + "tabs", + "page", + "natural", + "plans" + ] + }, { "a": { "kind": "agent", @@ -8096,39 +8310,18 @@ "kind": "command", "id": "create-new-component" }, - "score": 0.3509, + "score": 0.3491, "shared_terms": [ "components", "kit", "grep", "tsx", - "design", "app", + "design", "component", "include" ] }, - { - "a": { - "kind": "skill", - "id": "encode-policy-v2" - }, - "b": { - "kind": "agent", - "id": "model-worker" - }, - "score": 0.3504, - "shared_terms": [ - "encode", - "coordinator", - "contract", - "delegation", - "canonical", - "owned", - "own", - "tests" - ] - }, { "a": { "kind": "skill", @@ -8138,7 +8331,7 @@ "kind": "skill", "id": "policyengine-user-guide" }, - "score": 0.3479, + "score": 0.347, "shared_terms": [ "household", "income", @@ -8150,6 +8343,27 @@ "employment" ] }, + { + "a": { + "kind": "agent", + "id": "ci-fixer" + }, + "b": { + "kind": "agent", + "id": "implementation-validator" + }, + "score": 0.3459, + "shared_terms": [ + "root", + "fix", + "worktree", + "run", + "prefix", + "review", + "read", + "fixes" + ] + }, { "a": { "kind": "agent", @@ -8159,7 +8373,7 @@ "kind": "agent", "id": "reform-describer" }, - "score": 0.3477, + "score": 0.3456, "shared_terms": [ "parameter", "reform", @@ -8173,23 +8387,23 @@ }, { "a": { - "kind": "agent", - "id": "ci-fixer" + "kind": "skill", + "id": "policyengine-tools" }, "b": { - "kind": "agent", - "id": "implementation-validator" + "kind": "command", + "id": "new-tool" }, - "score": 0.3455, + "score": 0.342, "shared_terms": [ - "root", - "fix", - "worktree", - "run", - "prefix", - "review", - "read", - "fixes" + "kit", + "css", + "app", + "tailwind", + "next", + "bun", + "calculator", + "dashboard" ] }, { @@ -8201,7 +8415,7 @@ "kind": "command", "id": "audit-multizone" }, - "score": 0.3451, + "score": 0.3411, "shared_terms": [ "zone", "next", @@ -8215,26 +8429,47 @@ }, { "a": { - "kind": "skill", - "id": "fix-pr" + "kind": "agent", + "id": "backend-builder" }, "b": { - "kind": "agent", - "id": "implementation-validator" + "kind": "command", + "id": "deploy-dashboard" }, - "score": 0.3345, + "score": 0.3362, "shared_terms": [ - "fix", - "review", - "mode", - "read", - "role", - "code", - "only", - "verification" - ] - }, - { + "modal", + "dashboard", + "custom", + "pattern", + "precomputed", + "step", + "backend", + "data" + ] + }, + { + "a": { + "kind": "skill", + "id": "fix-pr" + }, + "b": { + "kind": "agent", + "id": "implementation-validator" + }, + "score": 0.3351, + "shared_terms": [ + "fix", + "review", + "mode", + "read", + "role", + "code", + "verification", + "only" + ] + }, + { "a": { "kind": "agent", "id": "reform-comparator" @@ -8243,7 +8478,7 @@ "kind": "command", "id": "analyze-policy" }, - "score": 0.3335, + "score": 0.3321, "shared_terms": [ "year", "microsim", @@ -8264,7 +8499,7 @@ "kind": "command", "id": "household-calc" }, - "score": 0.3314, + "score": 0.3317, "shared_terms": [ "household", "reform", @@ -8276,27 +8511,6 @@ "taxes" ] }, - { - "a": { - "kind": "agent", - "id": "dashboard-plan-validator" - }, - "b": { - "kind": "agent", - "id": "dashboard-scaffold" - }, - "score": 0.3312, - "shared_terms": [ - "plan", - "dashboard", - "tsx", - "components", - "yaml", - "embedding", - "api", - "zone" - ] - }, { "a": { "kind": "agent", @@ -8306,7 +8520,7 @@ "kind": "command", "id": "create-new-component" }, - "score": 0.331, + "score": 0.3301, "shared_terms": [ "component", "tsx", @@ -8318,27 +8532,6 @@ "kit" ] }, - { - "a": { - "kind": "skill", - "id": "policyengine-calibration-diagnostics" - }, - "b": { - "kind": "skill", - "id": "policyengine-data" - }, - "score": 0.3299, - "shared_terms": [ - "microcosm", - "calibration", - "target", - "diagnostics", - "calibrate", - "weights", - "fit", - "release" - ] - }, { "a": { "kind": "agent", @@ -8348,7 +8541,7 @@ "kind": "command", "id": "encode-reform" }, - "score": 0.3294, + "score": 0.3295, "shared_terms": [ "bill", "reform", @@ -8362,23 +8555,44 @@ }, { "a": { - "kind": "skill", - "id": "policyengine-tools" + "kind": "agent", + "id": "dashboard-planner" + }, + "b": { + "kind": "agent", + "id": "dashboard-scaffold" + }, + "score": 0.3289, + "shared_terms": [ + "dashboard", + "plan", + "api", + "data", + "custom", + "design", + "step", + "costs" + ] + }, + { + "a": { + "kind": "agent", + "id": "dashboard-scaffold" }, "b": { "kind": "command", - "id": "deploy-dashboard" + "id": "new-tool" }, - "score": 0.3277, + "score": 0.3281, "shared_terms": [ - "modal", - "host", - "vercel", - "app", "dashboard", - "config", - "next", - "zone" + "project", + "api", + "scaffold", + "kit", + "css", + "page", + "plan" ] }, { @@ -8390,7 +8604,7 @@ "kind": "command", "id": "deploy-dashboard" }, - "score": 0.3251, + "score": 0.3263, "shared_terms": [ "host", "zone", @@ -8398,71 +8612,92 @@ "path", "dashboard", "git", - "modal", - "config" + "config", + "modal" ] }, { "a": { - "kind": "agent", - "id": "backend-builder" + "kind": "skill", + "id": "policyengine-tools" }, "b": { "kind": "command", "id": "deploy-dashboard" }, - "score": 0.3185, + "score": 0.3251, "shared_terms": [ "modal", + "host", + "app", + "vercel", "dashboard", - "custom", - "pattern", - "precomputed", - "step", - "backend", - "data" + "config", + "next", + "zone" ] }, { "a": { - "kind": "command", - "id": "encode-reform" + "kind": "skill", + "id": "policyengine-calibration-diagnostics" }, "b": { - "kind": "command", - "id": "reform-pipeline" + "kind": "skill", + "id": "policyengine-data" }, - "score": 0.3154, + "score": 0.3209, "shared_terms": [ - "bill", - "reform", - "topic", - "arguments", - "bills", - "federal", - "phase", - "gov" + "microcosm", + "calibration", + "target", + "diagnostics", + "calibrate", + "fit", + "weights", + "release" ] }, { "a": { "kind": "agent", - "id": "issue-manager" + "id": "dashboard-integrator" }, "b": { "kind": "agent", - "id": "pr-pusher" + "id": "dashboard-scaffold" }, - "score": 0.3149, + "score": 0.3189, "shared_terms": [ - "repo", - "branch", - "head", - "push", - "issue", - "number", - "repository", - "base" + "api", + "client", + "backend", + "dashboard", + "data", + "components", + "plan", + "structure" + ] + }, + { + "a": { + "kind": "agent", + "id": "dashboard-plan-validator" + }, + "b": { + "kind": "agent", + "id": "dashboard-scaffold" + }, + "score": 0.3179, + "shared_terms": [ + "plan", + "dashboard", + "api", + "components", + "tsx", + "yaml", + "zone", + "embedding" ] }, { @@ -8474,7 +8709,7 @@ "kind": "command", "id": "text-to-reform" }, - "score": 0.3143, + "score": 0.3156, "shared_terms": [ "bill", "reform", @@ -8495,10 +8730,10 @@ "kind": "skill", "id": "policyengine-us" }, - "score": 0.3092, + "score": 0.3155, "shared_terms": [ - "unit", "tax", + "unit", "income", "medicaid", "household", @@ -8507,6 +8742,48 @@ "person" ] }, + { + "a": { + "kind": "command", + "id": "encode-reform" + }, + "b": { + "kind": "command", + "id": "reform-pipeline" + }, + "score": 0.3155, + "shared_terms": [ + "bill", + "reform", + "topic", + "arguments", + "bills", + "federal", + "phase", + "gov" + ] + }, + { + "a": { + "kind": "agent", + "id": "issue-manager" + }, + "b": { + "kind": "agent", + "id": "pr-pusher" + }, + "score": 0.3138, + "shared_terms": [ + "repo", + "branch", + "push", + "head", + "issue", + "number", + "base", + "repository" + ] + }, { "a": { "kind": "agent", @@ -8516,7 +8793,7 @@ "kind": "command", "id": "deploy-dashboard" }, - "score": 0.3089, + "score": 0.3124, "shared_terms": [ "dashboard", "modal", @@ -8531,34 +8808,55 @@ { "a": { "kind": "skill", - "id": "policyengine-design" + "id": "policyengine-model-development" }, "b": { - "kind": "skill", - "id": "policyengine-tools" - }, - "score": 0.3083, - "shared_terms": [ - "css", - "theme", - "postcss", - "kit", - "tailwindcss", - "app", - "tailwind", - "chart" - ] - }, - { - "a": { "kind": "agent", - "id": "reform-classifier" + "id": "test-creator" + }, + "score": 0.3067, + "shared_terms": [ + "tests", + "test", + "variables", + "variable", + "period", + "periods", + "formula", + "year" + ] + }, + { + "a": { + "kind": "agent", + "id": "backend-builder" + }, + "b": { + "kind": "agent", + "id": "dashboard-planner" + }, + "score": 0.3066, + "shared_terms": [ + "dashboard", + "data", + "api", + "plan", + "modal", + "custom", + "pattern", + "step" + ] + }, + { + "a": { + "kind": "agent", + "id": "reform-classifier" }, "b": { "kind": "agent", "id": "reform-describer" }, - "score": 0.3073, + "score": 0.3065, "shared_terms": [ "provisions", "parameter", @@ -8570,27 +8868,6 @@ "gov" ] }, - { - "a": { - "kind": "skill", - "id": "policyengine-tools" - }, - "b": { - "kind": "command", - "id": "new-tool" - }, - "score": 0.3069, - "shared_terms": [ - "kit", - "css", - "app", - "tailwind", - "next", - "bun", - "calculator", - "import" - ] - }, { "a": { "kind": "skill", @@ -8600,7 +8877,7 @@ "kind": "agent", "id": "parameter-locator" }, - "score": 0.3068, + "score": 0.3057, "shared_terms": [ "tax", "income", @@ -8612,6 +8889,27 @@ "states" ] }, + { + "a": { + "kind": "skill", + "id": "policyengine-design" + }, + "b": { + "kind": "skill", + "id": "policyengine-tools" + }, + "score": 0.304, + "shared_terms": [ + "css", + "theme", + "postcss", + "kit", + "tailwindcss", + "app", + "tailwind", + "chart" + ] + }, { "a": { "kind": "agent", @@ -8621,39 +8919,18 @@ "kind": "agent", "id": "reform-describer" }, - "score": 0.3041, + "score": 0.3034, "shared_terms": [ "ctc", "provisions", - "gov", "reform", "federal", "ages", + "gov", "description", "text" ] }, - { - "a": { - "kind": "skill", - "id": "policyengine-model-development" - }, - "b": { - "kind": "agent", - "id": "test-creator" - }, - "score": 0.3033, - "shared_terms": [ - "tests", - "variable", - "test", - "variables", - "period", - "periods", - "formula", - "year" - ] - }, { "a": { "kind": "agent", @@ -8663,7 +8940,7 @@ "kind": "command", "id": "backdate-program" }, - "score": 0.3007, + "score": 0.3018, "shared_terms": [ "program", "worktree", @@ -8684,7 +8961,7 @@ "kind": "command", "id": "household-calc" }, - "score": 0.2998, + "score": 0.3, "shared_terms": [ "household", "income", @@ -8698,23 +8975,65 @@ }, { "a": { + "kind": "skill", + "id": "policyengine-api-access" + }, + "b": { "kind": "agent", - "id": "parameter-locator" + "id": "dashboard-scaffold" + }, + "score": 0.2977, + "shared_terms": [ + "api", + "dashboard", + "client", + "public", + "backend", + "org", + "page", + "modal" + ] + }, + { + "a": { + "kind": "command", + "id": "create-dashboard" }, "b": { "kind": "command", - "id": "audit-state-tax" + "id": "new-tool" }, - "score": 0.2941, + "score": 0.2971, "shared_terms": [ - "state", - "tax", - "gov", - "income", - "states", - "parameter", - "phase", - "credits" + "dashboard", + "create", + "page", + "calculator", + "multi", + "widget", + "purpose", + "scaffold" + ] + }, + { + "a": { + "kind": "agent", + "id": "dashboard-scaffold" + }, + "b": { + "kind": "command", + "id": "create-dashboard" + }, + "score": 0.2968, + "shared_terms": [ + "dashboard", + "plan", + "page", + "scaffold", + "repo", + "api", + "init", + "directory" ] }, { @@ -8726,7 +9045,7 @@ "kind": "agent", "id": "review-worker" }, - "score": 0.2937, + "score": 0.2956, "shared_terms": [ "review", "canonical", @@ -8740,23 +9059,44 @@ }, { "a": { - "kind": "agent", - "id": "dashboard-planner" + "kind": "skill", + "id": "policyengine-app" }, "b": { - "kind": "agent", - "id": "dashboard-scaffold" + "kind": "command", + "id": "new-tool" }, - "score": 0.2936, + "score": 0.2937, "shared_terms": [ - "dashboard", - "plan", - "data", - "custom", - "design", - "step", + "app", + "calculator", + "params", "api", - "costs" + "page", + "bun", + "kit", + "next" + ] + }, + { + "a": { + "kind": "agent", + "id": "parameter-locator" + }, + "b": { + "kind": "command", + "id": "audit-state-tax" + }, + "score": 0.2937, + "shared_terms": [ + "state", + "tax", + "gov", + "income", + "states", + "parameter", + "phase", + "step" ] }, { @@ -8768,7 +9108,7 @@ "kind": "command", "id": "text-to-reform" }, - "score": 0.2912, + "score": 0.2919, "shared_terms": [ "reform", "dict", @@ -8789,7 +9129,7 @@ "kind": "skill", "id": "policyengine-design" }, - "score": 0.2898, + "score": 0.2906, "shared_terms": [ "theme", "teal", @@ -8810,7 +9150,7 @@ "kind": "agent", "id": "implementation-validator" }, - "score": 0.2895, + "score": 0.29, "shared_terms": [ "mode", "review", @@ -8818,8 +9158,29 @@ "read", "program", "only", - "audit", - "test" + "test", + "audit" + ] + }, + { + "a": { + "kind": "skill", + "id": "policyengine-api" + }, + "b": { + "kind": "agent", + "id": "backend-builder" + }, + "score": 0.2899, + "shared_terms": [ + "api", + "client", + "public", + "dashboard", + "json", + "data", + "simulation", + "cloud" ] }, { @@ -8831,7 +9192,7 @@ "kind": "command", "id": "audit-seo" }, - "score": 0.289, + "score": 0.2898, "shared_terms": [ "audit", "zone", @@ -8852,7 +9213,7 @@ "kind": "agent", "id": "pr-pusher" }, - "score": 0.2871, + "score": 0.2884, "shared_terms": [ "changelog", "repo", @@ -8861,70 +9222,91 @@ "linting", "towncrier", "ruff", - "never" + "committing" ] }, { "a": { "kind": "agent", - "id": "model-corroborator" + "id": "frontend-builder" }, "b": { "kind": "command", - "id": "prior-scores" + "id": "new-tool" }, - "score": 0.2856, + "score": 0.2869, "shared_terms": [ - "shape", - "external", - "reform", - "benchmark", - "scores", - "runs", - "microsim", - "published" + "kit", + "app", + "tailwind", + "font", + "css", + "import", + "design", + "colors" ] }, { "a": { - "kind": "command", - "id": "household-calc" + "kind": "skill", + "id": "policyengine-app" }, "b": { - "kind": "command", - "id": "text-to-reform" + "kind": "agent", + "id": "dashboard-architecture-validator" }, - "score": 0.2856, + "score": 0.2864, "shared_terms": [ - "reform", - "dict", + "app", + "router", + "tailwindcss", + "vite", + "next", + "kit", + "tsx", + "bun" + ] + }, + { + "a": { + "kind": "command", + "id": "household-calc" + }, + "b": { + "kind": "command", + "id": "text-to-reform" + }, + "score": 0.2858, + "shared_terms": [ + "reform", + "dict", "preset", "analyze", "entry", "description", - "api", - "microsim" + "microsim", + "api" ] }, { "a": { "kind": "agent", - "id": "dashboard-scaffold" + "id": "model-corroborator" }, "b": { "kind": "command", - "id": "create-dashboard" + "id": "prior-scores" }, - "score": 0.2845, + "score": 0.2844, "shared_terms": [ - "dashboard", - "plan", - "page", - "scaffold", - "repo", - "init", - "directory", - "create" + "shape", + "external", + "reform", + "benchmark", + "scores", + "microsim", + "runs", + "published" ] }, { @@ -8936,7 +9318,7 @@ "kind": "skill", "id": "policyengine-user-guide" }, - "score": 0.2837, + "score": 0.2836, "shared_terms": [ "household", "income", @@ -8948,27 +9330,6 @@ "population" ] }, - { - "a": { - "kind": "skill", - "id": "policyengine-app" - }, - "b": { - "kind": "agent", - "id": "dashboard-architecture-validator" - }, - "score": 0.2831, - "shared_terms": [ - "app", - "router", - "tailwindcss", - "vite", - "next", - "kit", - "tsx", - "bun" - ] - }, { "a": { "kind": "agent", @@ -8978,7 +9339,7 @@ "kind": "agent", "id": "reform-comparator" }, - "score": 0.2822, + "score": 0.2814, "shared_terms": [ "prior", "year", @@ -8990,27 +9351,6 @@ "result" ] }, - { - "a": { - "kind": "skill", - "id": "policyengine-api" - }, - "b": { - "kind": "agent", - "id": "dashboard-integrator" - }, - "score": 0.2812, - "shared_terms": [ - "api", - "household", - "client", - "data", - "frontend", - "values", - "response", - "policyid" - ] - }, { "a": { "kind": "skill", @@ -9020,7 +9360,7 @@ "kind": "skill", "id": "policyengine-uk" }, - "score": 0.2806, + "score": 0.28, "shared_terms": [ "household", "canada", @@ -9029,7 +9369,7 @@ "benefit", "tax", "poverty", - "net" + "age" ] }, { @@ -9041,7 +9381,7 @@ "kind": "agent", "id": "frontend-builder" }, - "score": 0.2806, + "score": 0.2797, "shared_terms": [ "kit", "app", @@ -9055,23 +9395,23 @@ }, { "a": { - "kind": "agent", - "id": "backend-builder" + "kind": "skill", + "id": "policyengine-design" }, "b": { - "kind": "agent", - "id": "dashboard-planner" + "kind": "command", + "id": "new-tool" }, - "score": 0.2786, + "score": 0.2787, "shared_terms": [ - "data", - "dashboard", - "plan", - "api", - "modal", - "custom", - "pattern", - "step" + "css", + "theme", + "kit", + "var", + "font", + "import", + "tailwind", + "colors" ] }, { @@ -9083,7 +9423,7 @@ "kind": "command", "id": "text-to-reform" }, - "score": 0.278, + "score": 0.2776, "shared_terms": [ "bill", "reform", @@ -9098,43 +9438,22 @@ { "a": { "kind": "agent", - "id": "reform-classifier" - }, - "b": { - "kind": "command", - "id": "implement-structural" - }, - "score": 0.2777, - "shared_terms": [ - "structural", - "backlog", - "reform", - "model", - "gov", - "deployed", - "formula", - "stop" - ] - }, - { - "a": { - "kind": "agent", - "id": "frontend-builder" + "id": "ci-fixer" }, "b": { "kind": "command", - "id": "new-tool" + "id": "backdate-program" }, - "score": 0.2757, + "score": 0.2759, "shared_terms": [ - "kit", - "tailwind", - "app", - "font", - "css", - "import", - "design", - "colors" + "worktree", + "root", + "files", + "prefix", + "program", + "git", + "read", + "run" ] }, { @@ -9146,39 +9465,18 @@ "kind": "agent", "id": "dashboard-plan-validator" }, - "score": 0.2754, + "score": 0.2756, "shared_terms": [ "grep", "app", "fail", "include", - "components", "tsx", + "components", "test", "node" ] }, - { - "a": { - "kind": "agent", - "id": "ci-fixer" - }, - "b": { - "kind": "command", - "id": "backdate-program" - }, - "score": 0.2751, - "shared_terms": [ - "worktree", - "root", - "files", - "prefix", - "program", - "git", - "run", - "read" - ] - }, { "a": { "kind": "agent", @@ -9188,7 +9486,7 @@ "kind": "command", "id": "reform-pipeline" }, - "score": 0.2741, + "score": 0.2739, "shared_terms": [ "bill", "reform", @@ -9203,22 +9501,43 @@ { "a": { "kind": "skill", - "id": "policyengine-app" + "id": "review-program" }, "b": { - "kind": "command", - "id": "new-tool" + "kind": "agent", + "id": "implementation-validator" }, - "score": 0.2724, + "score": 0.2722, "shared_terms": [ - "app", - "calculator", - "params", - "countryid", - "bun", - "kit", - "page", - "next" + "review", + "root", + "program", + "read", + "prefix", + "pdf", + "only", + "code" + ] + }, + { + "a": { + "kind": "agent", + "id": "ci-fixer" + }, + "b": { + "kind": "agent", + "id": "test-creator" + }, + "score": 0.2719, + "shared_terms": [ + "test", + "tests", + "files", + "variable", + "cases", + "not", + "fix", + "aggregation" ] }, { @@ -9230,7 +9549,7 @@ "kind": "agent", "id": "dashboard-architecture-validator" }, - "score": 0.2712, + "score": 0.2717, "shared_terms": [ "css", "postcss", @@ -9242,27 +9561,6 @@ "import" ] }, - { - "a": { - "kind": "skill", - "id": "review-program" - }, - "b": { - "kind": "agent", - "id": "implementation-validator" - }, - "score": 0.2711, - "shared_terms": [ - "review", - "root", - "program", - "read", - "prefix", - "pdf", - "only", - "code" - ] - }, { "a": { "kind": "agent", @@ -9272,14 +9570,14 @@ "kind": "agent", "id": "reform-comparator" }, - "score": 0.2695, + "score": 0.2688, "shared_terms": [ "version", "result", "dataset", "year", - "baseline", "cps", + "baseline", "enhanced", "microsim" ] @@ -9287,22 +9585,43 @@ { "a": { "kind": "agent", - "id": "ci-fixer" + "id": "reform-classifier" }, "b": { - "kind": "agent", - "id": "test-creator" + "kind": "command", + "id": "implement-structural" }, - "score": 0.2691, + "score": 0.2684, "shared_terms": [ - "test", - "tests", - "files", - "variable", - "cases", - "not", - "fix", - "aggregation" + "structural", + "backlog", + "reform", + "model", + "gov", + "deployed", + "formula", + "stop" + ] + }, + { + "a": { + "kind": "skill", + "id": "policyengine-prior-scores" + }, + "b": { + "kind": "agent", + "id": "reform-comparator" + }, + "score": 0.2672, + "shared_terms": [ + "prior", + "anchor", + "scores", + "microsim", + "published", + "reform", + "baseline", + "stage" ] }, { @@ -9314,7 +9633,7 @@ "kind": "agent", "id": "parameter-locator" }, - "score": 0.2681, + "score": 0.2666, "shared_terms": [ "income", "tax", @@ -9328,65 +9647,65 @@ }, { "a": { - "kind": "skill", - "id": "policyengine-design" + "kind": "agent", + "id": "model-corroborator" }, "b": { "kind": "command", - "id": "new-tool" + "id": "text-to-reform" }, - "score": 0.2681, + "score": 0.2656, "shared_terms": [ - "css", - "theme", - "kit", - "var", - "font", - "import", - "tailwind", - "colors" + "reform", + "dict", + "shape", + "runs", + "analyze", + "microsim", + "external", + "parameter" ] }, { "a": { "kind": "skill", - "id": "policyengine-prior-scores" + "id": "policyengine-app" }, "b": { "kind": "agent", - "id": "reform-comparator" + "id": "frontend-builder" }, - "score": 0.2665, + "score": 0.2637, "shared_terms": [ - "prior", - "anchor", - "scores", - "microsim", - "published", - "reform", - "baseline", - "stage" + "app", + "kit", + "design", + "component", + "react", + "frontend", + "tokens", + "next" ] }, { "a": { - "kind": "agent", - "id": "model-corroborator" + "kind": "skill", + "id": "policyengine-api-access" }, "b": { "kind": "command", - "id": "text-to-reform" + "id": "new-tool" }, - "score": 0.2651, + "score": 0.2628, "shared_terms": [ - "reform", - "dict", - "shape", - "runs", - "analyze", - "microsim", - "external", - "parameter" + "api", + "dashboard", + "public", + "client", + "const", + "page", + "app", + "org" ] }, { @@ -9398,7 +9717,7 @@ "kind": "command", "id": "household-calc" }, - "score": 0.2625, + "score": 0.2626, "shared_terms": [ "household", "tax", @@ -9413,43 +9732,64 @@ { "a": { "kind": "agent", - "id": "ci-fixer" + "id": "issue-manager" }, "b": { - "kind": "agent", - "id": "pr-pusher" + "kind": "command", + "id": "create-pr" }, - "score": 0.2621, + "score": 0.2618, "shared_terms": [ - "caller", + "branch", + "number", "git", + "base", + "create", + "repo", + "title", + "head" + ] + }, + { + "a": { + "kind": "agent", + "id": "ci-fixer" + }, + "b": { + "kind": "command", + "id": "write-tests" + }, + "score": 0.2616, + "shared_terms": [ + "test", + "tests", "files", - "formatting", - "fix", "run", - "sources", - "commit" + "file", + "fix", + "failures", + "write" ] }, { "a": { "kind": "agent", - "id": "issue-manager" + "id": "ci-fixer" }, "b": { - "kind": "command", - "id": "create-pr" + "kind": "agent", + "id": "pr-pusher" }, - "score": 0.2609, + "score": 0.2606, "shared_terms": [ - "branch", - "number", + "caller", "git", - "base", - "create", - "repo", - "title", - "head" + "files", + "formatting", + "fix", + "run", + "sources", + "commit" ] }, { @@ -9461,7 +9801,7 @@ "kind": "agent", "id": "frontend-builder" }, - "score": 0.2604, + "score": 0.2598, "shared_terms": [ "app", "kit", @@ -9476,64 +9816,43 @@ { "a": { "kind": "skill", - "id": "policyengine-app" + "id": "policyengine-prior-scores" }, "b": { "kind": "agent", - "id": "frontend-builder" + "id": "model-corroborator" }, - "score": 0.2598, + "score": 0.2591, "shared_terms": [ - "app", - "kit", - "design", - "component", - "react", - "tokens", - "frontend", - "next" + "reform", + "external", + "scores", + "published", + "scored", + "benchmark", + "shape", + "stage" ] }, { "a": { - "kind": "agent", - "id": "ci-fixer" + "kind": "skill", + "id": "policyengine-tools" }, "b": { - "kind": "command", - "id": "write-tests" - }, - "score": 0.2591, - "shared_terms": [ - "test", - "files", - "tests", - "run", - "file", - "fix", - "failures", - "write" - ] - }, - { - "a": { "kind": "agent", "id": "dashboard-scaffold" }, - "b": { - "kind": "command", - "id": "new-tool" - }, - "score": 0.2587, + "score": 0.258, "shared_terms": [ - "project", "dashboard", - "scaffold", + "api", "kit", "css", - "plan", - "page", - "import" + "basepath", + "backend", + "next", + "modal" ] }, { @@ -9545,7 +9864,7 @@ "kind": "command", "id": "text-to-reform" }, - "score": 0.2586, + "score": 0.2579, "shared_terms": [ "reform", "scorekeepers", @@ -9553,8 +9872,29 @@ "analyze", "presets", "bill", - "runs", - "consult" + "consult", + "runs" + ] + }, + { + "a": { + "kind": "agent", + "id": "report-logger" + }, + "b": { + "kind": "command", + "id": "analyze-policy" + }, + "score": 0.2566, + "shared_terms": [ + "log", + "archive", + "auto", + "analyst", + "country", + "prompt", + "data", + "default" ] }, { @@ -9566,70 +9906,49 @@ "kind": "agent", "id": "policy-text-researcher" }, - "score": 0.2584, + "score": 0.2563, "shared_terms": [ "gov", "state", "ctc", "federal", "reform", - "amount", "brackets", + "amount", "legislative" ] }, { "a": { "kind": "skill", - "id": "policyengine-prior-scores" + "id": "policyengine-app" }, "b": { - "kind": "agent", - "id": "model-corroborator" + "kind": "command", + "id": "create-new-component" }, - "score": 0.2576, + "score": 0.2561, "shared_terms": [ - "reform", - "scores", - "external", - "published", - "scored", - "shape", - "benchmark", - "stage" + "app", + "component", + "src", + "design", + "kit", + "tsx", + "react", + "website" ] }, { "a": { "kind": "agent", - "id": "report-logger" + "id": "dashboard-plan-validator" }, "b": { - "kind": "command", - "id": "analyze-policy" + "kind": "agent", + "id": "frontend-builder" }, - "score": 0.2573, - "shared_terms": [ - "log", - "archive", - "auto", - "analyst", - "country", - "prompt", - "data", - "default" - ] - }, - { - "a": { - "kind": "agent", - "id": "dashboard-plan-validator" - }, - "b": { - "kind": "agent", - "id": "frontend-builder" - }, - "score": 0.2571, + "score": 0.2552, "shared_terms": [ "component", "plan", @@ -9643,23 +9962,23 @@ }, { "a": { - "kind": "agent", - "id": "dashboard-scaffold" + "kind": "skill", + "id": "policyengine-prior-scores" }, "b": { - "kind": "agent", - "id": "frontend-builder" + "kind": "command", + "id": "text-to-reform" }, - "score": 0.2555, + "score": 0.2548, "shared_terms": [ - "plan", - "kit", - "design", - "components", - "app", - "css", - "component", - "tsx" + "reform", + "preset", + "presets", + "scorekeepers", + "scores", + "prior", + "dict", + "microsim" ] }, { @@ -9671,37 +9990,37 @@ "kind": "command", "id": "encode-reform" }, - "score": 0.2553, + "score": 0.2543, "shared_terms": [ "prefix", "arguments", "short", "orchestrator", - "page", "files", "agents", + "page", "review" ] }, { "a": { "kind": "skill", - "id": "policyengine-prior-scores" + "id": "review-program" }, "b": { "kind": "command", - "id": "text-to-reform" + "id": "backdate-program" }, - "score": 0.2546, + "score": 0.2535, "shared_terms": [ - "reform", - "preset", - "presets", - "scorekeepers", - "scores", - "prior", - "dict", - "microsim" + "review", + "program", + "root", + "prefix", + "pdf", + "read", + "workflow", + "phase" ] }, { @@ -9713,58 +10032,37 @@ "kind": "agent", "id": "implementation-validator" }, - "score": 0.2538, + "score": 0.253, "shared_terms": [ "review", "fix", "program", "encode", - "structural", "read", - "code", - "mode" + "structural", + "mode", + "worktree" ] }, { "a": { - "kind": "agent", - "id": "dashboard-architecture-validator" - }, - "b": { "kind": "agent", "id": "dashboard-scaffold" }, - "score": 0.2536, - "shared_terms": [ - "css", - "backend", - "config", - "modal", - "custom", - "tsx", - "app", - "dashboard" - ] - }, - { - "a": { - "kind": "skill", - "id": "policyengine-app" - }, "b": { - "kind": "command", - "id": "create-new-component" + "kind": "agent", + "id": "frontend-builder" }, - "score": 0.2531, + "score": 0.2529, "shared_terms": [ + "kit", + "plan", + "design", + "components", "app", + "css", "component", - "design", - "src", - "kit", - "tsx", - "react", - "website" + "api" ] }, { @@ -9776,39 +10074,18 @@ "kind": "agent", "id": "model-worker" }, - "score": 0.2526, + "score": 0.2511, "shared_terms": [ "canonical", "delegation", "coordinator", "work", "role", - "own", "model", + "own", "root" ] }, - { - "a": { - "kind": "skill", - "id": "review-program" - }, - "b": { - "kind": "command", - "id": "backdate-program" - }, - "score": 0.2521, - "shared_terms": [ - "review", - "program", - "root", - "prefix", - "pdf", - "read", - "workflow", - "phase" - ] - }, { "a": { "kind": "agent", @@ -9818,12 +10095,12 @@ "kind": "agent", "id": "reform-comparator" }, - "score": 0.2486, + "score": 0.2493, "shared_terms": [ "our", "microsim", - "baseline", "comparator", + "baseline", "result", "reform", "scores", @@ -9848,28 +10125,7 @@ "local", "country", "analysis", - "jurisdiction" - ] - }, - { - "a": { - "kind": "command", - "id": "create-dashboard" - }, - "b": { - "kind": "command", - "id": "new-tool" - }, - "score": 0.2481, - "shared_terms": [ - "dashboard", - "create", - "page", - "calculator", - "multi", - "widget", - "purpose", - "scaffold" + "repo" ] }, { @@ -9881,7 +10137,7 @@ "kind": "command", "id": "prior-analysis" }, - "score": 0.2479, + "score": 0.2474, "shared_terms": [ "prior", "scripts", @@ -9895,23 +10151,23 @@ }, { "a": { - "kind": "agent", - "id": "backend-builder" + "kind": "skill", + "id": "policyengine" }, "b": { "kind": "agent", - "id": "dashboard-plan-validator" + "id": "dashboard-integrator" }, - "score": 0.2451, + "score": 0.2468, "shared_terms": [ - "plan", + "household", "api", + "income", "data", - "dashboard", - "lib", - "yaml", - "types", - "test" + "tax", + "unit", + "result", + "year" ] }, { @@ -9923,58 +10179,37 @@ "kind": "command", "id": "create-pr" }, - "score": 0.245, + "score": 0.2463, "shared_terms": [ "branch", "git", "draft", - "commit", "number", - "head", + "commit", "create", + "head", "changelog" ] }, { "a": { - "kind": "skill", - "id": "policyengine-tools" - }, - "b": { "kind": "agent", - "id": "dashboard-scaffold" - }, - "score": 0.2424, - "shared_terms": [ - "dashboard", - "kit", - "css", - "config", - "basepath", - "next", - "modal", - "app" - ] - }, - { - "a": { - "kind": "skill", - "id": "policyengine-app" + "id": "dashboard-architecture-validator" }, "b": { "kind": "command", - "id": "audit-multizone" + "id": "new-tool" }, - "score": 0.2421, + "score": 0.2457, "shared_terms": [ + "css", "app", - "host", - "next", - "website", - "multizone", - "rewrites", - "zone", - "vercel" + "tailwind", + "kit", + "import", + "bash", + "globals", + "dashboard" ] }, { @@ -9986,7 +10221,7 @@ "kind": "agent", "id": "test-creator" }, - "score": 0.242, + "score": 0.2454, "shared_terms": [ "tests", "test", @@ -10000,67 +10235,88 @@ }, { "a": { - "kind": "agent", - "id": "reference-validator" + "kind": "skill", + "id": "policyengine-api-access" }, "b": { - "kind": "command", - "id": "audit-state-tax" + "kind": "skill", + "id": "policyengine-app" }, - "score": 0.2411, + "score": 0.2433, "shared_terms": [ - "pdf", - "reference", - "source", + "api", + "app", + "server", + "vite", + "client", "page", - "number", - "full", - "parameter", - "audit" + "org", + "next" ] }, { "a": { "kind": "skill", - "id": "policyengine-cloud-costs" + "id": "policyengine-app" }, "b": { "kind": "command", - "id": "deploy-dashboard" + "id": "audit-multizone" }, - "score": 0.2389, + "score": 0.2421, "shared_terms": [ - "modal", - "deploy", "app", - "dashboard", - "run", - "backend", - "cloud", - "merge" + "host", + "next", + "multizone", + "website", + "rewrites", + "zone", + "vercel" ] }, { "a": { "kind": "agent", - "id": "dashboard-plan-validator" + "id": "reference-validator" }, "b": { "kind": "command", - "id": "audit-multizone" + "id": "audit-state-tax" }, - "score": 0.2389, + "score": 0.2413, "shared_terms": [ - "zone", - "path", - "compliance", - "multi", - "app", - "fail", - "validator", - "dashboard" - ] - }, + "pdf", + "reference", + "source", + "number", + "full", + "page", + "parameter", + "audit" + ] + }, + { + "a": { + "kind": "agent", + "id": "dashboard-plan-validator" + }, + "b": { + "kind": "command", + "id": "audit-multizone" + }, + "score": 0.239, + "shared_terms": [ + "zone", + "path", + "compliance", + "multi", + "app", + "fail", + "validator", + "report" + ] + }, { "a": { "kind": "command", @@ -10070,7 +10326,7 @@ "kind": "command", "id": "text-to-reform" }, - "score": 0.2383, + "score": 0.2381, "shared_terms": [ "bill", "microsim", @@ -10079,7 +10335,7 @@ "year", "url", "log", - "api" + "description" ] }, { @@ -10089,60 +10345,81 @@ }, "b": { "kind": "skill", - "id": "policyengine-api" + "id": "policyengine-healthcare" }, - "score": 0.2338, + "score": 0.2356, "shared_terms": [ "household", - "api", - "python", - "country", - "rest", - "result", - "analysis", - "bundle" + "income", + "tax", + "unit", + "net", + "person", + "age", + "benefits" ] }, { "a": { - "kind": "agent", - "id": "reform-describer" + "kind": "skill", + "id": "policyengine-app" }, "b": { "kind": "command", - "id": "text-to-reform" + "id": "deploy-dashboard" }, - "score": 0.2332, + "score": 0.2355, "shared_terms": [ - "reform", - "description", - "dict", - "parameter", - "provisions", - "federal", - "ctc", - "text" + "app", + "host", + "rewrites", + "website", + "workspace", + "vercel", + "bun", + "next" ] }, { "a": { - "kind": "agent", - "id": "dashboard-architecture-validator" + "kind": "skill", + "id": "policyengine" + }, + "b": { + "kind": "skill", + "id": "policyengine-api" + }, + "score": 0.2352, + "shared_terms": [ + "household", + "api", + "python", + "country", + "server", + "rest", + "result", + "analysis" + ] + }, + { + "a": { + "kind": "skill", + "id": "policyengine-cloud-costs" }, "b": { "kind": "command", - "id": "new-tool" + "id": "deploy-dashboard" }, - "score": 0.2329, + "score": 0.2349, "shared_terms": [ - "css", + "modal", + "deploy", "app", - "tailwind", - "kit", - "import", - "bash", - "function", - "globals" + "dashboard", + "run", + "backend", + "merge", + "cloud" ] }, { @@ -10154,7 +10431,7 @@ "kind": "agent", "id": "edge-case-generator" }, - "score": 0.2324, + "score": 0.2348, "shared_terms": [ "test", "files", @@ -10163,91 +10440,49 @@ "tests", "root", "fix", - "name" + "read" ] }, { "a": { "kind": "agent", - "id": "document-collector" + "id": "component-test-writer" }, "b": { "kind": "agent", - "id": "program-reviewer" - }, - "score": 0.2323, - "shared_terms": [ - "implementations", - "regulations", - "program", - "income", - "disregards", - "government", - "benefit", - "deductions" - ] - }, - { - "a": { - "kind": "skill", - "id": "policyengine-app" - }, - "b": { - "kind": "command", - "id": "deploy-dashboard" + "id": "test-creator" }, - "score": 0.2316, + "score": 0.2342, "shared_terms": [ - "app", - "host", - "rewrites", - "website", - "vercel", - "workspace", - "bun", - "modal" + "test", + "tests", + "testing", + "expect", + "comprehensive", + "descriptive", + "names", + "integration" ] }, { "a": { "kind": "agent", - "id": "dashboard-integrator" - }, - "b": { - "kind": "agent", - "id": "dashboard-scaffold" - }, - "score": 0.2315, - "shared_terms": [ - "api", - "components", - "data", - "plan", - "client", - "backend", - "dashboard", - "structure" - ] - }, - { - "a": { - "kind": "skill", - "id": "policyengine" + "id": "reform-describer" }, "b": { - "kind": "skill", - "id": "policyengine-healthcare" + "kind": "command", + "id": "text-to-reform" }, - "score": 0.2314, + "score": 0.2339, "shared_terms": [ - "household", - "income", - "tax", - "unit", - "net", - "person", - "age", - "year" + "reform", + "description", + "dict", + "parameter", + "provisions", + "federal", + "text", + "ctc" ] }, { @@ -10259,7 +10494,7 @@ "kind": "agent", "id": "prior-scores-finder" }, - "score": 0.2305, + "score": 0.2324, "shared_terms": [ "reform", "external", @@ -10273,23 +10508,23 @@ }, { "a": { - "kind": "skill", - "id": "policyengine-app" + "kind": "agent", + "id": "document-collector" }, "b": { "kind": "agent", - "id": "multizone-validator" + "id": "program-reviewer" }, - "score": 0.2302, + "score": 0.2321, "shared_terms": [ - "app", - "host", - "next", - "legacy", - "website", - "zone", - "rewrites", - "server" + "implementations", + "regulations", + "program", + "income", + "disregards", + "government", + "benefit", + "deductions" ] }, { @@ -10301,7 +10536,7 @@ "kind": "command", "id": "household-calc" }, - "score": 0.2302, + "score": 0.2298, "shared_terms": [ "household", "canada", @@ -10310,28 +10545,49 @@ "income", "population", "net", - "microsimulation" + "not" ] }, { "a": { - "kind": "skill", - "id": "policyengine" + "kind": "command", + "id": "analyze-policy" + }, + "b": { + "kind": "command", + "id": "reform-pipeline" + }, + "score": 0.229, + "shared_terms": [ + "auto", + "bill", + "horizon", + "reform", + "window", + "pipeline", + "phase", + "default" + ] + }, + { + "a": { + "kind": "agent", + "id": "dashboard-architecture-validator" }, "b": { "kind": "agent", - "id": "dashboard-integrator" + "id": "dashboard-scaffold" }, - "score": 0.2286, + "score": 0.2284, "shared_terms": [ - "household", - "api", - "income", - "data", - "result", - "tax", - "unit", - "age" + "backend", + "css", + "dashboard", + "custom", + "modal", + "app", + "kit", + "tsx" ] }, { @@ -10343,7 +10599,7 @@ "kind": "agent", "id": "frontend-builder" }, - "score": 0.2285, + "score": 0.227, "shared_terms": [ "component", "button", @@ -10358,64 +10614,64 @@ { "a": { "kind": "skill", - "id": "policyengine-api" + "id": "policyengine-app" }, "b": { "kind": "agent", - "id": "backend-builder" + "id": "multizone-validator" }, - "score": 0.2277, + "score": 0.2269, "shared_terms": [ - "api", - "client", - "data", - "simulation", - "cloud", - "json", - "org", - "household" + "app", + "host", + "next", + "legacy", + "website", + "zone", + "rewrites", + "served" ] }, { "a": { - "kind": "agent", - "id": "component-test-writer" + "kind": "skill", + "id": "policyengine-healthcare" }, "b": { - "kind": "agent", - "id": "test-creator" + "kind": "skill", + "id": "policyengine-uk" }, - "score": 0.2275, + "score": 0.2262, "shared_terms": [ - "test", - "tests", - "testing", - "expect", - "comprehensive", - "descriptive", - "names", - "integration" + "household", + "income", + "tax", + "unit", + "net", + "person", + "credit", + "benefits" ] }, { "a": { - "kind": "command", - "id": "analyze-policy" + "kind": "skill", + "id": "encode-policy-v2" }, "b": { - "kind": "command", - "id": "reform-pipeline" + "kind": "agent", + "id": "pr-pusher" }, - "score": 0.227, + "score": 0.2261, "shared_terms": [ - "bill", - "auto", - "horizon", - "reform", - "window", - "pipeline", - "phase", - "default" + "review", + "push", + "contract", + "tests", + "encode", + "draft", + "commit", + "approved" ] }, { @@ -10427,7 +10683,7 @@ "kind": "skill", "id": "policyengine-user-guide" }, - "score": 0.2256, + "score": 0.2253, "shared_terms": [ "household", "tax", @@ -10439,109 +10695,88 @@ "population" ] }, - { - "a": { - "kind": "skill", - "id": "encode-policy-v2" - }, - "b": { - "kind": "agent", - "id": "pr-pusher" - }, - "score": 0.2249, - "shared_terms": [ - "review", - "push", - "contract", - "encode", - "tests", - "draft", - "commit", - "approved" - ] - }, { "a": { "kind": "agent", - "id": "dashboard-integrator" + "id": "backend-builder" }, "b": { "kind": "agent", "id": "dashboard-plan-validator" }, - "score": 0.2238, + "score": 0.2242, "shared_terms": [ - "api", - "components", "plan", + "api", "data", - "loading", - "component", - "chart", - "lib" + "dashboard", + "lib", + "yaml", + "client", + "contract" ] }, { "a": { - "kind": "skill", - "id": "policyengine" + "kind": "command", + "id": "create-dashboard" }, "b": { - "kind": "skill", - "id": "policyengine-canada" + "kind": "command", + "id": "reform-pipeline" }, - "score": 0.2231, + "score": 0.2223, "shared_terms": [ - "household", - "income", - "tax", - "age", - "microsimulation", - "net", - "assert", - "analysis" + "dashboard", + "reform", + "auto", + "crm", + "phase", + "confirm", + "arguments", + "dry" ] }, { "a": { - "kind": "skill", - "id": "policyengine-healthcare" + "kind": "agent", + "id": "edge-case-generator" }, "b": { - "kind": "skill", - "id": "policyengine-uk" + "kind": "agent", + "id": "program-reviewer" }, - "score": 0.2223, + "score": 0.2219, "shared_terms": [ - "household", "income", - "tax", - "unit", - "net", - "person", - "credit", - "benefits" + "tests", + "test", + "edge", + "program", + "mode", + "review", + "cases" ] }, { "a": { - "kind": "command", - "id": "create-dashboard" + "kind": "agent", + "id": "multizone-validator" }, "b": { "kind": "command", - "id": "reform-pipeline" + "id": "deploy-dashboard" }, - "score": 0.2223, + "score": 0.2217, "shared_terms": [ - "dashboard", - "reform", - "auto", - "crm", - "phase", - "confirm", - "arguments", - "dry" + "host", + "zone", + "app", + "path", + "modal", + "next", + "config", + "rewrites" ] }, { @@ -10553,7 +10788,7 @@ "kind": "agent", "id": "ci-fixer" }, - "score": 0.2218, + "score": 0.2215, "shared_terms": [ "fix", "files", @@ -10561,50 +10796,50 @@ "workflow", "review", "worktree", - "local", - "root" + "root", + "local" ] }, { "a": { "kind": "skill", - "id": "policyengine-research-lookup" + "id": "policyengine-api" }, "b": { - "kind": "skill", - "id": "policyengine-writing" + "kind": "agent", + "id": "dashboard-scaffold" }, - "score": 0.2216, + "score": 0.2211, "shared_terms": [ - "posts", - "blog", - "post", - "research", - "writing", - "published", - "about", - "descriptions" + "api", + "client", + "dashboard", + "alpha", + "org", + "cloud", + "household", + "public" ] }, { "a": { "kind": "skill", - "id": "policyengine-writing" + "id": "policyengine" }, "b": { - "kind": "command", - "id": "generate-content" + "kind": "skill", + "id": "policyengine-canada" }, - "score": 0.2215, + "score": 0.221, "shared_terms": [ - "social", - "blog", - "posts", - "post", - "copy", - "content", - "writing", - "neutrality" + "household", + "income", + "tax", + "age", + "microsimulation", + "net", + "assert", + "credit" ] }, { @@ -10616,7 +10851,7 @@ "kind": "agent", "id": "dashboard-plan-validator" }, - "score": 0.2202, + "score": 0.221, "shared_terms": [ "zone", "path", @@ -10631,85 +10866,64 @@ { "a": { "kind": "agent", - "id": "dashboard-architecture-validator" + "id": "dashboard-integrator" }, "b": { - "kind": "command", - "id": "deploy-dashboard" + "kind": "agent", + "id": "dashboard-plan-validator" }, - "score": 0.2201, + "score": 0.221, "shared_terms": [ - "modal", - "app", - "bash", - "config", - "dashboard", - "backend", - "custom", - "next" + "api", + "components", + "plan", + "data", + "loading", + "component", + "lib", + "error" ] }, { "a": { - "kind": "agent", - "id": "parameter-locator" + "kind": "skill", + "id": "policyengine-writing" }, "b": { "kind": "command", - "id": "household-calc" + "id": "generate-content" }, - "score": 0.22, + "score": 0.2205, "shared_terms": [ - "reform", - "income", - "tax", - "rate", - "dict", - "household", - "state", - "api" - ] - }, - { - "a": { - "kind": "agent", - "id": "multizone-validator" - }, - "b": { - "kind": "command", - "id": "deploy-dashboard" - }, - "score": 0.2199, - "shared_terms": [ - "host", - "zone", - "app", - "modal", - "path", - "next", - "config", - "rewrites" + "social", + "blog", + "posts", + "post", + "copy", + "content", + "writing", + "neutrality" ] }, { "a": { - "kind": "agent", - "id": "edge-case-generator" + "kind": "skill", + "id": "policyengine-research-lookup" }, "b": { - "kind": "agent", - "id": "program-reviewer" + "kind": "skill", + "id": "policyengine-writing" }, - "score": 0.2199, + "score": 0.2202, "shared_terms": [ - "income", - "tests", - "test", - "edge", - "program", - "review", - "mode", - "cases" + "posts", + "blog", + "post", + "research", + "writing", + "published", + "about", + "descriptions" ] }, { @@ -10721,7 +10935,7 @@ "kind": "agent", "id": "review-worker" }, - "score": 0.2195, + "score": 0.22, "shared_terms": [ "review", "program", @@ -10735,23 +10949,65 @@ }, { "a": { - "kind": "skill", - "id": "policyengine-tools" + "kind": "agent", + "id": "parameter-locator" }, "b": { "kind": "command", - "id": "create-new-component" + "id": "household-calc" }, - "score": 0.2188, + "score": 0.2197, "shared_terms": [ - "kit", + "reform", + "income", + "tax", + "rate", + "dict", + "household", + "state", + "api" + ] + }, + { + "a": { + "kind": "agent", + "id": "dashboard-architecture-validator" + }, + "b": { + "kind": "command", + "id": "deploy-dashboard" + }, + "score": 0.2167, + "shared_terms": [ + "modal", "app", - "design", - "tailwind", - "export", - "css", - "component", - "components" + "bash", + "config", + "dashboard", + "backend", + "custom", + "next" + ] + }, + { + "a": { + "kind": "command", + "id": "prior-analysis" + }, + "b": { + "kind": "command", + "id": "prior-scores" + }, + "score": 0.2163, + "shared_terms": [ + "analyses", + "prior", + "analyze", + "scripts", + "archive", + "change", + "runs", + "scores" ] }, { @@ -10763,7 +11019,7 @@ "kind": "command", "id": "create-new-component" }, - "score": 0.2174, + "score": 0.2159, "shared_terms": [ "component", "components", @@ -10778,43 +11034,64 @@ { "a": { "kind": "skill", - "id": "policyengine-api" + "id": "policyengine-tools" }, "b": { + "kind": "command", + "id": "create-new-component" + }, + "score": 0.2155, + "shared_terms": [ + "kit", + "app", + "design", + "tailwind", + "export", + "css", + "component", + "components" + ] + }, + { + "a": { "kind": "agent", - "id": "microsim-runner" + "id": "backend-builder" + }, + "b": { + "kind": "command", + "id": "new-tool" }, - "score": 0.2171, + "score": 0.2148, "shared_terms": [ "api", - "economy", - "version", - "over", - "country", - "python", - "baseline", - "org" + "dashboard", + "create", + "client", + "modal", + "precomputed", + "export", + "public" ] }, { "a": { - "kind": "command", - "id": "prior-analysis" + "kind": "skill", + "id": "policyengine-api-access" }, "b": { - "kind": "command", - "id": "prior-scores" + "kind": "agent", + "id": "dashboard-planner" }, - "score": 0.2165, + "score": 0.2146, "shared_terms": [ - "analyses", - "prior", - "analyze", - "scripts", - "archive", - "runs", - "change", - "scores" + "api", + "dashboard", + "public", + "server", + "pages", + "credential", + "answer", + "modal" ] }, { @@ -10826,7 +11103,7 @@ "kind": "agent", "id": "program-reviewer" }, - "score": 0.2141, + "score": 0.2145, "shared_terms": [ "mode", "program", @@ -10847,7 +11124,7 @@ "kind": "agent", "id": "ci-fixer" }, - "score": 0.2133, + "score": 0.2144, "shared_terms": [ "review", "contract", @@ -10859,6 +11136,69 @@ "scope" ] }, + { + "a": { + "kind": "skill", + "id": "encode-policy-v2" + }, + "b": { + "kind": "agent", + "id": "program-reviewer" + }, + "score": 0.214, + "shared_terms": [ + "review", + "program", + "tests", + "code", + "implementation", + "references", + "encode", + "coordinator" + ] + }, + { + "a": { + "kind": "command", + "id": "create-new-component" + }, + "b": { + "kind": "command", + "id": "new-tool" + }, + "score": 0.2136, + "shared_terms": [ + "kit", + "app", + "tailwind", + "design", + "dashboard", + "export", + "src", + "import" + ] + }, + { + "a": { + "kind": "agent", + "id": "dashboard-plan-validator" + }, + "b": { + "kind": "agent", + "id": "dashboard-planner" + }, + "score": 0.2123, + "shared_terms": [ + "plan", + "dashboard", + "api", + "data", + "implementation", + "embedding", + "yaml", + "components" + ] + }, { "a": { "kind": "agent", @@ -10874,8 +11214,8 @@ "kit", "grep", "tailwind", - "components", "tsx", + "components", "css", "design" ] @@ -10883,22 +11223,22 @@ { "a": { "kind": "skill", - "id": "encode-policy-v2" + "id": "policyengine-api" }, "b": { "kind": "agent", - "id": "program-reviewer" + "id": "microsim-runner" }, - "score": 0.2119, + "score": 0.2116, "shared_terms": [ - "review", - "program", - "tests", - "code", - "implementation", - "references", - "encode", - "coordinator" + "api", + "economy", + "version", + "over", + "country", + "baseline", + "org", + "python" ] }, { @@ -10910,7 +11250,7 @@ "kind": "command", "id": "create-new-component" }, - "score": 0.2115, + "score": 0.211, "shared_terms": [ "kit", "design", @@ -10931,7 +11271,7 @@ "kind": "agent", "id": "neutrality-reviewer" }, - "score": 0.2108, + "score": 0.211, "shared_terms": [ "neutral", "blog", @@ -10946,34 +11286,13 @@ { "a": { "kind": "skill", - "id": "policyengine-us" + "id": "review-program" }, "b": { - "kind": "command", - "id": "audit-state-tax" + "kind": "agent", + "id": "program-reviewer" }, - "score": 0.2103, - "shared_terms": [ - "tax", - "state", - "gov", - "income", - "year", - "wrong", - "states", - "parameter" - ] - }, - { - "a": { - "kind": "skill", - "id": "review-program" - }, - "b": { - "kind": "agent", - "id": "program-reviewer" - }, - "score": 0.2098, + "score": 0.2104, "shared_terms": [ "review", "program", @@ -10987,23 +11306,23 @@ }, { "a": { - "kind": "command", - "id": "create-new-component" + "kind": "skill", + "id": "policyengine-us" }, "b": { "kind": "command", - "id": "new-tool" + "id": "audit-state-tax" }, - "score": 0.2096, + "score": 0.21, "shared_terms": [ - "kit", - "app", - "const", - "tailwind", - "design", - "export", - "src", - "import" + "tax", + "state", + "gov", + "income", + "year", + "wrong", + "states", + "parameter" ] }, { @@ -11015,7 +11334,7 @@ "kind": "command", "id": "backdate-program" }, - "score": 0.2093, + "score": 0.2099, "shared_terms": [ "program", "review", @@ -11036,37 +11355,79 @@ "kind": "agent", "id": "ci-fixer" }, - "score": 0.2086, + "score": 0.2089, "shared_terms": [ "root", "review", "prefix", "workflow", "run", - "local", "test", + "local", "read" ] }, + { + "a": { + "kind": "skill", + "id": "policyengine-api-access" + }, + "b": { + "kind": "skill", + "id": "policyengine-tools" + }, + "score": 0.2073, + "shared_terms": [ + "api", + "public", + "org", + "app", + "dashboard", + "vercel", + "next", + "server" + ] + }, + { + "a": { + "kind": "skill", + "id": "policyengine-calibration-diagnostics" + }, + "b": { + "kind": "agent", + "id": "model-corroborator" + }, + "score": 0.2052, + "shared_terms": [ + "calibration", + "reform", + "diagnostics", + "stage", + "microsim", + "joint", + "source", + "registry" + ] + }, { "a": { "kind": "agent", - "id": "dashboard-plan-validator" + "id": "ci-fixer" }, "b": { "kind": "agent", - "id": "dashboard-planner" + "id": "model-worker" }, - "score": 0.2059, + "score": 0.205, "shared_terms": [ - "plan", - "dashboard", - "data", - "api", - "implementation", - "embedding", - "yaml", - "components" + "test", + "root", + "run", + "blocked", + "role", + "contract", + "manifest", + "encode" ] }, { @@ -11092,44 +11453,23 @@ }, { "a": { - "kind": "skill", - "id": "policyengine-calibration-diagnostics" - }, - "b": { "kind": "agent", - "id": "model-corroborator" - }, - "score": 0.2039, - "shared_terms": [ - "calibration", - "reform", - "diagnostics", - "stage", - "microsim", - "joint", - "source", - "registry" - ] - }, - { - "a": { - "kind": "skill", - "id": "policyengine-content" + "id": "implementation-validator" }, "b": { - "kind": "skill", - "id": "policyengine-writing" + "kind": "agent", + "id": "review-worker" }, - "score": 0.2039, + "score": 0.2031, "shared_terms": [ - "social", - "post", - "blog", - "copy", - "writing", - "neutrality", - "chart", - "content" + "review", + "program", + "role", + "read", + "code", + "root", + "verification", + "only" ] }, { @@ -11141,37 +11481,37 @@ "kind": "command", "id": "implement-structural" }, - "score": 0.2028, + "score": 0.2031, "shared_terms": [ "gov", "reform", "states", "rate", - "structural", "phase", + "structural", "change", "deployed" ] }, { "a": { - "kind": "agent", - "id": "structural-implementer" + "kind": "skill", + "id": "policyengine-content" }, "b": { - "kind": "agent", - "id": "reform-classifier" + "kind": "skill", + "id": "policyengine-writing" }, - "score": 0.2025, + "score": 0.202, "shared_terms": [ - "structural", - "backlog", - "parameter", - "model", - "amount", - "verdict", - "issue", - "reform" + "social", + "post", + "blog", + "copy", + "writing", + "neutrality", + "chart", + "content" ] }, { @@ -11183,11 +11523,11 @@ "kind": "agent", "id": "report-logger" }, - "score": 0.2018, + "score": 0.1999, "shared_terms": [ "issue", - "country", "draft", + "country", "structural", "repo", "verdict", @@ -11195,48 +11535,6 @@ "body" ] }, - { - "a": { - "kind": "agent", - "id": "implementation-validator" - }, - "b": { - "kind": "agent", - "id": "review-worker" - }, - "score": 0.2017, - "shared_terms": [ - "review", - "program", - "role", - "read", - "code", - "root", - "only", - "verification" - ] - }, - { - "a": { - "kind": "agent", - "id": "ci-fixer" - }, - "b": { - "kind": "agent", - "id": "model-worker" - }, - "score": 0.2016, - "shared_terms": [ - "test", - "root", - "run", - "blocked", - "role", - "contract", - "manifest", - "encode" - ] - }, { "a": { "kind": "skill", @@ -11246,7 +11544,7 @@ "kind": "agent", "id": "parameter-locator" }, - "score": 0.2015, + "score": 0.1995, "shared_terms": [ "gov", "parameter", @@ -11258,27 +11556,6 @@ "canada" ] }, - { - "a": { - "kind": "command", - "id": "create-dashboard" - }, - "b": { - "kind": "command", - "id": "deploy-dashboard" - }, - "score": 0.1989, - "shared_terms": [ - "dashboard", - "repo", - "multi", - "step", - "plan", - "clone", - "confirm", - "path" - ] - }, { "a": { "kind": "skill", @@ -11288,7 +11565,7 @@ "kind": "agent", "id": "report-logger" }, - "score": 0.1984, + "score": 0.1992, "shared_terms": [ "posts", "articles", @@ -11300,27 +11577,6 @@ "json" ] }, - { - "a": { - "kind": "agent", - "id": "report-logger" - }, - "b": { - "kind": "command", - "id": "implement-structural" - }, - "score": 0.1979, - "shared_terms": [ - "issue", - "structural", - "country", - "draft", - "verdict", - "archive", - "report", - "analyze" - ] - }, { "a": { "kind": "agent", @@ -11330,79 +11586,142 @@ "kind": "agent", "id": "reference-validator" }, - "score": 0.1978, + "score": 0.1984, "shared_terms": [ "requirements", "program", "income", "correct", "missing", + "reference", "review", - "implementations", - "manual" + "implementations" ] }, { "a": { "kind": "agent", - "id": "backend-builder" + "id": "pr-pusher" }, "b": { "kind": "agent", - "id": "dashboard-architecture-validator" + "id": "structural-implementer" }, - "score": 0.1957, + "score": 0.197, "shared_terms": [ - "backend", - "modal", - "json", - "custom", - "test", - "data", - "dashboard", - "pattern" + "changelog", + "issue", + "draft", + "commit", + "push", + "tests", + "caller", + "repo" ] }, { "a": { - "kind": "agent", - "id": "component-test-writer" + "kind": "skill", + "id": "policyengine-user-guide" }, "b": { - "kind": "agent", - "id": "dashboard-design-validator" + "kind": "command", + "id": "create-dashboard" }, - "score": 0.1955, + "score": 0.1967, "shared_terms": [ - "tsx", - "components", - "button", - "kit", - "app", - "component", - "classname", - "test" + "reform", + "calculator", + "household", + "charts", + "impact", + "poverty", + "build", + "income" ] }, { "a": { "kind": "agent", - "id": "pr-pusher" + "id": "report-logger" }, "b": { - "kind": "agent", - "id": "structural-implementer" + "kind": "command", + "id": "implement-structural" }, - "score": 0.1948, + "score": 0.1965, "shared_terms": [ - "changelog", "issue", + "structural", + "country", "draft", - "commit", - "push", - "caller", + "verdict", + "archive", + "report", + "analyze" + ] + }, + { + "a": { + "kind": "skill", + "id": "policyengine-plugin-maintenance" + }, + "b": { + "kind": "command", + "id": "write-tests" + }, + "score": 0.1951, + "shared_terms": [ + "test", "tests", - "repo" + "claude", + "source", + "repo", + "pytest", + "run", + "path" + ] + }, + { + "a": { + "kind": "command", + "id": "create-dashboard" + }, + "b": { + "kind": "command", + "id": "deploy-dashboard" + }, + "score": 0.1945, + "shared_terms": [ + "dashboard", + "repo", + "multi", + "step", + "plan", + "clone", + "confirm", + "path" + ] + }, + { + "a": { + "kind": "agent", + "id": "structural-implementer" + }, + "b": { + "kind": "agent", + "id": "reform-classifier" + }, + "score": 0.1944, + "shared_terms": [ + "structural", + "backlog", + "parameter", + "model", + "amount", + "verdict", + "issue", + "reform" ] }, { @@ -11414,7 +11733,7 @@ "kind": "command", "id": "backdate-program" }, - "score": 0.1944, + "score": 0.1941, "shared_terms": [ "pdf", "state", @@ -11422,29 +11741,29 @@ "phase", "arguments", "files", - "page", - "only" + "only", + "page" ] }, { "a": { "kind": "skill", - "id": "policyengine-user-guide" + "id": "policyengine-model-development" }, "b": { - "kind": "command", - "id": "create-dashboard" + "kind": "agent", + "id": "edge-case-generator" }, "score": 0.1938, "shared_terms": [ - "reform", - "calculator", - "household", - "charts", - "impact", - "poverty", - "income", - "build" + "tests", + "test", + "yaml", + "variable", + "year", + "parameters", + "periods", + "add" ] }, { @@ -11456,7 +11775,7 @@ "kind": "command", "id": "audit-seo" }, - "score": 0.1935, + "score": 0.1937, "shared_terms": [ "zone", "repo", @@ -11468,6 +11787,27 @@ "mounted" ] }, + { + "a": { + "kind": "agent", + "id": "component-test-writer" + }, + "b": { + "kind": "agent", + "id": "dashboard-design-validator" + }, + "score": 0.1936, + "shared_terms": [ + "tsx", + "components", + "button", + "kit", + "app", + "component", + "classname", + "test" + ] + }, { "a": { "kind": "skill", @@ -11477,7 +11817,7 @@ "kind": "agent", "id": "parameter-locator" }, - "score": 0.1929, + "score": 0.1931, "shared_terms": [ "reform", "income", @@ -11489,27 +11829,6 @@ "org" ] }, - { - "a": { - "kind": "skill", - "id": "policyengine-plugin-maintenance" - }, - "b": { - "kind": "command", - "id": "write-tests" - }, - "score": 0.1926, - "shared_terms": [ - "test", - "tests", - "claude", - "source", - "repo", - "pytest", - "run", - "path" - ] - }, { "a": { "kind": "skill", @@ -11519,39 +11838,18 @@ "kind": "command", "id": "audit-seo" }, - "score": 0.1926, + "score": 0.1924, "shared_terms": [ "vercel", "seo", "zone", - "bun", "org", + "bun", "standalone", "url", "app" ] }, - { - "a": { - "kind": "skill", - "id": "policyengine-tools" - }, - "b": { - "kind": "agent", - "id": "dashboard-design-validator" - }, - "score": 0.1918, - "shared_terms": [ - "kit", - "app", - "css", - "components", - "tsx", - "design", - "dashboard", - "required" - ] - }, { "a": { "kind": "agent", @@ -11561,7 +11859,7 @@ "kind": "agent", "id": "prior-scores-finder" }, - "score": 0.1914, + "score": 0.1912, "shared_terms": [ "analyses", "comparator", @@ -11575,56 +11873,77 @@ }, { "a": { - "kind": "skill", - "id": "policyengine-model-development" + "kind": "agent", + "id": "issue-manager" }, "b": { "kind": "agent", - "id": "edge-case-generator" + "id": "structural-implementer" }, - "score": 0.1913, + "score": 0.1905, "shared_terms": [ - "tests", - "test", - "yaml", - "variable", - "year", - "parameters", - "periods", - "add" + "issue", + "repo", + "branch", + "base", + "draft", + "push", + "url", + "number" ] }, { "a": { - "kind": "skill", - "id": "policyengine-uk" + "kind": "agent", + "id": "component-test-writer" }, "b": { "kind": "agent", - "id": "parameter-locator" + "id": "edge-case-generator" }, - "score": 0.1899, + "score": 0.1903, "shared_terms": [ - "income", - "tax", - "reform", - "model", - "canada", - "personal", - "household", - "gov" + "test", + "tests", + "edge", + "testing", + "case", + "cases", + "file", + "boundary" ] }, { "a": { - "kind": "skill", - "id": "fix-pr" + "kind": "agent", + "id": "backend-builder" }, "b": { - "kind": "command", - "id": "backdate-program" + "kind": "agent", + "id": "dashboard-architecture-validator" + }, + "score": 0.1897, + "shared_terms": [ + "backend", + "modal", + "json", + "custom", + "data", + "dashboard", + "pattern", + "api" + ] + }, + { + "a": { + "kind": "skill", + "id": "fix-pr" + }, + "b": { + "kind": "command", + "id": "backdate-program" }, - "score": 0.1891, + "score": 0.1896, "shared_terms": [ "review", "files", @@ -11638,23 +11957,23 @@ }, { "a": { - "kind": "agent", - "id": "issue-manager" + "kind": "skill", + "id": "policyengine-tools" }, "b": { "kind": "agent", - "id": "structural-implementer" + "id": "dashboard-design-validator" }, - "score": 0.1889, + "score": 0.1894, "shared_terms": [ - "issue", - "repo", - "branch", - "base", - "draft", - "push", - "url", - "open" + "kit", + "app", + "css", + "components", + "tsx", + "design", + "dashboard", + "required" ] }, { @@ -11666,7 +11985,7 @@ "kind": "command", "id": "deploy-dashboard" }, - "score": 0.1873, + "score": 0.1894, "shared_terms": [ "dashboard", "modal", @@ -11675,28 +11994,49 @@ "custom", "pattern", "data", - "cloud" + "needs" ] }, { "a": { + "kind": "skill", + "id": "policyengine-uk" + }, + "b": { "kind": "agent", - "id": "reform-comparator" + "id": "parameter-locator" + }, + "score": 0.1893, + "shared_terms": [ + "income", + "tax", + "reform", + "model", + "canada", + "personal", + "household", + "gov" + ] + }, + { + "a": { + "kind": "agent", + "id": "dashboard-integrator" }, "b": { - "kind": "command", - "id": "prior-scores" + "kind": "agent", + "id": "dashboard-planner" }, - "score": 0.1871, + "score": 0.1892, "shared_terms": [ - "microsim", - "prior", - "scores", - "year", - "cost", - "dataset", - "cbo", - "baseline" + "api", + "data", + "dashboard", + "step", + "plan", + "backend", + "components", + "input" ] }, { @@ -11708,11 +12048,11 @@ "kind": "command", "id": "household-calc" }, - "score": 0.1864, + "score": 0.1872, "shared_terms": [ "reform", - "baseline", "dict", + "baseline", "external", "single", "cap", @@ -11722,44 +12062,44 @@ }, { "a": { - "kind": "skill", - "id": "policyengine" + "kind": "agent", + "id": "reform-comparator" }, "b": { - "kind": "skill", - "id": "policyengine-model-development" + "kind": "command", + "id": "prior-scores" }, - "score": 0.1858, + "score": 0.1872, "shared_terms": [ - "household", - "age", - "analysis", - "microsimulation", - "variables", - "income", + "microsim", + "prior", + "scores", "year", - "model" + "cost", + "dataset", + "cbo", + "published" ] }, { "a": { - "kind": "agent", - "id": "rules-engineer" + "kind": "command", + "id": "create-pr" }, "b": { "kind": "command", - "id": "backdate-program" + "id": "deploy-dashboard" }, - "score": 0.1857, + "score": 0.1864, "shared_terms": [ - "page", - "program", - "tanf", - "files", - "href", - "pdf", - "yaml", - "only" + "branch", + "git", + "main", + "check", + "step", + "bash", + "feature", + "json" ] }, { @@ -11771,18 +12111,60 @@ "kind": "skill", "id": "policyengine-plugin-maintenance" }, - "score": 0.1849, + "score": 0.1861, "shared_terms": [ "claude", "codex", "routing", "catalog", "skills", - "source", "tests", + "source", "test" ] }, + { + "a": { + "kind": "agent", + "id": "backend-builder" + }, + "b": { + "kind": "command", + "id": "create-dashboard" + }, + "score": 0.1859, + "shared_terms": [ + "dashboard", + "plan", + "create", + "json", + "api", + "reform", + "precomputed", + "backend" + ] + }, + { + "a": { + "kind": "skill", + "id": "policyengine-model-development" + }, + "b": { + "kind": "command", + "id": "implement-structural" + }, + "score": 0.1856, + "shared_terms": [ + "formula", + "contrib", + "gov", + "bracket", + "parameters", + "reform", + "run", + "model" + ] + }, { "a": { "kind": "skill", @@ -11792,7 +12174,7 @@ "kind": "agent", "id": "frontend-builder" }, - "score": 0.1848, + "score": 0.1851, "shared_terms": [ "kit", "teal", @@ -11806,65 +12188,44 @@ }, { "a": { - "kind": "skill", - "id": "policyengine-data" - }, - "b": { - "kind": "agent", - "id": "calibration-diagnostics" - }, - "score": 0.1848, - "shared_terms": [ - "microcosm", - "calibration", - "diagnostics", - "imputation", - "weights", - "calibrated", - "data", - "target" - ] - }, - { - "a": { - "kind": "agent", - "id": "rules-engineer" + "kind": "command", + "id": "audit-multizone" }, "b": { "kind": "command", - "id": "audit-state-tax" + "id": "create-dashboard" }, "score": 0.1848, "shared_terms": [ - "pdf", - "oregon", - "page", - "gov", - "full", - "https", - "income", - "parameter" + "repo", + "multi", + "dashboard", + "path", + "phase", + "clone", + "create", + "arguments" ] }, { "a": { - "kind": "agent", - "id": "component-test-writer" + "kind": "skill", + "id": "policyengine-api" }, "b": { - "kind": "agent", - "id": "edge-case-generator" + "kind": "command", + "id": "new-tool" }, "score": 0.1846, "shared_terms": [ - "test", - "tests", - "edge", - "testing", - "case", - "cases", - "file", - "boundary" + "api", + "dashboard", + "public", + "client", + "org", + "calc", + "country", + "economy" ] }, { @@ -11890,86 +12251,65 @@ }, { "a": { - "kind": "command", - "id": "create-pr" + "kind": "skill", + "id": "policyengine" }, "b": { - "kind": "command", - "id": "deploy-dashboard" + "kind": "skill", + "id": "policyengine-model-development" }, - "score": 0.1842, + "score": 0.1839, "shared_terms": [ - "branch", - "git", - "main", - "check", - "bash", - "step", - "feature", - "json" - ] - }, - { - "a": { - "kind": "skill", - "id": "policyengine-model-development" - }, - "b": { - "kind": "command", - "id": "implement-structural" - }, - "score": 0.1839, - "shared_terms": [ - "formula", - "contrib", - "gov", - "bracket", - "parameters", - "reform", - "run", + "household", + "age", + "analysis", + "variables", + "microsimulation", + "income", + "year", "model" ] }, { "a": { "kind": "agent", - "id": "dashboard-integrator" + "id": "rules-engineer" }, "b": { - "kind": "agent", - "id": "frontend-builder" + "kind": "command", + "id": "audit-state-tax" }, - "score": 0.1832, + "score": 0.183, "shared_terms": [ - "components", - "frontend", - "component", - "api", - "builder", - "design", - "step", - "chart" + "pdf", + "oregon", + "page", + "gov", + "full", + "https", + "income", + "parameter" ] }, { "a": { "kind": "command", - "id": "audit-multizone" + "id": "encode-reform" }, "b": { "kind": "command", - "id": "create-dashboard" + "id": "implement-structural" }, - "score": 0.183, + "score": 0.1828, "shared_terms": [ - "repo", - "multi", - "dashboard", - "path", - "phase", - "clone", - "create", - "arguments" + "reform", + "contrib", + "implement", + "bill", + "gov", + "arguments", + "encode", + "phase" ] }, { @@ -11981,7 +12321,7 @@ "kind": "command", "id": "audit-state-tax" }, - "score": 0.183, + "score": 0.1827, "shared_terms": [ "audit", "url", @@ -11993,6 +12333,27 @@ "check" ] }, + { + "a": { + "kind": "agent", + "id": "program-reviewer" + }, + "b": { + "kind": "agent", + "id": "rules-engineer" + }, + "score": 0.1825, + "shared_terms": [ + "program", + "tanf", + "manual", + "income", + "amount", + "standards", + "payment", + "government" + ] + }, { "a": { "kind": "skill", @@ -12002,7 +12363,7 @@ "kind": "command", "id": "audit-state-tax" }, - "score": 0.1819, + "score": 0.182, "shared_terms": [ "pdf", "audit", @@ -12016,86 +12377,107 @@ }, { "a": { - "kind": "skill", - "id": "fix-pr" + "kind": "command", + "id": "analyze-policy" }, "b": { - "kind": "agent", - "id": "pr-pusher" + "kind": "command", + "id": "create-dashboard" }, - "score": 0.181, + "score": 0.1819, "shared_terms": [ - "fix", - "review", - "push", - "workflow", - "pusher", - "local", - "files", - "never" + "auto", + "horizon", + "phase", + "reform", + "single", + "confirm", + "multi", + "skip" ] }, { "a": { "kind": "agent", - "id": "document-collector" + "id": "issue-manager" }, "b": { - "kind": "agent", - "id": "reference-validator" + "kind": "command", + "id": "audit-seo" }, - "score": 0.181, + "score": 0.1818, "shared_terms": [ - "pdf", - "citation", - "text", - "page", - "source", - "doc", - "authoritative", - "references" + "url", + "repo", + "issue", + "title", + "open", + "search", + "github", + "group" ] }, { "a": { "kind": "agent", - "id": "program-reviewer" + "id": "dashboard-architecture-validator" }, "b": { + "kind": "command", + "id": "audit-multizone" + }, + "score": 0.1811, + "shared_terms": [ + "app", + "config", + "path", + "next", + "bash", + "package", + "json", + "validator" + ] + }, + { + "a": { "kind": "agent", "id": "rules-engineer" }, + "b": { + "kind": "command", + "id": "backdate-program" + }, "score": 0.181, "shared_terms": [ + "page", "program", "tanf", - "manual", - "income", - "amount", - "standards", - "payment", - "government" + "files", + "href", + "pdf", + "yaml", + "only" ] }, { "a": { - "kind": "agent", - "id": "dashboard-architecture-validator" + "kind": "skill", + "id": "fix-pr" }, "b": { - "kind": "command", - "id": "audit-multizone" + "kind": "agent", + "id": "pr-pusher" }, - "score": 0.181, + "score": 0.1809, "shared_terms": [ - "app", - "config", - "path", - "next", - "bash", - "package", - "json", - "validator" + "fix", + "review", + "push", + "workflow", + "pusher", + "files", + "local", + "never" ] }, { @@ -12107,7 +12489,7 @@ "kind": "skill", "id": "policyengine-plugin-maintenance" }, - "score": 0.1809, + "score": 0.1808, "shared_terms": [ "claude", "codex", @@ -12121,44 +12503,23 @@ }, { "a": { - "kind": "command", - "id": "encode-reform" + "kind": "skill", + "id": "encode-policy-v2" }, "b": { - "kind": "command", - "id": "implement-structural" - }, - "score": 0.1805, - "shared_terms": [ - "reform", - "contrib", - "implement", - "bill", - "gov", - "arguments", - "encode", - "phase" - ] - }, - { - "a": { "kind": "agent", - "id": "issue-manager" - }, - "b": { - "kind": "command", - "id": "audit-seo" + "id": "test-creator" }, - "score": 0.1802, + "score": 0.1807, "shared_terms": [ - "url", - "issue", - "repo", - "title", - "open", - "search", - "github", - "group" + "tests", + "test", + "program", + "contract", + "development", + "references", + "not", + "model" ] }, { @@ -12170,7 +12531,7 @@ "kind": "agent", "id": "reform-describer" }, - "score": 0.1801, + "score": 0.1805, "shared_terms": [ "sentence", "blog", @@ -12184,16 +12545,37 @@ }, { "a": { - "kind": "skill", - "id": "policyengine-us" + "kind": "agent", + "id": "document-collector" }, "b": { "kind": "agent", - "id": "calibration-diagnostics" + "id": "reference-validator" }, - "score": 0.1799, + "score": 0.1804, "shared_terms": [ - "unit", + "pdf", + "citation", + "text", + "source", + "page", + "doc", + "authoritative", + "references" + ] + }, + { + "a": { + "kind": "skill", + "id": "policyengine-us" + }, + "b": { + "kind": "agent", + "id": "calibration-diagnostics" + }, + "score": 0.1802, + "shared_terms": [ + "unit", "tax", "spm", "income", @@ -12203,6 +12585,48 @@ "eitc" ] }, + { + "a": { + "kind": "skill", + "id": "policyengine-data" + }, + "b": { + "kind": "agent", + "id": "calibration-diagnostics" + }, + "score": 0.1792, + "shared_terms": [ + "microcosm", + "calibration", + "imputation", + "weights", + "diagnostics", + "calibrated", + "data", + "target" + ] + }, + { + "a": { + "kind": "agent", + "id": "model-worker" + }, + "b": { + "kind": "agent", + "id": "test-creator" + }, + "score": 0.1792, + "shared_terms": [ + "test", + "tests", + "assigned", + "model", + "contract", + "cases", + "owned", + "manifest" + ] + }, { "a": { "kind": "agent", @@ -12212,7 +12636,7 @@ "kind": "command", "id": "analyze-policy" }, - "score": 0.1791, + "score": 0.1779, "shared_terms": [ "year", "microsim", @@ -12224,27 +12648,6 @@ "window" ] }, - { - "a": { - "kind": "command", - "id": "implement-structural" - }, - "b": { - "kind": "command", - "id": "text-to-reform" - }, - "score": 0.1785, - "shared_terms": [ - "reform", - "structural", - "analyze", - "bill", - "formula", - "deployed", - "extension", - "pre" - ] - }, { "a": { "kind": "skill", @@ -12254,100 +12657,100 @@ "kind": "skill", "id": "policyengine-us" }, - "score": 0.1779, + "score": 0.1773, "shared_terms": [ "tax", "household", "income", "credit", - "net", "benefit", + "net", "child", "poverty" ] }, { "a": { - "kind": "command", - "id": "analyze-policy" + "kind": "agent", + "id": "model-corroborator" }, "b": { - "kind": "command", - "id": "create-dashboard" + "kind": "agent", + "id": "parameter-locator" }, - "score": 0.1775, + "score": 0.1771, "shared_terms": [ - "auto", - "horizon", - "phase", "reform", - "single", - "confirm", - "multi", - "skip" + "parameter", + "family", + "model", + "dict", + "amount", + "phase", + "ctc" ] }, { "a": { "kind": "agent", - "id": "implementation-validator" + "id": "program-reviewer" }, "b": { "kind": "command", - "id": "implement-structural" + "id": "backdate-program" }, - "score": 0.1774, + "score": 0.177, "shared_terms": [ - "structural", - "mode", - "run", - "phase", - "verification", - "report", - "issue", - "read" + "program", + "review", + "tanf", + "research", + "read", + "state", + "you", + "findings" ] }, { "a": { - "kind": "agent", - "id": "backend-builder" + "kind": "skill", + "id": "policyengine-api" }, "b": { - "kind": "command", - "id": "create-dashboard" + "kind": "agent", + "id": "dashboard-planner" }, - "score": 0.1774, + "score": 0.1769, "shared_terms": [ - "dashboard", - "plan", - "create", - "json", "api", - "precomputed", - "backend", - "step" + "dashboard", + "cloud", + "server", + "rest", + "credential", + "alpha", + "household" ] }, { "a": { - "kind": "skill", - "id": "encode-policy-v2" + "kind": "command", + "id": "implement-structural" }, "b": { - "kind": "agent", - "id": "test-creator" + "kind": "command", + "id": "text-to-reform" }, - "score": 0.1771, + "score": 0.1764, "shared_terms": [ - "tests", - "test", - "program", - "contract", - "development", - "references", - "not", - "model" + "reform", + "structural", + "analyze", + "bill", + "formula", + "deployed", + "extension", + "pre" ] }, { @@ -12359,7 +12762,7 @@ "kind": "command", "id": "encode-reform" }, - "score": 0.177, + "score": 0.1763, "shared_terms": [ "pdf", "state", @@ -12368,154 +12771,154 @@ "full", "tax", "phase", - "page" + "audit" ] }, { "a": { "kind": "skill", - "id": "policyengine-api" + "id": "policyengine-model-development" }, "b": { - "kind": "skill", - "id": "policyengine-app" + "kind": "command", + "id": "write-tests" }, - "score": 0.1766, + "score": 0.1758, "shared_terms": [ - "api", - "app", - "org", - "economy", - "calc", - "client", - "household", - "over" + "tests", + "test", + "write", + "pytest", + "run", + "repo", + "references", + "path" ] }, { "a": { "kind": "agent", - "id": "program-reviewer" + "id": "dashboard-integrator" }, "b": { - "kind": "command", - "id": "backdate-program" + "kind": "agent", + "id": "frontend-builder" }, - "score": 0.1765, + "score": 0.1756, "shared_terms": [ - "program", - "review", - "tanf", - "research", - "state", - "read", - "you", - "findings" + "components", + "builder", + "frontend", + "api", + "component", + "design", + "step", + "client" ] }, { "a": { "kind": "agent", - "id": "model-corroborator" + "id": "dashboard-integrator" }, "b": { "kind": "agent", - "id": "parameter-locator" + "id": "microsim-runner" }, - "score": 0.1761, + "score": 0.1748, "shared_terms": [ - "reform", - "parameter", - "family", - "model", - "dict", - "amount", - "phase", - "ctc" + "api", + "data", + "response", + "year", + "result", + "request", + "client", + "state" ] }, { "a": { "kind": "skill", - "id": "policyengine-api" + "id": "policyengine-healthcare" }, "b": { "kind": "command", "id": "household-calc" }, - "score": 0.1758, + "score": 0.1741, "shared_terms": [ "household", - "api", - "get", - "country", - "baseline", + "income", + "net", "reform", - "calc", - "simulation" + "benefits", + "tax", + "year", + "single" ] }, { "a": { - "kind": "agent", - "id": "dashboard-planner" + "kind": "skill", + "id": "policyengine-us" }, "b": { - "kind": "command", - "id": "text-to-reform" + "kind": "agent", + "id": "dashboard-integrator" }, - "score": 0.1749, + "score": 0.1739, "shared_terms": [ - "reform", - "natural", - "description", - "language", - "provision", - "bill", - "analyze", - "api" + "household", + "unit", + "tax", + "year", + "income", + "spm", + "head", + "state" ] }, { "a": { "kind": "agent", - "id": "model-worker" + "id": "issue-manager" }, "b": { "kind": "agent", - "id": "test-creator" + "id": "report-logger" }, - "score": 0.1745, + "score": 0.1736, "shared_terms": [ - "test", - "tests", - "assigned", - "model", - "contract", - "cases", - "owned", - "manifest" + "issue", + "repo", + "open", + "draft", + "github", + "url", + "git", + "explicit" ] }, { "a": { "kind": "agent", - "id": "program-reviewer" + "id": "dashboard-integrator" }, "b": { - "kind": "agent", - "id": "parameter-locator" + "kind": "command", + "id": "new-tool" }, - "score": 0.173, + "score": 0.1732, "shared_terms": [ - "income", - "state", - "amount", - "deductions", - "step", - "program", - "credits", - "tax" + "api", + "client", + "dashboard", + "countryid", + "function", + "import", + "head", + "step" ] }, { @@ -12527,7 +12930,7 @@ "kind": "agent", "id": "parameter-locator" }, - "score": 0.1727, + "score": 0.1731, "shared_terms": [ "state", "income", @@ -12548,58 +12951,58 @@ "kind": "command", "id": "text-to-reform" }, - "score": 0.1721, + "score": 0.1725, "shared_terms": [ "reform", "impact", "analyze", "link", - "org", "reforms", + "org", "parameter", "into" ] }, { "a": { - "kind": "skill", - "id": "policyengine-healthcare" + "kind": "agent", + "id": "implementation-validator" }, "b": { "kind": "command", - "id": "household-calc" + "id": "implement-structural" }, - "score": 0.1715, + "score": 0.1722, "shared_terms": [ - "household", - "net", - "income", - "reform", - "benefits", - "tax", - "year", - "single" + "structural", + "mode", + "run", + "phase", + "verification", + "report", + "issue", + "read" ] }, { "a": { "kind": "agent", - "id": "issue-manager" + "id": "program-reviewer" }, "b": { "kind": "agent", - "id": "report-logger" + "id": "parameter-locator" }, - "score": 0.171, + "score": 0.1718, "shared_terms": [ - "issue", - "repo", - "open", - "draft", - "github", - "url", - "git", - "explicit" + "income", + "state", + "amount", + "deductions", + "step", + "program", + "credits", + "tax" ] }, { @@ -12611,7 +13014,7 @@ "kind": "command", "id": "write-tests" }, - "score": 0.1709, + "score": 0.1716, "shared_terms": [ "test", "app", @@ -12623,27 +13026,6 @@ "config" ] }, - { - "a": { - "kind": "skill", - "id": "policyengine-model-development" - }, - "b": { - "kind": "command", - "id": "write-tests" - }, - "score": 0.1704, - "shared_terms": [ - "tests", - "test", - "pytest", - "write", - "run", - "repo", - "references", - "path" - ] - }, { "a": { "kind": "agent", @@ -12653,7 +13035,7 @@ "kind": "command", "id": "analyze-policy" }, - "score": 0.1702, + "score": 0.1705, "shared_terms": [ "bill", "year", @@ -12662,7 +13044,28 @@ "reform", "ctc", "text", - "phase" + "number" + ] + }, + { + "a": { + "kind": "skill", + "id": "policyengine-api" + }, + "b": { + "kind": "command", + "id": "household-calc" + }, + "score": 0.1703, + "shared_terms": [ + "household", + "api", + "get", + "country", + "reform", + "baseline", + "calc", + "simulation" ] }, { @@ -12674,7 +13077,7 @@ "kind": "command", "id": "encode-reform" }, - "score": 0.1698, + "score": 0.1702, "shared_terms": [ "reform", "gov", @@ -12689,22 +13092,22 @@ { "a": { "kind": "skill", - "id": "policyengine-us" + "id": "encode-policy-v2" }, "b": { "kind": "agent", - "id": "dashboard-integrator" + "id": "edge-case-generator" }, - "score": 0.1697, + "score": 0.1698, "shared_terms": [ - "household", - "unit", - "tax", - "income", - "spm", - "person", - "year", - "state" + "tests", + "review", + "test", + "program", + "cases", + "read", + "existing", + "mode" ] }, { @@ -12730,71 +13133,71 @@ }, { "a": { - "kind": "agent", - "id": "dashboard-design-validator" + "kind": "command", + "id": "create-dashboard" }, "b": { - "kind": "agent", - "id": "dashboard-scaffold" + "kind": "command", + "id": "household-calc" }, - "score": 0.169, + "score": 0.1684, "shared_terms": [ - "tsx", - "components", - "dashboard", - "kit", - "design", - "app", - "css", - "custom" + "reform", + "household", + "single", + "questions", + "income", + "arguments", + "preset", + "under" ] }, { "a": { "kind": "agent", - "id": "dashboard-scaffold" + "id": "model-worker" }, "b": { "kind": "command", - "id": "create-new-component" + "id": "write-tests" }, - "score": 0.1669, + "score": 0.1682, "shared_terms": [ - "kit", - "design", - "tsx", - "dashboard", - "components", - "component", - "app", - "step" + "test", + "tests", + "run", + "your", + "path", + "write", + "model", + "source" ] }, { "a": { "kind": "skill", - "id": "encode-policy-v2" + "id": "policyengine-app" }, "b": { - "kind": "agent", - "id": "edge-case-generator" + "kind": "skill", + "id": "policyengine-design" }, - "score": 0.1668, + "score": 0.1671, "shared_terms": [ - "review", - "tests", - "test", - "program", - "cases", - "read", - "existing", - "mode" + "app", + "tailwindcss", + "kit", + "theme", + "vite", + "design", + "tokens", + "colors" ] }, { "a": { "kind": "agent", - "id": "edge-case-generator" + "id": "review-worker" }, "b": { "kind": "command", @@ -12802,56 +13205,77 @@ }, "score": 0.1668, "shared_terms": [ - "files", "program", "review", - "backdate", "read", - "existing", + "root", "only", - "root" + "workflow", + "task", + "you" ] }, { "a": { - "kind": "command", - "id": "analyze-policy" + "kind": "agent", + "id": "component-test-writer" + }, + "b": { + "kind": "agent", + "id": "dashboard-architecture-validator" + }, + "score": 0.1664, + "shared_terms": [ + "test", + "app", + "tsx", + "kit", + "import", + "components", + "classname", + "setup" + ] + }, + { + "a": { + "kind": "agent", + "id": "edge-case-generator" }, "b": { "kind": "command", - "id": "household-calc" + "id": "backdate-program" }, - "score": 0.1668, + "score": 0.1664, "shared_terms": [ - "reform", - "year", - "yyyy", - "single", - "cost", - "analyze", - "microsim", - "baseline" + "files", + "program", + "review", + "backdate", + "read", + "existing", + "only", + "root" ] }, { "a": { "kind": "command", - "id": "create-dashboard" + "id": "analyze-policy" }, "b": { "kind": "command", "id": "household-calc" }, - "score": 0.1664, + "score": 0.1662, "shared_terms": [ "reform", - "household", + "year", + "yyyy", "single", - "questions", - "income", - "arguments", - "preset", - "under" + "cost", + "analyze", + "microsim", + "baseline" ] }, { @@ -12860,42 +13284,21 @@ "id": "policyengine-app" }, "b": { - "kind": "skill", - "id": "policyengine-design" + "kind": "agent", + "id": "dashboard-design-validator" }, - "score": 0.1663, + "score": 0.1661, "shared_terms": [ "app", - "tailwindcss", + "tsx", "kit", - "theme", "design", - "vite", - "tokens", + "components", + "component", + "typography", "colors" ] }, - { - "a": { - "kind": "agent", - "id": "policy-text-researcher" - }, - "b": { - "kind": "command", - "id": "implement-structural" - }, - "score": 0.166, - "shared_terms": [ - "enacted", - "gov", - "structural", - "effective", - "bill", - "reform", - "draft", - "date" - ] - }, { "a": { "kind": "agent", @@ -12903,60 +13306,60 @@ }, "b": { "kind": "command", - "id": "create-dashboard" + "id": "household-calc" }, - "score": 0.166, + "score": 0.1658, "shared_terms": [ - "dashboard", "reform", - "ctc", - "provisions", "description", - "child", - "json", - "multi" + "baseline", + "tax", + "ctc", + "specific", + "dict", + "income" ] }, { "a": { - "kind": "agent", - "id": "dashboard-integrator" + "kind": "skill", + "id": "encode-policy-v2" }, "b": { - "kind": "agent", - "id": "microsim-runner" + "kind": "command", + "id": "write-tests" }, - "score": 0.1658, + "score": 0.1653, "shared_terms": [ - "api", - "data", - "response", - "request", - "result", - "year", - "state", - "step" + "tests", + "test", + "source", + "run", + "fix", + "claude", + "existing", + "shared" ] }, { "a": { "kind": "agent", - "id": "review-worker" + "id": "policy-text-researcher" }, "b": { "kind": "command", - "id": "backdate-program" + "id": "implement-structural" }, - "score": 0.1657, + "score": 0.1652, "shared_terms": [ - "program", - "review", - "read", - "root", - "only", - "you", - "workflow", - "task" + "enacted", + "gov", + "structural", + "effective", + "bill", + "reform", + "draft", + "date" ] }, { @@ -12966,39 +13369,39 @@ }, "b": { "kind": "command", - "id": "household-calc" + "id": "create-dashboard" }, - "score": 0.1657, + "score": 0.1649, "shared_terms": [ + "dashboard", "reform", - "description", - "baseline", "ctc", - "tax", - "specific", - "dict", - "income" + "provisions", + "description", + "child", + "json", + "multi" ] }, { "a": { - "kind": "agent", - "id": "component-test-writer" + "kind": "skill", + "id": "policyengine-api-access" }, "b": { - "kind": "agent", - "id": "dashboard-architecture-validator" + "kind": "command", + "id": "create-dashboard" }, - "score": 0.1653, + "score": 0.1648, "shared_terms": [ - "test", - "app", - "tsx", - "kit", - "import", - "components", - "classname", - "setup" + "dashboard", + "api", + "page", + "household", + "pages", + "ctc", + "analysis", + "json" ] }, { @@ -13010,13 +13413,13 @@ "kind": "agent", "id": "policy-text-researcher" }, - "score": 0.1651, + "score": 0.1645, "shared_terms": [ "enacted", "effective", - "structural", "draft", "bill", + "structural", "gov", "date", "enrolled" @@ -13024,65 +13427,44 @@ }, { "a": { - "kind": "skill", - "id": "policyengine-app" + "kind": "agent", + "id": "model-corroborator" }, "b": { "kind": "agent", - "id": "dashboard-design-validator" + "id": "reform-classifier" }, "score": 0.1643, "shared_terms": [ - "app", - "tsx", - "kit", - "design", - "components", - "component", - "typography", - "colors" + "reform", + "parameter", + "model", + "stage", + "microsim", + "amount", + "deployed", + "ctc" ] }, { "a": { - "kind": "agent", - "id": "document-collector" + "kind": "skill", + "id": "policyengine-tools" }, "b": { "kind": "agent", - "id": "test-creator" - }, - "score": 0.164, - "shared_terms": [ - "month", - "months", - "documentation", - "benefit", - "government", - "program", - "document", - "development" - ] - }, - { - "a": { - "kind": "agent", - "id": "model-worker" - }, - "b": { - "kind": "command", - "id": "write-tests" + "id": "backend-builder" }, - "score": 0.1637, + "score": 0.1641, "shared_terms": [ - "test", - "tests", - "run", - "your", - "path", - "write", - "model", - "source" + "modal", + "api", + "backend", + "dashboard", + "export", + "public", + "custom", + "static" ] }, { @@ -13094,14 +13476,14 @@ "kind": "agent", "id": "pr-pusher" }, - "score": 0.1636, + "score": 0.1639, "shared_terms": [ "fix", "review", "run", "only", - "caller", "git", + "caller", "issues", "program" ] @@ -13115,79 +13497,37 @@ "kind": "agent", "id": "test-creator" }, - "score": 0.1635, + "score": 0.1637, "shared_terms": [ "fix", "program", "test", "read", "not", - "only", "findings", - "variable" + "only", + "variables" ] }, { "a": { "kind": "skill", - "id": "policyengine-canada" + "id": "policyengine-app" }, "b": { - "kind": "skill", - "id": "policyengine-user-guide" - }, - "score": 0.1632, - "shared_terms": [ - "household", - "income", - "benefit", - "population", - "canada", - "tax", - "net", - "poverty" - ] - }, - { - "a": { "kind": "agent", - "id": "backend-builder" - }, - "b": { - "kind": "command", - "id": "new-tool" + "id": "dashboard-scaffold" }, - "score": 0.1631, + "score": 0.1636, "shared_terms": [ "api", - "dashboard", - "create", - "precomputed", - "function", - "modal", - "export", - "step" - ] - }, - { - "a": { - "kind": "agent", - "id": "model-corroborator" - }, - "b": { - "kind": "agent", - "id": "reform-classifier" - }, - "score": 0.1629, - "shared_terms": [ - "reform", - "parameter", - "model", - "stage", - "microsim", - "amount", - "deployed", - "ctc" + "app", + "page", + "design", + "tsx", + "next", + "router", + "kit" ] }, { @@ -13199,7 +13539,7 @@ "kind": "agent", "id": "model-corroborator" }, - "score": 0.1627, + "score": 0.1636, "shared_terms": [ "calibration", "reform", @@ -13214,43 +13554,22 @@ { "a": { "kind": "agent", - "id": "backend-builder" - }, - "b": { - "kind": "command", - "id": "write-tests" - }, - "score": 0.1622, - "shared_terms": [ - "test", - "tests", - "step", - "files", - "client", - "api", - "json", - "matching" - ] - }, - { - "a": { - "kind": "skill", - "id": "policyengine" + "id": "document-collector" }, "b": { "kind": "agent", - "id": "microsim-runner" + "id": "test-creator" }, - "score": 0.1614, + "score": 0.1632, "shared_terms": [ - "result", - "dataset", - "data", - "python", - "microsimulation", - "reform", - "api", - "impact" + "month", + "months", + "documentation", + "benefit", + "government", + "program", + "document", + "development" ] }, { @@ -13262,7 +13581,7 @@ "kind": "agent", "id": "issue-manager" }, - "score": 0.1609, + "score": 0.1631, "shared_terms": [ "repo", "number", @@ -13277,127 +13596,127 @@ { "a": { "kind": "skill", - "id": "encode-policy-v2" + "id": "policyengine-cloud-costs" }, "b": { - "kind": "command", - "id": "write-tests" + "kind": "agent", + "id": "backend-builder" }, - "score": 0.1607, + "score": 0.163, "shared_terms": [ - "tests", - "test", - "source", - "run", - "fix", - "claude", - "existing", - "shared" + "modal", + "backend", + "containers", + "api", + "dashboard", + "plan", + "cloud", + "max" ] }, { "a": { "kind": "skill", - "id": "review-program" + "id": "policyengine-api" }, "b": { - "kind": "agent", - "id": "reference-validator" + "kind": "skill", + "id": "policyengine-app" }, - "score": 0.1607, + "score": 0.1627, "shared_terms": [ - "pdf", - "review", - "reference", - "references", - "source", - "validation", - "program", - "missing" + "api", + "server", + "org", + "economy", + "client", + "household", + "calc", + "page" ] }, { "a": { "kind": "agent", - "id": "dashboard-planner" + "id": "dashboard-scaffold" }, "b": { - "kind": "agent", - "id": "reform-describer" + "kind": "command", + "id": "create-new-component" }, - "score": 0.1606, + "score": 0.1627, "shared_terms": [ + "kit", "dashboard", - "reform", - "description", - "explanation", - "parameter", - "structured", - "language", - "ctc" + "design", + "components", + "tsx", + "component", + "app", + "step" ] }, { "a": { - "kind": "agent", - "id": "pr-pusher" + "kind": "skill", + "id": "policyengine-canada" }, "b": { - "kind": "agent", - "id": "test-creator" + "kind": "skill", + "id": "policyengine-user-guide" }, - "score": 0.16, + "score": 0.1626, "shared_terms": [ - "tests", - "caller", - "commit", - "pusher", - "files", - "contract", - "fix", - "create" + "household", + "income", + "benefit", + "population", + "canada", + "tax", + "net", + "poverty" ] }, { "a": { - "kind": "agent", - "id": "reference-validator" + "kind": "skill", + "id": "review-program" }, "b": { - "kind": "command", - "id": "backdate-program" + "kind": "agent", + "id": "reference-validator" }, - "score": 0.1598, + "score": 0.1624, "shared_terms": [ - "page", "pdf", + "review", "reference", + "references", + "source", + "validation", "program", - "review", - "href", - "phase", - "parameter" + "missing" ] }, { "a": { - "kind": "command", - "id": "audit-multizone" + "kind": "agent", + "id": "pr-pusher" }, "b": { - "kind": "command", - "id": "write-tests" + "kind": "agent", + "id": "test-creator" }, - "score": 0.159, + "score": 0.1616, "shared_terms": [ - "path", - "target", - "repo", - "test", - "app", - "config", - "bash", - "existing" + "tests", + "commit", + "caller", + "pusher", + "files", + "contract", + "fix", + "create" ] }, { @@ -13409,121 +13728,121 @@ "kind": "agent", "id": "review-worker" }, - "score": 0.1587, + "score": 0.1606, "shared_terms": [ "test", "review", "blocked", "root", "role", - "evidence", "read", + "evidence", "program" ] }, { "a": { "kind": "agent", - "id": "edge-case-generator" + "id": "dashboard-design-validator" }, "b": { "kind": "agent", - "id": "review-worker" + "id": "dashboard-scaffold" }, - "score": 0.1582, + "score": 0.16, "shared_terms": [ - "review", - "test", - "assigned", - "program", - "read", - "one", - "only", - "model" - ] - }, + "components", + "tsx", + "dashboard", + "kit", + "design", + "app", + "css", + "custom" + ] + }, { "a": { "kind": "skill", - "id": "policyengine-cloud-costs" + "id": "policyengine-uk" }, "b": { "kind": "agent", - "id": "backend-builder" + "id": "dashboard-integrator" }, - "score": 0.158, + "score": 0.1599, "shared_terms": [ - "modal", - "backend", - "containers", - "api", - "plan", - "dashboard", - "cloud", - "declare" + "household", + "income", + "unit", + "data", + "tax", + "year", + "age", + "person" ] }, { "a": { "kind": "agent", - "id": "implementation-validator" + "id": "edge-case-generator" }, "b": { "kind": "agent", - "id": "reference-validator" + "id": "review-worker" }, - "score": 0.1574, + "score": 0.1596, "shared_terms": [ - "pdf", - "reference", - "validator", + "test", "review", - "mode", - "fix", + "assigned", "program", - "issues" + "read", + "one", + "only", + "model" ] }, { "a": { "kind": "agent", - "id": "prior-scores-finder" + "id": "dashboard-design-validator" }, "b": { "kind": "command", - "id": "text-to-reform" + "id": "new-tool" }, - "score": 0.1574, + "score": 0.1596, "shared_terms": [ - "reform", - "microsim", - "prior", - "bill", - "scores", - "reforms", - "same", - "not" + "kit", + "app", + "dashboard", + "css", + "font", + "design", + "node", + "modules" ] }, { "a": { "kind": "skill", - "id": "policyengine-model-development" + "id": "policyengine" }, "b": { - "kind": "skill", - "id": "policyengine-uk" + "kind": "agent", + "id": "microsim-runner" }, - "score": 0.1572, + "score": 0.1595, "shared_terms": [ - "household", - "entity", - "age", - "model", - "benunit", - "income", - "person", - "canada" + "result", + "dataset", + "data", + "python", + "microsimulation", + "reform", + "api", + "impact" ] }, { @@ -13535,7 +13854,7 @@ "kind": "command", "id": "implement-structural" }, - "score": 0.1572, + "score": 0.159, "shared_terms": [ "reform", "original", @@ -13549,65 +13868,107 @@ }, { "a": { - "kind": "command", - "id": "create-dashboard" + "kind": "skill", + "id": "policyengine" + }, + "b": { + "kind": "skill", + "id": "policyengine-api-access" + }, + "score": 0.1587, + "shared_terms": [ + "household", + "api", + "result", + "server", + "age", + "income", + "analysis", + "country" + ] + }, + { + "a": { + "kind": "agent", + "id": "implementation-validator" + }, + "b": { + "kind": "agent", + "id": "reference-validator" + }, + "score": 0.1582, + "shared_terms": [ + "reference", + "pdf", + "validator", + "review", + "mode", + "fix", + "program", + "issues" + ] + }, + { + "a": { + "kind": "agent", + "id": "reference-validator" }, "b": { "kind": "command", - "id": "encode-reform" + "id": "backdate-program" }, - "score": 0.1572, + "score": 0.1582, "shared_terms": [ - "reform", + "reference", "page", - "multi", - "arguments", - "implement", + "pdf", + "program", + "review", + "href", "phase", - "skip", - "workflow" + "parameter" ] }, { "a": { - "kind": "skill", - "id": "policyengine-design" + "kind": "command", + "id": "audit-multizone" }, "b": { - "kind": "agent", - "id": "dashboard-design-validator" + "kind": "command", + "id": "write-tests" }, - "score": 0.1567, + "score": 0.1578, "shared_terms": [ - "kit", - "css", - "design", + "path", + "target", + "repo", + "test", "app", - "font", - "var", - "colors", - "node" + "config", + "bash", + "existing" ] }, { "a": { "kind": "agent", - "id": "reform-comparator" + "id": "dashboard-planner" }, "b": { "kind": "command", - "id": "household-calc" + "id": "new-tool" }, - "score": 0.1565, + "score": 0.1577, "shared_terms": [ - "single", - "baseline", - "year", - "reform", - "microsim", - "version", - "cost", - "prior" + "dashboard", + "api", + "description", + "step", + "design", + "plan", + "page", + "purpose" ] }, { @@ -13619,7 +13980,7 @@ "kind": "agent", "id": "model-worker" }, - "score": 0.156, + "score": 0.1574, "shared_terms": [ "role", "canonical", @@ -13631,27 +13992,6 @@ "worker" ] }, - { - "a": { - "kind": "agent", - "id": "dashboard-integrator" - }, - "b": { - "kind": "command", - "id": "new-tool" - }, - "score": 0.156, - "shared_terms": [ - "api", - "const", - "function", - "import", - "countryid", - "export", - "return", - "create" - ] - }, { "a": { "kind": "agent", @@ -13661,37 +14001,16 @@ "kind": "agent", "id": "test-creator" }, - "score": 0.1558, + "score": 0.1573, "shared_terms": [ "test", "assigned", "program", "model", "needed", - "development", "read", - "actual" - ] - }, - { - "a": { - "kind": "skill", - "id": "policyengine-data" - }, - "b": { - "kind": "skill", - "id": "policyengine-uk" - }, - "score": 0.1557, - "shared_terms": [ - "microcosm", - "unit", - "data", - "entity", - "person", - "household", - "see", - "national" + "actual", + "development" ] }, { @@ -13703,7 +14022,7 @@ "kind": "command", "id": "audit-state-tax" }, - "score": 0.1554, + "score": 0.1571, "shared_terms": [ "tax", "state", @@ -13718,22 +14037,43 @@ { "a": { "kind": "agent", - "id": "dashboard-scaffold" + "id": "prior-scores-finder" }, "b": { "kind": "command", - "id": "audit-multizone" + "id": "text-to-reform" }, - "score": 0.1552, + "score": 0.1571, "shared_terms": [ - "zone", - "dashboard", - "config", - "directory", - "next", - "working", - "host", - "repo" + "reform", + "microsim", + "prior", + "bill", + "scores", + "reforms", + "same", + "external" + ] + }, + { + "a": { + "kind": "skill", + "id": "policyengine-design" + }, + "b": { + "kind": "agent", + "id": "dashboard-design-validator" + }, + "score": 0.1568, + "shared_terms": [ + "kit", + "css", + "design", + "app", + "font", + "var", + "colors", + "node" ] }, { @@ -13745,7 +14085,7 @@ "kind": "command", "id": "deploy-dashboard" }, - "score": 0.1552, + "score": 0.1561, "shared_terms": [ "vercel", "url", @@ -13757,46 +14097,109 @@ "check" ] }, + { + "a": { + "kind": "agent", + "id": "dashboard-planner" + }, + "b": { + "kind": "command", + "id": "text-to-reform" + }, + "score": 0.156, + "shared_terms": [ + "natural", + "description", + "language", + "api", + "reform", + "provision", + "bill", + "analyze" + ] + }, { "a": { "kind": "skill", - "id": "policyengine-uk" + "id": "policyengine" }, "b": { - "kind": "agent", - "id": "dashboard-integrator" + "kind": "command", + "id": "create-dashboard" }, - "score": 0.1551, + "score": 0.1559, "shared_terms": [ "household", - "income", - "person", - "unit", - "data", + "ctc", + "reform", + "poverty", + "analysis", "tax", + "income", + "page" + ] + }, + { + "a": { + "kind": "skill", + "id": "policyengine-model-development" + }, + "b": { + "kind": "skill", + "id": "policyengine-uk" + }, + "score": 0.1556, + "shared_terms": [ + "household", + "entity", + "model", "age", - "spm" + "benunit", + "income", + "person", + "canada" ] }, { "a": { "kind": "agent", - "id": "component-test-writer" + "id": "reform-comparator" }, "b": { - "kind": "agent", - "id": "dashboard-plan-validator" + "kind": "command", + "id": "household-calc" }, - "score": 0.1543, + "score": 0.1556, "shared_terms": [ - "component", - "tsx", - "test", - "components", - "app", - "chart", - "render", - "expect" + "single", + "baseline", + "year", + "reform", + "microsim", + "version", + "cost", + "prior" + ] + }, + { + "a": { + "kind": "command", + "id": "create-dashboard" + }, + "b": { + "kind": "command", + "id": "encode-reform" + }, + "score": 0.1556, + "shared_terms": [ + "reform", + "page", + "multi", + "arguments", + "implement", + "phase", + "skip", + "workflow" ] }, { @@ -13808,16 +14211,37 @@ "kind": "agent", "id": "structural-implementer" }, - "score": 0.1535, + "score": 0.1545, + "shared_terms": [ + "draft", + "changelog", + "repo", + "venv", + "ready", + "issue", + "committing", + "check" + ] + }, + { + "a": { + "kind": "skill", + "id": "policyengine-data" + }, + "b": { + "kind": "skill", + "id": "policyengine-uk" + }, + "score": 0.1543, "shared_terms": [ - "draft", - "changelog", - "repo", - "venv", - "ready", - "issue", - "committing", - "version" + "microcosm", + "data", + "person", + "entity", + "unit", + "household", + "see", + "national" ] }, { @@ -13829,7 +14253,7 @@ "kind": "command", "id": "prior-scores" }, - "score": 0.1535, + "score": 0.154, "shared_terms": [ "reform", "analyze", @@ -13844,64 +14268,43 @@ { "a": { "kind": "agent", - "id": "prior-scores-finder" + "id": "component-test-writer" }, "b": { "kind": "agent", - "id": "report-logger" + "id": "dashboard-plan-validator" }, - "score": 0.1531, + "score": 0.1535, "shared_terms": [ - "analyses", - "archive", - "verdict", - "country", - "local", - "jurisdiction", - "tracker", - "reform" + "component", + "tsx", + "test", + "components", + "app", + "chart", + "render", + "expect" ] }, { "a": { "kind": "agent", - "id": "policy-text-researcher" - }, - "b": { - "kind": "agent", - "id": "reform-classifier" - }, - "score": 0.1525, - "shared_terms": [ - "gov", - "ctc", - "reform", - "provisions", - "structural", - "amount", - "refundable", - "provision" - ] - }, - { - "a": { - "kind": "skill", - "id": "policyengine-content" + "id": "issue-manager" }, "b": { - "kind": "skill", - "id": "policyengine-research-lookup" + "kind": "command", + "id": "audit-state-tax" }, - "score": 0.1523, + "score": 0.153, "shared_terms": [ - "post", - "blog", - "image", - "published", - "app", - "live", - "tone", - "research" + "state", + "number", + "url", + "abbreviation", + "search", + "mode", + "discover", + "phase" ] }, { @@ -13913,7 +14316,7 @@ "kind": "command", "id": "household-calc" }, - "score": 0.1518, + "score": 0.1529, "shared_terms": [ "reform", "level", @@ -13921,50 +14324,29 @@ "baseline", "reforms", "tax", - "single", - "file" + "file", + "single" ] }, { "a": { "kind": "agent", - "id": "issue-manager" + "id": "prior-scores-finder" }, "b": { - "kind": "command", - "id": "audit-state-tax" - }, - "score": 0.1516, - "shared_terms": [ - "state", - "number", - "url", - "abbreviation", - "search", - "mode", - "discover", - "phase" - ] - }, - { - "a": { "kind": "agent", - "id": "dashboard-plan-validator" - }, - "b": { - "kind": "command", - "id": "create-dashboard" + "id": "report-logger" }, - "score": 0.1511, + "score": 0.1528, "shared_terms": [ - "dashboard", - "plan", - "multi", - "fail", - "path", - "api", - "name", - "validates" + "analyses", + "archive", + "verdict", + "country", + "local", + "jurisdiction", + "tracker", + "reform" ] }, { @@ -13976,7 +14358,7 @@ "kind": "command", "id": "encode-reform" }, - "score": 0.151, + "score": 0.1517, "shared_terms": [ "prefix", "test", @@ -13990,23 +14372,65 @@ }, { "a": { + "kind": "skill", + "id": "policyengine-model-development" + }, + "b": { "kind": "agent", - "id": "microsim-runner" + "id": "pr-pusher" + }, + "score": 0.1512, + "shared_terms": [ + "changelog", + "tests", + "add", + "never", + "branch", + "run", + "repo", + "references" + ] + }, + { + "a": { + "kind": "skill", + "id": "policyengine-model-development" }, "b": { + "kind": "agent", + "id": "structural-implementer" + }, + "score": 0.1512, + "shared_terms": [ + "tests", + "parameter", + "changelog", + "formula", + "encoding", + "bracket", + "contrib", + "model" + ] + }, + { + "a": { "kind": "command", - "id": "household-calc" + "id": "deploy-dashboard" }, - "score": 0.1509, + "b": { + "kind": "command", + "id": "new-tool" + }, + "score": 0.1511, "shared_terms": [ - "reform", - "version", - "baseline", - "dict", - "api", - "microsimulation", - "current", - "raw" + "dashboard", + "app", + "modal", + "step", + "bash", + "embedding", + "bun", + "next" ] }, { @@ -14018,7 +14442,7 @@ "kind": "agent", "id": "reform-comparator" }, - "score": 0.1506, + "score": 0.151, "shared_terms": [ "prior", "anchor", @@ -14026,50 +14450,71 @@ "microsim", "comparator", "cost", - "baseline", - "diagnostics" + "diagnostics", + "baseline" ] }, { "a": { "kind": "agent", - "id": "microsim-runner" + "id": "backend-builder" }, "b": { - "kind": "command", - "id": "analyze-policy" + "kind": "agent", + "id": "microsim-runner" }, - "score": 0.1504, + "score": 0.1507, "shared_terms": [ - "year", - "default", - "economy", "data", - "microsim", - "baseline", + "api", + "json", "reform", - "api" + "client", + "results", + "economy", + "step" ] }, { "a": { - "kind": "skill", - "id": "policyengine-model-development" + "kind": "agent", + "id": "policy-text-researcher" }, "b": { "kind": "agent", - "id": "structural-implementer" + "id": "reform-classifier" }, - "score": 0.15, + "score": 0.1503, "shared_terms": [ - "tests", - "parameter", - "changelog", - "formula", - "encoding", - "bracket", - "contrib", - "model" + "gov", + "ctc", + "reform", + "provisions", + "structural", + "amount", + "refundable", + "provision" + ] + }, + { + "a": { + "kind": "agent", + "id": "dashboard-integrator" + }, + "b": { + "kind": "agent", + "id": "parameter-locator" + }, + "score": 0.1501, + "shared_terms": [ + "api", + "state", + "income", + "values", + "tax", + "states", + "step", + "household" ] } ], @@ -14130,6 +14575,10 @@ "id": "policyengine-api", "path": "skills/policyengine-api/SKILL.md" }, + { + "id": "policyengine-api-access", + "path": "skills/policyengine-api-access/SKILL.md" + }, { "id": "policyengine-app", "path": "skills/policyengine-app/SKILL.md" @@ -14173,7 +14622,7 @@ "name": "analysis-tools", "description": "Policy analysis and research - impact studies, dashboards, notebooks, and visualizations", "category": "analysis", - "version": "4.1.0", + "version": "4.2.0", "keywords": [ "analysis", "research", @@ -14196,6 +14645,7 @@ "./skills/policyengine-canada", "./skills/policyengine-healthcare", "./skills/policyengine-user-guide", + "./skills/policyengine-api-access", "./skills/policyengine-design", "./skills/policyengine-writing", "./skills/policyengine-research-lookup", @@ -14240,7 +14690,7 @@ "name": "api-development", "description": "API development - Flask endpoints, caching, services, and integration patterns", "category": "development", - "version": "4.1.0", + "version": "4.2.0", "keywords": [ "api", "flask", @@ -14256,6 +14706,7 @@ "hooks": null, "skills": [ "./skills/policyengine-api", + "./skills/policyengine-api-access", "./skills/policyengine-model-development", "./skills/policyengine-us", "./skills/policyengine-uk", @@ -14280,7 +14731,7 @@ "name": "app-development", "description": "React app development - app-v2, standalone tools, Vercel deployment, and PolicyEngine-specific patterns", "category": "development", - "version": "4.1.0", + "version": "4.2.0", "keywords": [ "react", "frontend", @@ -14299,6 +14750,7 @@ "skills": [ "./skills/policyengine-app", "./skills/policyengine-tools", + "./skills/policyengine-api-access", "./skills/policyengine-model-development", "./skills/policyengine-us", "./skills/policyengine-uk", @@ -14331,7 +14783,7 @@ "name": "complete", "description": "Complete PolicyEngine knowledge base - all agents, commands, and skills for users, analysts, and contributors", "category": "complete", - "version": "4.1.0", + "version": "4.2.0", "keywords": [ "complete", "all", @@ -14354,6 +14806,7 @@ "./skills/policyengine-tools", "./skills/policyengine-app", "./skills/policyengine-api", + "./skills/policyengine-api-access", "./skills/policyengine-design", "./skills/policyengine-standards", "./skills/policyengine-cloud-costs", @@ -14434,7 +14887,7 @@ "name": "country-models", "description": "Complete multi-agent workflow for implementing government benefit programs in PolicyEngine country packages", "category": "development", - "version": "4.1.0", + "version": "4.2.0", "keywords": [ "tax", "benefits", @@ -14488,7 +14941,7 @@ "name": "dashboard-builder", "description": "Orchestrated AI workflow for creating PolicyEngine dashboards from natural-language descriptions", "category": "development", - "version": "4.1.0", + "version": "4.2.0", "keywords": [ "dashboard", "calculator", @@ -14507,6 +14960,7 @@ "skills": [ "./skills/policyengine-tools", "./skills/policyengine-api", + "./skills/policyengine-api-access", "./skills/policyengine-app", "./skills/policyengine-us", "./skills/policyengine-uk", @@ -14534,7 +14988,7 @@ "name": "data-science", "description": "Data analysis, survey enhancement, imputation, calibration, and microdata utilities", "category": "data", - "version": "4.1.0", + "version": "4.2.0", "keywords": [ "data", "microdata", @@ -14568,7 +15022,7 @@ "name": "essential", "description": "Essential PolicyEngine knowledge for all users - understanding the platform, using the web app, and basic concepts", "category": "getting-started", - "version": "4.1.0", + "version": "4.2.0", "keywords": [ "beginner", "users", @@ -17055,10 +17509,8 @@ "agents": [ "dashboard-architecture-validator" ], - "commands": [ - "new-tool" - ], - "total": 4 + "commands": [], + "total": 3 }, { "name": "policyengine-app-v2", @@ -17069,6 +17521,7 @@ "visibility": "PUBLIC", "pushed_at": "2026-05-28T13:10:20Z", "skills": [ + "policyengine-api-access", "policyengine-app", "policyengine-research-lookup", "policyengine-tools", @@ -17087,7 +17540,7 @@ "deploy-dashboard", "write-tests" ], - "total": 13 + "total": 14 }, { "name": "policyengine-canada", @@ -17488,6 +17941,7 @@ "visibility": "PUBLIC", "pushed_at": "2026-05-21T17:59:33Z", "skills": [ + "policyengine-data", "policyengine-model-development", "policyengine-standards", "policyengine-uk" @@ -17498,7 +17952,7 @@ "pr-pusher" ], "commands": [], - "total": 6 + "total": 7 }, { "name": "policyengine-demo", @@ -17658,8 +18112,10 @@ "skills": [ "policyengine", "policyengine-api", + "policyengine-api-access", "policyengine-app", - "policyengine-model-development" + "policyengine-model-development", + "policyengine-tools" ], "agents": [ "backend-builder", @@ -17673,7 +18129,7 @@ "deploy-dashboard", "new-tool" ], - "total": 12 + "total": 14 }, { "name": "policyengine-app", @@ -17685,6 +18141,7 @@ "pushed_at": "2026-05-25T16:00:28Z", "skills": [ "policyengine-api", + "policyengine-api-access", "policyengine-app", "policyengine-design", "policyengine-plugin-maintenance", @@ -17722,7 +18179,7 @@ "text-to-reform", "write-tests" ], - "total": 33 + "total": 34 }, { "name": "policyengine.dev", @@ -17918,14 +18375,13 @@ "generate-content", "household-calc", "implement-structural", - "new-tool", "prior-analysis", "prior-scores", "reform-pipeline", "text-to-reform", "write-tests" ], - "total": 31 + "total": 30 }, { "name": "policyengine-us", @@ -17940,6 +18396,7 @@ "fix-pr", "policyengine", "policyengine-api", + "policyengine-api-access", "policyengine-app", "policyengine-calibration-diagnostics", "policyengine-canada", @@ -18007,7 +18464,6 @@ "generate-content", "household-calc", "implement-structural", - "new-tool", "prior-analysis", "reform-pipeline", "setup-verbs", @@ -18039,13 +18495,12 @@ "pushed_at": "2026-05-28T12:37:26Z", "skills": [ "policyengine", - "policyengine-data", "policyengine-plugin-maintenance", "policyengine-tools" ], "agents": [], "commands": [], - "total": 4 + "total": 3 }, { "name": "tanf-calculator", diff --git a/docs/when-to-use.md b/docs/when-to-use.md index eccf768..d160fa0 100644 --- a/docs/when-to-use.md +++ b/docs/when-to-use.md @@ -1,6 +1,6 @@ # When to use what -The PolicyEngine plugin ships 20 command files, 36 agents, and 23 skills (20 knowledge skills plus the three workflow skills, consolidated from 53 in the July 2026 rebuild). The `/encode-policy-v2`, `/review-program`, and `/fix-pr` entries are provided directly by skills: each name resolves to a skill whose `references/workflow.md` is the canonical definition, with no duplicate command stub. Most days you'll only use 3-5 entry points. This guide maps common work to the right one. +The PolicyEngine plugin ships 20 command files, 36 agents, and 25 skills (22 knowledge skills plus the three workflow skills, consolidated from 53 in the July 2026 rebuild). The `/encode-policy-v2`, `/review-program`, and `/fix-pr` entries are provided directly by skills: each name resolves to a skill whose `references/workflow.md` is the canonical definition, with no duplicate command stub. Most days you'll only use 3-5 entry points. This guide maps common work to the right one. ## Quick decision matrix @@ -95,6 +95,7 @@ These get pulled in automatically by their trigger keywords, but useful to know **API integration**: - `policyengine-api` — REST endpoints (v1 production, api-v2 alpha); analysts calling from Python should use the `policyengine` skill instead +- `policyengine-api-access` — reaching the Public API from a dashboard page (the dashboard API through `@policyengine/api-client`) or from a server (its own Auth0 client); browser code never calls api.policyengine.org ## Common workflows (recipes) diff --git a/skills/README.md b/skills/README.md index 636b2c0..9d2ba65 100644 --- a/skills/README.md +++ b/skills/README.md @@ -54,6 +54,7 @@ lints the whole tree against the failure modes that rotted the previous catalog | **policyengine-cloud-costs** | Deploying your own tool without idle spend: defaults, declared exceptions, a README cost plan, a pre-merge config checker and a bill check | | **policyengine-design** | Design tokens, `@policyengine/ui-kit` consumption, chart styling, design-system migration | | **policyengine-api** | The REST APIs: v1 production endpoints, api-v2 alpha status | +| **policyengine-api-access** | Reaching the Public API from dashboards and servers: the dashboard API through `@policyengine/api-client`, server Auth0 client credentials and their token cache, errors | ### Process diff --git a/skills/policyengine-api-access/SKILL.md b/skills/policyengine-api-access/SKILL.md new file mode 100644 index 0000000..ffe3ab3 --- /dev/null +++ b/skills/policyengine-api-access/SKILL.md @@ -0,0 +1,295 @@ +--- +name: policyengine-api-access +description: | + Load before writing or reviewing code that reaches the PolicyEngine Public API + (api.policyengine.org) from a dashboard, tool, backend, Modal app, Vercel function or CI + job. The Public API serves only PolicyEngine's servers: pages call www.policyengine.org's + dashboard API through @policyengine/api-client, and servers send their own Auth0 + client-credentials token. Covers the rules, the four dashboard operations, polling, allowed + origins, cached server tokens, getting a client, local development and previews, asking for a + new operation, and 401/403/429/503. + Triggers: api.policyengine.org, Public API, dashboard API, @policyengine/api-client, household + calculate from the browser, economy comparison polling, NEXT_PUBLIC_API_URL, VITE_API_URL, + Auth0 client credentials, server token, PUBLIC_API_SERVER_CLIENT_IDS, Authentication is + required, CORS error from the API. + NOT for: Python analysis (policyengine skill) or the Public API's own endpoints and code + (policyengine-api). +metadata: + category: apps +--- + +# Reaching the Public API from dashboards and servers + +The Public API (`PolicyEngine/policyengine-api`, https://api.policyengine.org) is becoming +callable only by PolicyEngine's own servers. Once its credential requirement is switched on, it +answers every request without a server credential with 401, except `GET`/`HEAD /health`, +`GET /v2/openapi.json` and CORS preflights. Anything in browser code is public, so a page cannot hold that +credential. Pages go through a server that does. + +## The rules + +1. **Browser code never calls the Public API**: not `api.policyengine.org`, and not its Cloud + Run addresses (`policyengine-api-….a.run.app`, `policyengine-api-staging-….a.run.app`, + or a tagged revision's `…---policyengine-api-….a.run.app`). Browser code is everything that + runs in the page: components, hooks, client modules, static exports and anything a bundler + puts in the page. +2. **No Public API credential or address goes in a `NEXT_PUBLIC_*` or `VITE_*` variable.** + Next.js and Vite copy those values into the page's JavaScript. That covers the Auth0 + client ID and secret, tokens, and the Public API's URL. +3. **One Auth0 client per calling server**, never shared between callers or copied into + another repository. The client ID is how the Public API's records identify the caller. + +## Which route to use + +| Where the code runs | How it reaches the Public API | +|---|---| +| A dashboard page (browser) | www.policyengine.org's dashboard API, through `@policyengine/api-client` | +| A dashboard's own backend (Modal, Cloud Run, a Vercel function) | Its own Auth0 client, token in `Authorization: Bearer` | +| A CI job or script | Its own Auth0 client, one token per job run | +| Python analysis or a notebook | The `policyengine` package (policyengine skill), not REST | + +If a backend exists only to relay one of the four dashboard operations, drop it and call the +dashboard API from the page instead. + +## From pages: the dashboard API + +www.policyengine.org (`website/` in `PolicyEngine/policyengine-app-v2`) serves four operations. +Its server adds PolicyEngine's credential, so a dashboard needs none, and dashboards never see +the Public API's paths. + +```bash +npm install @policyengine/api-client +``` + +```ts +import { + createPolicyEngineApiClient, + EconomyComparisonTimeoutError, + PolicyEngineApiError, +} from "@policyengine/api-client"; + +const policyengine = createPolicyEngineApiClient(); + +const household = await policyengine.calculateHousehold({ + country: "us", + household: { + people: { you: { age: { 2026: 40 }, employment_income: { 2026: 50000 } } }, + households: { your_household: { members: ["you"], household_net_income: { 2026: null } } }, + }, + policy: { "gov.irs.credits.ctc.amount.base[0].amount": { "2026-01-01.2100-12-31": 3000 } }, +}); + +const reformId = await policyengine.createPolicy({ + country: "us", + parameters: { "gov.irs.credits.ctc.amount.base[0].amount": { "2026-01-01.2100-12-31": 3000 } }, +}); +const impact = await policyengine.compareEconomy( + { country: "us", policy: reformId, baseline: 2, region: "us", year: 2026 }, + { onProgress: (answer) => console.log(answer.status) }, +); + +const metadata = await policyengine.getMetadata("uk"); +``` + +| Operation | Request | Answer | +|---|---|---| +| `POST /api/household/calculate` | JSON `{country, household, policy?}`; `policy` is parameter changes, current law when omitted | The Public API's `{status, message, result}`; `calculateHousehold` resolves with `result` | +| `POST /api/policies` | JSON `{country, parameters}` | `{id}`: 201 for a new policy, 200 for an identical stored one; `createPolicy` resolves with the ID | +| `GET /api/economy/compare` | Query `country`, `policy`, `baseline`, `region`, `year`, each exactly once | `{status: "computing" \| "ok" \| "error", result, …}`, never cached | +| `GET /api/metadata/{country}` | — | `{status, message, result}`, cached by browsers for an hour (`max-age=3600`) | + +- `country` is `us` or `uk`. An unknown body field (household calculation, policies) or query + parameter (economy comparison) is refused with 400, and a body whose `Content-Type` is not + JSON with 415. +- `policy` and `baseline` are policy IDs (whole numbers). `baseline` is current law's ID: + 2 for `us`, 1 for `uk` (`result.current_law_id` in the metadata). `year` is four digits; + `region` is `us`, `uk` or a sub-national region such as `state/ca`, at most 200 characters. +- The household and parameter shapes are the Public API's (`household` nests values by year; + read variable and parameter names from the metadata). See the policyengine-api skill. +- `POST /api/household/calculate` is the Public API's `/{country}/calculate`, which adds no + variables the household leaves out: ask for each output by naming it on its entity + with `null` for the year (`income_tax: { 2026: null }`). To vary an input for a chart, send + one household with `axes` (`[[{ name: "employment_income", period: "2026", min: 0, max: + 200000, count: 41 }]]`) and its value `null`; each output comes back as a list over the + points. Never send one calculation per point: the limit is 30 a minute. +- Each method takes `{ signal }` to cancel. The result type is a type argument + (`calculateHousehold(…)`), not checked at run time. + +**Polling.** `compareEconomy` reads the comparison every 5 seconds until `status` is `ok`, and +gives up after 30 minutes with `EconomyComparisonTimeoutError`. After a 429 it waits for +`Retry-After`, or 60 seconds (`RATE_LIMIT_WAIT_MILLISECONDS`, the firewall's window) when there +is none, but never less than one interval or one second, and keeps polling. A page on another +origin cannot read the firewall's 429 (see rate limits below), so there polling stops with +`status` `null`. After a 502, 503 or 504 it waits for `Retry-After`, or one interval, and the +fourth such failure since the last answer ends polling. A comparison +that failed (502 with +`status: "error"` and `retry_after_seconds`) is thrown at once: the Public API keeps that +failure for `retry_after_seconds`, so polling sooner returns it again. Keep +`intervalMilliseconds` at a few seconds. Use `getEconomyComparison` for one read. + +**Where pages may call it from.** The client's default `baseUrl` is +`https://www.policyengine.org`. The dashboard API answers browser requests from: +- pages on www.policyengine.org itself, which is how dashboards served through the website's + rewrites (`website/src/data/appZoneRoutes.ts`) reach it: same origin, no CORS; +- `http://localhost` and `http://127.0.0.1` on any port; +- the PolicyEngine team's Vercel previews, `*-policy-engine.vercel.app`; +- each dashboard's own `*.vercel.app` host named in `appZoneRoutes.ts`. + +Every other origin gets 403, "Pages on this origin may not call the dashboard API.", and the +request never reaches the Public API. A new dashboard's own host is allowed once its rewrite is +in `appZoneRoutes.ts` and that change is deployed. A dashboard served from anywhere else, such +as a Modal URL, reaches the dashboard API only through its www.policyengine.org path. Allowed +request headers are `Content-Type`, `Authorization` and `PolicyEngine-Unauthenticated-User`; +the client sends no cookies (`credentials: "omit"`). + +**Rate limits.** www.policyengine.org's Vercel firewall limits each IP address in +fixed 60-second windows, counted per Vercel region: 30 household calculations, 120 +economy-comparison reads, and 300 policy creations and metadata reads together. Everyone behind one address, such +as an office network, shares them. Over a limit the firewall answers before the dashboard API +runs: 429, text/plain "Too Many Requests", no `Retry-After` (so `retryAfterSeconds` is `null`) +and no CORS headers. A page on www.policyengine.org reads that 429; a page on another origin, +such as `http://localhost` in development, gets a network failure (`status` `null`). Requests +are accepted again when the window ends. Calculate on a user's action or after a debounce, not +on every keystroke. + +## From servers: your own Auth0 client + +A server (a Modal backend, a Vercel function, a Cloud Run service, a CI job) calls the Public +API directly with an Auth0 client-credentials token from the Public API's own tenant: + +| | Production | Staging | +|---|---|---| +| Auth0 tenant (`POLICYENGINE_API_AUTH0_DOMAIN`) | `policyengine-api.uk.auth0.com` | `policyengine-api-staging.uk.auth0.com` | +| Audience (`POLICYENGINE_API_AUTH0_AUDIENCE`) | `https://api.policyengine.org/` | `https://staging-api.policyengine.org/` | +| Public API (`POLICYENGINE_API_URL`) | `https://api.policyengine.org` | the staging Cloud Run service, given with the client | + +The token request is `POST https:///oauth/token` with JSON +`{"grant_type": "client_credentials", "client_id": …, "client_secret": …, "audience": …}`. +Send the answer's `access_token` as `Authorization: Bearer ` on every request. +A client token from the sign-in tenants (`auth.policyengine.org`, +`policyengine-dev.uk.auth0.com`) is refused with 401. + +Other headers, when a server acts for a browser: +- a signed-in user's Auth0 token goes in `PolicyEngine-User-Authorization: Bearer `, + never in `Authorization`; +- `PolicyEngine-Unauthenticated-User: ` is passed on unchanged; +- send at most one of each; two `Authorization` headers are refused. + +**Cache the token across instances.** Tokens last 24 hours, and Auth0 counts the +machine-to-machine tokens it issues per tenant per month. Never request a token per request. +Without a shared store each instance requests one when it starts, which is fine for a few +long-lived instances (a Cloud Run service with a low maximum) but not where instances start +often, such as Modal containers: +- **Python (Modal, Cloud Run, scripts):** copy `templates/public_api_token.py`. It keeps the + token in memory and, when given one, in a store the instances share (a `modal.Dict` on Modal; + any object with `get` and item assignment), replaces it five minutes before it expires, + raises a 401 without requesting another token, and never follows a redirect. Its docstring + has the Modal wiring. +- **Vercel functions:** keep it in Vercel Runtime Cache, as app-v2's + `packages/public-api-forwarding/src/accessToken.ts` does: + + ```ts + import { getCache } from "@vercel/functions"; + + type CachedToken = { accessToken: string; refreshAtMilliseconds: number }; + const tokenCache = getCache({ namespace: "policyengine-public-api" }); + + const cached = (await tokenCache.get(cacheKey)) as CachedToken | null | undefined; + if (!cached || Date.now() >= cached.refreshAtMilliseconds) { + // Request a token; accessToken and expiresIn (seconds) come from Auth0's answer. Keep it + // until five minutes before it expires, or halfway through a shorter life. + const keepSeconds = expiresIn - Math.min(300, expiresIn / 2); + const refreshAtMilliseconds = Date.now() + keepSeconds * 1000; + await tokenCache.set(cacheKey, { accessToken, refreshAtMilliseconds }, { ttl: Math.max(1, Math.floor(keepSeconds)) }); + } + ``` + + The key is the tenant, audience and client ID, never the secret. Requests that arrive while a + token is being fetched should wait for that one request. +- **CI:** request one token at the start of the job and reuse it for every request in the job. + +Also: +- **Store the five variables in a secret store**, under the names app-v2 and the template use: + `POLICYENGINE_API_URL`, `POLICYENGINE_API_AUTH0_DOMAIN`, `POLICYENGINE_API_AUTH0_AUDIENCE`, + `POLICYENGINE_API_AUTH0_CLIENT_ID` and `POLICYENGINE_API_AUTH0_CLIENT_SECRET`. That is a Modal + secret, Vercel environment variables (the secret marked sensitive), or GitHub Actions secrets + and variables. Never the repository, never a `NEXT_PUBLIC_*` or `VITE_*` name. +- **Never follow redirects.** A followed 307 or 308 re-sends the token request's body, client + secret included, to wherever it points, and Python's `urllib` also copies `Authorization` to a + redirect's target. The template turns redirects off for both requests; in `fetch`, pass + `redirect: "error"`. +- **Cache what does not change.** `/{country}/metadata` is about 10 MB compressed; read it once + per deployment or hour, not per request. + +### Getting a client + +1. Ask the Public API's maintainers for a client for your caller. Say which repository and + service it is, and which environments it needs: production, and staging if previews or local + work call the Public API too. +2. They create a machine-to-machine application in each tenant, with only the + `client_credentials` grant and a grant for that tenant's audience, and put the secret straight + into your caller's secret store. A secret never goes in an issue, a chat message, a PR or a + file. +3. They add the client ID to `PUBLIC_API_SERVER_CLIENT_IDS` in policyengine-api's GitHub + `production` (and `staging`) environment and redeploy the Public API. +4. **Only then deploy code that sends the token.** A valid token of a client that is not on the + list gets 401 today, even while requests without any `Authorization` header are still served. +5. Check it: `GET /health` with the token answers 200. The Public API checks a sent token on + every route, including `/health`, so 401 there means the client, tenant or audience is wrong. + +## Local development and previews + +- **Dashboard pages:** a dashboard on `http://localhost:` or on its team preview + (`*-policy-engine.vercel.app`) calls the production dashboard API at www.policyengine.org + through CORS, with nothing to configure and no credential on the developer's machine. To try a + change to the dashboard API before it is deployed, pass that website preview's URL as + `baseUrl`; website previews call the Public API's staging service. +- **Servers:** production deployments use the production tenant, client and Public API; + preview and development deployments use the staging tenant, a staging client and the + staging service. On Vercel, set the five variables per environment (Production; Preview and + Development), then run `vercel env pull .env.local` for local work, which writes the + Development values. Vercel cannot mark Development values sensitive, so only staging values go + there. On Modal, keep production and staging values in separate secrets. +- Never point a preview or a local run at the production Public API with the production client. + +## Asking for a new dashboard operation + +The dashboard API has exactly the four operations above. Adding one is a change to +`PolicyEngine/policyengine-app-v2`: a route under `website/src/app/api/` and its check in +`website/src/lib/dashboardApi/operations.ts`, a method in `packages/api-client`, and a new +release of `@policyengine/api-client`. Ask for it in an issue in +`PolicyEngine/policyengine-app-v2` that names the Public API call it maps to, the request and +answer shapes the dashboard needs, how often a page calls it, and whether its answers can be +cached. Until it ships, do not call the Public API from the page as a stopgap; precompute the +data, or use a backend with its own client. + +## Errors + +`@policyengine/api-client` throws `PolicyEngineApiError` with `status` (`null` when no answer +could be read), `message`, `body` and `retryAfterSeconds`. The dashboard API's own errors are +`{"status": "error", "message": …, "result": null}`; the Public API's 401 and 503 bodies have +`status` and `message` only. Read `message`. + +| Status | From | Meaning | What to do | +|---|---|---|---| +| 400 | Dashboard API | A missing or unknown field, or a malformed value | Fix the request; `message` names the field | +| 401 | Public API | `{"status":"error","message":"Authentication is required"}` with `WWW-Authenticate: Bearer`. Usually the server's own token: an invalid or expired token, a client not on the allow list, a client token from another tenant, two `Authorization` headers, or, once the requirement is on, no server credential. A user route answers the same when the server forwards no user credential | Server: do not request another token, which would be refused too and counts against the tenant's monthly allowance. On a user route, check first that the request forwarded the user's credential; otherwise check that the client is listed in that environment and that its tenant and audience match, and report it. Page: the website's own credential failed; show an error, do not retry in a loop | +| 401 | Public API user routes | `WWW-Authenticate: Bearer error="invalid_token"` and "The bearer access token is invalid": the forwarded `PolicyEngine-User-Authorization` token is invalid or expired | Keep the server token; the user signs in again | +| 403 | Dashboard API | "Pages on this origin may not call the dashboard API." The page's origin is not allowed | Serve the page from an allowed origin. A cross-origin page cannot read this answer: the browser reports a CORS error and the client throws with `status` `null` | +| 403 | Public API user routes | A forwarded `PolicyEngine-User-Authorization` token is a machine-to-machine token, not a user's | Forward only a signed-in user's own token there | +| 404 | Dashboard API | An unknown country in `/api/metadata/{country}` | Use `us` or `uk` | +| 415 | Dashboard API | The body's `Content-Type` is not JSON | Send `Content-Type: application/json`; the client does | +| 429 | Dashboard API firewall | "Too Many Requests" as text/plain, with no `Retry-After` and no CORS headers: one IP address passed a per-minute limit | Wait for the 60-second window to end before sending again; never retry at once. On another origin (localhost) the page sees a network failure with `status` `null` instead, so treat repeated `null` statuses during heavy use the same way | +| 429 | The Public API's load balancer | Too many requests from one IP address, not JSON | Server: wait `Retry-After` when present, otherwise back off (5 s, then doubling). Requests with a bearer token skip its per-address limits on `/{country}/metadata`, `/{country}/calculate` and `/{country}/calculate-full` | +| 502 | Dashboard API | Auth0 refused or did not answer the website's token request, the Public API could not be reached or answered with a redirect, or the Public API's answer to `POST /api/policies` named no policy ID | Retry later with back-off | +| 502 | Public API, economy comparison | `status: "error"` with `retry_after_seconds`: the comparison failed | Show the failure; do not ask again before `retry_after_seconds` | +| 503 | Public API | `{"status":"error","message":"Authentication is unavailable"}`: it could not check the token (Auth0's signing keys unreachable, or its own settings) | Keep the token; retry with back-off. It is not a refusal of the client | +| 503 | Dashboard API | Its own settings are missing or incomplete, for example on a website preview | Not retryable; report it to the website's maintainers | +| 504 | Dashboard API | The Public API did not answer in time (100 s for a household calculation, 40 s for a policy or a comparison read, 60 s for metadata) | Retry later with back-off | + +## Related skills + +- policyengine-api — the Public API's endpoints, household and policy shapes, and the v2 alpha +- policyengine-tools — building and embedding a standalone tool; its data patterns +- policyengine-app — policyengine.org itself, including `appZoneRoutes.ts` +- policyengine-cloud-costs — Modal and Cloud Run settings and secrets for a backend diff --git a/skills/policyengine-api-access/templates/public_api_token.py b/skills/policyengine-api-access/templates/public_api_token.py new file mode 100644 index 0000000..f219629 --- /dev/null +++ b/skills/policyengine-api-access/templates/public_api_token.py @@ -0,0 +1,275 @@ +"""Auth0 client-credentials tokens for the PolicyEngine Public API, cached across instances. + +Copy this file into a server that calls the Public API (api.policyengine.org) +itself: a Modal backend, a Cloud Run service or a scheduled job. Browser code +never calls the Public API; pages use www.policyengine.org's dashboard API +(the policyengine-api-access skill). + +It reads the five variables below from the server's secret store, asks the +Auth0 tenant for one token, and keeps it until five minutes before it expires: +first in this process's memory, then in a store the server's instances share, +so a deployment asks Auth0 for about one token a day rather than one per +request or per container. Auth0 counts the machine-to-machine tokens it issues +per tenant per month. + + POLICYENGINE_API_URL https://api.policyengine.org in production + POLICYENGINE_API_AUTH0_DOMAIN policyengine-api.uk.auth0.com in production + POLICYENGINE_API_AUTH0_AUDIENCE https://api.policyengine.org/ in production + POLICYENGINE_API_AUTH0_CLIENT_ID this caller's own client + POLICYENGINE_API_AUTH0_CLIENT_SECRET that client's secret + +On Modal, the shared store is a Dict, and each container builds one provider the +first time it needs one (the secret's variables exist only inside the container). +Modal copies only the app's own module into containers, so the image adds this one: + + import functools + + import modal + from public_api_token import PublicApiSettings, PublicApiTokenProvider, call_public_api + + image = modal.Image.debian_slim().add_local_python_source("public_api_token") + TOKEN_STORE = modal.Dict.from_name("TOOL_NAME-public-api-token", create_if_missing=True) + + @functools.cache + def public_api_tokens() -> PublicApiTokenProvider: + return PublicApiTokenProvider(PublicApiSettings.from_environment(), shared_store=TOKEN_STORE) + + @app.function(image=image, secrets=[modal.Secret.from_name("TOOL_NAME-policyengine-api")]) + def household(situation: dict) -> dict: + return call_public_api(public_api_tokens(), "POST", "/us/calculate", {"household": situation}) + +Any object with ``get(key)`` and item assignment works as the shared store; a +plain ``dict`` keeps the token for one process only. Only the token and when to +replace it are stored, never the client secret. + +A 401 is raised, never answered with a new token: it means the client is not on +the Public API's allow list in this environment, or its tenant or audience is +wrong, so a new token would be refused too and would only use up the tenant's +monthly allowance. So a token that Auth0 has revoked stays in use until it is +replaced, at most 24 hours later. To drop it sooner, delete its entry from the +shared store (the key is "public-api-token ") and +restart the instances. + +Requests never follow redirects: a followed redirect would send the client +secret, or the token, to wherever it points. +""" + +from __future__ import annotations + +import json +import logging +import os +import threading +import time +import urllib.error +import urllib.request +from collections.abc import Callable, Mapping +from dataclasses import dataclass, field +from typing import Any, Protocol, TypeGuard + +LOGGER = logging.getLogger(__name__) + +# Replace a token this long before it expires (half its life, if shorter). +TOKEN_REFRESH_MARGIN_SECONDS = 300 +TOKEN_REQUEST_TIMEOUT_SECONDS = 10 +PUBLIC_API_TIMEOUT_SECONDS = 120 + +SETTING_VARIABLES = { + "api_url": "POLICYENGINE_API_URL", + "token_endpoint_host": "POLICYENGINE_API_AUTH0_DOMAIN", + "audience": "POLICYENGINE_API_AUTH0_AUDIENCE", + "client_id": "POLICYENGINE_API_AUTH0_CLIENT_ID", + "client_secret": "POLICYENGINE_API_AUTH0_CLIENT_SECRET", +} + + +class SharedTokenStore(Protocol): + """What the provider needs from a shared store: a dict, a modal.Dict, or similar.""" + + def get(self, key: str, default: Any = None) -> Any: ... + + def __setitem__(self, key: str, value: Any) -> None: ... + + +class Opener(Protocol): + """What the provider needs to send a request: urllib's OpenerDirector, or a fake in tests.""" + + # The request by position and the timeout by keyword, as OpenerDirector.open + # (fullurl, data=None, timeout=...) accepts them. + def open(self, request: urllib.request.Request, /, *, timeout: float) -> Any: ... + + +class PublicApiSettingsError(ValueError): + """A setting is missing or malformed.""" + + +class PublicApiTokenError(RuntimeError): + """Auth0 refused the token request or gave no usable token.""" + + +class _RefuseRedirects(urllib.request.HTTPRedirectHandler): + def redirect_request( + self, req: urllib.request.Request, fp: Any, code: int, msg: str, headers: Any, newurl: str + ) -> None: + return None # urllib then raises HTTPError with the 3xx status + + +NO_REDIRECT_OPENER = urllib.request.build_opener(_RefuseRedirects) + + +@dataclass(frozen=True) +class PublicApiSettings: + api_url: str + token_endpoint_host: str + audience: str + client_id: str + # Kept out of repr(), so a logged or printed settings object never shows it. + client_secret: str = field(repr=False) + + @classmethod + def from_environment(cls, environment: Mapping[str, str] | None = None) -> PublicApiSettings: + environment = os.environ if environment is None else environment + values = {field: environment.get(name, "").strip() for field, name in SETTING_VARIABLES.items()} + missing = [SETTING_VARIABLES[field] for field, value in values.items() if not value] + if missing: + raise PublicApiSettingsError(f"Set {', '.join(missing)}.") + values["api_url"] = values["api_url"].rstrip("/") + # Accept "tenant.uk.auth0.com", with or without https:// and a trailing slash. + values["token_endpoint_host"] = ( + values["token_endpoint_host"].removeprefix("https://").rstrip("/") + ) + return cls(**values) + + +class PublicApiTokenProvider: + """Gives out one cached access token per client; thread-safe within a process.""" + + def __init__( + self, + settings: PublicApiSettings, + shared_store: SharedTokenStore | None = None, + clock: Callable[[], float] = time.time, + opener: Opener = NO_REDIRECT_OPENER, + ) -> None: + self.settings = settings + self.shared_store = shared_store + self.clock = clock + self.opener = opener + # The client secret is not part of the key, so it never reaches the store. + self.cache_key = " ".join( + ("public-api-token", settings.token_endpoint_host, settings.audience, settings.client_id) + ) + self._remembered: dict[str, Any] | None = None + self._lock = threading.Lock() + + def access_token(self) -> str: + with self._lock: + if self._is_usable(self._remembered): + return str(self._remembered["access_token"]) + shared_token = self._read_shared_token() + if self._is_usable(shared_token): + self._remembered = shared_token + return str(shared_token["access_token"]) + fresh_token = self._request_token() + self._remembered = fresh_token + self._write_shared_token(fresh_token) + return str(fresh_token["access_token"]) + + def _is_usable(self, token: dict[str, Any] | None) -> TypeGuard[dict[str, Any]]: + return ( + isinstance(token, dict) + and isinstance(token.get("access_token"), str) + and bool(token["access_token"]) + and isinstance(token.get("refresh_at"), (int, float)) + and self.clock() < token["refresh_at"] + ) + + def _read_shared_token(self) -> dict[str, Any] | None: + if self.shared_store is None: + return None + try: + stored = self.shared_store.get(self.cache_key) + except Exception as error: # a store outage must not stop requests + LOGGER.warning("Could not read the Public API token from the shared store: %s", error) + return None + return stored if isinstance(stored, dict) else None + + def _write_shared_token(self, token: dict[str, Any]) -> None: + if self.shared_store is None: + return + try: + self.shared_store[self.cache_key] = token + except Exception as error: + LOGGER.warning("Could not store the Public API token in the shared store: %s", error) + + def _request_token(self) -> dict[str, Any]: + request = urllib.request.Request( + f"https://{self.settings.token_endpoint_host}/oauth/token", + data=json.dumps( + { + "grant_type": "client_credentials", + "client_id": self.settings.client_id, + "client_secret": self.settings.client_secret, + "audience": self.settings.audience, + } + ).encode(), + headers={"Content-Type": "application/json", "Accept": "application/json"}, + method="POST", + ) + try: + with self.opener.open(request, timeout=TOKEN_REQUEST_TIMEOUT_SECONDS) as response: + answer = json.loads(response.read()) + except urllib.error.HTTPError as error: + # Auth0's "error" field is a fixed code such as "access_denied"; the + # rest of the answer is not logged. + try: + error_code = json.loads(error.read()).get("error", "") + except Exception: + error_code = "" + raise PublicApiTokenError( + f"Auth0 refused the token request with HTTP {error.code} {error_code}".strip() + ) from None + except (urllib.error.URLError, TimeoutError, ValueError) as error: + raise PublicApiTokenError(f"The Auth0 token request failed: {error}") from None + + access_token = answer.get("access_token") + lifetime_seconds = answer.get("expires_in") + if not isinstance(access_token, str) or not access_token or not isinstance( + lifetime_seconds, (int, float) + ) or lifetime_seconds <= 0: + raise PublicApiTokenError("Auth0's answer has no access token or lifetime.") + margin_seconds = min(TOKEN_REFRESH_MARGIN_SECONDS, lifetime_seconds / 2) + return { + "access_token": access_token, + "refresh_at": self.clock() + lifetime_seconds - margin_seconds, + } + + +def call_public_api( + tokens: PublicApiTokenProvider, + method: str, + path: str, + body: Any = None, + *, + opener: Opener | None = None, + timeout_seconds: float = PUBLIC_API_TIMEOUT_SECONDS, +) -> Any: + """Sends one request with the server token and returns the parsed JSON answer. + + Raises: + PublicApiTokenError: no token could be obtained from Auth0. + urllib.error.HTTPError: the Public API answered 3xx (never followed) or an + error status; ``error.code`` is the status and ``error.read()`` the body. + A 401 is raised without asking Auth0 for another token (see the module + docstring). + urllib.error.URLError, TimeoutError: the Public API could not be reached in time. + ValueError: the answer was not JSON. + """ + opener = tokens.opener if opener is None else opener + data = None if body is None else json.dumps(body).encode() + headers = {"Accept": "application/json", "Authorization": f"Bearer {tokens.access_token()}"} + if data is not None: + headers["Content-Type"] = "application/json" + request = urllib.request.Request(f"{tokens.settings.api_url}{path}", data=data, headers=headers, method=method) + with opener.open(request, timeout=timeout_seconds) as response: + return json.loads(response.read()) diff --git a/skills/policyengine-api/SKILL.md b/skills/policyengine-api/SKILL.md index f7133bc..cabba92 100644 --- a/skills/policyengine-api/SKILL.md +++ b/skills/policyengine-api/SKILL.md @@ -1,15 +1,15 @@ --- name: policyengine-api description: | - Load when consuming or developing the PolicyEngine REST APIs — the v1 Flask API at - api.policyengine.org (production today) or the v2 alpha microservices in policyengine-api-v2. - Covers the v1 endpoint surface (household calc, economy-over-baseline polling), the v2 - architecture (three services, uv/Docker/Cloud Run, OpenAPI client-gen), and when to use REST - vs the Python package. + Load when developing the PolicyEngine REST APIs or writing server code against them — the v1 + Flask API at api.policyengine.org (the Public API, production today, callable only with a + server credential) or the v2 alpha microservices in policyengine-api-v2. Covers the v1 + endpoint surface (household calc, economy-over-baseline polling), the v2 architecture (three + services, uv/Docker/Cloud Run, OpenAPI client-gen), and when to use REST vs the Python package. Triggers: PolicyEngine API, api.policyengine.org, REST endpoint, household calculate, economy over baseline, v2 API, api-full, api-simulation, api-tagger, OpenAPI client, v2.api.policyengine.org. - NOT for: Python analysis (use the policyengine skill) or calling the API from the app - frontend (policyengine-app). + NOT for: Python analysis (use the policyengine skill), or reaching the API from a dashboard + page or your own server (policyengine-api-access). metadata: category: apps --- @@ -22,13 +22,20 @@ Two APIs coexist: **v1** (`PolicyEngine/policyengine-api`, Flask) is production **Pick the right client first.** If you are computing policy impacts **from Python**, use the `policyengine` package (`pe.us.calculate_household`, `Simulation`, `economic_impact_analysis`) — see the **policyengine** skill. It pins a certified model + data bundle and is reproducible. -Reach for REST only for **apps and external integrations** (JS frontends, third parties) that -need HTTP. + +**Who may call v1.** The Public API is becoming callable only by PolicyEngine's own servers, +each with its own Auth0 client-credentials token (a server credential) in +`Authorization: Bearer`. Once that requirement is on, every other request gets 401. Browser +code never calls it: dashboard pages use www.policyengine.org's dashboard API through +`@policyengine/api-client`. The **policyengine-api-access** skill covers both routes, getting a +client, and the errors. ## v1 API — production (`https://api.policyengine.org`) Flask (`policyengine_api`, Python 3.10/3.11). Country id (`us`, `uk`, `ca`, …) is the first -path segment. Verified route surface (`policyengine_api/routes/`, `api.py`): +path segment. Every route except the public ones below needs a server credential once the +requirement is on. Verified route surface (`policyengine_api/routes/` and `api.py`; the public +`/health` and `/v2/openapi.json` are in `policyengine_api/fastapi_routes/`): | Route | Method | Purpose | |---|---|---| @@ -41,7 +48,8 @@ path segment. Verified route surface (`policyengine_api/routes/`, `api.py`): | `/{country}/policy` · `/{country}/policy/{id}` | POST · GET | store / fetch a reform policy | | `/{country}/economy/{policyId}/over/{baselinePolicyId}` | GET | society-wide reform impact | | `/{country}/simulation`, `/{country}/report` | POST/GET/PATCH | stored simulations / report output | -| `/specification` | GET | OpenAPI spec (also `/liveness-check`, `/readiness-check`) | +| `/health` | GET, HEAD | health check; public | +| `/v2/openapi.json` | GET | OpenAPI schema; public | **The economy endpoint is asynchronous — poll it.** The same GET is re-issued until the job finishes; the response is a status envelope, not the result on the first call: @@ -90,11 +98,12 @@ app-v2 already carries a **v2 alpha adapter** in `app/src/api/v2/` (`API_V2_BASE_URL = process.env.NEXT_PUBLIC_API_V2_URL || 'https://v2.api.policyengine.org'`), with endpoints per **policyengine-api-v2-alpha PR #77**. v2 calculations are **async job + poll**, and it adds CRUD for households/policies/simulations. **Variation/axes household -calculations are not in v2** and remain on v1. Until v2 is promoted, keep new REST consumers on -v1 unless you specifically need a v2 capability. +calculations are not in v2** and remain on v1. Until v2 is promoted, keep new server-side REST +consumers on v1 unless you specifically need a v2 capability. ## Related skills - policyengine — Python analysis (the preferred path for computing impacts from Python) +- policyengine-api-access — reaching v1 from dashboard pages (the dashboard API) and from servers (a server credential) - policyengine-app — how the app-v2 frontend calls v1 and the v2 adapter - policyengine-tools — Modal backends for computations the API doesn't expose diff --git a/skills/policyengine-app/SKILL.md b/skills/policyengine-app/SKILL.md index 18218d1..cb6e9e6 100644 --- a/skills/policyengine-app/SKILL.md +++ b/skills/policyengine-app/SKILL.md @@ -90,8 +90,17 @@ that add country-prefixed `*.vercel.app` rewrites to `vercel.json`. Full zone me ## Calling the API -The legacy calculator calls the **v1 API** (`app/src/constants.ts`: -`BASE_URL = 'https://api.policyengine.org'`). Verified shapes: +The calculator's browser code reaches the **v1 API** (the Public API) through `BASE_URL` +(`app/src/constants.ts`: `VITE_API_URL`, falling back to `https://api.policyengine.org`). The +Public API is becoming callable only by PolicyEngine's servers, so those calls are moving to +app.policyengine.org's own same-origin route, `/api/policyengine/{Public API path}` +(`calculator-app/src/app/api/policyengine/[...path]/route.ts`). The route adds the calculator's +server credential and forwards only the operations in `app/src/libs/publicApiRoute/allowList.ts`; +anything else gets 404. New calculator code goes through that route, and an operation the app +has not called before needs an allow-list entry in the same PR. Never add a browser call to +api.policyengine.org. Dashboards use the website's dashboard API (`website/src/app/api/`) +instead; see policyengine-api-access. Verified shapes, as Public API paths (through the route, +each sits under `/api/policyengine`, for example `/api/policyengine/us/household/{id}`): - `GET {BASE_URL}/{country}/household/{id}` and `.../household/{id}/policy/{policyId}` — household calc - `POST {BASE_URL}/{country}/household` — create household @@ -127,6 +136,9 @@ policyengine-api skill for both APIs. | `website/src/data/appZoneRoutes.ts` | Registry of external Next zones | | `website/src/app/[countryId]//page.tsx` | Server page + client split | | `app/src/constants.ts` | `BASE_URL` (v1 API) | +| `app/src/libs/publicApiRoute/allowList.ts` | Operations app.policyengine.org's own route forwards to the Public API | +| `website/src/app/api/` | The dashboard API dashboards call (policyengine-api-access) | +| `packages/public-api-forwarding/` | Server-side forwarding both routes use: token cache, headers, errors | | `app/src/api/` and `app/src/api/v2/` | v1 fetchers and v2 alpha adapter | | `app/src/designTokens/` | Local JS token shim (re-exported by website) | | `vercel.json` (root) | Legacy config — do not add new zone rewrites here | @@ -136,4 +148,5 @@ policyengine-api skill for both APIs. - policyengine-tools — standalone embedded tools + full multizone zone config - policyengine-design — ui-kit 0.4.0 components and design tokens - policyengine-api — v1 and v2 API surfaces +- policyengine-api-access — the dashboard API and server credentials for the Public API - policyengine-writing — content and copy style diff --git a/skills/policyengine-tools/SKILL.md b/skills/policyengine-tools/SKILL.md index 9da8762..ab11b23 100644 --- a/skills/policyengine-tools/SKILL.md +++ b/skills/policyengine-tools/SKILL.md @@ -9,8 +9,9 @@ description: | Triggers: new tool, standalone calculator, dashboard, embed in policyengine.org, multizone, basePath, assetPrefix, appZoneRoutes, Modal backend, scale to zero, vercel --scope policy-engine, ui-kit theme.css, recharts colors. - NOT for: developing policyengine.org itself (use policyengine-app) or token values - (policyengine-design). + NOT for: developing policyengine.org itself (use policyengine-app), token values + (policyengine-design), or how a page or backend reaches api.policyengine.org + (policyengine-api-access). metadata: category: apps --- @@ -132,11 +133,21 @@ Never paste ad-hoc computed numbers into source. Always: Python script → data - **A — Precomputed JSON/CSV**: finite scenarios (legislative trackers, static analyses). A `scripts/*.py` microsimulation writes to `public/data/`; the frontend imports it. Zero latency, works offline. -- **B — v1 PolicyEngine API** (`https://api.policyengine.org`): household-level calc across - user-entered income/family/state. `POST /{country}/calculate` with a household payload, or - the stateful household/economy flow. Always up to date; network latency. See policyengine-api. -- **C — Custom Modal backend**: only when the v1 API can't do it (society-wide microsim, - custom reform params, non-standard entities). Pattern below. +- **B — the dashboard API** (`@policyengine/api-client`): household-level calc across + user-entered income/family/state, plus storing a reform and comparing it across the economy, + and country metadata. The page calls www.policyengine.org's dashboard API + (`POST /api/household/calculate`, `POST /api/policies`, `GET /api/economy/compare`, + `GET /api/metadata/{country}`), whose server adds PolicyEngine's credential and forwards to + the Public API. Always up to date; network latency. Never call the Public API + (api.policyengine.org) from the page. See policyengine-api-access for the operations, + polling, allowed origins and errors, and policyengine-api for the household shapes. +- **C — Custom Modal backend**: only when the dashboard API can't do it: microsimulation output + its economy comparison doesn't give (custom breakdowns, datasets or years), structural + reforms that parameter changes can't express, or non-standard entities. A parameter reform's + economy-wide comparison with current law is Pattern B. Pattern below. A backend that itself calls the + Public API needs its own Auth0 client and a cached server token (policyengine-api-access, + `templates/public_api_token.py`); one that runs `policyengine` in its own process needs + neither. ### Pattern C: Modal gateway + worker + polling @@ -274,5 +285,6 @@ test, `bun run build`) before launch. - policyengine-app — the host site (policyengine.org) and its multizone registry - policyengine-design — ui-kit 0.4.0 components, tokens, chart colors, Plotly style -- policyengine-api — v1/v2 API surfaces for Pattern B +- policyengine-api-access — Pattern B's dashboard API, and server credentials for backends that call the Public API +- policyengine-api — the Public API's endpoints and household shapes - policyengine (Python) — microsimulation for Pattern A/C data scripts diff --git a/skills/policyengine/SKILL.md b/skills/policyengine/SKILL.md index 32f9080..955820c 100644 --- a/skills/policyengine/SKILL.md +++ b/skills/policyengine/SKILL.md @@ -10,7 +10,8 @@ description: | congressional district, constituency, Microcosm dataset, MicroSeries, ensure_datasets, economic_impact_analysis, managed_microsimulation. NOT for: implementing new variables/parameters inside country models (use - policyengine-model-development) or calling the REST API from JS (use policyengine-api). + policyengine-model-development) or calling the REST API from a page or server (use + policyengine-api-access). metadata: category: analysis --- diff --git a/targets/claude/agents/dashboard/backend-builder.md b/targets/claude/agents/dashboard/backend-builder.md index 6256c1a..db78c57 100644 --- a/targets/claude/agents/dashboard/backend-builder.md +++ b/targets/claude/agents/dashboard/backend-builder.md @@ -20,6 +20,7 @@ Builds the data layer for a dashboard based on the approved `plan.yaml`. ## Skills Used - **policyengine-tools** - Data patterns, API integration, and Modal backend architecture +- **policyengine-api-access** - The dashboard API for pages, and server credentials for a backend that calls the Public API (policyengine-api and custom-modal) - **policyengine-us** or **policyengine-uk** - PolicyEngine variables - **policyengine** - Python simulation mechanics, reform dictionaries, and parameter path syntax (custom-modal only) - **policyengine-cloud-costs** - Modal defaults (scale to zero, `max_containers`, measured sizes) and how to declare an exception (custom-modal only) @@ -28,8 +29,8 @@ Builds the data layer for a dashboard based on the approved `plan.yaml`. **Always prefer simpler patterns.** Before building a custom backend: -1. **Can `api.policyengine.org/calculate` handle it?** → Use Pattern B (`policyengine-api`) -2. **Does it need microsimulation or custom reforms?** → Use Pattern C (`custom-modal`) with gateway + polling +1. **Can the dashboard API handle it?** It calculates households, stores a parameter reform and compares it with current law across the economy, and reads country metadata. → Use Pattern B (`policyengine-api`) +2. **Does it need other microsimulation output or structural reforms?** → Use Pattern C (`custom-modal`) with gateway + polling 3. **Is the parameter space finite?** → Use Pattern A/D (`precomputed` / `precomputed-csv`) Pattern C is the most complex and should be the last resort. If the plan specifies `custom-modal`, the plan MUST include a `reason` explaining why Pattern B is insufficient. @@ -39,10 +40,11 @@ Pattern C is the most complex and should be the last resort. If the plan specifi **Before starting ANY work, use the Skill tool to load each required skill:** 1. `Skill: policyengine-tools` -2. `Skill: policyengine-us` (if US dashboard) -3. `Skill: policyengine-uk` (if UK dashboard) -4. `Skill: policyengine` (if custom-modal pattern — **required** for simulation mechanics and correct reform parameter paths) -5. `Skill: policyengine-cloud-costs` (if custom-modal pattern) +2. `Skill: policyengine-api-access` (if policyengine-api pattern, or a custom-modal backend that calls the Public API) +3. `Skill: policyengine-us` (if US dashboard) +4. `Skill: policyengine-uk` (if UK dashboard) +5. `Skill: policyengine` (if custom-modal pattern — **required** for simulation mechanics and correct reform parameter paths) +6. `Skill: policyengine-cloud-costs` (if custom-modal pattern) ## Input @@ -115,59 +117,87 @@ Create `lib/api/__tests__/client.test.ts`: - Test that data matches expected TypeScript types - Test that all expected keys/dimensions are present -## Pattern B: PolicyEngine API (`policyengine-api`) +## Pattern B: the dashboard API (`policyengine-api`) -When `data_pattern: policyengine-api`, the dashboard calls `api.policyengine.org` directly for household calculations. No custom backend needed. +When `data_pattern: policyengine-api`, the page calls www.policyengine.org's dashboard API through `@policyengine/api-client`. The website's server adds PolicyEngine's credential and forwards each operation to the Public API, so no custom backend is needed. **The page never calls `api.policyengine.org`**: the Public API is becoming callable only by PolicyEngine's servers. The `policyengine-api-access` skill has the four operations, polling, the origins the dashboard API accepts, and its errors. -### Step 1: Define Types +### Step 1: Install the client and define types -Read the plan's endpoints and generate TypeScript interfaces for the household request and response: +```bash +bun add @policyengine/api-client +``` + +Read the plan's endpoints and write the household and result types. Use `type` aliases: the client takes `Record`, which an `interface` is not assignable to. ```typescript // types.ts -/** Household structure for the PolicyEngine API */ -export interface HouseholdRequest { - household: { - people: Record>>; - tax_units: Record; - spm_units: Record; - households: Record; - }; -} - -/** API response from /calculate */ -export interface CalculateResponse { - status: 'ok' | 'error'; - message: string | null; - result: Record; +/** + * A household in PolicyEngine's situation format: values keyed by year, and null for + * each output to calculate. The group entities differ by country (US: tax_units, + * spm_units, families, marital_units, households; UK: benunits, households); read them + * from the country's metadata. `axes` varies one input over a range (integrator Step 6). + */ +export type Household = { + people: Record>>; + axes?: Array>; + [groupEntities: string]: unknown; +}; + +/** A calculated value: one value, or a list over the points when the household has axes */ +export type CalculatedValue = number | boolean | string | null | Array; + +/** The calculated household: the request's shape, with the requested variables filled in */ +export type CalculatedHousehold = Record; + +/** + * One variable from a calculated household, for example + * readVariable(result, 'households', 'household', 'household_net_income', '2026'). + * Null when the household did not ask for it. + */ +export function readVariable( + result: CalculatedHousehold, + entityGroup: string, + entity: string, + variable: string, + year: string, +): CalculatedValue { + const entities = result[entityGroup] as Record> | undefined; + const values = entities?.[entity]?.[variable] as Record | undefined; + return values?.[year] ?? null; } // Add dashboard-specific types for the variables in the plan ``` -### Step 2: Build the API Client +### Step 2: Build the API client Create `lib/api/client.ts`: ```typescript // client.ts -const API_BASE = 'https://api.policyengine.org'; - -export async function calculate( - countryId: string, - household: HouseholdRequest['household'], -): Promise { - const res = await fetch(`${API_BASE}/${countryId}/calculate`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ household }), - }); - if (!res.ok) throw new Error(`API error: ${res.status}`); - return res.json(); +import { createPolicyEngineApiClient } from '@policyengine/api-client'; +import type { CalculatedHousehold, Household } from './types'; + +// The default base URL, https://www.policyengine.org, works from the dashboard's +// www.policyengine.org path, its team previews (*-policy-engine.vercel.app), localhost, and +// its own *.vercel.app host once website/src/data/appZoneRoutes.ts names it. +const policyengine = createPolicyEngineApiClient(); + +export function calculate( + countryId: 'us' | 'uk', + household: Household, + signal?: AbortSignal, +): Promise { + return policyengine.calculateHousehold( + { country: countryId, household }, + { signal }, + ); } ``` +For a reform, pass its parameter changes as `policy` to `calculateHousehold`. For an economy-wide comparison, store the reform with `createPolicy` and poll with `compareEconomy` (current law's policy ID is 2 for `us`, 1 for `uk`). + ### Step 3: Build React Query Hooks Create `lib/hooks/useCalculation.ts`: @@ -175,21 +205,20 @@ Create `lib/hooks/useCalculation.ts`: ```typescript import { useMutation } from '@tanstack/react-query'; import { calculate } from '../api/client'; -import type { HouseholdRequest } from '../api/types'; +import type { Household } from '../api/types'; -export function useCalculation(countryId: string) { +export function useCalculation(countryId: 'us' | 'uk') { return useMutation({ - mutationFn: (household: HouseholdRequest['household']) => - calculate(countryId, household), + mutationFn: (household: Household) => calculate(countryId, household), }); } ``` ### Step 4: Write Tests -Create `lib/api/__tests__/client.test.ts`: +Create `lib/api/__tests__/client.test.ts`. `client.ts` builds its client when it loads, so mock the package (`vi.mock('@policyengine/api-client', …)`) before importing it; no test calls the network: - Test that request bodies are correctly structured -- Test that the client handles error responses +- Test that `PolicyEngineApiError` from a refused request reaches the UI's error state - Test type conformance of expected response shapes ## Pattern C: Custom API on Modal (`custom-modal`) — Gateway + Polling diff --git a/targets/claude/agents/dashboard/dashboard-integrator.md b/targets/claude/agents/dashboard/dashboard-integrator.md index 6dca4dc..7e5fbdb 100644 --- a/targets/claude/agents/dashboard/dashboard-integrator.md +++ b/targets/claude/agents/dashboard/dashboard-integrator.md @@ -21,12 +21,14 @@ Wires the frontend components to the backend API client and ensures the full dat - **policyengine-tools** - Data flow patterns - **policyengine-design** - Loading/error state styling - **policyengine-api** - v1 API household payload structure (policyengine-api pattern only) +- **policyengine-api-access** - The dashboard API the page calls through `@policyengine/api-client`, and its errors (policyengine-api pattern only) ## First: Load Required Skills 1. `Skill: policyengine-tools` 2. `Skill: policyengine-design` 3. `Skill: policyengine-api` (if `data_pattern: policyengine-api`) +4. `Skill: policyengine-api-access` (if `data_pattern: policyengine-api`) ## Input @@ -104,6 +106,8 @@ function buildHouseholdDict(values: FormValues): Record { } ``` +For `data_pattern: policyengine-api`, declare it so that `buildHouseholdDict` returns `Household` (from `lib/api/types`), with each output the plan needs named on its entity as `null` for the year, and pass it to the backend-builder's `useCalculation(countryId)` mutation: `@policyengine/api-client` takes the country and the household, not `buildHouseholdRequest`'s shape. The result is the calculated household in the same shape, so the transformers read it with backend-builder's `readVariable(result, 'people', 'head', 'income_tax', year)` and `readVariable(result, 'households', 'household', 'household_net_income', year)` rather than `HouseholdSimulationResult`'s `household[0]` and `person[0]`. With `axes`, each value is a list over the points. + ### Step 3: Build Response Transformers Create functions that extract chart-ready data from API responses: @@ -137,7 +141,34 @@ export function buildChartData( ### Step 4: Wire React Query Hooks -Ensure the hooks in `useCalculation.ts` are correctly used by components: +For `data_pattern: policyengine-api`, calculate from debounced form values, so typing or dragging a slider sends one calculation when the user pauses, not one per keystroke: the dashboard API allows 30 household calculations a minute from one address. A query keyed on the debounced values also skips values it has already calculated: + +```typescript +import { useEffect, useState } from 'react'; +import { useQuery } from '@tanstack/react-query'; + +/** The value, once it has stopped changing for delayMilliseconds. */ +export function useDebouncedValue(value: Value, delayMilliseconds = 500): Value { + const [debounced, setDebounced] = useState(value); + const serialized = JSON.stringify(value); // compares by content, not object identity + useEffect(() => { + const timer = setTimeout(() => setDebounced(JSON.parse(serialized) as Value), delayMilliseconds); + return () => clearTimeout(timer); + }, [serialized, delayMilliseconds]); + return debounced; +} + +export function useHouseholdCalculation(values: FormValues, countryId: 'us' | 'uk') { + const debouncedValues = useDebouncedValue(values); + return useQuery({ + queryKey: ['household', countryId, debouncedValues], + queryFn: ({ signal }) => calculate(countryId, buildHouseholdDict(debouncedValues), signal), + retry: 1, + }); +} +``` + +For a custom backend, ensure the hooks in `useCalculation.ts` are correctly used by components: 1. Mutation hooks for on-demand calculations (user clicks "Calculate") 2. Query hooks for auto-fetching data (loads on mount or input change) @@ -190,7 +221,31 @@ For every component that displays data: ### Step 6: Variation Calculations (if applicable) -If the plan includes charts that vary a parameter (e.g., "tax by income"), implement the variation logic: +If the plan includes charts that vary a parameter (e.g., "tax by income"), implement the variation logic. + +For `data_pattern: policyengine-api`, send one calculation with `axes`, never one per point: the dashboard API allows 30 household calculations a minute from one address, and a refused one shows as `status` `null` on localhost and previews. Each output then comes back as a list over the points: + +```typescript +export function useIncomeVariation(baseValues: FormValues, countryId: 'us' | 'uk') { + const debouncedValues = useDebouncedValue(baseValues); // Step 4 + return useQuery({ + queryKey: ['income-variation', countryId, debouncedValues], + queryFn: ({ signal }) => { + const year = String(debouncedValues.year); + const household = buildHouseholdDict(debouncedValues); + household.people.head.employment_income = { [year]: null }; + household.axes = [[{ name: 'employment_income', period: year, min: 0, max: 500000, count: 21 }]]; + return calculate(countryId, household, signal); + }, + // Only run when required inputs are set; UK households have no state. + enabled: countryId === 'uk' || !!debouncedValues.state, + retry: 1, + staleTime: 30 * 60 * 1000, + }); +} +``` + +For a custom backend, which has no such limit: ```typescript export function useIncomeVariation(baseValues: FormValues, countryId: string) { diff --git a/targets/claude/agents/dashboard/dashboard-planner.md b/targets/claude/agents/dashboard/dashboard-planner.md index 32f9c24..e52b916 100644 --- a/targets/claude/agents/dashboard/dashboard-planner.md +++ b/targets/claude/agents/dashboard/dashboard-planner.md @@ -26,6 +26,7 @@ Produces a structured YAML implementation plan from a natural-language dashboard - **policyengine-us** - US tax/benefit variables and programs - **policyengine-uk** - UK tax/benefit variables and programs - **policyengine-api** - REST API endpoint catalog (v1 production, v2 alpha status) +- **policyengine-api-access** - What the dashboard API offers pages, and when a backend needs its own server credential - **policyengine-cloud-costs** - Worker sizing, maximums and the cost plan (custom-modal only) ## First: Load Required Skills @@ -37,7 +38,8 @@ Produces a structured YAML implementation plan from a natural-language dashboard 3. `Skill: policyengine-us` (if US dashboard) 4. `Skill: policyengine-uk` (if UK dashboard) 5. `Skill: policyengine-api` -6. `Skill: policyengine-cloud-costs` (if custom-modal pattern) +6. `Skill: policyengine-api-access` +7. `Skill: policyengine-cloud-costs` (if custom-modal pattern) ## Input @@ -235,8 +237,8 @@ Choose from the data patterns defined in the `policyengine-tools` skill (loaded **Decision hierarchy (most preferred first):** 1. `precomputed` / `precomputed-csv` — if parameter space is finite -2. `policyengine-api` — if household-level calculations suffice (always prefer this for standard household tools) -3. `custom-modal` — ONLY if microsimulation or custom reforms are needed +2. `policyengine-api` — if the dashboard API suffices: household calculations, a parameter reform compared with current law across the economy, or country metadata (always prefer this for standard household tools). The page calls it through `@policyengine/api-client`, never api.policyengine.org (policyengine-api-access) +3. `custom-modal` — ONLY if other microsimulation output or structural reforms are needed Write the chosen pattern into `data_pattern` in plan.yaml using these identifiers: @@ -244,7 +246,7 @@ Write the chosen pattern into `data_pattern` in plan.yaml using these identifier |---------------|----------------------| | Pattern A: Precomputed JSON | `precomputed` | | Pattern A: Precomputed CSV | `precomputed-csv` | -| Pattern B: PolicyEngine API | `policyengine-api` | +| Pattern B: the dashboard API (`@policyengine/api-client`) | `policyengine-api` | | Pattern C: Custom Modal backend (gateway + polling) | `custom-modal` | If the chosen pattern is `custom-modal`, the plan **must document why** the simpler patterns are insufficient. The plan must also specify which endpoints need long-running computation (microsimulation) vs. fast computation (household), as this determines worker timeout and memory allocation. diff --git a/targets/claude/agents/dashboard/dashboard-scaffold.md b/targets/claude/agents/dashboard/dashboard-scaffold.md index 3cf11b3..ccff223 100644 --- a/targets/claude/agents/dashboard/dashboard-scaffold.md +++ b/targets/claude/agents/dashboard/dashboard-scaffold.md @@ -31,7 +31,8 @@ Generates complete project structure from an approved `plan.yaml` into the curre 1. `Skill: policyengine-tools` 2. `Skill: policyengine-design` 3. `Skill: policyengine-standards` -4. `Skill: policyengine-cloud-costs` (if custom backend) +4. `Skill: policyengine-api-access` (if `data_pattern: policyengine-api`, or a custom backend that calls the Public API) +5. `Skill: policyengine-cloud-costs` (if custom backend) **CRITICAL: The `policyengine-tools` skill's "Required stack" defines the project structure, framework, and styling approach. Follow its specifications for project scaffolding. Where this document conflicts with the spec, THE SPEC WINS.** @@ -65,8 +66,8 @@ Extract key values: **Verify data pattern choice:** If the plan specifies `custom-modal`, confirm it includes a `reason` explaining why simpler patterns are insufficient. The preferred order is: 1. `precomputed` / `precomputed-csv` — if parameter space is finite -2. `policyengine-api` — if household-level calculations suffice (prefer this for standard household tools) -3. `custom-modal` — only if microsimulation or custom reforms are needed +2. `policyengine-api` — if the dashboard API suffices (prefer this for standard household tools): the page calls www.policyengine.org's dashboard API through `@policyengine/api-client`, never api.policyengine.org (policyengine-api-access skill) +3. `custom-modal` — only if other microsimulation output or structural reforms are needed If the plan uses `custom-modal` without a clear justification, flag this to the user before proceeding. @@ -328,11 +329,11 @@ export function Providers({ children }: { children: React.ReactNode }) { #### API Client Stubs -Generate `lib/api/types.ts` with TypeScript interfaces matching the plan's endpoint inputs/outputs. +Generate `lib/api/types.ts` with TypeScript types matching the plan's endpoint inputs/outputs. Declare anything passed to `@policyengine/api-client` as a `type` alias, not an `interface`: the client takes `Record`, which an interface is not assignable to. Generate `lib/api/fixtures.ts` with mock data from `plan.yaml`'s `stub_fixtures`. -**For `policyengine-api` or API v2 alpha patterns**, generate `lib/api/client.ts` with synchronous fetch stubs that return fixture data. +**For `policyengine-api` or API v2 alpha patterns**, generate `lib/api/client.ts` with synchronous fetch stubs that return fixture data. For `policyengine-api`, add `@policyengine/api-client` to the dependencies; the backend-builder replaces the stubs with calls through it (policyengine-api-access skill). Never generate a stub or constant that names api.policyengine.org. **For `custom-modal` pattern**, generate `lib/api/client.ts` with the gateway polling pattern: diff --git a/targets/claude/commands/new-tool.md b/targets/claude/commands/new-tool.md index f9151d9..9f5f05d 100644 --- a/targets/claude/commands/new-tool.md +++ b/targets/claude/commands/new-tool.md @@ -18,8 +18,8 @@ Ask the user for: 2. **Countries** (us, uk, or both) 3. **Data pattern** — how the tool gets model results: - **A) Precomputed** — Static JSON or CSV shipped with the app (best for finite parameter spaces) - - **B) PolicyEngine API** — Direct calls to `api.policyengine.org/us/calculate` (best for household calculators) - - **C) Custom Modal API** — Python serverless function with the top-level `policyengine[us]` / `policyengine[uk]` package (best when main API doesn't support needed variables/reforms) + - **B) Dashboard API** — The page calls www.policyengine.org's dashboard API through `@policyengine/api-client` (best for household calculators). Never call `api.policyengine.org` from the page: the Public API is becoming callable only by PolicyEngine's servers (policyengine-api-access skill) + - **C) Custom Modal API** — Python serverless function with the top-level `policyengine[us]` / `policyengine[uk]` package (best when the dashboard API doesn't cover it: structural reforms, microsimulation output beyond its economy comparison, or variables it doesn't return) 4. **Brief description** of what the tool calculates ## Step 2: Create the project @@ -156,28 +156,28 @@ export default function Home() { ## Step 4: Data pattern boilerplate -> **Prefer Pattern B** (PolicyEngine API) unless the tool needs microsimulation, -> custom reforms, or variables not in the API. Pattern C is significantly more complex -> and should be the last resort. +> **Prefer Pattern B** (the dashboard API) unless the tool needs structural reforms, +> microsimulation output beyond the dashboard API's economy comparison, or variables it +> doesn't return. Pattern C is significantly more complex and should be the last resort. Based on the user's choice, add the appropriate data fetching code. -**For Pattern B (PolicyEngine API):** Create `lib/api.js`: +**For Pattern B (the dashboard API):** run `bun add @policyengine/api-client`, then create `lib/api.js`: ```js -const API_BASE = "https://api.policyengine.org"; +import { createPolicyEngineApiClient } from "@policyengine/api-client"; -export async function calculate(countryId, household) { - const res = await fetch(`${API_BASE}/${countryId}/calculate`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ household }), - }); - if (!res.ok) throw new Error(`API error: ${res.status}`); - return res.json(); +// www.policyengine.org's dashboard API; its server adds PolicyEngine's credential. +const policyengine = createPolicyEngineApiClient(); + +export function calculate(countryId, household, signal) { + // Resolves with the calculated household; throws PolicyEngineApiError on a refusal. + return policyengine.calculateHousehold({ country: countryId, household }, { signal }); } ``` +The dashboard API answers pages on the tool's www.policyengine.org path, on `localhost`, on the team's Vercel previews and on the tool's own `*.vercel.app` host once its rewrite is in `appZoneRoutes.ts`. The policyengine-api-access skill has the other operations (policies, economy comparisons with polling, metadata) and the errors. + **For Pattern C (Custom Modal API — gateway + polling):** Pattern C uses a **three-file backend structure** mirroring policyengine-api-v2's simulation service: a standalone image setup, a worker app, and pure simulation logic. A lightweight gateway manages job submission/polling. This avoids Modal's ~150s gateway timeout and a common crash-loop where module-level imports fail. @@ -427,5 +427,6 @@ if jq -e '.scripts.test' package.json >/dev/null; then bun run test; fi See these skills for detailed guidance: - `policyengine-tools` — Embedding, hash sync, country detection, Vercel deployment patterns +- `policyengine-api-access` — The dashboard API for Pattern B, and server credentials for a backend that calls the Public API - `policyengine-design` — Token reference - `policyengine-cloud-costs` — Backend defaults, declared exceptions, the README's running costs, and the cost checks diff --git a/targets/claude/marketplace.template.json b/targets/claude/marketplace.template.json index e3dfc08..7d769f4 100644 --- a/targets/claude/marketplace.template.json +++ b/targets/claude/marketplace.template.json @@ -1,7 +1,7 @@ { "$schema": "https://claude.ai/schemas/plugin-marketplace.json", "name": "policyengine-claude", - "version": "4.1.0", + "version": "4.2.0", "description": "Complete PolicyEngine knowledge base - for users, analysts, and contributors across the entire PolicyEngine ecosystem", "owner": { "name": "PolicyEngine", diff --git a/targets/codex/agents/policyengine-app-v2/AGENTS.md b/targets/codex/agents/policyengine-app-v2/AGENTS.md index 59e49fc..3d8669e 100644 --- a/targets/codex/agents/policyengine-app-v2/AGENTS.md +++ b/targets/codex/agents/policyengine-app-v2/AGENTS.md @@ -5,6 +5,7 @@ When working in `policyengine-app-v2`, prefer these PolicyEngine skills: - `$policyengine-app` for app architecture, routing, multizone config, and frontend conventions. - `$policyengine-design` for design tokens, `@policyengine/ui-kit` consumption, Tailwind v4 theme setup, and chart styling. - `$policyengine-api` for API integration (v1 endpoints, api-v2 alpha status). +- `$policyengine-api-access` for the website's dashboard API, `@policyengine/api-client`, and the server credential the routes send to the Public API. - `$policyengine-standards` for formatters, CI, and PR conventions. For generic prompts like `/review`, "fix styling", "write tests", or "add a component", load the relevant skills above before editing. diff --git a/tests/test_api_access_skill.py b/tests/test_api_access_skill.py new file mode 100644 index 0000000..d86c08b --- /dev/null +++ b/tests/test_api_access_skill.py @@ -0,0 +1,516 @@ +"""The policyengine-api-access skill: where it is listed, what it teaches, and its token template. + +The skill tells dashboard developers and agents how to reach the Public API +(api.policyengine.org) once it serves only PolicyEngine's servers: pages use +www.policyengine.org's dashboard API through ``@policyengine/api-client``, and +servers send their own Auth0 client-credentials token. The operation paths, +header names, tenants and refusal bodies below are copied from merged code in +PolicyEngine/policyengine-app-v2 (``website/src/app/api/``, +``website/src/lib/dashboardApi/``, ``packages/public-api-forwarding/`` and +``packages/api-client/``) and PolicyEngine/policyengine-api +(``policyengine_api/request_caller.py``, +``policyengine_api/libs/server_credentials.py``), so an edit to the skill +cannot drift from them unnoticed. + +``templates/public_api_token.py`` is the token cache servers copy. Its tests +use a fake opener and a dict as the shared store, so they never call Auth0 or +the Public API. +""" + +from __future__ import annotations + +import http.server +import importlib.util +import json +import re +import subprocess +import sys +import threading +import urllib.error +from pathlib import Path +from types import ModuleType + +import pytest +import yaml + +REPO_ROOT = Path(__file__).resolve().parent.parent +SKILL_NAME = "policyengine-api-access" +SKILL_DIR = REPO_ROOT / "skills" / SKILL_NAME +SKILL_FILE = SKILL_DIR / "SKILL.md" +TOKEN_TEMPLATE = SKILL_DIR / "templates" / "public_api_token.py" + +# Bundles whose users build dashboards or tools, or call the Public API. +# analysis-tools is the bundle the create-dashboard workflow installs. +BUNDLES_WITH_SKILL = ( + "analysis-tools", + "api-development", + "app-development", + "complete", + "dashboard-builder", +) + +DASHBOARD_API_OPERATIONS = ( + "POST /api/household/calculate", + "POST /api/policies", + "GET /api/economy/compare", + "GET /api/metadata/{country}", +) +API_CLIENT_METHODS = ( + "createPolicyEngineApiClient", + "calculateHousehold", + "createPolicy", + "getEconomyComparison", + "compareEconomy", + "getMetadata", + "PolicyEngineApiError", + "EconomyComparisonTimeoutError", +) +SERVER_CONTRACT_FACTS = ( + "Authorization: Bearer", + "PolicyEngine-User-Authorization", + "PolicyEngine-Unauthenticated-User", + "policyengine-api.uk.auth0.com", + "policyengine-api-staging.uk.auth0.com", + "https://api.policyengine.org/", + "https://staging-api.policyengine.org/", + "PUBLIC_API_SERVER_CLIENT_IDS", + "POLICYENGINE_API_URL", + "POLICYENGINE_API_AUTH0_DOMAIN", + "POLICYENGINE_API_AUTH0_AUDIENCE", + "POLICYENGINE_API_AUTH0_CLIENT_ID", + "POLICYENGINE_API_AUTH0_CLIENT_SECRET", + "vercel env pull", + '{"status":"error","message":"Authentication is required"}', + '{"status":"error","message":"Authentication is unavailable"}', + "Pages on this origin may not call the dashboard API.", + "appZoneRoutes.ts", + "*-policy-engine.vercel.app", +) +HANDLED_STATUSES = ("401", "403", "429", "503") + + +def skill_text() -> str: + assert SKILL_FILE.is_file(), f"{SKILL_FILE.relative_to(REPO_ROOT)} is missing" + return SKILL_FILE.read_text() + + +def skill_frontmatter() -> dict: + text = skill_text() + match = re.match(r"\A---\n(.*?)\n---\n", text, re.DOTALL) + assert match, "SKILL.md has no YAML frontmatter" + return yaml.safe_load(match.group(1)) + + +def test_frontmatter_names_the_skill_and_its_category() -> None: + frontmatter = skill_frontmatter() + assert frontmatter["name"] == SKILL_NAME + assert frontmatter["metadata"]["category"] == "apps" + description = frontmatter["description"] + assert len(description) <= 1024 + for trigger in ("api.policyengine.org", "dashboard", "@policyengine/api-client"): + assert trigger in description, f"description lacks the trigger {trigger!r}" + + +@pytest.mark.parametrize("bundle_name", BUNDLES_WITH_SKILL) +def test_bundle_ships_the_skill(bundle_name: str) -> None: + bundle = json.loads((REPO_ROOT / "bundles" / f"{bundle_name}.json").read_text()) + assert f"./skills/{SKILL_NAME}" in bundle["skills"] + + +def test_catalog_lists_the_skill() -> None: + catalog = (REPO_ROOT / "skills" / "README.md").read_text() + assert f"| **{SKILL_NAME}** |" in catalog + + +def test_wrapper_build_ships_the_skill_and_template(tmp_path: Path) -> None: + output_dir = tmp_path / "policyengine-claude" + subprocess.run( + [ + sys.executable, + str(REPO_ROOT / "scripts" / "build_claude_wrapper.py"), + "--source-root", + str(REPO_ROOT), + "--output-root", + str(output_dir), + ], + check=True, + ) + assert (output_dir / "skills" / SKILL_NAME / "SKILL.md").is_file() + assert (output_dir / "skills" / SKILL_NAME / "templates" / "public_api_token.py").is_file() + manifest = json.loads((output_dir / ".claude-plugin" / "marketplace.json").read_text()) + plugins_with_skill = { + plugin["name"] + for plugin in manifest["plugins"] + if f"./skills/{SKILL_NAME}" in plugin.get("skills", []) + } + assert plugins_with_skill == set(BUNDLES_WITH_SKILL) + + +@pytest.mark.parametrize("operation", DASHBOARD_API_OPERATIONS) +def test_skill_names_each_dashboard_api_operation(operation: str) -> None: + assert operation in skill_text() + + +@pytest.mark.parametrize("name", API_CLIENT_METHODS) +def test_skill_names_the_browser_client_exports(name: str) -> None: + assert name in skill_text() + + +@pytest.mark.parametrize("fact", SERVER_CONTRACT_FACTS) +def test_skill_states_the_server_contract(fact: str) -> None: + assert fact in skill_text() + + +@pytest.mark.parametrize("status_code", HANDLED_STATUSES) +def test_skill_says_how_to_handle_each_refusal(status_code: str) -> None: + error_section = skill_text().split("## Errors", 1) + assert len(error_section) == 2, "SKILL.md has no '## Errors' section" + assert re.search(rf"^\| {status_code} ", error_section[1], re.MULTILINE), ( + f"the errors table has no row for {status_code}" + ) + + +def test_skill_points_servers_at_the_token_template() -> None: + assert "templates/public_api_token.py" in skill_text() + + +# --- templates/public_api_token.py ----------------------------------------- + + +def load_token_module() -> ModuleType: + assert TOKEN_TEMPLATE.is_file(), f"{TOKEN_TEMPLATE.relative_to(REPO_ROOT)} is missing" + spec = importlib.util.spec_from_file_location("public_api_token", TOKEN_TEMPLATE) + module = importlib.util.module_from_spec(spec) + # dataclasses resolves the module's annotations through sys.modules. + sys.modules["public_api_token"] = module + spec.loader.exec_module(module) + return module + + +class FakeResponse: + def __init__(self, status_code: int, body: dict) -> None: + self.status = status_code + self._body = json.dumps(body).encode() + + def read(self) -> bytes: + return self._body + + def __enter__(self) -> FakeResponse: + return self + + def __exit__(self, *exc_info: object) -> None: + return None + + +class RecordingOpener: + """Answers token requests with fresh tokens and Public API requests with 200.""" + + def __init__(self, token_lifetime_seconds: int = 86_400) -> None: + self.token_lifetime_seconds = token_lifetime_seconds + self.token_requests: list[dict] = [] + self.api_requests: list[tuple[str, str, str | None]] = [] + self.refuse_next_api_requests = 0 + + def open(self, request, timeout=None): # noqa: ANN001 - urllib's opener signature + if request.full_url.endswith("/oauth/token"): + self.token_requests.append(json.loads(request.data)) + return FakeResponse( + 200, + { + "access_token": f"token-{len(self.token_requests)}", + "expires_in": self.token_lifetime_seconds, + "token_type": "Bearer", + }, + ) + self.api_requests.append( + (request.get_method(), request.full_url, request.get_header("Authorization")) + ) + if self.refuse_next_api_requests > 0: + self.refuse_next_api_requests -= 1 + raise urllib.error.HTTPError( + request.full_url, + 401, + "Unauthorized", + {"WWW-Authenticate": "Bearer"}, + None, + ) + return FakeResponse(200, {"status": "ok", "message": None, "result": {}}) + + +class Clock: + def __init__(self, start: float = 1_000_000.0) -> None: + self.now = start + + def __call__(self) -> float: + return self.now + + +def make_settings(token_module: ModuleType, api_url: str = "https://api.example.test"): + return token_module.PublicApiSettings.from_environment( + { + "POLICYENGINE_API_URL": api_url, + "POLICYENGINE_API_AUTH0_DOMAIN": "tenant.example.test", + "POLICYENGINE_API_AUTH0_AUDIENCE": "https://audience.example.test/", + "POLICYENGINE_API_AUTH0_CLIENT_ID": "placeholder-client-id", + "POLICYENGINE_API_AUTH0_CLIENT_SECRET": "placeholder-client-secret", + } + ) + + +def test_template_requests_one_token_for_many_requests() -> None: + token_module = load_token_module() + opener = RecordingOpener() + tokens = token_module.PublicApiTokenProvider( + make_settings(token_module), shared_store={}, clock=Clock(), opener=opener + ) + assert [tokens.access_token() for _ in range(5)] == ["token-1"] * 5 + assert len(opener.token_requests) == 1 + assert opener.token_requests[0] == { + "grant_type": "client_credentials", + "client_id": "placeholder-client-id", + "client_secret": "placeholder-client-secret", + "audience": "https://audience.example.test/", + } + + +def test_template_shares_one_token_between_instances() -> None: + token_module = load_token_module() + shared_store: dict = {} + opener = RecordingOpener() + first_instance = token_module.PublicApiTokenProvider( + make_settings(token_module), shared_store=shared_store, clock=Clock(), opener=opener + ) + second_instance = token_module.PublicApiTokenProvider( + make_settings(token_module), shared_store=shared_store, clock=Clock(), opener=opener + ) + assert first_instance.access_token() == second_instance.access_token() == "token-1" + assert len(opener.token_requests) == 1 + assert "placeholder-client-secret" not in json.dumps(shared_store) + + +def test_template_replaces_the_token_five_minutes_before_it_expires() -> None: + token_module = load_token_module() + clock = Clock() + opener = RecordingOpener(token_lifetime_seconds=86_400) + tokens = token_module.PublicApiTokenProvider( + make_settings(token_module), shared_store={}, clock=clock, opener=opener + ) + assert tokens.access_token() == "token-1" + clock.now += 86_400 - 301 + assert tokens.access_token() == "token-1" + clock.now += 2 + assert tokens.access_token() == "token-2" + + +def test_template_raises_a_refused_request_without_requesting_another_token() -> None: + # A 401 means the client is not on the allow list, or its tenant or audience is + # wrong; a new token would be refused too, and each one counts against the + # tenant's monthly machine-to-machine allowance (app-v2's forwarder never retries). + token_module = load_token_module() + opener = RecordingOpener() + opener.refuse_next_api_requests = 3 + shared_store: dict = {} + tokens = token_module.PublicApiTokenProvider( + make_settings(token_module), shared_store=shared_store, clock=Clock(), opener=opener + ) + for _ in range(3): + with pytest.raises(urllib.error.HTTPError) as refusal: + token_module.call_public_api(tokens, "POST", "/us/calculate", {"household": {}}) + assert refusal.value.code == 401 + assert len(opener.token_requests) == 1 + assert [header for _, _, header in opener.api_requests] == ["Bearer token-1"] * 3 + assert tokens.access_token() == "token-1" + + +def test_template_turns_a_redirected_token_request_into_an_error() -> None: + token_module = load_token_module() + + class RedirectingTokenEndpoint(RecordingOpener): + def open(self, request, timeout=None): # noqa: ANN001 - urllib's opener signature + raise urllib.error.HTTPError( + request.full_url, 307, "Temporary Redirect", {"Location": "https://elsewhere.test/"}, None + ) + + shared_store: dict = {} + tokens = token_module.PublicApiTokenProvider( + make_settings(token_module), shared_store=shared_store, clock=Clock(), opener=RedirectingTokenEndpoint() + ) + with pytest.raises(token_module.PublicApiTokenError, match="307"): + tokens.access_token() + assert shared_store == {} + # By default the provider uses the opener that refuses every redirect. + assert token_module.PublicApiTokenProvider(make_settings(token_module)).opener is token_module.NO_REDIRECT_OPENER + + +def test_template_refuses_partial_settings() -> None: + token_module = load_token_module() + with pytest.raises(token_module.PublicApiSettingsError): + token_module.PublicApiSettings.from_environment( + {"POLICYENGINE_API_URL": "https://api.example.test"} + ) + + +class RedirectingHandler(http.server.BaseHTTPRequestHandler): + requested_paths: list[str] = [] + + def do_GET(self) -> None: # noqa: N802 - http.server's method name + RedirectingHandler.requested_paths.append(self.path) + self.send_response(307) + self.send_header("Location", "/elsewhere") + self.end_headers() + + def log_message(self, *args: object) -> None: + return None + + +def test_template_never_follows_a_redirect() -> None: + token_module = load_token_module() + RedirectingHandler.requested_paths = [] + server = http.server.HTTPServer(("127.0.0.1", 0), RedirectingHandler) + thread = threading.Thread(target=server.serve_forever, daemon=True) + thread.start() + try: + api_url = f"http://127.0.0.1:{server.server_address[1]}" + tokens = token_module.PublicApiTokenProvider( + make_settings(token_module, api_url=api_url), + shared_store={}, + clock=Clock(), + opener=RecordingOpener(), + ) + tokens.access_token() + with pytest.raises(urllib.error.HTTPError) as refusal: + token_module.call_public_api( + tokens, "GET", "/us/metadata", opener=token_module.NO_REDIRECT_OPENER + ) + assert refusal.value.code == 307 + assert RedirectingHandler.requested_paths == ["/us/metadata"] + finally: + server.shutdown() + server.server_close() + + +def test_skill_describes_the_dashboard_api_firewall_refusal() -> None: + # Vercel's firewall rules on www.policyengine.org (tracker 2.3 · 6), observed in + # production on 2026-11-10: over a limit it answers 429 itself, as text/plain + # "Too Many Requests", with no Retry-After and no CORS headers. + text = skill_text() + for fact in ( + "fixed 60-second windows", + "Too Many Requests", + "no `Retry-After`", + "no CORS headers", + "`RATE_LIMIT_WAIT_MILLISECONDS`", + ): + assert fact in text, fact + # The merged client (PolicyEngine/policyengine-app-v2#1543, in the first publish) + # exports RATE_LIMIT_WAIT_MILLISECONDS and does not count 429s as gateway failures. + assert "DEFAULT_RATE_LIMIT_WAIT_MILLISECONDS" not in text + assert "count the 429" not in text + error_rows = [row for row in text.split("## Errors", 1)[1].splitlines() if row.startswith("| 429 ")] + assert len(error_rows) == 2, "the errors table needs a 429 row for the firewall and one for the load balancer" + + +def test_dashboard_manifest_lists_the_skill() -> None: + # tests/pipeline/test_when_to_use_guide.py reads skill IDs from this committed + # file, so a guide that names the skill fails until the manifest is rebuilt. + manifest = json.loads((REPO_ROOT / "dashboard" / "src" / "data" / "manifest.json").read_text()) + matches = [entry for entry in manifest["artifacts"] if entry["kind"] == "skill" and entry["id"] == SKILL_NAME] + assert len(matches) == 1, f"dashboard/src/data/manifest.json lists {SKILL_NAME} {len(matches)} times" + artifact = matches[0] + assert artifact["path"] == f"skills/{SKILL_NAME}/SKILL.md" + assert sorted(artifact["bundles"]) == sorted(BUNDLES_WITH_SKILL) + + +def test_errors_table_tells_a_refused_server_token_from_a_refused_user_token() -> None: + # API v1's user routes answer an invalid or expired forwarded + # PolicyEngine-User-Authorization token with their own 401 and + # WWW-Authenticate: Bearer error="invalid_token"; the server's own token is + # refused with a plain "WWW-Authenticate: Bearer". + errors = skill_text().split("## Errors", 1)[1] + rows_401 = [row for row in errors.splitlines() if row.startswith("| 401 ")] + assert len(rows_401) == 2, "the errors table needs a 401 row for each credential" + assert any('error="invalid_token"' in row and "PolicyEngine-User-Authorization" in row for row in rows_401) + assert not any("retry once with a new" in row for row in rows_401) + + +# --- Review round 2 ------------------------------------------------------------ + + +def test_template_modal_wiring_adds_the_module_to_the_image() -> None: + # Modal 1.0 stopped copying imported local modules into containers, so the + # image must add the template's module, or the function fails at start. + token_module = load_token_module() + docstring = token_module.__doc__ + assert 'add_local_python_source("public_api_token")' in docstring + assert "image=" in docstring + + +def test_template_settings_never_print_the_client_secret() -> None: + token_module = load_token_module() + settings = make_settings(token_module) + assert "placeholder-client-secret" not in repr(settings) + assert settings.client_secret == "placeholder-client-secret" + + +def test_template_keeps_working_when_the_shared_store_fails() -> None: + token_module = load_token_module() + + class FailingStore: + def get(self, key, default=None): # noqa: ANN001, ANN201 + raise ConnectionError("store unavailable") + + def __setitem__(self, key, value): # noqa: ANN001 + raise ConnectionError("store unavailable") + + opener = RecordingOpener() + tokens = token_module.PublicApiTokenProvider( + make_settings(token_module), shared_store=FailingStore(), clock=Clock(), opener=opener + ) + assert [tokens.access_token() for _ in range(3)] == ["token-1"] * 3 + assert len(opener.token_requests) == 1 + + +def test_template_replaces_a_short_token_halfway_through_its_life() -> None: + token_module = load_token_module() + clock = Clock() + opener = RecordingOpener(token_lifetime_seconds=400) + tokens = token_module.PublicApiTokenProvider( + make_settings(token_module), shared_store={}, clock=clock, opener=opener + ) + assert tokens.access_token() == "token-1" + clock.now += 199 + assert tokens.access_token() == "token-1" + clock.now += 2 + assert tokens.access_token() == "token-2" + + +def test_skill_says_the_calculation_returns_only_the_variables_asked_for() -> None: + # The dashboard API's calculation is API v1's /{country}/calculate, which adds no + # omitted variables (add_missing=False): outputs are asked for with null. + text = skill_text() + assert "/{country}/calculate" in text + assert "with `null` for the year" in text + + +# --- Review round 3 ------------------------------------------------------------ + + +def test_template_opener_protocol_takes_the_request_positionally_and_timeout_by_keyword() -> None: + # urllib's OpenerDirector.open(fullurl, data=None, timeout=...) has data second, + # so a Protocol with timeout second does not fit it under mypy. + import inspect + + token_module = load_token_module() + parameters = inspect.signature(token_module.Opener.open).parameters + assert parameters["request"].kind is inspect.Parameter.POSITIONAL_ONLY + assert parameters["timeout"].kind is inspect.Parameter.KEYWORD_ONLY + + +def test_template_says_how_to_drop_a_revoked_token() -> None: + assert "revoked" in load_token_module().__doc__ + + +def test_skill_household_example_asks_for_an_output() -> None: + # The calculation adds no omitted variables, so an example that names no + # output would get its inputs back unchanged. + assert "household_net_income: { 2026: null }" in skill_text() diff --git a/tests/test_public_api_browser_guidance.py b/tests/test_public_api_browser_guidance.py new file mode 100644 index 0000000..e660367 --- /dev/null +++ b/tests/test_public_api_browser_guidance.py @@ -0,0 +1,307 @@ +"""No skill, agent, command or doc tells browser code to call the Public API. + +The Public API (api.policyengine.org) is becoming callable only by +PolicyEngine's own servers. Pages reach it through www.policyengine.org's +dashboard API with ``@policyengine/api-client``, and servers send their own +Auth0 client-credentials token; the policyengine-api-access skill says how. +These tests keep the rest of the catalog from teaching the old way: + +- a browser-language example (TypeScript or JavaScript) never names a Public + API host; +- no ``NEXT_PUBLIC_*`` or ``VITE_*`` variable is set to a Public API host, or + named like a credential; +- the skills, agents and commands that guide dashboard and app work point to + policyengine-api-access; +- the policyengine-api skill lists the Public API's routes as they are now. + +Server-side, notebook and analysis examples that call the Public API (shell +``curl``, Python) are not checked here: they run outside a browser. +""" + +from __future__ import annotations + +import re +from pathlib import Path + +import pytest + +REPO_ROOT = Path(__file__).resolve().parent.parent +SCAN_DIRS = ("skills", "targets", "docs") +TEXT_SUFFIXES = { + ".md", ".json", ".yaml", ".yml", ".py", ".sh", ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".html", ".vue", ".svelte", +} +BROWSER_FILE_SUFFIXES = {".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".html", ".vue", ".svelte"} + +BROWSER_LANGUAGES = { + "ts", "tsx", "js", "jsx", "javascript", "typescript", "mjs", "cjs", "html", "htm", "vue", "svelte", "astro", +} +# A fenced example of server-side TypeScript or JavaScript may name the Public API +# when the line before its fence is this marker. +SERVER_CODE_MARKER = "" +# The Public API only: not household.api.policyengine.org (the household API) or +# v2.api.policyengine.org (the v2 alpha), hence the left boundary. +PUBLIC_API_HOST = re.compile( + r"(?`{3,}|~{3,})[ \t]*(?P[A-Za-z0-9_+-]*)[^\n]*\n(?P.*?)^[ \t]*(?P=fence)[ \t]*$", + re.DOTALL | re.MULTILINE, +) +# An *_AUDIENCE name holds API v1's Auth0 identifier for a browser's sign-in, not an +# address to call, so it is left out. +PUBLIC_PREFIX_SET_TO_PUBLIC_API = re.compile( + r"\b(?:NEXT_PUBLIC|VITE|REACT_APP)_(?![A-Z0-9_]*AUDIENCE)[A-Z0-9_]+[\"']?\s*(?:[=:]|\|\||\?\?)\s*[\"'`]?https?://" + r"(?:api\.policyengine\.org|(?:[a-z0-9-]+---)?policyengine-api[a-z0-9-]*\.[a-z0-9.-]*run\.app)" +) +# A public-prefix name that holds a secret, that is an unqualified token or key +# (NEXT_PUBLIC_API_KEY, VITE_ACCESS_TOKEN), or that is named for the Public API's +# address or server client. A browser's own sign-in settings (VITE_AUTH0_DOMAIN, +# VITE_AUTH0_CLIENT_ID, NEXT_PUBLIC_AUTH0_AUDIENCE) and other services' public +# tokens named for their service (NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN) are public by design. +PUBLIC_PREFIX_CREDENTIAL = re.compile( + r"\b(?:NEXT_PUBLIC|VITE|REACT_APP)_(?=(?:API_)?(?:ACCESS_|BEARER_|AUTH_)?(?:TOKEN|KEY)\b" + r"|[A-Z0-9_]*(?:SECRET|PASSWORD|PRIVATE_KEY|CREDENTIAL" + r"|(? list[Path]: + return sorted( + path + for directory in SCAN_DIRS + for path in (REPO_ROOT / directory).rglob("*") + if path.is_file() and path.suffix in TEXT_SUFFIXES and "__pycache__" not in path.parts + ) + + +def browser_blocks_naming_the_public_api(text: str) -> list[str]: + offenders: list[str] = [] + for block in FENCE.finditer(text): + line_before = text[: block.start()].rstrip("\n").rsplit("\n", 1)[-1] + if SERVER_CODE_MARKER in line_before: + continue + body = block.group("body") + if block.group("language").lower() in BROWSER_LANGUAGES: + if PUBLIC_API_HOST.search(body): + offenders.append(body) + else: + # A Markdown or text block can hold a nested example. + offenders.extend(browser_blocks_naming_the_public_api(body)) + return offenders + + +def test_no_browser_language_example_names_the_public_api() -> None: + offenders = [ + path.relative_to(REPO_ROOT).as_posix() + for path in scanned_files() + if path.suffix == ".md" and browser_blocks_naming_the_public_api(path.read_text()) + ] + assert offenders == [], f"browser-language examples that call the Public API: {offenders}" + + +def test_no_code_file_in_the_catalog_names_the_public_api() -> None: + # Templates and examples shipped as files (for example under skills/*/templates/). + offenders = [ + path.relative_to(REPO_ROOT).as_posix() + for path in scanned_files() + if path.suffix in BROWSER_FILE_SUFFIXES and PUBLIC_API_HOST.search(path.read_text()) + ] + assert offenders == [] + + +def test_no_public_prefix_variable_holds_the_public_api_or_a_credential() -> None: + offenders = [] + for path in scanned_files(): + for line_number, line in enumerate(path.read_text().splitlines(), start=1): + if PUBLIC_PREFIX_SET_TO_PUBLIC_API.search(line) or PUBLIC_PREFIX_CREDENTIAL.search(line): + offenders.append(f"{path.relative_to(REPO_ROOT).as_posix()}:{line_number}") + assert offenders == [] + + +def test_guards_catch_the_patterns_they_name() -> None: + browser_examples = [ + "```ts\nconst API_BASE = 'https://api.policyengine.org';\n```\n", + " ```js\n fetch(`https://api.policyengine.org/us/calculate`)\n ```\n", + "```tsx\nfetch('https://policyengine-api-staging-abc123defg-uc.a.run.app/us/metadata')\n```\n", + "```ts\nfetch('https://policyengine-api-123456789012.us-central1.run.app/us/metadata')\n```\n", + "~~~ts\nfetch('https://api.policyengine.org/us/metadata')\n~~~\n", + "````tsx\nfetch('https://api.policyengine.org/us/metadata')\n````\n", + "```html\n\n```\n", + "```vue\n\n```\n", + "```svelte\n\n```\n", + "```cjs\nfetch('https://api.policyengine.org/uk/metadata')\n```\n", + "````markdown\nAn example:\n```ts\nfetch('https://api.policyengine.org/us/metadata')\n```\n````\n", + "``` ts\nfetch('https://api.policyengine.org/us/metadata')\n```\n", + ] + for example in browser_examples: + assert browser_blocks_naming_the_public_api(example), example + not_browser_examples = [ + "```bash\ncurl https://api.policyengine.org/us/metadata\n```\n", + "```ts\nfetch('https://household.api.policyengine.org/us/calculate')\n```\n", + # Server-side TypeScript, marked as such, may name the Public API. + "\n```ts\nawait fetch('https://api.policyengine.org/us/calculate', { headers })\n```\n", + ] + for example in not_browser_examples: + assert not browser_blocks_naming_the_public_api(example), example + for unsafe in ( + "NEXT_PUBLIC_API_URL=https://api.policyengine.org", + "VITE_API_URL: 'https://api.policyengine.org'", + '"NEXT_PUBLIC_API_URL": "https://api.policyengine.org"', + "`process.env.NEXT_PUBLIC_API_URL || 'https://api.policyengine.org'`", + "NEXT_PUBLIC_POLICYENGINE_API_AUTH0_CLIENT_SECRET", + "NEXT_PUBLIC_POLICYENGINE_API_AUTH0_CLIENT_ID", + "VITE_PE_API_KEY", + "VITE_AUTH0_CLIENT_SECRET", + "VITE_API_TOKEN_SECRET", + "`process.env.NEXT_PUBLIC_API_URL ?? 'https://api.policyengine.org'`", + "NEXT_PUBLIC_API_KEY", + "NEXT_PUBLIC_API_TOKEN", + "NEXT_PUBLIC_BEARER_TOKEN", + "VITE_ACCESS_TOKEN", + "VITE_POLICYENGINE_TOKEN", + "VITE_API_ACCESS_TOKEN", + "NEXT_PUBLIC_API_BEARER_TOKEN", + ): + assert PUBLIC_PREFIX_SET_TO_PUBLIC_API.search(unsafe) or PUBLIC_PREFIX_CREDENTIAL.search(unsafe), unsafe + for safe in ( + "NEXT_PUBLIC_API_URL=https://policyengine--my-tool-fastapi-app.modal.run", + "never a `NEXT_PUBLIC_*` or `VITE_*` name", + "NEXT_PUBLIC_API_V2_URL || 'https://v2.api.policyengine.org'", + "VITE_HOUSEHOLD_API_URL=https://household.api.policyengine.org", + # A browser's own sign-in settings, as app-v2 uses them, are public by design. + "VITE_AUTH0_DOMAIN=auth.policyengine.org", + "VITE_AUTH0_CLIENT_ID", + "NEXT_PUBLIC_AUTH0_AUDIENCE", + "NEXT_PUBLIC_MAPBOX_TOKEN", + "NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN", + # The browser sign-in audience is API v1's identifier, not an address to call. + "NEXT_PUBLIC_AUTH0_AUDIENCE=https://api.policyengine.org/", + "VITE_AUTH0_AUDIENCE: 'https://api.policyengine.org/'", + # app-v2's v2 alpha address, and a token named for another service. + "NEXT_PUBLIC_POLICYENGINE_API_V2_URL", + "VITE_POLICYENGINE_MAPBOX_TOKEN", + ): + assert not PUBLIC_PREFIX_SET_TO_PUBLIC_API.search(safe), safe + assert not PUBLIC_PREFIX_CREDENTIAL.search(safe), safe + + +def test_dashboard_scaffold_loads_the_access_skill() -> None: + text = (REPO_ROOT / "targets/claude/agents/dashboard/dashboard-scaffold.md").read_text() + loading = text.split("## First: Load Required Skills", 1) + assert len(loading) == 2 + assert "`Skill: policyengine-api-access`" in loading[1].split("\n## ", 1)[0] + + +def test_custom_backend_guidance_agrees_with_what_the_dashboard_api_offers() -> None: + # The dashboard API compares a parameter reform with current law across the + # economy, so that alone is no reason for a custom Modal backend. + tools_text = TOOLS_SKILL.read_text() + pattern_c = re.search(r"^- \*\*C — .*?(?=^\S)", tools_text, re.DOTALL | re.MULTILINE) + assert pattern_c, "the tools skill has no Pattern C entry" + assert "society-wide microsim," not in pattern_c.group(0) + assert "structural" in pattern_c.group(0) + new_tool = (REPO_ROOT / "targets/claude/commands/new-tool.md").read_text() + assert "unless the tool needs microsimulation,\n> custom reforms" not in new_tool + assert "structural" in new_tool.split("**Prefer Pattern B**", 1)[1].split("\n\n", 1)[0] + + +@pytest.mark.parametrize("relative_path", DASHBOARD_GUIDANCE) +def test_dashboard_guidance_points_to_the_access_skill(relative_path: str) -> None: + assert ACCESS_SKILL in (REPO_ROOT / relative_path).read_text() + + +def test_api_skill_lists_the_public_routes_as_they_are_now() -> None: + text = API_SKILL.read_text() + # The Public API removed these (policyengine_api/migration_flags.py). + for removed_path in ("/specification", "/liveness-check", "/readiness-check"): + assert removed_path not in text, removed_path + for public_path in ("/health", "/v2/openapi.json"): + assert public_path in text, public_path + + +def test_api_skill_does_not_send_frontends_to_the_public_api() -> None: + text = API_SKILL.read_text() + assert "JS frontends" not in text + assert "server credential" in text + + +def test_tools_skill_pattern_b_uses_the_dashboard_api() -> None: + text = TOOLS_SKILL.read_text() + pattern_b = re.search(r"^- \*\*B — .*?(?=^- \*\*C — )", text, re.DOTALL | re.MULTILINE) + assert pattern_b, "the tools skill has no Pattern B entry" + assert "@policyengine/api-client" in pattern_b.group(0) + assert "https://api.policyengine.org" not in pattern_b.group(0) + + +def test_vue_and_svelte_files_are_scanned() -> None: + assert {".vue", ".svelte"} <= TEXT_SUFFIXES + + +INTEGRATOR = REPO_ROOT / "targets/claude/agents/dashboard/dashboard-integrator.md" + + +def test_integrator_varies_a_household_in_one_calculation() -> None: + # The dashboard API allows 30 household calculations a minute from one address, + # so a chart over 20 incomes is one calculation with axes, not 20. + step = INTEGRATOR.read_text().split("### Step 6", 1) + assert len(step) == 2 + variation = step[1].split("\n### ", 1)[0] + assert "axes" in variation + assert "30 household calculations a minute" in variation + + +def test_integrator_types_the_household_for_the_client() -> None: + assert "`buildHouseholdDict` returns `Household`" in INTEGRATOR.read_text() + + +def test_contributing_documents_the_server_code_marker() -> None: + assert SERVER_CODE_MARKER in (REPO_ROOT / "CONTRIBUTING.md").read_text() + + +BACKEND_BUILDER = REPO_ROOT / "targets/claude/agents/dashboard/backend-builder.md" + + +def test_calculated_household_is_read_through_a_typed_helper() -> None: + # With an `unknown` index signature, result.households.household... does not + # compile under strict mode, so transformers read through readVariable(). + assert "export function readVariable(" in BACKEND_BUILDER.read_text() + integrator = INTEGRATOR.read_text() + assert "readVariable(" in integrator + assert "result.households.household.household_net_income[year]" not in integrator + + +def test_integrator_debounces_calculations_and_runs_for_every_country() -> None: + integrator = INTEGRATOR.read_text() + assert "useDebouncedValue" in integrator.split("### Step 4", 1)[1].split("\n### ", 1)[0] + variation = integrator.split("### Step 6", 1)[1].split("\n### ", 1)[0] + assert "useDebouncedValue" in variation + assert "countryId === 'uk'" in variation + + +def test_first_401_row_does_not_claim_a_malformed_anonymous_header_gets_the_same_answer() -> None: + skill = (REPO_ROOT / "skills/policyengine-api-access/SKILL.md").read_text() + rows_401 = [row for row in skill.split("## Errors", 1)[1].splitlines() if row.startswith("| 401 ")] + assert rows_401 and not any("malformed `PolicyEngine-Unauthenticated-User`" in row for row in rows_401)