diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0ec04dd..93308a4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,3 +11,21 @@ jobs: - run: python -m pytest -q - run: python -m pip wheel . --no-deps --wheel-dir dist - run: PYTHONPATH=src python -m living_context.cli --root . validate + - name: Compile warm-start constraint intelligence + run: | + python scripts/compile_constraint_corpus.py \ + --input data/constraint_seed_signals.json \ + --taxonomy config/constraint_taxonomy.json + test -s artifacts/constraint-intelligence.json + test -s artifacts/constraint-leaderboard.json + test -s artifacts/constraint-intelligence.md + - name: Upload warm-start intelligence + uses: actions/upload-artifact@v4 + with: + name: warm-start-constraint-intelligence + path: | + artifacts/constraint-intelligence.json + artifacts/constraint-leaderboard.json + artifacts/constraint-intelligence.md + if-no-files-found: error + retention-days: 14 diff --git a/.github/workflows/github-review-queue.yml b/.github/workflows/github-review-queue.yml new file mode 100644 index 0000000..5ce3120 --- /dev/null +++ b/.github/workflows/github-review-queue.yml @@ -0,0 +1,78 @@ +name: Build constraint intelligence queue + +on: + # The workflow is introduced by the constraint-intelligence PR itself, so the + # first authoritative live run occurs on the canonical main merge SHA. + push: + branches: + - main + pull_request: + workflow_dispatch: + inputs: + created_after: + description: Earliest issue creation date (YYYY-MM-DD) + required: false + default: "2026-05-01" + min_score: + description: Minimum discovery qualification score + required: false + default: "45" + schedule: + - cron: "17 5 * * 1,4" + +# GitHub issue search has a secondary abuse limiter independent of the normal +# request quota. Serialize this external-discovery workflow so PR, scheduled, +# and manual runs cannot create avoidable concurrent search bursts. +concurrency: + group: constraint-intelligence-${{ github.repository }} + cancel-in-progress: false + +permissions: + contents: read + +jobs: + discover-and-compile: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install test runner + run: python -m pip install pytest + + - name: Run pipeline tests + run: python -m pytest tests/test_github_issue_discovery.py tests/test_constraint_intelligence.py -q + + - name: Discover high-signal engineering pain + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CREATED_AFTER: ${{ inputs.created_after || '2026-05-01' }} + MIN_SCORE: ${{ inputs.min_score || '45' }} + run: | + python scripts/discover_github_issues.py \ + --config config/constraint_signal_queries.json \ + --created-after "$CREATED_AFTER" \ + --min-score "$MIN_SCORE" + + - name: Compile accumulated and fresh constraint intelligence + run: | + python scripts/compile_constraint_corpus.py \ + --input data/constraint_seed_signals.json \ + --input artifacts/github-review-queue.json \ + --taxonomy config/constraint_taxonomy.json + + - name: Upload intelligence artifacts + uses: actions/upload-artifact@v4 + with: + name: constraint-intelligence + path: | + artifacts/github-review-queue.json + artifacts/github-review-queue.md + artifacts/constraint-intelligence.json + artifacts/constraint-leaderboard.json + artifacts/constraint-intelligence.md + if-no-files-found: error + retention-days: 14 diff --git a/README.md b/README.md index d019bef..91faf5b 100644 --- a/README.md +++ b/README.md @@ -66,4 +66,35 @@ Project deletion is an explicit destructive operator action. The HTTP service is Classification is deterministic and intentionally conservative. A context pack is a source-linked retrieval artifact, not a completeness guarantee, autonomous memory, or decision-maker. +## Constraint intelligence pipeline + +The repository includes a human-reviewed engineering-pain discovery and constraint-intelligence pipeline. GitHub is treated as a sensor network: strong market evidence is preserved even when a thread is a poor public-comment target. + +```bash +python scripts/discover_github_issues.py \ + --config config/constraint_signal_queries.json + +python scripts/build_constraint_intelligence.py \ + --input artifacts/github-review-queue.json \ + --taxonomy config/constraint_taxonomy.json +``` + +The compiler keeps two decisions separate: + +- **constraint value** — production evidence, economic consequence, workaround burden, recurrence, cross-system generality, buyer proximity, serviceability, proof feasibility, and urgency; +- **intervention value** — whether this occurrence deserves corpus-only retention, watching, a diagnostic question, a precision comment, a reproduction, a patch/diagnostic, or operator assistance. + +Default public action is **no comment**. The system is designed to move repeated evidence from discovery toward diagnostics, reproductions, patches, audits, operator validation, and reusable product primitives rather than optimize comment count. + +Generated artifacts: + +- `artifacts/github-review-queue.json` / `.md` — evidence-preserving discovery queue; +- `artifacts/constraint-intelligence.json` — signal-level scores and recommended interventions; +- `artifacts/constraint-leaderboard.json` — ranked constraint families; +- `artifacts/constraint-intelligence.md` — human operating report. + +See [`docs/CONSTRAINT_INTELLIGENCE_OPERATING_SYSTEM.md`](docs/CONSTRAINT_INTELLIGENCE_OPERATING_SYSTEM.md) for the exploration/exploitation gates, proof ladder, public contribution policy, daily/weekly loops, success metrics, and provisional audit offers. + +The original inspection details remain in [`docs/GITHUB_REVIEW_PIPELINE.md`](docs/GITHUB_REVIEW_PIPELINE.md). + See [`docs/INTEGRATION.md`](docs/INTEGRATION.md), [`docs/API.md`](docs/API.md), and [`DEPLOYABILITY_DISTRIBUTION.md`](DEPLOYABILITY_DISTRIBUTION.md). diff --git a/config/constraint_signal_queries.json b/config/constraint_signal_queries.json new file mode 100644 index 0000000..05ef5ea --- /dev/null +++ b/config/constraint_signal_queries.json @@ -0,0 +1,132 @@ +{ + "queries": [ + { + "id": "runtime-truth-and-recovery", + "terms": [ + "manual restart", + "stuck running", + "silent failure", + "not registered", + "workflow active", + "published but", + "readiness", + "liveness", + "reconnect" + ], + "exclude_labels": ["duplicate", "invalid", "good first issue"], + "help_angle": "Look for declared state diverging from actual serving state. Preserve as evidence by default; interact only if a concrete liveness/reconciliation gap remains unresolved." + }, + { + "id": "side-effect-integrity", + "terms": [ + "duplicate execution", + "sent twice", + "duplicate message", + "partial delivery", + "retry duplicate", + "false 500", + "already committed", + "idempotency", + "timeout retry" + ], + "exclude_labels": ["duplicate", "invalid", "good first issue"], + "help_angle": "Identify the exact operation identity and ambiguous-outcome boundary. Prefer reproduction/attempt tracing over generic idempotency advice." + }, + { + "id": "identity-and-credentials", + "terms": [ + "anonymous caller", + "authorization header", + "credential works", + "credential missing", + "user_id none", + "wrong user", + "request context lost", + "external secrets", + "oauth refresh" + ], + "exclude_labels": ["duplicate", "invalid", "good first issue"], + "help_angle": "Trace the effective principal/credential at the exact production execution boundary and compare it with the test/configuration surface." + }, + { + "id": "persistence-resume-and-replay", + "terms": [ + "resume fails", + "after restart", + "stale run", + "checkpoint", + "suspended run", + "paused run", + "replay duplicate", + "rehydrate", + "persisted state" + ], + "exclude_labels": ["duplicate", "invalid", "good first issue"], + "help_angle": "Determine authoritative run state, generation/revision identity, and whether restart/replay can create duplicate or stale continuation." + }, + { + "id": "delivery-and-queue-assurance", + "terms": [ + "never delivered", + "never sent", + "queued but", + "queue stuck", + "missed webhook", + "missed email", + "trigger stopped", + "message lost", + "partial delivery" + ], + "exclude_labels": ["duplicate", "invalid", "good first issue"], + "help_angle": "Separate internal completion from downstream delivery. Look for durable receipts, claim/dispatch state, partial outcomes, and reconciliation." + }, + { + "id": "state-amplification-and-resource-pressure", + "terms": [ + "out of memory", + "OOM", + "memory leak", + "swap", + "snapshot size", + "history size", + "redis memory", + "payload too large", + "manual restart" + ], + "exclude_labels": ["duplicate", "invalid", "good first issue"], + "help_angle": "Look for state retained or copied disproportionately across persistence/replay boundaries and quantify the amplification factor." + }, + { + "id": "configuration-and-fidelity", + "terms": [ + "config ignored", + "environment variable ignored", + "setting lost", + "deserialize", + "serialization", + "external secrets", + "works in test", + "works in editor", + "fails in production" + ], + "exclude_labels": ["duplicate", "invalid", "good first issue"], + "help_angle": "Compare declared/saved configuration with the effective runtime configuration and preserve provenance of what actually executed." + }, + { + "id": "buyer-cost-and-workarounds", + "terms": [ + "production outage", + "manual workaround", + "daily restart", + "rolled back", + "downgrade", + "hours debugging", + "days debugging", + "customer impact", + "enterprise production" + ], + "exclude_labels": ["duplicate", "invalid", "good first issue"], + "help_angle": "Prioritize measurable operator cost and buyer proximity. This query is for corpus enrichment even when no public contribution is appropriate." + } + ] +} diff --git a/config/constraint_taxonomy.json b/config/constraint_taxonomy.json new file mode 100644 index 0000000..52d06fb --- /dev/null +++ b/config/constraint_taxonomy.json @@ -0,0 +1,106 @@ +{ + "version": 1, + "saturation_states": [ + "emerging", + "confirmed", + "saturated", + "validating", + "serviceable", + "proven", + "productizable", + "commoditized", + "deprioritized" + ], + "constraints": [ + { + "id": "operational-truth-divergence", + "name": "Operational truth divergence", + "description": "Configured, persisted, or control-plane state says a capability is healthy/active while the serving runtime is not actually performing it.", + "keywords": ["published", "active", "healthy", "ready", "liveness", "readiness", "not registered", "dead trigger", "stuck running", "silent failure", "manual restart", "reconnect"], + "economic_effects": ["missed events", "downtime", "manual monitoring", "incident response", "false operator confidence"], + "buyer_personas": ["automation engineer", "platform engineer", "devops", "technical founder"], + "interventions": ["runtime receipt", "active probe", "reconciliation", "degraded state", "watchdog"] + }, + { + "id": "side-effect-integrity", + "name": "Ambiguous or duplicate side effects", + "description": "Retries, timeouts, replay, or fan-out make externally visible actions duplicate or leave their outcome ambiguous.", + "keywords": ["duplicate", "sent twice", "multiple sends", "retry", "timeout", "idempotency", "false 500", "already committed", "replay", "double", "partial delivery"], + "economic_effects": ["duplicate customer communication", "duplicate charges", "duplicate records", "manual reconciliation"], + "buyer_personas": ["automation engineer", "operations engineer", "platform engineer", "technical founder"], + "interventions": ["idempotency envelope", "operation receipt", "attempt ledger", "external receipt reconciliation"] + }, + { + "id": "identity-credential-continuity", + "name": "Identity and credential continuity", + "description": "A user, tenant, credential, secret, scope, or request principal is lost or changed across execution surfaces.", + "keywords": ["credential", "authorization", "unauthorized", "anonymous caller", "user_id", "tenant", "request context", "secret", "oauth", "token", "principal", "401"], + "economic_effects": ["failed business action", "wrong-tenant action", "manual reauthentication", "security exposure"], + "buyer_personas": ["platform engineer", "security engineer", "automation engineer"], + "interventions": ["principal envelope", "credential-path trace", "scope verification", "context continuity test"] + }, + { + "id": "durable-execution-correctness", + "name": "Persistence and resume correctness", + "description": "Runtime, persisted, restored, or resumed execution state disagree about the same logical run.", + "keywords": ["resume", "restart", "paused", "suspended", "checkpoint", "rehydrate", "persisted", "stale run", "continue", "fork", "snapshot"], + "economic_effects": ["stranded work", "duplicate work", "wrong continuation", "manual recovery"], + "buyer_personas": ["platform engineer", "ai engineer", "automation engineer"], + "interventions": ["resume generation", "compare-and-swap", "storage-authoritative recovery", "run lineage"] + }, + { + "id": "delivery-assurance", + "name": "Silent delivery failure", + "description": "The internal operation completes or appears successful while the intended downstream delivery is partial, lost, or unclaimed.", + "keywords": ["not delivered", "never delivered", "partial delivery", "rejected", "queued", "never sent", "missing message", "webhook", "email", "discord", "sms"], + "economic_effects": ["lost customer communication", "missed SLA", "unprocessed events", "manual resend"], + "buyer_personas": ["operations engineer", "automation engineer", "support engineering"], + "interventions": ["delivery receipt", "outbox", "partial outcome semantics", "queue reconciliation"] + }, + { + "id": "state-amplification", + "name": "State amplification and resource pressure", + "description": "Persisted, serialized, replayed, or retained state grows disproportionately and causes memory, storage, transport, or runtime failure.", + "keywords": ["oom", "out of memory", "memory", "gigabyte", "snapshot", "4mb", "grpc", "history", "redis", "state size", "swap", "leak"], + "economic_effects": ["crashes", "latency", "infrastructure cost", "manual restart"], + "buyer_personas": ["platform engineer", "sre", "devops"], + "interventions": ["bounded retention", "claim check", "by-reference state", "pressure budget"] + }, + { + "id": "configuration-authority-drift", + "name": "Configuration authority drift", + "description": "Saved, displayed, cached, or reconstructed configuration differs from the configuration actually used by the runtime.", + "keywords": ["configuration", "config", "environment", "env var", "serialized", "deserialize", "external secrets", "model config", "ignored setting", "lost setting"], + "economic_effects": ["production mismatch", "failed deployment", "manual rollback", "unexpected behavior"], + "buyer_personas": ["platform engineer", "devops", "automation engineer"], + "interventions": ["effective-config fingerprint", "round-trip fidelity test", "configuration provenance"] + }, + { + "id": "evidence-freshness", + "name": "Evidence freshness and stale projections", + "description": "A status, verification, health result, or derived projection remains authoritative after the underlying evidence has changed.", + "keywords": ["stale", "freshness", "verification", "old state", "cached", "outdated", "projection", "observed_at", "last success", "last failure"], + "economic_effects": ["false confidence", "wrong automation decision", "repeated work", "missed failure"], + "buyer_personas": ["platform engineer", "sre", "automation engineer"], + "interventions": ["evidence receipt", "freshness TTL", "source revision", "reconciliation"] + }, + { + "id": "tenant-state-isolation", + "name": "Tenant and request state isolation", + "description": "State, memory, mutable objects, or routing identity crosses tenant, user, request, or session boundaries.", + "keywords": ["cross-user", "cross user", "wrong user", "tenant", "shared state", "same key", "collision", "leak", "request isolation", "session id"], + "economic_effects": ["data leakage", "wrong-user action", "security incident", "corrupted personalization"], + "buyer_personas": ["security engineer", "platform engineer", "ai engineer"], + "interventions": ["canonical scoped identity", "ownership validation", "request-generation isolation"] + }, + { + "id": "provider-adapter-fidelity", + "name": "Provider and adapter fidelity", + "description": "Provider-specific event, ordering, correlation, or serialization semantics are lost at an adapter boundary.", + "keywords": ["provider", "adapter", "schema", "event shape", "index", "ordering", "correlation", "compatibility", "api changed", "unknown event"], + "economic_effects": ["silent wrong output", "integration outage", "manual compatibility work", "incorrect mapping"], + "buyer_personas": ["integration engineer", "platform engineer", "ai engineer"], + "interventions": ["normalization boundary", "correlation identity", "versioned fixtures", "conformance test"] + } + ] +} diff --git a/config/github_issue_queries.json b/config/github_issue_queries.json new file mode 100644 index 0000000..c517278 --- /dev/null +++ b/config/github_issue_queries.json @@ -0,0 +1,52 @@ +{ + "queries": [ + { + "id": "context-loss-and-resume", + "terms": [ + "losing context", + "resume work", + "context handoff", + "stale context", + "project memory" + ], + "exclude_labels": ["duplicate", "invalid", "good first issue"], + "help_angle": "Show a source-linked handoff/resume pattern using deterministic extraction and a compact context pack. Mention LCE only if the issue needs durable project memory rather than chat memory." + }, + { + "id": "decision-provenance", + "terms": [ + "decision log", + "decision provenance", + "audit trail", + "source line", + "traceable decisions" + ], + "exclude_labels": ["duplicate", "invalid", "good first issue"], + "help_angle": "Offer a minimal decision-record schema preserving source path, line, timestamp, and content hash; explain how this prevents unsupported summaries." + }, + { + "id": "operational-state-drift", + "terms": [ + "state drift", + "project status stale", + "blocked by missing context", + "handoff failure", + "context synchronization" + ], + "exclude_labels": ["duplicate", "invalid", "good first issue"], + "help_angle": "Identify the missing state boundary, propose deterministic re-ingestion and replacement semantics, and distinguish searchable evidence from inferred state." + }, + { + "id": "local-first-operational-memory", + "terms": [ + "local first memory", + "offline project memory", + "sqlite knowledge base", + "local context store", + "no cloud knowledge base" + ], + "exclude_labels": ["duplicate", "invalid", "good first issue"], + "help_angle": "Provide a local SQLite plus CLI/API architecture and the smallest ingestion/query loop that addresses the issue without requiring a hosted service." + } + ] +} diff --git a/data/constraint_seed_signals.json b/data/constraint_seed_signals.json new file mode 100644 index 0000000..bb3f12e --- /dev/null +++ b/data/constraint_seed_signals.json @@ -0,0 +1,381 @@ +[ + { + "signal_id": "github:n8n-io/n8n#30871", + "source_platform": "github", + "source_kind": "observed_summary", + "repository": "n8n-io/n8n", + "number": 30871, + "url": "https://github.com/n8n-io/n8n/issues/30871", + "title": "Published IMAP trigger can silently die and require full restart", + "body": "Production workflow remains Published while the IMAP trigger has no live socket, no reconnect, no error workflow, and incoming email remains unprocessed until a full container restart.", + "production": true, + "customer_visible": true, + "workaround": "full container manual restart", + "operational_consequence": "incoming business email events accumulate without processing", + "economic_consequence": "missed SLA and manual monitoring", + "unresolved": true, + "contribution_gap": false + }, + { + "signal_id": "github:n8n-io/n8n#34115", + "source_platform": "github", + "source_kind": "observed_summary", + "repository": "n8n-io/n8n", + "number": 34115, + "url": "https://github.com/n8n-io/n8n/issues/34115", + "title": "Postgres connection stayed degraded for about 69 hours until restart", + "body": "Production n8n lost its database connection, readiness stayed failed for about 69 hours while liveness remained healthy, and a manual restart restored service within minutes.", + "production": true, + "workaround": "manual restart after 69 hours", + "operational_consequence": "workflow plane unavailable while process appeared alive", + "economic_consequence": "multi-day downtime and operator intervention", + "unresolved": true, + "contribution_gap": false + }, + { + "signal_id": "github:n8n-io/n8n#34936", + "source_platform": "github", + "source_kind": "observed_summary", + "repository": "n8n-io/n8n", + "number": 34936, + "url": "https://github.com/n8n-io/n8n/issues/34936", + "title": "Active production webhook returns not registered", + "body": "Workflow is active and persisted webhook records exist, but the advertised production webhook route is not registered in the runtime router and returns 404.", + "production": true, + "customer_visible": true, + "operational_consequence": "business webhook traffic is rejected despite declared active state", + "economic_consequence": "lost events and false operator confidence", + "unresolved": true, + "contribution_gap": false + }, + { + "signal_id": "github:n8n-io/n8n#35143", + "source_platform": "github", + "source_kind": "observed_summary", + "repository": "n8n-io/n8n", + "number": 35143, + "url": "https://github.com/n8n-io/n8n/issues/35143", + "title": "Google credential test passes but production writes are anonymous", + "body": "The same service-account credential works for reads and passes credential testing, but write requests go out with no Authorization header and Google reports Anonymous caller 401.", + "production": true, + "customer_visible": true, + "operational_consequence": "configured writes fail only on the production execution path", + "economic_consequence": "failed business actions and misdirected IAM debugging", + "unresolved": true, + "contribution_gap": false + }, + { + "signal_id": "github:n8n-io/n8n#35081", + "source_platform": "github", + "source_kind": "observed_summary", + "repository": "n8n-io/n8n", + "number": 35081, + "url": "https://github.com/n8n-io/n8n/issues/35081", + "title": "One workflow execution produces about ten WhatsApp sends", + "body": "The execution log shows one execution while the same customer-visible WhatsApp side effect is delivered about ten times, suggesting fan-out, re-entry, retry, or transport duplication inside one execution boundary.", + "production": true, + "customer_visible": true, + "operational_consequence": "one logical action creates multiple external sends", + "economic_consequence": "duplicate customer communication and trust damage", + "unresolved": true, + "contribution_gap": false + }, + { + "signal_id": "github:n8n-io/n8n#35839", + "source_platform": "github", + "source_kind": "observed_summary", + "repository": "n8n-io/n8n", + "number": 35839, + "url": "https://github.com/n8n-io/n8n/issues/35839", + "title": "SMTP partial delivery reported as successful execution", + "body": "SMTP returns accepted and rejected recipients with a concrete rejection error, but the node reports success. Partial delivery is collapsed into a successful workflow state.", + "production": true, + "customer_visible": true, + "operational_consequence": "downstream automation cannot distinguish full delivery from partial delivery", + "economic_consequence": "silent loss of intended customer communication", + "unresolved": true, + "contribution_gap": false + }, + { + "signal_id": "github:n8n-io/n8n#34723", + "source_platform": "github", + "source_kind": "observed_summary", + "repository": "n8n-io/n8n", + "number": 34723, + "url": "https://github.com/n8n-io/n8n/issues/34723", + "title": "Recurring first-of-day worker cohort crashes in production", + "body": "Three similar scheduled workflows crash together on roughly 60% of first heavy runs of the day, while identical runs three hours later succeed. The behavior persisted for months and points to shared runtime or memory pressure.", + "production": true, + "frequency": 6, + "workaround": "later rerun succeeds; operator investigation required", + "operational_consequence": "scheduled production work fails as a correlated cohort", + "economic_consequence": "missed automation windows and manual recovery", + "unresolved": true, + "contribution_gap": false + }, + { + "signal_id": "github:NousResearch/hermes-agent#80274", + "source_platform": "github", + "source_kind": "observed_summary", + "repository": "NousResearch/hermes-agent", + "number": 80274, + "url": "https://github.com/NousResearch/hermes-agent/issues/80274", + "title": "Green verification repeated dozens of times without freshness identity", + "body": "The same successful verification was repeated around 38 times because proof had no command/revision/dirty-state freshness receipt tied to current source state.", + "production": false, + "operational_consequence": "agent repeatedly spends work re-verifying evidence it already has", + "economic_consequence": "wasted compute and engineering time", + "unresolved": true, + "contribution_gap": false + }, + { + "signal_id": "github:NousResearch/hermes-agent#81091", + "source_platform": "github", + "source_kind": "observed_summary", + "repository": "NousResearch/hermes-agent", + "number": 81091, + "url": "https://github.com/NousResearch/hermes-agent/issues/81091", + "title": "Successful long-running turn never delivered to Discord", + "body": "A roughly 52-minute successful turn completed internally but the final message was never delivered to Discord, exposing a missing durable outbox/delivery receipt boundary.", + "production": true, + "customer_visible": true, + "operational_consequence": "completed work is invisible to the user", + "economic_consequence": "lost response and repeated work", + "unresolved": true, + "contribution_gap": false + }, + { + "signal_id": "github:NousResearch/hermes-agent#81114", + "source_platform": "github", + "source_kind": "observed_summary", + "repository": "NousResearch/hermes-agent", + "number": 81114, + "url": "https://github.com/NousResearch/hermes-agent/issues/81114", + "title": "Completed background tasks remain shown as running for hours", + "body": "Background process completion is not reconciled into control-plane lifecycle, so completed tasks remain stuck running for hours.", + "production": true, + "operational_consequence": "operators cannot trust task status", + "economic_consequence": "manual investigation and incorrect recovery decisions", + "unresolved": true, + "contribution_gap": false + }, + { + "signal_id": "github:NousResearch/hermes-agent#81050", + "source_platform": "github", + "source_kind": "observed_summary", + "repository": "NousResearch/hermes-agent", + "number": 81050, + "url": "https://github.com/NousResearch/hermes-agent/issues/81050", + "title": "Removed MCP configuration resurrects after restart", + "body": "Disabled or removed MCP configuration returns after restart because orphan token/metadata artifacts can recreate runtime configuration identity.", + "production": true, + "workaround": "manual cleanup and restart", + "operational_consequence": "runtime uses configuration the operator explicitly removed", + "economic_consequence": "unexpected integrations and manual recovery", + "unresolved": true, + "contribution_gap": false + }, + { + "signal_id": "github:NousResearch/hermes-agent#80388", + "source_platform": "github", + "source_kind": "observed_summary", + "repository": "NousResearch/hermes-agent", + "number": 80388, + "url": "https://github.com/NousResearch/hermes-agent/issues/80388", + "title": "Memory status remains healthy while every write fails", + "body": "The control plane stayed green for about 42 hours while every memory write failed, including 14 observed failures. Readiness and operational health were conflated.", + "production": true, + "operational_consequence": "operators trust a memory capability that is not retaining new state", + "economic_consequence": "silent loss of expected operational memory and delayed detection", + "unresolved": true, + "contribution_gap": false + }, + { + "signal_id": "github:agno-agi/agno#9319", + "source_platform": "github", + "source_kind": "observed_summary", + "repository": "agno-agi/agno", + "number": 9319, + "url": "https://github.com/agno-agi/agno/issues/9319", + "title": "User-private memory identity can collide across users", + "body": "The memory key excludes user identity, allowing cross-user overwrite or retrieval. The same logical key is not scoped by tenant/user ownership.", + "production": true, + "customer_visible": true, + "operational_consequence": "wrong user state can overwrite or surface in another session", + "economic_consequence": "data isolation and security exposure", + "unresolved": true, + "contribution_gap": false + }, + { + "signal_id": "github:agno-agi/agno#9447", + "source_platform": "github", + "source_kind": "observed_summary", + "repository": "agno-agi/agno", + "number": 9447, + "url": "https://github.com/agno-agi/agno/issues/9447", + "title": "Stale paused run can override terminal durable state", + "body": "A stale caller-held paused run object can override stored COMPLETED or CANCELLED state and execute an approved side effect twice because durable state is not reloaded/fenced before continuation.", + "production": true, + "customer_visible": true, + "operational_consequence": "stale continuation authorizes a new transition after terminal completion", + "economic_consequence": "duplicate side effects and corrupted execution history", + "unresolved": true, + "contribution_gap": false + }, + { + "signal_id": "github:agno-agi/agno#9288", + "source_platform": "github", + "source_kind": "observed_summary", + "repository": "agno-agi/agno", + "number": 9288, + "url": "https://github.com/agno-agi/agno/issues/9288", + "title": "HITL confirmation loses user identity", + "body": "After human approval, resumed RunContext user_id can become None, so authentication, tenant routing, and tools may execute without the original principal.", + "production": true, + "customer_visible": true, + "operational_consequence": "approved continuation runs under incomplete identity context", + "economic_consequence": "failed or wrong-tenant business actions", + "unresolved": true, + "contribution_gap": false + }, + { + "signal_id": "github:mastra-ai/mastra#20570", + "source_platform": "github", + "source_kind": "observed_summary", + "repository": "mastra-ai/mastra", + "number": 20570, + "url": "https://github.com/mastra-ai/mastra/issues/20570", + "title": "Structured-output retry leaves result object from rejected attempt", + "body": "A successful retry updates result text but result object remains derived from the rejected first attempt, allowing invalid output to be exposed as successful state.", + "production": true, + "customer_visible": true, + "operational_consequence": "public result projections disagree about which attempt was accepted", + "economic_consequence": "invalid customer-facing output can silently ship", + "unresolved": true, + "contribution_gap": false + }, + { + "signal_id": "github:mastra-ai/mastra#20600", + "source_platform": "github", + "source_kind": "observed_summary", + "repository": "mastra-ai/mastra", + "number": 20600, + "url": "https://github.com/mastra-ai/mastra/issues/20600", + "title": "Persisted suspended run cannot resume after process restart", + "body": "Storage still contains the suspended run, but resume refuses after restart because a process-local in-memory registry is empty. Process-local state becomes a correctness boundary over durable state.", + "production": true, + "workaround": "same-process resume only or manual recovery", + "operational_consequence": "durably suspended work becomes stranded after restart", + "economic_consequence": "failed recovery and operator intervention", + "unresolved": true, + "contribution_gap": false + }, + { + "signal_id": "github:mastra-ai/mastra#20670", + "source_platform": "github", + "source_kind": "observed_summary", + "repository": "mastra-ai/mastra", + "number": 20670, + "url": "https://github.com/mastra-ai/mastra/issues/20670", + "title": "Nested HITL resume targets wrong continuation and loses context", + "body": "Nested human-in-the-loop resume can target the wrong step, drop request context, and swallow dispatch failure because suspension identity is not bound to an exact generation/leaf continuation.", + "production": true, + "customer_visible": true, + "operational_consequence": "human approval may resume the wrong logical work or lose authorization context", + "economic_consequence": "incorrect side effects and manual recovery", + "unresolved": true, + "contribution_gap": false + }, + { + "signal_id": "github:mastra-ai/mastra#20747", + "source_platform": "github", + "source_kind": "observed_summary", + "repository": "mastra-ai/mastra", + "number": 20747, + "url": "https://github.com/mastra-ai/mastra/issues/20747", + "title": "Small conversation amplifies into very large durable state", + "body": "About 300 KB of conversation expands to roughly 135 MB disk and 1.2 GB Redis because full state is rewritten repeatedly. Approval persistence can take around 15.5 seconds.", + "production": true, + "operational_consequence": "state amplification creates storage, latency, and memory pressure", + "economic_consequence": "infrastructure cost and failed/slow approvals", + "unresolved": true, + "contribution_gap": true + }, + { + "signal_id": "github:pydantic/pydantic-ai#6976", + "source_platform": "github", + "source_kind": "observed_summary", + "repository": "pydantic/pydantic-ai", + "number": 6976, + "url": "https://github.com/pydantic/pydantic-ai/issues/6976", + "title": "Temporal fan-out serializes large dependency state into every activity", + "body": "A roughly 200 KB dependency object is copied into each fan-out activity; 20 calls exceed the 4 MiB gRPC limit and permanently bloat workflow history.", + "production": true, + "operational_consequence": "durable workflow payload crosses transport limits and history stays bloated", + "economic_consequence": "failed orchestration and excess storage/transport cost", + "unresolved": true, + "contribution_gap": false + }, + { + "signal_id": "github:pydantic/pydantic-ai#6974", + "source_platform": "github", + "source_kind": "observed_summary", + "repository": "pydantic/pydantic-ai", + "number": 6974, + "url": "https://github.com/pydantic/pydantic-ai/issues/6974", + "title": "Temporal replay re-emits telemetry as new external work", + "body": "Workflow replay reconstructs historical execution, but telemetry can treat reconstructed spans as new observations, double-counting usage or effects that did not happen again.", + "production": true, + "operational_consequence": "replay creates duplicate evidence of external work", + "economic_consequence": "incorrect usage/cost accounting and misleading observability", + "unresolved": true, + "contribution_gap": false + }, + { + "signal_id": "github:getpaseo/paseo#2673", + "source_platform": "github", + "source_kind": "observed_summary", + "repository": "getpaseo/paseo", + "number": 2673, + "url": "https://github.com/getpaseo/paseo/issues/2673", + "title": "Initial prompt timeout creates duplicate paid agents", + "body": "Client timeout clears pending creation identity although an agent may already exist, so retry can create 3 or more duplicate paid agents that each run for many minutes.", + "production": true, + "customer_visible": true, + "operational_consequence": "one logical create request can produce multiple active agents", + "economic_consequence": "duplicate LLM spend and manual cleanup", + "unresolved": true, + "contribution_gap": false + }, + { + "signal_id": "github:getpaseo/paseo#2829", + "source_platform": "github", + "source_kind": "observed_summary", + "repository": "getpaseo/paseo", + "number": 2829, + "url": "https://github.com/getpaseo/paseo/issues/2829", + "title": "Idle agents retain full process trees and pressure host memory", + "body": "Idle agents retain Codex and MCP process trees, contributing to multiple GiB of swap use and OOM pressure instead of separating logical lifecycle from runtime residency.", + "production": true, + "workaround": "manual process or host management", + "operational_consequence": "inactive logical agents continue consuming runtime resources", + "economic_consequence": "memory pressure, OOM risk, and infrastructure cost", + "unresolved": true, + "contribution_gap": false + }, + { + "signal_id": "github:run-llama/llama_index#22248", + "source_platform": "github", + "source_kind": "observed_summary", + "repository": "run-llama/llama_index", + "number": 22248, + "url": "https://github.com/run-llama/llama_index/issues/22248", + "title": "Formatted prompt projection can remain stale after underlying state changes", + "body": "State revision and formatted-state revision can diverge, leaving a derived prompt/context representation stale after source mutation.", + "production": true, + "operational_consequence": "runtime may act on outdated derived context", + "economic_consequence": "incorrect model decisions and repeated debugging", + "unresolved": true, + "contribution_gap": false + } +] diff --git a/docs/CONSTRAINT_INTELLIGENCE_OPERATING_SYSTEM.md b/docs/CONSTRAINT_INTELLIGENCE_OPERATING_SYSTEM.md new file mode 100644 index 0000000..b017b4c --- /dev/null +++ b/docs/CONSTRAINT_INTELLIGENCE_OPERATING_SYSTEM.md @@ -0,0 +1,275 @@ +# Constraint Intelligence Operating System + +This document replaces comment-count optimization with a constraint-first operating model. + +## Mission + +Continuously detect expensive recurring operational constraints, determine which are commercially serviceable, create concrete proof that we can solve them, and convert the strongest constraints into repeatable services, tools, products, and market intelligence. + +GitHub is a sensor network, not the product and not the scoreboard. + +## Canonical loop + +```text +DISCOVER + -> NORMALIZE SIGNAL + -> EXTRACT CONSTRAINT + -> MERGE WITH EVIDENCE FAMILY + -> SCORE CONSTRAINT VALUE + -> SCORE INTERVENTION VALUE + -> ROUTE ACTION + -> BUILD PROOF + -> MAP OFFER / PRODUCT + -> VALIDATE WITH OPERATOR + -> RECORD OUTCOME + -> UPDATE POLICY +``` + +## Two independent scores + +### Constraint value + +How economically and strategically important is the underlying pain? + +Weighted inputs: + +- production evidence: 18% +- economic consequence: 16% +- workaround burden: 14% +- recurrence: 12% +- cross-system generality: 12% +- buyer proximity: 10% +- serviceability: 8% +- proof feasibility: 6% +- urgency: 4% + +Penalties: + +- commodity/known solution +- already solved upstream +- weak evidence + +### Intervention value + +What should we do about this particular occurrence? + +A valuable constraint may still be a bad public-comment target because the thread is saturated, a fix is in progress, the issue is solved, or there is no contribution gap. + +Intervention ladder: + +```text +corpus_only +watch +ask_diagnostic_question +precision_comment +build_reproduction +build_patch_or_diagnostic +operator_assistance_or_offer +``` + +Default public action: **no comment**. + +## Constraint states + +```text +emerging +confirmed +saturated +validating +serviceable +proven +productizable +commoditized +deprioritized +``` + +The compiler currently promotes repeated strong evidence automatically from `emerging` to `confirmed` and `validating`. Later states require outcome evidence, not just more reports. + +## Exploration -> exploitation gates + +Continue exploration when any of these are true: + +- fewer than 3 independent observations +- mechanism remains uncertain +- buyer is unclear +- severity/economic consequence is unclear +- no cross-system evidence exists + +Begin exploitation when all of these are substantially true: + +- at least 3 independent production examples +- the same invariant appears in at least 2 systems or materially different deployments +- an economic consequence is visible +- an identifiable buyer/operator exists +- a plausible intervention can be delivered without owning the upstream platform + +Prioritize productization only when: + +- at least 5 strong observations exist +- at least 2 successful interventions exist +- the same diagnostic/fix is reused +- a buyer is willing to pay or repeatedly adopt it +- the intervention can be standardized + +## Proof ladder + +```text +0 Evidence +1 Mechanism +2 Diagnostic +3 Reproduction +4 Mitigation +5 Patch +6 Reusable primitive +7 Productized check +8 Commercial proof +9 Repeatability +``` + +Most discovery work should not stop at levels 0-2. The highest-value constraint families should be deliberately pushed toward levels 4-8. + +## Current root constraint taxonomy + +1. Operational truth divergence +2. Ambiguous or duplicate side effects +3. Identity and credential continuity +4. Persistence and resume correctness +5. Silent delivery failure +6. State amplification and resource pressure +7. Configuration authority drift +8. Evidence freshness and stale projections +9. Tenant and request state isolation +10. Provider and adapter fidelity + +The taxonomy is versioned in `config/constraint_taxonomy.json`. Create a new family only when evidence does not fit an existing root constraint without distorting it. + +## Mandatory economic chain + +For every high-value signal, preserve this path: + +```text +technical failure + -> workflow/operational consequence + -> economic consequence + -> buyer pain + -> current compensation/workaround +``` + +If a credible chain cannot be constructed, lower commercial priority even if the bug is technically interesting. + +## Public contribution gate + +Only comment publicly when at least one of these is true: + +- we can add a new causal discriminator +- we can provide a reproducible test +- we can identify missing evidence that materially narrows the search +- we can point to a precise implementation boundary +- we can provide a patch or failing test +- we can prevent a correctness/safety failure not already identified +- the operator has measurable production pain and the response creates a concrete path to resolution + +Do not post generic invariant checklists into already well-diagnosed threads. + +## Daily operation + +1. Discover 50-200 candidate objects. +2. Filter to high-signal observations. +3. Normalize into the signal corpus. +4. Run `build_constraint_intelligence.py`. +5. Review the constraint leaderboard. +6. Select at most a small number of high intervention-value items for public interaction. +7. Spend the majority of expert effort on reproduction, code inspection, diagnostics, patches, or operator assistance. +8. Record outcomes. + +Example: + +```bash +python scripts/discover_github_issues.py \ + --config config/github_issue_queries.json + +python scripts/build_constraint_intelligence.py \ + --input artifacts/github-review-queue.json +``` + +Outputs: + +- `artifacts/constraint-intelligence.json` — signal-level intelligence +- `artifacts/constraint-leaderboard.json` — family-level ranking +- `artifacts/constraint-intelligence.md` — human review report + +## Weekly operation + +1. Rank constraint families. +2. Promote/demote saturation states using actual evidence. +3. Choose one constraint of the week. +4. Produce one concrete artifact: diagnostic, repro, patch, probe, audit template, or operator deliverable. +5. Run a buyer/operator validation test. +6. Record technical and commercial outcomes. +7. Update the targeting policy. + +## Success metrics + +### Intelligence + +- strong signals retained +- independent confirmations +- cross-repo confirmations +- economic consequences captured +- constraints promoted from emerging -> confirmed + +### Technical + +- reproductions +- diagnostics +- patches/PRs +- reusable primitives +- prevented or resolved incidents + +### Engagement + +- substantive replies +- reporter follow-ups +- maintainer follow-ups +- requests for help + +### Commercial + +- operator conversations +- audits offered +- audits accepted +- paid engagements +- conversion by constraint family + +### Product + +- recurring checks +- reusable scripts +- repeated implementation patterns +- cross-platform applicability + +Comment count is not a primary KPI. + +## Provisional exploitation priorities + +### 1. Runtime / operational truth + +Potential deliverable: **Automation Runtime Truth Audit** + +Verify that active triggers, schedules, webhook routes, queues, dependencies, and health status are backed by current serving evidence. + +### 2. Side-effect integrity + +Potential deliverable: **Side-Effect Integrity Audit** + +Inventory consequential actions and test retry, timeout-after-commit, redelivery, duplicate scheduling, replay, external idempotency, and reconciliation behavior. + +### 3. Identity / credential continuity + +Potential deliverable: **Execution Identity & Credential Continuity Audit** + +Verify that tenant, user, credential, scopes, secrets, and request context remain correct across interactive, scheduled, webhook, queue, background, HITL, retry, resume, and subworkflow execution. + +## Architectural boundary + +This intelligence layer does not change LCE's evidence claims. LCE remains a deterministic local-first operational-memory compiler with source-linked records and keyword-overlap query. Constraint classification and scoring in this pipeline are heuristic operational analysis; they are not semantic-RAG or autonomous-memory claims. diff --git a/docs/GITHUB_REVIEW_PIPELINE.md b/docs/GITHUB_REVIEW_PIPELINE.md new file mode 100644 index 0000000..86f73b0 --- /dev/null +++ b/docs/GITHUB_REVIEW_PIPELINE.md @@ -0,0 +1,114 @@ +# GitHub inspection and review pipeline + +The objective is not to broadcast Living Context Engine into unrelated threads. The objective is to find active engineering problems where LCE's implemented mechanisms are directly relevant, contribute a useful answer, and create a legitimate reason for an engineer to inspect the repository. + +## Conversion path + +```text +LCE capability + -> precise pain language + -> live open GitHub issue + -> deterministic qualification + -> human technical review + -> value-first response + -> optional contextual repository reference + -> repository inspection + -> useful feedback, issue, PR, or star +``` + +A star is a downstream signal, not the primary action. The controllable target is a qualified repository inspection by a relevant engineer. + +## Run locally + +```bash +python scripts/discover_github_issues.py +``` + +Optional controls: + +```bash +python scripts/discover_github_issues.py \ + --created-after 2026-05-01 \ + --per-query 30 \ + --min-score 60 +``` + +Outputs: + +- `artifacts/github-review-queue.json` for processing and measurement; +- `artifacts/github-review-queue.md` for human review. + +The scheduled GitHub Action runs twice weekly and uploads both files as a workflow artifact. It does not post comments. + +## Qualification model + +The score is deliberately simple and inspectable: + +| Component | Maximum | What it represents | +|---|---:|---| +| Relevance | 40 | Exact LCE capability language found in the issue | +| Intent | 25 | The author is actively seeking a fix, workaround, or architecture | +| Recency | 15 | The problem is current enough to engage | +| Validation | 10 | Other humans have engaged with the issue | +| Spam penalty | -20 | Bot, stale, duplicate, invalid, or beginner-task noise | + +The score is a queueing heuristic, not evidence that LCE solves the issue. + +## Mandatory human gate + +Before responding to any issue: + +1. Read the complete issue and all comments. +2. Confirm the problem remains unresolved. +3. Inspect linked code, logs, reproductions, and maintainer guidance. +4. Write the useful part first: diagnosis, workaround, schema, or runnable example. +5. Remove the LCE reference if it is not necessary to the answer. +6. Never repeat the same promotional wording across repositories. + +## Value-first response structure + +Use this order: + +1. **Observed failure:** restate the concrete mechanism, not the product category. +2. **Immediate help:** provide a specific fix, data shape, command sequence, or diagnostic. +3. **Boundary:** state what the proposed fix does not guarantee. +4. **Optional reference:** mention LCE only when its implemented behavior directly covers the recurring pattern. +5. **Verification request:** ask whether the proposed mechanism matches the maintainer's constraints. + +Example skeleton: + +```text +The failure looks less like "missing AI memory" and more like replacement without source provenance: the resumed state cannot show which file/line produced each claim. + +A minimal fix is to persist records as {project, source_path, source_line, observed_at, content_hash, kind, text}, replace records only within the same project/source boundary, and build the handoff from those records rather than from a free-form summary. + +That gives traceability, but it does not prove the context pack is complete or make decisions autonomously. + +I maintain Living Context Engine, which implements this exact local-first pattern with SQLite, source-linked records, CLI queries, context packs, and a read-only API. The schema/approach above is usable independently of the project. +``` + +## Inspection readiness gate + +Do not increase outreach volume until the repository passes these checks: + +- the README communicates the mechanism and evidence boundary in under two minutes; +- activation works from a clean Python environment; +- examples produce inspectable output; +- tests pass on the default branch; +- repository topics and description use the same pain language as the query matrix; +- issue templates invite reproducible failures and integration requests; +- the first contribution path is explicit; +- generated outreach can be traced to a query, issue, reviewer decision, and outcome. + +## Weekly operating target + +Start with a small controlled batch: + +- review the top 10 queue entries; +- select at most 3 where direct technical help is possible; +- post no more than 3 fully individualized responses; +- ask 2 relevant engineers for blunt repository inspection; +- record inspections, substantive replies, issues opened, PRs, and stars; +- change query terms only from observed false positives and real conversations. + +This prevents optimizing for low-quality impressions while the repository and message are still being calibrated. diff --git a/scripts/build_constraint_intelligence.py b/scripts/build_constraint_intelligence.py new file mode 100644 index 0000000..0394ab7 --- /dev/null +++ b/scripts/build_constraint_intelligence.py @@ -0,0 +1,478 @@ +#!/usr/bin/env python3 +"""Compile discovered engineering pain into constraint intelligence. + +The input is a JSON list of signal-like records. Records may come directly from +GitHub discovery or from manually enriched observations. The compiler keeps +constraint value separate from intervention value so a strong market signal does +not automatically become a public-comment target. + +Uses only the Python standard library and never publishes externally. +""" + +from __future__ import annotations + +import argparse +import json +from collections import Counter, defaultdict +from dataclasses import asdict, dataclass +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + + +@dataclass(frozen=True) +class ConstraintMatch: + constraint_id: str + constraint_name: str + match_score: int + matched_terms: list[str] + + +@dataclass(frozen=True) +class IntelligenceRecord: + signal_id: str + source_platform: str + repository: str + number: int | None + url: str + title: str + author: str + observed_at: str + production_evidence: int + economic_consequence: int + workaround_burden: int + recurrence: int + cross_system_generality: int + buyer_proximity: int + serviceability: int + proof_feasibility: int + urgency: int + commodity_penalty: int + solved_penalty: int + weak_evidence_penalty: int + constraint_value: int + intervention_value: int + intervention: str + constraint_id: str + constraint_name: str + constraint_match_score: int + constraint_terms: list[str] + economic_chain: list[str] + saturation_state: str + reason: str + + +def load_json(path: Path) -> Any: + try: + return json.loads(path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as exc: + raise SystemExit(f"Could not load {path}: {exc}") from exc + + +def clamp(value: int, low: int = 0, high: int = 100) -> int: + return max(low, min(high, value)) + + +def text_of(record: dict[str, Any]) -> str: + chunks = [ + str(record.get("title", "")), + str(record.get("body", "")), + str(record.get("reason", "")), + str(record.get("draft_angle", "")), + str(record.get("symptom", "")), + str(record.get("actual", "")), + str(record.get("expected", "")), + str(record.get("workaround", "")), + str(record.get("impact", "")), + str(record.get("environment", "")), + ] + return "\n".join(chunks).lower() + + +def signal_id(record: dict[str, Any], index: int) -> str: + repository = str(record.get("repository", "unknown")) + number = record.get("number") + if number is not None: + return f"github:{repository}#{number}" + return str(record.get("signal_id") or f"signal:{index}") + + +def classify_constraint(record: dict[str, Any], taxonomy: dict[str, Any]) -> ConstraintMatch: + text = text_of(record) + best: tuple[int, dict[str, Any], list[str]] | None = None + for constraint in taxonomy.get("constraints", []): + keywords = [str(k).lower() for k in constraint.get("keywords", [])] + matched = sorted({keyword for keyword in keywords if keyword in text}) + score = min(100, 18 * len(matched)) + if best is None or score > best[0]: + best = (score, constraint, matched) + if best is None or best[0] == 0: + return ConstraintMatch("unclassified", "Unclassified", 0, []) + return ConstraintMatch( + str(best[1]["id"]), + str(best[1]["name"]), + best[0], + best[2], + ) + + +def marker_score(text: str, weighted_markers: tuple[tuple[str, int], ...], cap: int) -> int: + return min(cap, sum(weight for marker, weight in weighted_markers if marker in text)) + + +def score_production(record: dict[str, Any], text: str) -> int: + explicit = record.get("production") + if explicit is True: + return 100 + if explicit is False: + return 20 + score = marker_score(text, ( + ("production", 35), ("enterprise", 20), ("self-hosted", 15), + ("kubernetes", 15), ("queue mode", 15), ("customer", 10), + ("workflow", 5), ("scheduled", 8), ("incident", 10), + ), 100) + return max(25, score) + + +def score_economic(record: dict[str, Any], text: str) -> int: + score = marker_score(text, ( + ("data loss", 30), ("duplicate", 22), ("downtime", 25), + ("customer", 15), ("revenue", 25), ("manual restart", 20), + ("manual re-auth", 20), ("manual", 8), ("69 hours", 25), + ("hours", 8), ("days", 12), ("missed", 15), ("lost", 15), + ("unauthorized", 12), ("wrong user", 25), ("security", 25), + ("rollback", 18), ("100000", 15), ("2000 workflows", 20), + ), 100) + if record.get("customer_visible") is True: + score += 15 + return clamp(max(20, score)) + + +def score_workaround(record: dict[str, Any], text: str) -> int: + score = marker_score(text, ( + ("manual restart", 35), ("restart", 15), ("re-auth", 30), + ("downgrade", 25), ("rollback", 25), ("workaround", 15), + ("daily", 18), ("every day", 18), ("manually", 12), + ("only a full", 25), ("no workaround", 30), + ), 100) + return max(15, score) + + +def score_recurrence(record: dict[str, Any], text: str) -> int: + if isinstance(record.get("frequency"), int): + return clamp(int(record["frequency"]) * 15, 10, 100) + score = marker_score(text, ( + ("intermittent", 20), ("recurring", 30), ("consistently", 25), + ("every", 15), ("daily", 25), ("multiple", 18), ("again", 10), + ("months", 25), ("weeks", 15), ("60%", 25), + ), 100) + return max(15, score) + + +def score_buyer(record: dict[str, Any], text: str) -> int: + score = marker_score(text, ( + ("enterprise", 25), ("production", 20), ("our team", 15), + ("our instance", 12), ("our pod", 12), ("our workflow", 8), + ("self-hosted", 10), ("kubernetes", 10), ("customer", 10), + ), 100) + author_role = str(record.get("author_role", "")).lower() + if author_role in {"maintainer", "founder", "platform engineer", "sre", "devops", "automation engineer"}: + score += 25 + return clamp(max(15, score)) + + +def score_serviceability(record: dict[str, Any], text: str) -> int: + score = 35 + score += marker_score(text, ( + ("reproduce", 12), ("logs", 10), ("trace", 10), ("debug", 8), + ("workaround", 8), ("configuration", 6), ("request", 5), + ("postgres", 8), ("redis", 8), ("webhook", 8), ("credential", 8), + ), 45) + if record.get("requires_platform_ownership") is True: + score -= 30 + return clamp(score) + + +def score_proof(record: dict[str, Any], text: str) -> int: + score = 25 + marker_score(text, ( + ("steps to reproduce", 20), ("reproduce", 12), ("minimal", 12), + ("exact", 8), ("curl", 10), ("screenshot", 6), ("logs", 8), + ("execution id", 10), ("debug info", 8), ("same credential", 8), + ), 65) + return clamp(score) + + +def score_urgency(record: dict[str, Any], text: str) -> int: + score = marker_score(text, ( + ("production", 20), ("customer", 15), ("blocked", 15), + ("data loss", 30), ("security", 25), ("manual restart", 15), + ("cannot", 8), ("unusable", 15), ("indefinitely", 12), + ), 100) + return max(15, score) + + +def penalty(record: dict[str, Any], text: str) -> tuple[int, int, int]: + commodity = 20 if any(m in text for m in ("known workaround", "documented solution", "configuration mistake")) else 0 + solved = 35 if any(m in text for m in ("fixed in", "merged pr", "resolved by", "already fixed")) else 0 + weak = 20 if any(m in text for m in ("no reproduction", "cannot reproduce", "maybe", "unclear")) else 0 + if record.get("resolved") is True: + solved = max(solved, 35) + return commodity, solved, weak + + +def constraint_value(scores: dict[str, int], penalties: tuple[int, int, int]) -> int: + value = ( + 0.18 * scores["production"] + + 0.16 * scores["economic"] + + 0.14 * scores["workaround"] + + 0.12 * scores["recurrence"] + + 0.12 * scores["generality"] + + 0.10 * scores["buyer"] + + 0.08 * scores["serviceability"] + + 0.06 * scores["proof"] + + 0.04 * scores["urgency"] + ) + return clamp(round(value - sum(penalties))) + + +def intervention_value(record: dict[str, Any], constraint_score: int, value: int) -> tuple[int, str]: + comments = int(record.get("comments", 0) or 0) + saturated = bool(record.get("saturated")) or comments >= 12 + fix_in_progress = bool(record.get("fix_in_progress")) + unresolved = record.get("unresolved", True) is not False + has_gap = bool(record.get("contribution_gap", True)) + + score = value + if saturated: + score -= 30 + if fix_in_progress: + score -= 30 + if not unresolved: + score -= 40 + if not has_gap: + score -= 25 + if constraint_score == 0: + score -= 10 + score = clamp(score) + + if score < 30: + action = "corpus_only" + elif score < 45: + action = "watch" + elif score < 60: + action = "ask_diagnostic_question" + elif score < 72: + action = "precision_comment" + elif score < 82: + action = "build_reproduction" + elif score < 90: + action = "build_patch_or_diagnostic" + else: + action = "operator_assistance_or_offer" + return score, action + + +def economic_chain(record: dict[str, Any], match: ConstraintMatch) -> list[str]: + symptom = str(record.get("symptom") or record.get("title") or "technical failure") + workaround = str(record.get("workaround") or "manual detection/recovery") + return [ + symptom, + f"constraint: {match.constraint_name}", + str(record.get("operational_consequence") or "workflow outcome becomes unreliable"), + str(record.get("economic_consequence") or "operator time, downtime, or customer impact"), + f"current compensation: {workaround}", + ] + + +def compile_record(record: dict[str, Any], taxonomy: dict[str, Any], index: int, generality: int) -> IntelligenceRecord: + text = text_of(record) + match = classify_constraint(record, taxonomy) + scores = { + "production": score_production(record, text), + "economic": score_economic(record, text), + "workaround": score_workaround(record, text), + "recurrence": score_recurrence(record, text), + "generality": generality, + "buyer": score_buyer(record, text), + "serviceability": score_serviceability(record, text), + "proof": score_proof(record, text), + "urgency": score_urgency(record, text), + } + penalties = penalty(record, text) + value = constraint_value(scores, penalties) + ivalue, action = intervention_value(record, match.match_score, value) + observed_at = str(record.get("observed_at") or datetime.now(timezone.utc).isoformat()) + repository = str(record.get("repository", "")) + number = record.get("number") + reason = ( + f"constraint={match.constraint_id}:{match.match_score}; value={value}; " + f"production={scores['production']}, economic={scores['economic']}, " + f"workaround={scores['workaround']}, recurrence={scores['recurrence']}, " + f"buyer={scores['buyer']}; intervention={action}:{ivalue}." + ) + return IntelligenceRecord( + signal_id=signal_id(record, index), + source_platform=str(record.get("source_platform", "github")), + repository=repository, + number=int(number) if number is not None else None, + url=str(record.get("url", "")), + title=str(record.get("title", "")), + author=str(record.get("author", "")), + observed_at=observed_at, + production_evidence=scores["production"], + economic_consequence=scores["economic"], + workaround_burden=scores["workaround"], + recurrence=scores["recurrence"], + cross_system_generality=scores["generality"], + buyer_proximity=scores["buyer"], + serviceability=scores["serviceability"], + proof_feasibility=scores["proof"], + urgency=scores["urgency"], + commodity_penalty=penalties[0], + solved_penalty=penalties[1], + weak_evidence_penalty=penalties[2], + constraint_value=value, + intervention_value=ivalue, + intervention=action, + constraint_id=match.constraint_id, + constraint_name=match.constraint_name, + constraint_match_score=match.match_score, + constraint_terms=match.matched_terms, + economic_chain=economic_chain(record, match), + saturation_state="emerging", + reason=reason, + ) + + +def apply_constraint_states(records: list[IntelligenceRecord]) -> list[IntelligenceRecord]: + by_constraint: dict[str, list[IntelligenceRecord]] = defaultdict(list) + for record in records: + by_constraint[record.constraint_id].append(record) + + result: list[IntelligenceRecord] = [] + for record in records: + family = by_constraint[record.constraint_id] + repos = {item.repository for item in family if item.repository} + strong = [item for item in family if item.constraint_value >= 65] + if record.constraint_id == "unclassified": + state = "emerging" + elif len(strong) >= 5 and len(repos) >= 2: + state = "validating" + elif len(strong) >= 3: + state = "confirmed" + else: + state = "emerging" + data = asdict(record) + data["saturation_state"] = state + result.append(IntelligenceRecord(**data)) + return result + + +def generality_map(raw: list[dict[str, Any]], taxonomy: dict[str, Any]) -> dict[str, int]: + repos_by_constraint: dict[str, set[str]] = defaultdict(set) + for record in raw: + match = classify_constraint(record, taxonomy) + repository = str(record.get("repository", "")) + if repository: + repos_by_constraint[match.constraint_id].add(repository) + return { + constraint_id: clamp(25 + 18 * max(0, len(repos) - 1), 25, 100) + for constraint_id, repos in repos_by_constraint.items() + } + + +def summarize_constraints(records: list[IntelligenceRecord]) -> list[dict[str, Any]]: + grouped: dict[str, list[IntelligenceRecord]] = defaultdict(list) + for record in records: + grouped[record.constraint_id].append(record) + rows: list[dict[str, Any]] = [] + for cid, family in grouped.items(): + repos = sorted({item.repository for item in family if item.repository}) + actions = Counter(item.intervention for item in family) + rows.append({ + "constraint_id": cid, + "constraint_name": family[0].constraint_name, + "signals": len(family), + "repositories": repos, + "repository_count": len(repos), + "avg_constraint_value": round(sum(item.constraint_value for item in family) / len(family), 1), + "max_constraint_value": max(item.constraint_value for item in family), + "state": family[0].saturation_state, + "interventions": dict(actions), + "top_signals": [ + {"signal_id": item.signal_id, "title": item.title, "value": item.constraint_value, "intervention": item.intervention} + for item in sorted(family, key=lambda x: (-x.constraint_value, -x.intervention_value))[:5] + ], + }) + return sorted(rows, key=lambda row: (-row["avg_constraint_value"], -row["signals"])) + + +def render_markdown(records: list[IntelligenceRecord], constraints: list[dict[str, Any]]) -> str: + lines = [ + "# Constraint Intelligence Report", + "", + f"Generated: {datetime.now(timezone.utc).isoformat()}", + "", + "> Default public action is no comment. Constraint value and intervention value are intentionally independent.", + "", + "## Constraint leaderboard", + "", + ] + for index, row in enumerate(constraints, 1): + lines.extend([ + f"### {index}. {row['constraint_name']} — avg {row['avg_constraint_value']}/100", + f"- State: `{row['state']}`", + f"- Signals: {row['signals']} across {row['repository_count']} repositories", + f"- Repositories: {', '.join(row['repositories']) or 'unknown'}", + f"- Intervention mix: {row['interventions']}", + "", + ]) + lines.extend(["## Highest-leverage signals", ""]) + for record in sorted(records, key=lambda x: (-x.constraint_value, -x.intervention_value))[:30]: + target = f"[{record.repository} #{record.number}]({record.url})" if record.url else record.signal_id + lines.extend([ + f"### {target} — constraint {record.constraint_value}/100; intervention {record.intervention_value}/100", + f"**{record.title}**", + f"- Constraint: `{record.constraint_id}` / {record.constraint_name}", + f"- State: `{record.saturation_state}`", + f"- Recommended action: `{record.intervention}`", + f"- Reason: {record.reason}", + f"- Economic chain: {' -> '.join(record.economic_chain)}", + "", + ]) + return "\n".join(lines) + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--input", type=Path, default=Path("artifacts/github-review-queue.json")) + parser.add_argument("--taxonomy", type=Path, default=Path("config/constraint_taxonomy.json")) + parser.add_argument("--output", type=Path, default=Path("artifacts/constraint-intelligence.json")) + parser.add_argument("--constraints", type=Path, default=Path("artifacts/constraint-leaderboard.json")) + parser.add_argument("--markdown", type=Path, default=Path("artifacts/constraint-intelligence.md")) + args = parser.parse_args() + + raw = load_json(args.input) + taxonomy = load_json(args.taxonomy) + if not isinstance(raw, list): + raise SystemExit("Input must be a JSON list") + + gmap = generality_map(raw, taxonomy) + compiled = [ + compile_record(record, taxonomy, index, gmap.get(classify_constraint(record, taxonomy).constraint_id, 25)) + for index, record in enumerate(raw, 1) + if isinstance(record, dict) + ] + compiled = apply_constraint_states(compiled) + constraints = summarize_constraints(compiled) + + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps([asdict(item) for item in compiled], indent=2) + "\n", encoding="utf-8") + args.constraints.write_text(json.dumps(constraints, indent=2) + "\n", encoding="utf-8") + args.markdown.write_text(render_markdown(compiled, constraints), encoding="utf-8") + print(f"Compiled {len(compiled)} signals into {len(constraints)} constraint families") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/compile_constraint_corpus.py b/scripts/compile_constraint_corpus.py new file mode 100644 index 0000000..e804409 --- /dev/null +++ b/scripts/compile_constraint_corpus.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +"""Merge multiple signal lists, deduplicate them, then run constraint intelligence.""" + +from __future__ import annotations + +import argparse +import json +import subprocess +import sys +import tempfile +from pathlib import Path +from typing import Any + + +def load_list(path: Path) -> list[dict[str, Any]]: + try: + data = json.loads(path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as exc: + raise SystemExit(f"Could not load {path}: {exc}") from exc + if not isinstance(data, list): + raise SystemExit(f"{path} must contain a JSON list") + return [item for item in data if isinstance(item, dict)] + + +def identity(record: dict[str, Any], index: int) -> str: + if record.get("signal_id"): + return str(record["signal_id"]) + repository = str(record.get("repository", "")) + number = record.get("number") + if repository and number is not None: + return f"github:{repository}#{number}" + if record.get("url"): + return str(record["url"]) + return f"anonymous:{index}:{record.get('title', '')}" + + +def merge(inputs: list[Path]) -> list[dict[str, Any]]: + merged: dict[str, dict[str, Any]] = {} + counter = 0 + for path in inputs: + for record in load_list(path): + counter += 1 + key = identity(record, counter) + if key not in merged: + merged[key] = record + continue + # Later inputs enrich earlier seed evidence without erasing populated fields. + combined = dict(merged[key]) + for field, value in record.items(): + if value not in (None, "", [], {}): + combined[field] = value + merged[key] = combined + return list(merged.values()) + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--input", type=Path, action="append", required=True) + parser.add_argument("--taxonomy", type=Path, default=Path("config/constraint_taxonomy.json")) + parser.add_argument("--output", type=Path, default=Path("artifacts/constraint-intelligence.json")) + parser.add_argument("--constraints", type=Path, default=Path("artifacts/constraint-leaderboard.json")) + parser.add_argument("--markdown", type=Path, default=Path("artifacts/constraint-intelligence.md")) + args = parser.parse_args() + + corpus = merge(args.input) + with tempfile.NamedTemporaryFile("w", suffix=".json", encoding="utf-8", delete=False) as handle: + json.dump(corpus, handle, indent=2) + handle.write("\n") + temp_path = Path(handle.name) + + try: + command = [ + sys.executable, + "scripts/build_constraint_intelligence.py", + "--input", str(temp_path), + "--taxonomy", str(args.taxonomy), + "--output", str(args.output), + "--constraints", str(args.constraints), + "--markdown", str(args.markdown), + ] + completed = subprocess.run(command, check=False) + return completed.returncode + finally: + temp_path.unlink(missing_ok=True) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/discover_github_issues.py b/scripts/discover_github_issues.py new file mode 100644 index 0000000..6ddc8c6 --- /dev/null +++ b/scripts/discover_github_issues.py @@ -0,0 +1,275 @@ +#!/usr/bin/env python3 +"""Discover and score GitHub issues for human-reviewed constraint intelligence. + +Uses only the Python standard library. Public searches work without a token at a +lower rate limit; set GITHUB_TOKEN for authenticated requests. + +This script never posts comments. It preserves the issue evidence needed by the +constraint-intelligence compiler and produces a review queue for a human operator. +""" + +from __future__ import annotations + +import argparse +import json +import os +import re +import sys +import time +import urllib.error +import urllib.parse +import urllib.request +from dataclasses import asdict, dataclass +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + +API_URL = "https://api.github.com/search/issues" +USER_AGENT = "living-context-engine-review-pipeline/2.2" +# GitHub Search rejects queries with more than five explicit AND/OR/NOT operators. +# Five phrases require at most four OR operators and leave the safety qualifiers intact. +MAX_TERMS_PER_SEARCH = 5 +# Authenticated issue search allows a bounded request rate but can still trigger +# secondary abuse protection on bursts. Pace searches and recover conservatively. +SEARCH_PACE_SECONDS = 2.2 +SECONDARY_RETRY_LIMIT = 3 +SECONDARY_BACKOFF_SECONDS = 5.0 + + +@dataclass(frozen=True) +class Candidate: + repository: str + number: int + title: str + body: str + url: str + created_at: str + updated_at: str + comments: int + author: str + labels: list[str] + query_id: str + matched_terms: list[str] + relevance: int + intent: int + recency: int + validation: int + spam_penalty: int + score: int + reason: str + draft_angle: str + source_platform: str = "github" + unresolved: bool = True + contribution_gap: bool = True + + +def load_config(path: Path) -> dict[str, Any]: + try: + data = json.loads(path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as exc: + raise SystemExit(f"Could not load config {path}: {exc}") from exc + if not isinstance(data.get("queries"), list): + raise SystemExit("Config must contain a 'queries' list") + return data + + +def github_get(params: dict[str, str], token: str | None) -> dict[str, Any]: + url = f"{API_URL}?{urllib.parse.urlencode(params)}" + headers = { + "Accept": "application/vnd.github+json", + "User-Agent": USER_AGENT, + "X-GitHub-Api-Version": "2022-11-28", + } + if token: + headers["Authorization"] = f"Bearer {token}" + request = urllib.request.Request(url, headers=headers) + + for attempt in range(SECONDARY_RETRY_LIMIT + 1): + try: + with urllib.request.urlopen(request, timeout=30) as response: + return json.load(response) + except urllib.error.HTTPError as exc: + body = exc.read().decode("utf-8", errors="replace") + secondary_limited = exc.code == 403 and "secondary rate limit" in body.lower() + if secondary_limited and attempt < SECONDARY_RETRY_LIMIT: + retry_after = exc.headers.get("Retry-After") + try: + delay = float(retry_after) if retry_after else SECONDARY_BACKOFF_SECONDS * (2 ** attempt) + except ValueError: + delay = SECONDARY_BACKOFF_SECONDS * (2 ** attempt) + time.sleep(max(delay, SEARCH_PACE_SECONDS)) + continue + raise RuntimeError(f"GitHub API returned {exc.code}: {body}") from exc + except urllib.error.URLError as exc: + raise RuntimeError(f"GitHub API request failed: {exc.reason}") from exc + + raise RuntimeError("GitHub API search retry budget exhausted") + + +def age_score(created_at: str) -> int: + created = datetime.fromisoformat(created_at.replace("Z", "+00:00")) + days = max(0, (datetime.now(timezone.utc) - created).days) + if days <= 14: + return 15 + if days <= 45: + return 12 + if days <= 90: + return 8 + return 3 + + +def score_item(item: dict[str, Any], query: dict[str, Any]) -> Candidate: + title = str(item.get("title", "")) + body = str(item.get("body") or "") + text = f"{title}\n{body}".lower() + terms = [str(term).lower() for term in query.get("terms", [])] + matched = sorted({term for term in terms if term in text}) + + relevance = min(40, 12 + 7 * len(matched)) + intent_markers = ( + "how do i", "how can i", "need a way", "looking for", "workaround", + "blocked", "production", "manual restart", "rollback", "downgrade", + "duplicate", "silent", "stuck", "resume", "retry", "data loss", + "customer", "enterprise", "hours", "days", "wrong user", "unauthorized", + ) + intent = min(25, 7 + 4 * sum(marker in text for marker in intent_markers)) + recency = age_score(str(item.get("created_at"))) + comments = int(item.get("comments", 0)) + validation = 10 if comments >= 3 else 7 if comments >= 1 else 2 + + login = str((item.get("user") or {}).get("login", "")) + label_names = [str(label.get("name", "")) for label in item.get("labels", [])] + labels_text = " ".join(label_names) + spam_markers = ("dependabot", "renovate", "stale", "duplicate", "invalid", "good first issue") + spam_penalty = 20 if any(marker in f"{login} {labels_text}".lower() for marker in spam_markers) else 0 + + score = max(0, min(100, relevance + intent + recency + validation - spam_penalty)) + repository_url = str(item.get("repository_url", "")) + repository = repository_url.removeprefix("https://api.github.com/repos/") + angle = str(query.get("help_angle", "Preserve as market evidence by default; interact only when there is a concrete unresolved contribution gap.")) + reason = ( + f"Matched {len(matched)} signal terms; {comments} comments; " + f"intent={intent}, recency={recency}, penalty={spam_penalty}." + ) + return Candidate( + repository=repository, + number=int(item["number"]), + title=title, + body=body, + url=str(item["html_url"]), + created_at=str(item["created_at"]), + updated_at=str(item["updated_at"]), + comments=comments, + author=login, + labels=label_names, + query_id=str(query["id"]), + matched_terms=matched, + relevance=relevance, + intent=intent, + recency=recency, + validation=validation, + spam_penalty=spam_penalty, + score=score, + reason=reason, + draft_angle=angle, + ) + + +def build_query(query: dict[str, Any], created_after: str, terms: list[str] | None = None) -> str: + selected_terms = [str(term) for term in (terms if terms is not None else query.get("terms", []))] + phrases = " OR ".join(f'"{term}"' for term in selected_terms) + exclusions = " ".join(f'-label:"{label}"' for label in query.get("exclude_labels", [])) + repositories = " ".join(f"repo:{repo}" for repo in query.get("repositories", [])) + phrase_clause = f"({phrases}) " if phrases else "" + return ( + f"is:issue is:open created:>{created_after} {phrase_clause}" + f"{repositories} {exclusions} archived:false" + ).strip() + + +def build_queries(query: dict[str, Any], created_after: str) -> list[str]: + """Split broad signal families into GitHub-legal boolean query batches.""" + terms = [str(term) for term in query.get("terms", [])] + if not terms: + return [build_query(query, created_after, [])] + return [ + build_query(query, created_after, terms[index:index + MAX_TERMS_PER_SEARCH]) + for index in range(0, len(terms), MAX_TERMS_PER_SEARCH) + ] + + +def render_markdown(candidates: list[Candidate]) -> str: + lines = [ + "# GitHub signal review queue", + "", + f"Generated: {datetime.now(timezone.utc).isoformat()}", + "", + "> Human review required. Default public action is no comment. Preserve strong evidence even when intervention value is low.", + "", + ] + for index, candidate in enumerate(candidates, start=1): + lines.extend([ + f"## {index}. [{candidate.repository} #{candidate.number}]({candidate.url}) — discovery {candidate.score}/100", + "", + f"**{candidate.title}**", + "", + f"- Query: `{candidate.query_id}`", + f"- Author: `{candidate.author}`; comments: {candidate.comments}", + f"- Labels: {', '.join(candidate.labels) or 'none'}", + f"- Matched terms: {', '.join(candidate.matched_terms) or 'none'}", + f"- Qualification: {candidate.reason}", + f"- Review angle: {candidate.draft_angle}", + "- Next gate: compile constraint value independently from intervention value; inspect the full thread before any public action.", + "", + ]) + return "\n".join(lines) + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--config", type=Path, default=Path("config/constraint_signal_queries.json")) + parser.add_argument("--created-after", default="2026-05-01") + parser.add_argument("--per-query", type=int, default=30) + parser.add_argument("--min-score", type=int, default=45) + parser.add_argument("--output", type=Path, default=Path("artifacts/github-review-queue.json")) + parser.add_argument("--markdown", type=Path, default=Path("artifacts/github-review-queue.md")) + args = parser.parse_args() + + config = load_config(args.config) + token = os.getenv("GITHUB_TOKEN") + candidates: dict[str, Candidate] = {} + first_search = True + + for query in config["queries"]: + for search in build_queries(query, args.created_after): + if not first_search: + time.sleep(SEARCH_PACE_SECONDS) + first_search = False + payload = github_get( + {"q": search, "sort": "updated", "order": "desc", "per_page": str(args.per_query)}, + token, + ) + for item in payload.get("items", []): + if "pull_request" in item: + continue + candidate = score_item(item, query) + if candidate.score < args.min_score: + continue + current = candidates.get(candidate.url) + if current is None or candidate.score > current.score: + candidates[candidate.url] = candidate + + ranked = sorted(candidates.values(), key=lambda item: (-item.score, -item.comments, item.updated_at)) + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps([asdict(item) for item in ranked], indent=2) + "\n", encoding="utf-8") + args.markdown.write_text(render_markdown(ranked), encoding="utf-8") + print(f"Wrote {len(ranked)} qualified signals to {args.output} and {args.markdown}") + return 0 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except RuntimeError as exc: + print(str(exc), file=sys.stderr) + raise SystemExit(1) from exc diff --git a/tests/test_constraint_intelligence.py b/tests/test_constraint_intelligence.py new file mode 100644 index 0000000..b23fc49 --- /dev/null +++ b/tests/test_constraint_intelligence.py @@ -0,0 +1,135 @@ +import sys +from importlib.util import module_from_spec, spec_from_file_location +from pathlib import Path + + +SCRIPT = Path(__file__).parents[1] / "scripts" / "build_constraint_intelligence.py" +SPEC = spec_from_file_location("build_constraint_intelligence", SCRIPT) +assert SPEC and SPEC.loader +MODULE = module_from_spec(SPEC) +sys.modules[SPEC.name] = MODULE +SPEC.loader.exec_module(MODULE) + + +TAXONOMY = { + "constraints": [ + { + "id": "operational-truth-divergence", + "name": "Operational truth divergence", + "keywords": ["published", "dead trigger", "manual restart", "healthy"], + }, + { + "id": "side-effect-integrity", + "name": "Ambiguous or duplicate side effects", + "keywords": ["duplicate", "retry", "timeout", "partial delivery"], + }, + ] +} + + +def test_classifies_operational_truth_divergence(): + record = { + "title": "Workflow remains Published while dead trigger requires manual restart", + "body": "Health remains healthy although the runtime stopped serving events.", + } + match = MODULE.classify_constraint(record, TAXONOMY) + assert match.constraint_id == "operational-truth-divergence" + assert match.match_score >= 54 + assert "published" in match.matched_terms + + +def test_high_value_saturated_signal_stays_corpus_only_or_watch(): + record = { + "repository": "example/workflows", + "number": 9, + "title": "Production duplicate customer sends after timeout and retry", + "body": "Enterprise production workflow duplicates customer actions and needs manual reconciliation.", + "comments": 30, + "saturated": True, + "fix_in_progress": True, + "production": True, + "unresolved": True, + } + compiled = MODULE.compile_record(record, TAXONOMY, 1, 61) + assert compiled.constraint_value >= 45 + assert compiled.intervention_value < compiled.constraint_value + assert compiled.intervention in {"corpus_only", "watch", "ask_diagnostic_question"} + + +def test_high_value_unsaturated_signal_escalates_intervention(): + record = { + "repository": "example/workflows", + "number": 10, + "title": "Production duplicate customer sends after timeout and retry", + "body": ( + "Enterprise production customer workflow repeatedly duplicates actions. " + "Manual restart and reconciliation required daily. Steps to reproduce and logs attached." + ), + "comments": 1, + "production": True, + "unresolved": True, + "contribution_gap": True, + "author_role": "automation engineer", + } + compiled = MODULE.compile_record(record, TAXONOMY, 1, 61) + assert compiled.constraint_id == "side-effect-integrity" + assert compiled.constraint_value >= 60 + assert compiled.intervention_value >= 60 + assert compiled.intervention in { + "precision_comment", + "build_reproduction", + "build_patch_or_diagnostic", + "operator_assistance_or_offer", + } + + +def test_generality_increases_across_repositories(): + raw = [ + {"repository": "a/one", "title": "duplicate retry timeout"}, + {"repository": "b/two", "title": "duplicate retry timeout"}, + {"repository": "c/three", "title": "duplicate retry timeout"}, + ] + mapping = MODULE.generality_map(raw, TAXONOMY) + assert mapping["side-effect-integrity"] > 25 + + +def test_constraint_state_promotes_with_repeated_strong_evidence(): + base = { + "source_platform": "github", + "number": 1, + "url": "", + "title": "Production duplicate retry causes customer impact and manual restart", + "author": "operator", + "observed_at": "2026-08-08T00:00:00+00:00", + "production_evidence": 100, + "economic_consequence": 90, + "workaround_burden": 80, + "recurrence": 80, + "cross_system_generality": 70, + "buyer_proximity": 80, + "serviceability": 70, + "proof_feasibility": 70, + "urgency": 70, + "commodity_penalty": 0, + "solved_penalty": 0, + "weak_evidence_penalty": 0, + "constraint_value": 82, + "intervention_value": 75, + "intervention": "build_reproduction", + "constraint_id": "side-effect-integrity", + "constraint_name": "Ambiguous or duplicate side effects", + "constraint_match_score": 80, + "constraint_terms": ["duplicate", "retry"], + "economic_chain": ["failure", "constraint", "ops", "economic", "workaround"], + "saturation_state": "emerging", + "reason": "test", + } + records = [] + for index, repo in enumerate(("a/one", "b/two", "c/three"), 1): + data = dict(base) + data["signal_id"] = f"github:{repo}#{index}" + data["repository"] = repo + data["number"] = index + records.append(MODULE.IntelligenceRecord(**data)) + promoted = MODULE.apply_constraint_states(records) + assert all(item.saturation_state == "confirmed" for item in promoted) diff --git a/tests/test_github_issue_discovery.py b/tests/test_github_issue_discovery.py new file mode 100644 index 0000000..d021945 --- /dev/null +++ b/tests/test_github_issue_discovery.py @@ -0,0 +1,90 @@ +import sys +from importlib.util import module_from_spec, spec_from_file_location +from pathlib import Path + + +SCRIPT = Path(__file__).parents[1] / "scripts" / "discover_github_issues.py" +SPEC = spec_from_file_location("discover_github_issues", SCRIPT) +assert SPEC and SPEC.loader +MODULE = module_from_spec(SPEC) +sys.modules[SPEC.name] = MODULE +SPEC.loader.exec_module(MODULE) + + +def test_build_query_preserves_issue_and_safety_filters(): + query = { + "id": "test", + "terms": ["losing context", "decision log"], + "exclude_labels": ["duplicate", "invalid"], + } + + rendered = MODULE.build_query(query, "2026-05-01") + + assert "is:issue is:open" in rendered + assert '"losing context" OR "decision log"' in rendered + assert 'created:>2026-05-01' in rendered + assert '-label:"duplicate"' in rendered + assert "archived:false" in rendered + + +def test_build_queries_batches_broad_signal_families_within_github_boolean_limit(): + query = { + "id": "broad", + "terms": [f"signal {index}" for index in range(1, 10)], + "exclude_labels": ["duplicate", "invalid"], + } + + rendered = MODULE.build_queries(query, "2026-05-01") + + assert len(rendered) == 2 + assert all(search.count(" OR ") <= 4 for search in rendered) + for term in query["terms"]: + assert sum(f'"{term}"' in search for search in rendered) == 1 + + +def test_score_prefers_specific_active_human_pain(): + item = { + "number": 42, + "title": "How can I stop losing context during project handoffs?", + "body": "We need a way to resume work with a source line audit trail and decision log.", + "html_url": "https://github.com/example/repo/issues/42", + "repository_url": "https://api.github.com/repos/example/repo", + "created_at": "2026-08-01T00:00:00Z", + "updated_at": "2026-08-02T00:00:00Z", + "comments": 4, + "user": {"login": "human-author"}, + "labels": [], + } + query = { + "id": "context-loss", + "terms": ["losing context", "resume work", "source line", "decision log"], + "help_angle": "Offer a source-linked context pack.", + } + + candidate = MODULE.score_item(item, query) + + assert candidate.repository == "example/repo" + assert candidate.score >= 75 + assert candidate.spam_penalty == 0 + assert "decision log" in candidate.matched_terms + + +def test_score_penalizes_bot_or_stale_noise(): + item = { + "number": 7, + "title": "Decision log dependency update", + "body": "Automated update.", + "html_url": "https://github.com/example/repo/issues/7", + "repository_url": "https://api.github.com/repos/example/repo", + "created_at": "2026-08-01T00:00:00Z", + "updated_at": "2026-08-01T00:00:00Z", + "comments": 0, + "user": {"login": "dependabot[bot]"}, + "labels": [{"name": "stale"}], + } + query = {"id": "decision", "terms": ["decision log"]} + + candidate = MODULE.score_item(item, query) + + assert candidate.spam_penalty == 20 + assert candidate.score < 55