@event4u/agent-config 9.9.0 → 9.11.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 +212 -194
- package/README.md +13 -16
- package/agents/templates/.ai-council.yml.example +481 -0
- package/agents/templates/.ai-video.xml.example +247 -0
- package/dist/agent-src/commands/agent-status.md +12 -11
- package/dist/agent-src/commands/agents/user/accept.md +115 -30
- package/dist/agent-src/commands/agents/user/delete.md +147 -0
- package/dist/agent-src/commands/agents/user/review.md +65 -28
- package/dist/agent-src/commands/agents/user/show.md +82 -29
- package/dist/agent-src/commands/agents/user/update.md +2 -2
- package/dist/agent-src/commands/agents/user.md +16 -3
- package/dist/agent-src/commands/check-current-md.md +0 -10
- package/dist/agent-src/commands/condense.md +28 -97
- package/dist/agent-src/commands/cost/report.md +7 -0
- package/dist/agent-src/commands/fix/portability.md +0 -8
- package/dist/agent-src/commands/fix/refs.md +0 -8
- package/dist/agent-src/commands/humanize.md +2 -2
- package/dist/agent-src/commands/jira-ticket.md +1 -1
- package/dist/agent-src/commands/memory/load.md +11 -0
- package/dist/agent-src/commands/optimize/skills.md +1 -1
- package/dist/agent-src/commands/pr/create.md +4 -4
- package/dist/agent-src/commands/profile/show.md +3 -3
- package/dist/agent-src/commands/rule-compliance-audit.md +5 -4
- package/dist/agent-src/commands/security-audit-config.md +1 -1
- package/dist/agent-src/commands/sync/gitignore/fix.md +1 -1
- package/dist/agent-src/commands/tdd.md +21 -0
- package/dist/agent-src/commands/team/delegate.md +2 -2
- package/dist/agent-src/commands/team-knowledge/bootstrap.md +9 -8
- package/dist/agent-src/commands/team-knowledge/consolidate.md +8 -7
- package/dist/agent-src/commands/team.md +4 -1
- package/dist/agent-src/commands/upstream-contribute.md +0 -1
- package/dist/agent-src/contexts/authority/commit-mechanics.md +7 -0
- package/dist/agent-src/contexts/authority/destructive-mechanics.md +16 -0
- package/dist/agent-src/contexts/authority/scope-mechanics.md +61 -0
- package/dist/agent-src/contexts/communication/rules-auto/reply-close-mechanics.md +20 -18
- 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 +1 -1
- package/dist/agent-src/contexts/communication/rules-auto/source-of-truth-mechanics.md +9 -10
- package/dist/agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +1 -1
- package/dist/agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +11 -0
- package/dist/agent-src/contexts/contracts/artifact-engagement-flow.md +26 -15
- package/dist/agent-src/contexts/contracts/command-suggestion-flow.md +3 -0
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +67 -37
- package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +1 -1
- package/dist/agent-src/contexts/execution/autonomy-examples.md +1 -1
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +7 -6
- package/dist/agent-src/contexts/execution/evidence-discipline.md +92 -77
- package/dist/agent-src/contexts/execution/orchestration-benchmark-gate.md +22 -21
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +24 -0
- package/dist/agent-src/contexts/execution/project-intelligence.md +122 -103
- package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +23 -21
- package/dist/agent-src/contexts/execution/subagent-response-contract.md +5 -5
- package/dist/agent-src/contexts/execution/subagent-routing.md +11 -0
- package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +13 -13
- package/dist/agent-src/contexts/execution/subagent-steering.md +36 -34
- package/dist/agent-src/contexts/execution/toolchain-resolver.md +1 -1
- package/dist/agent-src/contexts/execution/user-memory-channels.md +189 -0
- package/dist/agent-src/contexts/execution/verification-mechanics.md +9 -0
- package/dist/agent-src/contexts/model-recommendations.md +3 -2
- package/dist/agent-src/contexts/subagent-configuration.md +3 -3
- package/dist/agent-src/ghostwriter/README.md +2 -2
- package/dist/agent-src/packs/README.md +1 -1
- package/dist/agent-src/personas/README.md +5 -5
- package/dist/agent-src/personas/_template-specialist/persona.md +3 -3
- package/dist/agent-src/personas/backend-architect.md +24 -23
- package/dist/agent-src/personas/eloquent-tamer.md +30 -30
- package/dist/agent-src/personas/frontend-engineer.md +30 -30
- package/dist/agent-src/personas/product-owner.md +41 -38
- package/dist/agent-src/personas/qa.md +13 -13
- package/dist/agent-src/personas/security-engineer.md +22 -22
- package/dist/agent-src/presets/README.md +3 -3
- package/dist/agent-src/profiles/README.md +3 -3
- package/dist/agent-src/rules/active-remediation.md +2 -2
- package/dist/agent-src/rules/artifact-engagement-recording.md +4 -4
- package/dist/agent-src/rules/ask-when-uncertain.md +0 -4
- package/dist/agent-src/rules/augment-edit-discipline.md +1 -1
- package/dist/agent-src/rules/broken-access-control.md +4 -4
- package/dist/agent-src/rules/code-comment-discipline.md +1 -1
- package/dist/agent-src/rules/code-provenance.md +2 -2
- package/dist/agent-src/rules/commit-policy.md +4 -9
- package/dist/agent-src/rules/communication-through-line.md +5 -5
- package/dist/agent-src/rules/decision-revisit-gate.md +1 -1
- package/dist/agent-src/rules/design-fidelity.md +1 -1
- package/dist/agent-src/rules/doc-screenshot-hygiene.md +1 -1
- package/dist/agent-src/rules/domain-adoption-policy.md +1 -1
- package/dist/agent-src/rules/domain-safety-pii.md +1 -1
- package/dist/agent-src/rules/external-code-graph-interop.md +1 -1
- package/dist/agent-src/rules/fast-path-marker-visibility.md +43 -7
- package/dist/agent-src/rules/finance-safety-floor.md +1 -1
- package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +3 -3
- package/dist/agent-src/rules/history-discipline.md +1 -1
- package/dist/agent-src/rules/legal-safety-floor.md +1 -1
- package/dist/agent-src/rules/lethal-trifecta-guard.md +5 -5
- package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +10 -8
- package/dist/agent-src/rules/media-governance-routing.md +13 -13
- package/dist/agent-src/rules/media-sync-ground-truth.md +29 -11
- package/dist/agent-src/rules/no-cheap-questions.md +7 -7
- package/dist/agent-src/rules/no-decorative-emojis-in-git-surfaces.md +29 -18
- package/dist/agent-src/rules/non-destructive-by-default.md +5 -23
- package/dist/agent-src/rules/onboarding-gate.md +14 -4
- package/dist/agent-src/rules/persona-governance.md +2 -2
- package/dist/agent-src/rules/prefer-enums-over-literals.md +9 -9
- package/dist/agent-src/rules/preservation-guard.md +10 -2
- package/dist/agent-src/rules/question-not-instruction.md +1 -1
- package/dist/agent-src/rules/roadmap-progress-sync.md +6 -6
- package/dist/agent-src/rules/rule-type-governance.md +1 -1
- package/dist/agent-src/rules/scale-discipline.md +1 -1
- package/dist/agent-src/rules/scope-control.md +1 -1
- package/dist/agent-src/rules/secret-vcs-guard.md +1 -1
- package/dist/agent-src/rules/security-sensitive-stop.md +17 -11
- package/dist/agent-src/rules/senior-engineering-discipline.md +17 -17
- package/dist/agent-src/rules/session-canary.md +1 -1
- package/dist/agent-src/rules/skill-quality.md +1 -1
- package/dist/agent-src/rules/source-confidentiality.md +21 -19
- package/dist/agent-src/rules/source-of-truth.md +6 -5
- package/dist/agent-src/rules/spreadsheet-source-quality.md +2 -2
- package/dist/agent-src/rules/strategy-safety-floor.md +1 -1
- package/dist/agent-src/rules/think-before-action.md +5 -5
- package/dist/agent-src/rules/token-budget-discipline.md +5 -3
- package/dist/agent-src/rules/token-efficiency.md +2 -2
- package/dist/agent-src/rules/untrusted-input-defense.md +4 -4
- package/dist/agent-src/rules/user-interaction.md +12 -0
- package/dist/agent-src/rules/verify-before-complete.md +4 -24
- package/dist/agent-src/skills/adversarial-review/SKILL.md +2 -2
- package/dist/agent-src/skills/agent-security-review/SKILL.md +52 -45
- package/dist/agent-src/skills/agents-md-thin-root/SKILL.md +2 -2
- package/dist/agent-src/skills/ai-code-blindspots/SKILL.md +20 -20
- package/dist/agent-src/skills/ai-council/SKILL.md +185 -128
- package/dist/agent-src/skills/analysis-skill-router/SKILL.md +3 -3
- package/dist/agent-src/skills/api-endpoint/SKILL.md +2 -2
- package/dist/agent-src/skills/artisan-commands/SKILL.md +2 -2
- package/dist/agent-src/skills/async-python-patterns/SKILL.md +8 -7
- package/dist/agent-src/skills/authz-review/SKILL.md +16 -15
- package/dist/agent-src/skills/aws-infrastructure/SKILL.md +5 -5
- package/dist/agent-src/skills/blade-ui/SKILL.md +1 -1
- package/dist/agent-src/skills/bug-analyzer/SKILL.md +30 -12
- package/dist/agent-src/skills/code-intelligence/SKILL.md +0 -1
- package/dist/agent-src/skills/code-intelligence/evals/triggers.json +52 -0
- package/dist/agent-src/skills/code-review/SKILL.md +6 -6
- package/dist/agent-src/skills/command-routing/SKILL.md +5 -4
- package/dist/agent-src/skills/composer-packages/SKILL.md +2 -2
- package/dist/agent-src/skills/condense-memory/SKILL.md +43 -41
- package/dist/agent-src/skills/context-document/SKILL.md +23 -21
- package/dist/agent-src/skills/copilot-config/SKILL.md +6 -1
- package/dist/agent-src/skills/decision-record/SKILL.md +20 -18
- package/dist/agent-src/skills/design-intelligence/SKILL.md +4 -4
- package/dist/agent-src/skills/design-review/SKILL.md +15 -14
- package/dist/agent-src/skills/design-system-capture/reference/design-system-json.md +28 -25
- package/dist/agent-src/skills/devcontainer/SKILL.md +2 -2
- package/dist/agent-src/skills/docker/SKILL.md +1 -1
- package/dist/agent-src/skills/docx-authoring/evals/triggers.json +52 -0
- package/dist/agent-src/skills/emit-tickets/SKILL.md +2 -2
- package/dist/agent-src/skills/error-handling-patterns/SKILL.md +1 -1
- package/dist/agent-src/skills/estimate-ticket/SKILL.md +2 -2
- package/dist/agent-src/skills/evaluate-llm-feature/SKILL.md +0 -1
- package/dist/agent-src/skills/evaluate-llm-feature/evals/triggers.json +52 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +21 -7
- package/dist/agent-src/skills/fe-design/SKILL.md +10 -0
- package/dist/agent-src/skills/feature-planning/SKILL.md +2 -2
- package/dist/agent-src/skills/finishing-a-development-branch/SKILL.md +3 -3
- package/dist/agent-src/skills/flux/SKILL.md +1 -1
- package/dist/agent-src/skills/frontend-render-security/SKILL.md +13 -13
- package/dist/agent-src/skills/git-workflow/SKILL.md +33 -33
- package/dist/agent-src/skills/grafana/SKILL.md +5 -0
- package/dist/agent-src/skills/humanizer/SKILL.md +83 -66
- package/dist/agent-src/skills/humanizer/data/patterns.md +14 -14
- package/dist/agent-src/skills/humanizer/references/anti-aiisms.md +4 -4
- package/dist/agent-src/skills/judge-artifact-completeness/SKILL.md +82 -37
- package/dist/agent-src/skills/judge-bug-hunter/SKILL.md +17 -5
- package/dist/agent-src/skills/judge-injection-defense/SKILL.md +63 -25
- package/dist/agent-src/skills/judge-security-auditor/SKILL.md +2 -0
- package/dist/agent-src/skills/judge-synthesis/SKILL.md +72 -28
- package/dist/agent-src/skills/laravel-horizon/SKILL.md +5 -0
- package/dist/agent-src/skills/laravel-mail/SKILL.md +5 -4
- package/dist/agent-src/skills/laravel-middleware/SKILL.md +5 -4
- package/dist/agent-src/skills/laravel-notifications/SKILL.md +10 -9
- package/dist/agent-src/skills/laravel-pennant/SKILL.md +1 -1
- package/dist/agent-src/skills/laravel-pulse/SKILL.md +9 -8
- package/dist/agent-src/skills/laravel-reverb/SKILL.md +6 -5
- package/dist/agent-src/skills/laravel-scheduling/SKILL.md +6 -1
- package/dist/agent-src/skills/laravel-validation/SKILL.md +12 -0
- package/dist/agent-src/skills/launch-readiness/SKILL.md +1 -1
- package/dist/agent-src/skills/learning-to-rule-or-skill/SKILL.md +7 -7
- package/dist/agent-src/skills/learning-tutor/SKILL.md +44 -43
- package/dist/agent-src/skills/learning-tutor/evals/triggers.json +52 -0
- package/dist/agent-src/skills/license-compliance-audit/SKILL.md +7 -7
- package/dist/agent-src/skills/license-compliance-borrow-check/SKILL.md +3 -3
- package/dist/agent-src/skills/license-compliance-credits/SKILL.md +4 -4
- package/dist/agent-src/skills/llm-provider-knowledge/SKILL.md +0 -1
- package/dist/agent-src/skills/memory-consolidation/SKILL.md +69 -64
- package/dist/agent-src/skills/motion-choreographer/SKILL.md +6 -6
- package/dist/agent-src/skills/pdf-tools/evals/triggers.json +52 -0
- package/dist/agent-src/skills/performance-analysis/SKILL.md +7 -3
- package/dist/agent-src/skills/persona-improvement/SKILL.md +0 -2
- package/dist/agent-src/skills/persona-improvement/evals/triggers.json +52 -0
- package/dist/agent-src/skills/php-coder/SKILL.md +10 -0
- package/dist/agent-src/skills/prediction-pool-optimizer/SKILL.md +129 -120
- package/dist/agent-src/skills/prediction-pool-optimizer/reference/ev-fixtures.md +36 -35
- package/dist/agent-src/skills/prediction-pool-optimizer/reference/odds-and-bonus.md +45 -41
- package/dist/agent-src/skills/project-analysis-core/SKILL.md +4 -4
- package/dist/agent-src/skills/project-analysis-hypothesis-driven/SKILL.md +3 -3
- package/dist/agent-src/skills/project-analysis-react/SKILL.md +1 -1
- package/dist/agent-src/skills/project-analysis-symfony/SKILL.md +1 -1
- package/dist/agent-src/skills/project-analysis-zend-laminas/SKILL.md +2 -2
- package/dist/agent-src/skills/prompt-engineering-patterns/SKILL.md +1 -1
- package/dist/agent-src/skills/prompt-validator/SKILL.md +39 -39
- package/dist/agent-src/skills/quality-tools/SKILL.md +15 -15
- package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +55 -19
- package/dist/agent-src/skills/react-shadcn-ui/reference/registry.md +29 -27
- package/dist/agent-src/skills/readme-writing-package/SKILL.md +2 -2
- package/dist/agent-src/skills/reasoning-orchestrator/SKILL.md +25 -25
- package/dist/agent-src/skills/recursive-verification/SKILL.md +6 -5
- package/dist/agent-src/skills/refine-prompt/SKILL.md +1 -1
- package/dist/agent-src/skills/refine-ticket/SKILL.md +20 -2
- 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/roadmap-management/SKILL.md +80 -66
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +84 -77
- package/dist/agent-src/skills/rule-refactor/SKILL.md +1 -1
- package/dist/agent-src/skills/screenshot-hygiene/evals/triggers.json +52 -0
- package/dist/agent-src/skills/security-audit/SKILL.md +26 -9
- package/dist/agent-src/skills/security-maturity-assessment/SKILL.md +38 -16
- package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +14 -11
- package/dist/agent-src/skills/skill-writing/SKILL.md +9 -8
- package/dist/agent-src/skills/song-to-script/SKILL.md +93 -85
- package/dist/agent-src/skills/sql-writing/SKILL.md +2 -2
- package/dist/agent-src/skills/stakeholder-tradeoff/SKILL.md +4 -3
- package/dist/agent-src/skills/standards-from-config/SKILL.md +61 -43
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +22 -8
- package/dist/agent-src/skills/subagent-orchestration/prompts/README.md +28 -2
- package/dist/agent-src/skills/supply-chain-intake/SKILL.md +13 -13
- package/dist/agent-src/skills/systematic-debugging/SKILL.md +22 -21
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +1 -1
- package/dist/agent-src/skills/technical-specification/SKILL.md +5 -0
- package/dist/agent-src/skills/terraform/SKILL.md +2 -2
- package/dist/agent-src/skills/terragrunt/SKILL.md +8 -8
- package/dist/agent-src/skills/testing-anti-patterns/process-anti-patterns.md +1 -1
- package/dist/agent-src/skills/threat-modeling/SKILL.md +19 -13
- package/dist/agent-src/skills/universal-project-analysis/SKILL.md +1 -1
- package/dist/agent-src/skills/upstream-contribute/SKILL.md +0 -1
- package/dist/agent-src/skills/using-git-worktrees/SKILL.md +9 -9
- package/dist/agent-src/skills/validate-feature-fit/SKILL.md +3 -1
- package/dist/agent-src/skills/verify-completion-evidence/SKILL.md +4 -6
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +67 -61
- package/dist/agent-src/skills/video-director/SKILL.md +7 -7
- package/dist/agent-src/skills/worktree-lifecycle/SKILL.md +3 -3
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/contexts/knowledge-pages.md +47 -45
- package/dist/agent-src/templates/contexts/lesson-card.md +2 -2
- package/dist/agent-src/templates/contexts/tenant-boundaries.md +2 -2
- package/dist/agent-src/templates/contexts.md +1 -1
- package/dist/agent-src/templates/features.md +1 -1
- package/dist/agent-src/templates/roadmaps.md +125 -116
- package/dist/agent-src/templates/rule.md +2 -1
- package/dist/agent-src/templates/scripts/pr_risk_review.ts +0 -0
- package/dist/agent-src/templates/scripts/telemetry/aggregator.ts +0 -0
- package/dist/agent-src/templates/scripts/tier_usage_report.ts +2 -2
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +30 -30
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/design.ts +88 -33
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +22 -2
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/review.ts +21 -2
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/stack_bundles.ts +179 -0
- package/dist/agent-src/templates/scripts/work_engine/stack/detect.ts +155 -4
- package/dist/agent-src/templates/scripts/work_engine/state.ts +21 -4
- package/dist/agent-src/user-types/README.md +8 -8
- package/dist/agent-src/user-types/galabau-field-crew.md +34 -33
- package/dist/agent-src/user-types/metalworking-shop.md +35 -34
- package/dist/agent-src/user-types/truck-driver.md +22 -20
- package/dist/cli/initRouting.js +74 -11
- package/dist/cli/initRouting.js.map +1 -1
- package/dist/cli/main.js +14 -3
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/python/workspace_roles.js +2 -2
- package/dist/cli/python/workspace_roles.js.map +1 -1
- package/dist/cli-delegate/chunk-4SV3XGFA.js +6910 -0
- package/dist/cli-delegate/chunk-736JCWLM.js +1104 -0
- package/dist/cli-delegate/chunk-7DSAMZFU.js +356 -0
- package/dist/cli-delegate/chunk-7HQ52DHU.js +74 -0
- package/dist/cli-delegate/chunk-BJGXZNPM.js +266 -0
- package/dist/cli-delegate/chunk-FTAISGDQ.js +117 -0
- package/dist/cli-delegate/chunk-GME3Q44L.js +82 -0
- package/dist/cli-delegate/chunk-GRRPEPRN.js +336 -0
- package/dist/cli-delegate/chunk-HJ6DAJHR.js +502 -0
- package/dist/cli-delegate/chunk-IR7P7JWA.js +872 -0
- package/dist/cli-delegate/chunk-J55WI3RR.js +47 -0
- package/dist/cli-delegate/chunk-MCJYCQPF.js +35 -0
- package/dist/cli-delegate/chunk-NDJLNC6Q.js +169 -0
- package/dist/cli-delegate/chunk-OMIVVCG2.js +26 -0
- package/dist/cli-delegate/chunk-OY53J6II.js +7336 -0
- package/dist/cli-delegate/chunk-RDGRHAAB.js +259 -0
- package/dist/cli-delegate/chunk-TFG57JJM.js +1969 -0
- package/dist/cli-delegate/chunk-XX6TOPD7.js +715 -0
- package/dist/cli-delegate/chunk-YMFO6FDN.js +10603 -0
- package/dist/cli-delegate/cmd_analyze_session.js +294 -0
- package/dist/cli-delegate/cmd_conformance.js +646 -0
- package/dist/cli-delegate/cmd_converge.js +326 -0
- package/dist/cli-delegate/cmd_doctor.js +144 -0
- package/dist/cli-delegate/cmd_explain.js +2508 -0
- package/dist/cli-delegate/cmd_export.js +296 -0
- package/dist/cli-delegate/cmd_fleet.js +332 -0
- package/dist/cli-delegate/cmd_memory_get.js +3878 -0
- package/dist/cli-delegate/cmd_migrate.js +11 -0
- package/dist/cli-delegate/cmd_preflight.js +270 -0
- package/dist/cli-delegate/cmd_prune.js +444 -0
- package/dist/cli-delegate/cmd_refresh.js +28 -0
- package/dist/cli-delegate/cmd_settings_check.js +574 -0
- package/dist/cli-delegate/cmd_settings_migrate.js +223 -0
- package/dist/cli-delegate/cmd_sync.js +264 -0
- package/dist/cli-delegate/cmd_uninstall.js +656 -0
- package/dist/cli-delegate/cmd_update.js +425 -0
- package/dist/cli-delegate/cmd_upgrade.js +444 -0
- package/dist/cli-delegate/cmd_validate.js +305 -0
- package/dist/cli-delegate/cmd_versions.js +370 -0
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +100 -67
- 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 +16 -10
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +7 -5
- package/dist/hooks/dispatch.js +186 -12
- package/dist/install/emit_host_rules_cli.js +2 -1
- package/dist/install/emit_host_rules_cli.js.map +1 -1
- package/dist/install/install.mjs +418 -378
- package/dist/install/rule_scope.js +5 -1
- package/dist/install/rule_scope.js.map +1 -1
- package/dist/install/toolDetection.js +40 -12
- package/dist/install/toolDetection.js.map +1 -1
- package/dist/install/wizard-plan.js +2 -2
- package/dist/install/wizard-plan.js.map +1 -1
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +2194 -1003
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/compile_time_toggles.js +48 -0
- package/dist/scripts/_lib/compile_time_toggles.js.map +1 -0
- package/dist/scripts/condense.js +234 -217
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/schemas/settings.js +4 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/shared/userMd/utils.js +30 -0
- package/dist/shared/userMd/utils.js.map +1 -1
- package/dist/ui/assets/index-BpSQt5vm.js.map +1 -1
- package/docs/guidelines/agent-infra/api-cost-levers.md +56 -5
- package/docs/guidelines/agent-infra/simplicity-and-goal-demos.md +93 -33
- package/package.json +4 -2
- 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/tier_usage_report.ts +2 -2
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +30 -30
- package/src/agent-src/templates/scripts/work_engine/directives/ui/design.ts +88 -33
- package/src/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +22 -2
- package/src/agent-src/templates/scripts/work_engine/directives/ui/review.ts +21 -2
- package/src/agent-src/templates/scripts/work_engine/directives/ui/stack_bundles.ts +179 -0
- package/src/agent-src/templates/scripts/work_engine/stack/detect.ts +155 -4
- package/src/agent-src/templates/scripts/work_engine/state.ts +21 -4
- package/src/config/agent-settings.template.yml +32 -2
- package/src/config/ci-local-parity.yml +142 -0
- package/src/config/discovery/packs.yml +7 -0
- package/src/config/evaluator-budgets.json +1 -1
- package/src/config/gate-coverage.yml +127 -0
- package/src/config/pack-size-budget.json +27 -0
- package/src/config/preamble-payload-budget.json +44 -0
- package/src/install/emit_host_rules_cli.ts +2 -1
- package/src/install/rule_scope.ts +5 -1
- package/src/install/toolDetection.ts +39 -8
- package/src/install/wizard-plan.ts +2 -2
- package/src/scripts/_cli/cmd_conformance.ts +2 -1
- package/src/scripts/_cli/cmd_converge.ts +2 -1
- package/src/scripts/_cli/cmd_doctor.ts +292 -15
- package/src/scripts/_cli/cmd_export.ts +2 -1
- package/src/scripts/_cli/cmd_fleet.ts +2 -1
- package/src/scripts/_cli/cmd_migrate.ts +2 -1
- package/src/scripts/_cli/cmd_preflight.ts +2 -1
- package/src/scripts/_cli/cmd_refresh.ts +2 -1
- package/src/scripts/_cli/cmd_update.ts +2 -1
- package/src/scripts/_cli/cmd_upgrade.ts +2 -1
- package/src/scripts/_cli/cmd_versions.ts +2 -1
- package/src/scripts/_cli/detection_report.ts +480 -0
- package/src/scripts/_dispatch.bash +52 -14
- package/src/scripts/_lib/agent_user_profile.ts +381 -0
- package/src/scripts/_lib/agents_overlay.ts +9 -0
- package/src/scripts/_lib/anchor_eval.ts +311 -0
- package/src/scripts/_lib/cc_transcript.ts +316 -0
- package/src/scripts/_lib/changelog_eras.ts +103 -9
- package/src/scripts/_lib/compile_time_toggles.ts +79 -0
- package/src/scripts/_lib/confusables.ts +101 -0
- package/src/scripts/_lib/duplicate_scope_census.ts +77 -0
- package/src/scripts/_lib/environment_detector.ts +441 -0
- package/src/scripts/_lib/managed_agents_folder.ts +181 -0
- package/src/scripts/_lib/package_root.ts +72 -0
- package/src/scripts/_lib/payload_hash_drift.ts +126 -0
- package/src/scripts/_lib/preservation_migration.ts +291 -0
- package/src/scripts/_lib/retrieval_sanitize.ts +166 -2
- package/src/scripts/_lib/scan_scope.ts +115 -0
- package/src/scripts/_lib/user_global_memory_audit.ts +187 -0
- package/src/scripts/_lib/user_global_observations.ts +830 -0
- package/src/scripts/_lib/user_global_revocations.ts +110 -0
- package/src/scripts/_lib/user_memory_gate_counters.ts +291 -0
- package/src/scripts/ai_council/blind_review.ts +0 -0
- package/src/scripts/ai_council/budget_guard.ts +90 -11
- package/src/scripts/ai_council/bundler.ts +2 -2
- package/src/scripts/ai_council/clients.ts +59 -3
- package/src/scripts/ai_council/config.ts +53 -7
- package/src/scripts/ai_council/modes.ts +68 -2
- package/src/scripts/ai_council/orchestrator.ts +54 -9
- package/src/scripts/ai_council/transport_resolver.ts +331 -0
- package/src/scripts/ai_team/team_dispatch.ts +17 -6
- package/src/scripts/anchor_eval_falsify.ts +198 -0
- package/src/scripts/anchor_scoring_run.ts +239 -0
- package/src/scripts/annotate_discovery.ts +1 -65
- package/src/scripts/bench_quality_run.ts +168 -4
- package/src/scripts/cache_realization_report.ts +782 -0
- package/src/scripts/check_artefact_count_messaging.ts +5 -1
- package/src/scripts/check_augment_description_cap.ts +15 -4
- package/src/scripts/check_bundle_path_leakage.ts +10 -4
- package/src/scripts/check_ci_local_parity.ts +317 -0
- package/src/scripts/check_condensation.ts +96 -191
- package/src/scripts/check_context_paths.ts +38 -9
- package/src/scripts/check_dependency_floors.ts +153 -0
- package/src/scripts/check_gate_coverage.ts +299 -0
- package/src/scripts/check_iron_law_prominence.ts +22 -1
- package/src/scripts/check_kernel_prefix_stability.ts +0 -0
- package/src/scripts/check_no_new_legacy_path.ts +16 -6
- package/src/scripts/check_no_roadmap_refs.ts +22 -9
- package/src/scripts/check_originality_freshness.ts +167 -0
- package/src/scripts/check_pack_size.ts +188 -0
- package/src/scripts/check_preamble_payload_budget.ts +130 -0
- package/src/scripts/check_proposal.ts +0 -0
- package/src/scripts/check_references.ts +0 -1
- package/src/scripts/check_safety_floor_untouched.ts +171 -24
- package/src/scripts/check_site_links.ts +62 -3
- package/src/scripts/ci_status.ts +0 -0
- package/src/scripts/cluster_near_miss_patterns.ts +0 -0
- package/src/scripts/code_graph/build.ts +0 -0
- package/src/scripts/command_suggester/types.ts +0 -0
- package/src/scripts/compile_router.ts +10 -14
- package/src/scripts/condense.sh +3 -4
- package/src/scripts/condense.ts +247 -228
- package/src/scripts/cost/track.mjs +168 -33
- package/src/scripts/cost_summary.ts +33 -1
- package/src/scripts/council_cli.ts +51 -5
- package/src/scripts/discovery_graph.ts +0 -0
- package/src/scripts/encoding_corpus.ts +500 -0
- package/src/scripts/encoding_corpus_report.ts +216 -0
- package/src/scripts/evaluator_umbrella.sh +34 -1
- package/src/scripts/generate_index.ts +19 -2
- package/src/scripts/install-hooks.sh +2 -2
- package/src/scripts/learning_sidecar.ts +0 -0
- package/src/scripts/lint_confusables.ts +10 -72
- package/src/scripts/lint_eval_fixture_citations.ts +113 -0
- package/src/scripts/lint_featured_skills.ts +0 -0
- package/src/scripts/lint_hidden_unicode.ts +208 -0
- package/src/scripts/lint_load_context.ts +17 -5
- package/src/scripts/lint_new_skill_gate.ts +9 -3
- package/src/scripts/lint_no_python_twin_rationale.ts +17 -2
- package/src/scripts/lint_originality.ts +1 -1
- package/src/scripts/lint_skill_originality.ts +1 -1
- package/src/scripts/lint_ticket_buildable.ts +1 -1
- package/src/scripts/lint_ui_stack_bundles.ts +170 -0
- package/src/scripts/mcp_server/tools.ts +4 -4
- package/src/scripts/measure_scope_dedup.ts +224 -0
- package/src/scripts/memory_lookup.ts +0 -0
- package/src/scripts/mine_session.ts +98 -0
- package/src/scripts/orchestration_payload_hash_drift.ts +145 -0
- package/src/scripts/preamble_byte_census.ts +574 -0
- package/src/scripts/probe_skill_registration.ts +2 -2
- package/src/scripts/redact_hook_capture.ts +13 -0
- package/src/scripts/release.ts +65 -10
- package/src/scripts/router_telemetry.ts +1 -1
- package/src/scripts/second_brain_retrieval.ts +14 -4
- package/src/scripts/skill_linter.ts +5 -0
- package/src/scripts/surface-tiers.yml +2 -0
- package/src/scripts/validate_pack_yaml.ts +0 -0
- package/src/server/schemas/settings.ts +8 -0
- package/src/shared/userMd/utils.ts +31 -0
- package/dist/agent-src/rules/telegraph-speak.md +0 -87
|
@@ -0,0 +1,830 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* The global observation buffer — the write path of ADR-138's sibling phase
|
|
4
|
+
* (road-to-global-user-memory Phase 2, "the learning channel"). Mirrors the
|
|
5
|
+
* project-local `.agent-user.observations.jsonl` contract
|
|
6
|
+
* (`docs/contracts/agent-user-schema.md § Observation buffer`) exactly, one
|
|
7
|
+
* level up: `~/.event4u/agent-config/user/observations.jsonl`.
|
|
8
|
+
*
|
|
9
|
+
* This module is the ONLY writer of that file. It is never read by the
|
|
10
|
+
* profile loader (`agent_user_profile.ts`) directly — only
|
|
11
|
+
* `/agents:user review` / `/agents:user accept` (and, through them,
|
|
12
|
+
* `applyObservationToGlobalProfile` in `agent_user_profile.ts`) may turn a
|
|
13
|
+
* buffered line into a change to `profile.md`. Append-only; nothing here
|
|
14
|
+
* ever rewrites `profile.md`.
|
|
15
|
+
*
|
|
16
|
+
* Capture-time guards (council cut, 2026-07-29 — see
|
|
17
|
+
* `agents/settings/contexts/global-user-memory-cut.md`): every candidate is
|
|
18
|
+
* checked BEFORE it is written, not filtered later at review. Four
|
|
19
|
+
* independently-testable classes, in the order they run:
|
|
20
|
+
*
|
|
21
|
+
* 1. `standing_command` — a verbatim standing directive ("always fetch
|
|
22
|
+
* <url> on every message") stored as memory becomes a durable
|
|
23
|
+
* injection that re-fires forever — and now in every project, not
|
|
24
|
+
* just one.
|
|
25
|
+
* 2. `self_harmful_preference` — a preference that would disable honest
|
|
26
|
+
* feedback ("never criticize me", "always agree with me") is
|
|
27
|
+
* surfaced, never stored, per `direct-answers`.
|
|
28
|
+
* 3. `exclusion_list` — the `.agent-user.md` explicit-exclusions list
|
|
29
|
+
* (credentials, third-party names/birthdays, financial figures,
|
|
30
|
+
* health/legal/therapy status, demographics, external-source
|
|
31
|
+
* identifiers) refused at CAPTURE, not at review. Rejecting the same
|
|
32
|
+
* class fifty times at review is the noise problem this avoids.
|
|
33
|
+
* 4. `hidden_unicode` — invisible-character identifier smuggling
|
|
34
|
+
* (the ADR-103 zero-width-smuggling class). Same gate as (3) — both
|
|
35
|
+
* come from one call to `knowledge_global_redaction.redaction_scan`
|
|
36
|
+
* — but reported as its own category so a test can pin it
|
|
37
|
+
* independently.
|
|
38
|
+
*
|
|
39
|
+
* The third persist-time guard the miner already documents — the
|
|
40
|
+
* derivability check ("if git/config answers it, store the surprising
|
|
41
|
+
* part") — is a judgment call the agent makes before a candidate ever
|
|
42
|
+
* reaches this module; it is not a deterministic function here (see
|
|
43
|
+
* `memory-consolidation` SKILL.md § Global user-scoped channel).
|
|
44
|
+
*
|
|
45
|
+
* The ≤5-normalised-facts-per-cycle gate applies GLOBALLY across the
|
|
46
|
+
* project-scoped intake channel (`mine_session.ts`) and this one — see
|
|
47
|
+
* `applySharedFactCap`. Enforcing it per-channel would let the second
|
|
48
|
+
* channel double the write volume the cap was meant to bound.
|
|
49
|
+
*
|
|
50
|
+
* Pure except for `appendGlobalObservation` and `readGlobalObservations`,
|
|
51
|
+
* which are the only functions that touch disk. Every disk path is
|
|
52
|
+
* resolved via `user_global_paths.ts`, honouring `$EVENT4U_CONFIG_HOME`
|
|
53
|
+
* exactly like every other artefact under the global root — tests MUST
|
|
54
|
+
* inject that variable rather than touch the real `~/.event4u/`.
|
|
55
|
+
*
|
|
56
|
+
* Phase 3 (project attribution, below the read path) adds `routeProjectObservation`
|
|
57
|
+
* — the Phase 0 managed/unmanaged/not-a-project predicate wired as a router
|
|
58
|
+
* for a project-scoped fact that has no managed `agents/` folder to live in.
|
|
59
|
+
* It writes through the SAME `appendGlobalObservation` guard pipeline above,
|
|
60
|
+
* plus a `context` object (`project_path`/`project_name`/`first_seen`) and a
|
|
61
|
+
* `seen_count`/`seen_in[]` recurrence tally — the only generalisation path
|
|
62
|
+
* into the durable profile (`findPromotionCandidates`,
|
|
63
|
+
* `promotionValueFor`). No project-indexed directory is ever created; see
|
|
64
|
+
* `tests/lib/user_global_observations_project_attribution.test.ts`.
|
|
65
|
+
*
|
|
66
|
+
* Phase 4 (delete, revoke, audit) adds `deleteGlobalObservation` and
|
|
67
|
+
* `purgeProjectContext` — the delete counterpart of `appendGlobalObservation`
|
|
68
|
+
* / `routeProjectObservation` above. Both write an append-only tombstone to
|
|
69
|
+
* `user_global_revocations.ts`'s ledger BEFORE the buffer is rewritten
|
|
70
|
+
* without the removed line(s), reusing ADR-121's tombstone-before-deletion
|
|
71
|
+
* discipline (see that module's docstring for the one necessary adaptation).
|
|
72
|
+
* `observationId` supplies the stable identifier neither the JSONL schema
|
|
73
|
+
* nor a knowledge card's `card_id_from` slug has an analogue for.
|
|
74
|
+
*/
|
|
75
|
+
import * as crypto from 'node:crypto';
|
|
76
|
+
import * as fs from 'node:fs';
|
|
77
|
+
import * as path from 'node:path';
|
|
78
|
+
|
|
79
|
+
import { redaction_scan } from './knowledge_global_redaction.js';
|
|
80
|
+
import { detect_managed_agents_folder } from './managed_agents_folder.js';
|
|
81
|
+
import type { ManagedAgentsFolderStatus } from './managed_agents_folder.js';
|
|
82
|
+
import { classifySimilarity, jaccardSimilarity } from './text_similarity.js';
|
|
83
|
+
import * as revocations from './user_global_revocations.js';
|
|
84
|
+
import type { RevocationEntry } from './user_global_revocations.js';
|
|
85
|
+
import * as user_global_paths from './user_global_paths.js';
|
|
86
|
+
import { recordObservationProposed } from './user_memory_gate_counters.js';
|
|
87
|
+
|
|
88
|
+
/** Relative-to-root path of the global observation buffer. */
|
|
89
|
+
export const GLOBAL_OBSERVATIONS_RELATIVE = path.join('user', 'observations.jsonl');
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Shared cap across BOTH channels (the project-scoped miner and this one).
|
|
93
|
+
* Duplicated from `mine_session.MAX_FACTS` deliberately rather than
|
|
94
|
+
* imported — `_lib/` modules do not import from `src/scripts/*.ts` CLI
|
|
95
|
+
* entrypoints, keeping the dependency direction one-way. Keep the two
|
|
96
|
+
* constants in sync; a test in `tests/lib/user_global_observations.test.ts`
|
|
97
|
+
* would need updating (not silently pass) if they diverge.
|
|
98
|
+
*/
|
|
99
|
+
export const SHARED_FACT_CAP = 5;
|
|
100
|
+
|
|
101
|
+
/** Allowed `field` values — mirrors `docs/contracts/agent-user-schema.md § Observation buffer`. */
|
|
102
|
+
export const ALLOWED_OBSERVATION_FIELDS = [
|
|
103
|
+
'identity.name',
|
|
104
|
+
'language',
|
|
105
|
+
'role',
|
|
106
|
+
'style.pace',
|
|
107
|
+
'voice_sample',
|
|
108
|
+
'notes',
|
|
109
|
+
] as const;
|
|
110
|
+
|
|
111
|
+
export type ObservationField = (typeof ALLOWED_OBSERVATION_FIELDS)[number];
|
|
112
|
+
|
|
113
|
+
const _ALLOWED_FIELD_SET: ReadonlySet<string> = new Set(ALLOWED_OBSERVATION_FIELDS);
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Project-attribution footer (road-to-global-user-memory Phase 3). Present
|
|
117
|
+
* only on an observation routed here because its project has no managed
|
|
118
|
+
* `agents/` folder (see `routeProjectObservation`, below) — a pure
|
|
119
|
+
* user-attribute observation (Phase 2's `mineUserObservationCandidates`
|
|
120
|
+
* channel) never carries one. Mirrors the semantics of the global knowledge
|
|
121
|
+
* card's `seen_in` provenance footer (`knowledge_global.ts`) rather than
|
|
122
|
+
* inventing a parallel primitive — this is a field on a user observation,
|
|
123
|
+
* never a project-shaped artefact of its own (the council's round-2
|
|
124
|
+
* namespace refusal; see `agents/settings/contexts/global-user-memory-cut.md`).
|
|
125
|
+
*/
|
|
126
|
+
export interface ObservationContext {
|
|
127
|
+
readonly project_path: string;
|
|
128
|
+
readonly project_name: string;
|
|
129
|
+
/** ISO timestamp of the FIRST sighting — never updated on recurrence. */
|
|
130
|
+
readonly first_seen: string;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* One candidate/buffered observation. Shape mirrors the project-local
|
|
135
|
+
* buffer's JSONL line, plus the optional Phase 3 project-attribution
|
|
136
|
+
* fields. `context`/`seen_count`/`seen_in` are present together or not at
|
|
137
|
+
* all — set only via `routeProjectObservation`, never by
|
|
138
|
+
* `appendGlobalObservation` callers directly (Phase 2's pure user-attribute
|
|
139
|
+
* path has no project to attribute to).
|
|
140
|
+
*/
|
|
141
|
+
export interface ObservationCandidate {
|
|
142
|
+
readonly ts: string;
|
|
143
|
+
readonly field: string;
|
|
144
|
+
readonly suggest: string;
|
|
145
|
+
readonly source: string;
|
|
146
|
+
readonly evidence: string;
|
|
147
|
+
readonly context?: ObservationContext | undefined;
|
|
148
|
+
/** Human-confirmed-recurrence tally — see `findPromotionCandidates`. Counting evidence, never a detector: it only ever grows via `routeProjectObservation` observing another project's write, never a cross-project batch scan. */
|
|
149
|
+
readonly seen_count?: number | undefined;
|
|
150
|
+
/** Project names the observation has recurred in, oldest first. Pruned to nothing at promotion — see `promotionValueFor`. */
|
|
151
|
+
readonly seen_in?: readonly string[] | undefined;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export type CaptureGuardCategory =
|
|
155
|
+
| 'standing_command'
|
|
156
|
+
| 'self_harmful_preference'
|
|
157
|
+
| 'exclusion_list'
|
|
158
|
+
| 'hidden_unicode';
|
|
159
|
+
|
|
160
|
+
export interface CaptureGuardResult {
|
|
161
|
+
readonly allowed: boolean;
|
|
162
|
+
readonly category?: CaptureGuardCategory | undefined;
|
|
163
|
+
readonly reason?: string | undefined;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface AppendResult {
|
|
167
|
+
readonly written: boolean;
|
|
168
|
+
readonly reason?: string | undefined;
|
|
169
|
+
readonly category?: CaptureGuardCategory | 'invalid_field' | undefined;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface ReadObservationsResult {
|
|
173
|
+
readonly entries: readonly ObservationCandidate[];
|
|
174
|
+
/** Lines that were not valid JSON objects at all. */
|
|
175
|
+
readonly droppedMalformed: number;
|
|
176
|
+
/** Lines whose `field` fell outside {@link ALLOWED_OBSERVATION_FIELDS}. */
|
|
177
|
+
readonly droppedUnknownField: number;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
// Path resolution — mirrors agent_user_profile.ts's global-layer functions.
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
/** Canonical write target for the global observation buffer. */
|
|
185
|
+
export function globalObservationsWriteTarget(
|
|
186
|
+
env?: user_global_paths.EnvMap | null,
|
|
187
|
+
): string {
|
|
188
|
+
return user_global_paths.write_target(GLOBAL_OBSERVATIONS_RELATIVE, { env: env ?? null });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Resolve the buffer's on-disk path — new namespace first, legacy fallback, `null` if neither exists. */
|
|
192
|
+
export function resolveGlobalObservationsPath(
|
|
193
|
+
env?: user_global_paths.EnvMap | null,
|
|
194
|
+
): string | null {
|
|
195
|
+
return user_global_paths.resolve_with_fallback(GLOBAL_OBSERVATIONS_RELATIVE, { env: env ?? null });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
// Capture-time guards
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
|
|
202
|
+
// Matches an imperative paired with a recurrence marker, in either order —
|
|
203
|
+
// "always fetch <url> on every message" / "run <cmd> at the start of each
|
|
204
|
+
// session". Deliberately broad: a false positive here costs one refused
|
|
205
|
+
// candidate the human never sees; a false negative persists a durable
|
|
206
|
+
// injection forever, in every project.
|
|
207
|
+
const _STANDING_COMMAND_RE =
|
|
208
|
+
/\b(?:always|every\s+(?:session|message|time|turn))\b[\s\S]{0,80}?\b(?:fetch|run|curl|execute|call|check)\b|\b(?:fetch|run|curl|execute|call|check)\b[\s\S]{0,80}?\b(?:at\s+the\s+start\s+of\s+(?:each|every)\s+(?:session|message)|on\s+every\s+(?:message|turn|session)|always)\b/i;
|
|
209
|
+
|
|
210
|
+
// "never criticize me", "always agree with me", "never say I'm wrong",
|
|
211
|
+
// "don't ever disagree with me" — a user weaponizing their own memory to
|
|
212
|
+
// enforce sycophancy (direct-answers Iron Law 1).
|
|
213
|
+
const _SELF_HARMFUL_PREFERENCE_RE =
|
|
214
|
+
/\bnever\s+(?:criticize|disagree\s+with|push\s+back\s+on|question|correct)\s+me\b|\balways\s+agree\s+with\s+me\b|\bnever\s+say\s+i(?:'|’)?m\s+wrong\b|\bdon'?t\s+ever\s+(?:disagree|criticize)\b/i;
|
|
215
|
+
|
|
216
|
+
/** `true` when `text` reads as a verbatim standing command. */
|
|
217
|
+
export function detectStandingCommand(text: string): boolean {
|
|
218
|
+
return _STANDING_COMMAND_RE.test(text);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** `true` when `text` reads as a self-harmful standing preference. */
|
|
222
|
+
export function detectSelfHarmfulPreference(text: string): boolean {
|
|
223
|
+
return _SELF_HARMFUL_PREFERENCE_RE.test(text);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Run all four capture-time guards against a candidate, in order. Returns
|
|
228
|
+
* the FIRST violation found (a candidate tripping two guards at once is
|
|
229
|
+
* refused for the earlier one; the categories are independently testable,
|
|
230
|
+
* not mutually exclusive in the input space).
|
|
231
|
+
*/
|
|
232
|
+
export function evaluateCaptureGuards(
|
|
233
|
+
candidate: ObservationCandidate,
|
|
234
|
+
options: { repo_root?: string | null } = {},
|
|
235
|
+
): CaptureGuardResult {
|
|
236
|
+
const text = `${candidate.suggest}\n${candidate.evidence}`;
|
|
237
|
+
|
|
238
|
+
if (detectStandingCommand(text)) {
|
|
239
|
+
return {
|
|
240
|
+
allowed: false,
|
|
241
|
+
category: 'standing_command',
|
|
242
|
+
reason:
|
|
243
|
+
'reads as a verbatim standing command — a stored directive becomes a durable ' +
|
|
244
|
+
'injection that re-fires forever, and at global scope that means every project, ' +
|
|
245
|
+
'not just this one. Refused at capture.',
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
if (detectSelfHarmfulPreference(text)) {
|
|
249
|
+
return {
|
|
250
|
+
allowed: false,
|
|
251
|
+
category: 'self_harmful_preference',
|
|
252
|
+
reason:
|
|
253
|
+
'would disable honest feedback across every project — surfaced to the user, ' +
|
|
254
|
+
'never stored.',
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const violations = redaction_scan(text, { repo_root: options.repo_root ?? null });
|
|
259
|
+
const hiddenUnicode = violations.find((v) => v.category === 'hidden_unicode');
|
|
260
|
+
if (hiddenUnicode) {
|
|
261
|
+
return {
|
|
262
|
+
allowed: false,
|
|
263
|
+
category: 'hidden_unicode',
|
|
264
|
+
reason: `invisible characters detected — possible identifier smuggling: ${hiddenUnicode.snippet}`,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
if (violations.length > 0) {
|
|
268
|
+
return {
|
|
269
|
+
allowed: false,
|
|
270
|
+
category: 'exclusion_list',
|
|
271
|
+
reason:
|
|
272
|
+
'capture-time exclusion-list match (credentials / third-party PII / financial ' +
|
|
273
|
+
`figures / health-legal-therapy / demographics / external-source identifiers): ${violations
|
|
274
|
+
.map((v) => v.category)
|
|
275
|
+
.join(', ')}`,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
return { allowed: true };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ---------------------------------------------------------------------------
|
|
282
|
+
// Write path
|
|
283
|
+
// ---------------------------------------------------------------------------
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Append one candidate to the global observation buffer, after running
|
|
287
|
+
* every capture-time guard. Never rewrites — a refused candidate is
|
|
288
|
+
* refused outright, not silently redacted-then-stored (the low-impact
|
|
289
|
+
* corpus's own "halt-and-surface, never silent-rewrite" discipline).
|
|
290
|
+
*/
|
|
291
|
+
export function appendGlobalObservation(
|
|
292
|
+
candidate: ObservationCandidate,
|
|
293
|
+
options: { env?: user_global_paths.EnvMap | null; repo_root?: string | null } = {},
|
|
294
|
+
): AppendResult {
|
|
295
|
+
if (!_ALLOWED_FIELD_SET.has(candidate.field)) {
|
|
296
|
+
return {
|
|
297
|
+
written: false,
|
|
298
|
+
category: 'invalid_field',
|
|
299
|
+
reason: `field '${candidate.field}' is outside the allowed schema enum`,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const guard = evaluateCaptureGuards(candidate, { repo_root: options.repo_root ?? null });
|
|
304
|
+
if (!guard.allowed) {
|
|
305
|
+
return { written: false, category: guard.category, reason: guard.reason };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const target = globalObservationsWriteTarget(options.env ?? null);
|
|
309
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
310
|
+
fs.appendFileSync(target, JSON.stringify(candidate) + '\n', 'utf-8');
|
|
311
|
+
// Phase 5 gate: count the proposal where it actually lands, so the
|
|
312
|
+
// review→accept rate cannot be skewed by a caller that bypasses the
|
|
313
|
+
// command layer. A refused candidate is deliberately NOT counted — it was
|
|
314
|
+
// never proposed to the human. Counter failure never fails the append.
|
|
315
|
+
try {
|
|
316
|
+
recordObservationProposed({ env: options.env ?? null });
|
|
317
|
+
} catch {
|
|
318
|
+
/* a counter is telemetry, never a gate on the buffer write */
|
|
319
|
+
}
|
|
320
|
+
return { written: true };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Apply the shared ≤5-facts-per-cycle cap across both channels. `existingCount`
|
|
325
|
+
* is however many facts the project-scoped channel already produced THIS
|
|
326
|
+
* cycle; only the remaining headroom is available to the candidates here.
|
|
327
|
+
* Never negative — a project-scoped channel that already hit the cap leaves
|
|
328
|
+
* zero room for the second channel this cycle.
|
|
329
|
+
*/
|
|
330
|
+
export function applySharedFactCap<T>(
|
|
331
|
+
existingCount: number,
|
|
332
|
+
candidates: readonly T[],
|
|
333
|
+
cap: number = SHARED_FACT_CAP,
|
|
334
|
+
): T[] {
|
|
335
|
+
const remaining = Math.max(0, cap - existingCount);
|
|
336
|
+
return candidates.slice(0, remaining);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// ---------------------------------------------------------------------------
|
|
340
|
+
// Read path — feeds `/agents:user review` / `/agents:user accept`.
|
|
341
|
+
// ---------------------------------------------------------------------------
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Read + parse the global observation buffer. Tolerant: a malformed JSON
|
|
345
|
+
* line or a line whose `field` falls outside the schema enum is dropped
|
|
346
|
+
* silently, with a count so the caller can surface "N observations
|
|
347
|
+
* skipped" — mirroring "Anything outside that set is dropped on read" in
|
|
348
|
+
* the project-local contract.
|
|
349
|
+
*/
|
|
350
|
+
/** Tolerant parse of a JSONL line's `context` field — malformed shapes read back as absent, never thrown. */
|
|
351
|
+
function _parseContext(raw: unknown): ObservationContext | undefined {
|
|
352
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
353
|
+
return undefined;
|
|
354
|
+
}
|
|
355
|
+
const rec = raw as Record<string, unknown>;
|
|
356
|
+
const project_path = typeof rec.project_path === 'string' ? rec.project_path : undefined;
|
|
357
|
+
const project_name = typeof rec.project_name === 'string' ? rec.project_name : undefined;
|
|
358
|
+
const first_seen = typeof rec.first_seen === 'string' ? rec.first_seen : undefined;
|
|
359
|
+
if (project_path === undefined || project_name === undefined || first_seen === undefined) {
|
|
360
|
+
return undefined;
|
|
361
|
+
}
|
|
362
|
+
return { project_path, project_name, first_seen };
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** Tolerant parse of a JSONL line's `seen_in` field — drops non-string entries rather than failing the whole line. */
|
|
366
|
+
function _parseSeenIn(raw: unknown): readonly string[] | undefined {
|
|
367
|
+
if (!Array.isArray(raw)) {
|
|
368
|
+
return undefined;
|
|
369
|
+
}
|
|
370
|
+
const out = raw.filter((v): v is string => typeof v === 'string');
|
|
371
|
+
return out.length > 0 ? out : undefined;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export function readGlobalObservations(
|
|
375
|
+
options: { env?: user_global_paths.EnvMap | null } = {},
|
|
376
|
+
): ReadObservationsResult {
|
|
377
|
+
const target = resolveGlobalObservationsPath(options.env ?? null);
|
|
378
|
+
if (target === null) {
|
|
379
|
+
return { entries: [], droppedMalformed: 0, droppedUnknownField: 0 };
|
|
380
|
+
}
|
|
381
|
+
let raw: string;
|
|
382
|
+
try {
|
|
383
|
+
raw = fs.readFileSync(target, 'utf-8');
|
|
384
|
+
} catch {
|
|
385
|
+
return { entries: [], droppedMalformed: 0, droppedUnknownField: 0 };
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
let droppedMalformed = 0;
|
|
389
|
+
let droppedUnknownField = 0;
|
|
390
|
+
const entries: ObservationCandidate[] = [];
|
|
391
|
+
|
|
392
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
393
|
+
const trimmed = line.trim();
|
|
394
|
+
if (trimmed === '') {
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
let obj: unknown;
|
|
398
|
+
try {
|
|
399
|
+
obj = JSON.parse(trimmed);
|
|
400
|
+
} catch {
|
|
401
|
+
droppedMalformed += 1;
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) {
|
|
405
|
+
droppedMalformed += 1;
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
const rec = obj as Record<string, unknown>;
|
|
409
|
+
const field = typeof rec.field === 'string' ? rec.field : '';
|
|
410
|
+
if (!_ALLOWED_FIELD_SET.has(field)) {
|
|
411
|
+
droppedUnknownField += 1;
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
entries.push({
|
|
415
|
+
ts: typeof rec.ts === 'string' ? rec.ts : '',
|
|
416
|
+
field,
|
|
417
|
+
suggest: typeof rec.suggest === 'string' ? rec.suggest : '',
|
|
418
|
+
source: typeof rec.source === 'string' ? rec.source : '',
|
|
419
|
+
evidence: typeof rec.evidence === 'string' ? rec.evidence : '',
|
|
420
|
+
context: _parseContext(rec.context),
|
|
421
|
+
seen_count: typeof rec.seen_count === 'number' ? rec.seen_count : undefined,
|
|
422
|
+
seen_in: _parseSeenIn(rec.seen_in),
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return { entries, droppedMalformed, droppedUnknownField };
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// ---------------------------------------------------------------------------
|
|
430
|
+
// Phase 3 — project attribution and the generalisation promotion.
|
|
431
|
+
//
|
|
432
|
+
// The operator's third ask ("P — project facts with no managed folder"),
|
|
433
|
+
// implemented as the council's round-2 reading: attribution, not isolation.
|
|
434
|
+
// No global project registry, no project-indexed directory tree, no fourth
|
|
435
|
+
// sensitivity class — see `agents/settings/contexts/global-user-memory-cut.md`.
|
|
436
|
+
//
|
|
437
|
+
// `routeProjectObservation` is the Phase 0 predicate wired as a router: a
|
|
438
|
+
// project WITH a managed `agents/` folder keeps its facts local exactly as
|
|
439
|
+
// today (this module never touches them); a project withOUT one persists the
|
|
440
|
+
// fact here, tagged with `context` so it survives that project's deletion.
|
|
441
|
+
// ---------------------------------------------------------------------------
|
|
442
|
+
|
|
443
|
+
/** Where a project-scoped observation is routed, per the Phase 0 predicate. */
|
|
444
|
+
export type ProjectObservationRoute = 'local' | 'global';
|
|
445
|
+
|
|
446
|
+
/** `managed` keeps facts in the project's own `agents/memory/`; anything else has nowhere local to land. */
|
|
447
|
+
export function routeForManagedStatus(status: ManagedAgentsFolderStatus): ProjectObservationRoute {
|
|
448
|
+
return status === 'managed' ? 'local' : 'global';
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/** Build the attribution footer for a project-scoped observation. `project_name` is the directory basename — the same human-readable unit the global knowledge card's `seen_in` footer already uses. */
|
|
452
|
+
export function buildObservationContext(projectRoot: string, firstSeen: string): ObservationContext {
|
|
453
|
+
return {
|
|
454
|
+
project_path: projectRoot,
|
|
455
|
+
project_name: path.basename(projectRoot) || projectRoot,
|
|
456
|
+
first_seen: firstSeen,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Capture-time guards for the two context strings ADDED by Phase 3, run
|
|
462
|
+
* before the context is ever attached to a candidate. `project_path` is
|
|
463
|
+
* *expected* to look like an absolute path — `redaction_scan`'s generic
|
|
464
|
+
* `project_path` category exists to catch a path LEAKED into free-form
|
|
465
|
+
* text, and would refuse every real value here by design, so only the
|
|
466
|
+
* `hidden_unicode` class (identifier smuggling, ADR-103) applies to it.
|
|
467
|
+
* `project_name` is free-form and gets the full scan, since a hostile or
|
|
468
|
+
* careless directory name could otherwise smuggle a secret or a hidden
|
|
469
|
+
* character past capture. Reuses the same hardened gate Phase 2 already
|
|
470
|
+
* calls for `suggest`/`evidence` — never a second detector.
|
|
471
|
+
*/
|
|
472
|
+
export function evaluateContextCaptureGuards(context: ObservationContext): CaptureGuardResult {
|
|
473
|
+
const pathHiddenUnicode = redaction_scan(context.project_path).find(
|
|
474
|
+
(v) => v.category === 'hidden_unicode',
|
|
475
|
+
);
|
|
476
|
+
if (pathHiddenUnicode) {
|
|
477
|
+
return {
|
|
478
|
+
allowed: false,
|
|
479
|
+
category: 'hidden_unicode',
|
|
480
|
+
reason:
|
|
481
|
+
'invisible characters detected in project_path — possible identifier smuggling: ' +
|
|
482
|
+
pathHiddenUnicode.snippet,
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const nameViolations = redaction_scan(context.project_name);
|
|
487
|
+
const nameHiddenUnicode = nameViolations.find((v) => v.category === 'hidden_unicode');
|
|
488
|
+
if (nameHiddenUnicode) {
|
|
489
|
+
return {
|
|
490
|
+
allowed: false,
|
|
491
|
+
category: 'hidden_unicode',
|
|
492
|
+
reason:
|
|
493
|
+
'invisible characters detected in project_name — possible identifier smuggling: ' +
|
|
494
|
+
nameHiddenUnicode.snippet,
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
if (nameViolations.length > 0) {
|
|
498
|
+
return {
|
|
499
|
+
allowed: false,
|
|
500
|
+
category: 'exclusion_list',
|
|
501
|
+
reason:
|
|
502
|
+
'project_name matched a capture-time exclusion pattern: ' +
|
|
503
|
+
nameViolations.map((v) => v.category).join(', '),
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
return { allowed: true };
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/** Current `seen_count` for an already-buffered context-bearing entry — 1 when the field is absent (its first sighting was never re-written with an explicit count). */
|
|
510
|
+
function _existingSeenCount(entry: ObservationCandidate): number {
|
|
511
|
+
return entry.seen_count ?? 1;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/** Current `seen_in` for an already-buffered context-bearing entry, falling back to its own context's project when the field is absent. */
|
|
515
|
+
function _existingSeenIn(entry: ObservationCandidate): readonly string[] {
|
|
516
|
+
if (entry.seen_in !== undefined) {
|
|
517
|
+
return entry.seen_in;
|
|
518
|
+
}
|
|
519
|
+
return entry.context !== undefined ? [entry.context.project_name] : [];
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/** Result of matching a new project-scoped candidate against the existing context-bearing buffer. */
|
|
523
|
+
export interface ObservationRecurrence {
|
|
524
|
+
readonly seen_count: number;
|
|
525
|
+
readonly seen_in: readonly string[];
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Decide whether `candidateText` is the SAME observation as one already
|
|
530
|
+
* buffered from a different project, using the identical dedup primitive
|
|
531
|
+
* (`_lib/text_similarity.ts`'s `MERGE_THRESHOLD`, via `classifySimilarity`)
|
|
532
|
+
* the curated-memory dedup path uses — "same observation" means the same
|
|
533
|
+
* thing in both places, per the roadmap. Only entries carrying a `context`
|
|
534
|
+
* are eligible matches; a pure user-attribute observation (no project to
|
|
535
|
+
* attribute to) never participates in cross-project recurrence counting.
|
|
536
|
+
*
|
|
537
|
+
* The buffer is append-only, so a recurring observation accumulates
|
|
538
|
+
* multiple lines over time, each carrying that append's cumulative
|
|
539
|
+
* `seen_count`/`seen_in`. Deliberately scans linearly (not
|
|
540
|
+
* `findMostSimilar`, which ties to the FIRST equal-score match) and keeps
|
|
541
|
+
* the LAST line at/above `MERGE_THRESHOLD` — otherwise a run of
|
|
542
|
+
* byte-identical `suggest` values would always tie back to the stale first
|
|
543
|
+
* sighting instead of the most recently accumulated state.
|
|
544
|
+
*
|
|
545
|
+
* Recurrence in the SAME project again does not bump the counter — the
|
|
546
|
+
* roadmap ties `seen_count` to "recurs in a DIFFERENT project" specifically,
|
|
547
|
+
* so the growth stays a cross-project confirmation tally, never a
|
|
548
|
+
* same-project noise counter.
|
|
549
|
+
*/
|
|
550
|
+
export function computeRecurrence(
|
|
551
|
+
candidateContext: ObservationContext,
|
|
552
|
+
candidateText: string,
|
|
553
|
+
existingEntries: readonly ObservationCandidate[],
|
|
554
|
+
): ObservationRecurrence {
|
|
555
|
+
const contextBearing = existingEntries.filter((e) => e.context !== undefined);
|
|
556
|
+
|
|
557
|
+
let matched: ObservationCandidate | undefined;
|
|
558
|
+
for (const entry of contextBearing) {
|
|
559
|
+
const score = jaccardSimilarity(candidateText, entry.suggest);
|
|
560
|
+
if (classifySimilarity(score) === 'merge') {
|
|
561
|
+
matched = entry;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
if (matched === undefined) {
|
|
565
|
+
return { seen_count: 1, seen_in: [candidateContext.project_name] };
|
|
566
|
+
}
|
|
567
|
+
const priorSeenIn = _existingSeenIn(matched);
|
|
568
|
+
if (priorSeenIn.includes(candidateContext.project_name)) {
|
|
569
|
+
// Same project recurring — evidence already counted; hold steady.
|
|
570
|
+
return { seen_count: _existingSeenCount(matched), seen_in: priorSeenIn };
|
|
571
|
+
}
|
|
572
|
+
return {
|
|
573
|
+
seen_count: _existingSeenCount(matched) + 1,
|
|
574
|
+
seen_in: [...priorSeenIn, candidateContext.project_name],
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/** A project-scoped candidate awaiting routing — no `context`/`seen_count`/`seen_in` yet; `routeProjectObservation` attaches them. */
|
|
579
|
+
export type ProjectObservationInput = Pick<ObservationCandidate, 'ts' | 'suggest' | 'source' | 'evidence'> & {
|
|
580
|
+
readonly field?: string;
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
export interface RouteProjectObservationOptions {
|
|
584
|
+
readonly env?: user_global_paths.EnvMap | null;
|
|
585
|
+
readonly repo_root?: string | null;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
export interface ProjectObservationRouteResult {
|
|
589
|
+
readonly route: ProjectObservationRoute;
|
|
590
|
+
readonly written: boolean;
|
|
591
|
+
readonly reason?: string | undefined;
|
|
592
|
+
readonly category?: CaptureGuardCategory | 'invalid_field' | undefined;
|
|
593
|
+
readonly context?: ObservationContext | undefined;
|
|
594
|
+
readonly seen_count?: number | undefined;
|
|
595
|
+
readonly seen_in?: readonly string[] | undefined;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* The Phase 0 predicate wired as the router. `managed` → returns `route:
|
|
600
|
+
* 'local'` and writes NOTHING here — the fact stays in that project's
|
|
601
|
+
* `agents/memory/` exactly as today, unchanged by this module. `unmanaged` /
|
|
602
|
+
* `not-a-project` → the observation is tagged with `context`, checked for
|
|
603
|
+
* cross-project recurrence, and appended to the global buffer via the SAME
|
|
604
|
+
* `appendGlobalObservation` guard pipeline Phase 2 established (never a
|
|
605
|
+
* second write path).
|
|
606
|
+
*
|
|
607
|
+
* `field` defaults to `'notes'` — the schema's free-form catch-all — since a
|
|
608
|
+
* project-scoped fact (a convention, an invariant, a recurring gotcha) has
|
|
609
|
+
* no natural mapping onto the closed identity/style enum; `context` is what
|
|
610
|
+
* distinguishes it from a plain user preference note, not a new field value.
|
|
611
|
+
*/
|
|
612
|
+
export function routeProjectObservation(
|
|
613
|
+
input: ProjectObservationInput,
|
|
614
|
+
projectRoot: string,
|
|
615
|
+
options: RouteProjectObservationOptions = {},
|
|
616
|
+
): ProjectObservationRouteResult {
|
|
617
|
+
const status = detect_managed_agents_folder(projectRoot);
|
|
618
|
+
const route = routeForManagedStatus(status);
|
|
619
|
+
if (route === 'local') {
|
|
620
|
+
return {
|
|
621
|
+
route: 'local',
|
|
622
|
+
written: false,
|
|
623
|
+
reason:
|
|
624
|
+
"managed agents/ folder detected — the fact stays in this project's agents/memory/ " +
|
|
625
|
+
'exactly as today; never routed to the global buffer.',
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const context = buildObservationContext(projectRoot, input.ts);
|
|
630
|
+
const contextGuard = evaluateContextCaptureGuards(context);
|
|
631
|
+
if (!contextGuard.allowed) {
|
|
632
|
+
return {
|
|
633
|
+
route: 'global',
|
|
634
|
+
written: false,
|
|
635
|
+
category: contextGuard.category,
|
|
636
|
+
reason: contextGuard.reason,
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const existing = readGlobalObservations({ env: options.env ?? null }).entries;
|
|
641
|
+
const recurrence = computeRecurrence(context, input.suggest, existing);
|
|
642
|
+
|
|
643
|
+
const candidate: ObservationCandidate = {
|
|
644
|
+
ts: input.ts,
|
|
645
|
+
field: input.field ?? 'notes',
|
|
646
|
+
suggest: input.suggest,
|
|
647
|
+
source: input.source,
|
|
648
|
+
evidence: input.evidence,
|
|
649
|
+
context,
|
|
650
|
+
seen_count: recurrence.seen_count,
|
|
651
|
+
seen_in: recurrence.seen_in,
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
const result = appendGlobalObservation(candidate, {
|
|
655
|
+
env: options.env ?? null,
|
|
656
|
+
repo_root: options.repo_root ?? null,
|
|
657
|
+
});
|
|
658
|
+
return {
|
|
659
|
+
route: 'global',
|
|
660
|
+
written: result.written,
|
|
661
|
+
reason: result.reason,
|
|
662
|
+
category: result.category,
|
|
663
|
+
context,
|
|
664
|
+
seen_count: recurrence.seen_count,
|
|
665
|
+
seen_in: recurrence.seen_in,
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// ---------------------------------------------------------------------------
|
|
670
|
+
// Promotion — the ONLY generalisation path (see agent-user-schema.md).
|
|
671
|
+
// ---------------------------------------------------------------------------
|
|
672
|
+
|
|
673
|
+
/** Human-confirmed-recurrence threshold for promotion candidacy. Never auto-promotes past this — it only makes the observation eligible for `/agents:user review` to surface, with a mandatory `promotion_reason` as human input before `/agents:user accept` writes anything. */
|
|
674
|
+
export const PROMOTION_SEEN_COUNT_THRESHOLD = 3;
|
|
675
|
+
|
|
676
|
+
export interface PromotionCandidate {
|
|
677
|
+
readonly observation: ObservationCandidate;
|
|
678
|
+
readonly seenCount: number;
|
|
679
|
+
readonly projects: readonly string[];
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Observations that have crossed the promotion-candidacy threshold. This is
|
|
684
|
+
* the ONLY generalisation path in the whole layer — the agent never infers a
|
|
685
|
+
* cross-project pattern itself (a non-goal in the roadmap's Goal section);
|
|
686
|
+
* `seen_count` only ever grows one write at a time via `routeProjectObservation`
|
|
687
|
+
* observing a genuinely new project, never a batch scan across the store.
|
|
688
|
+
* Crossing the threshold surfaces candidacy; it never writes `profile.md` —
|
|
689
|
+
* that still requires the human-confirmed `/agents:user accept` step, with a
|
|
690
|
+
* `promotion_reason` the review flow must collect (mirroring ADR-121's rule
|
|
691
|
+
* that there is no auto-`shareable` path for a knowledge card).
|
|
692
|
+
*/
|
|
693
|
+
export function findPromotionCandidates(
|
|
694
|
+
entries: readonly ObservationCandidate[],
|
|
695
|
+
): PromotionCandidate[] {
|
|
696
|
+
const out: PromotionCandidate[] = [];
|
|
697
|
+
for (const entry of entries) {
|
|
698
|
+
if (entry.context === undefined) {
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
const seenCount = _existingSeenCount(entry);
|
|
702
|
+
if (seenCount >= PROMOTION_SEEN_COUNT_THRESHOLD) {
|
|
703
|
+
out.push({ observation: entry, seenCount, projects: _existingSeenIn(entry) });
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
return out;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* The value that may ever reach `profile.md` for a promoted observation —
|
|
711
|
+
* the fact text ONLY, never `context`, never `seen_in`. This is what keeps
|
|
712
|
+
* the long-lived artefact converging to zero project references: the
|
|
713
|
+
* counter is evidence that lived on the buffer entry, and only the
|
|
714
|
+
* short-lived buffer entry needed it (see agent-user-schema.md § Project
|
|
715
|
+
* attribution — "`seen_in[]` is a narrower metadata surface, not a null
|
|
716
|
+
* one").
|
|
717
|
+
*/
|
|
718
|
+
export function promotionValueFor(candidate: ObservationCandidate): string {
|
|
719
|
+
return candidate.suggest;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// ---------------------------------------------------------------------------
|
|
723
|
+
// Phase 4 — delete, revoke, audit (road-to-global-user-memory).
|
|
724
|
+
//
|
|
725
|
+
// `deleteGlobalObservation` and `purgeProjectContext` are the delete
|
|
726
|
+
// counterpart of `appendGlobalObservation` / `routeProjectObservation`
|
|
727
|
+
// above — this module remains the ONLY writer (now also the only deleter)
|
|
728
|
+
// of the observation buffer, mirroring the single-writer discipline the
|
|
729
|
+
// module docstring already states for the append path.
|
|
730
|
+
// ---------------------------------------------------------------------------
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* Deterministic content-derived identifier for one buffered observation —
|
|
734
|
+
* this layer's analogue of a knowledge card's `card_id_from` slug. The
|
|
735
|
+
* JSONL schema (Phase 2/3) never added a stored `id` field, so identity is
|
|
736
|
+
* derived from the fields that make one capture event unique: `ts`
|
|
737
|
+
* (capture timestamp) plus `field` + `suggest` (what was proposed).
|
|
738
|
+
* `source` / `evidence` / `context` are deliberately excluded — two
|
|
739
|
+
* identical suggestions logged at the same instant from a different source
|
|
740
|
+
* string, or with different evidence phrasing, are still "the same
|
|
741
|
+
* observation" to a human choosing to delete it.
|
|
742
|
+
*/
|
|
743
|
+
export function observationId(entry: Pick<ObservationCandidate, 'ts' | 'field' | 'suggest'>): string {
|
|
744
|
+
const canonical = `${entry.ts}\u0000${entry.field}\u0000${entry.suggest}`;
|
|
745
|
+
return crypto.createHash('sha256').update(canonical, 'utf-8').digest('hex').slice(0, 16);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/** Rewrite the buffer file with exactly `entries` — the shared primitive both delete paths use. */
|
|
749
|
+
function _writeBufferEntries(
|
|
750
|
+
entries: readonly ObservationCandidate[],
|
|
751
|
+
env: user_global_paths.EnvMap | null,
|
|
752
|
+
): void {
|
|
753
|
+
const target = globalObservationsWriteTarget(env);
|
|
754
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
755
|
+
const body = entries.map((e) => JSON.stringify(e)).join('\n');
|
|
756
|
+
fs.writeFileSync(target, entries.length > 0 ? body + '\n' : '', 'utf-8');
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
export interface DeleteObservationResult {
|
|
760
|
+
readonly deleted: boolean;
|
|
761
|
+
readonly tombstone?: RevocationEntry | undefined;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* Delete ONE buffered observation by its content-derived id. Writes an
|
|
766
|
+
* append-only tombstone to `user_global_revocations.ts`'s ledger BEFORE the
|
|
767
|
+
* buffer is rewritten without it — never the reverse — mirroring
|
|
768
|
+
* `knowledge_global_cli.ts`'s `_forget_one` (tombstone THEN delete).
|
|
769
|
+
* `deleted: false` (no tombstone written) when no entry matches `observation_id`.
|
|
770
|
+
*/
|
|
771
|
+
export function deleteGlobalObservation(
|
|
772
|
+
observation_id: string,
|
|
773
|
+
reason: string,
|
|
774
|
+
options: { env?: user_global_paths.EnvMap | null; today?: string } = {},
|
|
775
|
+
): DeleteObservationResult {
|
|
776
|
+
const env = options.env ?? null;
|
|
777
|
+
const { entries } = readGlobalObservations({ env });
|
|
778
|
+
const hasMatch = entries.some((e) => observationId(e) === observation_id);
|
|
779
|
+
if (!hasMatch) {
|
|
780
|
+
return { deleted: false };
|
|
781
|
+
}
|
|
782
|
+
const tombstone = revocations.appendTombstone(observation_id, reason, {
|
|
783
|
+
today: options.today,
|
|
784
|
+
env,
|
|
785
|
+
});
|
|
786
|
+
_writeBufferEntries(
|
|
787
|
+
entries.filter((e) => observationId(e) !== observation_id),
|
|
788
|
+
env,
|
|
789
|
+
);
|
|
790
|
+
return { deleted: true, tombstone };
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
export interface PurgeProjectContextResult {
|
|
794
|
+
readonly purgedCount: number;
|
|
795
|
+
readonly tombstones: readonly RevocationEntry[];
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Whole-project-context purge — removes EVERY buffered observation
|
|
800
|
+
* attributed (via `context.project_path`) to `projectPath`, so a deleted
|
|
801
|
+
* project's facts do not linger in the global buffer forever. Tombstones
|
|
802
|
+
* each removed observation individually BEFORE the buffer is rewritten,
|
|
803
|
+
* mirroring `knowledge_global_cli.ts`'s `cmd_purge` (one tombstone per card,
|
|
804
|
+
* written before that card is wiped). Observations from OTHER projects and
|
|
805
|
+
* observations carrying no `context` at all (pure user-attribute facts, not
|
|
806
|
+
* project-attributed) are left untouched. `purgedCount: 0` when no
|
|
807
|
+
* observation matches `projectPath` — the buffer is not rewritten in that
|
|
808
|
+
* case, so an unrelated purge call never touches the file's mtime.
|
|
809
|
+
*/
|
|
810
|
+
export function purgeProjectContext(
|
|
811
|
+
projectPath: string,
|
|
812
|
+
reason: string,
|
|
813
|
+
options: { env?: user_global_paths.EnvMap | null; today?: string } = {},
|
|
814
|
+
): PurgeProjectContextResult {
|
|
815
|
+
const env = options.env ?? null;
|
|
816
|
+
const { entries } = readGlobalObservations({ env });
|
|
817
|
+
const toRemove = entries.filter((e) => e.context?.project_path === projectPath);
|
|
818
|
+
if (toRemove.length === 0) {
|
|
819
|
+
return { purgedCount: 0, tombstones: [] };
|
|
820
|
+
}
|
|
821
|
+
const tombstones = toRemove.map((e) =>
|
|
822
|
+
revocations.appendTombstone(observationId(e), reason, { today: options.today, env }),
|
|
823
|
+
);
|
|
824
|
+
const removedIds = new Set(toRemove.map((e) => observationId(e)));
|
|
825
|
+
_writeBufferEntries(
|
|
826
|
+
entries.filter((e) => !removedIds.has(observationId(e))),
|
|
827
|
+
env,
|
|
828
|
+
);
|
|
829
|
+
return { purgedCount: toRemove.length, tombstones };
|
|
830
|
+
}
|