@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,97 @@
1
+ #!/usr/bin/env python3
2
+ """delivery-enforcement enforcer: route substantive coding through `uap deliver`.
3
+
4
+ Fires on Edit/Write/MultiEdit to source-code files. The intent is that
5
+ non-trivial coding work goes through the `uap deliver` convergence loop (which
6
+ drives a model to verified completion against the real gates) rather than
7
+ ad-hoc hand edits.
8
+
9
+ SAFETY — default mode is ADVISORY (always allows, logs a nudge), so installing
10
+ this policy never breaks editing. Strict enforcement is opt-in:
11
+
12
+ UAP_ENFORCE_DELIVERY=block # direct source edits outside a deliver context
13
+ # are blocked (exit 2)
14
+
15
+ Escape hatches (always honored, even in block mode):
16
+ - UAP_DELIVER_ACTIVE=1 set by the deliver loop for its own subprocesses
17
+ - UAP_DELIVER_BYPASS=1 explicit operator override for a sanctioned manual edit
18
+
19
+ Exempt by construction: non-source files, docs/configs/scripts/policies, test
20
+ files (deliver protects those itself), and tooling dot-dirs.
21
+ """
22
+ from __future__ import annotations
23
+ import os
24
+ import sys
25
+ from pathlib import Path
26
+
27
+ sys.path.insert(0, str(Path(__file__).parent))
28
+ from _common import emit, parse_cli, repo_root # noqa: E402
29
+
30
+ EDIT_OPS = {"Edit", "Write", "MultiEdit", "edit", "write", "multiedit"}
31
+
32
+ # Only real implementation code is gated.
33
+ SOURCE_EXTS = (
34
+ ".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs",
35
+ ".py", ".go", ".rs", ".java", ".rb", ".php", ".cs", ".swift", ".kt",
36
+ ".c", ".cc", ".cpp", ".h", ".hpp",
37
+ )
38
+
39
+ EXEMPT_PREFIXES = (
40
+ ".claude/", ".cursor/", ".opencode/", ".codex/", ".forge/", ".omp/",
41
+ ".uap/", ".policy-tools/", ".worktrees/",
42
+ "src/policies/", "scripts/", "docs/", "policies/", "test/", "tests/",
43
+ )
44
+
45
+ # Test files are protected by deliver itself; never gate them here.
46
+ TEST_MARKERS = (".test.", ".spec.", "_test.", "/test/", "/tests/", "/__tests__/")
47
+
48
+
49
+ def main() -> None:
50
+ op, args = parse_cli()
51
+ if op not in EDIT_OPS:
52
+ emit(True, "not a file-edit operation")
53
+
54
+ target = args.get("file_path") or args.get("path") or args.get("target") or ""
55
+ if not target:
56
+ emit(True, "no file path in args")
57
+
58
+ root = repo_root()
59
+ try:
60
+ rel = str(Path(target).resolve().relative_to(root))
61
+ except ValueError:
62
+ emit(True, "target outside repo")
63
+
64
+ rel_posix = rel.replace(os.sep, "/")
65
+ low = rel_posix.lower()
66
+
67
+ if not low.endswith(SOURCE_EXTS):
68
+ emit(True, "not source code")
69
+ if any(rel_posix.startswith(p) for p in EXEMPT_PREFIXES):
70
+ emit(True, f"exempt path: {rel_posix}")
71
+ if any(m in "/" + low for m in TEST_MARKERS):
72
+ emit(True, "test file (protected by deliver itself)")
73
+
74
+ # Escape hatches.
75
+ if os.environ.get("UAP_DELIVER_ACTIVE") == "1":
76
+ emit(True, "inside a deliver-driven run")
77
+ if os.environ.get("UAP_DELIVER_BYPASS") == "1":
78
+ emit(True, "UAP_DELIVER_BYPASS override set")
79
+
80
+ msg = (
81
+ f"delivery-enforcement: '{rel_posix}' is source code being edited directly. "
82
+ "Route substantive coding through `uap deliver` (drives a model to verified "
83
+ "completion against the gates), or set UAP_DELIVER_BYPASS=1 for a sanctioned "
84
+ "manual edit."
85
+ )
86
+
87
+ mode = os.environ.get("UAP_ENFORCE_DELIVERY", "advisory").lower()
88
+ if mode == "block":
89
+ emit(False, msg)
90
+
91
+ # Advisory (default): never blocks. Surface the nudge, then allow.
92
+ print(f"[delivery-enforcement advisory] {msg}", file=sys.stderr)
93
+ emit(True, "advisory: nudge logged (set UAP_ENFORCE_DELIVERY=block to enforce)")
94
+
95
+
96
+ if __name__ == "__main__":
97
+ main()
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env python3
2
+ """doc-live-over-report enforcer: block new *_REPORT/*_COMPLETE/*_SUMMARY/*_PLAN md files."""
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
+ BLOCKED_RE = re.compile(
12
+ r"(_REPORT|_COMPLETE|_SUMMARY|_PLAN|_FIX_\d|_\d{4}-\d{2}-\d{2})\.md$",
13
+ re.I,
14
+ )
15
+ SCOPED_DIRS = ("infra/", "docs/", "")
16
+ WRITE_OPS = {"Write", "write", "create-file"}
17
+
18
+
19
+ def main() -> None:
20
+ op, args = parse_cli()
21
+ if op not in WRITE_OPS:
22
+ emit(True, "not a write op")
23
+
24
+ path = args.get("file_path") or args.get("path") or ""
25
+ if not path.endswith(".md"):
26
+ emit(True, "not a markdown file")
27
+
28
+ rel = path.replace("\\", "/")
29
+ # Strip leading abs path prefix if present
30
+ for marker in ("/pay2u/", "/miller-tech/"):
31
+ if marker in rel:
32
+ rel = rel.split(marker, 1)[1]
33
+ break
34
+
35
+ is_scoped = any(rel.startswith(d) for d in SCOPED_DIRS if d) or "/" not in rel
36
+ if not is_scoped:
37
+ emit(True, "out-of-scope path")
38
+
39
+ if BLOCKED_RE.search(rel):
40
+ emit(
41
+ False,
42
+ f"doc-live-over-report: '{rel}' is a retrospective/dated doc pattern. "
43
+ "Update canonical README/runbook instead.",
44
+ )
45
+
46
+ emit(True, "not a blocked doc pattern")
47
+
48
+
49
+ if __name__ == "__main__":
50
+ main()
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env python3
2
+ """expert-review-required enforcer: a parallel expert review must precede ship.
3
+
4
+ Blocks ship actions (git commit / git push / gh pr create / merge / pr-ready /
5
+ signoff) unless a review artifact exists for the current branch AND covers the
6
+ current HEAD. This makes the `parallel-expert-review` skill's "REQUIRED by
7
+ policy" claim real rather than advisory.
8
+
9
+ Review artifact: .uap/reviews/<branch-slug>.json, written by the
10
+ parallel-expert-review flow on consolidation. Recognised shape:
11
+ { "head": "<sha>", "verdict": "approve|...", "reviewers": [...] }
12
+ If the artifact carries a `head` that differs from the current HEAD, the review
13
+ is stale and the op is blocked.
14
+
15
+ Fail-open: if branch/HEAD cannot be resolved, the op is allowed — non-UAP repos
16
+ and detached states are unaffected. Override: set UAP_NO_REVIEW=1 to bypass.
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ import os
22
+ import re
23
+ import sys
24
+ from pathlib import Path
25
+
26
+ sys.path.insert(0, str(Path(__file__).parent))
27
+ from _common import emit, parse_cli, repo_root, run # noqa: E402
28
+
29
+ # Ship verbs are anchored to their tool prefix so that the bare tokens "merge"
30
+ # or "signoff" inside read-only commands (git diff --merge-base, rg merge,
31
+ # cat docs/merge-strategy.md) do not trip the gate.
32
+ SHIP_PATTERNS = (
33
+ re.compile(r"\bgit\s+(commit|push|merge)\b"),
34
+ re.compile(r"\bgh\s+pr\s+(create|merge|ready)\b"),
35
+ re.compile(r"\b(pr[-_ ]?ready|sign[-_ ]?off|ready[-_ ]for[-_ ]review)\b", re.I),
36
+ )
37
+
38
+
39
+ def current_branch(root: Path) -> str | None:
40
+ # symbolic-ref resolves the branch name even on an unborn branch (no commits
41
+ # yet); rev-parse --abbrev-ref returns "HEAD" in that state.
42
+ rc, out, _ = run(["git", "symbolic-ref", "--short", "HEAD"], cwd=root)
43
+ if rc == 0 and out.strip():
44
+ return out.strip()
45
+ rc, out, _ = run(["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=root)
46
+ if rc == 0 and out.strip() and out.strip() != "HEAD":
47
+ return out.strip()
48
+ return None
49
+
50
+
51
+ def slug_for(branch: str) -> str:
52
+ """Injective filename slug for a branch ref.
53
+
54
+ A naive `/`->`-` substitution collapses distinct refs (`feature/foo` and
55
+ `feature-foo`, which can coexist) onto the same artifact, silently bypassing
56
+ the gate. Percent-encode `%` first, then `/`, so the mapping is reversible
57
+ and collision-free: `feature/foo` -> `feature%2Ffoo`, `feature-foo` stays
58
+ `feature-foo`.
59
+ """
60
+ return branch.replace("%", "%25").replace("/", "%2F")
61
+
62
+
63
+ def head_sha(root: Path) -> str | None:
64
+ rc, out, _ = run(["git", "rev-parse", "HEAD"], cwd=root)
65
+ return out.strip() if rc == 0 and out.strip() else None
66
+
67
+
68
+ def main() -> None:
69
+ op, args = parse_cli()
70
+
71
+ if os.environ.get("UAP_NO_REVIEW") == "1":
72
+ emit(True, "UAP_NO_REVIEW override set")
73
+
74
+ op_l = op.lower()
75
+ if op_l != "bash":
76
+ emit(True, "not a ship operation")
77
+
78
+ cmd = args.get("command") or args.get("cmd") or ""
79
+ if not any(p.search(cmd) for p in SHIP_PATTERNS):
80
+ emit(True, "not a ship action")
81
+
82
+ root = repo_root()
83
+ branch = current_branch(root)
84
+ if branch is None:
85
+ emit(True, "branch not resolvable (detached/non-git) — fail-open")
86
+ slug = slug_for(branch)
87
+
88
+ review = root / ".uap" / "reviews" / f"{slug}.json"
89
+ if not review.exists():
90
+ emit(
91
+ False,
92
+ f"expert-review-required: no review artifact at .uap/reviews/{slug}.json. "
93
+ "Run the parallel-expert-review skill (code-quality, security, performance, "
94
+ "docs, test-coverage reviewers) and record the consolidated verdict before "
95
+ "shipping. Override for one-off meta-work: UAP_NO_REVIEW=1.",
96
+ )
97
+
98
+ head = head_sha(root)
99
+ try:
100
+ data = json.loads(review.read_text())
101
+ except Exception: # noqa: BLE001
102
+ data = {}
103
+
104
+ # Defense-in-depth against artifact reuse across branches: if the artifact
105
+ # records the branch it covers, it must match the current branch.
106
+ artifact_branch = data.get("branch") if isinstance(data, dict) else None
107
+ if artifact_branch and artifact_branch != branch:
108
+ emit(
109
+ False,
110
+ f"expert-review-required: review at .uap/reviews/{slug}.json covers branch "
111
+ f"'{artifact_branch}', not '{branch}'. Re-run the parallel expert review on "
112
+ "this branch. Override: UAP_NO_REVIEW=1.",
113
+ )
114
+
115
+ # Stale check relative to current HEAD.
116
+ reviewed_head = data.get("head") if isinstance(data, dict) else None
117
+ if reviewed_head and head and reviewed_head != head:
118
+ emit(
119
+ False,
120
+ f"expert-review-required: review at .uap/reviews/{slug}.json covers "
121
+ f"{reviewed_head[:8]} but HEAD is {head[:8]} — the review is stale. "
122
+ "Re-run the parallel expert review for the current changes. "
123
+ "Override: UAP_NO_REVIEW=1.",
124
+ )
125
+
126
+ emit(
127
+ True,
128
+ f"expert-review satisfied (.uap/reviews/{slug}.json"
129
+ + (f", head {reviewed_head[:8]}" if reviewed_head else "")
130
+ + ")",
131
+ )
132
+
133
+
134
+ if __name__ == "__main__":
135
+ main()
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env python3
2
+ """iac-parity enforcer: live-state changes must have matching IaC diff."""
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, worktree_root # noqa: E402
10
+
11
+ MUTATING_RE = re.compile(
12
+ r"\b(kubectl|helm|doctl|aws|gcloud)\b.*?\b(apply|patch|create|edit|delete|install|upgrade|rollout|scale|set)\b",
13
+ re.I,
14
+ )
15
+ IAC_PATHS = (
16
+ "infra/terraform/",
17
+ "infra/helm_charts/",
18
+ "infra/kubernetes/",
19
+ "infra/k8s/",
20
+ "infra/policies/",
21
+ )
22
+
23
+
24
+ def main() -> None:
25
+ op, args = parse_cli()
26
+ blob = f"{op} {arg_str(args)}"
27
+
28
+ if not MUTATING_RE.search(blob):
29
+ emit(True, "not a mutating IaC-scope command")
30
+
31
+ root = worktree_root() # git status must run against the working tree, not MAIN_ROOT
32
+ rc, out, _ = run(["git", "status", "--porcelain"], cwd=root)
33
+ if rc != 0:
34
+ emit(True, "git status unavailable; deferring to post-commit check")
35
+
36
+ has_iac = any(
37
+ line[3:].startswith(IAC_PATHS) or line[3:].lstrip().startswith(IAC_PATHS)
38
+ for line in out.splitlines()
39
+ )
40
+
41
+ if not has_iac:
42
+ emit(
43
+ False,
44
+ "iac-parity: live-state mutation without matching IaC diff under "
45
+ + ", ".join(IAC_PATHS)
46
+ + ". Update Terraform/Helm/K8s manifests in the same worktree.",
47
+ )
48
+
49
+ emit(True, "IaC diff present in worktree")
50
+
51
+
52
+ if __name__ == "__main__":
53
+ main()
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env python3
2
+ """mcp-router-first enforcer: MCP tools must be loaded on demand."""
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 # noqa: E402
10
+
11
+ BULK_PATTERNS = re.compile(r"(load[-_ ]all|bulk[-_ ]load|all[-_ ]tools|eager)", re.I)
12
+
13
+
14
+ def main() -> None:
15
+ op, args = parse_cli()
16
+ blob = f"{op} {arg_str(args)}"
17
+
18
+ if op not in {"ToolSearch", "tool_search", "mcp-router", "mcp_router"}:
19
+ emit(True, "not an MCP-router op")
20
+
21
+ query = (args.get("query") or "").strip()
22
+ max_results = int(args.get("max_results") or 5)
23
+
24
+ if BULK_PATTERNS.search(blob):
25
+ emit(False, "mcp-router-first: bulk/eager MCP tool load detected; query by specific tool name instead")
26
+
27
+ if not query or max_results > 20:
28
+ emit(
29
+ False,
30
+ "mcp-router-first: ToolSearch must use a specific query and max_results<=20",
31
+ )
32
+
33
+ emit(True, "scoped MCP router query")
34
+
35
+
36
+ if __name__ == "__main__":
37
+ main()
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env python3
2
+ """memory-before-plan enforcer: plans require a recent uap memory query."""
3
+ from __future__ import annotations
4
+ import re
5
+ import sqlite3
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, repo_root # noqa: E402
12
+
13
+ PLAN_OPS = {"ExitPlanMode", "Plan", "TodoWrite", "plan", "design"}
14
+ # Only match standalone words, not compounds like 'validate-plan-before-build'
15
+ PLAN_WORD_RE = re.compile(r"(?<![-\w/])(plan the|design the|architect the|propose a plan|roadmap for)", re.I)
16
+ RECENT_SEC = 300
17
+
18
+
19
+ def recent_memory_query(root: Path) -> bool:
20
+ db = root / "agents" / "data" / "memory" / "short_term.db"
21
+ if not db.exists():
22
+ return False
23
+ try:
24
+ con = sqlite3.connect(f"file:{db}?mode=ro", uri=True, timeout=1.0)
25
+ cur = con.execute(
26
+ "SELECT timestamp FROM session_memories "
27
+ "WHERE content LIKE '%uap memory query%' OR type='memory_query' "
28
+ "ORDER BY id DESC LIMIT 1"
29
+ )
30
+ row = cur.fetchone()
31
+ con.close()
32
+ if not row:
33
+ return False
34
+ raw = row[0][:19].replace("T", " ")
35
+ try:
36
+ ts = time.mktime(time.strptime(raw, "%Y-%m-%d %H:%M:%S"))
37
+ except Exception: # noqa: BLE001
38
+ return False
39
+ # Memory is stored as UTC from datetime('now'); compare with UTC now
40
+ return (time.time() - (ts - time.timezone)) < RECENT_SEC
41
+ except sqlite3.Error:
42
+ return False
43
+
44
+
45
+ def main() -> None:
46
+ op, args = parse_cli()
47
+ blob = f"{op} {arg_str(args)}"
48
+ if op not in PLAN_OPS and not PLAN_WORD_RE.search(blob):
49
+ emit(True, "not a plan operation")
50
+
51
+ if recent_memory_query(repo_root()):
52
+ emit(True, "recent uap memory query on record")
53
+
54
+ emit(
55
+ False,
56
+ "memory-before-plan: run `uap memory query <topic>` before planning to surface prior context",
57
+ )
58
+
59
+
60
+ if __name__ == "__main__":
61
+ main()
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env python3
2
+ """parallel-reads enforcer: nudge when serial read fan-out is detected."""
3
+ from __future__ import annotations
4
+ import os
5
+ import sys
6
+ import time
7
+ from pathlib import Path
8
+
9
+ sys.path.insert(0, str(Path(__file__).parent))
10
+ from _common import emit, parse_cli # noqa: E402
11
+
12
+ READ_OPS = {"Read", "Grep", "Glob", "WebFetch", "read", "grep", "glob", "webfetch"}
13
+ STATE = Path(os.environ.get("UAP_STATE_DIR", ".uap")) / "parallel_reads.state"
14
+ WINDOW_SEC = 4.0
15
+ THRESHOLD = 2
16
+
17
+
18
+ def main() -> None:
19
+ op, _args = parse_cli()
20
+ if op not in READ_OPS:
21
+ emit(True, "not a read op")
22
+
23
+ STATE.parent.mkdir(parents=True, exist_ok=True)
24
+ now = time.time()
25
+ history: list[float] = []
26
+ if STATE.exists():
27
+ try:
28
+ history = [
29
+ float(l) for l in STATE.read_text().splitlines() if l.strip()
30
+ ]
31
+ except ValueError:
32
+ history = []
33
+
34
+ history = [t for t in history if now - t < WINDOW_SEC]
35
+ history.append(now)
36
+ STATE.write_text("\n".join(f"{t}" for t in history[-10:]))
37
+
38
+ if len(history) > THRESHOLD:
39
+ emit(
40
+ True,
41
+ f"parallel-reads: {len(history)} serial read ops in {WINDOW_SEC}s "
42
+ "— batch independent reads in a single tool-call message for 2-5x speed-up",
43
+ warning=True,
44
+ )
45
+
46
+ emit(True, "read cadence within batch window")
47
+
48
+
49
+ if __name__ == "__main__":
50
+ main()
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env python3
2
+ """rtk-wrap enforcer: heavy CLIs must be invoked via rtk."""
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
+ WRAPPED = ("git", "kubectl", "docker", "docker-compose", "npm", "pnpm", "yarn", "helm", "terraform")
12
+ RTK_META = re.compile(r"^\s*rtk\s+(gain|discover|proxy|--version|-V|--help)\b")
13
+ ALREADY_WRAPPED = re.compile(r"^\s*rtk\s+\S+")
14
+
15
+
16
+ def main() -> None:
17
+ op, args = parse_cli()
18
+ cmd = (args.get("command") or args.get("cmd") or "").strip()
19
+ if not cmd or op.lower() != "bash":
20
+ emit(True, "not a Bash command")
21
+
22
+ first = cmd.split(maxsplit=1)[0].lstrip("(").lstrip("{")
23
+ if first == "rtk" and RTK_META.search(cmd):
24
+ emit(True, "rtk meta command")
25
+ if ALREADY_WRAPPED.match(cmd):
26
+ emit(True, "already wrapped")
27
+
28
+ # Inspect tokens (ignore env assignments like FOO=bar cmd)
29
+ tokens = [t for t in cmd.split() if "=" not in t.split("/")[0]]
30
+ for tok in tokens[:3]:
31
+ bin_name = tok.split("/")[-1]
32
+ if bin_name in WRAPPED:
33
+ emit(
34
+ False,
35
+ f"rtk-wrap: '{bin_name}' must be invoked via rtk. "
36
+ f"Use: rtk {cmd}",
37
+ bin=bin_name,
38
+ )
39
+
40
+ emit(True, "no wrapped CLI in command")
41
+
42
+
43
+ if __name__ == "__main__":
44
+ main()
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env python3
2
+ """schema-diff-gate enforcer: schema/pool changes must pass uap schema-diff."""
3
+ from __future__ import annotations
4
+ import re
5
+ import sqlite3
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 emit, parse_cli, repo_root, run, worktree_root # noqa: E402
12
+
13
+ WATCHED_RE = re.compile(
14
+ r"(migrations/.*\.sql|infra/postgres-spock/|infra/helm_charts/[^/]*pgdog|"
15
+ r"infra/helm_charts/[^/]*cnpg|infra/helm_charts/[^/]*redis|"
16
+ r"infra/helm_charts/[^/]*envoy|infra/helm_charts/[^/]*sentinel)",
17
+ re.I,
18
+ )
19
+ COMMIT_OPS = {"git-commit", "git commit", "Bash"}
20
+ RECENT_SEC = 3600
21
+
22
+
23
+ def touched_watched_paths(root: Path) -> list[str]:
24
+ rc, out, _ = run(["git", "diff", "--name-only", "HEAD"], cwd=root)
25
+ if rc != 0:
26
+ return []
27
+ rc2, staged, _ = run(["git", "diff", "--name-only", "--cached"], cwd=root)
28
+ all_files = (out + "\n" + (staged if rc2 == 0 else "")).splitlines()
29
+ return [f for f in all_files if f and WATCHED_RE.search(f)]
30
+
31
+
32
+ def schema_diff_ok(root: Path) -> bool:
33
+ db = root / "agents" / "data" / "memory" / "short_term.db"
34
+ if not db.exists():
35
+ return False
36
+ try:
37
+ con = sqlite3.connect(f"file:{db}?mode=ro", uri=True, timeout=1.0)
38
+ cur = con.execute(
39
+ "SELECT timestamp FROM session_memories "
40
+ "WHERE content LIKE '%schema-diff%pass%' "
41
+ "ORDER BY id DESC LIMIT 1"
42
+ )
43
+ row = cur.fetchone()
44
+ con.close()
45
+ if not row:
46
+ return False
47
+ try:
48
+ ts = time.mktime(time.strptime(row[0][:19], "%Y-%m-%dT%H:%M:%S"))
49
+ except Exception: # noqa: BLE001
50
+ return False
51
+ return (time.time() - ts) < RECENT_SEC
52
+ except sqlite3.Error:
53
+ return False
54
+
55
+
56
+ def main() -> None:
57
+ op, args = parse_cli()
58
+ cmd = (args.get("command") or "").lower()
59
+ is_commit = op in COMMIT_OPS or "git commit" in cmd or "git push" in cmd
60
+ if not is_commit:
61
+ emit(True, "not a commit/push gate point")
62
+
63
+ # git diff runs against the working tree; short_term.db lives in MAIN_ROOT
64
+ watched = touched_watched_paths(worktree_root())
65
+ if not watched:
66
+ emit(True, "no watched schema/pool paths in diff")
67
+
68
+ if schema_diff_ok(repo_root()):
69
+ emit(True, f"recent schema-diff pass covers: {', '.join(watched[:5])}")
70
+
71
+ emit(
72
+ False,
73
+ "schema-diff-gate: changes to "
74
+ + ", ".join(watched[:5])
75
+ + " require `uap schema-diff` to pass (within 1h). Run it and re-commit.",
76
+ )
77
+
78
+
79
+ if __name__ == "__main__":
80
+ main()
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env python3
2
+ """session-memory-write enforcer: code-changing sessions must write a lesson."""
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
+ END_OPS = {"session-end", "stop", "terminate", "SessionEnd"}
12
+
13
+
14
+ def recent_lesson(root: Path) -> bool:
15
+ db = root / "agents" / "data" / "memory" / "short_term.db"
16
+ if not db.exists():
17
+ return False
18
+ try:
19
+ con = sqlite3.connect(f"file:{db}?mode=ro", uri=True, timeout=1.0)
20
+ cur = con.execute(
21
+ "SELECT COUNT(*) FROM session_memories "
22
+ "WHERE type IN ('decision','lesson','pattern') "
23
+ "AND session_id='current'"
24
+ )
25
+ n = cur.fetchone()[0]
26
+ con.close()
27
+ return n > 0
28
+ except sqlite3.Error:
29
+ return False
30
+
31
+
32
+ def main() -> None:
33
+ op, args = parse_cli()
34
+ if op not in END_OPS:
35
+ emit(True, "not a session-end op")
36
+
37
+ code_changed = bool(args.get("code_changed"))
38
+ if not code_changed:
39
+ emit(True, "no code changes this session")
40
+
41
+ if recent_lesson(repo_root()):
42
+ emit(True, "lesson/decision recorded this session")
43
+
44
+ emit(
45
+ False,
46
+ "session-memory-write: code changed but no decision/lesson/pattern row in short_term.db. "
47
+ "Insert one before terminating.",
48
+ )
49
+
50
+
51
+ if __name__ == "__main__":
52
+ main()