@miller-tech/uap 1.12.0 → 1.13.1
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/dist/.tsbuildinfo +1 -1
- package/dist/cli/hooks.js +216 -65
- package/dist/cli/hooks.js.map +1 -1
- package/dist/dashboard/data-seeder.d.ts +6 -14
- package/dist/dashboard/data-seeder.d.ts.map +1 -1
- package/dist/dashboard/data-seeder.js +213 -58
- package/dist/dashboard/data-seeder.js.map +1 -1
- package/dist/dashboard/data-service.d.ts.map +1 -1
- package/dist/dashboard/data-service.js +16 -14
- package/dist/dashboard/data-service.js.map +1 -1
- package/package.json +1 -1
- package/templates/hooks/forgecode.plugin.sh +348 -47
- package/templates/hooks/post-compact.sh +111 -0
- package/templates/hooks/post-tool-use-edit-write.sh +38 -0
- package/templates/hooks/pre-tool-use-bash.sh +87 -0
- package/templates/hooks/pre-tool-use-edit-write.sh +44 -0
- package/templates/hooks/session-end.sh +25 -40
- package/templates/hooks/stop.sh +142 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# UAP Post-Compact Compliance Re-injection — INFORMATIONAL hook
|
|
3
|
+
# Event: PostCompact
|
|
4
|
+
# Re-injects policy awareness after context compaction.
|
|
5
|
+
# Always exits 0 (never blocks).
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-${FACTORY_PROJECT_DIR:-${CURSOR_PROJECT_DIR:-.}}}"
|
|
9
|
+
DB_PATH="${PROJECT_DIR}/agents/data/memory/short_term.db"
|
|
10
|
+
COORD_DB="${PROJECT_DIR}/agents/data/coordination/coordination.db"
|
|
11
|
+
|
|
12
|
+
output=""
|
|
13
|
+
|
|
14
|
+
# ─── Active Policy Summary ──────────────────────────────────────
|
|
15
|
+
output+="<system-reminder>"$'\n'
|
|
16
|
+
output+="## UAP COMPLIANCE RESTORED (Post-Compact)"$'\n'
|
|
17
|
+
output+=""$'\n'
|
|
18
|
+
output+="Context was compacted. All policies remain in effect:"$'\n'
|
|
19
|
+
output+=""$'\n'
|
|
20
|
+
output+="### BLOCKING HOOKS (hard enforcement):"$'\n'
|
|
21
|
+
output+="- **Worktree File Guard**: Edit/Write operations BLOCKED outside .worktrees/"$'\n'
|
|
22
|
+
output+="- **Dangerous Command Guard**: terraform apply/destroy, git push --force, direct master commits BLOCKED"$'\n'
|
|
23
|
+
output+="- **Completion Gate**: Stop/completion checked for test, build, version gates"$'\n'
|
|
24
|
+
output+=""$'\n'
|
|
25
|
+
output+="### ACTIVE POLICIES [all REQUIRED]:"$'\n'
|
|
26
|
+
output+="1. worktree-enforcement — All file changes in .worktrees/NNN-<slug>/"$'\n'
|
|
27
|
+
output+="2. worktree-file-guard — Edit/Write paths must be inside worktrees"$'\n'
|
|
28
|
+
output+="3. pre-edit-build-gate — npm run build before/after .ts edits"$'\n'
|
|
29
|
+
output+="4. completion-gate — 2+ tests, build, lint, version bump before DONE"$'\n'
|
|
30
|
+
output+="5. semver-versioning — npm run version:patch/minor/major (no manual edits)"$'\n'
|
|
31
|
+
output+="6. mandatory-file-backup — Backup files before modification"$'\n'
|
|
32
|
+
output+="7. iac-state-parity — All infra changes reflected in IaC"$'\n'
|
|
33
|
+
output+="8. iac-pipeline-enforcement — No local terraform apply/destroy"$'\n'
|
|
34
|
+
output+="9. kubectl-verify-backport — kubectl changes backported to IaC"$'\n'
|
|
35
|
+
output+="10. definition-of-done-iac — Pipeline apply + kubectl verify required"$'\n'
|
|
36
|
+
output+="11. image-asset-verification — Script-based, not vision-based [RECOMMENDED]"$'\n'
|
|
37
|
+
output+=""$'\n'
|
|
38
|
+
output+="### MANDATORY BEFORE WORK:"$'\n'
|
|
39
|
+
output+="- Verify you are in a worktree: pwd must contain .worktrees/"$'\n'
|
|
40
|
+
output+="- Run: uap task ready"$'\n'
|
|
41
|
+
output+="- Query memory: uap memory query \"<current task>\""$'\n'
|
|
42
|
+
output+=""$'\n'
|
|
43
|
+
|
|
44
|
+
# ─── Restore session context from memory ─────────────────────────
|
|
45
|
+
if [ -f "$DB_PATH" ]; then
|
|
46
|
+
recent=$(sqlite3 "$DB_PATH" "
|
|
47
|
+
SELECT type || ': ' || substr(content, 1, 100) FROM memories
|
|
48
|
+
WHERE timestamp >= datetime('now', '-2 hours')
|
|
49
|
+
ORDER BY id DESC LIMIT 5;
|
|
50
|
+
" 2>/dev/null || true)
|
|
51
|
+
|
|
52
|
+
if [ -n "$recent" ]; then
|
|
53
|
+
output+="### Recent Memory (last 2h):"$'\n'
|
|
54
|
+
output+="$recent"$'\n'
|
|
55
|
+
output+=""$'\n'
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# Session decisions
|
|
59
|
+
decisions=$(sqlite3 "$DB_PATH" "
|
|
60
|
+
SELECT substr(content, 1, 120) FROM session_memories
|
|
61
|
+
WHERE type = 'decision' AND importance >= 6
|
|
62
|
+
ORDER BY id DESC LIMIT 3;
|
|
63
|
+
" 2>/dev/null || true)
|
|
64
|
+
|
|
65
|
+
if [ -n "$decisions" ]; then
|
|
66
|
+
output+="### Recent Decisions:"$'\n'
|
|
67
|
+
output+="$decisions"$'\n'
|
|
68
|
+
output+=""$'\n'
|
|
69
|
+
fi
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# ─── Multi-agent coordination status ────────────────────────────
|
|
73
|
+
if [ -f "$COORD_DB" ]; then
|
|
74
|
+
active_work=$(sqlite3 "$COORD_DB" "
|
|
75
|
+
SELECT agent_id || ' editing ' || resource
|
|
76
|
+
FROM work_announcements
|
|
77
|
+
WHERE completed_at IS NULL
|
|
78
|
+
ORDER BY announced_at DESC LIMIT 5;
|
|
79
|
+
" 2>/dev/null || true)
|
|
80
|
+
|
|
81
|
+
if [ -n "$active_work" ]; then
|
|
82
|
+
output+="### Active Work (coordinate — do not conflict):"$'\n'
|
|
83
|
+
output+="$active_work"$'\n'
|
|
84
|
+
output+=""$'\n'
|
|
85
|
+
fi
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
# ─── Worktree status ────────────────────────────────────────────
|
|
89
|
+
CURRENT_BRANCH=$(git -C "$PROJECT_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
|
90
|
+
IS_WORKTREE="false"
|
|
91
|
+
if echo "$PROJECT_DIR" | grep -q '\.worktrees/'; then
|
|
92
|
+
IS_WORKTREE="true"
|
|
93
|
+
fi
|
|
94
|
+
GIT_DIR=$(git -C "$PROJECT_DIR" rev-parse --git-dir 2>/dev/null || true)
|
|
95
|
+
GIT_COMMON=$(git -C "$PROJECT_DIR" rev-parse --git-common-dir 2>/dev/null || true)
|
|
96
|
+
if [ -n "$GIT_DIR" ] && [ -n "$GIT_COMMON" ] && [ "$GIT_DIR" != "$GIT_COMMON" ]; then
|
|
97
|
+
IS_WORKTREE="true"
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
if [ "$IS_WORKTREE" = "false" ] && { [ "$CURRENT_BRANCH" = "main" ] || [ "$CURRENT_BRANCH" = "master" ]; }; then
|
|
101
|
+
output+="### ⚠ WORKTREE VIOLATION: You are on $CURRENT_BRANCH in the project root."$'\n'
|
|
102
|
+
output+="Run: uap worktree create <slug> BEFORE any file edits."$'\n'
|
|
103
|
+
output+=""$'\n'
|
|
104
|
+
else
|
|
105
|
+
output+="### Worktree: ACTIVE (branch: $CURRENT_BRANCH)"$'\n'
|
|
106
|
+
output+=""$'\n'
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
output+="</system-reminder>"$'\n'
|
|
110
|
+
|
|
111
|
+
echo "$output"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# UAP Build Gate Reminder — INFORMATIONAL hook
|
|
3
|
+
# Event: PostToolUse (matcher: Edit|Write)
|
|
4
|
+
# Reminds about pre-edit build gate after .ts file modifications.
|
|
5
|
+
# Enforces: pre-edit-build-gate policy.
|
|
6
|
+
# Always exits 0 (never blocks).
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
# Read tool input from stdin (JSON)
|
|
10
|
+
INPUT=$(cat)
|
|
11
|
+
|
|
12
|
+
# Extract file_path from tool_input
|
|
13
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.filePath // empty' 2>/dev/null || true)
|
|
14
|
+
|
|
15
|
+
# If we can't determine the file path, exit silently
|
|
16
|
+
if [ -z "$FILE_PATH" ]; then
|
|
17
|
+
exit 0
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# Only remind for TypeScript files
|
|
21
|
+
if echo "$FILE_PATH" | grep -qE '\.tsx?$'; then
|
|
22
|
+
echo "[BUILD GATE] TypeScript file modified: $(basename "$FILE_PATH"). Run 'npm run build' before editing the next file. See policies/pre-edit-build-gate.md"
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
# Remind about file backup policy for any file edit
|
|
26
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"
|
|
27
|
+
BACKUP_DIR="${PROJECT_DIR}/.uap-backups/$(date +%Y-%m-%d)"
|
|
28
|
+
RELATIVE_PATH="${FILE_PATH#"$PROJECT_DIR"/}"
|
|
29
|
+
|
|
30
|
+
# Check if backup exists for this file
|
|
31
|
+
if [ ! -f "${BACKUP_DIR}/${RELATIVE_PATH}" ] 2>/dev/null; then
|
|
32
|
+
# Only warn for source files, not generated or runtime files
|
|
33
|
+
if ! echo "$FILE_PATH" | grep -qE '(node_modules|dist|\.uap-backups|agents/data|\.git)/'; then
|
|
34
|
+
echo "[BACKUP REMINDER] No backup found for $(basename "$FILE_PATH"). Policy requires: mkdir -p $(dirname "${BACKUP_DIR}/${RELATIVE_PATH}") && cp \"$FILE_PATH\" \"${BACKUP_DIR}/${RELATIVE_PATH}\""
|
|
35
|
+
fi
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
exit 0
|
|
@@ -0,0 +1,87 @@
|
|
|
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
|
+
# Allow automated version bump commits (message contains "bump version")
|
|
46
|
+
if echo "$CMD" | grep -qE 'chore: bump version|version:patch|version:minor|version:major|version-bump'; then
|
|
47
|
+
exit 0
|
|
48
|
+
fi
|
|
49
|
+
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
|
|
50
|
+
exit 2
|
|
51
|
+
fi
|
|
52
|
+
fi
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# ─── Direct Push to Master/Main Protection ──────────────────────
|
|
56
|
+
# Block git push targeting main/master directly (not through PR)
|
|
57
|
+
if echo "$CMD" | grep -qE '\bgit\s+push\b'; then
|
|
58
|
+
# Block explicit pushes to main/master
|
|
59
|
+
if echo "$CMD" | grep -qE '\bgit\s+push\s+(origin\s+)?(main|master)\b'; then
|
|
60
|
+
# Allow push after version bump (git push && git push --tags pattern)
|
|
61
|
+
if echo "$CMD" | grep -qE 'git\s+push\s+--tags|git\s+push\s*&&\s*git\s+push\s+--tags'; then
|
|
62
|
+
exit 0
|
|
63
|
+
fi
|
|
64
|
+
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
|
|
65
|
+
exit 2
|
|
66
|
+
fi
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# ─── Destructive Git Operations ─────────────────────────────────
|
|
70
|
+
# Block git reset --hard and git clean -f outside worktrees
|
|
71
|
+
if echo "$CMD" | grep -qE '\bgit\s+reset\s+--hard\b|\bgit\s+clean\s+-[a-z]*f'; then
|
|
72
|
+
CWD=$(echo "$INPUT" | jq -r '.cwd // empty' 2>/dev/null || true)
|
|
73
|
+
if ! echo "${CWD:-.}" | grep -q '\.worktrees/'; then
|
|
74
|
+
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
|
|
75
|
+
exit 2
|
|
76
|
+
fi
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# ─── Manual Version Edit Protection ─────────────────────────────
|
|
80
|
+
# Block direct edits to package.json version field via sed/awk
|
|
81
|
+
if echo "$CMD" | grep -qE "(sed|awk).*package\.json.*(version|\"version\")|((sed|awk).*version.*package\.json)|(jq.*\.version.*package\.json)"; then
|
|
82
|
+
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
|
|
83
|
+
exit 2
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
# Command allowed
|
|
87
|
+
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
|
|
3
|
-
#
|
|
4
|
-
#
|
|
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
|
|
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
|
-
#
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
21
|
+
# Clean up all active agents and work claims
|
|
39
22
|
if [ -f "$COORD_DB" ]; then
|
|
40
23
|
sqlite3 "$COORD_DB" "
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|