@event4u/agent-config 6.0.0 → 6.1.0
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.
- package/.claude-plugin/marketplace.json +5 -5
- package/CHANGELOG.md +167 -440
- package/README.md +3 -3
- package/dist/agent-src/commands/agent-handoff.md +5 -4
- package/dist/agent-src/commands/agent-status.md +1 -0
- package/dist/agent-src/commands/agents/audit.md +1 -0
- package/dist/agent-src/commands/agents/init.md +3 -0
- package/dist/agent-src/commands/agents/optimize.md +1 -0
- package/dist/agent-src/commands/agents/user/accept.md +1 -0
- package/dist/agent-src/commands/agents/user/init.md +1 -0
- package/dist/agent-src/commands/agents/user/review.md +1 -0
- package/dist/agent-src/commands/agents/user/show.md +1 -0
- package/dist/agent-src/commands/agents/user/update.md +1 -0
- package/dist/agent-src/commands/agents/user.md +1 -0
- package/dist/agent-src/commands/agents.md +1 -0
- package/dist/agent-src/commands/analytics/prune.md +3 -2
- package/dist/agent-src/commands/analytics/show.md +3 -2
- package/dist/agent-src/commands/analytics.md +3 -2
- package/dist/agent-src/commands/analyze-reference-repo.md +1 -0
- package/dist/agent-src/commands/bug-fix.md +1 -0
- package/dist/agent-src/commands/bug-investigate.md +1 -0
- package/dist/agent-src/commands/challenge-me/vision.md +3 -2
- package/dist/agent-src/commands/challenge-me/with-docs.md +3 -2
- package/dist/agent-src/commands/challenge-me.md +3 -2
- package/dist/agent-src/commands/chat-history/import.md +9 -9
- package/dist/agent-src/commands/chat-history.md +32 -30
- package/dist/agent-src/commands/check-current-md.md +1 -0
- package/dist/agent-src/commands/commit/in-chunks.md +1 -0
- package/dist/agent-src/commands/commit.md +1 -0
- package/dist/agent-src/commands/condense.md +1 -0
- package/dist/agent-src/commands/context/create.md +1 -0
- package/dist/agent-src/commands/context/refactor.md +1 -0
- package/dist/agent-src/commands/context.md +1 -0
- package/dist/agent-src/commands/cost-report.md +5 -4
- package/dist/agent-src/commands/council/analysis.md +3 -2
- package/dist/agent-src/commands/council/debate.md +5 -4
- package/dist/agent-src/commands/council/default.md +3 -2
- package/dist/agent-src/commands/council/design.md +3 -2
- package/dist/agent-src/commands/council/optimize.md +3 -2
- package/dist/agent-src/commands/council/pr.md +3 -2
- package/dist/agent-src/commands/council.md +4 -3
- package/dist/agent-src/commands/e2e-heal.md +1 -0
- package/dist/agent-src/commands/e2e-plan.md +1 -0
- package/dist/agent-src/commands/estimate-ticket.md +1 -0
- package/dist/agent-src/commands/feature/dev.md +1 -0
- package/dist/agent-src/commands/feature/explore.md +1 -0
- package/dist/agent-src/commands/feature/plan.md +6 -6
- package/dist/agent-src/commands/feature/refactor.md +1 -0
- package/dist/agent-src/commands/feature/roadmap.md +1 -0
- package/dist/agent-src/commands/feature.md +1 -0
- package/dist/agent-src/commands/fix/ci.md +1 -0
- package/dist/agent-src/commands/fix/portability.md +1 -0
- package/dist/agent-src/commands/fix/pr-comments.md +147 -15
- package/dist/agent-src/commands/fix/refs.md +1 -0
- package/dist/agent-src/commands/fix/seeder.md +1 -0
- package/dist/agent-src/commands/fix.md +8 -8
- package/dist/agent-src/commands/ghostwriter/delete.md +1 -0
- package/dist/agent-src/commands/ghostwriter/fetch.md +1 -0
- package/dist/agent-src/commands/ghostwriter/list.md +1 -0
- package/dist/agent-src/commands/ghostwriter/show.md +1 -0
- package/dist/agent-src/commands/ghostwriter/write.md +1 -0
- package/dist/agent-src/commands/ghostwriter.md +1 -0
- package/dist/agent-src/commands/grill-me.md +3 -2
- package/dist/agent-src/commands/image/analyse.md +1 -0
- package/dist/agent-src/commands/image/create.md +1 -0
- package/dist/agent-src/commands/image/verify.md +1 -0
- package/dist/agent-src/commands/image.md +1 -0
- package/dist/agent-src/commands/implement-ticket.md +1 -0
- package/dist/agent-src/commands/jira-ticket.md +1 -0
- package/dist/agent-src/commands/judge/on-diff.md +1 -0
- package/dist/agent-src/commands/judge/solo.md +1 -0
- package/dist/agent-src/commands/judge/steps.md +1 -0
- package/dist/agent-src/commands/judge.md +1 -0
- package/dist/agent-src/commands/knowledge/cross-repo.md +1 -0
- package/dist/agent-src/commands/knowledge/forget.md +1 -0
- package/dist/agent-src/commands/knowledge/ingest.md +1 -0
- package/dist/agent-src/commands/knowledge/list.md +1 -0
- package/dist/agent-src/commands/knowledge.md +1 -0
- package/dist/agent-src/commands/memory/add.md +8 -6
- package/dist/agent-src/commands/memory/learn-low-impact.md +3 -2
- package/dist/agent-src/commands/memory/load.md +7 -7
- package/dist/agent-src/commands/memory/mine-session.md +39 -12
- package/dist/agent-src/commands/memory/promote.md +3 -2
- package/dist/agent-src/commands/memory/propose.md +7 -6
- package/dist/agent-src/commands/memory.md +3 -2
- package/dist/agent-src/commands/mode.md +1 -0
- package/dist/agent-src/commands/module/create.md +1 -0
- package/dist/agent-src/commands/module/explore.md +1 -0
- package/dist/agent-src/commands/module.md +1 -0
- package/dist/agent-src/commands/optimize/agents-dir.md +1 -0
- package/dist/agent-src/commands/optimize/augmentignore.md +1 -0
- package/dist/agent-src/commands/optimize/rtk.md +1 -0
- package/dist/agent-src/commands/optimize/skills.md +1 -0
- package/dist/agent-src/commands/optimize-prompt.md +1 -0
- package/dist/agent-src/commands/optimize.md +1 -0
- package/dist/agent-src/commands/orchestrate.md +1 -0
- package/dist/agent-src/commands/override/create.md +1 -0
- package/dist/agent-src/commands/override/manage.md +1 -0
- package/dist/agent-src/commands/override.md +1 -0
- package/dist/agent-src/commands/package-reset.md +1 -0
- package/dist/agent-src/commands/package-test.md +1 -0
- package/dist/agent-src/commands/post-as/ghostwriter.md +1 -0
- package/dist/agent-src/commands/post-as/me.md +1 -0
- package/dist/agent-src/commands/post-as.md +1 -0
- package/dist/agent-src/commands/pr/create/description-only.md +1 -0
- package/dist/agent-src/commands/pr/create.md +25 -0
- package/dist/agent-src/commands/prediction-pool.md +1 -0
- package/dist/agent-src/commands/prepare-for-review.md +1 -0
- package/dist/agent-src/commands/profile/activate.md +1 -0
- package/dist/agent-src/commands/profile/deactivate.md +1 -0
- package/dist/agent-src/commands/profile/show.md +1 -0
- package/dist/agent-src/commands/profile.md +1 -0
- package/dist/agent-src/commands/project-analyze.md +1 -0
- package/dist/agent-src/commands/project-health.md +1 -0
- package/dist/agent-src/commands/quality-fix.md +1 -0
- package/dist/agent-src/commands/refine-ticket.md +1 -0
- package/dist/agent-src/commands/research/deep.md +1 -0
- package/dist/agent-src/commands/research/report.md +1 -0
- package/dist/agent-src/commands/research.md +1 -0
- package/dist/agent-src/commands/review-changes.md +1 -0
- package/dist/agent-src/commands/review-routing.md +1 -0
- package/dist/agent-src/commands/roadmap/ai-council.md +1 -0
- package/dist/agent-src/commands/roadmap/create.md +1 -0
- package/dist/agent-src/commands/roadmap/process-full.md +1 -0
- package/dist/agent-src/commands/roadmap/process-phase.md +1 -0
- package/dist/agent-src/commands/roadmap/process-step.md +1 -0
- package/dist/agent-src/commands/roadmap.md +1 -0
- package/dist/agent-src/commands/rule-compliance-audit.md +1 -0
- package/dist/agent-src/commands/security-audit-config.md +84 -0
- package/dist/agent-src/commands/set-cost-profile.md +1 -0
- package/dist/agent-src/commands/skill/preview.md +1 -0
- package/dist/agent-src/commands/skill.md +1 -0
- package/dist/agent-src/commands/skills/discover.md +1 -0
- package/dist/agent-src/commands/skills.md +1 -0
- package/dist/agent-src/commands/sync-agent-settings.md +1 -0
- package/dist/agent-src/commands/sync-gitignore/fix.md +1 -0
- package/dist/agent-src/commands/sync-gitignore.md +1 -0
- package/dist/agent-src/commands/tests/create.md +1 -0
- package/dist/agent-src/commands/tests/execute.md +1 -0
- package/dist/agent-src/commands/tests.md +1 -0
- package/dist/agent-src/commands/threat-model.md +1 -0
- package/dist/agent-src/commands/update-form-request-messages.md +1 -0
- package/dist/agent-src/commands/upstream-contribute.md +1 -0
- package/dist/agent-src/commands/video/from-script.md +1 -0
- package/dist/agent-src/commands/video/from-song.md +1 -0
- package/dist/agent-src/commands/video/scene.md +1 -0
- package/dist/agent-src/commands/video/stitch.md +1 -0
- package/dist/agent-src/commands/video/storyboard.md +1 -0
- package/dist/agent-src/commands/video.md +1 -0
- package/dist/agent-src/commands/work.md +1 -0
- package/dist/agent-src/contexts/augment-infrastructure.md +1 -1
- package/dist/agent-src/contexts/communication/rules-auto/skill-quality-mechanics.md +1 -1
- package/dist/agent-src/contexts/communication/rules-auto/slash-command-routing-policy-mechanics.md +2 -2
- package/dist/agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +6 -6
- package/dist/agent-src/contexts/contracts/consumer-agents-md-guide.md +2 -2
- package/dist/agent-src/contexts/execution/rdp-gate.md +75 -0
- package/dist/agent-src/contexts/subagent-configuration.md +1 -0
- package/dist/agent-src/personas/advisors/contrarian.md +1 -1
- package/dist/agent-src/personas/advisors/executor.md +1 -1
- package/dist/agent-src/personas/advisors/expansionist.md +1 -1
- package/dist/agent-src/personas/advisors/first-principles.md +1 -1
- package/dist/agent-src/personas/advisors/outsider.md +1 -1
- package/dist/agent-src/rules/autonomous-execution.md +12 -0
- package/dist/agent-src/rules/external-reference-deep-dive.md +1 -1
- package/dist/agent-src/rules/git-history-discipline.md +47 -1
- package/dist/agent-src/rules/improve-before-implement.md +12 -0
- package/dist/agent-src/rules/lethal-trifecta-guard.md +80 -0
- package/dist/agent-src/rules/no-pr-progress-comments.md +3 -4
- package/dist/agent-src/rules/notes-first-reasoning.md +71 -0
- package/dist/agent-src/rules/roadmap-progress-sync.md +48 -31
- package/dist/agent-src/rules/security-sensitive-stop.md +14 -1
- package/dist/agent-src/rules/source-confidentiality.md +97 -0
- package/dist/agent-src/rules/think-before-action.md +9 -1
- package/dist/agent-src/rules/untrusted-input-defense.md +76 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.py +171 -0
- package/dist/agent-src/skills/adversarial-review/SKILL.md +14 -0
- package/dist/agent-src/skills/agent-security-review/SKILL.md +113 -0
- package/dist/agent-src/skills/agent-security-review/evals/triggers.json +51 -0
- package/dist/agent-src/skills/ai-council/SKILL.md +3 -3
- package/dist/agent-src/skills/async-python-patterns/SKILL.md +1 -1
- package/dist/agent-src/skills/blast-radius-analyzer/SKILL.md +12 -11
- package/dist/agent-src/skills/command-routing/SKILL.md +1 -1
- package/dist/agent-src/skills/complexity-first-planning/SKILL.md +96 -0
- package/dist/agent-src/skills/complexity-first-planning/evals/triggers.json +16 -0
- package/dist/agent-src/skills/copilot-config/SKILL.md +3 -4
- package/dist/agent-src/skills/defense-in-depth/SKILL.md +1 -1
- package/dist/agent-src/skills/developer-like-execution/SKILL.md +5 -4
- package/dist/agent-src/skills/error-handling-patterns/SKILL.md +1 -1
- package/dist/agent-src/skills/feature-planning/SKILL.md +2 -2
- package/dist/agent-src/skills/mcp-builder/SKILL.md +1 -1
- package/dist/agent-src/skills/memory-consolidation/SKILL.md +63 -17
- package/dist/agent-src/skills/prompt-engineering-patterns/SKILL.md +1 -1
- package/dist/agent-src/skills/readme-writing-package/SKILL.md +1 -1
- package/dist/agent-src/skills/reasoning-orchestrator/SKILL.md +119 -0
- package/dist/agent-src/skills/reasoning-orchestrator/evals/triggers.json +16 -0
- package/dist/agent-src/skills/receiving-code-review/SKILL.md +6 -6
- package/dist/agent-src/skills/refine-prompt/SKILL.md +1 -1
- package/dist/agent-src/skills/refine-ticket/SKILL.md +1 -1
- package/dist/agent-src/skills/repomix-packer/SKILL.md +1 -1
- package/dist/agent-src/skills/secrets-management/SKILL.md +1 -1
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +10 -3
- package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +1 -1
- package/dist/agent-src/skills/testing-anti-patterns/process-anti-patterns.md +1 -1
- package/dist/agent-src/skills/token-optimizer/SKILL.md +1 -1
- package/dist/agent-src/templates/agents/.gitattributes.fragment +0 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +4 -4
- package/dist/agent-src/templates/scripts/check_memory.py +1 -2
- package/dist/agent-src/templates/scripts/check_memory_proposal.py +1 -1
- package/dist/agent-src/templates/scripts/memory_lookup.py +148 -289
- package/dist/agent-src/templates/scripts/memory_report.py +132 -2
- package/dist/agent-src/templates/scripts/memory_signal.py +7 -9
- package/dist/agent-src/templates/scripts/memory_status.py +25 -206
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/memory.py +6 -6
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/_passthrough.py +3 -3
- package/dist/agent-src/templates/scripts/work_engine/scoring/memory_visibility.py +0 -1
- package/dist/cli/agent-config.js +31 -300
- package/dist/cli/agent-config.js.map +1 -1
- package/dist/cli/commands/commands.js +10 -5
- package/dist/cli/commands/commands.js.map +1 -1
- package/dist/cli/discovery/loadManifest.js.map +1 -1
- package/dist/cli/main.js +309 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +645 -342
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +8 -5
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +149 -37
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +61 -36
- package/dist/mcp/registry-manifest.json +4 -4
- package/dist/router.json +1 -1
- package/dist/server/routes/wizard.js +4 -3
- package/dist/server/routes/wizard.js.map +1 -1
- package/dist/server/schemas/settings.js +18 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/MIGRATION.md +1 -1
- package/docs/adrs/cost/0001-hard-stop-hook.md +5 -5
- package/docs/adrs/memory/0001-consumer-side-snapshot.md +15 -7
- package/docs/adrs/memory/README.md +6 -5
- package/docs/adrs/router/0001-three-tier-routing.md +2 -2
- package/docs/adrs/schema/0001-json-schema-frontmatter.md +2 -2
- package/docs/adrs/smoke/0001-per-tier-smoke-scripts.md +5 -5
- package/docs/adrs/telegraph/0001-default-off-until-bench.md +3 -3
- package/docs/architecture.md +9 -9
- package/docs/archive/CHANGELOG-pre-2.2.0.md +30 -30
- package/docs/archive/CHANGELOG-pre-2.25.0.md +1 -1
- package/docs/archive/CHANGELOG-pre-4.5.0.md +1 -1
- package/docs/archive/CHANGELOG-pre-6.0.0.md +473 -0
- package/docs/benchmark.md +54 -53
- package/docs/benchmarks.md +2 -2
- package/docs/case-studies/{frontend-design-vs-ui-ux-pro-max.md → frontend-design-positioning.md} +4 -4
- package/docs/catalog.md +20 -13
- package/docs/command-flows.md +90 -92
- package/docs/contracts/adr-layout.md +2 -3
- package/docs/contracts/adr-level-6-productization.md +1 -1
- package/docs/contracts/ai-council-config.md +42 -7
- package/docs/contracts/command-clusters.md +1 -1
- package/docs/contracts/cost-enforcement.md +1 -1
- package/docs/contracts/cost-summary-schema.md +1 -1
- package/docs/contracts/daily-workspace.md +1 -0
- package/docs/contracts/discovery-manifest.schema.json +4 -2
- package/docs/contracts/explain-modes.md +1 -1
- package/docs/contracts/implement-ticket-flow.md +6 -7
- package/docs/contracts/mcp-tool-inventory.md +10 -10
- package/docs/contracts/measurement-baseline.md +1 -1
- package/docs/contracts/memory-visibility-v1.md +1 -5
- package/docs/contracts/namespace.md +1 -1
- package/docs/contracts/persona-schema.md +1 -1
- package/docs/contracts/rule-interactions.md +1 -1
- package/docs/contracts/smoke-contracts.md +1 -1
- package/docs/contracts/universal-skills.md +0 -1
- package/docs/contracts/workspace-boundary.md +84 -0
- package/docs/customization.md +3 -3
- package/docs/decisions/ADR-009-event4u-namespace.md +1 -1
- package/docs/decisions/ADR-013-discovery-frontmatter-contract.md +1 -1
- package/docs/decisions/ADR-026-explain-mode-translation.md +1 -1
- package/docs/decisions/ADR-088-no-external-runtime-federation.md +26 -27
- package/docs/decisions/ADR-090-visibility-command-frontmatter-field.md +95 -0
- package/docs/decisions/ADR-091-split-meta-capability-packs.md +113 -0
- package/docs/decisions/ADR-092-defer-command-tier-alias-removal.md +93 -0
- package/docs/decisions/ADR-093-ai-council-config-user-global.md +111 -0
- package/docs/decisions/ADR-094-agent-memory-layer-removal.md +94 -0
- package/docs/decisions/ADR-095-workspace-boundary-contract.md +108 -0
- package/docs/decisions/INDEX.md +6 -0
- package/docs/development.md +5 -7
- package/docs/getting-started.md +4 -4
- package/docs/guidelines/agent-infra/5w2h-analysis.md +1 -1
- package/docs/guidelines/agent-infra/comparison-matrix.md +1 -1
- package/docs/guidelines/agent-infra/corpus-grounding-authoring.md +1 -1
- package/docs/guidelines/agent-infra/critical-thinking.md +1 -1
- package/docs/guidelines/agent-infra/engineering-memory-data-format.md +1 -5
- package/docs/guidelines/agent-infra/first-principles.md +1 -1
- package/docs/guidelines/agent-infra/frontier-reasoning-operating-profile.md +164 -0
- package/docs/guidelines/agent-infra/inversion-thinking.md +1 -1
- package/docs/guidelines/agent-infra/ios-simulator-guide.md +9 -14
- package/docs/guidelines/agent-infra/mcp-request-signing.md +19 -22
- package/docs/guidelines/agent-infra/memory-access.md +25 -31
- package/docs/guidelines/agent-infra/mental-models.md +1 -1
- package/docs/guidelines/agent-infra/model-recommendation.md +29 -0
- package/docs/guidelines/agent-infra/scqa-framework.md +3 -3
- package/docs/guidelines/agent-infra/security-lint-containment.md +81 -0
- package/docs/guidelines/agent-infra/six-hats.md +1 -1
- package/docs/guidelines/agent-infra/systems-thinking.md +1 -1
- package/docs/guidelines/agent-infra/untrusted-input-spotlighting.md +72 -0
- package/docs/installation.md +1 -1
- package/docs/mcp.md +2 -2
- package/docs/parity/{bench-ruflo.json → bench-external.json} +10 -10
- package/docs/parity/{ruflo.md → external-runtime.md} +9 -9
- package/docs/quality.md +3 -3
- package/docs/safety.md +3 -3
- package/docs/skills-catalog.md +4 -1
- package/llms.txt +3 -0
- package/package.json +1 -1
- package/src/config/agent-settings.template.yml +65 -3
- package/src/config/discovery/packs.yml +29 -0
- package/src/config/discovery/workspaces.yml +3 -1
- package/src/config/gitignore-block.txt +6 -0
- package/src/scripts/__pycache__/validate_frontmatter.cpython-312.pyc +0 -0
- package/src/scripts/_cli/cmd_doctor.py +99 -13
- package/src/scripts/_lib/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/scripts/_lib/__pycache__/agent_src.cpython-312.pyc +0 -0
- package/src/scripts/_lib/bench_ab_scoring_v2.py +227 -0
- package/src/scripts/_lib/global_deploy_inventory.py +39 -9
- package/src/scripts/_lib/link_crypto.py +206 -0
- package/src/scripts/_lib/security_lint.py +228 -0
- package/src/scripts/ai_council/clients.py +2 -2
- package/src/scripts/ai_council/config.py +55 -0
- package/src/scripts/audit_adr_coverage.py +0 -2
- package/src/scripts/audit_command_surface.py +18 -5
- package/src/scripts/audit_mcp_tools.py +2 -2
- package/src/scripts/audit_skill_descriptions.py +2 -2
- package/src/scripts/bench_ab_clone.py +62 -12
- package/src/scripts/bench_ab_task_runner.py +475 -30
- package/src/scripts/bench_ab_v2_run.py +247 -0
- package/src/scripts/bench_ab_v2_stats.py +347 -0
- package/src/scripts/bench_run.py +1 -1
- package/src/scripts/build_discovery_manifest.py +10 -0
- package/src/scripts/check_bite_sized_granularity.py +1 -2
- package/src/scripts/check_memory.py +49 -63
- package/src/scripts/check_memory_proposal.py +1 -1
- package/src/scripts/check_no_external_sources.py +101 -0
- package/src/scripts/check_references.py +2 -0
- package/src/scripts/cost_by_conversation.py +1 -1
- package/src/scripts/council_cli.py +28 -14
- package/src/scripts/external_sources_denylist.json +91 -0
- package/src/scripts/hook_manifest.yaml +14 -6
- package/src/scripts/injection_scan_hook.py +145 -0
- package/src/scripts/install-hooks.sh +11 -0
- package/src/scripts/install.py +88 -13
- package/src/scripts/lint_agent_security.py +112 -0
- package/src/scripts/lint_bench_ab.py +5 -4
- package/src/scripts/lint_command_tiers.py +63 -22
- package/src/scripts/lint_discovery_vocabulary.py +2 -0
- package/src/scripts/lint_empty_roadmaps.py +80 -0
- package/src/scripts/lint_hidden_unicode.py +132 -0
- package/src/scripts/lint_instruction_smuggling.py +107 -0
- package/src/scripts/lint_marketplace.py +1 -1
- package/src/scripts/lint_mcp_config_security.py +124 -0
- package/src/scripts/lint_skill_frontmatter_safety.py +144 -0
- package/src/scripts/lint_workspace_boundary.py +122 -0
- package/src/scripts/mcp_server/consumer_tool_catalog.json +2 -3
- package/src/scripts/mcp_server/tools.py +8 -32
- package/src/scripts/memory_lookup.py +27 -296
- package/src/scripts/memory_report.py +1 -23
- package/src/scripts/memory_signal.py +6 -53
- package/src/scripts/memory_status.py +25 -206
- package/src/scripts/mine_session.py +118 -41
- package/src/scripts/pack_dependency_allowlist.json +2 -2
- package/src/scripts/render_benchmark_md.py +141 -52
- package/src/scripts/schemas/command.schema.json +6 -1
- package/src/scripts/security_audit_config.py +153 -0
- package/dist/agent-src/commands/chat-history/learn.md +0 -184
- package/dist/agent-src/commands/chat-history/show.md +0 -113
- package/dist/agent-src/commands/fix/pr-bot-comments.md +0 -157
- package/dist/agent-src/commands/fix/pr-developer-comments.md +0 -163
- package/dist/agent-src/templates/agents/memory/architecture-decisions.example.yml +0 -95
- package/docs/contracts/agent-memory-contract.md +0 -159
|
@@ -1,28 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
2
|
+
"""File-first memory retrieval.
|
|
3
3
|
|
|
4
4
|
Implements the shared `retrieve(types, keys, limit)` abstraction used
|
|
5
5
|
by skills. Reads YAML under `agents/memory/<type>/` (curated, hand-
|
|
6
6
|
reviewed) and JSONL under `agents/memory/intake/*.jsonl` (agent-written,
|
|
7
|
-
append-only, supersede-chain aware)
|
|
7
|
+
append-only, supersede-chain aware), plus user-ingested `knowledge`
|
|
8
|
+
chunks and opted-in `cross-repo` matches.
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
through the package's semantic CLI. Repo entries always win on
|
|
13
|
-
conflict — see `_apply_conflict_rule`.
|
|
10
|
+
Retrieval is entirely repo-side and file-backed — there is no external
|
|
11
|
+
backend. (The former optional `@event4u/agent-memory` package routing
|
|
12
|
+
was removed; see `docs/decisions/` for the agent-memory removal ADR.)
|
|
14
13
|
|
|
15
14
|
Usage:
|
|
16
15
|
python3 scripts/memory_lookup.py --types domain-invariants,ownership \\
|
|
17
16
|
--key "app/Http/Controllers/Foo" --limit 5
|
|
18
17
|
python3 scripts/memory_lookup.py --types incident-learnings --format json
|
|
19
|
-
python3 scripts/memory_lookup.py --types ownership --key billing --auto
|
|
20
18
|
|
|
21
|
-
from scripts.memory_lookup import retrieve
|
|
22
|
-
hits = retrieve(
|
|
23
|
-
types=["ownership"], keys=["app/Http"], limit=3,
|
|
24
|
-
operational_provider=package_operational_provider(),
|
|
25
|
-
)
|
|
19
|
+
from scripts.memory_lookup import retrieve
|
|
20
|
+
hits = retrieve(types=["ownership"], keys=["app/Http"], limit=3)
|
|
26
21
|
"""
|
|
27
22
|
|
|
28
23
|
from __future__ import annotations
|
|
@@ -30,31 +25,43 @@ from __future__ import annotations
|
|
|
30
25
|
import argparse
|
|
31
26
|
import fnmatch
|
|
32
27
|
import json
|
|
33
|
-
import os
|
|
34
|
-
import subprocess
|
|
35
28
|
import sys
|
|
36
29
|
from dataclasses import dataclass, asdict, field
|
|
37
30
|
from pathlib import Path
|
|
38
|
-
from typing import
|
|
31
|
+
from typing import Iterable, Union
|
|
39
32
|
|
|
40
33
|
MEMORY_ROOT = Path("agents/memory")
|
|
41
34
|
INTAKE_ROOT = MEMORY_ROOT / "intake"
|
|
35
|
+
KNOWLEDGE_ROOT = MEMORY_ROOT / "knowledge"
|
|
42
36
|
|
|
43
37
|
CURATED_TYPES = {
|
|
44
38
|
"ownership",
|
|
45
39
|
"historical-patterns",
|
|
46
40
|
"domain-invariants",
|
|
47
|
-
"architecture-decisions",
|
|
48
41
|
"incident-learnings",
|
|
49
42
|
"product-rules",
|
|
50
43
|
}
|
|
51
44
|
|
|
45
|
+
# `knowledge` is its own type: user-ingested local documents that live
|
|
46
|
+
# under `agents/memory/knowledge/<ingest-id>/chunks/*.md`. They are
|
|
47
|
+
# repo-side (file-backed) but not "curated" and not intake.
|
|
48
|
+
KNOWLEDGE_TYPE = "knowledge"
|
|
49
|
+
|
|
50
|
+
# Cross-repo retrieval (road-to-leaner-core-and-discovery Phase 4). When this
|
|
51
|
+
# type is requested AND opted-in linked-project siblings exist, matches from
|
|
52
|
+
# scripts/cross_repo_retrieve.py are projected as `source="cross-repo"` Hits,
|
|
53
|
+
# scored below curated/knowledge so cross-repo context never outranks the
|
|
54
|
+
# project's own truth (mirrors the 0.85× knowledge discount, then floored
|
|
55
|
+
# further). Opt-in by caller (type must be requested) + lazy import → existing
|
|
56
|
+
# call sites and consumers without the script are unaffected.
|
|
57
|
+
CROSS_REPO_TYPE = "cross-repo"
|
|
58
|
+
|
|
52
59
|
|
|
53
60
|
@dataclass
|
|
54
61
|
class Hit:
|
|
55
62
|
id: str
|
|
56
63
|
type: str
|
|
57
|
-
source: str # "curated" | "intake" | "
|
|
64
|
+
source: str # "curated" | "intake" | "knowledge" | "cross-repo"
|
|
58
65
|
path: str # file (or logical locator) that produced the hit
|
|
59
66
|
score: float # naive, content-match based [0..1]
|
|
60
67
|
entry: dict = field(default_factory=dict)
|
|
@@ -63,36 +70,13 @@ class Hit:
|
|
|
63
70
|
return asdict(self)
|
|
64
71
|
|
|
65
72
|
|
|
66
|
-
@dataclass
|
|
67
|
-
class Shadow:
|
|
68
|
-
"""An operational entry suppressed by the conflict rule."""
|
|
69
|
-
id: str
|
|
70
|
-
type: str
|
|
71
|
-
reason: str # "same-id" | "repo-deprecated"
|
|
72
|
-
operational_path: str # where the suppressed entry came from
|
|
73
|
-
repo_path: str # repo entry that shadowed it
|
|
74
|
-
|
|
75
|
-
def as_dict(self) -> dict:
|
|
76
|
-
return asdict(self)
|
|
77
|
-
|
|
78
|
-
|
|
79
73
|
@dataclass
|
|
80
74
|
class RetrievalResult:
|
|
81
|
-
"""Full retrieval payload
|
|
75
|
+
"""Full retrieval payload."""
|
|
82
76
|
hits: list
|
|
83
|
-
shadows: list = field(default_factory=list)
|
|
84
77
|
|
|
85
78
|
def as_dict(self) -> dict:
|
|
86
|
-
return {
|
|
87
|
-
"hits": [h.as_dict() for h in self.hits],
|
|
88
|
-
"shadows": [s.as_dict() for s in self.shadows],
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
# An operational provider returns repo-shaped Hit objects with
|
|
93
|
-
# source="operational". Backend adapters (e.g. @event4u/agent-memory)
|
|
94
|
-
# are expected to translate their native payload into this shape.
|
|
95
|
-
OperationalProvider = Callable[[list[str], list[str]], Iterable[Hit]]
|
|
79
|
+
return {"hits": [h.as_dict() for h in self.hits]}
|
|
96
80
|
|
|
97
81
|
|
|
98
82
|
def _load_yaml(path: Path):
|
|
@@ -167,11 +151,62 @@ def _iter_intake_entries(mtype: str) -> Iterable[tuple[Path, dict]]:
|
|
|
167
151
|
yield jsonl, obj
|
|
168
152
|
|
|
169
153
|
|
|
154
|
+
def _iter_knowledge_entries() -> Iterable[tuple[Path, dict]]:
|
|
155
|
+
"""Yield (chunk-file, entry) pairs from `agents/memory/knowledge/`.
|
|
156
|
+
|
|
157
|
+
Layout (frozen in `docs/contracts/local-knowledge-ingestion.md`):
|
|
158
|
+
|
|
159
|
+
agents/memory/knowledge/<ingest-id>/
|
|
160
|
+
manifest.json
|
|
161
|
+
chunks/<n>.md
|
|
162
|
+
|
|
163
|
+
Each chunk becomes one retrieval entry. The chunk body, the
|
|
164
|
+
manifest source path, and pinned flag are surfaced into the entry
|
|
165
|
+
so `_score()` can match on either the source path or the chunk
|
|
166
|
+
text. The entry id is ``<ingest-id>:<chunk-stem>`` so callers can
|
|
167
|
+
locate the exact file on disk.
|
|
168
|
+
"""
|
|
169
|
+
if not KNOWLEDGE_ROOT.is_dir():
|
|
170
|
+
return
|
|
171
|
+
for ingest_dir in sorted(KNOWLEDGE_ROOT.iterdir()):
|
|
172
|
+
if not ingest_dir.is_dir():
|
|
173
|
+
continue
|
|
174
|
+
manifest_path = ingest_dir / "manifest.json"
|
|
175
|
+
manifest: dict = {}
|
|
176
|
+
if manifest_path.is_file():
|
|
177
|
+
try:
|
|
178
|
+
manifest = json.loads(
|
|
179
|
+
manifest_path.read_text(encoding="utf-8")
|
|
180
|
+
)
|
|
181
|
+
except (ValueError, OSError):
|
|
182
|
+
manifest = {}
|
|
183
|
+
ingest_id = str(manifest.get("ingest_id") or ingest_dir.name)
|
|
184
|
+
source = str(manifest.get("source") or "")
|
|
185
|
+
pinned = bool(manifest.get("pinned", False))
|
|
186
|
+
chunks_dir = ingest_dir / "chunks"
|
|
187
|
+
if not chunks_dir.is_dir():
|
|
188
|
+
continue
|
|
189
|
+
for chunk in sorted(chunks_dir.glob("*.md")):
|
|
190
|
+
try:
|
|
191
|
+
body = chunk.read_text(encoding="utf-8")
|
|
192
|
+
except OSError:
|
|
193
|
+
continue
|
|
194
|
+
entry = {
|
|
195
|
+
"id": f"{ingest_id}:{chunk.stem}",
|
|
196
|
+
"ingest_id": ingest_id,
|
|
197
|
+
"source": source,
|
|
198
|
+
"path": source,
|
|
199
|
+
"body": body,
|
|
200
|
+
"pinned": pinned,
|
|
201
|
+
"source_kind": "knowledge",
|
|
202
|
+
}
|
|
203
|
+
yield chunk, entry
|
|
204
|
+
|
|
205
|
+
|
|
170
206
|
def _score(entry: dict, keys: list[str]) -> float:
|
|
171
207
|
"""Naive relevance score: max over keys of (glob-match | substring).
|
|
172
208
|
|
|
173
|
-
Good enough for
|
|
174
|
-
The `present` path returns a real score from agent-memory.
|
|
209
|
+
Good enough for best-effort file retrieval.
|
|
175
210
|
"""
|
|
176
211
|
if not keys:
|
|
177
212
|
return 0.1 # any hit beats no hit when there is no key
|
|
@@ -193,191 +228,80 @@ def _score(entry: dict, keys: list[str]) -> float:
|
|
|
193
228
|
return best
|
|
194
229
|
|
|
195
230
|
|
|
196
|
-
def
|
|
197
|
-
|
|
198
|
-
operational_hits: list[Hit],
|
|
199
|
-
) -> tuple[list[Hit], list[Shadow]]:
|
|
200
|
-
"""Enforce REPO WINS / OPERATIONAL AUGMENTS / NEVER CONTRADICTS SILENTLY.
|
|
231
|
+
def _cross_repo_hits(keys: list[str], limit: int) -> list[Hit]:
|
|
232
|
+
"""Project cross-repo matches into discounted, tagged Hits.
|
|
201
233
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
234
|
+
Lazy + guarded: imports `cross_repo_retrieve` on demand and swallows any
|
|
235
|
+
failure (script absent in a consumer install, no opted-in siblings) so the
|
|
236
|
+
cross-repo type degrades to zero hits rather than breaking retrieval. Scores
|
|
237
|
+
sit below curated/knowledge (0.85× floor, then a small per-rank decrement)
|
|
238
|
+
so cross-repo context never outranks the project's own truth.
|
|
205
239
|
"""
|
|
206
|
-
|
|
207
|
-
# the conflict rule. The operational store is the only non-repo side.
|
|
208
|
-
repo_by_id: dict[str, Hit] = {h.id: h for h in repo_hits if h.id}
|
|
209
|
-
|
|
210
|
-
merged: list[Hit] = list(repo_hits)
|
|
211
|
-
shadows: list[Shadow] = []
|
|
212
|
-
|
|
213
|
-
for op in operational_hits:
|
|
214
|
-
if op.id and op.id in repo_by_id:
|
|
215
|
-
# Case 1+2: same id → repo wins (including when repo is
|
|
216
|
-
# status:deprecated — operational cannot revive a retired
|
|
217
|
-
# entry). Suppress the operational entry and record shadow.
|
|
218
|
-
repo = repo_by_id[op.id]
|
|
219
|
-
reason = (
|
|
220
|
-
"repo-deprecated"
|
|
221
|
-
if repo.entry.get("status") == "deprecated"
|
|
222
|
-
else "same-id"
|
|
223
|
-
)
|
|
224
|
-
shadows.append(Shadow(
|
|
225
|
-
id=op.id,
|
|
226
|
-
type=op.type,
|
|
227
|
-
reason=reason,
|
|
228
|
-
operational_path=op.path,
|
|
229
|
-
repo_path=repo.path,
|
|
230
|
-
))
|
|
231
|
-
continue
|
|
232
|
-
# Case 3 (different ids on same logical key) and Case 4 (repo
|
|
233
|
-
# has no entry) — both simply include the operational hit.
|
|
234
|
-
# Repo entries naturally rank higher because their score is not
|
|
235
|
-
# discounted (see _score / operational scoring in retrieve()).
|
|
236
|
-
merged.append(op)
|
|
237
|
-
|
|
238
|
-
return merged, shadows
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
# ---------------------------------------------------------------------------
|
|
242
|
-
# Package-backed operational provider (the `present` path)
|
|
243
|
-
# ---------------------------------------------------------------------------
|
|
244
|
-
#
|
|
245
|
-
# When `memory_status.status() == "present"` the consumer-facing contract
|
|
246
|
-
# says retrieval should route through `@event4u/agent-memory`. The package
|
|
247
|
-
# CLI is purely **semantic** (`memory retrieve <query> --type T …`); the
|
|
248
|
-
# shared `retrieve(types, keys, …)` API is **key-based**. The hybrid
|
|
249
|
-
# resolution agreed in `docs/contracts/agent-memory-contract.md` synthesises
|
|
250
|
-
# `keys` into a single natural-language query for the package call, while
|
|
251
|
-
# the file fallback continues to do glob/substring matching on the same
|
|
252
|
-
# keys. Both legs land in the same `Hit` shape so the conflict rule can
|
|
253
|
-
# merge them transparently.
|
|
254
|
-
|
|
255
|
-
_CLI_TIMEOUT_SECONDS = 5.0
|
|
256
|
-
_CLI_RETRIEVE_LIMIT_DEFAULT = 20
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
def _synthesize_query(keys: list[str]) -> str:
|
|
260
|
-
"""Turn a list of retrieval keys into one natural-language query.
|
|
261
|
-
|
|
262
|
-
Keys are typically file paths (`app/Http/Controllers/Foo`), feature
|
|
263
|
-
names (`billing`), or short identifiers — joining them with spaces
|
|
264
|
-
gives the package's semantic search enough surface to score against
|
|
265
|
-
without inventing structure. Empty or whitespace-only keys are
|
|
266
|
-
dropped; if nothing remains the caller falls back to the file path.
|
|
267
|
-
"""
|
|
268
|
-
cleaned = [k.strip() for k in keys if isinstance(k, str) and k.strip()]
|
|
269
|
-
return " ".join(cleaned)
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
def _cli_operational_provider(
|
|
273
|
-
types: list[str],
|
|
274
|
-
keys: list[str],
|
|
275
|
-
*,
|
|
276
|
-
cli_path: str = "memory",
|
|
277
|
-
timeout: float = _CLI_TIMEOUT_SECONDS,
|
|
278
|
-
limit: int = _CLI_RETRIEVE_LIMIT_DEFAULT,
|
|
279
|
-
) -> Iterable[Hit]:
|
|
280
|
-
"""Run `memory retrieve` and yield operational `Hit` objects.
|
|
281
|
-
|
|
282
|
-
Pino structured logs from the package go to stderr; stdout is a
|
|
283
|
-
clean v1 retrieval envelope. Any non-zero exit, timeout, or parse
|
|
284
|
-
failure degrades to "no operational hits" — `retrieve()` already
|
|
285
|
-
treats provider exceptions as a soft warning, so the caller still
|
|
286
|
-
gets the file-fallback result.
|
|
287
|
-
"""
|
|
288
|
-
query = _synthesize_query(keys)
|
|
240
|
+
query = " ".join(k for k in keys if k).strip()
|
|
289
241
|
if not query:
|
|
290
|
-
return
|
|
291
|
-
cmd: list[str] = [cli_path, "retrieve", query, "--limit", str(limit)]
|
|
292
|
-
for t in types:
|
|
293
|
-
cmd.extend(["--type", t])
|
|
294
|
-
try:
|
|
295
|
-
out = subprocess.run(
|
|
296
|
-
cmd,
|
|
297
|
-
capture_output=True, text=True, timeout=timeout,
|
|
298
|
-
)
|
|
299
|
-
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
|
|
300
|
-
return
|
|
301
|
-
if out.returncode != 0:
|
|
302
|
-
return
|
|
242
|
+
return []
|
|
303
243
|
try:
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
source="operational",
|
|
329
|
-
path=f"agent-memory:{eid}",
|
|
330
|
-
score=score,
|
|
331
|
-
entry=body,
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
def package_operational_provider() -> Optional[OperationalProvider]:
|
|
336
|
-
"""Return a CLI-backed provider when the package is `present`, else None.
|
|
337
|
-
|
|
338
|
-
Callers who want automatic backend routing pass the result directly
|
|
339
|
-
to :func:`retrieve` — `None` is a safe value that yields file-only
|
|
340
|
-
retrieval, so this is the recommended one-liner for skills:
|
|
341
|
-
|
|
342
|
-
retrieve(types, keys, operational_provider=package_operational_provider())
|
|
343
|
-
|
|
344
|
-
The status probe is bounded (≤ 2s, cached per process) — see
|
|
345
|
-
`scripts/memory_status.py`. We import lazily so pure file-fallback
|
|
346
|
-
callers never pay for the probe.
|
|
347
|
-
"""
|
|
348
|
-
# Late import: keeps `memory_lookup` importable even when
|
|
349
|
-
# `memory_status` is missing in stripped consumer installs.
|
|
350
|
-
try:
|
|
351
|
-
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
352
|
-
import memory_status # type: ignore[import-not-found]
|
|
353
|
-
except ImportError:
|
|
354
|
-
return None
|
|
355
|
-
if memory_status.status().status != "present":
|
|
356
|
-
return None
|
|
357
|
-
return _cli_operational_provider
|
|
244
|
+
import os
|
|
245
|
+
import sys as _sys
|
|
246
|
+
from pathlib import Path as _Path
|
|
247
|
+
|
|
248
|
+
here = _Path(__file__).resolve().parent
|
|
249
|
+
if str(here) not in _sys.path:
|
|
250
|
+
_sys.path.insert(0, str(here))
|
|
251
|
+
import cross_repo_retrieve # type: ignore
|
|
252
|
+
|
|
253
|
+
result = cross_repo_retrieve.retrieve(_Path(os.getcwd()), query, None, limit)
|
|
254
|
+
except Exception: # noqa: BLE001 — optional surface; never break retrieval
|
|
255
|
+
return []
|
|
256
|
+
|
|
257
|
+
hits: list[Hit] = []
|
|
258
|
+
for i, m in enumerate(result.get("matches", [])):
|
|
259
|
+
hits.append(Hit(
|
|
260
|
+
id=f"cross-repo:{m.get('source_repo', '')}:{m.get('path', '')}",
|
|
261
|
+
type=CROSS_REPO_TYPE,
|
|
262
|
+
source="cross-repo",
|
|
263
|
+
path=f"{m.get('source_repo', '')}/{m.get('path', '')}",
|
|
264
|
+
score=round(0.7 * 0.85 - i * 0.01, 4),
|
|
265
|
+
entry=m,
|
|
266
|
+
))
|
|
267
|
+
return hits
|
|
358
268
|
|
|
359
269
|
|
|
360
270
|
def retrieve(
|
|
361
271
|
types: list[str],
|
|
362
272
|
keys: list[str],
|
|
363
273
|
limit: int = 5,
|
|
364
|
-
|
|
365
|
-
with_shadows: bool = False,
|
|
366
|
-
) -> Union[list[Hit], RetrievalResult]:
|
|
274
|
+
) -> list[Hit]:
|
|
367
275
|
"""Return up to `limit` hits across the requested types, highest score first.
|
|
368
276
|
|
|
369
277
|
Repo entries (curated + intake) are preferred on ties — they are
|
|
370
|
-
hand-reviewed or session-captured against the repo itself.
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
REPO WINS conflict rule; suppressed operational entries surface as
|
|
374
|
-
`shadows` when `with_shadows=True`.
|
|
375
|
-
|
|
376
|
-
The return type stays `list[Hit]` by default for backward
|
|
377
|
-
compatibility with existing skill call sites.
|
|
278
|
+
hand-reviewed or session-captured against the repo itself. Knowledge
|
|
279
|
+
and cross-repo hits are discounted so the project's own truth wins on
|
|
280
|
+
equal relevance.
|
|
378
281
|
"""
|
|
379
282
|
repo_hits: list[Hit] = []
|
|
380
283
|
for mtype in types:
|
|
284
|
+
if mtype == KNOWLEDGE_TYPE:
|
|
285
|
+
for path, entry in _iter_knowledge_entries():
|
|
286
|
+
base = _score(entry, keys)
|
|
287
|
+
# Pinned entries get a slight ranking boost so the
|
|
288
|
+
# `/knowledge:list --pin` flag has retrieval effect.
|
|
289
|
+
if entry.get("pinned"):
|
|
290
|
+
base = min(1.0, base + 0.05)
|
|
291
|
+
repo_hits.append(Hit(
|
|
292
|
+
id=str(entry.get("id", "")),
|
|
293
|
+
type=KNOWLEDGE_TYPE,
|
|
294
|
+
source="knowledge",
|
|
295
|
+
path=str(path),
|
|
296
|
+
# Discount vs curated/intake so hand-reviewed repo
|
|
297
|
+
# entries still win on equal relevance.
|
|
298
|
+
score=base * 0.85,
|
|
299
|
+
entry=entry,
|
|
300
|
+
))
|
|
301
|
+
continue
|
|
302
|
+
if mtype == CROSS_REPO_TYPE:
|
|
303
|
+
repo_hits.extend(_cross_repo_hits(keys, limit))
|
|
304
|
+
continue
|
|
381
305
|
if mtype not in CURATED_TYPES:
|
|
382
306
|
continue
|
|
383
307
|
for path, entry in _iter_curated_entries(mtype):
|
|
@@ -399,41 +323,22 @@ def retrieve(
|
|
|
399
323
|
entry=entry,
|
|
400
324
|
))
|
|
401
325
|
|
|
402
|
-
|
|
403
|
-
if
|
|
404
|
-
|
|
405
|
-
for oh in operational_provider(list(types), list(keys)) or []:
|
|
406
|
-
# Discount operational vs curated/intake so repo ranks
|
|
407
|
-
# higher on equal relevance. Providers may already return
|
|
408
|
-
# trust-adjusted scores; we only apply a floor discount.
|
|
409
|
-
oh.score = min(oh.score, 0.85)
|
|
410
|
-
operational_hits.append(oh)
|
|
411
|
-
except Exception as exc: # noqa: BLE001 — providers are external
|
|
412
|
-
print(f"warning: operational_provider raised "
|
|
413
|
-
f"{exc.__class__.__name__}: {exc}", file=sys.stderr)
|
|
414
|
-
|
|
415
|
-
merged, shadows = _apply_conflict_rule(repo_hits, operational_hits)
|
|
416
|
-
merged.sort(key=lambda h: (h.score, h.source == "curated"), reverse=True)
|
|
417
|
-
positives = [h for h in merged if h.score > 0]
|
|
418
|
-
final_hits = (positives or merged)[:limit]
|
|
419
|
-
|
|
420
|
-
if with_shadows:
|
|
421
|
-
return RetrievalResult(hits=final_hits, shadows=shadows)
|
|
422
|
-
return final_hits
|
|
326
|
+
repo_hits.sort(key=lambda h: (h.score, h.source == "curated"), reverse=True)
|
|
327
|
+
positives = [h for h in repo_hits if h.score > 0]
|
|
328
|
+
return (positives or repo_hits)[:limit]
|
|
423
329
|
|
|
424
330
|
|
|
425
331
|
CONTRACT_VERSION = 1
|
|
426
332
|
|
|
427
333
|
# Memory types this file-backed backend can answer. Types outside this
|
|
428
334
|
# set map to `unknown_type` per the retrieval contract.
|
|
429
|
-
_KNOWN_TYPES = CURATED_TYPES
|
|
335
|
+
_KNOWN_TYPES = CURATED_TYPES | {KNOWLEDGE_TYPE, CROSS_REPO_TYPE}
|
|
430
336
|
|
|
431
337
|
|
|
432
338
|
def retrieve_v1(
|
|
433
339
|
types: list[str],
|
|
434
340
|
keys: list[str],
|
|
435
341
|
limit: int = 20,
|
|
436
|
-
operational_provider: Optional[OperationalProvider] = None,
|
|
437
342
|
) -> dict:
|
|
438
343
|
"""Return a v1 retrieval-contract envelope.
|
|
439
344
|
|
|
@@ -441,49 +346,27 @@ def retrieve_v1(
|
|
|
441
346
|
the shape defined by
|
|
442
347
|
``internal/schemas/retrieval-v1.schema.json``. Unknown types are reported as
|
|
443
348
|
``status: unknown_type`` for that slice only, rather than failing
|
|
444
|
-
the whole call.
|
|
349
|
+
the whole call. All entries are file-backed (``source: "repo"``).
|
|
445
350
|
"""
|
|
446
351
|
known = [t for t in types if t in _KNOWN_TYPES]
|
|
447
352
|
unknown = [t for t in types if t not in _KNOWN_TYPES]
|
|
448
353
|
|
|
449
|
-
|
|
450
|
-
operational_provider=operational_provider,
|
|
451
|
-
with_shadows=True)
|
|
452
|
-
assert isinstance(result, RetrievalResult)
|
|
453
|
-
hits, shadows = result.hits, result.shadows
|
|
454
|
-
shadow_by_id = {s.id: s for s in shadows if s.id}
|
|
354
|
+
hits = retrieve(known, keys, limit=limit)
|
|
455
355
|
|
|
456
356
|
slice_counts: dict[str, int] = {t: 0 for t in known}
|
|
457
357
|
entries: list[dict] = []
|
|
458
358
|
for h in hits:
|
|
459
|
-
source = "operational" if h.source == "operational" else "repo"
|
|
460
359
|
envelope_entry: dict = {
|
|
461
360
|
"id": h.id,
|
|
462
361
|
"type": h.type,
|
|
463
|
-
"source":
|
|
362
|
+
"source": "repo",
|
|
464
363
|
"confidence": round(float(h.score), 4),
|
|
465
364
|
"body": dict(h.entry) if isinstance(h.entry, dict) else {},
|
|
466
|
-
"shadowed_by": None,
|
|
467
365
|
}
|
|
468
366
|
if h.type in slice_counts:
|
|
469
367
|
slice_counts[h.type] += 1
|
|
470
368
|
entries.append(envelope_entry)
|
|
471
369
|
|
|
472
|
-
# Surface shadowed operational entries as additional entries carrying
|
|
473
|
-
# `shadowed_by`. The conformance harness checks that only
|
|
474
|
-
# source="operational" entries ever set this field.
|
|
475
|
-
for sid, s in shadow_by_id.items():
|
|
476
|
-
entries.append({
|
|
477
|
-
"id": sid,
|
|
478
|
-
"type": s.type,
|
|
479
|
-
"source": "operational",
|
|
480
|
-
"confidence": 0.0,
|
|
481
|
-
"body": {},
|
|
482
|
-
"shadowed_by": f"repo:{sid}",
|
|
483
|
-
})
|
|
484
|
-
if s.type in slice_counts:
|
|
485
|
-
slice_counts[s.type] += 1
|
|
486
|
-
|
|
487
370
|
slices: dict[str, dict] = {
|
|
488
371
|
t: {"status": "ok", "count": slice_counts.get(t, 0)}
|
|
489
372
|
for t in known
|
|
@@ -527,35 +410,18 @@ def main() -> int:
|
|
|
527
410
|
ap.add_argument("--envelope", choices=["legacy", "v1"], default="legacy",
|
|
528
411
|
help="Output shape: `legacy` (Hit list) or `v1` "
|
|
529
412
|
"(retrieval contract v1 envelope). `v1` implies JSON output.")
|
|
530
|
-
ap.add_argument("--with-shadows", action="store_true",
|
|
531
|
-
help="Include shadowed-operational entries in the output "
|
|
532
|
-
"(no-op until an operational backend is wired)")
|
|
533
|
-
ap.add_argument("--auto", action="store_true",
|
|
534
|
-
help="Auto-route to the @event4u/agent-memory package "
|
|
535
|
-
"when memory_status.status() == 'present'; "
|
|
536
|
-
"falls through to file-only retrieval otherwise")
|
|
537
413
|
args = ap.parse_args()
|
|
538
414
|
types = [t.strip() for t in args.types.split(",") if t.strip()]
|
|
539
415
|
if not types:
|
|
540
416
|
print("error: --types is required", file=sys.stderr)
|
|
541
417
|
return 2
|
|
542
|
-
op_provider = package_operational_provider() if args.auto else None
|
|
543
418
|
if args.envelope == "v1":
|
|
544
|
-
envelope = retrieve_v1(types, args.key, args.limit
|
|
545
|
-
operational_provider=op_provider)
|
|
419
|
+
envelope = retrieve_v1(types, args.key, args.limit)
|
|
546
420
|
print(json.dumps(envelope, indent=2, default=str))
|
|
547
421
|
return 0
|
|
548
|
-
|
|
549
|
-
operational_provider=op_provider,
|
|
550
|
-
with_shadows=args.with_shadows)
|
|
551
|
-
if args.with_shadows:
|
|
552
|
-
assert isinstance(result, RetrievalResult)
|
|
553
|
-
hits, shadows = result.hits, result.shadows
|
|
554
|
-
else:
|
|
555
|
-
hits, shadows = result, [] # type: ignore[assignment]
|
|
422
|
+
hits = retrieve(types, args.key, args.limit)
|
|
556
423
|
if args.format == "json":
|
|
557
|
-
payload = {"hits": [h.as_dict() for h in hits]
|
|
558
|
-
"shadows": [s.as_dict() for s in shadows]}
|
|
424
|
+
payload = {"hits": [h.as_dict() for h in hits]}
|
|
559
425
|
print(json.dumps(payload, indent=2, default=str))
|
|
560
426
|
else:
|
|
561
427
|
if not hits:
|
|
@@ -563,13 +429,6 @@ def main() -> int:
|
|
|
563
429
|
for h in hits:
|
|
564
430
|
print(f" [{h.source}] {h.type} score={h.score:.2f} "
|
|
565
431
|
f"id={h.id or '-'} path={h.path}")
|
|
566
|
-
if shadows:
|
|
567
|
-
print(f"\n shadows: {len(shadows)} operational entr"
|
|
568
|
-
f"{'y' if len(shadows) == 1 else 'ies'} suppressed by "
|
|
569
|
-
f"the conflict rule")
|
|
570
|
-
for s in shadows:
|
|
571
|
-
print(f" [{s.reason}] {s.type} id={s.id} "
|
|
572
|
-
f"op={s.operational_path} repo={s.repo_path}")
|
|
573
432
|
return 0
|
|
574
433
|
|
|
575
434
|
|