afv-library/skills/investigating-agentforce-d360/tools/archive_data_dir.sh
rjayagopal 20ae436442 @W-22707610 feat: add investigating-agentforce-d360 skill
Data Cloud 360° view of a single Agentforce session — DC-only, zero
Splunk dependency. Pulls 24 STDM + GenAI DMOs via the Data Cloud Query
REST API, assembles a hierarchical session tree (Interaction → Step →
Generation → GatewayRequest), and renders a human-readable markdown
summary with transcript + per-turn topic/action invocations + LLM
generations + tool calls + audit chain.

Migrated as a standalone Apache-2.0 skill from an internal hub plugin —
self-contained, no sibling-skill or plugin dependencies.

What this skill answers:
  - "Trace session <uuid>" / "Summarize what happened in <0Mw…>"
  - "Find escalated sessions today on Messaging in <org>"
  - Session discovery by time / agent / channel / outcome / conversation
    text when the user has no session id

What it does NOT answer (use a different surface):
  - Design-time architecture — use investigating-agentforce-architecture
  - Runtime planner availability — DC alone can't tell you which
    topic/action was eligible for the classifier on a given turn

Skill layout:
  - 8 Python pipeline modules (fetch_dc, assemble_dc, render_dc,
    discover_sessions, resolve_session, dc, storage, config)
  - 4 _shared helpers (paths, fs_guard, sql, __init__) with skill-scoped
    DATA_ROOT (~/.claude/data/investigating-agentforce-d360/)
  - 26 SQL templates under assets/dc/
  - 27 test files (367 tests + 18 subtests, 100% passing)
  - 3 reference docs (artifacts.md, dc_dmo_fields.md,
    dc_pipeline_contract.md)
  - SKILL.md (sf-skills frontmatter, license: Apache-2.0,
    metadata.version: "1.0")
  - README.md (external-facing quick-start)
  - tools/grant_allowlist.py (idempotent first-run permission grant)
  - tools/archive_data_dir.sh (opt-in stop-hook tarballer)

Quality gates:
  - pytest scripts/tests/: 367 passed + 18 subtests, 0 failures
  - npm run validate:skills: 62 of 62 skill(s) checked, 0 errors
  - Live end-to-end runs against 3 real Salesforce sessions exercising
    both the full-tree and STDM-lag gateway-direct render branches
  - 4 independent code-review rounds (correctness, security, markdown,
    architecture-critic) — all findings addressed

Customer-data hygiene: no live tenant ids, no internal sprint markers,
no hub/sibling-skill references. Synthetic fixtures look obviously
synthetic (`019dface-…` UUIDs, `0MwTESTMSG…` MessagingSession ids,
`00DTESTORG…` org ids, `MyAgent` placeholder agent name).

Sibling skill: investigating-agentforce-architecture (PR #278) — same
migration pattern, design-time metadata; complementary scope.
2026-05-28 20:57:52 +10:00

79 lines
3.1 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Stop-hook helper — archive the most-recently-modified session dir under
# ~/.claude/data/investigating-agentforce-d360/ into a tarball for
# compliance / retro purposes.
#
# This is a USER-LEVEL hook (in ~/.claude/settings.json), opt-in.
# Archiving sessions is a per-team preference; users who want it wire it
# up, users who don't, don't.
#
# Behavior:
# - Find the most-recently-touched <sid>/ directory under DATA_ROOT
# - Tar + gzip it into ~/.claude/data/investigating-agentforce-d360-archive/
# with filename <sid>-<YYYY-MM-DD-HHMMSS>.tar.gz
# - Silent no-op if no session dir exists, or the latest is already
# archived (same mtime as the most-recent archive)
# - Never deletes the source; archiving only
#
# Exit codes: always 0 — Stop hooks block session exit only if non-zero.
#
set -eu
DATA_ROOT="$HOME/.claude/data/investigating-agentforce-d360"
ARCHIVE_ROOT="$HOME/.claude/data/investigating-agentforce-d360-archive"
[ -d "$DATA_ROOT" ] || exit 0
# Find the most-recently-modified session dir.
#
# Layout (per scripts/_shared/paths.py):
# DATA_ROOT/<org_id_15>/<agent_api_name>__<agent_version>/<session_id>/
# That's three levels deep — using `find -mindepth 3 -maxdepth 3` is the
# portable way to address the right level without globbing the org-id or
# agent levels (which would archive an entire org's worth of sessions
# under a filename pretending to be one session id).
#
# `find -printf` is GNU-only, so we sort by mtime via `stat` for portability.
# `find ... -depth 3 -type d` lists the right level on both macOS (BSD find)
# and Linux (GNU find).
LATEST=""
LATEST_MTIME=0
while IFS= read -r d; do
[ -d "$d" ] || continue
mt=$(stat -f %m "$d" 2>/dev/null || stat -c %Y "$d" 2>/dev/null || echo 0)
if [ "$mt" -gt "$LATEST_MTIME" ]; then
LATEST_MTIME=$mt
LATEST="$d"
fi
done < <(find "$DATA_ROOT" -mindepth 3 -maxdepth 3 -type d 2>/dev/null)
[ -n "$LATEST" ] || exit 0
[ -d "$LATEST" ] || exit 0
SID=$(basename "$LATEST")
TS=$(date +"%Y-%m-%d-%H%M%S")
mkdir -p "$ARCHIVE_ROOT"
TARBALL="$ARCHIVE_ROOT/${SID}-${TS}.tar.gz"
# Skip if an archive for this sid created within the last minute already
# exists — avoids redundant tarring on rapid session starts/stops.
#
# `-mmin -1` is the portable "modified within the last 1 minute" form on
# both BSD find (macOS) and GNU find (Linux). Earlier `-mtime -1m` was a
# silent foot-gun: BSD interpreted `m` as MONTHS (matched ~30 days), and
# GNU rejected the unit suffix entirely.
RECENT=$(find "$ARCHIVE_ROOT" -name "${SID}-*.tar.gz" -mmin -1 2>/dev/null | head -1)
[ -n "$RECENT" ] && exit 0
# Tar the session dir relative to its parent (the agent dir) so the archive
# unpacks as just `<session_id>/` without the org/agent path prefix. `--`
# guards against any future SID that ever started with `-`. On failure,
# remove the partial tarball so the next hook invocation retries cleanly
# instead of treating a corrupt 0-byte file as a recent dedup hit.
PARENT=$(dirname "$LATEST")
if ! tar --no-absolute-names -C "$PARENT" -czf "$TARBALL" -- "$SID" 2>/dev/null; then
rm -f "$TARBALL" 2>/dev/null
fi
exit 0