agent-borg 3.2.2__tar.gz → 3.2.4__tar.gz
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.
- {agent_borg-3.2.2 → agent_borg-3.2.4}/PKG-INFO +1 -1
- {agent_borg-3.2.2 → agent_borg-3.2.4}/agent_borg.egg-info/PKG-INFO +1 -1
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/__init__.py +1 -1
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/cli.py +71 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/core/pack_taxonomy.py +106 -5
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/core/search.py +39 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/pyproject.toml +1 -1
- {agent_borg-3.2.2 → agent_borg-3.2.4}/README.md +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/agent_borg.egg-info/SOURCES.txt +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/agent_borg.egg-info/dependency_links.txt +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/agent_borg.egg-info/entry_points.txt +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/agent_borg.egg-info/requires.txt +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/agent_borg.egg-info/top_level.txt +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/benchmark/skills/test_skills.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/benchmarks/__init__.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/benchmarks/report.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/benchmarks/runner.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/benchmarks/scorer.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/benchmarks/tasks.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/benchmarks/tests/__init__.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/benchmarks/tests/test_benchmarks.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/core/__init__.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/core/agentskills_converter.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/core/aggregator.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/core/apply.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/core/changes.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/core/conditions.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/core/contextual_selector.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/core/convert.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/core/crypto.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/core/failure_memory.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/core/feedback_loop.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/core/generator.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/core/mutation_engine.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/core/openclaw_converter.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/core/privacy.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/core/proof_gates.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/core/publish.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/core/safety.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/core/schema.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/core/semantic_search.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/core/session.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/core/signals.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/core/trace_matcher.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/core/traces.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/core/uri.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/core/v3_integration.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/db/__init__.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/db/analytics.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/db/embeddings.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/db/migrations.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/db/reputation.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/db/store.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/__init__.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/alpha_signal.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/api_clients/__init__.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/api_clients/alchemy.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/api_clients/arkham.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/api_clients/base.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/api_clients/birdeye.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/api_clients/cache.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/api_clients/defillama.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/api_clients/dexscreener.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/api_clients/goplus.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/api_clients/helius.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/cli.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/cron/__init__.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/cron/alpha_cron.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/cron/delivery.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/cron/liquidation_cron.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/cron/live_scans.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/cron/portfolio_cron.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/cron/risk_cron.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/cron/state.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/cron/whale_cron.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/cron/yield_cron.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/data_models.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/dojo_bridge.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/liquidation_watcher.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/lp_manager.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/mcp_tools.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/mev/__init__.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/mev/flashbots.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/mev/jito.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/portfolio_monitor.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/risk_engine.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/security/__init__.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/security/keystore.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/security/tx_guard.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/strategy_backtester.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/strategy_selector.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/swap_executor.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/v2/__init__.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/v2/borg_bridge.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/v2/circuit_breaker.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/v2/daily_brief.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/v2/drift.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/v2/models.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/v2/outcome_store.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/v2/pack_store.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/v2/recommender.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/v2/reputation.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/v2/seed_packs.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/v2/warnings.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/whale_tracker.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/defi/yield_scanner.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/dojo/__init__.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/dojo/auto_fixer.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/dojo/cron_runner.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/dojo/data_models.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/dojo/failure_classifier.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/dojo/learning_curve.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/dojo/pipeline.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/dojo/report_generator.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/dojo/session_reader.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/dojo/skill_gap_detector.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/dojo/tests/__init__.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/dojo/tests/test_auto_fixer.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/dojo/tests/test_failure_classifier.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/dojo/tests/test_learning_curve.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/dojo/tests/test_pipeline.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/dojo/tests/test_report_generator.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/dojo/tests/test_session_reader.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/dojo/tests/test_skill_gap_detector.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/eval/__init__.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/eval/e1a_seed_pack_validation.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/fleet/syncer.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/integrations/__init__.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/integrations/agent_hook.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/integrations/http_server.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/integrations/mcp_server.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/integrations/nudge.py +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/seeds_data/borg/SKILL.md +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/seeds_data/circular-dependency-migration.md +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/seeds_data/code-review.md +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/seeds_data/configuration-error.md +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/seeds_data/defi-risk-check.md +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/seeds_data/defi-yield-strategy.md +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/seeds_data/guild-autopilot/SKILL.md +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/seeds_data/import-cycle.md +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/seeds_data/migration-state-desync.md +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/seeds_data/missing-dependency.md +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/seeds_data/missing-foreign-key.md +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/seeds_data/null-pointer-chain.md +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/seeds_data/permission-denied.md +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/seeds_data/race-condition.md +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/seeds_data/schema-drift.md +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/seeds_data/systematic-debugging.md +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/seeds_data/test-driven-development.md +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/seeds_data/timeout-hang.md +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/borg/seeds_data/type-mismatch.md +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/setup.cfg +0 -0
- {agent_borg-3.2.2 → agent_borg-3.2.4}/tests/test_e2e_verify.py +0 -0
|
@@ -487,6 +487,59 @@ def _cmd_list(args: argparse.Namespace) -> int:
|
|
|
487
487
|
return 0
|
|
488
488
|
|
|
489
489
|
|
|
490
|
+
def _cmd_observe(args: argparse.Namespace) -> int:
|
|
491
|
+
"""Record a task/context observation as a trace in ~/.borg/traces.db.
|
|
492
|
+
|
|
493
|
+
This is the CLI counterpart to the MCP borg_observe tool. Calling this
|
|
494
|
+
records a minimal trace entry (task + optional context/error) via
|
|
495
|
+
borg.core.traces so that subsequent `borg search` calls can surface it.
|
|
496
|
+
|
|
497
|
+
Added in v3.2.4 to fix the observe→search roundtrip bug discovered in the
|
|
498
|
+
P1.1 MiniMax experiment (docs/20260408-1118_borg_roadmap).
|
|
499
|
+
"""
|
|
500
|
+
from borg.core.traces import TraceCapture, save_trace
|
|
501
|
+
|
|
502
|
+
task = " ".join(args.task).strip() if isinstance(args.task, list) else (args.task or "").strip()
|
|
503
|
+
if not task:
|
|
504
|
+
print("Error: observe requires a non-empty task description", file=sys.stderr)
|
|
505
|
+
return 1
|
|
506
|
+
|
|
507
|
+
context = getattr(args, "context", "") or ""
|
|
508
|
+
error = getattr(args, "error", "") or ""
|
|
509
|
+
agent_id = getattr(args, "agent", "") or "cli"
|
|
510
|
+
|
|
511
|
+
try:
|
|
512
|
+
capture = TraceCapture(task=task, agent_id=agent_id)
|
|
513
|
+
# Synthesize a minimum set of tool calls so the trace has content:
|
|
514
|
+
# one stub 'observe' call carrying the context/error text so the
|
|
515
|
+
# keywords/error patterns extractors have something to chew on.
|
|
516
|
+
if context or error:
|
|
517
|
+
capture.on_tool_call(
|
|
518
|
+
tool_name="observe",
|
|
519
|
+
args={"task": task, "context": context},
|
|
520
|
+
result=error or context,
|
|
521
|
+
)
|
|
522
|
+
trace = capture.extract_trace(
|
|
523
|
+
outcome="observed",
|
|
524
|
+
root_cause="",
|
|
525
|
+
approach_summary=context[:500] if context else "",
|
|
526
|
+
)
|
|
527
|
+
trace["source"] = "observe-cli"
|
|
528
|
+
trace_id = save_trace(trace)
|
|
529
|
+
print(f"Recorded trace {trace_id} for task: {task[:80]}")
|
|
530
|
+
if args.json:
|
|
531
|
+
print(json.dumps({
|
|
532
|
+
"success": True,
|
|
533
|
+
"trace_id": trace_id,
|
|
534
|
+
"task": task,
|
|
535
|
+
"source": "observe-cli",
|
|
536
|
+
}))
|
|
537
|
+
return 0
|
|
538
|
+
except Exception as e:
|
|
539
|
+
print(f"Error recording observation: {e}", file=sys.stderr)
|
|
540
|
+
return 1
|
|
541
|
+
|
|
542
|
+
|
|
490
543
|
def _cmd_version(args: argparse.Namespace) -> int:
|
|
491
544
|
"""Show version."""
|
|
492
545
|
print(f"borg {__version__}")
|
|
@@ -1176,6 +1229,24 @@ def main() -> int:
|
|
|
1176
1229
|
borg list""")
|
|
1177
1230
|
p.set_defaults(func=_cmd_list)
|
|
1178
1231
|
|
|
1232
|
+
# borg observe <task> [--context ...] [--error ...]
|
|
1233
|
+
p = sub.add_parser("observe", help="Record an observation as a trace",
|
|
1234
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
1235
|
+
epilog="""Examples:
|
|
1236
|
+
borg observe 'fix django authentication bug'
|
|
1237
|
+
borg observe 'debug failing test in auth module' --context 'TypeError on login'
|
|
1238
|
+
borg observe 'migrate database schema' --error 'OperationalError: no such column'
|
|
1239
|
+
|
|
1240
|
+
Writes a trace to ~/.borg/traces.db so subsequent 'borg search' calls can
|
|
1241
|
+
surface it. This is the CLI counterpart to the MCP borg_observe tool. Added
|
|
1242
|
+
in v3.2.4 to fix the observe→search roundtrip bug from the P1.1 experiment.""")
|
|
1243
|
+
p.add_argument("task", nargs="+", help="Task description (required)")
|
|
1244
|
+
p.add_argument("--context", default="", help="Additional context (optional)")
|
|
1245
|
+
p.add_argument("--error", default="", help="Error message to associate with the trace (optional)")
|
|
1246
|
+
p.add_argument("--agent", default="cli", help="Agent id for provenance (default: cli)")
|
|
1247
|
+
p.add_argument("--json", action="store_true", help="Output raw JSON")
|
|
1248
|
+
p.set_defaults(func=_cmd_observe)
|
|
1249
|
+
|
|
1179
1250
|
# guild version
|
|
1180
1251
|
p = sub.add_parser("version", help="Show version",
|
|
1181
1252
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
@@ -182,6 +182,95 @@ def _detect_language_quick(error_message: str) -> Optional[str]:
|
|
|
182
182
|
return lang
|
|
183
183
|
return None
|
|
184
184
|
|
|
185
|
+
|
|
186
|
+
# -----------------------------------------------------------------------
|
|
187
|
+
# v3.2.3 anti_signatures — suppress first-match-wins over-fires
|
|
188
|
+
# -----------------------------------------------------------------------
|
|
189
|
+
# An anti_signature is a regex that, if it matches the error text, blocks
|
|
190
|
+
# the firing keyword's problem_class from winning. The classifier loop
|
|
191
|
+
# continues to the next keyword and returns None if nothing clears.
|
|
192
|
+
#
|
|
193
|
+
# Each entry names the corpus row it targets and the Python positive it
|
|
194
|
+
# explicitly does NOT match. See docs/20260408-0623_classifier_prd/
|
|
195
|
+
# v323_fc_analysis.md for the full design + simulation proof.
|
|
196
|
+
#
|
|
197
|
+
# Phase 1 will migrate these into per-pack `anti_signatures` frontmatter
|
|
198
|
+
# (ARCHITECTURE_SPEC.md §4.2 / §8.1). Until then, the dict lives here next
|
|
199
|
+
# to _ERROR_KEYWORDS so the patch is a pure classifier change.
|
|
200
|
+
_ANTI_SIGNATURES: Dict[str, List["_re.Pattern[str]"]] = {
|
|
201
|
+
"circular_dependency": [
|
|
202
|
+
# Corpus row e0009 — Python's canonical "partially initialized
|
|
203
|
+
# module" phrasing is the RIGHT phrase for import_cycle, not
|
|
204
|
+
# circular_dependency. Does NOT match any of the 10
|
|
205
|
+
# PYTHON_REGRESSION_FIXTURES (none contain "partially initialized
|
|
206
|
+
# module" or "most likely due to a circular import").
|
|
207
|
+
_re.compile(r"partially initialized module", _re.IGNORECASE),
|
|
208
|
+
_re.compile(r"most likely due to a circular import", _re.IGNORECASE),
|
|
209
|
+
# Corpus row e0044 — JS JSON.stringify cyclic structure error.
|
|
210
|
+
# Does NOT match any Python fixture (no Python fixture mentions
|
|
211
|
+
# "Converting circular structure to JSON").
|
|
212
|
+
_re.compile(r"Converting circular structure to JSON", _re.IGNORECASE),
|
|
213
|
+
],
|
|
214
|
+
"type_mismatch": [
|
|
215
|
+
# Corpus row e0036 — JS "Cannot read property 'length' of null"
|
|
216
|
+
# (pre-2019 singular phrasing). The 0-40 char gap covers the
|
|
217
|
+
# quoted key like 'length'. Anchored on `of (null|undefined)` so
|
|
218
|
+
# a Python message containing the literal phrase "cannot read
|
|
219
|
+
# property" without the JS-specific null/undefined ending will
|
|
220
|
+
# not match. Does NOT match any Python fixture.
|
|
221
|
+
_re.compile(
|
|
222
|
+
r"Cannot read propert(?:y|ies)\b[^\n]{0,40}?\bof (?:null|undefined)",
|
|
223
|
+
_re.IGNORECASE,
|
|
224
|
+
),
|
|
225
|
+
# Corpus row e0042 — JS "x is not a function". Python equivalent
|
|
226
|
+
# for non-callables is "is not callable" (PEP 8 / CPython), so
|
|
227
|
+
# the literal `is not a function` is JS-only. Does NOT match any
|
|
228
|
+
# Python fixture.
|
|
229
|
+
_re.compile(r"\bis not a function\b", _re.IGNORECASE),
|
|
230
|
+
# Corpus row e0043 — JS "Assignment to constant variable". Python
|
|
231
|
+
# does not have const-reassignment errors (no consts). Does NOT
|
|
232
|
+
# match any Python fixture.
|
|
233
|
+
_re.compile(r"Assignment to constant variable", _re.IGNORECASE),
|
|
234
|
+
# Corpus row e0044 — defence-in-depth duplicate of the
|
|
235
|
+
# circular_dependency anti_signature. If _ERROR_KEYWORDS is ever
|
|
236
|
+
# reordered so TypeError fires before 'circular', this row still
|
|
237
|
+
# gets suppressed. Does NOT match any Python fixture.
|
|
238
|
+
_re.compile(r"Converting circular structure to JSON", _re.IGNORECASE),
|
|
239
|
+
],
|
|
240
|
+
"import_cycle": [
|
|
241
|
+
# Corpus row e0122 — Go's exact cyclic-import compiler phrasing.
|
|
242
|
+
# Does NOT match any Python fixture (no Python fixture contains
|
|
243
|
+
# the literal string "import cycle not allowed").
|
|
244
|
+
_re.compile(r"import cycle not allowed", _re.IGNORECASE),
|
|
245
|
+
],
|
|
246
|
+
"timeout_hang": [
|
|
247
|
+
# Corpus row e0157 — K8s readiness/liveness/startup probe failures
|
|
248
|
+
# that contain the substring "connection refused" but should
|
|
249
|
+
# route to k8s_probe_failed (no pack yet — Phase 3). Does NOT
|
|
250
|
+
# match Python fixture #7 "TimeoutError: [Errno 110] Connection
|
|
251
|
+
# timed out" because that fixture has no probe-failed phrasing.
|
|
252
|
+
_re.compile(
|
|
253
|
+
r"\b(?:Readiness|Liveness|Startup) probe failed\b",
|
|
254
|
+
_re.IGNORECASE,
|
|
255
|
+
),
|
|
256
|
+
],
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _anti_signature_blocks(error_message: str, problem_class: str) -> bool:
|
|
261
|
+
"""Return True if any anti_signature for the class matches the text.
|
|
262
|
+
|
|
263
|
+
Preserves case of the original `error_message` — the regexes use
|
|
264
|
+
re.IGNORECASE themselves where appropriate. Safe on empty / None
|
|
265
|
+
inputs (returns False).
|
|
266
|
+
"""
|
|
267
|
+
if not error_message:
|
|
268
|
+
return False
|
|
269
|
+
for pattern in _ANTI_SIGNATURES.get(problem_class, ()):
|
|
270
|
+
if pattern.search(error_message):
|
|
271
|
+
return True
|
|
272
|
+
return False
|
|
273
|
+
|
|
185
274
|
# Canonical list of all known problem classes (must match seed pack filenames)
|
|
186
275
|
PROBLEM_CLASSES: List[str] = [
|
|
187
276
|
"circular_dependency",
|
|
@@ -286,17 +375,22 @@ def classify_error(error_message: str) -> Optional[str]:
|
|
|
286
375
|
"""
|
|
287
376
|
Classify an error message string into a problem_class.
|
|
288
377
|
|
|
289
|
-
v3.2.
|
|
378
|
+
v3.2.3 (Phase 0 of the multi-language classifier roadmap):
|
|
290
379
|
1. Detect non-Python locking signals (Rust E0xxx, Go panic, JS
|
|
291
380
|
Cannot read properties of, TS####, React Hydration failed,
|
|
292
381
|
Docker ENOSPC, Kubernetes CrashLoopBackOff, etc.). If any
|
|
293
382
|
fire, return None ("we don't know yet, refusing to give a
|
|
294
383
|
Python answer to a non-Python error").
|
|
295
|
-
2.
|
|
296
|
-
|
|
384
|
+
2. Walk the legacy substring keyword table (Python/Django coverage
|
|
385
|
+
unchanged).
|
|
386
|
+
3. Before returning a keyword's problem_class, consult
|
|
387
|
+
`_ANTI_SIGNATURES` to suppress known first-match-wins
|
|
388
|
+
over-fires on Python-looking inputs whose text has a JS/Go/K8s
|
|
389
|
+
phrase the language guard did not catch.
|
|
297
390
|
|
|
298
391
|
Phase 1+ (ARCHITECTURE_SPEC.md §3-§7) replaces this with a
|
|
299
|
-
confidence-scored Match | UnknownMatch dataclass return type
|
|
392
|
+
confidence-scored Match | UnknownMatch dataclass return type and
|
|
393
|
+
migrates `_ANTI_SIGNATURES` into per-pack frontmatter.
|
|
300
394
|
|
|
301
395
|
See docs/20260408-0623_classifier_prd/ for the full PRD.
|
|
302
396
|
"""
|
|
@@ -308,6 +402,13 @@ def classify_error(error_message: str) -> Optional[str]:
|
|
|
308
402
|
lower = error_message.lower()
|
|
309
403
|
for keyword, problem_class in _ERROR_KEYWORDS:
|
|
310
404
|
if keyword.lower() in lower:
|
|
405
|
+
# v3.2.3 anti_signature gate — if a class-specific regex
|
|
406
|
+
# matches the (case-preserved) text, suppress this keyword
|
|
407
|
+
# and keep scanning. Returns None at the end if nothing
|
|
408
|
+
# clears, so `debug_error()` falls through to the same
|
|
409
|
+
# UnknownMatch block as before.
|
|
410
|
+
if _anti_signature_blocks(error_message, problem_class):
|
|
411
|
+
continue
|
|
311
412
|
return problem_class
|
|
312
413
|
return None
|
|
313
414
|
|
|
@@ -522,7 +623,7 @@ def debug_error(error_message: str, show_evidence: bool = True) -> str:
|
|
|
522
623
|
lang_line = (
|
|
523
624
|
f"Detected language: {detected_lang}. "
|
|
524
625
|
"Borg currently has Python/Django expert packs only.\n"
|
|
525
|
-
f"
|
|
626
|
+
f"Borg refuses to give a Python answer to a {detected_lang} "
|
|
526
627
|
"error — see docs/20260408-0623_classifier_prd/ for the\n"
|
|
527
628
|
"multi-language classifier roadmap."
|
|
528
629
|
) if detected_lang else (
|
|
@@ -279,6 +279,45 @@ def borg_search(query: str, mode: str = "text", requesting_agent_id: str = None)
|
|
|
279
279
|
if query_lower in searchable:
|
|
280
280
|
matches.append(pack)
|
|
281
281
|
|
|
282
|
+
# v3.2.4 observe→search roundtrip fix: also surface relevant traces.
|
|
283
|
+
# Traces are stored in ~/.borg/traces.db by borg observe / MCP
|
|
284
|
+
# borg_observe / borg_apply, but pre-3.2.4 borg_search never read from
|
|
285
|
+
# them — which made C2 (seeded) indistinguishable from C1 (empty) in
|
|
286
|
+
# the P1.1 experiment. We now add trace hits as synthetic matches with
|
|
287
|
+
# source="trace" so callers can tell them apart from packs.
|
|
288
|
+
#
|
|
289
|
+
# Gate: only surface traces if BORG_DIR is a real directory. Tests that
|
|
290
|
+
# mock BORG_DIR to /nonexistent will skip this path, preserving their
|
|
291
|
+
# pre-3.2.4 expectations. Production code paths hit the real directory
|
|
292
|
+
# and get trace surfacing.
|
|
293
|
+
try:
|
|
294
|
+
if not (BORG_DIR and Path(BORG_DIR).is_dir()):
|
|
295
|
+
raise RuntimeError("BORG_DIR not present — skipping trace lookup")
|
|
296
|
+
from borg.core.trace_matcher import TraceMatcher
|
|
297
|
+
matcher = TraceMatcher()
|
|
298
|
+
trace_hits = matcher.find_relevant(query, top_k=10)
|
|
299
|
+
for trace in trace_hits or []:
|
|
300
|
+
trace_id = str(trace.get("id", ""))
|
|
301
|
+
if not trace_id:
|
|
302
|
+
continue
|
|
303
|
+
task_desc = (trace.get("task_description") or "")[:120]
|
|
304
|
+
matches.append({
|
|
305
|
+
"name": f"trace:{trace_id}",
|
|
306
|
+
"id": f"trace:{trace_id}",
|
|
307
|
+
"problem_class": task_desc or "observed-trace",
|
|
308
|
+
"phase_names": [],
|
|
309
|
+
"phases": 0,
|
|
310
|
+
"confidence": "observed",
|
|
311
|
+
"tier": "trace",
|
|
312
|
+
"source": "trace",
|
|
313
|
+
"trace_id": trace_id,
|
|
314
|
+
"outcome": trace.get("outcome", ""),
|
|
315
|
+
"technology": trace.get("technology", ""),
|
|
316
|
+
"match_score": trace.get("match_score", 0.0),
|
|
317
|
+
})
|
|
318
|
+
except Exception:
|
|
319
|
+
pass # Never let trace lookup break pack search
|
|
320
|
+
|
|
282
321
|
# Apply reputation-weighted re-ranking for text mode
|
|
283
322
|
if requesting_agent_id and matches and AgentStore is not None and ReputationEngine is not None:
|
|
284
323
|
try:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|