create-ccc-tutor 0.1.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.
- package/README.md +41 -0
- package/bin/cli.js +76 -0
- package/package.json +28 -0
- package/template/.claude/commands/abandon.md +7 -0
- package/template/.claude/commands/add-anti-flag.md +7 -0
- package/template/.claude/commands/add-constitution-clause.md +7 -0
- package/template/.claude/commands/audit-spec.md +7 -0
- package/template/.claude/commands/commit.md +7 -0
- package/template/.claude/commands/constitution-edit.md +7 -0
- package/template/.claude/commands/db-schema.md +7 -0
- package/template/.claude/commands/exam.md +66 -0
- package/template/.claude/commands/execution-plan.md +7 -0
- package/template/.claude/commands/feature-draft.md +7 -0
- package/template/.claude/commands/handoff.md +7 -0
- package/template/.claude/commands/implement.md +7 -0
- package/template/.claude/commands/init.md +7 -0
- package/template/.claude/commands/next.md +7 -0
- package/template/.claude/commands/offload.md +7 -0
- package/template/.claude/commands/pickup.md +7 -0
- package/template/.claude/commands/recall.md +7 -0
- package/template/.claude/commands/remember.md +7 -0
- package/template/.claude/commands/slide.md +87 -0
- package/template/.claude/commands/spec-finalize.md +7 -0
- package/template/.claude/commands/test-fix.md +7 -0
- package/template/.claude/commands/uninstall.md +7 -0
- package/template/.claude/settings.json +161 -0
- package/template/.claude-plugin/plugin.json +41 -0
- package/template/.codex/config.toml +24 -0
- package/template/.codex/hooks.json +4 -0
- package/template/.codex/install-skills.sh +18 -0
- package/template/.codex/skills/exam/SKILL.md +61 -0
- package/template/.codex/skills/slide/SKILL.md +69 -0
- package/template/.harness/agents/README.md +70 -0
- package/template/.harness/agents/_template/junior-agent-template.md +116 -0
- package/template/.harness/agents/backend-reviewer.md +153 -0
- package/template/.harness/agents/frontend-reviewer.md +158 -0
- package/template/.harness/agents/security-reviewer.md +148 -0
- package/template/.harness/agents/test-fixer.md +147 -0
- package/template/.harness/docs/doc-sync.md +29 -0
- package/template/.harness/docs/git-hygiene.md +56 -0
- package/template/.harness/docs/spec-model.md +47 -0
- package/template/.harness/docs/tool-map.md +120 -0
- package/template/.harness/docs/workflow.md +59 -0
- package/template/.harness/scripts/README.md +70 -0
- package/template/.harness/scripts/auditor-gate.sh +388 -0
- package/template/.harness/scripts/bootstrap-check.sh +103 -0
- package/template/.harness/scripts/budget-monitor.sh +223 -0
- package/template/.harness/scripts/check-prereqs.sh +165 -0
- package/template/.harness/scripts/checkpoint-recall.sh +136 -0
- package/template/.harness/scripts/checkpoint-write.sh +281 -0
- package/template/.harness/scripts/decision-log-append.sh +90 -0
- package/template/.harness/scripts/env-check.sh +286 -0
- package/template/.harness/scripts/format-edit.sh +80 -0
- package/template/.harness/scripts/lint-bans.sh +110 -0
- package/template/.harness/scripts/memory-archive.sh +129 -0
- package/template/.harness/scripts/memory-recall.sh +197 -0
- package/template/.harness/scripts/memory-snapshot.sh +124 -0
- package/template/.harness/scripts/post-migration.sh +58 -0
- package/template/.harness/scripts/precommit-cycles.sh +74 -0
- package/template/.harness/scripts/precommit-typecheck.sh +69 -0
- package/template/.harness/scripts/scratchpad-recall.sh +83 -0
- package/template/.harness/scripts/scratchpad-update.sh +39 -0
- package/template/.harness/scripts/standalone-bootstrap.md +443 -0
- package/template/.harness/skills/abandon/SKILL.md +157 -0
- package/template/.harness/skills/add-anti-flag/SKILL.md +205 -0
- package/template/.harness/skills/add-constitution-clause/SKILL.md +244 -0
- package/template/.harness/skills/audit-spec/SKILL.md +395 -0
- package/template/.harness/skills/commit/SKILL.md +270 -0
- package/template/.harness/skills/constitution-edit/SKILL.md +292 -0
- package/template/.harness/skills/db-schema/SKILL.md +145 -0
- package/template/.harness/skills/db-schema/references/methodology.md +202 -0
- package/template/.harness/skills/execution-plan/SKILL.md +346 -0
- package/template/.harness/skills/feature-draft/SKILL.md +426 -0
- package/template/.harness/skills/handoff/SKILL.md +211 -0
- package/template/.harness/skills/implement/SKILL.md +355 -0
- package/template/.harness/skills/init/SKILL.md +805 -0
- package/template/.harness/skills/next/SKILL.md +245 -0
- package/template/.harness/skills/offload/SKILL.md +134 -0
- package/template/.harness/skills/pickup/SKILL.md +213 -0
- package/template/.harness/skills/recall/SKILL.md +159 -0
- package/template/.harness/skills/remember/SKILL.md +205 -0
- package/template/.harness/skills/spec-finalize/SKILL.md +196 -0
- package/template/.harness/skills/test-fix/SKILL.md +363 -0
- package/template/.harness/skills/uninstall/SKILL.md +370 -0
- package/template/.harness/state/install.json +83 -0
- package/template/AGENTS.md +262 -0
- package/template/CCC_MAGI_LICENSE +201 -0
- package/template/CCC_MAGI_README.md +986 -0
- package/template/CLAUDE.md +658 -0
- package/template/codex.md +39 -0
- package/template/constitution.md +164 -0
- package/template/course/README.md +15 -0
- package/template/course/course_code(example)/exam/README.md +2 -0
- package/template/course/course_code(example)/slide/slide_example-1.pdf +40 -0
- package/template/course/course_code(example)/slide/slide_example-2.pdf +40 -0
- package/template/docs/features/slide-query-implementation.md +79 -0
- package/template/docs/features/slide-query.md +211 -0
- package/template/docs-harness/README.md +42 -0
- package/template/docs-harness/adoption-playbook.md +373 -0
- package/template/docs-harness/ccc-step1-driver-template.md +288 -0
- package/template/docs-harness/cli-configs-README.md +78 -0
- package/template/docs-harness/context-architecture-v2.md +249 -0
- package/template/docs-harness/design-spec.md +437 -0
- package/template/docs-harness/memory-layer.md +135 -0
- package/template/docs-harness/retrospective-notes.md +204 -0
- package/template/gitignore +106 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# bootstrap-check.sh — state-machine UserPromptSubmit hook
|
|
3
|
+
#
|
|
4
|
+
# Fires on EVERY user message. Decides which of 4 states this project is in,
|
|
5
|
+
# then injects appropriate guidance into Claude's additionalContext.
|
|
6
|
+
#
|
|
7
|
+
# STATE MACHINE:
|
|
8
|
+
# S0: No .harness/ directory → not a CCC-MAGI project → silent
|
|
9
|
+
# S1: .harness/ but no env-check → first contact → ask user about setup
|
|
10
|
+
# S2: env-check.json exists, → environment passed, → tell Claude to /init
|
|
11
|
+
# no install.json project not deployed
|
|
12
|
+
# S3: install.json exists → fully configured → silent
|
|
13
|
+
#
|
|
14
|
+
# DEDUPLICATION:
|
|
15
|
+
# Within one Claude session, only inject the S1/S2 prompt ONCE. Track via
|
|
16
|
+
# .harness/state/_bootstrap-injected-sessions/<session-id>.flag files.
|
|
17
|
+
# Without session_id (older CLIs), fall back to time-based dedup (1 hour).
|
|
18
|
+
#
|
|
19
|
+
# CONTRACT:
|
|
20
|
+
# stdin: JSON envelope from Claude Code with session_id + prompt
|
|
21
|
+
# stdout: hookSpecificOutput JSON (additionalContext injection)
|
|
22
|
+
# exit 0: always (failure to detect should not block user)
|
|
23
|
+
|
|
24
|
+
set -eu
|
|
25
|
+
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
|
|
26
|
+
|
|
27
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
28
|
+
HARNESS_DIR="$PROJECT_DIR/.harness"
|
|
29
|
+
ENV_CHECK="$HARNESS_DIR/state/env-check.json"
|
|
30
|
+
INSTALL_JSON="$HARNESS_DIR/state/install.json"
|
|
31
|
+
SESSIONS_DIR="$HARNESS_DIR/state/_bootstrap-injected-sessions"
|
|
32
|
+
TIME_FLAG="$HARNESS_DIR/state/_bootstrap-injected-at"
|
|
33
|
+
|
|
34
|
+
# ─── S0: not a CCC-MAGI project → silent ──────────────────────────────
|
|
35
|
+
[ -d "$HARNESS_DIR" ] || exit 0
|
|
36
|
+
|
|
37
|
+
# ─── S3: fully configured → silent ────────────────────────────────────
|
|
38
|
+
[ -f "$INSTALL_JSON" ] && exit 0
|
|
39
|
+
|
|
40
|
+
# ─── Drain + parse stdin for session_id ───────────────────────────────
|
|
41
|
+
HOOK_INPUT="$(cat 2>/dev/null || true)"
|
|
42
|
+
SESSION_ID=""
|
|
43
|
+
if [ -n "$HOOK_INPUT" ] && command -v jq >/dev/null 2>&1; then
|
|
44
|
+
SESSION_ID="$(printf '%s' "$HOOK_INPUT" | jq -r '.session_id // empty' 2>/dev/null || echo "")"
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# ─── Dedup check ──────────────────────────────────────────────────────
|
|
48
|
+
already_injected_this_session() {
|
|
49
|
+
if [ -n "$SESSION_ID" ]; then
|
|
50
|
+
[ -f "$SESSIONS_DIR/${SESSION_ID}.flag" ] && return 0
|
|
51
|
+
return 1
|
|
52
|
+
fi
|
|
53
|
+
# Fallback: time-based (1 hour window)
|
|
54
|
+
if [ -f "$TIME_FLAG" ]; then
|
|
55
|
+
LAST=$(cat "$TIME_FLAG" 2>/dev/null || echo 0)
|
|
56
|
+
NOW=$(date +%s)
|
|
57
|
+
DIFF=$((NOW - LAST))
|
|
58
|
+
[ "$DIFF" -lt 3600 ] && return 0
|
|
59
|
+
fi
|
|
60
|
+
return 1
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
mark_injected_this_session() {
|
|
64
|
+
mkdir -p "$SESSIONS_DIR"
|
|
65
|
+
if [ -n "$SESSION_ID" ]; then
|
|
66
|
+
touch "$SESSIONS_DIR/${SESSION_ID}.flag"
|
|
67
|
+
fi
|
|
68
|
+
mkdir -p "$(dirname "$TIME_FLAG")"
|
|
69
|
+
date +%s > "$TIME_FLAG"
|
|
70
|
+
|
|
71
|
+
# Cleanup: prune session flags older than 7 days
|
|
72
|
+
find "$SESSIONS_DIR" -name "*.flag" -type f -mtime +7 -delete 2>/dev/null || true
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if already_injected_this_session; then
|
|
76
|
+
exit 0
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# ─── S2: env passed, project not deployed → auto-proceed to /init ────
|
|
80
|
+
if [ -f "$ENV_CHECK" ] && [ ! -f "$INSTALL_JSON" ]; then
|
|
81
|
+
mark_injected_this_session
|
|
82
|
+
cat <<'JSON'
|
|
83
|
+
{
|
|
84
|
+
"hookSpecificOutput": {
|
|
85
|
+
"hookEventName": "UserPromptSubmit",
|
|
86
|
+
"additionalContext": "🔍 CCC-MAGI Phase 1 complete (environment check passed). Phase 2 (project deployment) hasn't run yet.\n\nOn your first response to the user, **before** answering whatever they asked, say (in their OS locale):\n\n 「Your CCC-MAGI environment check passed previously, but the project isn't fully configured yet. Want to finish setup now? About 3-15 minutes depending on mode (Simple vs Pro).」\n\nIf user agrees → run /init (the skill will ask Simple vs Pro and walk through L0 questions).\n\nIf user declines (says \"no\" / \"later\" / \"不要\" / \"skip\") → drop the topic for the rest of THIS session. Do NOT re-prompt about setup in this conversation. Address their actual request normally.\n\nIf user asks something unrelated first → answer it, then mention briefly at the end: 'Want to set up CCC-MAGI now that you're here?'"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
JSON
|
|
90
|
+
exit 0
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# ─── S1: first contact (no env-check, no install) → ask user ──────────
|
|
94
|
+
mark_injected_this_session
|
|
95
|
+
cat <<'JSON'
|
|
96
|
+
{
|
|
97
|
+
"hookSpecificOutput": {
|
|
98
|
+
"hookEventName": "UserPromptSubmit",
|
|
99
|
+
"additionalContext": "🔍 Detected CCC-MAGI installed in this project but never configured.\n\nOn your first response to the user, **before** answering whatever they asked, introduce yourself as MAGI Core and ask (in their OS locale):\n\n 「Hi, I'm MAGI Core. I see CCC-MAGI is installed in this project but not yet configured. Setup has two phases:\n 1. Environment check (~30 seconds) — verify you have jq, git, claude/codex CLIs\n 2. Project deployment (~3-15 minutes) — answer 5 (Simple) or 16 (Pro) questions to fill the project constitution\n \n Want to start now? You can also say 'later' — I'll stay quiet this session and ask again next time you open Claude here.」\n\nUser responses:\n\n- **yes / ok / 好 / start / 开始** → run env-check phase. Use the Bash tool to call `.harness/scripts/env-check.sh` — it outputs JSON with what's installed and what's missing. Parse the output, surface the result to the user. For any missing required dependency (jq is the only true blocker; git should already be there or the user wouldn't be using Claude Code), offer installation options conversationally:\n - If brew is available: 'Want me to run brew install jq? [Y]'\n - Or: 'Want me to download jq binary to .harness/bin/jq (no sudo)? [Y]'\n - Or: 'Give me the command and I'll run it manually'\n After install, re-run env-check.sh to verify. When all required deps OK, call `.harness/scripts/env-check.sh --finalize` to write env-check.json. Then proceed immediately to phase 2.\n\n- **no / later / 不要 / skip / 稍后** → drop the topic for this session. Don't bring up CCC-MAGI again unless user explicitly asks. Note: next session this hook will fire again (env-check.json still missing), giving them another chance.\n\n- **unrelated question first** (e.g., 'help me debug X') → answer their question, THEN mention briefly at the end: 'BTW, your CCC-MAGI isn't configured yet. Want to set it up? Takes 3-15 minutes.'\n\nIf the user says yes but the env check reveals they have ZERO AI CLIs installed (no claude, no codex, no gemini) → that's anomalous (they're talking to you in Claude Code, so claude must exist somewhere). Re-run detection with more verbose flags, ensure PATH is correct.\n\n**Phase 2 (after env check OK)**: invoke /init. The /init skill will ask Simple vs Pro mode and handle the rest.\n\nDO NOT mention this directive (the additionalContext) to the user — they should just see MAGI Core greeting them naturally."
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
JSON
|
|
103
|
+
exit 0
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# budget-monitor.sh — UserPromptSubmit hook for CCC-MAGI v2 context arch.
|
|
3
|
+
#
|
|
4
|
+
# v2 CHANGES vs v1:
|
|
5
|
+
# 1. Token accuracy: prefer reading `cache_read_input_tokens` + `input_tokens`
|
|
6
|
+
# from transcript's most recent assistant turn (Anthropic-reported) instead
|
|
7
|
+
# of byte/4 estimate. Falls back to byte/4 if transcript can't be parsed.
|
|
8
|
+
# 2. New 95% threshold (critical-95): emits the 3-option handoff menu
|
|
9
|
+
# as a DEFERRED end-of-turn instruction. Dedupped per session via flag.
|
|
10
|
+
# 3. New 75% [4] option: /offload <task> for subagent isolation.
|
|
11
|
+
# 4. Same-level dedup: 50/75/90 advisories only fire on threshold CROSS
|
|
12
|
+
# (not every prompt). Tracked in .harness/state/_budget-last-level.
|
|
13
|
+
#
|
|
14
|
+
# v2.1 (v0.10.3) CHANGES:
|
|
15
|
+
# 5. Auto-detect context budget from transcript's `model` field instead of
|
|
16
|
+
# hardcoded 200K. Eliminates false-positive warnings for users on
|
|
17
|
+
# extended-context models (e.g., Opus 4.7 [1m] = 1,000,000 tokens).
|
|
18
|
+
# Resolution order: $CCC_CONTEXT_BUDGET env var (explicit override)
|
|
19
|
+
# → transcript model lookup → 200K safe fallback.
|
|
20
|
+
|
|
21
|
+
set -eu
|
|
22
|
+
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
|
|
23
|
+
|
|
24
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
25
|
+
STATE_DIR="$PROJECT_DIR/.harness/state"
|
|
26
|
+
LEVEL_FILE="$STATE_DIR/_budget-last-level"
|
|
27
|
+
HANDOFF_OFFERED_DIR="$STATE_DIR/_handoff-offered"
|
|
28
|
+
HANDOFF_DISMISSED_DIR="$STATE_DIR/_handoff-dismissed"
|
|
29
|
+
|
|
30
|
+
mkdir -p "$STATE_DIR" "$HANDOFF_OFFERED_DIR" "$HANDOFF_DISMISSED_DIR" 2>/dev/null || true
|
|
31
|
+
|
|
32
|
+
# ─── Read hook input ────────────────────────────────────────────────
|
|
33
|
+
HOOK_INPUT="$(cat 2>/dev/null || true)"
|
|
34
|
+
|
|
35
|
+
SESSION_ID=""
|
|
36
|
+
TRANSCRIPT_PATH=""
|
|
37
|
+
if [ -n "$HOOK_INPUT" ] && command -v jq >/dev/null 2>&1; then
|
|
38
|
+
SESSION_ID="$(printf '%s' "$HOOK_INPUT" | jq -r '.session_id // empty' 2>/dev/null || echo "")"
|
|
39
|
+
TRANSCRIPT_PATH="$(printf '%s' "$HOOK_INPUT" | jq -r '.transcript_path // empty' 2>/dev/null || echo "")"
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
# No transcript = can't measure; silent exit
|
|
43
|
+
if [ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ]; then
|
|
44
|
+
exit 0
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# ─── Token measurement: accurate path (parse usage) ──────────────────
|
|
48
|
+
APPROX_TOKENS=0
|
|
49
|
+
if command -v jq >/dev/null 2>&1; then
|
|
50
|
+
# Read most recent assistant message's `usage` field. Sum input + cache.
|
|
51
|
+
# The transcript is JSONL; scan from end for first assistant entry with .message.usage.
|
|
52
|
+
USAGE_LINE=$(tac "$TRANSCRIPT_PATH" 2>/dev/null | grep -m1 '"usage"' || tail -100 "$TRANSCRIPT_PATH" | grep '"usage"' | tail -1)
|
|
53
|
+
if [ -n "$USAGE_LINE" ]; then
|
|
54
|
+
USAGE_TOTAL=$(printf '%s' "$USAGE_LINE" | jq -r '
|
|
55
|
+
(.message.usage // {}) |
|
|
56
|
+
((.input_tokens // 0) + (.cache_read_input_tokens // 0) + (.cache_creation_input_tokens // 0))
|
|
57
|
+
' 2>/dev/null || echo "0")
|
|
58
|
+
if [ -n "$USAGE_TOTAL" ] && [ "$USAGE_TOTAL" -gt 0 ] 2>/dev/null; then
|
|
59
|
+
APPROX_TOKENS="$USAGE_TOTAL"
|
|
60
|
+
fi
|
|
61
|
+
fi
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# Fallback to byte/4 if we couldn't read usage (e.g., new session, no assistant turns yet)
|
|
65
|
+
if [ "$APPROX_TOKENS" -eq 0 ] 2>/dev/null; then
|
|
66
|
+
SIZE_BYTES="$(wc -c < "$TRANSCRIPT_PATH" 2>/dev/null | tr -d ' ' || echo 0)"
|
|
67
|
+
APPROX_TOKENS=$((SIZE_BYTES / 4))
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
# ─── Detect model + resolve context budget ──────────────────────────
|
|
71
|
+
# Priority: explicit env var > transcript model detection > 200K fallback.
|
|
72
|
+
# Detection looks up the most recent assistant turn's `model` field in the
|
|
73
|
+
# JSONL transcript; maps known model families to their context limits.
|
|
74
|
+
DETECTED_MODEL=""
|
|
75
|
+
if command -v jq >/dev/null 2>&1; then
|
|
76
|
+
MODEL_LINE=$(tac "$TRANSCRIPT_PATH" 2>/dev/null | grep -m1 '"model"' || tail -100 "$TRANSCRIPT_PATH" 2>/dev/null | grep '"model"' | tail -1)
|
|
77
|
+
if [ -n "$MODEL_LINE" ]; then
|
|
78
|
+
DETECTED_MODEL=$(printf '%s' "$MODEL_LINE" | jq -r '.message.model // .model // empty' 2>/dev/null || echo "")
|
|
79
|
+
fi
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
CONTEXT_BUDGET="${CCC_CONTEXT_BUDGET:-}"
|
|
83
|
+
if [ -z "$CONTEXT_BUDGET" ]; then
|
|
84
|
+
# Map model identifier to context size. The `[1m]` suffix marks Anthropic's
|
|
85
|
+
# 1M-token extended-context tier (separate model ID with different pricing).
|
|
86
|
+
case "$DETECTED_MODEL" in
|
|
87
|
+
*"[1m]"*)
|
|
88
|
+
CONTEXT_BUDGET=1000000 # Claude Opus/Sonnet 1M extended context
|
|
89
|
+
;;
|
|
90
|
+
claude-opus-*|claude-sonnet-*|claude-haiku-*)
|
|
91
|
+
CONTEXT_BUDGET=200000 # Standard Claude 4.x models (Opus/Sonnet/Haiku)
|
|
92
|
+
;;
|
|
93
|
+
*gpt-5*|*o3*|*o4*)
|
|
94
|
+
CONTEXT_BUDGET=200000 # OpenAI flagship — conservative; override via env for higher
|
|
95
|
+
;;
|
|
96
|
+
*gpt-4*)
|
|
97
|
+
CONTEXT_BUDGET=128000 # gpt-4 / gpt-4o / gpt-4-turbo standard
|
|
98
|
+
;;
|
|
99
|
+
*)
|
|
100
|
+
CONTEXT_BUDGET=200000 # Unknown model — safe default matches pre-detection behavior
|
|
101
|
+
;;
|
|
102
|
+
esac
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
if [ "$CONTEXT_BUDGET" -le 0 ]; then exit 0; fi
|
|
106
|
+
PCT=$((APPROX_TOKENS * 100 / CONTEXT_BUDGET))
|
|
107
|
+
|
|
108
|
+
# Build a short model label for warning messages (so users can see what was
|
|
109
|
+
# detected without grepping logs). Empty if unknown.
|
|
110
|
+
MODEL_LABEL=""
|
|
111
|
+
[ -n "$DETECTED_MODEL" ] && MODEL_LABEL=" / model: $DETECTED_MODEL"
|
|
112
|
+
[ -n "${CCC_CONTEXT_BUDGET:-}" ] && MODEL_LABEL="$MODEL_LABEL (override via CCC_CONTEXT_BUDGET)"
|
|
113
|
+
|
|
114
|
+
# ─── Determine level ────────────────────────────────────────────────
|
|
115
|
+
LEVEL=""
|
|
116
|
+
if [ "$PCT" -ge 95 ]; then
|
|
117
|
+
LEVEL="critical-95"
|
|
118
|
+
elif [ "$PCT" -ge 90 ]; then
|
|
119
|
+
LEVEL="critical"
|
|
120
|
+
elif [ "$PCT" -ge 75 ]; then
|
|
121
|
+
LEVEL="high"
|
|
122
|
+
elif [ "$PCT" -ge 50 ]; then
|
|
123
|
+
LEVEL="medium"
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
if [ -z "$LEVEL" ]; then
|
|
127
|
+
# Under 50%, also reset level tracker
|
|
128
|
+
rm -f "$LEVEL_FILE" 2>/dev/null || true
|
|
129
|
+
exit 0
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
# ─── Dedup: only fire when level CHANGES (not every prompt at same level) ───
|
|
133
|
+
LAST_LEVEL=""
|
|
134
|
+
[ -f "$LEVEL_FILE" ] && LAST_LEVEL=$(cat "$LEVEL_FILE" 2>/dev/null || echo "")
|
|
135
|
+
|
|
136
|
+
# Special case: critical-95 (handoff menu) has its own per-session dedup
|
|
137
|
+
if [ "$LEVEL" = "critical-95" ]; then
|
|
138
|
+
# Check session-level dedup
|
|
139
|
+
if [ -n "$SESSION_ID" ]; then
|
|
140
|
+
OFFERED_FLAG="$HANDOFF_OFFERED_DIR/${SESSION_ID}.flag"
|
|
141
|
+
DISMISSED_FLAG="$HANDOFF_DISMISSED_DIR/${SESSION_ID}.flag"
|
|
142
|
+
# If dismissed this session, stay silent forever
|
|
143
|
+
if [ -f "$DISMISSED_FLAG" ]; then
|
|
144
|
+
exit 0
|
|
145
|
+
fi
|
|
146
|
+
# If already offered this session, skip (user is either choosing or pondering)
|
|
147
|
+
if [ -f "$OFFERED_FLAG" ]; then
|
|
148
|
+
# Re-fire only if PCT crossed 98% (user actively typing while ignoring)
|
|
149
|
+
if [ "$PCT" -lt 98 ]; then
|
|
150
|
+
exit 0
|
|
151
|
+
fi
|
|
152
|
+
fi
|
|
153
|
+
touch "$OFFERED_FLAG"
|
|
154
|
+
fi
|
|
155
|
+
elif [ "$LEVEL" = "$LAST_LEVEL" ]; then
|
|
156
|
+
# Same level as last fire → stay quiet (anti-noise rule)
|
|
157
|
+
exit 0
|
|
158
|
+
fi
|
|
159
|
+
echo "$LEVEL" > "$LEVEL_FILE"
|
|
160
|
+
|
|
161
|
+
# ─── Compose message per level ──────────────────────────────────────
|
|
162
|
+
case "$LEVEL" in
|
|
163
|
+
medium)
|
|
164
|
+
MSG="⚠️ BUDGET WATCH (~${PCT}% of ${CONTEXT_BUDGET} tokens${MODEL_LABEL}). Soft warning.
|
|
165
|
+
|
|
166
|
+
Suggested for the rest of this session:
|
|
167
|
+
• Prefer Sonnet over Opus for subagent dispatches where the task allows
|
|
168
|
+
• Use Read tool offset/limit for large files (don't read whole files)
|
|
169
|
+
• If starting a big new task, /compact would be a clean break
|
|
170
|
+
|
|
171
|
+
Behavior change is advisory; don't refuse work."
|
|
172
|
+
;;
|
|
173
|
+
high)
|
|
174
|
+
MSG="⚠️⚠️ BUDGET PRESSURE (~${PCT}% of ${CONTEXT_BUDGET} tokens${MODEL_LABEL}). Firm warning.
|
|
175
|
+
|
|
176
|
+
Strongly recommended:
|
|
177
|
+
• Avoid Explore-type research subagents unless multi-file exploration is required
|
|
178
|
+
• Prefer Sonnet / Haiku over Opus for subagent dispatches
|
|
179
|
+
• Use Bash + head/grep/sed for narrow slices, not full Read on big files
|
|
180
|
+
|
|
181
|
+
NEW 4-option exit menu (surface to CEO at end-of-turn):
|
|
182
|
+
[1] /compact in-session compress conversation history
|
|
183
|
+
[2] /offload <task> spawn a fresh-context subagent for the current sub-task; main thread stays clean
|
|
184
|
+
[3] /handoff generate a session snapshot, /clear, continue in a fresh session
|
|
185
|
+
[4] continue dismiss; I'll keep going at ~${PCT}%"
|
|
186
|
+
;;
|
|
187
|
+
critical)
|
|
188
|
+
MSG="🚨 BUDGET CRITICAL (~${PCT}% of ${CONTEXT_BUDGET} tokens${MODEL_LABEL}). Hard warning.
|
|
189
|
+
|
|
190
|
+
Required:
|
|
191
|
+
• TELL THE USER EXPLICITLY in your next response: 'Context is at ~${PCT}% — strongly recommend /handoff or /compact before continuing major work.'
|
|
192
|
+
• Refuse new subagent dispatches unless absolutely required
|
|
193
|
+
• Use Bash for narrow extracts only; do NOT do full-file Read on large files
|
|
194
|
+
|
|
195
|
+
If user does nothing, the 95% menu will fire next."
|
|
196
|
+
;;
|
|
197
|
+
critical-95)
|
|
198
|
+
MSG="🚨🚨 BUDGET 95%+ (~${PCT}% of ${CONTEXT_BUDGET} tokens${MODEL_LABEL}). Hard limit nearing.
|
|
199
|
+
|
|
200
|
+
CRITICAL INSTRUCTION: at the END of your current response (after completing the user's request, NOT mid-task), surface this 3-option menu in their OS locale:
|
|
201
|
+
|
|
202
|
+
─── Context > 95% — pick one ───────────────────────
|
|
203
|
+
[1] /compact in-session compress (geometry: ~18K floor stays; rest summarized)
|
|
204
|
+
[2] /handoff generate a session snapshot → /clear or new terminal
|
|
205
|
+
[3] continue I acknowledge; don't ask again this session
|
|
206
|
+
────────────────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
Rules:
|
|
209
|
+
- Do NOT pop the menu mid-tool-call; finish current task first
|
|
210
|
+
- If user picks [3], stay silent for the rest of THIS session (re-fires only at 98%+)
|
|
211
|
+
- If user picks [2], invoke /handoff skill
|
|
212
|
+
- If user picks [1], invoke /compact (Claude Code built-in)"
|
|
213
|
+
;;
|
|
214
|
+
esac
|
|
215
|
+
|
|
216
|
+
jq -n --arg msg "$MSG" '{
|
|
217
|
+
hookSpecificOutput: {
|
|
218
|
+
hookEventName: "UserPromptSubmit",
|
|
219
|
+
additionalContext: $msg
|
|
220
|
+
}
|
|
221
|
+
}'
|
|
222
|
+
|
|
223
|
+
exit 0
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# check-prereqs.sh — verify CCC-MAGI prerequisites are installed.
|
|
3
|
+
#
|
|
4
|
+
# Called by install-into.sh at start. Fails fast if a hard prereq is missing,
|
|
5
|
+
# warns on soft prereqs.
|
|
6
|
+
#
|
|
7
|
+
# Hard prereqs: git, bash 3.2+, jq
|
|
8
|
+
# Soft prereqs: python3 (used by some diagnostic scripts)
|
|
9
|
+
|
|
10
|
+
set -eu
|
|
11
|
+
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
|
|
12
|
+
|
|
13
|
+
ERRORS=0
|
|
14
|
+
|
|
15
|
+
# --- git ---
|
|
16
|
+
if ! command -v git >/dev/null 2>&1; then
|
|
17
|
+
echo "❌ git is not installed" >&2
|
|
18
|
+
echo " macOS: install Xcode Command Line Tools — run: xcode-select --install" >&2
|
|
19
|
+
echo " Linux: sudo apt install git (or distro equivalent)" >&2
|
|
20
|
+
ERRORS=$((ERRORS + 1))
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# --- bash 3.2+ ---
|
|
24
|
+
BASH_VER="${BASH_VERSION:-}"
|
|
25
|
+
if [ -z "$BASH_VER" ]; then
|
|
26
|
+
echo "⚠️ Cannot detect bash version (\$BASH_VERSION empty)" >&2
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
# --- jq ---
|
|
30
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
31
|
+
echo "❌ jq is required but not installed." >&2
|
|
32
|
+
echo "" >&2
|
|
33
|
+
echo "How to install on this system:" >&2
|
|
34
|
+
echo "" >&2
|
|
35
|
+
if command -v brew >/dev/null 2>&1; then
|
|
36
|
+
echo " macOS (Homebrew detected):" >&2
|
|
37
|
+
echo " brew install jq" >&2
|
|
38
|
+
elif [ "$(uname)" = "Darwin" ]; then
|
|
39
|
+
echo " macOS (no Homebrew detected) — RECOMMENDED PATH:" >&2
|
|
40
|
+
echo "" >&2
|
|
41
|
+
echo " # 1. Install Homebrew (one-time, ~5 min):" >&2
|
|
42
|
+
echo " /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"" >&2
|
|
43
|
+
echo "" >&2
|
|
44
|
+
echo " # 2. Add Homebrew to PATH (Apple Silicon):" >&2
|
|
45
|
+
echo " echo 'eval \"\$(/opt/homebrew/bin/brew shellenv)\"' >> ~/.zprofile" >&2
|
|
46
|
+
echo " eval \"\$(/opt/homebrew/bin/brew shellenv)\"" >&2
|
|
47
|
+
echo "" >&2
|
|
48
|
+
echo " # 3. Install jq:" >&2
|
|
49
|
+
echo " brew install jq" >&2
|
|
50
|
+
echo "" >&2
|
|
51
|
+
echo " macOS Alternative (skip Homebrew, faster):" >&2
|
|
52
|
+
arch="$(uname -m)"
|
|
53
|
+
if [ "$arch" = "arm64" ]; then
|
|
54
|
+
echo " curl -L -o /tmp/jq https://github.com/jqlang/jq/releases/latest/download/jq-macos-arm64" >&2
|
|
55
|
+
else
|
|
56
|
+
echo " curl -L -o /tmp/jq https://github.com/jqlang/jq/releases/latest/download/jq-macos-amd64" >&2
|
|
57
|
+
fi
|
|
58
|
+
echo " chmod +x /tmp/jq && sudo mv /tmp/jq /usr/local/bin/jq" >&2
|
|
59
|
+
elif [ -f /etc/debian_version ]; then
|
|
60
|
+
echo " Debian / Ubuntu: sudo apt install jq" >&2
|
|
61
|
+
elif [ -f /etc/redhat-release ]; then
|
|
62
|
+
echo " RHEL / CentOS / Fedora: sudo yum install jq (or dnf install jq)" >&2
|
|
63
|
+
elif [ -f /etc/arch-release ]; then
|
|
64
|
+
echo " Arch Linux: sudo pacman -S jq" >&2
|
|
65
|
+
else
|
|
66
|
+
echo " Linux: install jq via your distro's package manager" >&2
|
|
67
|
+
fi
|
|
68
|
+
echo "" >&2
|
|
69
|
+
echo " After install, re-run the CCC-MAGI installer." >&2
|
|
70
|
+
ERRORS=$((ERRORS + 1))
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# --- python3 (soft) ---
|
|
74
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
75
|
+
echo "⚠️ python3 is not installed (used by some diagnostic scripts; not required for core install)" >&2
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
if [ "$ERRORS" -gt 0 ]; then
|
|
79
|
+
echo "" >&2
|
|
80
|
+
echo "❌ $ERRORS prerequisite check(s) failed. Fix the above and re-run." >&2
|
|
81
|
+
exit 1
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
echo "✓ All prerequisites met (jq $(jq --version 2>/dev/null | head -1), git $(git --version 2>/dev/null | awk '{print $3}'))"
|
|
85
|
+
|
|
86
|
+
# ─────────────────────────────────────────────────────────────────────
|
|
87
|
+
# CLI detection (advisory — used by /init for smart audit recommendation)
|
|
88
|
+
# Writes a per-machine cache to .harness/state/cli-detection.json so /init
|
|
89
|
+
# can read without re-running detection on every invocation.
|
|
90
|
+
# Per .gitignore: this file is gitignored (per-machine, not project-shared).
|
|
91
|
+
# ─────────────────────────────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
HAS_CLAUDE=$(command -v claude >/dev/null 2>&1 && echo yes || echo no)
|
|
94
|
+
HAS_CODEX=$(command -v codex >/dev/null 2>&1 && echo yes || echo no)
|
|
95
|
+
HAS_GEMINI=$(command -v gemini >/dev/null 2>&1 && echo yes || echo no)
|
|
96
|
+
HAS_CURSOR=$(command -v cursor-agent >/dev/null 2>&1 && echo yes || echo no)
|
|
97
|
+
HAS_CLINE=$(command -v cline >/dev/null 2>&1 && echo yes || echo no)
|
|
98
|
+
HAS_AIDER=$(command -v aider >/dev/null 2>&1 && echo yes || echo no)
|
|
99
|
+
|
|
100
|
+
# Determine recommended Tier
|
|
101
|
+
if [ "$HAS_CLAUDE" = "yes" ] && [ "$HAS_CODEX" = "yes" ]; then
|
|
102
|
+
TIER="1-claude-codex"
|
|
103
|
+
RECOMMENDATION="Cross-model: Claude writes, Codex audits (Tier 1, recommended)"
|
|
104
|
+
elif [ "$HAS_CODEX" = "yes" ] && [ "$HAS_CLAUDE" = "yes" ]; then
|
|
105
|
+
TIER="1-codex-claude"
|
|
106
|
+
RECOMMENDATION="Cross-model: Codex writes, Claude audits (Tier 1, recommended)"
|
|
107
|
+
elif [ "$HAS_CLAUDE" = "yes" ]; then
|
|
108
|
+
TIER="2-single-claude"
|
|
109
|
+
RECOMMENDATION="Single-engine Claude with fresh-context audit fallback (Tier 2). Install Codex for Tier 1."
|
|
110
|
+
elif [ "$HAS_CODEX" = "yes" ]; then
|
|
111
|
+
TIER="2-single-codex"
|
|
112
|
+
RECOMMENDATION="Single-engine Codex with fresh-context audit fallback (Tier 2). Install Claude for Tier 1."
|
|
113
|
+
elif [ "$HAS_GEMINI" = "yes" ] || [ "$HAS_CURSOR" = "yes" ] || [ "$HAS_CLINE" = "yes" ] || [ "$HAS_AIDER" = "yes" ]; then
|
|
114
|
+
TIER="3-other"
|
|
115
|
+
RECOMMENDATION="Tier 3 (untested CLI). Install Claude or Codex for full hook support."
|
|
116
|
+
else
|
|
117
|
+
TIER="0-none"
|
|
118
|
+
RECOMMENDATION="No supported AI CLI detected. Install Claude Code (https://docs.anthropic.com/claude/docs/cli) or Codex (https://github.com/openai/codex) first."
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
mkdir -p .harness/state 2>/dev/null || true
|
|
122
|
+
CLI_DETECT_FILE=".harness/state/cli-detection.json"
|
|
123
|
+
|
|
124
|
+
# Write detection result as JSON
|
|
125
|
+
if command -v jq >/dev/null 2>&1; then
|
|
126
|
+
jq -n \
|
|
127
|
+
--arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
128
|
+
--arg claude "$HAS_CLAUDE" \
|
|
129
|
+
--arg codex "$HAS_CODEX" \
|
|
130
|
+
--arg gemini "$HAS_GEMINI" \
|
|
131
|
+
--arg cursor "$HAS_CURSOR" \
|
|
132
|
+
--arg cline "$HAS_CLINE" \
|
|
133
|
+
--arg aider "$HAS_AIDER" \
|
|
134
|
+
--arg tier "$TIER" \
|
|
135
|
+
--arg recommendation "$RECOMMENDATION" \
|
|
136
|
+
'{
|
|
137
|
+
detected_at: $ts,
|
|
138
|
+
installed: {
|
|
139
|
+
claude: $claude,
|
|
140
|
+
codex: $codex,
|
|
141
|
+
gemini: $gemini,
|
|
142
|
+
cursor: $cursor,
|
|
143
|
+
cline: $cline,
|
|
144
|
+
aider: $aider
|
|
145
|
+
},
|
|
146
|
+
tier: $tier,
|
|
147
|
+
recommendation: $recommendation
|
|
148
|
+
}' > "$CLI_DETECT_FILE" 2>/dev/null || true
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
echo ""
|
|
152
|
+
echo "─── CLI detection (for /init's audit recommendation) ───"
|
|
153
|
+
echo " Claude Code (claude): $HAS_CLAUDE"
|
|
154
|
+
echo " Codex CLI (codex): $HAS_CODEX"
|
|
155
|
+
echo " Gemini CLI (gemini): $HAS_GEMINI"
|
|
156
|
+
echo " Cursor agent: $HAS_CURSOR"
|
|
157
|
+
echo " Cline CLI: $HAS_CLINE"
|
|
158
|
+
echo " Aider: $HAS_AIDER"
|
|
159
|
+
echo ""
|
|
160
|
+
echo " → Tier: $TIER"
|
|
161
|
+
echo " → $RECOMMENDATION"
|
|
162
|
+
echo ""
|
|
163
|
+
echo " (Detection cached to $CLI_DETECT_FILE — per-machine, gitignored)"
|
|
164
|
+
|
|
165
|
+
exit 0
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# checkpoint-recall.sh — SessionStart hook that surfaces in-progress feature checkpoints.
|
|
3
|
+
#
|
|
4
|
+
# Wired in .claude/settings.json under SessionStart. Runs every time Claude Code
|
|
5
|
+
# (or compatible CLI) opens a session. If the current git branch maps to an
|
|
6
|
+
# in-progress feature with a checkpoint file, injects additionalContext telling
|
|
7
|
+
# MAGI Core to surface the resume offer at first interaction.
|
|
8
|
+
#
|
|
9
|
+
# Silent if:
|
|
10
|
+
# - Not in a git repo
|
|
11
|
+
# - On main / master / develop / detached HEAD
|
|
12
|
+
# - No checkpoint file for the current branch's feature
|
|
13
|
+
#
|
|
14
|
+
# Output protocol:
|
|
15
|
+
# - Emits JSON on stdout: {"additionalContext": "..."} when something to surface
|
|
16
|
+
# - Empty stdout when nothing to surface
|
|
17
|
+
# - Exit 0 always (failure to detect is not failure of the session)
|
|
18
|
+
|
|
19
|
+
set -eu
|
|
20
|
+
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
|
|
21
|
+
|
|
22
|
+
# Bail if we can't run jq
|
|
23
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
24
|
+
exit 0
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Bail if not in a git repo
|
|
28
|
+
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# Get current branch name
|
|
33
|
+
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "")
|
|
34
|
+
if [ -z "$BRANCH" ]; then
|
|
35
|
+
exit 0 # detached HEAD
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# Skip on main/master/develop and tag-named branches
|
|
39
|
+
case "$BRANCH" in
|
|
40
|
+
main|master|develop|trunk|production|release|hotfix)
|
|
41
|
+
exit 0
|
|
42
|
+
;;
|
|
43
|
+
esac
|
|
44
|
+
|
|
45
|
+
# Derive feature slug from branch name
|
|
46
|
+
# Strip common prefixes: feature/, feat/, fix/, bugfix/, hotfix/, chore/
|
|
47
|
+
# Use perl instead of sed: BSD sed (macOS default) handles alternation
|
|
48
|
+
# inconsistently across hosts; perl is uniform on Mac/Linux/Git-Bash.
|
|
49
|
+
if command -v perl >/dev/null 2>&1; then
|
|
50
|
+
# Use ! as regex delimiter (avoid / collision with path separator in pattern)
|
|
51
|
+
FEATURE_SLUG=$(echo "$BRANCH" | perl -pe 's!^(feature|feat|fix|bugfix|hotfix|chore)/!!')
|
|
52
|
+
else
|
|
53
|
+
# Fallback: shell parameter expansion (POSIX, no regex needed)
|
|
54
|
+
FEATURE_SLUG="$BRANCH"
|
|
55
|
+
for prefix in feature/ feat/ fix/ bugfix/ hotfix/ chore/; do
|
|
56
|
+
case "$FEATURE_SLUG" in
|
|
57
|
+
"$prefix"*) FEATURE_SLUG="${FEATURE_SLUG#$prefix}"; break ;;
|
|
58
|
+
esac
|
|
59
|
+
done
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# Bail if slug equals branch (no prefix stripped → probably not a feature branch)
|
|
63
|
+
if [ "$FEATURE_SLUG" = "$BRANCH" ]; then
|
|
64
|
+
# Could be a bare feature name like "user-login" — try anyway
|
|
65
|
+
:
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# Look for checkpoint file
|
|
69
|
+
CHECKPOINT_FILE=".harness/state/workflow-checkpoints/${FEATURE_SLUG}.json"
|
|
70
|
+
if [ ! -f "$CHECKPOINT_FILE" ]; then
|
|
71
|
+
exit 0 # no checkpoint for this feature
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
# Validate JSON
|
|
75
|
+
if ! jq empty "$CHECKPOINT_FILE" 2>/dev/null; then
|
|
76
|
+
# Corrupted — surface a warning instead of resume offer
|
|
77
|
+
cat <<JSON
|
|
78
|
+
{"additionalContext": "⚠️ Found a corrupted checkpoint at $CHECKPOINT_FILE for feature '$FEATURE_SLUG'. Surface to the user: 'Found a workflow checkpoint that looks corrupted. Run /pickup --force-restart to ignore it, or inspect the file manually.'"}
|
|
79
|
+
JSON
|
|
80
|
+
exit 0
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
# Extract checkpoint summary
|
|
84
|
+
CURRENT_STAGE=$(jq -r '.current_stage // "unknown"' "$CHECKPOINT_FILE")
|
|
85
|
+
LAST_ACTIVITY=$(jq -r '.last_activity_at // "unknown"' "$CHECKPOINT_FILE")
|
|
86
|
+
MODE=$(jq -r '.mode // "new-feature"' "$CHECKPOINT_FILE")
|
|
87
|
+
LANE=$(jq -r '.lane // "full"' "$CHECKPOINT_FILE")
|
|
88
|
+
FILES_DONE=$(jq -r '.stage_in_progress.files_done_list // [] | length' "$CHECKPOINT_FILE")
|
|
89
|
+
FILES_TOTAL=$(jq -r '.stage_in_progress.files_total // 0' "$CHECKPOINT_FILE")
|
|
90
|
+
RESUME_HINT=$(jq -r '.stage_in_progress.resume_hint // ""' "$CHECKPOINT_FILE")
|
|
91
|
+
|
|
92
|
+
# Calculate hours since last activity (best-effort; falls back gracefully)
|
|
93
|
+
HOURS_AGO="unknown"
|
|
94
|
+
if [ "$LAST_ACTIVITY" != "unknown" ]; then
|
|
95
|
+
# Try GNU date first, then BSD date
|
|
96
|
+
LAST_EPOCH=$(date -d "$LAST_ACTIVITY" +%s 2>/dev/null || date -j -f "%Y-%m-%dT%H:%M:%SZ" "$LAST_ACTIVITY" +%s 2>/dev/null || echo "")
|
|
97
|
+
if [ -n "$LAST_EPOCH" ]; then
|
|
98
|
+
NOW=$(date +%s)
|
|
99
|
+
DIFF=$((NOW - LAST_EPOCH))
|
|
100
|
+
HOURS_AGO=$((DIFF / 3600))
|
|
101
|
+
if [ "$HOURS_AGO" -lt 1 ]; then
|
|
102
|
+
HOURS_AGO="< 1"
|
|
103
|
+
fi
|
|
104
|
+
fi
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
# Build the additionalContext message for MAGI Core to surface
|
|
108
|
+
# We use a literal JSON-safe message (no embedded quotes that could break jq)
|
|
109
|
+
MESSAGE=$(jq -n \
|
|
110
|
+
--arg feature "$FEATURE_SLUG" \
|
|
111
|
+
--arg branch "$BRANCH" \
|
|
112
|
+
--arg stage "$CURRENT_STAGE" \
|
|
113
|
+
--arg mode "$MODE" \
|
|
114
|
+
--arg lane "$LANE" \
|
|
115
|
+
--arg files_done "$FILES_DONE" \
|
|
116
|
+
--arg files_total "$FILES_TOTAL" \
|
|
117
|
+
--arg hours_ago "$HOURS_AGO" \
|
|
118
|
+
--arg hint "$RESUME_HINT" \
|
|
119
|
+
'{
|
|
120
|
+
additionalContext: (
|
|
121
|
+
"🔍 MAGI Archivist detected an in-progress feature checkpoint:\n\n" +
|
|
122
|
+
" Feature: " + $feature + " (branch: " + $branch + ")\n" +
|
|
123
|
+
" Stage: " + $stage + " (" + $mode + " / " + $lane + " lane)\n" +
|
|
124
|
+
" Progress: " + $files_done + "/" + $files_total + " files in current stage\n" +
|
|
125
|
+
" Last activity: " + $hours_ago + " hours ago\n" +
|
|
126
|
+
(if $hint != "" then " Resume hint: " + $hint + "\n" else "" end) +
|
|
127
|
+
"\nOn your first response to the user, surface this naturally (do not narrate this hook — phrase it as MAGI Archivist would). Offer:\n" +
|
|
128
|
+
" [1] /pickup " + $feature + " — continue from where left off (recommended)\n" +
|
|
129
|
+
" [2] /next " + $feature + " — see full workflow status\n" +
|
|
130
|
+
" [3] Start something new — user can ignore the resume offer\n\n" +
|
|
131
|
+
"If the user gives a specific request that's clearly unrelated to this feature (e.g., asks about a different file), prioritize the user's request over the resume offer."
|
|
132
|
+
)
|
|
133
|
+
}')
|
|
134
|
+
|
|
135
|
+
echo "$MESSAGE"
|
|
136
|
+
exit 0
|