@miller-tech/uap 1.12.0 → 1.13.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.
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env bash
2
+ # UAP Dangerous Command Guard — BLOCKING hook
3
+ # Event: PreToolUse (matcher: Bash)
4
+ # Exit 2 = BLOCK the command. Exit 0 = allow.
5
+ # Enforces: iac-pipeline-enforcement, worktree-enforcement, git safety policies.
6
+ set -euo pipefail
7
+
8
+ # Read tool input from stdin (JSON)
9
+ INPUT=$(cat)
10
+
11
+ # Extract command from tool_input
12
+ CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null || true)
13
+
14
+ # If we can't determine the command, fail open
15
+ if [ -z "$CMD" ]; then
16
+ exit 0
17
+ fi
18
+
19
+ # ─── IaC Pipeline Enforcement ───────────────────────────────────
20
+ # Block local terraform apply/destroy (policies/iac-pipeline-enforcement.md)
21
+ # Allow: terraform fmt, validate, init, plan, output, show, state list, graph
22
+ if echo "$CMD" | grep -qiE '\bterraform\s+(apply|destroy)\b'; then
23
+ echo "BLOCKED [iac-pipeline-enforcement]: terraform apply/destroy MUST go through CI/CD pipeline. Local execution is prohibited. Use: terraform fmt, validate, or plan locally. See policies/iac-pipeline-enforcement.md" >&2
24
+ exit 2
25
+ fi
26
+
27
+ # ─── Git Force Push Protection ──────────────────────────────────
28
+ # Block force pushes to any branch
29
+ if echo "$CMD" | grep -qE 'git\s+push\s+.*--force|git\s+push\s+-f\b|git\s+push\s+.*--force-with-lease'; then
30
+ echo "BLOCKED [git-safety]: Force push is prohibited. Use standard push and resolve conflicts through PRs. If you believe this is necessary, ask the user for explicit approval first." >&2
31
+ exit 2
32
+ fi
33
+
34
+ # ─── Direct Master/Main Commit Protection ───────────────────────
35
+ # Block git commit when on master/main AND not inside a worktree
36
+ if echo "$CMD" | grep -qE '\bgit\s+commit\b'; then
37
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"
38
+ CWD=$(echo "$INPUT" | jq -r '.cwd // empty' 2>/dev/null || true)
39
+ CHECK_DIR="${CWD:-$PROJECT_DIR}"
40
+
41
+ # Only block if NOT inside a worktree directory
42
+ if ! echo "$CHECK_DIR" | grep -q '\.worktrees/'; then
43
+ CURRENT_BRANCH=$(git -C "$CHECK_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
44
+ if [ "$CURRENT_BRANCH" = "main" ] || [ "$CURRENT_BRANCH" = "master" ]; then
45
+ echo "BLOCKED [worktree-enforcement]: Direct commits to ${CURRENT_BRANCH} are prohibited. Create a worktree first: uap worktree create <slug>. See policies/worktree-enforcement.md" >&2
46
+ exit 2
47
+ fi
48
+ fi
49
+ fi
50
+
51
+ # ─── Direct Push to Master/Main Protection ──────────────────────
52
+ # Block git push targeting main/master directly (not through PR)
53
+ if echo "$CMD" | grep -qE '\bgit\s+push\b'; then
54
+ # Block explicit pushes to main/master
55
+ if echo "$CMD" | grep -qE '\bgit\s+push\s+(origin\s+)?(main|master)\b'; then
56
+ echo "BLOCKED [worktree-enforcement]: Direct push to main/master is prohibited. Use: uap worktree pr <id> to create a PR instead. See policies/worktree-enforcement.md" >&2
57
+ exit 2
58
+ fi
59
+ fi
60
+
61
+ # ─── Destructive Git Operations ─────────────────────────────────
62
+ # Block git reset --hard and git clean -f outside worktrees
63
+ if echo "$CMD" | grep -qE '\bgit\s+reset\s+--hard\b|\bgit\s+clean\s+-[a-z]*f'; then
64
+ CWD=$(echo "$INPUT" | jq -r '.cwd // empty' 2>/dev/null || true)
65
+ if ! echo "${CWD:-.}" | grep -q '\.worktrees/'; then
66
+ echo "BLOCKED [git-safety]: Destructive git operations (reset --hard, clean -f) are prohibited outside worktrees. These can destroy uncommitted work in the project root." >&2
67
+ exit 2
68
+ fi
69
+ fi
70
+
71
+ # ─── Manual Version Edit Protection ─────────────────────────────
72
+ # Block direct edits to package.json version field via sed/awk
73
+ if echo "$CMD" | grep -qE "(sed|awk).*package\.json.*(version|\"version\")|((sed|awk).*version.*package\.json)|(jq.*\.version.*package\.json)"; then
74
+ echo "BLOCKED [semver-versioning]: Manual package.json version edits are prohibited. Use: npm run version:patch, version:minor, or version:major. See policies/semver-versioning.md" >&2
75
+ exit 2
76
+ fi
77
+
78
+ # Command allowed
79
+ exit 0
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env bash
2
+ # UAP Worktree File Guard — BLOCKING hook
3
+ # Event: PreToolUse (matcher: Edit|Write)
4
+ # Exit 2 = BLOCK the edit/write. Exit 0 = allow.
5
+ # Enforces: worktree-file-guard, worktree-enforcement policies.
6
+ set -euo pipefail
7
+
8
+ # Read tool input from stdin (JSON)
9
+ INPUT=$(cat)
10
+
11
+ # Extract file_path from tool_input
12
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.filePath // empty' 2>/dev/null || true)
13
+
14
+ # If we can't determine the file path, fail open
15
+ if [ -z "$FILE_PATH" ]; then
16
+ exit 0
17
+ fi
18
+
19
+ # Exempt paths — runtime data, not source code
20
+ EXEMPT_PATTERNS=(
21
+ "agents/data/"
22
+ "node_modules/"
23
+ ".uap-backups/"
24
+ ".uap/"
25
+ ".git/"
26
+ "dist/"
27
+ "/tmp/"
28
+ "/dev/"
29
+ )
30
+
31
+ for pattern in "${EXEMPT_PATTERNS[@]}"; do
32
+ if echo "$FILE_PATH" | grep -q "$pattern"; then
33
+ exit 0
34
+ fi
35
+ done
36
+
37
+ # Allow if path is inside a worktree
38
+ if echo "$FILE_PATH" | grep -q '\.worktrees/'; then
39
+ exit 0
40
+ fi
41
+
42
+ # BLOCK: path is outside worktrees and not exempt
43
+ echo '{"decision":"block","reason":"WORKTREE POLICY VIOLATION: File path is outside .worktrees/. All edits must target files inside a worktree. Run: uap worktree create <slug> then edit files in .worktrees/NNN-<slug>/. See policies/worktree-file-guard.md"}' >&2
44
+ exit 2
@@ -1,53 +1,38 @@
1
1
  #!/usr/bin/env bash
2
- # UAP Session End Hook (universal - all coding harnesses)
3
- # Records session completion in memory and cleans up agent state.
4
- # Fails safely - never blocks the agent.
2
+ # UAP Session End Hook Cleanup and archival
3
+ # Event: SessionEnd
4
+ # Stores final session summary and cleans up coordination state.
5
+ # Always exits 0 (never blocks).
5
6
  set -euo pipefail
6
7
 
7
- PROJECT_DIR="${CLAUDE_PROJECT_DIR:-${FACTORY_PROJECT_DIR:-${CURSOR_PROJECT_DIR:-${UAP_PROJECT_DIR:-.}}}}"
8
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-${FACTORY_PROJECT_DIR:-${CURSOR_PROJECT_DIR:-.}}}"
8
9
  DB_PATH="${PROJECT_DIR}/agents/data/memory/short_term.db"
9
10
  COORD_DB="${PROJECT_DIR}/agents/data/coordination/coordination.db"
10
-
11
- if [ ! -f "$DB_PATH" ]; then
12
- exit 0
13
- fi
14
-
15
11
  TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
16
12
 
17
- # Record session end marker in memory
18
- sqlite3 "$DB_PATH" "
19
- INSERT OR IGNORE INTO memories (timestamp, type, content)
20
- VALUES ('$TIMESTAMP', 'action', '[post-session] Session completed at $TIMESTAMP');
21
- " 2>/dev/null || true
22
-
23
- # Count decisions stored this session
24
- session_decisions=$(sqlite3 "$DB_PATH" "
25
- SELECT COUNT(*) FROM session_memories
26
- WHERE timestamp >= datetime('now', '-4 hours')
27
- AND type = 'decision';
28
- " 2>/dev/null || echo "0")
29
-
30
- if [ "$session_decisions" = "0" ]; then
31
- echo "<system-reminder>"
32
- echo "WARNING: No decisions stored this session."
33
- echo "Consider storing a summary before ending:"
34
- echo " sqlite3 ./agents/data/memory/short_term.db \"INSERT INTO session_memories (session_id,timestamp,type,content,importance) VALUES ('current',datetime('now'),'decision','<summary>',7);\""
35
- echo "</system-reminder>"
13
+ # Store session end marker
14
+ if [ -f "$DB_PATH" ]; then
15
+ sqlite3 "$DB_PATH" "
16
+ INSERT OR IGNORE INTO memories (timestamp, type, content)
17
+ VALUES ('$TIMESTAMP', 'action', '[session-end] Session terminated at $TIMESTAMP');
18
+ " 2>/dev/null || true
36
19
  fi
37
20
 
38
- # Clean up agent registrations from this session
21
+ # Clean up all active agents and work claims
39
22
  if [ -f "$COORD_DB" ]; then
40
23
  sqlite3 "$COORD_DB" "
41
- DELETE FROM work_claims WHERE agent_id IN (
42
- SELECT id FROM agent_registry
43
- WHERE status='active' AND last_heartbeat >= datetime('now','-10 minutes')
44
- );
45
- UPDATE work_announcements SET completed_at='$TIMESTAMP'
46
- WHERE completed_at IS NULL AND agent_id IN (
47
- SELECT id FROM agent_registry
48
- WHERE status='active' AND last_heartbeat >= datetime('now','-10 minutes')
49
- );
50
- UPDATE agent_registry SET status='completed'
51
- WHERE status='active' AND last_heartbeat >= datetime('now','-10 minutes');
24
+ UPDATE work_announcements SET completed_at = '$TIMESTAMP'
25
+ WHERE completed_at IS NULL;
26
+ DELETE FROM work_claims;
27
+ UPDATE agent_registry SET status = 'completed'
28
+ WHERE status IN ('active', 'idle');
52
29
  " 2>/dev/null || true
53
30
  fi
31
+
32
+ # Clean up backup files older than 7 days (retention policy)
33
+ BACKUP_DIR="${PROJECT_DIR}/.uap-backups"
34
+ if [ -d "$BACKUP_DIR" ]; then
35
+ find "$BACKUP_DIR" -maxdepth 1 -mindepth 1 -type d -mtime +7 -exec rm -rf {} \; 2>/dev/null || true
36
+ fi
37
+
38
+ exit 0
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env bash
2
+ # UAP Completion Gate + Session Cleanup — Stop hook
3
+ # Event: Stop
4
+ # Checks completion gates and cleans up session state.
5
+ # Exit 2 = BLOCK stop (force agent to continue). Exit 0 = allow stop.
6
+ # Enforces: completion-gate, mandatory-testing-deployment policies.
7
+ set -euo pipefail
8
+
9
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-${FACTORY_PROJECT_DIR:-${CURSOR_PROJECT_DIR:-.}}}"
10
+ DB_PATH="${PROJECT_DIR}/agents/data/memory/short_term.db"
11
+ COORD_DB="${PROJECT_DIR}/agents/data/coordination/coordination.db"
12
+
13
+ # ─── Detect if code was changed ─────────────────────────────────
14
+ CODE_CHANGED="false"
15
+ TS_CHANGED="false"
16
+ TEST_FILES_CHANGED="false"
17
+ UNCOMMITTED_CHANGES="false"
18
+
19
+ # Check for uncommitted changes in the working tree
20
+ CHANGED_FILES=$(git -C "$PROJECT_DIR" diff --name-only HEAD 2>/dev/null || true)
21
+ STAGED_FILES=$(git -C "$PROJECT_DIR" diff --cached --name-only 2>/dev/null || true)
22
+ UNTRACKED_FILES=$(git -C "$PROJECT_DIR" ls-files --others --exclude-standard 2>/dev/null || true)
23
+
24
+ ALL_CHANGES="${CHANGED_FILES}${STAGED_FILES}${UNTRACKED_FILES}"
25
+
26
+ if [ -n "$ALL_CHANGES" ]; then
27
+ UNCOMMITTED_CHANGES="true"
28
+
29
+ # Check for source code changes
30
+ if echo "$ALL_CHANGES" | grep -qE '\.(ts|tsx|js|jsx)$'; then
31
+ CODE_CHANGED="true"
32
+ fi
33
+
34
+ # Check for TypeScript changes specifically
35
+ if echo "$ALL_CHANGES" | grep -qE '\.tsx?$'; then
36
+ TS_CHANGED="true"
37
+ fi
38
+
39
+ # Check for test file changes
40
+ if echo "$ALL_CHANGES" | grep -qE 'test/.*\.(ts|tsx|js|jsx)$'; then
41
+ TEST_FILES_CHANGED="true"
42
+ fi
43
+ fi
44
+
45
+ # ─── Completion Gate Checklist ───────────────────────────────────
46
+ output=""
47
+ warnings=0
48
+
49
+ if [ "$CODE_CHANGED" = "true" ]; then
50
+ output+="## COMPLETION GATE CHECKLIST"$'\n'
51
+ output+=""$'\n'
52
+
53
+ # Gate 1: New tests written
54
+ if [ "$TEST_FILES_CHANGED" = "true" ]; then
55
+ output+="[PASS] New test files modified/added"$'\n'
56
+ else
57
+ output+="[WARN] No test files modified — completion-gate requires 2+ new tests for code changes"$'\n'
58
+ warnings=$((warnings + 1))
59
+ fi
60
+
61
+ # Gate 2: Build check (heuristic — check if dist/ is newer than last src change)
62
+ if [ -d "${PROJECT_DIR}/dist" ]; then
63
+ DIST_TIME=$(stat -c %Y "${PROJECT_DIR}/dist" 2>/dev/null || echo "0")
64
+ SRC_TIME=$(find "${PROJECT_DIR}/src" -name "*.ts" -newer "${PROJECT_DIR}/dist" 2>/dev/null | head -1)
65
+ if [ -z "$SRC_TIME" ]; then
66
+ output+="[PASS] Build appears up-to-date (dist/ newer than src/)"$'\n'
67
+ else
68
+ output+="[WARN] Build may be stale — run 'npm run build' to verify"$'\n'
69
+ warnings=$((warnings + 1))
70
+ fi
71
+ else
72
+ output+="[WARN] No dist/ directory — run 'npm run build'"$'\n'
73
+ warnings=$((warnings + 1))
74
+ fi
75
+
76
+ # Gate 3: Uncommitted changes
77
+ if [ -n "$STAGED_FILES" ] || [ -n "$CHANGED_FILES" ]; then
78
+ output+="[WARN] Uncommitted changes detected — commit or stash before version bump"$'\n'
79
+ warnings=$((warnings + 1))
80
+ fi
81
+
82
+ # Gate 4: Version bump check (was package.json version changed?)
83
+ VERSION_BUMPED="false"
84
+ if echo "$ALL_CHANGES" | grep -q "package.json"; then
85
+ # Check if version field actually changed
86
+ VERSION_DIFF=$(git -C "$PROJECT_DIR" diff HEAD -- package.json 2>/dev/null | grep -E '^\+.*"version"' || true)
87
+ if [ -n "$VERSION_DIFF" ]; then
88
+ VERSION_BUMPED="true"
89
+ fi
90
+ fi
91
+ if [ "$VERSION_BUMPED" = "true" ]; then
92
+ output+="[PASS] Version bump detected in package.json"$'\n'
93
+ else
94
+ output+="[WARN] No version bump — run 'npm run version:patch/minor/major' before claiming done"$'\n'
95
+ warnings=$((warnings + 1))
96
+ fi
97
+
98
+ output+=""$'\n'
99
+
100
+ if [ "$warnings" -gt 0 ]; then
101
+ output+="$warnings completion gate warning(s). Review policies/completion-gate.md before claiming task done."$'\n'
102
+ else
103
+ output+="All completion gates appear satisfied."$'\n'
104
+ fi
105
+ fi
106
+
107
+ # ─── Session Cleanup ─────────────────────────────────────────────
108
+ # Store session marker in memory DB
109
+ if [ -f "$DB_PATH" ]; then
110
+ TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
111
+ sqlite3 "$DB_PATH" "
112
+ INSERT OR IGNORE INTO memories (timestamp, type, content)
113
+ VALUES ('$TIMESTAMP', 'action', '[session-end] Agent stopping at $TIMESTAMP. Code changed: $CODE_CHANGED, Tests: $TEST_FILES_CHANGED, Warnings: $warnings');
114
+ " 2>/dev/null || true
115
+ fi
116
+
117
+ # Mark agent as completed in coordination DB
118
+ if [ -f "$COORD_DB" ]; then
119
+ # Complete all active announcements for agents from this session
120
+ sqlite3 "$COORD_DB" "
121
+ UPDATE work_announcements SET completed_at = datetime('now')
122
+ WHERE completed_at IS NULL AND agent_id IN (
123
+ SELECT id FROM agent_registry
124
+ WHERE status = 'active' AND last_heartbeat >= datetime('now', '-5 minutes')
125
+ );
126
+ DELETE FROM work_claims WHERE agent_id IN (
127
+ SELECT id FROM agent_registry
128
+ WHERE status = 'active' AND last_heartbeat >= datetime('now', '-5 minutes')
129
+ );
130
+ UPDATE agent_registry SET status = 'completed'
131
+ WHERE status = 'active' AND last_heartbeat >= datetime('now', '-5 minutes');
132
+ " 2>/dev/null || true
133
+ fi
134
+
135
+ # Output the checklist (informational — shown to model)
136
+ if [ -n "$output" ]; then
137
+ echo "$output"
138
+ fi
139
+
140
+ # Allow stop (exit 0) — we use warnings, not hard blocks, because
141
+ # the model needs agency to decide when it's truly done vs still working.
142
+ exit 0