platformforge 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- platformforge/__init__.py +7 -0
- platformforge/__main__.py +5 -0
- platformforge/_forge_portal.py +913 -0
- platformforge/_graphstudio.py +376 -0
- platformforge/_graphview.py +181 -0
- platformforge/_installkit.py +1706 -0
- platformforge/agents/__init__.py +156 -0
- platformforge/agents/bench.py +61 -0
- platformforge/agents/contextpack.py +137 -0
- platformforge/agents/contracts.py +394 -0
- platformforge/agents/coordinators.py +98 -0
- platformforge/agents/critic.py +113 -0
- platformforge/agents/debate.py +161 -0
- platformforge/agents/executors.py +160 -0
- platformforge/agents/guardian.py +48 -0
- platformforge/agents/mirrors.py +240 -0
- platformforge/agents/orchestrator.py +295 -0
- platformforge/agents/planner.py +116 -0
- platformforge/agents/playbook.py +51 -0
- platformforge/agents/referee.py +132 -0
- platformforge/agents/reviewers.py +113 -0
- platformforge/agents/roster.py +893 -0
- platformforge/agents/runledger.py +85 -0
- platformforge/agents/runstore.py +74 -0
- platformforge/agents/specialists.py +73 -0
- platformforge/agents/taskspec.py +94 -0
- platformforge/agents/uniqueness.py +115 -0
- platformforge/agents/verifier.py +106 -0
- platformforge/aiplat/__init__.py +4 -0
- platformforge/aiplat/models.py +175 -0
- platformforge/analytics/__init__.py +6 -0
- platformforge/analytics/capacity.py +109 -0
- platformforge/analytics/debt.py +92 -0
- platformforge/analytics/dx.py +69 -0
- platformforge/analytics/finops_v4.py +171 -0
- platformforge/analytics/goldenpath.py +131 -0
- platformforge/analytics/history.py +233 -0
- platformforge/analytics/measurement.py +149 -0
- platformforge/analytics/models.py +127 -0
- platformforge/analytics/opsanalytics.py +119 -0
- platformforge/analytics/policyintel.py +139 -0
- platformforge/analytics/reliability.py +106 -0
- platformforge/analytics/soak.py +135 -0
- platformforge/analytics/store.py +254 -0
- platformforge/bench.py +174 -0
- platformforge/cases/__init__.py +13 -0
- platformforge/cases/casefile.py +106 -0
- platformforge/cases/context_audit.py +134 -0
- platformforge/cases/ledgers.py +174 -0
- platformforge/cases/replay.py +142 -0
- platformforge/cases/routing_audit.py +162 -0
- platformforge/caveman/__init__.py +11 -0
- platformforge/caveman/compress.py +156 -0
- platformforge/cicd/__init__.py +16 -0
- platformforge/cicd/backstage.py +51 -0
- platformforge/cicd/dockerfile.py +64 -0
- platformforge/cicd/github_actions.py +122 -0
- platformforge/cicd/gitlab_ci.py +56 -0
- platformforge/cicd/gitops.py +130 -0
- platformforge/cli/__init__.py +0 -0
- platformforge/cli/fleetcmd.py +457 -0
- platformforge/cli/main.py +3832 -0
- platformforge/cloud/__init__.py +8 -0
- platformforge/cloud/aws.py +254 -0
- platformforge/cloud/common.py +69 -0
- platformforge/cloud/multicloud.py +143 -0
- platformforge/collect/__init__.py +11 -0
- platformforge/collect/detect.py +254 -0
- platformforge/context/__init__.py +10 -0
- platformforge/context/capsule.py +60 -0
- platformforge/context/gateway.py +227 -0
- platformforge/context/refs.py +49 -0
- platformforge/context/sufficiency.py +50 -0
- platformforge/core/__init__.py +1 -0
- platformforge/core/hashing.py +39 -0
- platformforge/core/ownership.py +220 -0
- platformforge/core/receipts.py +74 -0
- platformforge/core/redaction.py +97 -0
- platformforge/core/store.py +119 -0
- platformforge/core/workspace.py +89 -0
- platformforge/data/contracts/agent-handoff.schema.json +26 -0
- platformforge/data/contracts/agent-run.schema.json +27 -0
- platformforge/data/contracts/agent-spec.schema.json +43 -0
- platformforge/data/contracts/capability.schema.json +23 -0
- platformforge/data/contracts/change-event.schema.json +22 -0
- platformforge/data/contracts/change-intent.schema.json +62 -0
- platformforge/data/contracts/change-plan.schema.json +43 -0
- platformforge/data/contracts/collector-receipt.schema.json +36 -0
- platformforge/data/contracts/context-pack.schema.json +29 -0
- platformforge/data/contracts/debate.schema.json +31 -0
- platformforge/data/contracts/distribution-manifest-v1.schema.json +81 -0
- platformforge/data/contracts/drift-event.schema.json +21 -0
- platformforge/data/contracts/fact.schema.json +35 -0
- platformforge/data/contracts/finding.schema.json +20 -0
- platformforge/data/contracts/graph-v2.schema.json +67 -0
- platformforge/data/contracts/graph.schema.json +40 -0
- platformforge/data/contracts/install-receipt-v1.schema.json +49 -0
- platformforge/data/contracts/observation.schema.json +93 -0
- platformforge/data/contracts/receipt.schema.json +21 -0
- platformforge/data/contracts/recommendation.schema.json +32 -0
- platformforge/data/contracts/refusal.schema.json +16 -0
- platformforge/data/contracts/slo-contract.schema.json +25 -0
- platformforge/data/contracts/task-spec.schema.json +37 -0
- platformforge/data/contracts/workspace-manifest-v1.schema.json +62 -0
- platformforge/data/evals/cases/agent-budget-exhaustion/case.yaml +6 -0
- platformforge/data/evals/cases/agent-conflicting-specialists/case.yaml +11 -0
- platformforge/data/evals/cases/agent-fleet-optimization/case.yaml +13 -0
- platformforge/data/evals/cases/agent-host-no-subagents/case.yaml +6 -0
- platformforge/data/evals/cases/agent-incident-multi/case.yaml +13 -0
- platformforge/data/evals/cases/agent-missing-evidence/case.yaml +6 -0
- platformforge/data/evals/cases/agent-multi-domain-audit/case.yaml +14 -0
- platformforge/data/evals/cases/agent-production-change/case.yaml +15 -0
- platformforge/data/evals/cases/agent-security-mandatory/case.yaml +12 -0
- platformforge/data/evals/cases/agent-simple-iac/case.yaml +14 -0
- platformforge/data/evals/cases/approval-integrity-seal/case.yaml +6 -0
- platformforge/data/evals/cases/capacity-hot/case.yaml +7 -0
- platformforge/data/evals/cases/capacity-hot/fixture/capacity.json +5 -0
- platformforge/data/evals/cases/catalog-noowner/case.yaml +7 -0
- platformforge/data/evals/cases/catalog-noowner/fixture/catalog-info.yaml +6 -0
- platformforge/data/evals/cases/contract-fact/case.yaml +5 -0
- platformforge/data/evals/cases/contract-fact/fixture/doc.json +2 -0
- platformforge/data/evals/cases/cosign-unverified/case.yaml +8 -0
- platformforge/data/evals/cases/cosign-unverified/fixture/sig.json +33 -0
- platformforge/data/evals/cases/expected-delta-refusal/case.yaml +6 -0
- platformforge/data/evals/cases/finops-noattr/case.yaml +9 -0
- platformforge/data/evals/cases/finops-noattr/fixture/costs.json +7 -0
- platformforge/data/evals/cases/fleet-inv-absent-allocation-unknown/case.yaml +7 -0
- platformforge/data/evals/cases/fleet-inv-centrality-not-criticality/case.yaml +7 -0
- platformforge/data/evals/cases/fleet-inv-correlation-not-causality/case.yaml +7 -0
- platformforge/data/evals/cases/fleet-inv-dx-no-person-metrics/case.yaml +7 -0
- platformforge/data/evals/cases/fleet-inv-federation-restricted-denied/case.yaml +7 -0
- platformforge/data/evals/cases/fleet-inv-federation-secret-denied/case.yaml +7 -0
- platformforge/data/evals/cases/fleet-inv-history-window-deterministic/case.yaml +7 -0
- platformforge/data/evals/cases/fleet-inv-low-util-not-deletable/case.yaml +7 -0
- platformforge/data/evals/cases/fleet-inv-member-key-deterministic/case.yaml +7 -0
- platformforge/data/evals/cases/fleet-inv-missing-denominator/case.yaml +7 -0
- platformforge/data/evals/cases/fleet-inv-partial-coverage-visible/case.yaml +7 -0
- platformforge/data/evals/cases/fleet-inv-plan-changeintent-only/case.yaml +7 -0
- platformforge/data/evals/cases/fleet-inv-report-all-cited/case.yaml +7 -0
- platformforge/data/evals/cases/fleet-inv-small-sample-confidence/case.yaml +7 -0
- platformforge/data/evals/cases/fleet-inv-uncertain-optimization-suppressed/case.yaml +7 -0
- platformforge/data/evals/cases/gha-dangerous/case.yaml +12 -0
- platformforge/data/evals/cases/gha-dangerous/fixture/.github/workflows/ci.yaml +11 -0
- platformforge/data/evals/cases/gitops-v2/case.yaml +11 -0
- platformforge/data/evals/cases/gitops-v2/fixture/argo.yaml +41 -0
- platformforge/data/evals/cases/graph-k8s-blast/case.yaml +7 -0
- platformforge/data/evals/cases/graph-k8s-blast/fixture/deploy.yaml +15 -0
- platformforge/data/evals/cases/iac-module-nopin/case.yaml +8 -0
- platformforge/data/evals/cases/iac-module-nopin/fixture/main.tf +3 -0
- platformforge/data/evals/cases/iac-plan-replace/case.yaml +8 -0
- platformforge/data/evals/cases/iac-plan-replace/fixture/plan.json +12 -0
- platformforge/data/evals/cases/iac-sg-open/case.yaml +8 -0
- platformforge/data/evals/cases/iac-sg-open/fixture/main.tf +9 -0
- platformforge/data/evals/cases/iam-oidc/case.yaml +8 -0
- platformforge/data/evals/cases/iam-oidc/fixture/policy.json +11 -0
- platformforge/data/evals/cases/iam-wildcard-positive/case.yaml +8 -0
- platformforge/data/evals/cases/iam-wildcard-positive/fixture/policy.json +3 -0
- platformforge/data/evals/cases/k8s-bad-actor/case.yaml +19 -0
- platformforge/data/evals/cases/k8s-bad-actor/fixture/app.yaml +97 -0
- platformforge/data/evals/cases/k8s-beta-api/case.yaml +8 -0
- platformforge/data/evals/cases/k8s-beta-api/fixture/cr.yaml +7 -0
- platformforge/data/evals/cases/k8s-dep-api-boundary/case.yaml +15 -0
- platformforge/data/evals/cases/k8s-dep-api-boundary/fixture/app.yaml +25 -0
- platformforge/data/evals/cases/k8s-dep-api-clean/case.yaml +12 -0
- platformforge/data/evals/cases/k8s-dep-api-clean/fixture/app.yaml +29 -0
- platformforge/data/evals/cases/k8s-dep-api-unknown/case.yaml +11 -0
- platformforge/data/evals/cases/k8s-dep-api-unknown/fixture/app.yaml +25 -0
- platformforge/data/evals/cases/k8s-dep-api-v120/case.yaml +12 -0
- platformforge/data/evals/cases/k8s-dep-api-v120/fixture/app.yaml +25 -0
- platformforge/data/evals/cases/k8s-dep-api-v129/case.yaml +9 -0
- platformforge/data/evals/cases/k8s-dep-api-v129/fixture/app.yaml +25 -0
- platformforge/data/evals/cases/k8s-hpa-noreq/case.yaml +8 -0
- platformforge/data/evals/cases/k8s-hpa-noreq/fixture/app.yaml +28 -0
- platformforge/data/evals/cases/k8s-limits-boundary/case.yaml +10 -0
- platformforge/data/evals/cases/k8s-limits-boundary/fixture/app.yaml +15 -0
- platformforge/data/evals/cases/k8s-limits-negative/case.yaml +8 -0
- platformforge/data/evals/cases/k8s-limits-negative/fixture/deploy.yaml +15 -0
- platformforge/data/evals/cases/k8s-limits-positive/case.yaml +8 -0
- platformforge/data/evals/cases/k8s-limits-positive/fixture/deploy.yaml +14 -0
- platformforge/data/evals/cases/k8s-nopdb/case.yaml +7 -0
- platformforge/data/evals/cases/k8s-nopdb/fixture/app.yaml +34 -0
- platformforge/data/evals/cases/knowledge-registry/case.yaml +10 -0
- platformforge/data/evals/cases/kyverno-dep/case.yaml +10 -0
- platformforge/data/evals/cases/kyverno-dep/fixture/pol.yaml +18 -0
- platformforge/data/evals/cases/kyverno-legacy/case.yaml +8 -0
- platformforge/data/evals/cases/kyverno-legacy/fixture/pol.yaml +7 -0
- platformforge/data/evals/cases/live-aws-collect/case.yaml +11 -0
- platformforge/data/evals/cases/live-aws-collect/fixture/aws/ec2_describe-vpcs.json +15 -0
- platformforge/data/evals/cases/live-aws-collect/fixture/aws/iam_list-users.denied.txt +1 -0
- platformforge/data/evals/cases/live-aws-collect/fixture/aws/sts_get-caller-identity.json +5 -0
- platformforge/data/evals/cases/live-drift/case.yaml +8 -0
- platformforge/data/evals/cases/live-drift/fixture/after.json +22 -0
- platformforge/data/evals/cases/live-drift/fixture/before.json +22 -0
- platformforge/data/evals/cases/live-incident/case.yaml +7 -0
- platformforge/data/evals/cases/live-incident/fixture/changes.json +8 -0
- platformforge/data/evals/cases/live-incident/fixture/incident.json +7 -0
- platformforge/data/evals/cases/live-k8s-collect/case.yaml +10 -0
- platformforge/data/evals/cases/live-k8s-collect/fixture/kubectl/get_configmaps.denied.txt +1 -0
- platformforge/data/evals/cases/live-k8s-collect/fixture/kubectl/get_deployments.apps.json +25 -0
- platformforge/data/evals/cases/live-k8s-collect/fixture/kubectl/get_pods.json +17 -0
- platformforge/data/evals/cases/live-k8s-collect/fixture/kubectl/get_secrets.json +13 -0
- platformforge/data/evals/cases/live-multicluster/case.yaml +7 -0
- platformforge/data/evals/cases/live-multicluster/fixture/clusters.json +6 -0
- platformforge/data/evals/cases/live-plan/case.yaml +7 -0
- platformforge/data/evals/cases/live-plan/fixture/drift.json +8 -0
- platformforge/data/evals/cases/live-reconcile/case.yaml +7 -0
- platformforge/data/evals/cases/live-reconcile/fixture/desired.json +7 -0
- platformforge/data/evals/cases/live-reconcile/fixture/observed.json +18 -0
- platformforge/data/evals/cases/live-secrets-boundary/case.yaml +8 -0
- platformforge/data/evals/cases/live-secrets-boundary/fixture/envelope.json +16 -0
- platformforge/data/evals/cases/live-temporal-graph/case.yaml +10 -0
- platformforge/data/evals/cases/live-temporal-graph/fixture/graph.json +22 -0
- platformforge/data/evals/cases/ops-approval/case.yaml +6 -0
- platformforge/data/evals/cases/ops-convergence/case.yaml +6 -0
- platformforge/data/evals/cases/ops-execution-safety/case.yaml +6 -0
- platformforge/data/evals/cases/ops-intent/case.yaml +6 -0
- platformforge/data/evals/cases/ops-planning/case.yaml +6 -0
- platformforge/data/evals/cases/ops-policy/case.yaml +6 -0
- platformforge/data/evals/cases/ops-rollback/case.yaml +6 -0
- platformforge/data/evals/cases/ops-verification/case.yaml +6 -0
- platformforge/data/evals/cases/rollback-argocd-revision/case.yaml +6 -0
- platformforge/data/evals/cases/rollback-blind-inverse/case.yaml +6 -0
- platformforge/data/evals/cases/rollback-git-commit/case.yaml +6 -0
- platformforge/data/evals/cases/rollback-impossible/case.yaml +6 -0
- platformforge/data/evals/cases/rollback-k8s-annotation-restore/case.yaml +6 -0
- platformforge/data/evals/cases/rollback-k8s-scale/case.yaml +6 -0
- platformforge/data/evals/cases/rollback-material-tamper/case.yaml +6 -0
- platformforge/data/evals/cases/rollback-terraform-replan/case.yaml +6 -0
- platformforge/data/evals/cases/rollback-unknown/case.yaml +6 -0
- platformforge/data/evals/cases/routing-specialist/case.yaml +9 -0
- platformforge/data/evals/cases/rtk-roundtrip/case.yaml +5 -0
- platformforge/data/evals/cases/rtk-roundtrip/fixture/verbose.txt +300 -0
- platformforge/data/evals/cases/sbom-vuln/case.yaml +9 -0
- platformforge/data/evals/cases/sbom-vuln/fixture/sbom.json +9 -0
- platformforge/data/evals/cases/secret-prose-mixed/fixture/app.env +1 -0
- platformforge/data/evals/cases/secret-prose-mixed/fixture/policy-notes.md +7 -0
- platformforge/data/evals/cases/secrets-noleak/case.yaml +5 -0
- platformforge/data/evals/cases/secrets-noleak/fixture/app.env +2 -0
- platformforge/data/evals/cases/slo-fastburn/case.yaml +8 -0
- platformforge/data/evals/cases/slo-fastburn/fixture/events.yaml +2 -0
- platformforge/data/evals/cases/slo-fastburn/fixture/slo.yaml +4 -0
- platformforge/data/evals/cases/slo-noinput/case.yaml +8 -0
- platformforge/data/evals/cases/slo-noinput/fixture/events.yaml +1 -0
- platformforge/data/evals/cases/slo-noinput/fixture/slo.yaml +4 -0
- platformforge/data/evals/cases/sot-conflict/case.yaml +6 -0
- platformforge/data/evals/cases/token-budget/case.yaml +6 -0
- platformforge/data/evals/cases/token-budget/fixture/big.py +50 -0
- platformforge/data/evals/cases/xr-delete/case.yaml +8 -0
- platformforge/data/evals/cases/xr-delete/fixture/xr.yaml +10 -0
- platformforge/data/evals/cases/xr-graph-chain/case.yaml +8 -0
- platformforge/data/evals/cases/xr-graph-chain/fixture/xrd.yaml +90 -0
- platformforge/data/evals/cases/xr-negative-crd/case.yaml +8 -0
- platformforge/data/evals/cases/xr-negative-crd/fixture/widget.yaml +19 -0
- platformforge/data/evals/cases/xr-upbound-mr/case.yaml +8 -0
- platformforge/data/evals/cases/xr-upbound-mr/fixture/bucket.yaml +12 -0
- platformforge/data/evals/qpt-bench/change-review/case.yaml +5 -0
- platformforge/data/evals/qpt-bench/change-review/fixture/plan.json +9 -0
- platformforge/data/evals/qpt-bench/gha-security/case.yaml +5 -0
- platformforge/data/evals/qpt-bench/gha-security/fixture/ci.yml +10 -0
- platformforge/data/evals/qpt-bench/iam-investigate/case.yaml +5 -0
- platformforge/data/evals/qpt-bench/iam-investigate/fixture/policy.json +6 -0
- platformforge/data/evals/qpt-bench/incident-analysis/case.yaml +5 -0
- platformforge/data/evals/qpt-bench/incident-analysis/fixture/query.json +5 -0
- platformforge/data/evals/qpt-bench/incident-analysis/fixture/rules.yaml +6 -0
- platformforge/data/evals/qpt-bench/k8s-review/case.yaml +6 -0
- platformforge/data/evals/qpt-bench/k8s-review/fixture/deploy.yaml +15 -0
- platformforge/data/evals/qpt-bench/tf-security/case.yaml +5 -0
- platformforge/data/evals/qpt-bench/tf-security/fixture/main.tf +17 -0
- platformforge/data/knowledge/aws/eks.yaml +20 -0
- platformforge/data/knowledge/aws/iam.yaml +28 -0
- platformforge/data/knowledge/aws/network-exposure.yaml +19 -0
- platformforge/data/knowledge/aws/organizations.yaml +19 -0
- platformforge/data/knowledge/crossplane/composition-providers.yaml +20 -0
- platformforge/data/knowledge/crossplane/managed-resources.yaml +26 -0
- platformforge/data/knowledge/crossplane/v1-v2.yaml +25 -0
- platformforge/data/knowledge/kubernetes/api-deprecations.yaml +33 -0
- platformforge/data/knowledge/kubernetes/autoscaling-rbac.yaml +27 -0
- platformforge/data/knowledge/kubernetes/gateway-api.yaml +20 -0
- platformforge/data/knowledge/kubernetes/pod-security.yaml +26 -0
- platformforge/data/knowledge/sources.yaml +516 -0
- platformforge/data/knowledge/terraform/lifecycle.yaml +18 -0
- platformforge/data/knowledge/terraform/plan-semantics.yaml +26 -0
- platformforge/data/lab/chaos-pod-kill/chaos.yaml +3 -0
- platformforge/data/lab/chaos-pod-kill/facts.json +13 -0
- platformforge/data/lab/fleets/acme/capacity.yaml +35 -0
- platformforge/data/lab/fleets/acme/costs.yaml +25 -0
- platformforge/data/lab/fleets/acme/events.yaml +41 -0
- platformforge/data/lab/fleets/acme/fleet.yaml +51 -0
- platformforge/data/lab/fleets/acme/graph.yaml +45 -0
- platformforge/data/lab/fleets/acme/policies.yaml +28 -0
- platformforge/data/lab/fleets/acme/requests.yaml +24 -0
- platformforge/data/lab/scenarios/argo-bad-rollout/expected.yaml +6 -0
- platformforge/data/lab/scenarios/argo-bad-rollout/fixture/app.yaml +9 -0
- platformforge/data/lab/scenarios/aws-dump/expected.yaml +19 -0
- platformforge/data/lab/scenarios/aws-dump/fixture/compute.json +4 -0
- platformforge/data/lab/scenarios/aws-dump/fixture/iam.json +7 -0
- platformforge/data/lab/scenarios/aws-dump/fixture/network.json +6 -0
- platformforge/data/lab/scenarios/aws-dump/fixture/org.json +4 -0
- platformforge/data/lab/scenarios/aws-dump/fixture/storage.json +4 -0
- platformforge/data/lab/scenarios/container-unsigned/expected.yaml +5 -0
- platformforge/data/lab/scenarios/container-unsigned/fixture/supply.json +11 -0
- platformforge/data/lab/scenarios/cost-idle-workload/expected.yaml +5 -0
- platformforge/data/lab/scenarios/cost-idle-workload/fixture/costs.json +12 -0
- platformforge/data/lab/scenarios/crossplane-platform/expected.yaml +16 -0
- platformforge/data/lab/scenarios/crossplane-platform/fixture/negative.yaml +18 -0
- platformforge/data/lab/scenarios/crossplane-platform/fixture/xrd.yaml +90 -0
- platformforge/data/lab/scenarios/demo-platform/expected.yaml +16 -0
- platformforge/data/lab/scenarios/demo-platform/fixture/.github/workflows/ci.yml +10 -0
- platformforge/data/lab/scenarios/demo-platform/fixture/deploy.yaml +16 -0
- platformforge/data/lab/scenarios/demo-platform/fixture/infra.tf +5 -0
- platformforge/data/lab/scenarios/fleet-ai/expected.yaml +12 -0
- platformforge/data/lab/scenarios/fleet-capacity/expected.yaml +12 -0
- platformforge/data/lab/scenarios/fleet-cost/expected.yaml +18 -0
- platformforge/data/lab/scenarios/fleet-federation/expected.yaml +11 -0
- platformforge/data/lab/scenarios/fleet-golden-path/expected.yaml +18 -0
- platformforge/data/lab/scenarios/fleet-op-hotspot/expected.yaml +11 -0
- platformforge/data/lab/scenarios/fleet-ownership/expected.yaml +17 -0
- platformforge/data/lab/scenarios/fleet-partial/expected.yaml +10 -0
- platformforge/data/lab/scenarios/fleet-policy-friction/expected.yaml +10 -0
- platformforge/data/lab/scenarios/fleet-recurring-incident/expected.yaml +12 -0
- platformforge/data/lab/scenarios/fleet-report/expected.yaml +13 -0
- platformforge/data/lab/scenarios/fleet-risk/expected.yaml +18 -0
- platformforge/data/lab/scenarios/gitops-drift/expected.yaml +6 -0
- platformforge/data/lab/scenarios/gitops-drift/fixture/app.yaml +12 -0
- platformforge/data/lab/scenarios/k8s-bad-readiness/expected.yaml +6 -0
- platformforge/data/lab/scenarios/k8s-bad-readiness/fixture/deploy.yaml +23 -0
- platformforge/data/lab/scenarios/k8s-bad-readiness/fixture/netpol.yaml +9 -0
- platformforge/data/lab/scenarios/k8s-missing-limits/expected.yaml +6 -0
- platformforge/data/lab/scenarios/k8s-missing-limits/fixture/deploy.yaml +26 -0
- platformforge/data/lab/scenarios/k8s-missing-limits/fixture/netpol.yaml +9 -0
- platformforge/data/lab/scenarios/k8s-single-replica/expected.yaml +5 -0
- platformforge/data/lab/scenarios/k8s-single-replica/fixture/deploy.yaml +26 -0
- platformforge/data/lab/scenarios/k8s-single-replica/fixture/netpol.yaml +4 -0
- platformforge/data/lab/scenarios/live-aws-collect/expected.yaml +4 -0
- platformforge/data/lab/scenarios/live-aws-collect/fixture/aws/ec2_describe-vpcs.json +15 -0
- platformforge/data/lab/scenarios/live-aws-collect/fixture/aws/iam_list-users.denied.txt +1 -0
- platformforge/data/lab/scenarios/live-aws-collect/fixture/aws/sts_get-caller-identity.json +5 -0
- platformforge/data/lab/scenarios/live-k8s-collect/expected.yaml +4 -0
- platformforge/data/lab/scenarios/live-k8s-collect/fixture/kubectl/get_configmaps.denied.txt +1 -0
- platformforge/data/lab/scenarios/live-k8s-collect/fixture/kubectl/get_deployments.apps.json +25 -0
- platformforge/data/lab/scenarios/live-k8s-collect/fixture/kubectl/get_pods.json +17 -0
- platformforge/data/lab/scenarios/live-k8s-collect/fixture/kubectl/get_secrets.json +13 -0
- platformforge/data/lab/scenarios/live-kind/expected.yaml +14 -0
- platformforge/data/lab/scenarios/live-kind/fixture/kubectl/get_configmaps.denied.txt +1 -0
- platformforge/data/lab/scenarios/live-kind/fixture/kubectl/get_deployments.apps.json +25 -0
- platformforge/data/lab/scenarios/live-kind/fixture/kubectl/get_pods.json +17 -0
- platformforge/data/lab/scenarios/live-kind/fixture/kubectl/get_secrets.json +13 -0
- platformforge/data/lab/scenarios/network-policy-missing/expected.yaml +6 -0
- platformforge/data/lab/scenarios/network-policy-missing/fixture/deploy.yaml +26 -0
- platformforge/data/lab/scenarios/ops-bad-rollout/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-bad-rollout/fixture/ops.yaml +33 -0
- platformforge/data/lab/scenarios/ops-break-glass/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-break-glass/fixture/ops.yaml +21 -0
- platformforge/data/lab/scenarios/ops-broken-selector/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-broken-selector/fixture/ops.yaml +21 -0
- platformforge/data/lab/scenarios/ops-changed-plan-hash/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-changed-plan-hash/fixture/ops.yaml +20 -0
- platformforge/data/lab/scenarios/ops-conflicting-operation/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-conflicting-operation/fixture/ops.yaml +21 -0
- platformforge/data/lab/scenarios/ops-expired-approval/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-expired-approval/fixture/ops.yaml +20 -0
- platformforge/data/lab/scenarios/ops-partial-failure/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-partial-failure/fixture/ops.yaml +24 -0
- platformforge/data/lab/scenarios/ops-policy-denial/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-policy-denial/fixture/ops.yaml +17 -0
- platformforge/data/lab/scenarios/ops-replica-drift/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-replica-drift/fixture/ops.yaml +28 -0
- platformforge/data/lab/scenarios/ops-rollback-annotate/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-rollback-annotate/fixture/ops.yaml +41 -0
- platformforge/data/lab/scenarios/ops-rollback-gitops/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-rollback-gitops/fixture/ops.yaml +38 -0
- platformforge/data/lab/scenarios/ops-rollback-impossible/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-rollback-impossible/fixture/ops.yaml +34 -0
- platformforge/data/lab/scenarios/ops-rollback-scale/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-rollback-scale/fixture/ops.yaml +40 -0
- platformforge/data/lab/scenarios/ops-rollback-terraform/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-rollback-terraform/fixture/ops.yaml +38 -0
- platformforge/data/lab/scenarios/ops-rollback-terraform/fixture/tf/tfplan.bin +1 -0
- platformforge/data/lab/scenarios/ops-stale-observation/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-stale-observation/fixture/ops.yaml +20 -0
- platformforge/data/lab/scenarios/secret-exposure/expected.yaml +5 -0
- platformforge/data/lab/scenarios/secret-exposure/fixture/app.env +2 -0
- platformforge/data/lab/scenarios/slo-burn/expected.yaml +5 -0
- platformforge/data/lab/scenarios/slo-burn/fixture/events.yaml +2 -0
- platformforge/data/lab/scenarios/slo-burn/fixture/slo.yaml +4 -0
- platformforge/data/lab/scenarios/terraform-iam-wildcard/expected.yaml +5 -0
- platformforge/data/lab/scenarios/terraform-iam-wildcard/fixture/policy.json +14 -0
- platformforge/data/lab/scenarios/terraform-public-bucket/expected.yaml +6 -0
- platformforge/data/lab/scenarios/terraform-public-bucket/fixture/main.tf +5 -0
- platformforge/data/portable_skills/platformforge-agents/SKILL.md +47 -0
- platformforge/data/portable_skills/platformforge-change/SKILL.md +41 -0
- platformforge/data/portable_skills/platformforge-core/SKILL.md +62 -0
- platformforge/data/portable_skills/platformforge-economy/SKILL.md +42 -0
- platformforge/data/portable_skills/platformforge-finops/SKILL.md +34 -0
- platformforge/data/portable_skills/platformforge-fleet/SKILL.md +39 -0
- platformforge/data/portable_skills/platformforge-governance/SKILL.md +76 -0
- platformforge/data/portable_skills/platformforge-graph/SKILL.md +46 -0
- platformforge/data/portable_skills/platformforge-lab/SKILL.md +39 -0
- platformforge/data/portable_skills/platformforge-sdd/SKILL.md +39 -0
- platformforge/data/portable_skills/platformforge-security/SKILL.md +38 -0
- platformforge/data/portable_skills/platformforge-sre/SKILL.md +41 -0
- platformforge/data/rules/catalog/finops.yaml +26 -0
- platformforge/data/rules/catalog/gitops.yaml +161 -0
- platformforge/data/rules/catalog/iac.yaml +84 -0
- platformforge/data/rules/catalog/k8s.yaml +322 -0
- platformforge/data/rules/catalog/orchestration.yaml +99 -0
- platformforge/data/rules/catalog/product.yaml +42 -0
- platformforge/data/rules/catalog/routing.yaml +122 -0
- platformforge/data/rules/catalog/security.yaml +159 -0
- platformforge/data/rules/catalog/sre.yaml +67 -0
- platformforge/diagnose/__init__.py +75 -0
- platformforge/distribution/__init__.py +18 -0
- platformforge/distribution/bundle.py +127 -0
- platformforge/distribution/hostflow.py +247 -0
- platformforge/distribution/models.py +80 -0
- platformforge/distribution/service.py +225 -0
- platformforge/economy/__init__.py +27 -0
- platformforge/economy/bench.py +66 -0
- platformforge/economy/budget.py +198 -0
- platformforge/economy/cache.py +287 -0
- platformforge/economy/checkpoint.py +112 -0
- platformforge/economy/collection.py +142 -0
- platformforge/economy/doctor.py +207 -0
- platformforge/economy/engine.py +158 -0
- platformforge/economy/envelope.py +61 -0
- platformforge/economy/fleetpack.py +86 -0
- platformforge/economy/ledger.py +232 -0
- platformforge/economy/plan.py +131 -0
- platformforge/economy/pricing.py +101 -0
- platformforge/economy/qpt.py +286 -0
- platformforge/economy/reconcile.py +86 -0
- platformforge/economy/verifyplan.py +107 -0
- platformforge/economy/waste.py +123 -0
- platformforge/evals/__init__.py +16 -0
- platformforge/evals/coverage.py +140 -0
- platformforge/evals/fleet_invariants.py +260 -0
- platformforge/evals/ops_invariants.py +158 -0
- platformforge/evals/runner.py +541 -0
- platformforge/federation/__init__.py +4 -0
- platformforge/federation/node.py +135 -0
- platformforge/finops/__init__.py +14 -0
- platformforge/finops/alloc.py +31 -0
- platformforge/finops/costs.py +79 -0
- platformforge/finops/focus.py +100 -0
- platformforge/finops/graph_cost.py +51 -0
- platformforge/finops/ingest.py +144 -0
- platformforge/finops/insights.py +193 -0
- platformforge/finops/unit.py +36 -0
- platformforge/fleet/__init__.py +5 -0
- platformforge/fleet/loader.py +73 -0
- platformforge/fleet/models.py +155 -0
- platformforge/fleet/orggraph.py +172 -0
- platformforge/fleet/query.py +154 -0
- platformforge/forge/__init__.py +9 -0
- platformforge/forge/collect.py +88 -0
- platformforge/forge/delegate.py +77 -0
- platformforge/forge/manifest.py +106 -0
- platformforge/freeze/__init__.py +22 -0
- platformforge/freeze/exceptions.py +56 -0
- platformforge/freeze/manifest.py +167 -0
- platformforge/freeze/snapshots.py +108 -0
- platformforge/graph/__init__.py +39 -0
- platformforge/graph/backend.py +174 -0
- platformforge/graph/bench.py +245 -0
- platformforge/graph/build.py +130 -0
- platformforge/graph/delivery.py +183 -0
- platformforge/graph/diff.py +292 -0
- platformforge/graph/events.py +116 -0
- platformforge/graph/migrate.py +44 -0
- platformforge/graph/model.py +264 -0
- platformforge/graph/persist.py +97 -0
- platformforge/graph/query.py +290 -0
- platformforge/graph/temporal.py +78 -0
- platformforge/graph/vocab.py +63 -0
- platformforge/graphview.py +94 -0
- platformforge/iac/__init__.py +7 -0
- platformforge/iac/drift.py +53 -0
- platformforge/iac/plan.py +84 -0
- platformforge/iac/terraform.py +123 -0
- platformforge/install/__init__.py +1 -0
- platformforge/install/service.py +421 -0
- platformforge/k8s/__init__.py +8 -0
- platformforge/k8s/helm.py +152 -0
- platformforge/k8s/hubble.py +87 -0
- platformforge/k8s/manifests.py +513 -0
- platformforge/knowledge/__init__.py +1 -0
- platformforge/knowledge/packs.py +157 -0
- platformforge/knowledge/registry.py +249 -0
- platformforge/knowledge/select.py +127 -0
- platformforge/lab/__init__.py +5 -0
- platformforge/lab/chaos.py +71 -0
- platformforge/lab/fleetlab.py +351 -0
- platformforge/lab/opslab.py +271 -0
- platformforge/lab/runner.py +219 -0
- platformforge/live/__init__.py +36 -0
- platformforge/live/budget.py +92 -0
- platformforge/live/capability.py +80 -0
- platformforge/live/collectors/__init__.py +6 -0
- platformforge/live/collectors/aws.py +370 -0
- platformforge/live/collectors/aws_transport.py +98 -0
- platformforge/live/collectors/k8s.py +389 -0
- platformforge/live/collectors/k8s_transport.py +85 -0
- platformforge/live/cursors.py +48 -0
- platformforge/live/drift.py +130 -0
- platformforge/live/envelope.py +85 -0
- platformforge/live/federation.py +113 -0
- platformforge/live/fixtures.py +168 -0
- platformforge/live/identity.py +258 -0
- platformforge/live/incident.py +200 -0
- platformforge/live/models.py +435 -0
- platformforge/live/reconcile.py +382 -0
- platformforge/live/remediate.py +169 -0
- platformforge/live/store.py +256 -0
- platformforge/live/topology.py +249 -0
- platformforge/mcp/__init__.py +7 -0
- platformforge/mcp/parity.py +69 -0
- platformforge/mcp/registry.py +427 -0
- platformforge/mcp/server.py +84 -0
- platformforge/mcp/tools.py +732 -0
- platformforge/models/__init__.py +21 -0
- platformforge/models/base.py +214 -0
- platformforge/observe/__init__.py +26 -0
- platformforge/observe/capacity.py +39 -0
- platformforge/observe/dr.py +67 -0
- platformforge/observe/grafana.py +76 -0
- platformforge/observe/incident.py +58 -0
- platformforge/observe/otel.py +135 -0
- platformforge/observe/postmortem.py +72 -0
- platformforge/observe/prometheus.py +88 -0
- platformforge/observe/slo.py +125 -0
- platformforge/ops/__init__.py +8 -0
- platformforge/ops/actions.py +215 -0
- platformforge/ops/analytics.py +83 -0
- platformforge/ops/approval.py +275 -0
- platformforge/ops/autorem.py +101 -0
- platformforge/ops/config.py +175 -0
- platformforge/ops/engine.py +758 -0
- platformforge/ops/envelope.py +135 -0
- platformforge/ops/executors/__init__.py +1 -0
- platformforge/ops/executors/base.py +211 -0
- platformforge/ops/executors/git.py +77 -0
- platformforge/ops/executors/gitops.py +134 -0
- platformforge/ops/executors/terraform.py +84 -0
- platformforge/ops/gates.py +135 -0
- platformforge/ops/goldenpath.py +180 -0
- platformforge/ops/material.py +181 -0
- platformforge/ops/models.py +421 -0
- platformforge/ops/operation.py +335 -0
- platformforge/ops/opgraph.py +233 -0
- platformforge/ops/policy.py +275 -0
- platformforge/ops/preconditions.py +109 -0
- platformforge/ops/registry.py +101 -0
- platformforge/ops/risk.py +216 -0
- platformforge/ops/rollback.py +287 -0
- platformforge/ops/rollback_builders.py +163 -0
- platformforge/ops/runbook.py +269 -0
- platformforge/ops/simulate.py +125 -0
- platformforge/ops/source_of_truth.py +198 -0
- platformforge/ops/store.py +157 -0
- platformforge/ops/verify.py +190 -0
- platformforge/optimize/__init__.py +4 -0
- platformforge/optimize/engine.py +109 -0
- platformforge/optimize/models.py +68 -0
- platformforge/optimize/scan.py +196 -0
- platformforge/plan/__init__.py +107 -0
- platformforge/portable_cli.py +199 -0
- platformforge/portal_adapter.py +740 -0
- platformforge/portal_assets/chunk-7_l-b4Tr.js +1 -0
- platformforge/portal_assets/chunk-B0Iad6pD.js +1 -0
- platformforge/portal_assets/chunk-B605XnCF.js +1 -0
- platformforge/portal_assets/chunk-B7N3TXcn.js +1 -0
- platformforge/portal_assets/chunk-BAj6yGem.js +1 -0
- platformforge/portal_assets/chunk-BGUitn2r.js +1 -0
- platformforge/portal_assets/chunk-BVKnCv85.js +1 -0
- platformforge/portal_assets/chunk-BYyM9V_w.js +1 -0
- platformforge/portal_assets/chunk-BaQMhq-G.js +1 -0
- platformforge/portal_assets/chunk-BavOrxVm.js +1 -0
- platformforge/portal_assets/chunk-Bb0C24YV.js +1 -0
- platformforge/portal_assets/chunk-BfCFGPzs.js +1 -0
- platformforge/portal_assets/chunk-C2t4OUVu.js +1 -0
- platformforge/portal_assets/chunk-C3pI4QjU.js +1 -0
- platformforge/portal_assets/chunk-C4gpbtbP.js +1 -0
- platformforge/portal_assets/chunk-CGWeHhKY.js +1 -0
- platformforge/portal_assets/chunk-CJCT86Dr.js +1 -0
- platformforge/portal_assets/chunk-CXcsLDPJ.js +1 -0
- platformforge/portal_assets/chunk-CcjxoLxw.js +1 -0
- platformforge/portal_assets/chunk-D-gBPXHa.js +1 -0
- platformforge/portal_assets/chunk-D3eZogIe.js +1 -0
- platformforge/portal_assets/chunk-DQU0VyoQ.js +1 -0
- platformforge/portal_assets/chunk-DqWNkF1O.js +1 -0
- platformforge/portal_assets/chunk-DuPPkSIE.js +3 -0
- platformforge/portal_assets/chunk-DyIHOW_a.js +1 -0
- platformforge/portal_assets/chunk-Fx4zSGL5.js +1 -0
- platformforge/portal_assets/chunk-UBKt05Rg.js +1 -0
- platformforge/portal_assets/chunk-htnJDQEY.js +1 -0
- platformforge/portal_assets/chunk-kXRFMGqe.js +1 -0
- platformforge/portal_assets/chunk-qe3u763a.js +1 -0
- platformforge/portal_assets/favicon.ico +0 -0
- platformforge/portal_assets/index.html +13 -0
- platformforge/portal_assets/main-QDFVZK3T.js +5 -0
- platformforge/portal_assets/styles-MQEBDFDX.css +1 -0
- platformforge/product/__init__.py +10 -0
- platformforge/product/backstage.py +49 -0
- platformforge/product/catalog.py +74 -0
- platformforge/product/crossplane.py +341 -0
- platformforge/product/golden_paths/__init__.py +5 -0
- platformforge/product/golden_paths/engine.py +97 -0
- platformforge/product/golden_paths/library.yaml +198 -0
- platformforge/product/golden_paths/model.py +84 -0
- platformforge/product/maturity.py +107 -0
- platformforge/product/scorecards.py +84 -0
- platformforge/resources.py +36 -0
- platformforge/risk/__init__.py +5 -0
- platformforge/risk/engine.py +95 -0
- platformforge/risk/signals.py +49 -0
- platformforge/routing/__init__.py +13 -0
- platformforge/routing/decision.py +191 -0
- platformforge/routing/router.py +328 -0
- platformforge/rtk/__init__.py +9 -0
- platformforge/rtk/compact.py +127 -0
- platformforge/rtk/parsers.py +199 -0
- platformforge/rules/__init__.py +5 -0
- platformforge/rules/engine.py +345 -0
- platformforge/sandbox/__init__.py +7 -0
- platformforge/sandbox/env.py +151 -0
- platformforge/sandbox/review.py +92 -0
- platformforge/sdd/__init__.py +8 -0
- platformforge/sdd/lifecycle.py +239 -0
- platformforge/security/__init__.py +19 -0
- platformforge/security/cosign.py +67 -0
- platformforge/security/iam.py +112 -0
- platformforge/security/identity.py +84 -0
- platformforge/security/kyverno.py +106 -0
- platformforge/security/sbom.py +59 -0
- platformforge/security/scan.py +63 -0
- platformforge/security/slsa.py +86 -0
- platformforge/security/supply.py +62 -0
- platformforge/tokensave/__init__.py +20 -0
- platformforge/tokensave/budget.py +62 -0
- platformforge/tokensave/estimate.py +21 -0
- platformforge/tokensave/index.py +172 -0
- platformforge/tokensave/ledger.py +121 -0
- platformforge/tokensave/packs.py +285 -0
- platformforge/ui/__init__.py +28 -0
- platformforge/ui/app.py +465 -0
- platformforge/ui/home.py +113 -0
- platformforge/ui/i18n.py +143 -0
- platformforge/ui/kit.py +689 -0
- platformforge/ui/screen.py +628 -0
- platformforge/ui/tui.py +94 -0
- platformforge/ui/wizard.py +309 -0
- platformforge/workspace/__init__.py +13 -0
- platformforge/workspace/models.py +35 -0
- platformforge/workspace/service.py +69 -0
- platformforge-0.1.0.dist-info/METADATA +212 -0
- platformforge-0.1.0.dist-info/RECORD +659 -0
- platformforge-0.1.0.dist-info/WHEEL +4 -0
- platformforge-0.1.0.dist-info/entry_points.txt +3 -0
- platformforge-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Token estimation — honest heuristic.
|
|
2
|
+
|
|
3
|
+
~4 chars/token is the documented approximation for code+prose in English.
|
|
4
|
+
Estimates are ALWAYS labeled `estimated`, never `observed` — provider token
|
|
5
|
+
counts are only real when a host transcript supplies them.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
CHARS_PER_TOKEN = 4.0
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def estimate_tokens(text: str | bytes) -> int:
|
|
14
|
+
if isinstance(text, bytes):
|
|
15
|
+
text = text.decode("utf-8", errors="replace")
|
|
16
|
+
return max(1, round(len(text) / CHARS_PER_TOKEN)) if text else 0
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def estimate_obj(obj) -> int:
|
|
20
|
+
import json
|
|
21
|
+
return estimate_tokens(json.dumps(obj, default=str))
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Local search index — SQLite FTS5, incremental by content hash.
|
|
2
|
+
|
|
3
|
+
Indexes file content + extracted symbols. Unchanged files (same sha256) are
|
|
4
|
+
never re-indexed. No embeddings, no external service.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
import sqlite3
|
|
11
|
+
from collections.abc import Iterable
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from platformforge.core.hashing import sha256_file, sha256_text
|
|
16
|
+
from platformforge.core.redaction import redact_text_report, redaction_receipt
|
|
17
|
+
|
|
18
|
+
IGNORE_DIRS = {".git", ".venv", "node_modules", ".platformforge", "vendor",
|
|
19
|
+
"__pycache__", "dist", "build", ".terraform", ".idea", ".vscode"}
|
|
20
|
+
TEXT_EXT = {".py", ".tf", ".tfvars", ".yaml", ".yml", ".json", ".md", ".go",
|
|
21
|
+
".ts", ".js", ".java", ".rb", ".rs", ".sh", ".toml", ".hcl",
|
|
22
|
+
".sql", ".j2", ".tpl", ".rego", ".cel", ".xml", ".gradle",
|
|
23
|
+
".properties", ".txt", ".cfg", ".ini", ".env.example"}
|
|
24
|
+
MAX_FILE_BYTES = 2_000_000
|
|
25
|
+
|
|
26
|
+
SYMBOL_PATTERNS = {
|
|
27
|
+
".py": [re.compile(r"^\s*(?:class|def|async def)\s+([A-Za-z_][\w]*)", re.MULTILINE)],
|
|
28
|
+
".go": [re.compile(r"^\s*func\s+(?:\([^)]*\)\s*)?([A-Za-z_][\w]*)", re.MULTILINE),
|
|
29
|
+
re.compile(r"^\s*type\s+([A-Za-z_][\w]*)", re.MULTILINE)],
|
|
30
|
+
".java": [re.compile(r"\b(?:class|interface|enum|record)\s+([A-Za-z_][\w]*)")],
|
|
31
|
+
".tf": [re.compile(r'\bresource\s+"([^"]+)"\s+"([^"]+)"'),
|
|
32
|
+
re.compile(r'\bmodule\s+"([^"]+)"'),
|
|
33
|
+
re.compile(r'\b(?:variable|output|data|provider)\s+"?([A-Za-z_][\w]*)"?')],
|
|
34
|
+
".hcl": [re.compile(r'\bresource\s+"([^"]+)"\s+"([^"]+)"')],
|
|
35
|
+
".yaml": [re.compile(r"^\s*name:\s*([A-Za-z0-9_.\-/]+)", re.MULTILINE)],
|
|
36
|
+
".yml": [re.compile(r"^\s*name:\s*([A-Za-z0-9_.\-/]+)", re.MULTILINE)],
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
SCHEMA = """
|
|
40
|
+
CREATE TABLE IF NOT EXISTS files(
|
|
41
|
+
path TEXT PRIMARY KEY, sha256 TEXT, kind TEXT, bytes INTEGER, symbols TEXT);
|
|
42
|
+
CREATE TABLE IF NOT EXISTS facts_idx(
|
|
43
|
+
fact_id TEXT PRIMARY KEY, kind TEXT, location TEXT, doc TEXT);
|
|
44
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS content USING fts5(
|
|
45
|
+
path UNINDEXED, body, tokenize='porter unicode61');
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class SearchIndex:
|
|
50
|
+
def __init__(self, db_path: str | Path):
|
|
51
|
+
self.db_path = Path(db_path)
|
|
52
|
+
self.db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
53
|
+
self.db = sqlite3.connect(str(self.db_path))
|
|
54
|
+
self.db.executescript(SCHEMA)
|
|
55
|
+
self.db.commit()
|
|
56
|
+
|
|
57
|
+
def close(self) -> None:
|
|
58
|
+
self.db.close()
|
|
59
|
+
|
|
60
|
+
def __enter__(self) -> SearchIndex: # noqa: PYI034 - typing.Self is 3.11+; no typing_extensions dep
|
|
61
|
+
return self
|
|
62
|
+
|
|
63
|
+
def __exit__(self, *exc: object) -> None:
|
|
64
|
+
self.close()
|
|
65
|
+
|
|
66
|
+
# ── indexing ──────────────────────────────────────────────
|
|
67
|
+
def _iter_files(self, root: Path) -> Iterable[Path]:
|
|
68
|
+
for p in sorted(root.rglob("*")):
|
|
69
|
+
if not p.is_file() or any(part in IGNORE_DIRS for part in p.parts):
|
|
70
|
+
continue
|
|
71
|
+
if p.suffix in TEXT_EXT and p.stat().st_size <= MAX_FILE_BYTES:
|
|
72
|
+
yield p
|
|
73
|
+
|
|
74
|
+
def index_workspace(self, root: str | Path) -> dict[str, int]:
|
|
75
|
+
"""Incremental: only changed/new files are parsed and re-indexed."""
|
|
76
|
+
root = Path(root)
|
|
77
|
+
stats = {"indexed": 0, "reused": 0, "removed": 0}
|
|
78
|
+
seen = set()
|
|
79
|
+
for p in self._iter_files(root):
|
|
80
|
+
rel = str(p.relative_to(root))
|
|
81
|
+
seen.add(rel)
|
|
82
|
+
sha = sha256_file(p)
|
|
83
|
+
row = self.db.execute(
|
|
84
|
+
"SELECT sha256 FROM files WHERE path=?", (rel,)).fetchone()
|
|
85
|
+
if row and row[0] == sha:
|
|
86
|
+
stats["reused"] += 1
|
|
87
|
+
continue
|
|
88
|
+
body = p.read_text(errors="replace")
|
|
89
|
+
# §17 — secrets are never stored raw in the context engine;
|
|
90
|
+
# the index keeps redacted text + a receipt of what was masked.
|
|
91
|
+
body, redactions = redact_text_report(body)
|
|
92
|
+
if redactions:
|
|
93
|
+
stats.setdefault("redactions", {})[rel] = redaction_receipt(
|
|
94
|
+
rel, redactions)["redactions"]
|
|
95
|
+
symbols = sorted({m for pat in SYMBOL_PATTERNS.get(p.suffix, [])
|
|
96
|
+
for m in pat.findall(body)
|
|
97
|
+
for m in (m if isinstance(m, tuple) else (m,))})
|
|
98
|
+
self.db.execute(
|
|
99
|
+
"INSERT OR REPLACE INTO files VALUES(?,?,?,?,?)",
|
|
100
|
+
(rel, sha, p.suffix.lstrip("."), p.stat().st_size,
|
|
101
|
+
" ".join(symbols)))
|
|
102
|
+
self.db.execute("DELETE FROM content WHERE path=?", (rel,))
|
|
103
|
+
self.db.execute("INSERT INTO content(path, body) VALUES(?,?)",
|
|
104
|
+
(rel, body))
|
|
105
|
+
stats["indexed"] += 1
|
|
106
|
+
stale = {r[0] for r in self.db.execute(
|
|
107
|
+
"SELECT path FROM files")} - seen
|
|
108
|
+
for rel in stale:
|
|
109
|
+
self.db.execute("DELETE FROM files WHERE path=?", (rel,))
|
|
110
|
+
self.db.execute("DELETE FROM content WHERE path=?", (rel,))
|
|
111
|
+
stats["removed"] += 1
|
|
112
|
+
self.db.commit()
|
|
113
|
+
return stats
|
|
114
|
+
|
|
115
|
+
def index_fact(self, fact_id: str, kind: str, location: str, doc: str) -> None:
|
|
116
|
+
self.db.execute("INSERT OR REPLACE INTO facts_idx VALUES(?,?,?,?)",
|
|
117
|
+
(fact_id, kind, location, doc))
|
|
118
|
+
self.db.commit()
|
|
119
|
+
|
|
120
|
+
# ── queries ───────────────────────────────────────────────
|
|
121
|
+
def search(self, query: str, limit: int = 20) -> list[dict[str, Any]]:
|
|
122
|
+
"""FTS5 literal/phrase search."""
|
|
123
|
+
try:
|
|
124
|
+
rows = self.db.execute(
|
|
125
|
+
"SELECT path, snippet(content,1,'«','»','…',12) FROM content "
|
|
126
|
+
"WHERE content MATCH ? ORDER BY rank LIMIT ?",
|
|
127
|
+
(query, limit)).fetchall()
|
|
128
|
+
except sqlite3.OperationalError:
|
|
129
|
+
return []
|
|
130
|
+
return [{"path": p, "snippet": s} for p, s in rows]
|
|
131
|
+
|
|
132
|
+
def search_regex(self, pattern: str, limit: int = 50) -> list[dict[str, Any]]:
|
|
133
|
+
rx = re.compile(pattern)
|
|
134
|
+
hits = []
|
|
135
|
+
for path, in self.db.execute("SELECT path FROM files"):
|
|
136
|
+
body_rows = self.db.execute(
|
|
137
|
+
"SELECT body FROM content WHERE path=?", (path,)).fetchall()
|
|
138
|
+
if body_rows and rx.search(body_rows[0][0] or ""):
|
|
139
|
+
hits.append({"path": path})
|
|
140
|
+
if len(hits) >= limit:
|
|
141
|
+
break
|
|
142
|
+
return hits
|
|
143
|
+
|
|
144
|
+
def symbol(self, name: str) -> list[dict[str, Any]]:
|
|
145
|
+
rows = self.db.execute(
|
|
146
|
+
"SELECT path, symbols FROM files WHERE symbols LIKE ?",
|
|
147
|
+
(f"%{name}%",)).fetchall()
|
|
148
|
+
return [{"path": p, "symbols": s.split() if s else []} for p, s in rows]
|
|
149
|
+
|
|
150
|
+
def by_path(self, glob: str) -> list[str]:
|
|
151
|
+
like = glob.replace("*", "%").replace("?", "_")
|
|
152
|
+
return [r[0] for r in self.db.execute(
|
|
153
|
+
"SELECT path FROM files WHERE path LIKE ?", (like,))]
|
|
154
|
+
|
|
155
|
+
def by_kind(self, kind: str) -> list[str]:
|
|
156
|
+
return [r[0] for r in self.db.execute(
|
|
157
|
+
"SELECT path FROM files WHERE kind=?", (kind,))]
|
|
158
|
+
|
|
159
|
+
def read(self, path: str) -> str | None:
|
|
160
|
+
row = self.db.execute(
|
|
161
|
+
"SELECT body FROM content WHERE path=?", (path,)).fetchone()
|
|
162
|
+
return row[0] if row else None
|
|
163
|
+
|
|
164
|
+
def stats(self) -> dict[str, int]:
|
|
165
|
+
files = self.db.execute("SELECT COUNT(*), COALESCE(SUM(bytes),0) "
|
|
166
|
+
"FROM files").fetchone()
|
|
167
|
+
return {"files": files[0], "bytes": files[1]}
|
|
168
|
+
|
|
169
|
+
def fingerprint(self) -> str:
|
|
170
|
+
return sha256_text(",".join(
|
|
171
|
+
f"{r[0]}:{r[1]}" for r in self.db.execute(
|
|
172
|
+
"SELECT path, sha256 FROM files ORDER BY path")))
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Token ledger — append-only JSONL accounting per run.
|
|
2
|
+
|
|
3
|
+
Records context requested/delivered/reused/skipped/compressed, provider tokens
|
|
4
|
+
when observed, estimates when estimated, cache hits/misses. Three honesty
|
|
5
|
+
states: observed | estimated | unknown.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import time
|
|
12
|
+
from dataclasses import asdict, dataclass, field
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class LedgerEntry:
|
|
19
|
+
"""§29 — v2 fields: requested/candidate/selected/delivered/reused/
|
|
20
|
+
cached/compressed/skipped + essential/optional split. Provider-side
|
|
21
|
+
tokens stay None unless a host transcript observes them."""
|
|
22
|
+
operation: str
|
|
23
|
+
context_requested: int = 0 # estimated tokens requested
|
|
24
|
+
context_candidate: int = 0 # ranked candidates before budget
|
|
25
|
+
context_selected: int = 0 # selected after ranking
|
|
26
|
+
context_delivered: int = 0
|
|
27
|
+
context_reused: int = 0 # served from cache/content-address
|
|
28
|
+
context_cached: int = 0
|
|
29
|
+
context_skipped: int = 0
|
|
30
|
+
context_compressed: int = 0
|
|
31
|
+
essential_tokens: int = 0 # evidence that must not be dropped
|
|
32
|
+
optional_tokens: int = 0
|
|
33
|
+
input_tokens: int | None = None # observed (provider transcript)
|
|
34
|
+
output_tokens: int | None = None
|
|
35
|
+
reasoning_tokens: int | None = None
|
|
36
|
+
cost_usd: float | None = None
|
|
37
|
+
input_tokens_est: int | None = None # estimated
|
|
38
|
+
output_tokens_est: int | None = None
|
|
39
|
+
cache_hits: int = 0
|
|
40
|
+
cache_misses: int = 0
|
|
41
|
+
# §42 — deterministic vs model split. Deterministic bytes are the local
|
|
42
|
+
# working set (never billed as model spend); model_context_* counts only
|
|
43
|
+
# the payload a model/agent receives. Provider counts stay None when no
|
|
44
|
+
# transcript observed them (`unknown`).
|
|
45
|
+
deterministic_input_bytes: int = 0
|
|
46
|
+
deterministic_fact_count: int = 0
|
|
47
|
+
model_context_bytes: int = 0
|
|
48
|
+
model_context_tokens: int | None = None
|
|
49
|
+
model_output_tokens: int | None = None
|
|
50
|
+
cache_tokens: int = 0
|
|
51
|
+
latency_ms: float = 0.0
|
|
52
|
+
token_basis: str = "unknown" # closed vocab: observed|estimated|unknown
|
|
53
|
+
# an "observed" row must bind to the transcript that observed it —
|
|
54
|
+
# otherwise the count is unfalsifiable
|
|
55
|
+
transcript_ref: str | None = None
|
|
56
|
+
ts: float = field(default_factory=time.time)
|
|
57
|
+
extra: dict[str, Any] = field(default_factory=dict)
|
|
58
|
+
|
|
59
|
+
_TOKEN_BASES = ("observed", "estimated", "unknown")
|
|
60
|
+
|
|
61
|
+
def __post_init__(self) -> None:
|
|
62
|
+
if self.token_basis not in self._TOKEN_BASES:
|
|
63
|
+
raise ValueError(
|
|
64
|
+
f"token_basis must be one of {self._TOKEN_BASES}, "
|
|
65
|
+
f"got {self.token_basis!r}")
|
|
66
|
+
observed = any(v is not None for v in
|
|
67
|
+
(self.input_tokens, self.output_tokens,
|
|
68
|
+
self.reasoning_tokens))
|
|
69
|
+
if observed and self.token_basis != "observed":
|
|
70
|
+
raise ValueError(
|
|
71
|
+
"provider token counts require token_basis='observed'")
|
|
72
|
+
if self.token_basis == "observed" and observed \
|
|
73
|
+
and not self.transcript_ref:
|
|
74
|
+
raise ValueError(
|
|
75
|
+
"observed token counts require transcript_ref — "
|
|
76
|
+
"unbindable numbers are fabricated")
|
|
77
|
+
|
|
78
|
+
def to_dict(self) -> dict[str, Any]:
|
|
79
|
+
return asdict(self)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class TokenLedger:
|
|
83
|
+
def __init__(self, root: str | Path):
|
|
84
|
+
self.dir = Path(root) / ".platformforge" / "ledger"
|
|
85
|
+
self.dir.mkdir(parents=True, exist_ok=True)
|
|
86
|
+
self.path = self.dir / "tokens.jsonl"
|
|
87
|
+
|
|
88
|
+
def record(self, entry: LedgerEntry) -> None:
|
|
89
|
+
with open(self.path, "a") as fh:
|
|
90
|
+
fh.write(json.dumps(entry.to_dict(), default=str) + "\n")
|
|
91
|
+
|
|
92
|
+
def entries(self) -> list[dict[str, Any]]:
|
|
93
|
+
if not self.path.exists():
|
|
94
|
+
return []
|
|
95
|
+
return [json.loads(l) for l in self.path.read_text().splitlines() if l.strip()]
|
|
96
|
+
|
|
97
|
+
def report(self) -> dict[str, Any]:
|
|
98
|
+
ents = self.entries()
|
|
99
|
+
tot = lambda k: sum(e.get(k) or 0 for e in ents)
|
|
100
|
+
hits, misses = tot("cache_hits"), tot("cache_misses")
|
|
101
|
+
return {
|
|
102
|
+
"operations": len(ents),
|
|
103
|
+
"context_requested": tot("context_requested"),
|
|
104
|
+
"context_delivered": tot("context_delivered"),
|
|
105
|
+
"context_reused": tot("context_reused"),
|
|
106
|
+
"context_skipped": tot("context_skipped"),
|
|
107
|
+
"context_compressed": tot("context_compressed"),
|
|
108
|
+
"cache_hit_ratio": (hits / (hits + misses)) if (hits + misses) else None,
|
|
109
|
+
"input_tokens_observed": tot("input_tokens"),
|
|
110
|
+
"output_tokens_observed": tot("output_tokens"),
|
|
111
|
+
"input_tokens_estimated": tot("input_tokens_est"),
|
|
112
|
+
"output_tokens_estimated": tot("output_tokens_est"),
|
|
113
|
+
"deterministic_input_bytes": tot("deterministic_input_bytes"),
|
|
114
|
+
"deterministic_fact_count": tot("deterministic_fact_count"),
|
|
115
|
+
"model_context_bytes": tot("model_context_bytes"),
|
|
116
|
+
"cache_tokens": tot("cache_tokens"),
|
|
117
|
+
"basis_counts": {
|
|
118
|
+
b: sum(1 for e in ents if e.get("token_basis") == b)
|
|
119
|
+
for b in ("observed", "estimated", "unknown")
|
|
120
|
+
},
|
|
121
|
+
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"""Context packs v2 — graph-aware, explainable, budget-deciding (§22–§28).
|
|
2
|
+
|
|
3
|
+
Pipeline (§23): task → intent terms → candidate files → changed artifacts →
|
|
4
|
+
graph seeds/neighborhood → rules → facts → historical findings → risk
|
|
5
|
+
modifiers → rank → dedup → budget → pack.
|
|
6
|
+
|
|
7
|
+
Every item carries `score` + `reasons` (§25). Items are classified
|
|
8
|
+
essential / high-value / optional / background (§27). If essential alone
|
|
9
|
+
exceeds the budget the pack returns `budget_decision: refuse` — essential
|
|
10
|
+
evidence is never dropped silently.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import re
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from platformforge.tokensave.budget import Budget
|
|
20
|
+
from platformforge.tokensave.estimate import estimate_tokens
|
|
21
|
+
from platformforge.tokensave.index import SearchIndex
|
|
22
|
+
from platformforge.tokensave.ledger import LedgerEntry, TokenLedger
|
|
23
|
+
|
|
24
|
+
WORD_RE = re.compile(r"[A-Za-z0-9_.\-/]{3,}")
|
|
25
|
+
|
|
26
|
+
ESSENTIAL = "essential" # findings, facts, rules, unresolved — never dropped
|
|
27
|
+
HIGH_VALUE = "high-value" # changed files, graph distance ≤ 1, risk-flagged
|
|
28
|
+
OPTIONAL = "optional"
|
|
29
|
+
BACKGROUND = "background"
|
|
30
|
+
|
|
31
|
+
# §24 — explainable scoring weights. Each contributes a named reason.
|
|
32
|
+
W_CHANGED = 10.0
|
|
33
|
+
W_TASK_TERM = 2.0
|
|
34
|
+
W_SYMBOL = 3.0
|
|
35
|
+
W_PATH_TERM = 4.0
|
|
36
|
+
W_GRAPH_DIST = {1: 6.0, 2: 3.0}
|
|
37
|
+
W_RISK = {"critical": 8.0, "high": 5.0, "medium": 2.0}
|
|
38
|
+
W_RULE = 4.0
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ContextPackBuilder:
|
|
42
|
+
def __init__(self, index: SearchIndex, ledger: TokenLedger | None = None):
|
|
43
|
+
self.index = index
|
|
44
|
+
self.ledger = ledger
|
|
45
|
+
|
|
46
|
+
def _task_terms(self, task: str) -> list[str]:
|
|
47
|
+
return [t.lower() for t in WORD_RE.findall(task)]
|
|
48
|
+
|
|
49
|
+
def _rank_files(self, terms: list[str], changed: list[str],
|
|
50
|
+
graph_nodes: list[str] | None,
|
|
51
|
+
rule_ids: list[str] | None,
|
|
52
|
+
risk: str | None,
|
|
53
|
+
limit: int = 50) -> list[dict[str, Any]]:
|
|
54
|
+
"""Ranked candidates with explainable reasons (§24–§25)."""
|
|
55
|
+
scores: dict[str, float] = {}
|
|
56
|
+
reasons: dict[str, list[str]] = {}
|
|
57
|
+
|
|
58
|
+
def bump(path: str, w: float, reason: str) -> None:
|
|
59
|
+
scores[path] = scores.get(path, 0.0) + w
|
|
60
|
+
reasons.setdefault(path, [])
|
|
61
|
+
if reason not in reasons[path]:
|
|
62
|
+
reasons[path].append(reason)
|
|
63
|
+
|
|
64
|
+
for path in changed:
|
|
65
|
+
bump(path, W_CHANGED, "changed-file")
|
|
66
|
+
for term in terms:
|
|
67
|
+
for hit in self.index.search(f'"{term}"', limit=20):
|
|
68
|
+
bump(hit["path"], W_TASK_TERM, f"task-term:{term}")
|
|
69
|
+
for hit in self.index.symbol(term):
|
|
70
|
+
bump(hit["path"], W_SYMBOL, f"symbol:{term}")
|
|
71
|
+
for path in self.index.by_path(f"*{term}*"):
|
|
72
|
+
bump(path, W_PATH_TERM, f"path:{term}")
|
|
73
|
+
for node in graph_nodes or []:
|
|
74
|
+
# graph seeds/neighbors: node label terms hit path ranking
|
|
75
|
+
depth = 1 if ":" not in node else int(node.split(":", 1)[1])
|
|
76
|
+
w = W_GRAPH_DIST.get(depth, 1.0)
|
|
77
|
+
label = node.split("/")[-1].split(":")[0]
|
|
78
|
+
for hit in self.index.search(f'"{label}"', limit=10):
|
|
79
|
+
bump(hit["path"], w, f"graph-distance:{depth}")
|
|
80
|
+
for path in self.index.by_path(f"*{label}*"):
|
|
81
|
+
bump(path, w, f"graph-distance:{depth}")
|
|
82
|
+
for rid in rule_ids or []:
|
|
83
|
+
for hit in self.index.search(f'"{rid}"', limit=5):
|
|
84
|
+
bump(hit["path"], W_RULE, f"rule:{rid}")
|
|
85
|
+
if risk:
|
|
86
|
+
for term in ("iam", "policy", "secret", "sg", "security"):
|
|
87
|
+
for hit in self.index.by_path(f"*{term}*"):
|
|
88
|
+
bump(hit, W_RISK.get(risk, 0.0), f"risk:{risk}")
|
|
89
|
+
ranked = sorted(scores.items(), key=lambda kv: (-kv[1], kv[0]))
|
|
90
|
+
return [{"path": p, "score": round(s, 2), "reasons": reasons[p]}
|
|
91
|
+
for p, s in ranked[:limit]]
|
|
92
|
+
|
|
93
|
+
def build(self, task: str, budget: Budget | None = None,
|
|
94
|
+
changed_files: list[str] | None = None,
|
|
95
|
+
facts: list[dict] | None = None,
|
|
96
|
+
findings: list[dict] | None = None,
|
|
97
|
+
rules: list[str] | None = None,
|
|
98
|
+
graph_neighborhood: list[dict] | None = None,
|
|
99
|
+
risk: str | None = None,
|
|
100
|
+
previous_pack_hash: str | None = None,
|
|
101
|
+
previous_files: dict[str, str] | None = None,
|
|
102
|
+
sources: list[str] | None = None,
|
|
103
|
+
allow_escalate: bool = False,
|
|
104
|
+
max_file_bytes: int = 20_000,
|
|
105
|
+
retain_content: bool = False) -> dict[str, Any]:
|
|
106
|
+
budget = budget or Budget()
|
|
107
|
+
changed = changed_files or []
|
|
108
|
+
node_terms = [
|
|
109
|
+
f"{n.get('node', '')}:{n.get('depth', '')}" if isinstance(n, dict)
|
|
110
|
+
else str(n) for n in (graph_neighborhood or [])]
|
|
111
|
+
ranked = self._rank_files(self._task_terms(task), changed,
|
|
112
|
+
node_terms, rules, risk)
|
|
113
|
+
seen_hash: set[str] = set()
|
|
114
|
+
|
|
115
|
+
files_out: list[dict[str, Any]] = []
|
|
116
|
+
symbols_out: list[str] = []
|
|
117
|
+
used_tokens = 0
|
|
118
|
+
skipped = 0
|
|
119
|
+
reused = 0
|
|
120
|
+
for cand in ranked:
|
|
121
|
+
path, score, c_reasons = cand["path"], cand["score"], cand["reasons"]
|
|
122
|
+
body = self.index.read(path)
|
|
123
|
+
if body is None:
|
|
124
|
+
continue
|
|
125
|
+
sha = self.index.db.execute(
|
|
126
|
+
"SELECT sha256 FROM files WHERE path=?", (path,)).fetchone()[0]
|
|
127
|
+
if sha in seen_hash:
|
|
128
|
+
skipped += 1
|
|
129
|
+
continue # dedup identical content
|
|
130
|
+
cls = HIGH_VALUE if ("changed-file" in c_reasons
|
|
131
|
+
or "graph-distance:1" in c_reasons) \
|
|
132
|
+
else OPTIONAL
|
|
133
|
+
truncated = body[:max_file_bytes]
|
|
134
|
+
tokens = estimate_tokens(truncated)
|
|
135
|
+
if budget.input_budget and used_tokens + tokens > budget.input_budget:
|
|
136
|
+
head = truncated[: len(truncated) // 4]
|
|
137
|
+
head_tokens = estimate_tokens(head)
|
|
138
|
+
if used_tokens + head_tokens <= budget.input_budget:
|
|
139
|
+
truncated, tokens = head + "\n…[truncated by budget]", head_tokens
|
|
140
|
+
elif cls == HIGH_VALUE and previous_pack_hash is None:
|
|
141
|
+
# high-value overflowing: degrade to optional head anyway
|
|
142
|
+
skipped += 1
|
|
143
|
+
continue
|
|
144
|
+
else:
|
|
145
|
+
skipped += 1
|
|
146
|
+
continue
|
|
147
|
+
seen_hash.add(sha)
|
|
148
|
+
# §26 — real delta accounting: identical sha as the previous
|
|
149
|
+
# pack means the file was reused, not re-read fresh.
|
|
150
|
+
if previous_files and previous_files.get(path) == sha:
|
|
151
|
+
reused += 1
|
|
152
|
+
used_tokens += tokens
|
|
153
|
+
entry = {"path": path, "score": score,
|
|
154
|
+
"reasons": c_reasons, "class": cls,
|
|
155
|
+
"tokens": tokens, "sha256": sha,
|
|
156
|
+
"delivered_bytes": len(truncated.encode()),
|
|
157
|
+
"delivered_chars": len(truncated)}
|
|
158
|
+
if retain_content:
|
|
159
|
+
# exact delivered payload — QPT measures what ships
|
|
160
|
+
entry["content"] = truncated
|
|
161
|
+
files_out.append(entry)
|
|
162
|
+
sym_row = self.index.db.execute(
|
|
163
|
+
"SELECT symbols FROM files WHERE path=?", (path,)).fetchone()
|
|
164
|
+
if sym_row and sym_row[0]:
|
|
165
|
+
symbols_out.extend(sym_row[0].split()[:8])
|
|
166
|
+
|
|
167
|
+
# §27–28 — essential evidence is facts/findings/rules/unresolved.
|
|
168
|
+
essential = {"facts": facts or [], "findings": findings or [],
|
|
169
|
+
"rules": rules or []}
|
|
170
|
+
essential_tokens = estimate_tokens(json.dumps(essential, default=str))
|
|
171
|
+
over_essential = bool(budget.input_budget
|
|
172
|
+
and essential_tokens > budget.input_budget)
|
|
173
|
+
if over_essential and allow_escalate:
|
|
174
|
+
decision = "escalate"
|
|
175
|
+
decision_reason = ("essential evidence exceeds input budget — "
|
|
176
|
+
"escalating beyond budget (caller allowed)")
|
|
177
|
+
elif over_essential:
|
|
178
|
+
decision = "refuse"
|
|
179
|
+
decision_reason = ("essential evidence exceeds input budget "
|
|
180
|
+
"— refusing rather than dropping evidence")
|
|
181
|
+
elif used_tokens > 0 and budget.input_budget \
|
|
182
|
+
and used_tokens + essential_tokens > budget.input_budget:
|
|
183
|
+
decision = "reduced_scope"
|
|
184
|
+
decision_reason = "optional items dropped to fit budget"
|
|
185
|
+
elif skipped:
|
|
186
|
+
decision = "reduced_scope"
|
|
187
|
+
decision_reason = "duplicates/overflow skipped"
|
|
188
|
+
else:
|
|
189
|
+
decision, decision_reason = "ok", "within budget"
|
|
190
|
+
|
|
191
|
+
pack = {
|
|
192
|
+
"task": {"description": task, "changed_files": changed},
|
|
193
|
+
"relevant_files": files_out,
|
|
194
|
+
"relevant_symbols": sorted(set(symbols_out))[:100],
|
|
195
|
+
"facts": facts or [],
|
|
196
|
+
"findings": findings or [],
|
|
197
|
+
"graph_neighborhood": graph_neighborhood or [],
|
|
198
|
+
"rules": rules or [],
|
|
199
|
+
"sources": sorted(set(sources or [])),
|
|
200
|
+
"security": {
|
|
201
|
+
# §21 contract field — redaction happens at index write
|
|
202
|
+
# (pre-FTS); packs never carry raw secrets downstream.
|
|
203
|
+
"redaction": "applied-at-index",
|
|
204
|
+
"secrets_in_pack": 0},
|
|
205
|
+
"budget": budget.to_dict(),
|
|
206
|
+
"budget_decision": decision,
|
|
207
|
+
"budget_reason": decision_reason,
|
|
208
|
+
"packed_files": len(files_out),
|
|
209
|
+
"packed_bytes": sum(f["delivered_bytes"] for f in files_out),
|
|
210
|
+
"est_input_tokens": used_tokens + essential_tokens,
|
|
211
|
+
"essential_tokens": essential_tokens,
|
|
212
|
+
"skipped_duplicates": skipped,
|
|
213
|
+
"delta_aware": previous_pack_hash is not None,
|
|
214
|
+
"reused_from_previous": reused,
|
|
215
|
+
}
|
|
216
|
+
if decision == "refuse":
|
|
217
|
+
pack["refusals"] = [{
|
|
218
|
+
"code": "PF-BUDGET-ESSENTIAL",
|
|
219
|
+
"reason": decision_reason,
|
|
220
|
+
"unlock": "raise --input-budget or narrow the task scope"}]
|
|
221
|
+
pack["relevant_files"] = []
|
|
222
|
+
else:
|
|
223
|
+
pack["refusals"] = []
|
|
224
|
+
|
|
225
|
+
if self.ledger:
|
|
226
|
+
self.ledger.record(LedgerEntry(
|
|
227
|
+
operation="context.pack",
|
|
228
|
+
context_requested=estimate_tokens(task) + sum(
|
|
229
|
+
estimate_tokens(self.index.read(c["path"]) or "")
|
|
230
|
+
for c in ranked),
|
|
231
|
+
context_candidate=sum(
|
|
232
|
+
estimate_tokens(self.index.read(c["path"]) or "")
|
|
233
|
+
for c in ranked),
|
|
234
|
+
context_selected=used_tokens,
|
|
235
|
+
context_delivered=used_tokens + essential_tokens,
|
|
236
|
+
context_skipped=skipped,
|
|
237
|
+
essential_tokens=essential_tokens,
|
|
238
|
+
optional_tokens=used_tokens,
|
|
239
|
+
input_tokens_est=used_tokens + essential_tokens,
|
|
240
|
+
token_basis="estimated",
|
|
241
|
+
extra={"budget_decision": decision},
|
|
242
|
+
))
|
|
243
|
+
return pack
|
|
244
|
+
|
|
245
|
+
def delta_for_change(self, changed_files: list[str],
|
|
246
|
+
budget: Budget | None = None,
|
|
247
|
+
graph_neighborhood: list[dict] | None = None,
|
|
248
|
+
previous_pack_hash: str | None = None,
|
|
249
|
+
risk: str | None = None) -> dict[str, Any]:
|
|
250
|
+
"""§26 — PR-style delta: diff → files → graph neighbors → rules →
|
|
251
|
+
pack. previous_pack_hash marks the delta (content-addressed dedup
|
|
252
|
+
already skips identical bodies)."""
|
|
253
|
+
return self.build(
|
|
254
|
+
task="review change", budget=budget, changed_files=changed_files,
|
|
255
|
+
graph_neighborhood=graph_neighborhood,
|
|
256
|
+
previous_pack_hash=previous_pack_hash, risk=risk)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def pack_to_text(pack: dict[str, Any], index: SearchIndex) -> str:
|
|
260
|
+
"""Render a pack for an agent prompt — file bodies bounded by what the
|
|
261
|
+
pack actually delivered (`delivered_bytes`), plus the essential
|
|
262
|
+
facts/findings/rules the pack carries. Never re-reads beyond the
|
|
263
|
+
pack's own truncation."""
|
|
264
|
+
out = [f"# task: {pack['task']['description']}"]
|
|
265
|
+
for f in pack["relevant_files"]:
|
|
266
|
+
path = f["path"] if isinstance(f, dict) else f
|
|
267
|
+
reasons = ""
|
|
268
|
+
content = None
|
|
269
|
+
if isinstance(f, dict):
|
|
270
|
+
reasons = f" # {', '.join(f.get('reasons', []))}"
|
|
271
|
+
content = f.get("content")
|
|
272
|
+
out.append(f"\n## {path}{reasons}")
|
|
273
|
+
if content is None:
|
|
274
|
+
body = index.read(path) or ""
|
|
275
|
+
delivered = f.get("delivered_chars") if isinstance(f, dict) \
|
|
276
|
+
else None
|
|
277
|
+
content = body[:delivered] if delivered else body[:4000]
|
|
278
|
+
out.append(content)
|
|
279
|
+
essential = {"facts": pack.get("facts") or [],
|
|
280
|
+
"findings": pack.get("findings") or [],
|
|
281
|
+
"rules": pack.get("rules") or []}
|
|
282
|
+
if any(essential.values()):
|
|
283
|
+
out.append("\n## essential-evidence")
|
|
284
|
+
out.append(json.dumps(essential, default=str)[:8000])
|
|
285
|
+
return "\n".join(out)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""platform-forge vendored Forge UI Kit — stdlib-only interactive terminal layer.
|
|
2
|
+
|
|
3
|
+
Zero dependencies, cross-platform (msvcrt on Windows, termios on
|
|
4
|
+
POSIX). Every interaction has a headless equivalent so automation never
|
|
5
|
+
blocks on a TTY that isn't there.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from platformforge.ui.i18n import t
|
|
9
|
+
from platformforge.ui.kit import (
|
|
10
|
+
UIContext,
|
|
11
|
+
confirm,
|
|
12
|
+
dashboard,
|
|
13
|
+
multi_select,
|
|
14
|
+
prompt,
|
|
15
|
+
select,
|
|
16
|
+
status_table,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"UIContext",
|
|
21
|
+
"confirm",
|
|
22
|
+
"dashboard",
|
|
23
|
+
"multi_select",
|
|
24
|
+
"prompt",
|
|
25
|
+
"select",
|
|
26
|
+
"status_table",
|
|
27
|
+
"t",
|
|
28
|
+
]
|