@event4u/agent-config 8.4.0 → 8.5.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 +1 -1
- package/CHANGELOG.md +73 -0
- package/README.md +17 -5
- package/dist/agent-src/README.md +1 -1
- package/dist/agent-src/commands/{analyze-reference-repo.md → analyze/reference-repo.md} +3 -1
- package/dist/agent-src/commands/analyze.md +9 -6
- package/dist/agent-src/commands/{bug-fix.md → bug/fix.md} +3 -1
- package/dist/agent-src/commands/{bug-investigate.md → bug/investigate.md} +4 -2
- package/dist/agent-src/commands/bug.md +59 -0
- package/dist/agent-src/commands/{set-cost-profile.md → cost/profile.md} +9 -6
- package/dist/agent-src/commands/{cost-report.md → cost/report.md} +18 -17
- package/dist/agent-src/commands/cost.md +57 -0
- package/dist/agent-src/commands/evals/{review-changes.json → review.json} +12 -8
- package/dist/agent-src/commands/{quality-fix.md → fix/quality.md} +5 -2
- package/dist/agent-src/commands/fix.md +7 -4
- package/dist/agent-src/commands/optimize/project.md +250 -0
- package/dist/agent-src/commands/{optimize-prompt.md → optimize/prompt.md} +3 -2
- package/dist/agent-src/commands/optimize.md +17 -12
- package/dist/agent-src/commands/{package-reset.md → package/reset.md} +2 -0
- package/dist/agent-src/commands/{package-test.md → package/test.md} +3 -1
- package/dist/agent-src/commands/package.md +56 -0
- package/dist/agent-src/commands/{project-analyze.md → project/analyze.md} +10 -8
- package/dist/agent-src/commands/{project-health.md → project/health.md} +2 -0
- package/dist/agent-src/commands/project.md +60 -0
- package/dist/agent-src/commands/{review-changes.md → review/changes.md} +26 -24
- package/dist/agent-src/commands/{review-routing.md → review/routing.md} +21 -17
- package/dist/agent-src/commands/review.md +61 -0
- package/dist/agent-src/commands/security-audit-config.md +24 -23
- package/dist/agent-src/commands/{sync-agent-settings.md → sync/agent-settings.md} +11 -7
- package/dist/agent-src/commands/{sync-gitignore → sync/gitignore}/fix.md +36 -27
- package/dist/agent-src/commands/{sync-gitignore.md → sync/gitignore.md} +22 -21
- package/dist/agent-src/commands/sync.md +58 -0
- package/dist/agent-src/commands/{e2e-heal.md → tests/e2e-heal.md} +5 -2
- package/dist/agent-src/commands/{e2e-plan.md → tests/e2e-plan.md} +5 -2
- package/dist/agent-src/commands/tests.md +11 -5
- package/dist/agent-src/contexts/augment-infrastructure.md +3 -3
- package/dist/agent-src/contexts/execution/toolchain-resolver.md +1 -1
- package/dist/agent-src/contexts/skills-and-commands.md +2 -2
- package/dist/agent-src/rules/external-reference-deep-dive.md +1 -1
- package/dist/agent-src/skills/finishing-a-development-branch/SKILL.md +2 -2
- package/dist/agent-src/skills/judge-artifact-completeness/SKILL.md +1 -1
- package/dist/agent-src/skills/judge-bug-hunter/SKILL.md +1 -1
- package/dist/agent-src/skills/judge-code-quality/SKILL.md +1 -1
- package/dist/agent-src/skills/judge-security-auditor/SKILL.md +1 -1
- package/dist/agent-src/skills/judge-synthesis/SKILL.md +1 -1
- package/dist/agent-src/skills/judge-test-coverage/SKILL.md +1 -1
- package/dist/agent-src/skills/markitdown/SKILL.md +1 -1
- package/dist/agent-src/skills/mobile-e2e-strategy/SKILL.md +5 -5
- package/dist/agent-src/skills/prompt-optimizer/SKILL.md +2 -2
- package/dist/agent-src/skills/quality-tools/SKILL.md +1 -1
- package/dist/agent-src/skills/repomix-packer/SKILL.md +1 -1
- package/dist/agent-src/skills/requesting-code-review/SKILL.md +2 -2
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +2 -2
- package/dist/agent-src/templates/agent-settings.md +1 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +2 -2
- package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +1 -1
- package/dist/cli/commands/config.js +44 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/uiServe.js +1 -0
- package/dist/cli/commands/uiServe.js.map +1 -1
- package/dist/cli/help.js +38 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/initRouting.js +16 -1
- package/dist/cli/initRouting.js.map +1 -1
- package/dist/cli/main.js +43 -37
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/registry.js +45 -44
- package/dist/cli/registry.js.map +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +419 -210
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +4 -4
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +31 -25
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +30 -24
- package/dist/install/install.mjs +6064 -159
- package/dist/install/selectedTools.js +39 -10
- package/dist/install/selectedTools.js.map +1 -1
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/server/app.js +15 -3
- package/dist/server/app.js.map +1 -1
- package/dist/server/routes/ping.js +16 -0
- package/dist/server/routes/ping.js.map +1 -1
- package/dist/server/routes/settings.js +42 -6
- package/dist/server/routes/settings.js.map +1 -1
- package/dist/server/routes/settingsChanges.js +55 -0
- package/dist/server/routes/settingsChanges.js.map +1 -0
- package/dist/server/routes/userMd.js +7 -2
- package/dist/server/routes/userMd.js.map +1 -1
- package/dist/server/routes/wizard.js +76 -14
- package/dist/server/routes/wizard.js.map +1 -1
- package/dist/server/writeRoot.js +17 -0
- package/dist/server/writeRoot.js.map +1 -1
- package/dist/shared/settingsSurface.js +120 -0
- package/dist/shared/settingsSurface.js.map +1 -0
- package/dist/ui/assets/index-BPFMZWUs.css +1 -0
- package/dist/ui/assets/index-BiEZiZ31.js +40 -0
- package/dist/ui/assets/index-BiEZiZ31.js.map +1 -0
- package/dist/ui/index.html +15 -2
- package/docs/CLAIMS.md +20 -15
- package/docs/architecture.md +1 -1
- package/docs/catalog.md +37 -31
- package/docs/command-flows.md +22 -16
- package/docs/contracts/command-clusters.md +39 -9
- package/docs/contracts/file-ownership-matrix.json +21 -0
- package/docs/contracts/frontmatter-contract.md +1 -1
- package/docs/contracts/gui-wizard.md +16 -4
- package/docs/contracts/iron-law-overrides.txt +1 -0
- package/docs/contracts/rule-classification.md +2 -2
- package/docs/contracts/settings-api.md +58 -2
- package/docs/contracts/surface-matrix.md +5 -0
- package/docs/cookbook.md +7 -7
- package/docs/decisions/ADR-114-commit-policy-iron-law-override.md +74 -0
- package/docs/decisions/ADR-115-command-cluster-consolidation-phase-4.md +91 -0
- package/docs/decisions/INDEX.md +2 -0
- package/docs/featured-commands.md +1 -1
- package/docs/featured-skills.md +4 -4
- package/docs/flows.md +2 -2
- package/docs/getting-started-by-role.md +1 -1
- package/docs/getting-started-laravel.md +1 -1
- package/docs/getting-started.md +6 -3
- package/docs/governance-advantage.md +1 -1
- package/docs/guidelines/agent-infra/5w2h-analysis.md +1 -1
- package/docs/guidelines/agent-infra/ios-simulator-guide.md +1 -1
- package/docs/proof.md +11 -2
- package/package.json +6 -1
- package/src/agent-src/README.md +64 -0
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +497 -0
- package/src/agent-src/scripts/scan-seeder-violations.php +145 -0
- package/src/agent-src/scripts/update_roadmap_progress.ts +1087 -0
- package/src/agent-src/templates/AGENTS.md +34 -0
- package/src/agent-src/templates/scripts/README.md +195 -0
- package/src/agent-src/templates/scripts/check_memory.ts +662 -0
- package/src/agent-src/templates/scripts/check_memory_proposal.ts +370 -0
- package/src/agent-src/templates/scripts/historical-bug-patterns.example.yml +84 -0
- package/src/agent-src/templates/scripts/implement_ticket/__main__.ts +27 -0
- package/src/agent-src/templates/scripts/memory_hash.ts +355 -0
- package/src/agent-src/templates/scripts/memory_lookup.ts +1089 -0
- package/src/agent-src/templates/scripts/memory_report.ts +868 -0
- package/src/agent-src/templates/scripts/memory_signal.ts +444 -0
- package/src/agent-src/templates/scripts/memory_status.ts +215 -0
- package/src/agent-src/templates/scripts/ownership-map.example.yml +87 -0
- package/src/agent-src/templates/scripts/pr-risk-config.example.yml +76 -0
- package/src/agent-src/templates/scripts/pr_review_routing.ts +544 -0
- package/src/agent-src/templates/scripts/pr_risk_review.ts +0 -0
- package/src/agent-src/templates/scripts/telemetry/aggregator.ts +0 -0
- package/src/agent-src/templates/scripts/telemetry/boundary.ts +164 -0
- package/src/agent-src/templates/scripts/telemetry/engagement.ts +479 -0
- package/src/agent-src/templates/scripts/telemetry/report_renderer.ts +394 -0
- package/src/agent-src/templates/scripts/telemetry/settings.ts +210 -0
- package/src/agent-src/templates/scripts/telemetry_record.ts +276 -0
- package/src/agent-src/templates/scripts/telemetry_report.ts +211 -0
- package/src/agent-src/templates/scripts/telemetry_status.ts +333 -0
- package/src/agent-src/templates/scripts/tier_usage_report.ts +619 -0
- package/src/agent-src/templates/scripts/work_engine/__main__.ts +14 -0
- package/src/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +1261 -0
- package/src/agent-src/templates/scripts/work_engine/_lib/user_global_paths.ts +329 -0
- package/src/agent-src/templates/scripts/work_engine/cli.ts +206 -0
- package/src/agent-src/templates/scripts/work_engine/cli_args.ts +249 -0
- package/src/agent-src/templates/scripts/work_engine/delivery_state.ts +225 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/analyze.ts +125 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/implement.ts +189 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/index.ts +94 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/memory.ts +193 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/plan.ts +267 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/refine.ts +518 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/report.ts +379 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +268 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +258 -0
- package/src/agent-src/templates/scripts/work_engine/directives/index.ts +32 -0
- package/src/agent-src/templates/scripts/work_engine/directives/mixed/contract.ts +243 -0
- package/src/agent-src/templates/scripts/work_engine/directives/mixed/index.ts +108 -0
- package/src/agent-src/templates/scripts/work_engine/directives/mixed/stitch.ts +259 -0
- package/src/agent-src/templates/scripts/work_engine/directives/mixed/ui.ts +216 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/_passthrough.ts +40 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +241 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +216 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +506 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/design.ts +325 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/index.ts +102 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +462 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/review.ts +474 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +352 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/_skipped.ts +33 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/apply.ts +213 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/index.ts +111 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/refine.ts +126 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/report.ts +112 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/test.ts +164 -0
- package/src/agent-src/templates/scripts/work_engine/dispatcher.ts +515 -0
- package/src/agent-src/templates/scripts/work_engine/emitters.ts +119 -0
- package/src/agent-src/templates/scripts/work_engine/errors.ts +24 -0
- package/src/agent-src/templates/scripts/work_engine/hook_bootstrap.ts +104 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/_chat_history_base.ts +189 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_append.ts +41 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_halt_append.ts +89 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/decision_gate.ts +193 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/decision_trace.ts +304 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/directive_set_guard.ts +110 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/halt_surface_audit.ts +118 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/index.ts +17 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/memory_visibility.ts +161 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/state_shape_validation.ts +45 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/trace.ts +134 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/context.ts +94 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/events.ts +58 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/exceptions.ts +85 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/index.ts +27 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/registry.ts +66 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/runner.ts +90 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/settings.ts +260 -0
- package/src/agent-src/templates/scripts/work_engine/input_builders.ts +260 -0
- package/src/agent-src/templates/scripts/work_engine/intent/classify.ts +466 -0
- package/src/agent-src/templates/scripts/work_engine/migration/v0_to_v1.ts +531 -0
- package/src/agent-src/templates/scripts/work_engine/orchestration.ts +366 -0
- package/src/agent-src/templates/scripts/work_engine/persona_policy.ts +97 -0
- package/src/agent-src/templates/scripts/work_engine/resolvers/diff.ts +135 -0
- package/src/agent-src/templates/scripts/work_engine/resolvers/file.ts +175 -0
- package/src/agent-src/templates/scripts/work_engine/resolvers/prompt.ts +115 -0
- package/src/agent-src/templates/scripts/work_engine/scoring/confidence.ts +415 -0
- package/src/agent-src/templates/scripts/work_engine/scoring/decision_engine.ts +466 -0
- package/src/agent-src/templates/scripts/work_engine/scoring/decision_trace.ts +298 -0
- package/src/agent-src/templates/scripts/work_engine/scoring/memory_visibility.ts +444 -0
- package/src/agent-src/templates/scripts/work_engine/stack/detect.ts +252 -0
- package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +745 -0
- package/src/agent-src/templates/scripts/work_engine/state.ts +1151 -0
- package/src/agent-src/templates/scripts/work_engine/state_io.ts +413 -0
- package/src/config/agent-settings.template.yml +1 -1
- package/src/install/atomic.ts +111 -0
- package/src/install/conflict.ts +226 -0
- package/src/install/detect.ts +280 -0
- package/src/install/paths.ts +90 -0
- package/src/install/plan.ts +205 -0
- package/src/install/preflight.ts +210 -0
- package/src/install/selectedTools.ts +80 -0
- package/src/install/toolDetection.ts +120 -0
- package/src/install/txlog.ts +161 -0
- package/src/install/types.ts +175 -0
- package/src/install/wizard-plan.ts +216 -0
- package/src/scripts/_cli/cmd_converge.ts +38 -0
- package/src/scripts/_cli/cmd_doctor.ts +113 -2
- package/src/scripts/_cli/cmd_explain.ts +2 -2
- package/src/scripts/_cli/cmd_export.ts +3 -3
- package/src/scripts/_cli/cmd_fleet.ts +13 -1
- package/src/scripts/_cli/cmd_migrate.ts +3 -3
- package/src/scripts/_cli/cmd_prune.ts +2 -2
- package/src/scripts/_cli/cmd_refresh.ts +4 -4
- package/src/scripts/_cli/cmd_settings_check.ts +2 -2
- package/src/scripts/_cli/cmd_settings_migrate.ts +2 -2
- package/src/scripts/_cli/cmd_sync.ts +2 -2
- package/src/scripts/_cli/cmd_uninstall.ts +2 -2
- package/src/scripts/_cli/cmd_update.ts +8 -8
- package/src/scripts/_cli/cmd_upgrade.ts +4 -3
- package/src/scripts/_cli/cmd_validate.ts +3 -3
- package/src/scripts/_cli/cmd_versions.ts +2 -2
- package/src/scripts/_cli/explain_last/assumptions.ts +2 -2
- package/src/scripts/_cli/explain_last/council.ts +2 -2
- package/src/scripts/_cli/explain_last/halt.ts +2 -2
- package/src/scripts/_cli/explain_last/index.ts +2 -2
- package/src/scripts/_cli/explain_last/inputs.ts +4 -4
- package/src/scripts/_cli/explain_last/memory.ts +2 -2
- package/src/scripts/_cli/explain_last/provider.ts +2 -2
- package/src/scripts/_cli/explain_last/render.ts +2 -2
- package/src/scripts/_cli/explain_last/route.ts +2 -2
- package/src/scripts/_cli/explain_last/scrubber.ts +2 -2
- package/src/scripts/_cli/explain_last/sections/assumptions.ts +2 -2
- package/src/scripts/_cli/explain_last/sections/council.ts +2 -2
- package/src/scripts/_cli/explain_last/sections/halt.ts +2 -2
- package/src/scripts/_cli/explain_last/sections/header.ts +2 -2
- package/src/scripts/_cli/explain_last/sections/index.ts +1 -1
- package/src/scripts/_cli/explain_last/sections/inputs.ts +2 -2
- package/src/scripts/_cli/explain_last/sections/memory.ts +2 -2
- package/src/scripts/_cli/explain_last/sections/pack.ts +2 -2
- package/src/scripts/_cli/explain_last/sections/provider.ts +2 -2
- package/src/scripts/_cli/explain_last/sections/route.ts +2 -2
- package/src/scripts/_cli/explain_last/state_loader.ts +2 -2
- package/src/scripts/_dispatch.bash +20 -1
- package/src/scripts/_lib/agent_settings.ts +1 -1
- package/src/scripts/_lib/agent_src.ts +7 -7
- package/src/scripts/_lib/agents_overlay.ts +1 -1
- package/src/scripts/_lib/bench_ab_cache.ts +1 -1
- package/src/scripts/_lib/bench_ab_scoring.ts +1 -1
- package/src/scripts/_lib/bench_ab_scoring_v2.ts +3 -3
- package/src/scripts/_lib/bench_cost.ts +1 -1
- package/src/scripts/_lib/bench_quality.ts +3 -3
- package/src/scripts/_lib/bench_report.ts +4 -4
- package/src/scripts/_lib/bench_telegraph.ts +1 -1
- package/src/scripts/_lib/bench_telegraph_report.ts +4 -4
- package/src/scripts/_lib/changelog_eras.ts +1 -1
- package/src/scripts/_lib/claude_desktop_bundler.ts +1 -1
- package/src/scripts/_lib/cli_wrapper.ts +1 -1
- package/src/scripts/_lib/fs_atomic.ts +1 -1
- package/src/scripts/_lib/global_deploy_inventory.ts +1 -1
- package/src/scripts/_lib/install_layout.ts +1 -1
- package/src/scripts/_lib/install_regenerator.ts +1 -1
- package/src/scripts/_lib/installed_lock.ts +1 -1
- package/src/scripts/_lib/installed_tools.ts +1 -1
- package/src/scripts/_lib/json_pointers.ts +1 -1
- package/src/scripts/_lib/knowledge_global.ts +1 -1
- package/src/scripts/_lib/knowledge_global_promote.ts +2 -2
- package/src/scripts/_lib/knowledge_global_redaction.ts +2 -2
- package/src/scripts/_lib/link_crypto.ts +4 -4
- package/src/scripts/_lib/linked_projects.ts +4 -4
- package/src/scripts/_lib/model_tier.ts +1 -1
- package/src/scripts/_lib/module_detection.ts +2 -2
- package/src/scripts/_lib/pin_resolver.ts +1 -1
- package/src/scripts/_lib/script_output.ts +1 -1
- package/src/scripts/_lib/security_lint.ts +1 -1
- package/src/scripts/_lib/surface_tiers.ts +1 -1
- package/src/scripts/_lib/text_similarity.ts +1 -1
- package/src/scripts/_lib/update_check.ts +2 -2
- package/src/scripts/_lib/user_global_paths.ts +1 -1
- package/src/scripts/_lib/value_ladder.ts +1 -1
- package/src/scripts/_lib/value_report.ts +1 -1
- package/src/scripts/_lib/zip_min.ts +2 -2
- package/src/scripts/adoption_report.ts +3 -3
- package/src/scripts/adoption_snapshot.ts +3 -3
- package/src/scripts/adoption_status.ts +2 -2
- package/src/scripts/adr/regenerate_index.ts +3 -3
- package/src/scripts/ai_council/_default_prices.ts +2 -3
- package/src/scripts/ai_council/advisors.ts +1 -1
- package/src/scripts/ai_council/airgap.ts +1 -1
- package/src/scripts/ai_council/budget_guard.ts +1 -1
- package/src/scripts/ai_council/bundler.ts +1 -1
- package/src/scripts/ai_council/cli_hints.ts +1 -1
- package/src/scripts/ai_council/clients.ts +5 -5
- package/src/scripts/ai_council/compile_corpus.ts +1 -1
- package/src/scripts/ai_council/confidence_gate.ts +1 -1
- package/src/scripts/ai_council/config.ts +1 -1
- package/src/scripts/ai_council/consensus.ts +1 -1
- package/src/scripts/ai_council/events_log.ts +1 -1
- package/src/scripts/ai_council/learn_low_impact_preview.ts +1 -1
- package/src/scripts/ai_council/low_impact_corpus.ts +1 -1
- package/src/scripts/ai_council/low_impact_intake.ts +1 -1
- package/src/scripts/ai_council/modes.ts +1 -1
- package/src/scripts/ai_council/orchestrator.ts +3 -3
- package/src/scripts/ai_council/pricing.ts +2 -2
- package/src/scripts/ai_council/probation_gate.ts +2 -2
- package/src/scripts/ai_council/project_context.ts +1 -1
- package/src/scripts/ai_council/prompts.ts +1 -1
- package/src/scripts/ai_council/redact_low_impact_entry.ts +2 -2
- package/src/scripts/ai_council/session.ts +1 -1
- package/src/scripts/ai_council/shadow_dispatch.ts +1 -1
- package/src/scripts/ai_council/solo_dispatch.ts +1 -1
- package/src/scripts/analysis_freshness.ts +2 -2
- package/src/scripts/annotate_discovery.ts +2 -2
- package/src/scripts/apply_modules_config.ts +2 -2
- package/src/scripts/audit_adr_coverage.ts +3 -3
- package/src/scripts/audit_auto_rules.ts +2 -2
- package/src/scripts/audit_cloud_compatibility.ts +3 -3
- package/src/scripts/audit_command_surface.ts +2 -2
- package/src/scripts/audit_initial_context.ts +7 -6
- package/src/scripts/audit_likelihood.ts +5 -5
- package/src/scripts/audit_mcp_tools.ts +3 -3
- package/src/scripts/audit_overlap.ts +2 -2
- package/src/scripts/audit_skill_descriptions.ts +2 -2
- package/src/scripts/audit_skill_overlap.ts +5 -5
- package/src/scripts/audit_user_type_axis.ts +4 -4
- package/src/scripts/backfill_model_tier.ts +1 -1
- package/src/scripts/bench_ab_cache_dispatch.ts +2 -2
- package/src/scripts/bench_ab_clone.ts +2 -2
- package/src/scripts/bench_ab_diff.ts +3 -3
- package/src/scripts/bench_ab_integrity.ts +2 -2
- package/src/scripts/bench_ab_run.ts +4 -4
- package/src/scripts/bench_ab_task_runner.ts +1 -1
- package/src/scripts/bench_ab_tracka_run.ts +2 -2
- package/src/scripts/bench_ab_v2_run.ts +1 -1
- package/src/scripts/bench_ab_v2_stats.ts +2 -2
- package/src/scripts/bench_baseline_ready.ts +1 -1
- package/src/scripts/bench_condense_memory.ts +4 -4
- package/src/scripts/bench_drift_check.ts +3 -3
- package/src/scripts/bench_per_tool.ts +1 -1
- package/src/scripts/bench_rtk_savings.ts +4 -4
- package/src/scripts/bench_run.ts +4 -4
- package/src/scripts/bench_runner.ts +1 -1
- package/src/scripts/build_cloud_bundle.ts +3 -3
- package/src/scripts/build_discovery_manifest.ts +13 -15
- package/src/scripts/build_linear_digest.ts +2 -2
- package/src/scripts/build_mcp_registry_manifest.ts +3 -3
- package/src/scripts/build_proof.ts +6 -0
- package/src/scripts/build_rule_trigger_matrix.ts +2 -2
- package/src/scripts/capture_showcase_session.ts +3 -3
- package/src/scripts/chat_history.ts +1 -1
- package/src/scripts/check_always_budget.ts +1 -1
- package/src/scripts/check_artefact_checksums.ts +4 -4
- package/src/scripts/check_artefact_count_messaging.ts +197 -0
- package/src/scripts/check_augment_description_cap.ts +3 -3
- package/src/scripts/check_augmentignore.ts +1 -1
- package/src/scripts/check_beta_review_markers.ts +3 -3
- package/src/scripts/check_bite_sized_granularity.ts +1 -1
- package/src/scripts/check_cluster_patterns.ts +47 -3
- package/src/scripts/check_command_count_messaging.ts +2 -2
- package/src/scripts/check_condensation.ts +2 -2
- package/src/scripts/check_condensed_paths.ts +6 -6
- package/src/scripts/check_context_paths.ts +1 -1
- package/src/scripts/check_council_config_location.ts +3 -3
- package/src/scripts/check_council_layout.ts +2 -2
- package/src/scripts/check_council_references.ts +2 -2
- package/src/scripts/check_discovery_determinism.ts +3 -3
- package/src/scripts/check_gate_paths.ts +4 -4
- package/src/scripts/check_iron_law_prominence.ts +2 -2
- package/src/scripts/check_kernel_rule_bundle.ts +3 -3
- package/src/scripts/check_knowledge_cards.ts +4 -4
- package/src/scripts/check_knowledge_pages.ts +1 -1
- package/src/scripts/check_md_language.ts +3 -3
- package/src/scripts/check_memory.ts +3 -3
- package/src/scripts/check_memory_proposal.ts +2 -2
- package/src/scripts/check_module_management_neutral.ts +1 -1
- package/src/scripts/check_no_conflict_markers.ts +1 -1
- package/src/scripts/check_no_external_sources.ts +1 -1
- package/src/scripts/check_no_local_settings_committed.ts +2 -2
- package/src/scripts/check_no_new_legacy_path.ts +3 -3
- package/src/scripts/check_no_roadmap_refs.ts +3 -3
- package/src/scripts/check_one_off_location.ts +3 -3
- package/src/scripts/check_overlay_cascade_subdirs.ts +3 -3
- package/src/scripts/check_portability.ts +3 -3
- package/src/scripts/check_proposal.ts +0 -0
- package/src/scripts/check_public_catalog_links.ts +1 -1
- package/src/scripts/check_public_links.ts +2 -2
- package/src/scripts/check_references.ts +3 -3
- package/src/scripts/check_release_includes_discovery.ts +2 -2
- package/src/scripts/check_release_pr_shape.ts +2 -2
- package/src/scripts/check_release_published.ts +1 -1
- package/src/scripts/check_release_trunk_sync.ts +3 -3
- package/src/scripts/check_reply_consistency.ts +1 -1
- package/src/scripts/check_roadmap_trackable.ts +2 -2
- package/src/scripts/check_role_doc_links.ts +2 -2
- package/src/scripts/check_safety_floor_untouched.ts +3 -3
- package/src/scripts/check_skill_requires.ts +2 -2
- package/src/scripts/check_structural_breaking.ts +2 -2
- package/src/scripts/check_surface_tiers.ts +2 -2
- package/src/scripts/check_template_pin_drift.ts +1 -1
- package/src/scripts/check_test_coverage_diff.ts +2 -2
- package/src/scripts/check_token_optimizer_freshness.ts +3 -3
- package/src/scripts/check_trigger_evals.ts +2 -2
- package/src/scripts/check_update_banner.ts +1 -1
- package/src/scripts/ci_status.ts +0 -0
- package/src/scripts/ci_summary.ts +3 -3
- package/src/scripts/ci_time_ratio.ts +1 -1
- package/src/scripts/command_suggester/cooldown.ts +1 -1
- package/src/scripts/command_suggester/index.ts +1 -1
- package/src/scripts/command_suggester/loader.ts +1 -1
- package/src/scripts/command_suggester/match.ts +1 -1
- package/src/scripts/command_suggester/rank.ts +1 -1
- package/src/scripts/command_suggester/render.ts +1 -1
- package/src/scripts/command_suggester/sanitize.ts +1 -1
- package/src/scripts/command_suggester/settings.ts +1 -1
- package/src/scripts/command_suggester/types.ts +0 -0
- package/src/scripts/compile_router.ts +4 -4
- package/src/scripts/condense.ts +3 -3
- package/src/scripts/condense_memory.ts +1 -1
- package/src/scripts/config/index.ts +1 -1
- package/src/scripts/config/packs.ts +1 -1
- package/src/scripts/config/presets.ts +1 -1
- package/src/scripts/config/profile_explain.ts +1 -1
- package/src/scripts/config/profiles.ts +1 -1
- package/src/scripts/config/session_profiles.ts +1 -1
- package/src/scripts/context_hygiene_hook.ts +2 -2
- package/src/scripts/cost_by_conversation.ts +1 -1
- package/src/scripts/cost_summary.ts +2 -2
- package/src/scripts/council_cli.ts +1 -1
- package/src/scripts/council_prune.ts +1 -1
- package/src/scripts/cross_repo_retrieve.ts +2 -2
- package/src/scripts/discovery_stats.ts +3 -3
- package/src/scripts/evidence_report.ts +4 -4
- package/src/scripts/extract_audit_patterns.ts +2 -2
- package/src/scripts/first_run_gate_hook.ts +1 -1
- package/src/scripts/gen_discovery_baseline.ts +2 -2
- package/src/scripts/generate_capabilities_index.ts +2 -2
- package/src/scripts/generate_capability_matrix.ts +2 -2
- package/src/scripts/generate_catalog.ts +2 -2
- package/src/scripts/generate_command_flows.ts +2 -2
- package/src/scripts/generate_cookbook.ts +1 -1
- package/src/scripts/generate_index.ts +3 -3
- package/src/scripts/generate_ownership_matrix.ts +2 -2
- package/src/scripts/generate_pack_manifests.ts +2 -2
- package/src/scripts/generate_role_experiences_catalog.ts +3 -3
- package/src/scripts/hooks/block_no_verify.ts +2 -2
- package/src/scripts/hooks/dispatch_hook.ts +2 -2
- package/src/scripts/hooks/dispatch_issues.ts +1 -1
- package/src/scripts/hooks/envelope.ts +1 -1
- package/src/scripts/hooks/replay_hook.ts +1 -1
- package/src/scripts/hooks/state_io.ts +1 -1
- package/src/scripts/hooks_doctor.ts +1 -1
- package/src/scripts/hooks_status.ts +1 -1
- package/src/scripts/injection_scan_hook.ts +1 -1
- package/src/scripts/install.ts +145 -3
- package/src/scripts/inventory_abstraction_budget.ts +5 -5
- package/src/scripts/inventory_frontmatter.ts +1 -1
- package/src/scripts/inventory_meta_layers.ts +1 -1
- package/src/scripts/iron_law_sha.ts +1 -1
- package/src/scripts/knowledge_global_cli.ts +3 -3
- package/src/scripts/linked_projects_list.ts +1 -1
- package/src/scripts/lint_agent_security.ts +4 -4
- package/src/scripts/lint_agent_skill_names.ts +1 -1
- package/src/scripts/lint_agents_layout.ts +2 -2
- package/src/scripts/lint_agents_md.ts +3 -3
- package/src/scripts/lint_archived_skills.ts +2 -2
- package/src/scripts/lint_artefact_frontmatter.ts +2 -2
- package/src/scripts/lint_bench_ab.ts +2 -2
- package/src/scripts/lint_bench_corpus.ts +1 -1
- package/src/scripts/lint_command_flow_coverage.ts +1 -1
- package/src/scripts/lint_command_routing.ts +2 -2
- package/src/scripts/lint_command_tiers.ts +1 -1
- package/src/scripts/lint_command_verbs.ts +4 -4
- package/src/scripts/lint_commit_subjects.ts +2 -2
- package/src/scripts/lint_context_spine_usage.ts +3 -3
- package/src/scripts/lint_discovery_manifest.ts +4 -4
- package/src/scripts/lint_discovery_vocabulary.ts +2 -2
- package/src/scripts/lint_empty_roadmaps.ts +3 -3
- package/src/scripts/lint_eval_freshness.ts +2 -2
- package/src/scripts/lint_examples.ts +2 -2
- package/src/scripts/lint_explain_trace.ts +2 -2
- package/src/scripts/lint_featured_skills.ts +0 -0
- package/src/scripts/lint_flows.ts +5 -5
- package/src/scripts/lint_framework_leakage.ts +1 -1
- package/src/scripts/lint_framework_leakage_allowlist.json +10 -2
- package/src/scripts/lint_frontmatter_boilerplate.ts +2 -2
- package/src/scripts/lint_ghostwriter_source.ts +3 -3
- package/src/scripts/lint_global_paths.ts +1 -1
- package/src/scripts/lint_handoffs.ts +1 -1
- package/src/scripts/lint_hidden_unicode.ts +1 -1
- package/src/scripts/lint_hook_concern_budget.ts +2 -2
- package/src/scripts/lint_hook_manifest.ts +2 -2
- package/src/scripts/lint_instruction_smuggling.ts +1 -1
- package/src/scripts/lint_load_context.ts +2 -2
- package/src/scripts/lint_marketplace.ts +1 -1
- package/src/scripts/lint_marketplace_install_completeness.ts +2 -2
- package/src/scripts/lint_mcp_config_security.ts +1 -1
- package/src/scripts/lint_mcp_registry_manifest.ts +1 -1
- package/src/scripts/lint_media_policy_linkage.ts +3 -3
- package/src/scripts/lint_missions.ts +3 -3
- package/src/scripts/lint_model_tier_coverage.ts +3 -3
- package/src/scripts/lint_namespace.ts +2 -2
- package/src/scripts/lint_namespace_collisions.ts +2 -2
- package/src/scripts/lint_new_skill_gate.ts +3 -3
- package/src/scripts/lint_no_new_atomic_commands.ts +9 -6
- package/src/scripts/lint_no_python_twin_rationale.ts +102 -0
- package/src/scripts/lint_one_off_age.ts +2 -2
- package/src/scripts/lint_orchestration_dsl.ts +3 -3
- package/src/scripts/lint_orchestrator_auto_detect.ts +1 -1
- package/src/scripts/lint_pack_boundaries.ts +2 -2
- package/src/scripts/lint_pack_dependencies.ts +2 -2
- package/src/scripts/lint_pack_first_win.ts +2 -2
- package/src/scripts/lint_persona_governance.ts +3 -3
- package/src/scripts/lint_positioning.ts +1 -1
- package/src/scripts/lint_profile_overlay_set_only.ts +2 -2
- package/src/scripts/lint_readme_jargon.ts +2 -2
- package/src/scripts/lint_readme_size.ts +2 -2
- package/src/scripts/lint_regression.ts +3 -3
- package/src/scripts/lint_roadmap_ci_steps.ts +3 -3
- package/src/scripts/lint_roadmap_complexity.ts +1 -1
- package/src/scripts/lint_roadmap_later_disposition.ts +2 -2
- package/src/scripts/lint_role_experiences.ts +2 -2
- package/src/scripts/lint_rule_interactions.ts +1 -1
- package/src/scripts/lint_rule_tiers.ts +1 -1
- package/src/scripts/lint_showcase_sessions.ts +2 -2
- package/src/scripts/lint_skill_frontmatter_safety.ts +1 -1
- package/src/scripts/lint_skill_originality.ts +1 -1
- package/src/scripts/lint_skill_tools.ts +3 -3
- package/src/scripts/lint_ticket_buildable.ts +2 -2
- package/src/scripts/lint_topics_yaml.ts +3 -3
- package/src/scripts/lint_trust_coherence.ts +3 -3
- package/src/scripts/lint_value_dashboard.ts +3 -3
- package/src/scripts/lint_workflow_security.ts +3 -3
- package/src/scripts/lint_workspace_boundary.ts +2 -2
- package/src/scripts/mcp_parity_smoke.ts +1 -1
- package/src/scripts/mcp_render.ts +1 -1
- package/src/scripts/mcp_server/catalog.ts +1 -1
- package/src/scripts/mcp_server/index.ts +1 -1
- package/src/scripts/mcp_server/metadata.ts +1 -1
- package/src/scripts/mcp_server/prompts.ts +1 -1
- package/src/scripts/mcp_server/resources.ts +1 -1
- package/src/scripts/mcp_server/server.ts +1 -1
- package/src/scripts/mcp_server/telemetry.ts +1 -1
- package/src/scripts/mcp_server/tools.ts +1 -1
- package/src/scripts/mcp_telemetry_health.ts +1 -1
- package/src/scripts/mcp_telemetry_query.ts +3 -3
- package/src/scripts/mcp_telemetry_store.ts +3 -3
- package/src/scripts/measure_augment_budget.ts +2 -2
- package/src/scripts/measure_density.ts +2 -2
- package/src/scripts/measure_frugality_savings.ts +3 -3
- package/src/scripts/measure_markitdown_lift.ts +1 -1
- package/src/scripts/measure_patterns.ts +1 -1
- package/src/scripts/measure_projection_bytes.ts +1 -1
- package/src/scripts/measure_rule_budget.ts +1 -1
- package/src/scripts/measure_skill_reduction.ts +1 -1
- package/src/scripts/memory_hash.ts +1 -1
- package/src/scripts/memory_lookup.ts +6 -6
- package/src/scripts/memory_report.ts +1 -1
- package/src/scripts/memory_signal.ts +1 -1
- package/src/scripts/memory_status.ts +2 -2
- package/src/scripts/migrate_command_suggestions.ts +1 -1
- package/src/scripts/migrate_frontmatter_defaults.ts +1 -1
- package/src/scripts/mine_session.ts +3 -3
- package/src/scripts/minimal_safe_diff_hook.ts +2 -2
- package/src/scripts/move_artefact.ts +3 -3
- package/src/scripts/new_skill.ts +3 -3
- package/src/scripts/onboarding_gate_hook.ts +2 -2
- package/src/scripts/pack_mcp_content.ts +1 -1
- package/src/scripts/parity/replay.ts +3 -2
- package/src/scripts/pattern_share.ts +2 -2
- package/src/scripts/plan_physical_move.ts +3 -3
- package/src/scripts/prediction-pool/poisson_sim.ts +2 -2
- package/src/scripts/prediction-pool/pool_winsim.ts +2 -2
- package/src/scripts/prediction-pool/score_ev.ts +2 -2
- package/src/scripts/prepack-check.mjs +108 -5
- package/src/scripts/print_required_checks.ts +2 -2
- package/src/scripts/probe_projection_fidelity.ts +2 -2
- package/src/scripts/probe_skill_registration.ts +2 -2
- package/src/scripts/profile_staleness_hook.ts +2 -2
- package/src/scripts/profile_use.ts +2 -2
- package/src/scripts/project_thin_rules.ts +3 -3
- package/src/scripts/propose_modules_config.ts +2 -2
- package/src/scripts/prototype_lint_contradictions.ts +2 -2
- package/src/scripts/prove_pack_extractable.ts +1 -1
- package/src/scripts/readme_linter.ts +4 -4
- package/src/scripts/redact_hook_capture.ts +2 -2
- package/src/scripts/refine_ticket_detect.ts +1 -1
- package/src/scripts/release.ts +30 -19
- package/src/scripts/render_benchmark_md.ts +2 -2
- package/src/scripts/render_value_md.ts +2 -2
- package/src/scripts/roadmap_progress_hook.ts +1 -1
- package/src/scripts/router_telemetry.ts +1 -1
- package/src/scripts/run_skill_evals.ts +2 -2
- package/src/scripts/runtime_dispatcher.ts +1 -1
- package/src/scripts/runtime_handler.ts +1 -1
- package/src/scripts/runtime_registry.ts +3 -3
- package/src/scripts/score_skill_selection.ts +1 -1
- package/src/scripts/security_audit_config.ts +2 -2
- package/src/scripts/skill_collision_clusters.ts +1 -1
- package/src/scripts/skill_discovery.ts +1 -1
- package/src/scripts/skill_linter.ts +8 -7
- package/src/scripts/skill_overlap.ts +2 -2
- package/src/scripts/skill_preview.ts +1 -1
- package/src/scripts/skill_tools/audit_persona_coverage.ts +3 -3
- package/src/scripts/skill_tools/audit_user_type_coverage.ts +3 -3
- package/src/scripts/skill_tools/index.ts +1 -1
- package/src/scripts/skill_tools/run_block_d_eval.ts +3 -3
- package/src/scripts/skill_tools/score_skill_relevance.ts +3 -3
- package/src/scripts/skill_tools/suggest_skill_for_task.ts +3 -3
- package/src/scripts/skill_trigger_eval.ts +2 -2
- package/src/scripts/skill_usage_collect.ts +2 -2
- package/src/scripts/skill_usage_report.ts +2 -2
- package/src/scripts/smoke_path_resolution.ts +2 -2
- package/src/scripts/smoke_quickstart.ts +4 -4
- package/src/scripts/snapshot_agent_outputs.ts +2 -2
- package/src/scripts/spotcheck_thin_root.ts +2 -2
- package/src/scripts/surface-tiers.yml +1 -0
- package/src/scripts/sync_agent_settings.ts +2 -2
- package/src/scripts/sync_github_metadata.ts +4 -4
- package/src/scripts/sync_gitignore.ts +3 -3
- package/src/scripts/sync_yaml_rt.ts +2 -2
- package/src/scripts/telegraph_stats.ts +1 -1
- package/src/scripts/tool_registry.ts +2 -2
- package/src/scripts/tools/adapter_errors.ts +1 -1
- package/src/scripts/tools/base_adapter.ts +1 -1
- package/src/scripts/tools/github_adapter.ts +1 -1
- package/src/scripts/tools/jira_adapter.ts +1 -1
- package/src/scripts/trigger_coverage.ts +2 -2
- package/src/scripts/update_counts.ts +68 -6
- package/src/scripts/update_prices.ts +3 -3
- package/src/scripts/validate_agent_settings.ts +3 -3
- package/src/scripts/validate_decision_engine.ts +2 -2
- package/src/scripts/validate_discovery_manifest.ts +3 -3
- package/src/scripts/validate_frontmatter.ts +5 -5
- package/src/scripts/validate_pack_yaml.ts +0 -0
- package/src/scripts/validate_safe_paths.ts +2 -2
- package/src/scripts/validate_telegraph_carveouts.ts +3 -3
- package/src/scripts/verify_before_complete_hook.ts +2 -2
- package/src/scripts/verify_physical_move.ts +2 -2
- package/src/scripts/wrapper_freshness_hook.ts +3 -3
- package/src/server/schemas/settings.ts +426 -0
- package/src/shared/settingsSurface.ts +184 -0
- package/src/shared/userMd/formAdapter.ts +86 -0
- package/src/shared/userMd/schema.ts +59 -0
- package/src/shared/userMd/utils.ts +91 -0
- package/dist/agent-src/commands/optimize-project.md +0 -243
- package/dist/ui/assets/index-BbWWuFrF.css +0 -1
- package/dist/ui/assets/index-UjcTG_9O.js +0 -40
- package/dist/ui/assets/index-UjcTG_9O.js.map +0 -1
|
@@ -0,0 +1,1089 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* File-first memory retrieval.
|
|
4
|
+
*
|
|
5
|
+
* TypeScript twin of `src/agent-src/templates/scripts/memory_lookup.py`
|
|
6
|
+
* (ADR-200, consumer-shipped template; byte-identical to the dev-side
|
|
7
|
+
* `src/scripts/memory_lookup.py`). The public API and CLI contract mirror
|
|
8
|
+
* the Python original EXACTLY — same exported names (snake_case kept
|
|
9
|
+
* deliberately, especially `retrieve(...)` whose
|
|
10
|
+
* signature/return shape is cited by rules), same exit codes, stdout/stderr
|
|
11
|
+
* split, byte-identical messages, same memory-file scan + ranking + JSON
|
|
12
|
+
* output. No behaviour changes — latent Python bugs are replicated and
|
|
13
|
+
* flagged as divergence candidates.
|
|
14
|
+
*
|
|
15
|
+
* Implements the shared `retrieve(types, keys, limit)` abstraction used
|
|
16
|
+
* by skills. Reads YAML under `agents/memory/<type>/` (curated, hand-
|
|
17
|
+
* reviewed) and JSONL under `agents/memory/intake/*.jsonl` (agent-written,
|
|
18
|
+
* append-only, supersede-chain aware), plus user-ingested `knowledge`
|
|
19
|
+
* chunks and opted-in `cross-repo` matches.
|
|
20
|
+
*
|
|
21
|
+
* Retrieval is entirely repo-side and file-backed — there is no external
|
|
22
|
+
* backend. (The former optional `@event4u/agent-memory` package routing
|
|
23
|
+
* was removed; see `docs/decisions/` for the agent-memory removal ADR.)
|
|
24
|
+
*
|
|
25
|
+
* Usage:
|
|
26
|
+
* memory_lookup --types domain-invariants,ownership --key "app/Http/Controllers/Foo" --limit 5
|
|
27
|
+
* memory_lookup --types incident-learnings --format json
|
|
28
|
+
*
|
|
29
|
+
* import { retrieve } from './memory_lookup.js';
|
|
30
|
+
* const hits = retrieve(['ownership'], ['app/Http'], 3);
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import * as fs from 'node:fs';
|
|
34
|
+
import * as path from 'node:path';
|
|
35
|
+
import process from 'node:process';
|
|
36
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
37
|
+
import YAML, { parseDocument } from 'yaml';
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
// PyYAML implicit timestamp resolver. PyYAML's `safe_load` turns a plain
|
|
41
|
+
// `YYYY-MM-DD` scalar into a `datetime.date` (str → `YYYY-MM-DD`) and a full
|
|
42
|
+
// timestamp into a `datetime.datetime`. The `yaml` npm parses both to JS Date
|
|
43
|
+
// objects whose String()/JSON form differs, so `json.dumps(..., default=str)`
|
|
44
|
+
// in the CLI would not match. This marker preserves PyYAML's str() form so
|
|
45
|
+
// JSON output and as_dict() are byte-identical to the Python original.
|
|
46
|
+
const PYYAML_DATE_ONLY_RE = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/;
|
|
47
|
+
const PYYAML_TIMESTAMP_RE =
|
|
48
|
+
/^(?:[0-9]{4}-[0-9]{2}-[0-9]{2}|[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}(?:[Tt]|[ \t]+)[0-9]{1,2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]*)?(?:[ \t]*(?:Z|[-+][0-9]{1,2}(?::[0-9]{2})?))?)$/;
|
|
49
|
+
|
|
50
|
+
/** Marker carrying PyYAML's `str(datetime.*)` form (for JSON/default=str). */
|
|
51
|
+
class PyTimestamp {
|
|
52
|
+
constructor(readonly pyStr: string) {}
|
|
53
|
+
toString(): string {
|
|
54
|
+
return this.pyStr;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Marker for a Python `float` so json.dumps renders integer-valued floats as
|
|
60
|
+
* `0.0`/`1.0`, not `0`/`1`. The Hit `score` and the v1 `confidence` are always
|
|
61
|
+
* Python floats; JS has no int/float distinction, so this preserves byte-for-byte
|
|
62
|
+
* parity for whole-number scores.
|
|
63
|
+
*/
|
|
64
|
+
class PyFloat {
|
|
65
|
+
constructor(readonly value: number) {}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Mutable so tests can repoint them at a tmp tree (monkeypatch parity).
|
|
69
|
+
export let MEMORY_ROOT = path.join('agents', 'memory');
|
|
70
|
+
export let INTAKE_ROOT = path.join(MEMORY_ROOT, 'intake');
|
|
71
|
+
export let KNOWLEDGE_ROOT = path.join(MEMORY_ROOT, 'knowledge');
|
|
72
|
+
|
|
73
|
+
/** Test-only setters mirroring pytest monkeypatch on the module constants. */
|
|
74
|
+
export function _setMemoryRoot(p: string): void {
|
|
75
|
+
MEMORY_ROOT = p;
|
|
76
|
+
}
|
|
77
|
+
export function _setIntakeRoot(p: string): void {
|
|
78
|
+
INTAKE_ROOT = p;
|
|
79
|
+
}
|
|
80
|
+
export function _setKnowledgeRoot(p: string): void {
|
|
81
|
+
KNOWLEDGE_ROOT = p;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const CURATED_TYPES: ReadonlySet<string> = new Set([
|
|
85
|
+
'ownership',
|
|
86
|
+
'historical-patterns',
|
|
87
|
+
'domain-invariants',
|
|
88
|
+
'incident-learnings',
|
|
89
|
+
'product-rules',
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
// `knowledge` is its own type: user-ingested local documents that live
|
|
93
|
+
// under `agents/memory/knowledge/<ingest-id>/chunks/*.md`. They are
|
|
94
|
+
// repo-side (file-backed) but not "curated" and not intake.
|
|
95
|
+
export const KNOWLEDGE_TYPE = 'knowledge';
|
|
96
|
+
|
|
97
|
+
// Cross-repo retrieval. When this type is requested AND opted-in
|
|
98
|
+
// linked-project siblings exist, matches from `cross_repo_retrieve` are
|
|
99
|
+
// projected as `source="cross-repo"` Hits, scored below curated/knowledge.
|
|
100
|
+
// Opt-in by caller + lazy import → existing call sites unaffected.
|
|
101
|
+
export const CROSS_REPO_TYPE = 'cross-repo';
|
|
102
|
+
|
|
103
|
+
export type HitSource = 'curated' | 'intake' | 'knowledge' | 'cross-repo';
|
|
104
|
+
|
|
105
|
+
export class Hit {
|
|
106
|
+
id: string;
|
|
107
|
+
type: string;
|
|
108
|
+
source: string; // HitSource, kept as string to mirror the Python dataclass
|
|
109
|
+
path: string; // file (or logical locator) that produced the hit
|
|
110
|
+
score: number; // naive, content-match based [0..1]
|
|
111
|
+
entry: Record<string, unknown>;
|
|
112
|
+
|
|
113
|
+
constructor(
|
|
114
|
+
id: string,
|
|
115
|
+
type: string,
|
|
116
|
+
source: string,
|
|
117
|
+
p: string,
|
|
118
|
+
score: number,
|
|
119
|
+
entry: Record<string, unknown> = {},
|
|
120
|
+
) {
|
|
121
|
+
this.id = id;
|
|
122
|
+
this.type = type;
|
|
123
|
+
this.source = source;
|
|
124
|
+
this.path = p;
|
|
125
|
+
this.score = score;
|
|
126
|
+
this.entry = entry;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
as_dict(): Record<string, unknown> {
|
|
130
|
+
return {
|
|
131
|
+
id: this.id,
|
|
132
|
+
type: this.type,
|
|
133
|
+
source: this.source,
|
|
134
|
+
path: this.path,
|
|
135
|
+
// score is a Python float — render `0.0` not `0` for whole numbers.
|
|
136
|
+
score: new PyFloat(this.score),
|
|
137
|
+
entry: this.entry,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function _isPlainObject(v: unknown): v is Record<string, unknown> {
|
|
143
|
+
return (
|
|
144
|
+
typeof v === 'object' &&
|
|
145
|
+
v !== null &&
|
|
146
|
+
!Array.isArray(v) &&
|
|
147
|
+
!(v instanceof PyTimestamp) &&
|
|
148
|
+
!(v instanceof PyFloat)
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function _isFile(p: string): boolean {
|
|
153
|
+
try {
|
|
154
|
+
return fs.statSync(p).isFile();
|
|
155
|
+
} catch {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function _isDir(p: string): boolean {
|
|
161
|
+
try {
|
|
162
|
+
return fs.statSync(p).isDirectory();
|
|
163
|
+
} catch {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Load a YAML file the way the Python `_load_yaml` does:
|
|
170
|
+
* `yaml.safe_load(fh) or {}`. version: '1.1' matches PyYAML's default so
|
|
171
|
+
* implicit scalar typing (dates, yes/no booleans) lands identically.
|
|
172
|
+
*/
|
|
173
|
+
function _load_yaml(p: string): unknown {
|
|
174
|
+
const text = fs.readFileSync(p, 'utf-8');
|
|
175
|
+
const doc = parseDocument(text, { version: '1.1', prettyErrors: false });
|
|
176
|
+
if (doc.errors.length > 0) {
|
|
177
|
+
const err = doc.errors[0];
|
|
178
|
+
throw new Error(err ? err.message : 'YAML parse error');
|
|
179
|
+
}
|
|
180
|
+
YAML.visit(doc, {
|
|
181
|
+
Scalar(_key, node) {
|
|
182
|
+
if (!node.range) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const raw = text.slice(node.range[0], node.range[1]);
|
|
186
|
+
// Under YAML 1.1 the `yaml` lib resolves plain timestamp scalars to
|
|
187
|
+
// JS Date objects. Recover PyYAML's str() form from the original
|
|
188
|
+
// source so JSON output / as_dict() match the Python original.
|
|
189
|
+
if (node.value instanceof Date) {
|
|
190
|
+
if (PYYAML_DATE_ONLY_RE.test(raw)) {
|
|
191
|
+
(node as { value: unknown }).value = new PyTimestamp(raw);
|
|
192
|
+
} else if (PYYAML_TIMESTAMP_RE.test(raw)) {
|
|
193
|
+
(node as { value: unknown }).value = new PyTimestamp(_pyYamlDatetimeStr(raw));
|
|
194
|
+
}
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
// PyYAML's bool resolver does NOT treat bare `y/Y/n/N` as boolean
|
|
198
|
+
// (only yes/no/on/off/true/false) — restore those to strings.
|
|
199
|
+
if (typeof node.value === 'boolean' && node.type === 'PLAIN' && /^[ynYN]$/.test(raw)) {
|
|
200
|
+
(node as { value: unknown }).value = raw;
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
const data = doc.toJS({ mapAsMap: false });
|
|
205
|
+
return data == null ? {} : data;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Compute PyYAML `str(datetime.datetime(...))` for a matched timestamp scalar. */
|
|
209
|
+
function _pyYamlDatetimeStr(raw: string): string {
|
|
210
|
+
const m =
|
|
211
|
+
/^(\d{4})-(\d{1,2})-(\d{1,2})(?:[Tt]|[ \t]+)(\d{1,2}):(\d{2}):(\d{2})(?:\.(\d*))?(?:[ \t]*(Z|[-+]\d{1,2}(?::\d{2})?))?$/.exec(
|
|
212
|
+
raw,
|
|
213
|
+
);
|
|
214
|
+
if (!m) {
|
|
215
|
+
return raw;
|
|
216
|
+
}
|
|
217
|
+
const [, y, mo, d, h, mi, s, frac, tz] = m;
|
|
218
|
+
const date = `${y}-${_pad2(mo as string)}-${_pad2(d as string)}`;
|
|
219
|
+
let time = `${_pad2(h as string)}:${mi}:${s}`;
|
|
220
|
+
if (frac && /[1-9]/.test(frac)) {
|
|
221
|
+
time += `.${(frac + '000000').slice(0, 6)}`;
|
|
222
|
+
}
|
|
223
|
+
let off = '';
|
|
224
|
+
if (tz) {
|
|
225
|
+
if (tz === 'Z') {
|
|
226
|
+
off = '+00:00';
|
|
227
|
+
} else {
|
|
228
|
+
const tm = /^([-+])(\d{1,2})(?::(\d{2}))?$/.exec(tz);
|
|
229
|
+
if (tm) {
|
|
230
|
+
off = `${tm[1]}${_pad2(tm[2] as string)}:${tm[3] ?? '00'}`;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return `${date} ${time}${off}`;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function _pad2(v: string): string {
|
|
238
|
+
return v.length === 1 ? `0${v}` : v;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Yield [file, entry] pairs for curated files of `mtype`.
|
|
243
|
+
*
|
|
244
|
+
* Supports both the content-addressed layout (`agents/memory/<type>/
|
|
245
|
+
* <hash>.yml` — one entry per file) and the single-file layout
|
|
246
|
+
* (`agents/memory/<type>.yml` or `<type>/entries.yml` with an `entries:`
|
|
247
|
+
* list), so consumers can adopt either.
|
|
248
|
+
*/
|
|
249
|
+
function* _iter_curated_entries(mtype: string): Generator<[string, Record<string, unknown>]> {
|
|
250
|
+
const type_dir = path.join(MEMORY_ROOT, mtype);
|
|
251
|
+
const single_file = path.join(MEMORY_ROOT, `${mtype}.yml`);
|
|
252
|
+
if (_isFile(single_file)) {
|
|
253
|
+
const data = _load_yaml(single_file);
|
|
254
|
+
const entries = _isPlainObject(data) ? data['entries'] : null;
|
|
255
|
+
for (const e of Array.isArray(entries) ? entries : []) {
|
|
256
|
+
if (_isPlainObject(e)) {
|
|
257
|
+
yield [single_file, e];
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (_isDir(type_dir)) {
|
|
262
|
+
for (const yml of _rglobYmlSorted(type_dir)) {
|
|
263
|
+
const dataRaw = _load_yaml(yml);
|
|
264
|
+
const data = dataRaw == null ? {} : dataRaw;
|
|
265
|
+
const entries = _isPlainObject(data) ? data['entries'] : undefined;
|
|
266
|
+
if (Array.isArray(entries)) {
|
|
267
|
+
for (const e of entries) {
|
|
268
|
+
if (_isPlainObject(e)) {
|
|
269
|
+
yield [yml, e];
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
} else if (_isPlainObject(data) && data['id']) {
|
|
273
|
+
// Flat, one-entry-per-file layout (content-addressed).
|
|
274
|
+
yield [yml, data];
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Yield [file, entry] from intake JSONL, applying supersede chains. */
|
|
281
|
+
function* _iter_intake_entries(mtype: string): Generator<[string, Record<string, unknown>]> {
|
|
282
|
+
if (!_isDir(INTAKE_ROOT)) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
// Resolve supersede chains globally per file: later lines win.
|
|
286
|
+
for (const jsonl of _globJsonlSorted(INTAKE_ROOT)) {
|
|
287
|
+
const by_id = new Map<string, Record<string, unknown>>();
|
|
288
|
+
const superseded = new Set<string>();
|
|
289
|
+
const content = fs.readFileSync(jsonl, 'utf-8');
|
|
290
|
+
for (let line of content.split(/\n/)) {
|
|
291
|
+
line = line.trim();
|
|
292
|
+
if (!line) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
let obj: unknown;
|
|
296
|
+
try {
|
|
297
|
+
obj = JSON.parse(line);
|
|
298
|
+
} catch {
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
if (!_isPlainObject(obj)) {
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (obj['type'] === 'supersede') {
|
|
305
|
+
const target = obj['supersedes'];
|
|
306
|
+
if (typeof target === 'string') {
|
|
307
|
+
superseded.add(target);
|
|
308
|
+
}
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
const eid = obj['id'];
|
|
312
|
+
if (typeof eid === 'string') {
|
|
313
|
+
by_id.set(eid, obj);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
for (const [eid, obj] of by_id.entries()) {
|
|
317
|
+
if (superseded.has(eid)) {
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if (mtype && obj['entry_type'] && obj['entry_type'] !== mtype) {
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
yield [jsonl, obj];
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Yield [chunk-file, entry] pairs from `agents/memory/knowledge/`.
|
|
330
|
+
*
|
|
331
|
+
* Layout (frozen in `docs/contracts/local-knowledge-ingestion.md`):
|
|
332
|
+
*
|
|
333
|
+
* agents/memory/knowledge/<ingest-id>/
|
|
334
|
+
* manifest.json
|
|
335
|
+
* chunks/<n>.md
|
|
336
|
+
*
|
|
337
|
+
* Each chunk becomes one retrieval entry. The chunk body, the manifest
|
|
338
|
+
* source path, and pinned flag are surfaced into the entry so `_score()`
|
|
339
|
+
* can match on either the source path or the chunk text. The entry id is
|
|
340
|
+
* `<ingest-id>:<chunk-stem>` so callers can locate the exact file on disk.
|
|
341
|
+
*/
|
|
342
|
+
function* _iter_knowledge_entries(): Generator<[string, Record<string, unknown>]> {
|
|
343
|
+
if (!_isDir(KNOWLEDGE_ROOT)) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
for (const ingest_dir of _iterdirSorted(KNOWLEDGE_ROOT)) {
|
|
347
|
+
if (!_isDir(ingest_dir)) {
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
const manifest_path = path.join(ingest_dir, 'manifest.json');
|
|
351
|
+
let manifest: Record<string, unknown> = {};
|
|
352
|
+
if (_isFile(manifest_path)) {
|
|
353
|
+
try {
|
|
354
|
+
const parsed = JSON.parse(fs.readFileSync(manifest_path, 'utf-8'));
|
|
355
|
+
manifest = _isPlainObject(parsed) ? parsed : {};
|
|
356
|
+
} catch {
|
|
357
|
+
manifest = {};
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
const ingest_id = String(manifest['ingest_id'] || path.basename(ingest_dir));
|
|
361
|
+
const source = String(manifest['source'] || '');
|
|
362
|
+
const pinned = Boolean(manifest['pinned'] ?? false);
|
|
363
|
+
const chunks_dir = path.join(ingest_dir, 'chunks');
|
|
364
|
+
if (!_isDir(chunks_dir)) {
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
for (const chunk of _globMdSorted(chunks_dir)) {
|
|
368
|
+
let body: string;
|
|
369
|
+
try {
|
|
370
|
+
body = fs.readFileSync(chunk, 'utf-8');
|
|
371
|
+
} catch {
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
const entry: Record<string, unknown> = {
|
|
375
|
+
id: `${ingest_id}:${_stem(chunk)}`,
|
|
376
|
+
ingest_id,
|
|
377
|
+
source,
|
|
378
|
+
path: source,
|
|
379
|
+
body,
|
|
380
|
+
pinned,
|
|
381
|
+
source_kind: 'knowledge',
|
|
382
|
+
};
|
|
383
|
+
yield [chunk, entry];
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Naive relevance score: max over keys of (glob-match | substring).
|
|
390
|
+
*
|
|
391
|
+
* Good enough for the `absent` path where retrieval is best-effort.
|
|
392
|
+
* The `present` path returns a real score from agent-memory.
|
|
393
|
+
*/
|
|
394
|
+
export function _score(entry: Record<string, unknown>, keys: string[]): number {
|
|
395
|
+
if (keys.length === 0) {
|
|
396
|
+
return 0.1; // any hit beats no hit when there is no key
|
|
397
|
+
}
|
|
398
|
+
const hay_parts: string[] = [];
|
|
399
|
+
for (const field_name of ['path', 'key', 'symptom', 'feature', 'rule', 'body']) {
|
|
400
|
+
const v = entry[field_name];
|
|
401
|
+
if (typeof v === 'string') {
|
|
402
|
+
hay_parts.push(v);
|
|
403
|
+
} else if (Array.isArray(v)) {
|
|
404
|
+
for (const x of v) {
|
|
405
|
+
hay_parts.push(_pyStr(x));
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
const hay = hay_parts.join(' | ').toLowerCase();
|
|
410
|
+
let best = 0.0;
|
|
411
|
+
for (const k of keys) {
|
|
412
|
+
const kl = k.toLowerCase();
|
|
413
|
+
if (_fnmatch(hay, `*${kl}*`)) {
|
|
414
|
+
best = Math.max(best, 0.8);
|
|
415
|
+
} else if (hay.includes(kl)) {
|
|
416
|
+
best = Math.max(best, 0.6);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return best;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Project cross-repo matches into discounted, tagged Hits.
|
|
424
|
+
*
|
|
425
|
+
* Lazy + guarded: the Python original imports `cross_repo_retrieve` on
|
|
426
|
+
* demand and swallows any failure (script absent, no opted-in siblings) so
|
|
427
|
+
* the cross-repo type degrades to zero hits rather than breaking retrieval.
|
|
428
|
+
* The TypeScript twin of `cross_repo_retrieve` is unported in this phase, so
|
|
429
|
+
* this always degrades to zero hits — flagged as a divergence candidate.
|
|
430
|
+
* Scores sit below curated/knowledge (0.85× floor, then a small per-rank
|
|
431
|
+
* decrement) so cross-repo context never outranks the project's own truth.
|
|
432
|
+
*/
|
|
433
|
+
function _cross_repo_hits(keys: string[], limit: number): Hit[] {
|
|
434
|
+
const query = keys
|
|
435
|
+
.filter((k) => k)
|
|
436
|
+
.join(' ')
|
|
437
|
+
.trim();
|
|
438
|
+
if (!query) {
|
|
439
|
+
return [];
|
|
440
|
+
}
|
|
441
|
+
// cross_repo_retrieve has no TS twin yet, so the optional surface degrades
|
|
442
|
+
// to zero hits exactly as the Python `except` branch would when the script
|
|
443
|
+
// is absent in a consumer install.
|
|
444
|
+
void limit;
|
|
445
|
+
return [];
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Return up to `limit` hits across the requested types, highest score first.
|
|
450
|
+
*
|
|
451
|
+
* Repo entries (curated + intake) are preferred on ties — they are
|
|
452
|
+
* hand-reviewed or session-captured against the repo itself. Knowledge
|
|
453
|
+
* and cross-repo hits are discounted so the project's own truth wins on
|
|
454
|
+
* equal relevance.
|
|
455
|
+
*/
|
|
456
|
+
export function retrieve(types: string[], keys: string[], limit = 5): Hit[] {
|
|
457
|
+
const repo_hits: Hit[] = [];
|
|
458
|
+
for (const mtype of types) {
|
|
459
|
+
if (mtype === KNOWLEDGE_TYPE) {
|
|
460
|
+
for (const [p, entry] of _iter_knowledge_entries()) {
|
|
461
|
+
let base = _score(entry, keys);
|
|
462
|
+
// Pinned entries get a slight ranking boost so the
|
|
463
|
+
// `/knowledge:list --pin` flag has retrieval effect.
|
|
464
|
+
if (entry['pinned']) {
|
|
465
|
+
base = Math.min(1.0, base + 0.05);
|
|
466
|
+
}
|
|
467
|
+
repo_hits.push(
|
|
468
|
+
new Hit(
|
|
469
|
+
String(entry['id'] ?? ''),
|
|
470
|
+
KNOWLEDGE_TYPE,
|
|
471
|
+
'knowledge',
|
|
472
|
+
String(p),
|
|
473
|
+
// Discount vs curated/intake so hand-reviewed repo
|
|
474
|
+
// entries still win on equal relevance.
|
|
475
|
+
base * 0.85,
|
|
476
|
+
entry,
|
|
477
|
+
),
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
if (mtype === CROSS_REPO_TYPE) {
|
|
483
|
+
for (const h of _cross_repo_hits(keys, limit)) {
|
|
484
|
+
repo_hits.push(h);
|
|
485
|
+
}
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
if (!CURATED_TYPES.has(mtype)) {
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
for (const [p, entry] of _iter_curated_entries(mtype)) {
|
|
492
|
+
repo_hits.push(new Hit(String(entry['id'] ?? ''), mtype, 'curated', String(p), _score(entry, keys), entry));
|
|
493
|
+
}
|
|
494
|
+
for (const [p, entry] of _iter_intake_entries(mtype)) {
|
|
495
|
+
repo_hits.push(
|
|
496
|
+
new Hit(
|
|
497
|
+
String(entry['id'] ?? ''),
|
|
498
|
+
mtype,
|
|
499
|
+
'intake',
|
|
500
|
+
String(p),
|
|
501
|
+
_score(entry, keys) * 0.9, // slight discount vs curated
|
|
502
|
+
entry,
|
|
503
|
+
),
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
_sortMerged(repo_hits);
|
|
509
|
+
const positives = repo_hits.filter((h) => h.score > 0);
|
|
510
|
+
return (positives.length > 0 ? positives : repo_hits).slice(0, limit);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Mirror `repo_hits.sort(key=lambda h: (h.score, h.source == "curated"),
|
|
515
|
+
* reverse=True)` — stable descending by (score, isCurated).
|
|
516
|
+
*/
|
|
517
|
+
function _sortMerged(merged: Hit[]): void {
|
|
518
|
+
// Python's list.sort is stable. To reproduce a stable descending sort by
|
|
519
|
+
// a composite key, sort the (index, hit) pairs and compare keys; equal
|
|
520
|
+
// keys keep original order (lower index first).
|
|
521
|
+
const indexed = merged.map((h, i) => ({ h, i }));
|
|
522
|
+
indexed.sort((a, b) => {
|
|
523
|
+
if (a.h.score !== b.h.score) {
|
|
524
|
+
return b.h.score - a.h.score; // descending score
|
|
525
|
+
}
|
|
526
|
+
const ac = a.h.source === 'curated' ? 1 : 0;
|
|
527
|
+
const bc = b.h.source === 'curated' ? 1 : 0;
|
|
528
|
+
if (ac !== bc) {
|
|
529
|
+
return bc - ac; // descending: curated (True) before non-curated
|
|
530
|
+
}
|
|
531
|
+
return a.i - b.i; // stable: preserve original order
|
|
532
|
+
});
|
|
533
|
+
for (let i = 0; i < indexed.length; i += 1) {
|
|
534
|
+
merged[i] = (indexed[i] as { h: Hit }).h;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
export const _CONTRACT_VERSION = 1;
|
|
539
|
+
// Exported under the Python name too.
|
|
540
|
+
export { _CONTRACT_VERSION as CONTRACT_VERSION };
|
|
541
|
+
|
|
542
|
+
// Memory types this file-backed backend can answer. Types outside this set
|
|
543
|
+
// map to `unknown_type` per the retrieval contract.
|
|
544
|
+
const _KNOWN_TYPES: ReadonlySet<string> = new Set([...CURATED_TYPES, KNOWLEDGE_TYPE, CROSS_REPO_TYPE]);
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Return a v1 retrieval-contract envelope.
|
|
548
|
+
*
|
|
549
|
+
* Wraps {@link retrieve} and projects the internal `Hit` shape into the
|
|
550
|
+
* shape defined by `internal/schemas/retrieval-v1.schema.json`. Unknown
|
|
551
|
+
* types are reported as `status: unknown_type` for that slice only, rather
|
|
552
|
+
* than failing the whole call. All entries are file-backed (`source: "repo"`).
|
|
553
|
+
*/
|
|
554
|
+
export function retrieve_v1(types: string[], keys: string[], limit = 20): Record<string, unknown> {
|
|
555
|
+
const known = types.filter((t) => _KNOWN_TYPES.has(t));
|
|
556
|
+
const unknown = types.filter((t) => !_KNOWN_TYPES.has(t));
|
|
557
|
+
|
|
558
|
+
const hits = retrieve(known, keys, limit);
|
|
559
|
+
|
|
560
|
+
const slice_counts: Record<string, number> = {};
|
|
561
|
+
for (const t of known) {
|
|
562
|
+
slice_counts[t] = 0;
|
|
563
|
+
}
|
|
564
|
+
const entries: Record<string, unknown>[] = [];
|
|
565
|
+
for (const h of hits) {
|
|
566
|
+
const envelope_entry: Record<string, unknown> = {
|
|
567
|
+
id: h.id,
|
|
568
|
+
type: h.type,
|
|
569
|
+
source: 'repo',
|
|
570
|
+
// confidence = round(float(score), 4) — always a Python float.
|
|
571
|
+
confidence: new PyFloat(_round4(h.score)),
|
|
572
|
+
body: _isPlainObject(h.entry) ? { ...h.entry } : {},
|
|
573
|
+
};
|
|
574
|
+
if (h.type in slice_counts) {
|
|
575
|
+
slice_counts[h.type] = (slice_counts[h.type] as number) + 1;
|
|
576
|
+
}
|
|
577
|
+
entries.push(envelope_entry);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const slices: Record<string, Record<string, unknown>> = {};
|
|
581
|
+
for (const t of known) {
|
|
582
|
+
slices[t] = { status: 'ok', count: slice_counts[t] ?? 0 };
|
|
583
|
+
}
|
|
584
|
+
const errors: Record<string, unknown>[] = [];
|
|
585
|
+
for (const t of unknown) {
|
|
586
|
+
slices[t] = { status: 'unknown_type', count: 0 };
|
|
587
|
+
errors.push({
|
|
588
|
+
type: t,
|
|
589
|
+
code: 'unknown_type',
|
|
590
|
+
message: `file-backed backend does not know type ${_pyRepr(t)}`,
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const sliceVals = Object.values(slices);
|
|
595
|
+
const oks = sliceVals.filter((s) => s['status'] === 'ok');
|
|
596
|
+
const fails = sliceVals.filter((s) => s['status'] !== 'ok');
|
|
597
|
+
const envelope_status = fails.length === 0 ? 'ok' : oks.length === 0 ? 'error' : 'partial';
|
|
598
|
+
|
|
599
|
+
const envelope: Record<string, unknown> = {
|
|
600
|
+
contract_version: _CONTRACT_VERSION,
|
|
601
|
+
status: envelope_status,
|
|
602
|
+
entries,
|
|
603
|
+
slices,
|
|
604
|
+
};
|
|
605
|
+
if (errors.length > 0) {
|
|
606
|
+
envelope['errors'] = errors;
|
|
607
|
+
}
|
|
608
|
+
return envelope;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// ---------------------------------------------------------------------------
|
|
612
|
+
// CLI
|
|
613
|
+
// ---------------------------------------------------------------------------
|
|
614
|
+
|
|
615
|
+
interface ParsedArgs {
|
|
616
|
+
types: string;
|
|
617
|
+
key: string[];
|
|
618
|
+
limit: number;
|
|
619
|
+
format: 'text' | 'json';
|
|
620
|
+
envelope: 'legacy' | 'v1';
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function _parseArgs(argv: string[]): ParsedArgs {
|
|
624
|
+
const args: ParsedArgs = {
|
|
625
|
+
types: '',
|
|
626
|
+
key: [],
|
|
627
|
+
limit: 5,
|
|
628
|
+
format: 'text',
|
|
629
|
+
envelope: 'legacy',
|
|
630
|
+
};
|
|
631
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
632
|
+
const a = argv[i] as string;
|
|
633
|
+
if (a === '--types') {
|
|
634
|
+
args.types = argv[++i] as string;
|
|
635
|
+
} else if (a.startsWith('--types=')) {
|
|
636
|
+
args.types = a.slice('--types='.length);
|
|
637
|
+
} else if (a === '--key') {
|
|
638
|
+
args.key.push(argv[++i] as string);
|
|
639
|
+
} else if (a.startsWith('--key=')) {
|
|
640
|
+
args.key.push(a.slice('--key='.length));
|
|
641
|
+
} else if (a === '--limit') {
|
|
642
|
+
args.limit = _parseInt(argv[++i], '--limit');
|
|
643
|
+
} else if (a.startsWith('--limit=')) {
|
|
644
|
+
args.limit = _parseInt(a.slice('--limit='.length), '--limit');
|
|
645
|
+
} else if (a === '--format') {
|
|
646
|
+
args.format = _checkChoice(argv[++i], ['text', 'json'], '--format') as 'text' | 'json';
|
|
647
|
+
} else if (a.startsWith('--format=')) {
|
|
648
|
+
args.format = _checkChoice(a.slice('--format='.length), ['text', 'json'], '--format') as 'text' | 'json';
|
|
649
|
+
} else if (a === '--envelope') {
|
|
650
|
+
args.envelope = _checkChoice(argv[++i], ['legacy', 'v1'], '--envelope') as 'legacy' | 'v1';
|
|
651
|
+
} else if (a.startsWith('--envelope=')) {
|
|
652
|
+
args.envelope = _checkChoice(a.slice('--envelope='.length), ['legacy', 'v1'], '--envelope') as
|
|
653
|
+
| 'legacy'
|
|
654
|
+
| 'v1';
|
|
655
|
+
} else if (a === '-h' || a === '--help') {
|
|
656
|
+
_printUsage();
|
|
657
|
+
process.exit(0);
|
|
658
|
+
} else {
|
|
659
|
+
process.stderr.write(`memory_lookup: error: unrecognized arguments: ${a}\n`);
|
|
660
|
+
process.exit(2);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
return args;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function _parseInt(value: string | undefined, flag: string): number {
|
|
667
|
+
const n = Number(value);
|
|
668
|
+
if (value === undefined || !Number.isInteger(n)) {
|
|
669
|
+
process.stderr.write(
|
|
670
|
+
`memory_lookup: error: argument ${flag}: invalid int value: '${value ?? ''}'\n`,
|
|
671
|
+
);
|
|
672
|
+
process.exit(2);
|
|
673
|
+
}
|
|
674
|
+
return n;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function _checkChoice(value: string | undefined, choices: string[], flag: string): string {
|
|
678
|
+
if (value === undefined || !choices.includes(value)) {
|
|
679
|
+
process.stderr.write(
|
|
680
|
+
`memory_lookup: error: argument ${flag}: invalid choice: '${value ?? ''}' (choose from ${choices
|
|
681
|
+
.map((c) => `'${c}'`)
|
|
682
|
+
.join(', ')})\n`,
|
|
683
|
+
);
|
|
684
|
+
process.exit(2);
|
|
685
|
+
}
|
|
686
|
+
return value;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function _printUsage(): void {
|
|
690
|
+
process.stdout.write(
|
|
691
|
+
'usage: memory_lookup [-h] [--types TYPES] [--key KEY] [--limit LIMIT] ' +
|
|
692
|
+
'[--format {text,json}] [--envelope {legacy,v1}]\n',
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
export function main(): number {
|
|
697
|
+
const args = _parseArgs(process.argv.slice(2));
|
|
698
|
+
const types = args.types
|
|
699
|
+
.split(',')
|
|
700
|
+
.map((t) => t.trim())
|
|
701
|
+
.filter((t) => t);
|
|
702
|
+
if (types.length === 0) {
|
|
703
|
+
process.stderr.write('error: --types is required\n');
|
|
704
|
+
return 2;
|
|
705
|
+
}
|
|
706
|
+
if (args.envelope === 'v1') {
|
|
707
|
+
const envelope = retrieve_v1(types, args.key, args.limit);
|
|
708
|
+
process.stdout.write(`${pyJsonDumps(envelope, 2)}\n`);
|
|
709
|
+
return 0;
|
|
710
|
+
}
|
|
711
|
+
const hits = retrieve(types, args.key, args.limit);
|
|
712
|
+
if (args.format === 'json') {
|
|
713
|
+
const payload = {
|
|
714
|
+
hits: hits.map((h) => h.as_dict()),
|
|
715
|
+
};
|
|
716
|
+
process.stdout.write(`${pyJsonDumps(payload, 2)}\n`);
|
|
717
|
+
} else {
|
|
718
|
+
if (hits.length === 0) {
|
|
719
|
+
process.stdout.write(' (no hits)\n');
|
|
720
|
+
}
|
|
721
|
+
for (const h of hits) {
|
|
722
|
+
process.stdout.write(
|
|
723
|
+
` [${h.source}] ${h.type} score=${_fixed2(h.score)} ` +
|
|
724
|
+
`id=${h.id || '-'} path=${h.path}\n`,
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return 0;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// ---------------------------------------------------------------------------
|
|
732
|
+
// helpers — Python compatibility
|
|
733
|
+
// ---------------------------------------------------------------------------
|
|
734
|
+
|
|
735
|
+
/** Mirror Python `f"{score:.2f}"`. */
|
|
736
|
+
function _fixed2(n: number): string {
|
|
737
|
+
return n.toFixed(2);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/** Mirror Python `round(x, 4)` (banker's rounding). */
|
|
741
|
+
function _round4(n: number): number {
|
|
742
|
+
return _pyRound(n, 4);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/** Python 3 round(): round-half-to-even at the given decimal places. */
|
|
746
|
+
function _pyRound(value: number, ndigits: number): number {
|
|
747
|
+
const factor = 10 ** ndigits;
|
|
748
|
+
const scaled = value * factor;
|
|
749
|
+
const floor = Math.floor(scaled);
|
|
750
|
+
const diff = scaled - floor;
|
|
751
|
+
let rounded: number;
|
|
752
|
+
if (diff > 0.5) {
|
|
753
|
+
rounded = floor + 1;
|
|
754
|
+
} else if (diff < 0.5) {
|
|
755
|
+
rounded = floor;
|
|
756
|
+
} else {
|
|
757
|
+
// exactly .5 → round to even
|
|
758
|
+
rounded = floor % 2 === 0 ? floor : floor + 1;
|
|
759
|
+
}
|
|
760
|
+
return rounded / factor;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/** fnmatch.fnmatch — translate a shell glob to a regex (POSIX-normcase). */
|
|
764
|
+
function _fnmatch(name: string, pattern: string): boolean {
|
|
765
|
+
const re = _fnmatchTranslate(pattern);
|
|
766
|
+
return re.test(name);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* Mirror fnmatch.translate: `*` → `.*`, `?` → `.`, `[...]` char class,
|
|
771
|
+
* everything else escaped. Anchored full-match.
|
|
772
|
+
*/
|
|
773
|
+
function _fnmatchTranslate(pat: string): RegExp {
|
|
774
|
+
let res = '';
|
|
775
|
+
let i = 0;
|
|
776
|
+
const n = pat.length;
|
|
777
|
+
while (i < n) {
|
|
778
|
+
const c = pat[i] as string;
|
|
779
|
+
i += 1;
|
|
780
|
+
if (c === '*') {
|
|
781
|
+
res += '.*';
|
|
782
|
+
} else if (c === '?') {
|
|
783
|
+
res += '.';
|
|
784
|
+
} else if (c === '[') {
|
|
785
|
+
let j = i;
|
|
786
|
+
if (j < n && pat[j] === '!') {
|
|
787
|
+
j += 1;
|
|
788
|
+
}
|
|
789
|
+
if (j < n && pat[j] === ']') {
|
|
790
|
+
j += 1;
|
|
791
|
+
}
|
|
792
|
+
while (j < n && pat[j] !== ']') {
|
|
793
|
+
j += 1;
|
|
794
|
+
}
|
|
795
|
+
if (j >= n) {
|
|
796
|
+
res += '\\[';
|
|
797
|
+
} else {
|
|
798
|
+
let stuff = pat.slice(i, j);
|
|
799
|
+
if (!stuff.includes('-')) {
|
|
800
|
+
stuff = stuff.replace(/\\/g, '\\\\');
|
|
801
|
+
} else {
|
|
802
|
+
// Match CPython's bracket-range handling minimally.
|
|
803
|
+
stuff = stuff.replace(/\\/g, '\\\\');
|
|
804
|
+
}
|
|
805
|
+
i = j + 1;
|
|
806
|
+
if (stuff.startsWith('!')) {
|
|
807
|
+
stuff = `^${stuff.slice(1)}`;
|
|
808
|
+
} else if (stuff.startsWith('^') || stuff.startsWith('[')) {
|
|
809
|
+
stuff = `\\${stuff}`;
|
|
810
|
+
}
|
|
811
|
+
res += `[${stuff}]`;
|
|
812
|
+
}
|
|
813
|
+
} else {
|
|
814
|
+
res += _reEscape(c);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
// CPython uses (?s:...)\Z — dotall, anchored to end. Anchor both ends.
|
|
818
|
+
return new RegExp(`^(?:${res})$`, 's');
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
function _reEscape(ch: string): string {
|
|
822
|
+
return ch.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/** Mirror Python str(x) for list-element haystack building / repr. */
|
|
826
|
+
function _pyStr(value: unknown): string {
|
|
827
|
+
if (value === null || value === undefined) {
|
|
828
|
+
return 'None';
|
|
829
|
+
}
|
|
830
|
+
if (value === true) {
|
|
831
|
+
return 'True';
|
|
832
|
+
}
|
|
833
|
+
if (value === false) {
|
|
834
|
+
return 'False';
|
|
835
|
+
}
|
|
836
|
+
return String(value);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/** Mirror Python repr() for a string in the unknown_type message: {t!r}. */
|
|
840
|
+
function _pyRepr(s: string): string {
|
|
841
|
+
// Python repr prefers single quotes unless the string contains a single
|
|
842
|
+
// quote and no double quote.
|
|
843
|
+
const hasSingle = s.includes("'");
|
|
844
|
+
const hasDouble = s.includes('"');
|
|
845
|
+
const quote = hasSingle && !hasDouble ? '"' : "'";
|
|
846
|
+
let out = quote;
|
|
847
|
+
for (const ch of s) {
|
|
848
|
+
if (ch === quote || ch === '\\') {
|
|
849
|
+
out += `\\${ch}`;
|
|
850
|
+
} else if (ch === '\n') {
|
|
851
|
+
out += '\\n';
|
|
852
|
+
} else if (ch === '\r') {
|
|
853
|
+
out += '\\r';
|
|
854
|
+
} else if (ch === '\t') {
|
|
855
|
+
out += '\\t';
|
|
856
|
+
} else {
|
|
857
|
+
out += ch;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
return out + quote;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// ---------------------------------------------------------------------------
|
|
864
|
+
// filesystem helpers — mirror pathlib glob/rglob/iterdir ordering
|
|
865
|
+
// ---------------------------------------------------------------------------
|
|
866
|
+
|
|
867
|
+
/** Mirror sorted(dir.rglob("*.yml")) — recursive, sorted by POSIX path string. */
|
|
868
|
+
function _rglobYmlSorted(root: string): string[] {
|
|
869
|
+
const out: string[] = [];
|
|
870
|
+
const walk = (dir: string): void => {
|
|
871
|
+
let entries: fs.Dirent[];
|
|
872
|
+
try {
|
|
873
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
874
|
+
} catch {
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
for (const ent of entries) {
|
|
878
|
+
const full = path.join(dir, ent.name);
|
|
879
|
+
if (ent.name.endsWith('.yml')) {
|
|
880
|
+
out.push(full);
|
|
881
|
+
}
|
|
882
|
+
if (ent.isDirectory() || (ent.isSymbolicLink() && _isDir(full))) {
|
|
883
|
+
walk(full);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
walk(root);
|
|
888
|
+
out.sort();
|
|
889
|
+
return out;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
/** Mirror sorted(dir.glob("*.jsonl")) — non-recursive, sorted. */
|
|
893
|
+
function _globJsonlSorted(dir: string): string[] {
|
|
894
|
+
return _globSorted(dir, '.jsonl');
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
/** Mirror sorted(dir.glob("*.md")). */
|
|
898
|
+
function _globMdSorted(dir: string): string[] {
|
|
899
|
+
return _globSorted(dir, '.md');
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
function _globSorted(dir: string, suffix: string): string[] {
|
|
903
|
+
let entries: fs.Dirent[];
|
|
904
|
+
try {
|
|
905
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
906
|
+
} catch {
|
|
907
|
+
return [];
|
|
908
|
+
}
|
|
909
|
+
const out = entries
|
|
910
|
+
.filter((e) => e.name.endsWith(suffix) && (e.isFile() || (e.isSymbolicLink() && _isFile(path.join(dir, e.name)))))
|
|
911
|
+
.map((e) => path.join(dir, e.name));
|
|
912
|
+
out.sort();
|
|
913
|
+
return out;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/** Mirror sorted(dir.iterdir()). */
|
|
917
|
+
function _iterdirSorted(dir: string): string[] {
|
|
918
|
+
let names: string[];
|
|
919
|
+
try {
|
|
920
|
+
names = fs.readdirSync(dir);
|
|
921
|
+
} catch {
|
|
922
|
+
return [];
|
|
923
|
+
}
|
|
924
|
+
const out = names.map((name) => path.join(dir, name));
|
|
925
|
+
out.sort();
|
|
926
|
+
return out;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
function _stem(p: string): string {
|
|
930
|
+
const base = path.basename(p);
|
|
931
|
+
const dot = base.lastIndexOf('.');
|
|
932
|
+
return dot <= 0 ? base : base.slice(0, dot);
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// ---------------------------------------------------------------------------
|
|
936
|
+
// Python-compatible json.dumps(obj, indent=N, default=str)
|
|
937
|
+
// ---------------------------------------------------------------------------
|
|
938
|
+
|
|
939
|
+
/**
|
|
940
|
+
* `json.dumps(obj, indent=2, default=str)`. Uses item separator `,\n` +
|
|
941
|
+
* indent and key separator `": "`. Non-serialisable values fall back to
|
|
942
|
+
* str(). `ensure_ascii=True` by default → non-ASCII escaped.
|
|
943
|
+
*/
|
|
944
|
+
function pyJsonDumps(value: unknown, indent: number): string {
|
|
945
|
+
return _escapeNonAscii(_dumpsIndent(value, indent, 0));
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
function _dumpsIndent(value: unknown, indent: number, depth: number): string {
|
|
949
|
+
const pad = ' '.repeat(indent * (depth + 1));
|
|
950
|
+
const closePad = ' '.repeat(indent * depth);
|
|
951
|
+
if (value === null || value === undefined) {
|
|
952
|
+
return 'null';
|
|
953
|
+
}
|
|
954
|
+
if (typeof value === 'boolean') {
|
|
955
|
+
return value ? 'true' : 'false';
|
|
956
|
+
}
|
|
957
|
+
if (typeof value === 'number') {
|
|
958
|
+
return _jsonNum(value);
|
|
959
|
+
}
|
|
960
|
+
if (typeof value === 'string') {
|
|
961
|
+
return _jsonStrAscii(value);
|
|
962
|
+
}
|
|
963
|
+
if (value instanceof PyTimestamp) {
|
|
964
|
+
// default=str on a datetime → PyYAML str() form (already computed).
|
|
965
|
+
return _jsonStrAscii(value.pyStr);
|
|
966
|
+
}
|
|
967
|
+
if (value instanceof PyFloat) {
|
|
968
|
+
return _jsonFloat(value.value);
|
|
969
|
+
}
|
|
970
|
+
if (Array.isArray(value)) {
|
|
971
|
+
if (value.length === 0) {
|
|
972
|
+
return '[]';
|
|
973
|
+
}
|
|
974
|
+
const items = value.map((v) => pad + _dumpsIndent(v, indent, depth + 1));
|
|
975
|
+
return `[\n${items.join(',\n')}\n${closePad}]`;
|
|
976
|
+
}
|
|
977
|
+
if (_isPlainObject(value)) {
|
|
978
|
+
const keys = Object.keys(value);
|
|
979
|
+
if (keys.length === 0) {
|
|
980
|
+
return '{}';
|
|
981
|
+
}
|
|
982
|
+
const items = keys.map((k) => `${pad}${_jsonStrAscii(k)}: ${_dumpsIndent(value[k], indent, depth + 1)}`);
|
|
983
|
+
return `{\n${items.join(',\n')}\n${closePad}}`;
|
|
984
|
+
}
|
|
985
|
+
// default=str
|
|
986
|
+
return _jsonStrAscii(String(value));
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
function _jsonNum(n: number): string {
|
|
990
|
+
if (!Number.isFinite(n)) {
|
|
991
|
+
if (Number.isNaN(n)) {
|
|
992
|
+
return 'NaN';
|
|
993
|
+
}
|
|
994
|
+
return n > 0 ? 'Infinity' : '-Infinity';
|
|
995
|
+
}
|
|
996
|
+
return String(n);
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
/** Render a Python float — integer-valued floats keep a `.0` suffix (repr(float)). */
|
|
1000
|
+
function _jsonFloat(n: number): string {
|
|
1001
|
+
if (!Number.isFinite(n)) {
|
|
1002
|
+
if (Number.isNaN(n)) {
|
|
1003
|
+
return 'NaN';
|
|
1004
|
+
}
|
|
1005
|
+
return n > 0 ? 'Infinity' : '-Infinity';
|
|
1006
|
+
}
|
|
1007
|
+
return Number.isInteger(n) ? `${n}.0` : String(n);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
/** JSON string with standard escapes (ensure_ascii handled by caller pass). */
|
|
1011
|
+
function _jsonStrAscii(s: string): string {
|
|
1012
|
+
let out = '"';
|
|
1013
|
+
for (const ch of s) {
|
|
1014
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
1015
|
+
switch (ch) {
|
|
1016
|
+
case '"':
|
|
1017
|
+
out += '\\"';
|
|
1018
|
+
break;
|
|
1019
|
+
case '\\':
|
|
1020
|
+
out += '\\\\';
|
|
1021
|
+
break;
|
|
1022
|
+
case '\n':
|
|
1023
|
+
out += '\\n';
|
|
1024
|
+
break;
|
|
1025
|
+
case '\r':
|
|
1026
|
+
out += '\\r';
|
|
1027
|
+
break;
|
|
1028
|
+
case '\t':
|
|
1029
|
+
out += '\\t';
|
|
1030
|
+
break;
|
|
1031
|
+
case '\b':
|
|
1032
|
+
out += '\\b';
|
|
1033
|
+
break;
|
|
1034
|
+
case '\f':
|
|
1035
|
+
out += '\\f';
|
|
1036
|
+
break;
|
|
1037
|
+
default:
|
|
1038
|
+
if (code < 0x20) {
|
|
1039
|
+
out += `\\u${code.toString(16).padStart(4, '0')}`;
|
|
1040
|
+
} else {
|
|
1041
|
+
out += ch;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
return `${out}"`;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
function _escapeNonAscii(s: string): string {
|
|
1049
|
+
let out = '';
|
|
1050
|
+
for (const ch of s) {
|
|
1051
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
1052
|
+
if (code > 0x7f) {
|
|
1053
|
+
for (let i = 0; i < ch.length; i += 1) {
|
|
1054
|
+
out += `\\u${ch.charCodeAt(i).toString(16).padStart(4, '0')}`;
|
|
1055
|
+
}
|
|
1056
|
+
} else {
|
|
1057
|
+
out += ch;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
return out;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
function _isCliEntry(): boolean {
|
|
1064
|
+
if (process.argv[1] === undefined) {
|
|
1065
|
+
return false;
|
|
1066
|
+
}
|
|
1067
|
+
const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
1068
|
+
if (import.meta.url === argvUrl) {
|
|
1069
|
+
return true;
|
|
1070
|
+
}
|
|
1071
|
+
// A symlinked invocation (e.g. via an installed `.augment/` projection,
|
|
1072
|
+
// or macOS /var → /private/var temp dirs) makes the raw URLs differ:
|
|
1073
|
+
// import.meta.url is the resolved real path while argv[1] keeps the
|
|
1074
|
+
// symlink path. Compare realpaths so the entry guard still fires
|
|
1075
|
+
// (without this the CLI silently no-ops when run through a symlink).
|
|
1076
|
+
try {
|
|
1077
|
+
const here = fs.realpathSync(fileURLToPath(import.meta.url));
|
|
1078
|
+
const argv = fs.realpathSync(path.resolve(process.argv[1]));
|
|
1079
|
+
return here === argv;
|
|
1080
|
+
} catch {
|
|
1081
|
+
return false;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
const _isMain =
|
|
1086
|
+
_isCliEntry();
|
|
1087
|
+
if (_isMain) {
|
|
1088
|
+
process.exit(main());
|
|
1089
|
+
}
|