@miller-tech/uap 1.40.0 → 1.41.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.
Files changed (150) hide show
  1. package/README.md +109 -642
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/cli/deliver-defaults.d.ts +23 -0
  4. package/dist/cli/deliver-defaults.d.ts.map +1 -0
  5. package/dist/cli/deliver-defaults.js +121 -0
  6. package/dist/cli/deliver-defaults.js.map +1 -0
  7. package/dist/cli/init.d.ts.map +1 -1
  8. package/dist/cli/init.js +29 -0
  9. package/dist/cli/init.js.map +1 -1
  10. package/dist/cli/setup.d.ts.map +1 -1
  11. package/dist/cli/setup.js +19 -0
  12. package/dist/cli/setup.js.map +1 -1
  13. package/dist/policies/policy-tools.d.ts +7 -0
  14. package/dist/policies/policy-tools.d.ts.map +1 -1
  15. package/dist/policies/policy-tools.js +24 -2
  16. package/dist/policies/policy-tools.js.map +1 -1
  17. package/docs/INDEX.md +48 -286
  18. package/docs/architecture/OVERVIEW.md +328 -0
  19. package/docs/architecture/PROTOCOL.md +204 -0
  20. package/docs/benchmarks/README.md +17 -192
  21. package/docs/getting-started/CONFIGURATION.md +237 -0
  22. package/docs/getting-started/INSTALLATION.md +125 -0
  23. package/docs/getting-started/QUICKSTART.md +115 -0
  24. package/docs/guides/COORDINATION.md +162 -0
  25. package/docs/guides/DELIVER.md +115 -0
  26. package/docs/guides/DEPLOY_BATCHING.md +212 -0
  27. package/docs/guides/DROIDS_AND_SKILLS.md +202 -0
  28. package/docs/guides/LOCAL_MODELS.md +148 -0
  29. package/docs/guides/MCP_ROUTER.md +195 -0
  30. package/docs/guides/MEMORY.md +235 -0
  31. package/docs/guides/MULTI_MODEL.md +223 -0
  32. package/docs/guides/POLICIES.md +190 -0
  33. package/docs/guides/WORKTREE_WORKFLOW.md +185 -0
  34. package/docs/integrations/MCP_ROUTER.md +147 -0
  35. package/docs/integrations/RTK.md +102 -0
  36. package/docs/reference/API.md +485 -0
  37. package/docs/reference/CLI.md +719 -0
  38. package/docs/reference/CONFIGURATION.md +90 -193
  39. package/docs/reference/DATABASE_SCHEMA.md +110 -344
  40. package/docs/reference/FEATURES.md +176 -472
  41. package/docs/reference/PATTERNS.md +102 -0
  42. package/docs/reference/PLATFORMS.md +83 -0
  43. package/package.json +3 -1
  44. package/src/policies/enforcers/7ebbc721-7540-4e9f-879a-770e0213a09b_architecture_review.py +101 -0
  45. package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
  46. package/src/policies/enforcers/_common.py +100 -0
  47. package/src/policies/enforcers/artifact_hygiene.py +52 -0
  48. package/src/policies/enforcers/cluster_routing.py +63 -0
  49. package/src/policies/enforcers/codebase_read_before_plan.py +52 -0
  50. package/src/policies/enforcers/coord_overlap.py +81 -0
  51. package/src/policies/enforcers/delivery_enforcement.py +97 -0
  52. package/src/policies/enforcers/doc_live_over_report.py +50 -0
  53. package/src/policies/enforcers/expert_review_required.py +135 -0
  54. package/src/policies/enforcers/iac_parity.py +53 -0
  55. package/src/policies/enforcers/mcp_router_first.py +37 -0
  56. package/src/policies/enforcers/memory_before_plan.py +61 -0
  57. package/src/policies/enforcers/parallel_reads.py +50 -0
  58. package/src/policies/enforcers/rtk_wrap.py +44 -0
  59. package/src/policies/enforcers/schema_diff_gate.py +80 -0
  60. package/src/policies/enforcers/session_memory_write.py +52 -0
  61. package/src/policies/enforcers/task_required.py +131 -0
  62. package/src/policies/enforcers/test_gate.py +58 -0
  63. package/src/policies/enforcers/validate_plan_before_build.py +75 -0
  64. package/src/policies/enforcers/worktree_required.py +57 -0
  65. package/src/policies/schemas/policies/architecture-review.md +51 -0
  66. package/src/policies/schemas/policies/artifact-hygiene.md +29 -0
  67. package/src/policies/schemas/policies/cluster-routing.md +31 -0
  68. package/src/policies/schemas/policies/codebase-read-before-plan.md +30 -0
  69. package/src/policies/schemas/policies/coord-overlap.md +24 -0
  70. package/src/policies/schemas/policies/delivery-enforcement.md +45 -0
  71. package/src/policies/schemas/policies/doc-live-over-report.md +32 -0
  72. package/src/policies/schemas/policies/expert-review-required.md +60 -0
  73. package/src/policies/schemas/policies/iac-parity.md +31 -0
  74. package/src/policies/schemas/policies/mandatory-testing-deployment.md +147 -0
  75. package/src/policies/schemas/policies/mcp-router-first.md +24 -0
  76. package/src/policies/schemas/policies/memory-before-plan.md +24 -0
  77. package/src/policies/schemas/policies/merge-deploy-monitor-verify.md +145 -0
  78. package/src/policies/schemas/policies/parallel-reads.md +24 -0
  79. package/src/policies/schemas/policies/rtk-wrap.md +26 -0
  80. package/src/policies/schemas/policies/schema-diff-gate.md +30 -0
  81. package/src/policies/schemas/policies/session-memory-write.md +24 -0
  82. package/src/policies/schemas/policies/task-required.md +49 -0
  83. package/src/policies/schemas/policies/test-gate.md +24 -0
  84. package/src/policies/schemas/policies/validate-plan-before-build.md +28 -0
  85. package/src/policies/schemas/policies/worktree-required.md +28 -0
  86. package/templates/hooks/uap-policy-gate.sh +5 -0
  87. package/docs/AGENTS.md +0 -423
  88. package/docs/DOCUMENTATION_AUDIT_REPORT.md +0 -131
  89. package/docs/GETTING_STARTED.md +0 -288
  90. package/docs/PROJECT_ANALYSIS_REPORT.md +0 -510
  91. package/docs/architecture/COMPLETE_ARCHITECTURE.md +0 -748
  92. package/docs/architecture/EXPERT_STACK.md +0 -137
  93. package/docs/architecture/MULTI_MODEL.md +0 -224
  94. package/docs/architecture/PLATFORM_GATING.md +0 -68
  95. package/docs/architecture/SYSTEM_ANALYSIS.md +0 -334
  96. package/docs/architecture/UAP_COMPLIANCE.md +0 -217
  97. package/docs/architecture/UAP_PROTOCOL.md +0 -339
  98. package/docs/architecture/UAP_STRICT_DROIDS.md +0 -172
  99. package/docs/archive/BALLS_MODE_SELF_ANALYSIS.md +0 -260
  100. package/docs/archive/BENCHMARK_GAPS_AND_PLAN.md +0 -146
  101. package/docs/archive/FAILING_TASKS_SOLUTION_PLAN.md +0 -668
  102. package/docs/archive/JINJA2-SYSTEM-MESSAGE-FIX.md +0 -209
  103. package/docs/archive/MODEL_ROUTING_IMPLEMENTATION_SUMMARY.md +0 -281
  104. package/docs/archive/MODEL_ROUTING_OPTIMIZATION_PLAN.md +0 -320
  105. package/docs/archive/NPM-PUBLISH-V0.9.1.md +0 -240
  106. package/docs/archive/OPTIMIZATION_OPTIONS.md +0 -334
  107. package/docs/archive/PARALLELISM_GAPS_AND_OPTIONS.md +0 -422
  108. package/docs/archive/POLICY_GATE_IMPLEMENTATION.md +0 -245
  109. package/docs/archive/SETUP_IMPROVEMENTS.md +0 -213
  110. package/docs/archive/UAP_GENERIC_OPTIMIZATION_PLAN.md +0 -270
  111. package/docs/archive/UAP_OPTIMIZATION_PLAN.md +0 -701
  112. package/docs/archive/UAP_V103_PATTERN_DESIGN.md +0 -315
  113. package/docs/archive/UAP_V104_COMPLIANCE_DESIGN.md +0 -223
  114. package/docs/archive/changelog/2026-03-10_uap-100-compliance.md +0 -77
  115. package/docs/archive/changelog/2026-03-10_uap-full-system-verification.md +0 -109
  116. package/docs/archive/opencode-integration-guide.md +0 -740
  117. package/docs/archive/opencode-integration-quickref.md +0 -180
  118. package/docs/benchmarks/OVERNIGHT_RUNNER.md +0 -341
  119. package/docs/benchmarks/SPECULATIVE_DECODING_JOURNEY_2026-03.md +0 -221
  120. package/docs/benchmarks/VALIDATION_PLAN.md +0 -568
  121. package/docs/blog/SPECULATIVE_DECODING_PRODUCTION_PLAYBOOK.md +0 -139
  122. package/docs/blog/local-coding-agents.md +0 -266
  123. package/docs/blog/x-thread.md +0 -254
  124. package/docs/deployment/DEPLOYMENT.md +0 -895
  125. package/docs/deployment/DEPLOYMENT_STRATEGIES.md +0 -518
  126. package/docs/deployment/DEPLOY_BATCHER_ANALYSIS.md +0 -224
  127. package/docs/deployment/DEPLOY_BATCHING.md +0 -273
  128. package/docs/deployment/DEPLOY_BUCKETING_ANALYSIS.md +0 -420
  129. package/docs/deployment/QWEN35_LLAMA_CPP.md +0 -426
  130. package/docs/deployment/UAP_LLAMA_ANTHROPIC_PROXY_BOOTSTRAP.md +0 -279
  131. package/docs/getting-started/INTEGRATION.md +0 -628
  132. package/docs/getting-started/OVERVIEW.md +0 -324
  133. package/docs/getting-started/SETUP.md +0 -377
  134. package/docs/integrations/MCP_ROUTER_SETUP.md +0 -445
  135. package/docs/integrations/RTK_INTEGRATION.md +0 -468
  136. package/docs/operations/TROUBLESHOOTING.md +0 -660
  137. package/docs/pr/PR_SPECULATIVE_DOCS_TEMPLATE.md +0 -146
  138. package/docs/pr/UPSTREAM_PRS.md +0 -424
  139. package/docs/reference/API_REFERENCE.md +0 -903
  140. package/docs/reference/EXPERT_DROIDS.md +0 -219
  141. package/docs/reference/HARNESS-MATRIX.md +0 -318
  142. package/docs/reference/PATTERN_LIBRARY.md +0 -636
  143. package/docs/reference/UAP_CLI_REFERENCE.md +0 -620
  144. package/docs/research/BEHAVIORAL_PATTERNS.md +0 -228
  145. package/docs/research/DOMAIN_STRATEGIES.md +0 -316
  146. package/docs/research/MEMORY_SYSTEMS_COMPARISON.md +0 -812
  147. package/docs/research/PATTERN_ANALYSIS_2026-01-18.md +0 -436
  148. package/docs/research/PERFORMANCE_ANALYSIS_2026-01-18.md +0 -209
  149. package/docs/research/PERFORMANCE_TEST_PLAN.md +0 -383
  150. package/docs/research/TERMINAL_BENCH_LEARNINGS.md +0 -217
@@ -0,0 +1,102 @@
1
+ # Pattern Library Reference
2
+
3
+ > Universal Agent Protocol (UAP) v1.40.0
4
+
5
+ UAP ships an execution-pattern library: a set of reusable problem-solving
6
+ strategies that are matched to the current task and surfaced to the agent.
7
+ Patterns are defined as markdown files under `.factory/patterns/`, catalogued
8
+ in `.factory/patterns/index.json`, and retrieved on demand via a Qdrant-backed
9
+ RAG flow (`uap patterns query`).
10
+
11
+ ## The 23 Patterns
12
+
13
+ The canonical roster lives in `.factory/patterns/index.json`. Each pattern has
14
+ a numeric (or string) id, a markdown body file, a title, an abbreviation, a
15
+ category, and a keyword set used for retrieval.
16
+
17
+ | ID | Title | Abbreviation | Category | What it does |
18
+ |----|-------|--------------|----------|--------------|
19
+ | P12 | Output Existence Verification | OE-Verify | Verification | Confirm the artifact a task was supposed to produce actually exists before claiming done. **Always enforced.** |
20
+ | P13 | Iterative Refinement Loop | Iter-Ref | Testing | Run tests, fix, and re-run in a loop until they pass. |
21
+ | P14 | Output Format Validation | Format-Check | Verification | Validate that produced output matches the required format (JSON/YAML/CSV). |
22
+ | P16 | Task-First Execution | Task-First | Execution | When the task is clear, execute directly instead of over-planning. |
23
+ | P17 | Constraint Extraction | Extract-Constraints | Planning | Pull hard constraints ("exactly", "only", "no more than") out of the prompt before acting. |
24
+ | P19 | Impossible Task Refusal | Refuse-Impossible | Safety | Detect and refuse tasks that are fundamentally impossible. |
25
+ | P20 | Adversarial Thinking | Adversarial | Security | Reason about how something could be bypassed/exploited/evaded. |
26
+ | P21 | Chess Engine Integration | Chess-Engine | Domain-Specific | Delegate chess reasoning (FEN, best move, checkmate) to an engine. |
27
+ | P22 | Git Recovery Forensics | Git-Recovery | Recovery | Recover lost/corrupted git state via reflog and forensic inspection. |
28
+ | P23 | Compression Impossibility Detection | Compress-Check | Verification | Detect already-compressed data so re-compression isn't attempted. |
29
+ | P24 | Polyglot Code Construction | Polyglot | Code-Golf | Construct source that compiles/runs as multiple languages. |
30
+ | P25 | Service Configuration Pipeline | Service-Config | DevOps | Configure, validate, and reload a service/daemon as a pipeline. |
31
+ | P26 | Near-Miss Iteration | Near-Miss | Testing | When a test result is a small gap away, tweak and re-check. |
32
+ | P28 | Service Smoke Test | Smoke-Test | Verification | After deploy/start, run a health check / smoke test to verify. |
33
+ | P30 | Performance Threshold Tuning | Perf-Threshold | Optimization | Measure performance and tune against a percentage/threshold target. |
34
+ | P31 | Round-Trip Verification | Round-Trip | Verification | Encode then decode (or compress/decompress) and verify the round trip. |
35
+ | P32 | CLI Execution Verification | CLI-Verify | Verification | Verify a CLI/binary actually runs as expected. |
36
+ | P33 | Numerical Stability Testing | Num-Stable | Testing | Test floating-point/numerical work for precision and stability. |
37
+ | P34 | Image-to-Structured Pipeline | Image-Structured | Domain-Specific | Convert images (OCR/diagram/board) into structured data. |
38
+ | P35 | Decoder-First Analysis | Decoder-First | Analysis | Build/understand the decoder/parser first when reverse-engineering a format. **Always enforced.** |
39
+ | P36 | Competition Domain Research | Competition-Research | Research | Research the competitive domain (win rate, leaderboard, tournament) before optimizing. |
40
+ | P37 | Ambiguity Detection & Resolution | Ambiguity-Detect | Planning | Detect vague/unspecified requirements and clarify before acting. |
41
+ | IaC | Infrastructure as Code Parity | IaC-Parity | Infrastructure | Keep infrastructure changes reflected in IaC (terraform/kubernetes) for reproducibility. |
42
+
43
+ ### Always-on patterns
44
+
45
+ Two patterns are unconditionally included regardless of the matched task, set
46
+ in `src/coordination/pattern-router.ts`:
47
+
48
+ ```js
49
+ const alwaysIncludeIds = ['P12', 'P35']; // Output Existence, Decoder-First
50
+ ```
51
+
52
+ - **P12 (Output Existence Verification)** — guards against "claiming done"
53
+ without producing the artifact.
54
+ - **P35 (Decoder-First Analysis)** — anchors format/reverse-engineering work.
55
+
56
+ ## How pattern RAG works
57
+
58
+ Pattern retrieval is semantic, not keyword-based. The flow:
59
+
60
+ 1. **Indexing** (`uap patterns index`) runs a generated Python indexer
61
+ (`agents/scripts/index_patterns_to_qdrant.py`). It scans multiple sources —
62
+ `CLAUDE.md` (with `@include` resolution), additional source files
63
+ (`AGENTS.md`, etc.), `*/SKILL.md` skill files, and `.factory/patterns/*.md`
64
+ (using `index.json` to preserve the canonical `PNN: Title` identity and
65
+ keyword set). Documents are de-duplicated by content hash, embedded, and
66
+ upserted into the Qdrant collection.
67
+ 2. **Querying** (`uap patterns query "<task>"`) runs the generated query script
68
+ (`agents/scripts/query_patterns.py`), embeds the query with the same model,
69
+ and runs a cosine `query_points` search against the collection, returning the
70
+ top-K hits above the score threshold.
71
+ 3. **Fallback** — if the Python query script is absent, the CLI falls back to a
72
+ direct Qdrant `scroll` + keyword match (less accurate) and prints a notice.
73
+
74
+ ### RAG defaults
75
+
76
+ From `src/cli/patterns.ts` (`getPatternRagConfig`), overridable via
77
+ `.uap.json` under `memory.patternRag`:
78
+
79
+ | Setting | Default |
80
+ |---------|---------|
81
+ | Collection | `agent_patterns` |
82
+ | Embedding model | `all-MiniLM-L6-v2` |
83
+ | Vector size | `384` (Cosine distance) |
84
+ | Score threshold | `0.35` |
85
+ | Top-K | `2` |
86
+ | Index script | `./agents/scripts/index_patterns_to_qdrant.py` |
87
+ | Query script | `./agents/scripts/query_patterns.py` |
88
+ | Source file | `CLAUDE.md` |
89
+ | Max body chars | `400` (display); indexer truncates bodies at 2000 |
90
+
91
+ The query script requires a Python with `sentence-transformers` and
92
+ `qdrant-client`. The CLI auto-discovers `agents/.venv/bin/python`,
93
+ `.venv/bin/python`, then `python3`/`python`, and can bootstrap a venv.
94
+
95
+ ## `uap patterns` CLI
96
+
97
+ | Command | Description |
98
+ |---------|-------------|
99
+ | `uap patterns status` | Show RAG config, Qdrant collection point count, Python/script availability. |
100
+ | `uap patterns index` | (Re)index pattern/doc sources into the Qdrant collection. `--verbose` for full output. |
101
+ | `uap patterns query "<task>"` | Semantic search for task-relevant patterns. Flags: `--top <n>`, `--min-score <f>`, `--format text\|json`. |
102
+ | `uap patterns generate` | Generate the Python index/query scripts. `--force` to overwrite. |
@@ -0,0 +1,83 @@
1
+ # Platform & Harness Reference
2
+
3
+ > Universal Agent Protocol (UAP) v1.40.0
4
+
5
+ UAP integrates with 9 agent harnesses. The canonical list is `ALL_TARGETS` in
6
+ `src/cli/hooks.ts`. Every platform receives the UAP enforcement layer: lifecycle
7
+ hooks, the DB-driven policy gate, and (where supported) the hierarchical MCP
8
+ router.
9
+
10
+ ## Supported platforms
11
+
12
+ | Target flag | Platform | Config location | Integration style |
13
+ |-------------|----------|-----------------|-------------------|
14
+ | `claude` | Claude Code | `.claude/settings.local.json` + `.claude/hooks/` | Native hook events |
15
+ | `factory` | Factory.AI Droid | `.factory/settings.local.json` + `.factory/hooks/` | Native hook events (`$FACTORY_PROJECT_DIR`) |
16
+ | `cursor` | Cursor | `.cursor/hooks.json` + `.cursor/hooks/` | Native hook events |
17
+ | `vscode` | VSCode | `.claude/settings.local.json` + `.claude/hooks/` | Claude Code hook format (via third-party skills) |
18
+ | `opencode` | OpenCode | `.opencode/plugin/uap-session-hooks.ts` + `.opencode/hooks/` | TypeScript plugin |
19
+ | `codex` | Codex CLI | `AGENTS.md`, `.codex/config.toml`, `.codex/hooks/`, `.agents/skills/` | AGENTS.md + skills + MCP server |
20
+ | `forgecode` | ForgeCode | `.forge/forgecode.plugin.sh` + `.forge/hooks/` | ZSH plugin |
21
+ | `omp` | Oh-My-Pi | `.uap/omp/settings.json` + `.uap/omp/hooks/{pre,post}/` | omp pre/post hook dirs |
22
+ | `hermes` | Hermes Agent (NousResearch) | `~/.hermes/config.yaml` + `~/.hermes/agent-hooks/` | **Global** YAML config + MCP |
23
+
24
+ > **Hermes is global.** Its config lives under `~/.hermes` (or `$HERMES_HOME`),
25
+ > so it is excluded from the default project install loop (`PROJECT_TARGETS`).
26
+ > Installing it requires an explicit `-t hermes`. `status`/`doctor` still report it.
27
+
28
+ ## Support matrix
29
+
30
+ | Platform | Lifecycle hooks | Worktree/Bash guards | Policy gate | MCP router |
31
+ |----------|-----------------|----------------------|-------------|------------|
32
+ | Claude Code | Native | Yes | Hard (PreToolUse) | Yes |
33
+ | Factory.AI | Native | Yes | Hard (PreToolUse) | Yes |
34
+ | Cursor | Native | Yes | Hard (preToolUse) | Yes |
35
+ | VSCode | Native (Claude format) | Yes | Hard (PreToolUse) | Yes |
36
+ | OpenCode | Plugin events | Yes | Hard (`tool.execute.before` → exit 2 blocks) | Yes |
37
+ | Codex CLI | Advisory (no native pre-tool event) | Advisory | Hard for MCP-routed tools; advisory for native edit/bash | Yes (`[mcp_servers.uap]`) |
38
+ | ForgeCode | ZSH plugin | Yes (scripts) | Via gate script | — |
39
+ | Oh-My-Pi | pre/post dirs | Yes | Yes (`uap-policy-gate.sh`) | — |
40
+ | Hermes | `pre_tool_call` / `on_session_start` | `pre_tool_call` matcher | Hard via `uap-policy-gate-hermes.sh` (emits block JSON) | Yes (`mcp_servers.uap`) |
41
+
42
+ > **Gating notes.** Codex CLI has no native pre-tool-use hook event, so policy
43
+ > gating is **hard** only for tools routed through the UAP MCP server
44
+ > (`execute_tool` runs the PolicyGate) and **advisory** for Codex-native
45
+ > edit/bash. `uap hooks doctor` reports Codex as MCP-gated. Hermes hooks are
46
+ > fail-open by design, but the UAP gate always emits a decision JSON so real
47
+ > blocks are enforced; Hermes prompts once to approve each hook command
48
+ > (`~/.hermes/shell-hooks-allowlist.json`) unless `hooks_auto_accept: true`.
49
+
50
+ ## Lifecycle hooks installed
51
+
52
+ The shared hook scripts (copied from `templates/hooks/`) are wired into each
53
+ platform's lifecycle events:
54
+
55
+ | Script | Event | Purpose |
56
+ |--------|-------|---------|
57
+ | `session-start.sh` | SessionStart | Injects recent memory context. |
58
+ | `pre-tool-use-edit-write.sh` | PreToolUse (Edit/Write/MultiEdit) | Worktree file guard — **blocks** edits outside worktree dirs. |
59
+ | `pre-tool-use-bash.sh` | PreToolUse (Bash) | Dangerous-command guard — **blocks** force push, `terraform apply`, etc. |
60
+ | `uap-policy-gate.sh` | PreToolUse | DB-driven policy gate (`policies.db` + `.policy-tools/*.py`). |
61
+ | `post-tool-use-edit-write.sh` | PostToolUse (Edit/Write) | Build gate + backup reminder after edits. |
62
+ | `pre-compact.sh` | PreCompact | Flushes a compaction marker to memory. |
63
+ | `post-compact.sh` | PostCompact | Re-injects policy awareness after compaction. |
64
+ | `stop.sh` | Stop | Completion-gate checklist + session cleanup. |
65
+ | `session-end.sh` | SessionEnd | Agent deregistration + backup retention. |
66
+ | `loop-protection.sh` | — | Loop/spawn protection. |
67
+
68
+ ## Install & verify
69
+
70
+ ```bash
71
+ uap hooks install # install for all PROJECT_TARGETS (excludes hermes)
72
+ uap hooks install -t claude # install for a single platform
73
+ uap hooks install -t hermes # required to install the global Hermes config
74
+ uap hooks status # per-platform script + settings status (all targets)
75
+ uap hooks doctor # health check across all targets
76
+ ```
77
+
78
+ The MCP router is configured separately and across all harnesses with:
79
+
80
+ ```bash
81
+ uap mcp-setup # configure the hierarchical MCP router everywhere
82
+ uap mcp-router start # run the stdio MCP router server
83
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miller-tech/uap",
3
- "version": "1.40.0",
3
+ "version": "1.41.0",
4
4
  "description": "Autonomous AI agent memory system with CLAUDE.md protocol enforcement",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -107,6 +107,8 @@
107
107
  "docs",
108
108
  "config",
109
109
  "templates",
110
+ "src/policies/schemas/policies",
111
+ "src/policies/enforcers",
110
112
  "tools/agents",
111
113
  "scripts/setup",
112
114
  "scripts/version-bump.sh",
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env python3
2
+ """architecture-review-required enforcer.
3
+
4
+ Triggered when merge / PR-ready operations are attempted and the diff
5
+ touches qualifying paths. Requires either:
6
+ - An ADR file under docs/architecture/adr/ added or modified in the same diff, OR
7
+ - An active waiver under policies/waivers/ matching this policy slug
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import re
12
+ import sys
13
+ from pathlib import Path
14
+
15
+ sys.path.insert(0, str(Path(__file__).parent))
16
+ from _common import emit, parse_cli, repo_root, run # noqa: E402
17
+
18
+ PR_OPS_RE = re.compile(
19
+ r"\b(pr[-_ ]?ready|pr-create|gh pr create|signoff|ready[-_ ]for[-_ ]review|merge)\b",
20
+ re.I,
21
+ )
22
+
23
+ # Paths whose modification triggers the architecture-review requirement.
24
+ TRIGGER_PATTERNS = [
25
+ re.compile(r"^src/types/"),
26
+ re.compile(r".*/schemas/"),
27
+ re.compile(r"^src/index\.ts$"),
28
+ re.compile(r"^docs/architecture/(?!adr/)"), # docs/architecture/ but NOT the adr/ we want
29
+ re.compile(r"^src/coordination/(capability|pattern)-router\.ts$"),
30
+ ]
31
+
32
+ ADR_PATH_RE = re.compile(r"^docs/architecture/adr/.+\.md$")
33
+ WAIVER_RE = re.compile(r"^policies/waivers/.*architecture-review.*\.md$", re.I)
34
+
35
+
36
+ def main() -> None:
37
+ op, args = parse_cli()
38
+ cmd = (args.get("command") or "").lower()
39
+
40
+ if not (PR_OPS_RE.search(op) or PR_OPS_RE.search(cmd)):
41
+ emit(True, "not a PR-ready gate point")
42
+
43
+ root = repo_root()
44
+ rc, out, _ = run(
45
+ ["git", "diff", "--name-only", "origin/master...HEAD"], cwd=root, timeout=10
46
+ )
47
+ if rc != 0:
48
+ # Try origin/main as fallback
49
+ rc, out, _ = run(
50
+ ["git", "diff", "--name-only", "origin/main...HEAD"], cwd=root, timeout=10
51
+ )
52
+ if rc != 0:
53
+ emit(True, "cannot compute diff vs upstream; allowing")
54
+
55
+ changed = [line for line in out.splitlines() if line.strip()]
56
+
57
+ # Detect qualifying changes
58
+ triggered = []
59
+ for f in changed:
60
+ for pat in TRIGGER_PATTERNS:
61
+ if pat.match(f):
62
+ triggered.append(f)
63
+ break
64
+
65
+ if not triggered:
66
+ emit(True, "no architecture-qualifying paths touched in diff")
67
+
68
+ # Look for an ADR in the diff
69
+ adr_present = any(ADR_PATH_RE.match(f) for f in changed)
70
+
71
+ # Look for an active waiver
72
+ waiver_present = False
73
+ waivers_dir = root / "policies" / "waivers"
74
+ if waivers_dir.exists():
75
+ for waiver in waivers_dir.glob("*architecture-review*.md"):
76
+ if waiver.is_file():
77
+ waiver_present = True
78
+ break
79
+
80
+ if adr_present or waiver_present:
81
+ emit(
82
+ True,
83
+ f"architecture-review satisfied "
84
+ f"({'adr-in-diff' if adr_present else 'active-waiver'})",
85
+ adr=adr_present,
86
+ waiver=waiver_present,
87
+ triggers=sorted(set(triggered)),
88
+ )
89
+
90
+ emit(
91
+ False,
92
+ "architecture-review-required: diff touches "
93
+ f"{', '.join(sorted(set(triggered))[:3])} but no ADR under "
94
+ "docs/architecture/adr/ and no active waiver. "
95
+ "Add an ADR or request a waiver from compliance-officer.",
96
+ triggers=sorted(set(triggered)),
97
+ )
98
+
99
+
100
+ if __name__ == "__main__":
101
+ main()
@@ -0,0 +1,100 @@
1
+ """Shared helpers for UAP policy enforcers."""
2
+ from __future__ import annotations
3
+ import argparse
4
+ import json
5
+ import os
6
+ import subprocess
7
+ import sys
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+
12
+ def parse_cli() -> tuple[str, dict[str, Any]]:
13
+ p = argparse.ArgumentParser()
14
+ p.add_argument("--operation", required=True)
15
+ p.add_argument("--args", default="{}")
16
+ ns = p.parse_args()
17
+ try:
18
+ args = json.loads(ns.args)
19
+ except json.JSONDecodeError:
20
+ args = {}
21
+ return ns.operation, args
22
+
23
+
24
+ def emit(allowed: bool, reason: str, **extra: Any) -> None:
25
+ payload: dict[str, Any] = {"allowed": allowed, "reason": reason}
26
+ payload.update(extra)
27
+ json.dump(payload, sys.stdout)
28
+ sys.exit(0 if allowed else 2)
29
+
30
+
31
+ def repo_root() -> Path:
32
+ env = os.environ.get("UAP_REPO_ROOT")
33
+ if env:
34
+ return Path(env)
35
+ cwd = Path.cwd()
36
+ for p in [cwd, *cwd.parents]:
37
+ if (p / ".git").exists():
38
+ return p
39
+ return cwd
40
+
41
+
42
+ def worktree_root() -> Path:
43
+ """Root of the current WORKING TREE for git operations.
44
+
45
+ Distinct from repo_root() (the main checkout, where runtime data like
46
+ policies.db lives). git-diff based enforcers must run against the working
47
+ tree — which is the worktree when an operation runs from inside one. The
48
+ policy gate exports UAP_WORKTREE_ROOT; fall back to `git rev-parse` from cwd,
49
+ then to repo_root().
50
+ """
51
+ env = os.environ.get("UAP_WORKTREE_ROOT")
52
+ if env:
53
+ return Path(env)
54
+ try:
55
+ r = subprocess.run(
56
+ ["git", "rev-parse", "--show-toplevel"],
57
+ capture_output=True, text=True, timeout=3, env=_clean_env(),
58
+ )
59
+ if r.returncode == 0 and r.stdout.strip():
60
+ return Path(r.stdout.strip())
61
+ except Exception: # noqa: BLE001
62
+ pass
63
+ return repo_root()
64
+
65
+
66
+ # git exports repo-context vars (GIT_DIR, GIT_WORK_TREE, GIT_INDEX_FILE, ...)
67
+ # into hook environments. An enforcer spawned during a hook would then run its
68
+ # own git calls against the HOOK'S repo instead of cwd — silently no-op'ing
69
+ # every git-diff based check. Strip them so cwd decides the repo.
70
+ _GIT_CONTEXT_VARS = (
71
+ "GIT_DIR",
72
+ "GIT_WORK_TREE",
73
+ "GIT_INDEX_FILE",
74
+ "GIT_COMMON_DIR",
75
+ "GIT_OBJECT_DIRECTORY",
76
+ "GIT_PREFIX",
77
+ )
78
+
79
+
80
+ def _clean_env() -> dict[str, str]:
81
+ return {k: v for k, v in os.environ.items() if k not in _GIT_CONTEXT_VARS}
82
+
83
+
84
+ def run(cmd: list[str], cwd: Path | None = None, timeout: int = 5) -> tuple[int, str, str]:
85
+ try:
86
+ r = subprocess.run(
87
+ cmd, cwd=cwd, capture_output=True, text=True, timeout=timeout,
88
+ env=_clean_env(),
89
+ )
90
+ return r.returncode, r.stdout, r.stderr
91
+ except Exception as e: # noqa: BLE001
92
+ return 1, "", str(e)
93
+
94
+
95
+ def arg_str(args: dict[str, Any]) -> str:
96
+ """Flatten args to a single lowercase string for substring checks."""
97
+ try:
98
+ return json.dumps(args, default=str).lower()
99
+ except Exception: # noqa: BLE001
100
+ return str(args).lower()
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env python3
2
+ """artifact-hygiene enforcer: block binary artifacts outside curated dirs."""
3
+ from __future__ import annotations
4
+ import re
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ sys.path.insert(0, str(Path(__file__).parent))
9
+ from _common import emit, parse_cli # noqa: E402
10
+
11
+ BINARY_RE = re.compile(r"\.(png|jpe?g|gif|pdf|zip|tar\.gz|tgz|db|sqlite\d?)$", re.I)
12
+ ALLOWED_PREFIXES = (
13
+ "docs/",
14
+ "tests/",
15
+ "apps/",
16
+ "agents/data/memory/",
17
+ ".playwright-mcp/",
18
+ "observability/",
19
+ )
20
+ ALLOWED_SUBSTRINGS = ("/__screenshots__/", "/public/", "/static/", "/assets/")
21
+ WRITE_OPS = {"Write", "write", "create-file"}
22
+
23
+
24
+ def main() -> None:
25
+ op, args = parse_cli()
26
+ if op not in WRITE_OPS:
27
+ emit(True, "not a write op")
28
+
29
+ path = (args.get("file_path") or args.get("path") or "").replace("\\", "/")
30
+ if not BINARY_RE.search(path):
31
+ emit(True, "not a binary artifact")
32
+
33
+ rel = path
34
+ for marker in ("/pay2u/", "/miller-tech/"):
35
+ if marker in rel:
36
+ rel = rel.split(marker, 1)[1]
37
+ break
38
+
39
+ if any(rel.startswith(p) for p in ALLOWED_PREFIXES):
40
+ emit(True, f"allowed prefix: {rel}")
41
+ if any(s in rel for s in ALLOWED_SUBSTRINGS):
42
+ emit(True, f"allowed subdir: {rel}")
43
+
44
+ emit(
45
+ False,
46
+ f"artifact-hygiene: binary '{rel}' must live under docs/, tests/, or apps/**/public|static|assets. "
47
+ "Do not litter repo root.",
48
+ )
49
+
50
+
51
+ if __name__ == "__main__":
52
+ main()
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env python3
2
+ """cluster-routing enforcer: kubectl/helm context must match component domain."""
3
+ from __future__ import annotations
4
+ import re
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ sys.path.insert(0, str(Path(__file__).parent))
9
+ from _common import arg_str, emit, parse_cli, run # noqa: E402
10
+
11
+ DOMAINS = {
12
+ "openobserve": re.compile(
13
+ r"grafana|prometheus|openobserve|fluent-?bit|servicemonitor|alertmanager|loki|tempo|jaeger",
14
+ re.I,
15
+ ),
16
+ "zitadel": re.compile(r"zitadel|oidc|keycloak|iam-crd", re.I),
17
+ }
18
+ CONTEXTS = {
19
+ "openobserve": "do-syd1-pay2u-openobserve",
20
+ "zitadel": "do-syd1-zitadel",
21
+ "main": "do-syd1-pay2u",
22
+ }
23
+
24
+
25
+ def pick_domain(blob: str) -> str:
26
+ for d, rx in DOMAINS.items():
27
+ if rx.search(blob):
28
+ return d
29
+ return "main"
30
+
31
+
32
+ def main() -> None:
33
+ op, args = parse_cli()
34
+ blob = f"{op} {arg_str(args)}"
35
+
36
+ if not re.search(r"\b(kubectl|helm)\b", blob):
37
+ emit(True, "not a kubectl/helm call")
38
+
39
+ if not re.search(
40
+ r"\b(apply|patch|create|edit|delete|install|upgrade|uninstall|rollout)\b", blob
41
+ ):
42
+ emit(True, "read-only kubectl/helm call")
43
+
44
+ rc, out, _ = run(["kubectl", "config", "current-context"])
45
+ ctx = out.strip() if rc == 0 else ""
46
+
47
+ wanted_domain = pick_domain(blob)
48
+ wanted_ctx = CONTEXTS[wanted_domain]
49
+
50
+ if ctx != wanted_ctx:
51
+ emit(
52
+ False,
53
+ f"cluster-routing: context '{ctx}' does not match domain "
54
+ f"'{wanted_domain}'. Run: kubectl config use-context {wanted_ctx}",
55
+ wanted_context=wanted_ctx,
56
+ current_context=ctx,
57
+ )
58
+
59
+ emit(True, f"context '{ctx}' matches domain '{wanted_domain}'")
60
+
61
+
62
+ if __name__ == "__main__":
63
+ main()
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env python3
2
+ """codebase-read-before-plan enforcer: plans require prior reads of target paths."""
3
+ from __future__ import annotations
4
+ import os
5
+ import re
6
+ import sys
7
+ import time
8
+ from pathlib import Path
9
+
10
+ sys.path.insert(0, str(Path(__file__).parent))
11
+ from _common import arg_str, emit, parse_cli # noqa: E402
12
+
13
+ PLAN_OPS = {"ExitPlanMode", "Plan", "TodoWrite"}
14
+ PLAN_WORD_RE = re.compile(r"(?<![-\w/])(plan the|design the|architect the|propose a plan|spec the)", re.I)
15
+ READ_LOG = Path(os.environ.get("UAP_STATE_DIR", ".uap")) / "read_log.state"
16
+ RECENT_SEC = 1800
17
+
18
+
19
+ def recent_reads() -> set[str]:
20
+ if not READ_LOG.exists():
21
+ return set()
22
+ out: set[str] = set()
23
+ now = time.time()
24
+ for line in READ_LOG.read_text().splitlines():
25
+ try:
26
+ ts, path = line.split("\t", 1)
27
+ if now - float(ts) < RECENT_SEC:
28
+ out.add(path)
29
+ except ValueError:
30
+ continue
31
+ return out
32
+
33
+
34
+ def main() -> None:
35
+ op, args = parse_cli()
36
+ blob = f"{op} {arg_str(args)}"
37
+ if op not in PLAN_OPS and not PLAN_WORD_RE.search(blob):
38
+ emit(True, "not a plan op")
39
+
40
+ reads = recent_reads()
41
+ if reads:
42
+ emit(True, f"{len(reads)} recent codebase reads on record")
43
+
44
+ emit(
45
+ False,
46
+ "codebase-read-before-plan: no Read/Grep/Glob within the last 30 min. "
47
+ "Read the existing codebase in the target scope before emitting a plan.",
48
+ )
49
+
50
+
51
+ if __name__ == "__main__":
52
+ main()
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env python3
2
+ """coord-overlap enforcer: check for in-flight agent path reservations."""
3
+ from __future__ import annotations
4
+ import sqlite3
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ sys.path.insert(0, str(Path(__file__).parent))
9
+ from _common import emit, parse_cli, repo_root # noqa: E402
10
+
11
+ AGENT_OPS = {"Agent", "spawn-agent", "subagent", "delegate"}
12
+
13
+
14
+ def overlapping_reservations(root: Path, paths: list[str]) -> list[str]:
15
+ db = root / "agents" / "data" / "coordination" / "coordination.db"
16
+ if not db.exists() or not paths:
17
+ return []
18
+ try:
19
+ con = sqlite3.connect(f"file:{db}?mode=ro", uri=True, timeout=1.0)
20
+ # Best-effort: look for any reservations table with path-like column
21
+ cur = con.execute(
22
+ "SELECT name FROM sqlite_master WHERE type='table'"
23
+ )
24
+ tables = [r[0] for r in cur.fetchall()]
25
+ hits: list[str] = []
26
+ for t in tables:
27
+ try:
28
+ cols = [r[1] for r in con.execute(f"PRAGMA table_info({t})")]
29
+ path_col = next(
30
+ (c for c in cols if c.lower() in ("path", "paths", "file", "scope")),
31
+ None,
32
+ )
33
+ status_col = next(
34
+ (c for c in cols if c.lower() in ("status", "state", "active")), None
35
+ )
36
+ if not path_col:
37
+ continue
38
+ where = f" WHERE {status_col} IN ('active','in_progress',1)" if status_col else ""
39
+ rows = con.execute(f"SELECT {path_col} FROM {t}{where}").fetchall()
40
+ for (v,) in rows:
41
+ if not v:
42
+ continue
43
+ for p in paths:
44
+ if p and p in str(v):
45
+ hits.append(f"{t}:{v}")
46
+ except sqlite3.Error:
47
+ continue
48
+ con.close()
49
+ return hits
50
+ except sqlite3.Error:
51
+ return []
52
+
53
+
54
+ def main() -> None:
55
+ op, args = parse_cli()
56
+ if op not in AGENT_OPS:
57
+ emit(True, "not an agent-spawn op")
58
+
59
+ paths_raw = (
60
+ args.get("paths")
61
+ or args.get("scope")
62
+ or args.get("prompt", "")
63
+ )
64
+ if isinstance(paths_raw, list):
65
+ paths = [str(p) for p in paths_raw]
66
+ else:
67
+ paths = [p for p in str(paths_raw).split() if "/" in p]
68
+
69
+ hits = overlapping_reservations(repo_root(), paths)
70
+ if hits:
71
+ emit(
72
+ False,
73
+ f"coord-overlap: active reservations on: {', '.join(hits[:5])}. "
74
+ "Run `uap coordination check` before spawning.",
75
+ )
76
+
77
+ emit(True, "no overlapping reservations")
78
+
79
+
80
+ if __name__ == "__main__":
81
+ main()