anvil-dev-framework 0.1.6
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 +719 -0
- package/VERSION +1 -0
- package/docs/ANVIL-REPO-IMPLEMENTATION-PLAN.md +441 -0
- package/docs/FIRST-SKILL-TUTORIAL.md +408 -0
- package/docs/INSTALLATION-RETRO-NOTES.md +458 -0
- package/docs/INSTALLATION.md +984 -0
- package/docs/anvil-hud.md +469 -0
- package/docs/anvil-init.md +255 -0
- package/docs/anvil-state.md +210 -0
- package/docs/boris-cherny-ralph-wiggum-insights.md +608 -0
- package/docs/command-reference.md +2022 -0
- package/docs/hooks-tts.md +368 -0
- package/docs/implementation-guide.md +810 -0
- package/docs/linear-github-integration.md +247 -0
- package/docs/local-issues.md +677 -0
- package/docs/patterns/README.md +419 -0
- package/docs/planning-responsibilities.md +139 -0
- package/docs/session-workflow.md +573 -0
- package/docs/simplification-plan-template.md +297 -0
- package/docs/simplification-principles.md +129 -0
- package/docs/specifications/CCS-RALPH-INTEGRATION-DESIGN.md +633 -0
- package/docs/specifications/CCS-RESEARCH-REPORT.md +169 -0
- package/docs/specifications/PLAN-ANV-verification-ralph-wiggum.md +403 -0
- package/docs/specifications/PLAN-parallel-tracks-anvil-memory-ccs.md +494 -0
- package/docs/specifications/SPEC-ANV-VRW/component-01-verify.md +208 -0
- package/docs/specifications/SPEC-ANV-VRW/component-02-stop-gate.md +226 -0
- package/docs/specifications/SPEC-ANV-VRW/component-03-posttooluse.md +209 -0
- package/docs/specifications/SPEC-ANV-VRW/component-04-ralph-wiggum.md +604 -0
- package/docs/specifications/SPEC-ANV-VRW/component-05-atomic-actions.md +311 -0
- package/docs/specifications/SPEC-ANV-VRW/component-06-verify-subagent.md +264 -0
- package/docs/specifications/SPEC-ANV-VRW/component-07-claude-md.md +363 -0
- package/docs/specifications/SPEC-ANV-VRW/index.md +182 -0
- package/docs/specifications/SPEC-ANV-anvil-memory.md +573 -0
- package/docs/specifications/SPEC-ANV-context-checkpoints.md +781 -0
- package/docs/specifications/SPEC-ANV-verification-ralph-wiggum.md +789 -0
- package/docs/sync.md +122 -0
- package/global/CLAUDE.md +140 -0
- package/global/agents/verify-app.md +164 -0
- package/global/commands/anvil-settings.md +527 -0
- package/global/commands/anvil-sync.md +121 -0
- package/global/commands/change.md +197 -0
- package/global/commands/clarify.md +252 -0
- package/global/commands/cleanup.md +292 -0
- package/global/commands/commit-push-pr.md +207 -0
- package/global/commands/decay-review.md +127 -0
- package/global/commands/discover.md +158 -0
- package/global/commands/doc-coverage.md +122 -0
- package/global/commands/evidence.md +307 -0
- package/global/commands/explore.md +121 -0
- package/global/commands/force-exit.md +135 -0
- package/global/commands/handoff.md +191 -0
- package/global/commands/healthcheck.md +302 -0
- package/global/commands/hud.md +84 -0
- package/global/commands/insights.md +319 -0
- package/global/commands/linear-setup.md +184 -0
- package/global/commands/lint-fix.md +198 -0
- package/global/commands/orient.md +510 -0
- package/global/commands/plan.md +228 -0
- package/global/commands/ralph.md +346 -0
- package/global/commands/ready.md +182 -0
- package/global/commands/release.md +305 -0
- package/global/commands/retro.md +96 -0
- package/global/commands/shard.md +166 -0
- package/global/commands/spec.md +227 -0
- package/global/commands/sprint.md +184 -0
- package/global/commands/tasks.md +228 -0
- package/global/commands/test-and-commit.md +151 -0
- package/global/commands/validate.md +132 -0
- package/global/commands/verify.md +251 -0
- package/global/commands/weekly-review.md +156 -0
- package/global/hooks/__pycache__/ralph_context_monitor.cpython-314.pyc +0 -0
- package/global/hooks/__pycache__/statusline_agent_sync.cpython-314.pyc +0 -0
- package/global/hooks/anvil_memory_observe.ts +322 -0
- package/global/hooks/anvil_memory_session.ts +166 -0
- package/global/hooks/anvil_memory_stop.ts +187 -0
- package/global/hooks/parse_transcript.py +116 -0
- package/global/hooks/post_merge_cleanup.sh +132 -0
- package/global/hooks/post_tool_format.sh +215 -0
- package/global/hooks/ralph_context_monitor.py +240 -0
- package/global/hooks/ralph_stop.sh +502 -0
- package/global/hooks/statusline.sh +1110 -0
- package/global/hooks/statusline_agent_sync.py +224 -0
- package/global/hooks/stop_gate.sh +250 -0
- package/global/lib/.claude/anvil-state.json +21 -0
- package/global/lib/__pycache__/agent_registry.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/claim_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/coderabbit_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/config_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/coordination_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/doc_coverage_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/gate_logger.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/github_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/hygiene_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/issue_models.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/issue_provider.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/linear_data_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/linear_provider.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/local_provider.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/quality_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/ralph_state.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/state_manager.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/transcript_parser.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/verification_runner.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/verify_iteration.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/verify_subagent.cpython-314.pyc +0 -0
- package/global/lib/agent_registry.py +995 -0
- package/global/lib/anvil-state.sh +435 -0
- package/global/lib/claim_service.py +515 -0
- package/global/lib/coderabbit_service.py +314 -0
- package/global/lib/config_service.py +423 -0
- package/global/lib/coordination_service.py +331 -0
- package/global/lib/doc_coverage_service.py +1305 -0
- package/global/lib/gate_logger.py +316 -0
- package/global/lib/github_service.py +310 -0
- package/global/lib/handoff_generator.py +775 -0
- package/global/lib/hygiene_service.py +712 -0
- package/global/lib/issue_models.py +257 -0
- package/global/lib/issue_provider.py +339 -0
- package/global/lib/linear_data_service.py +210 -0
- package/global/lib/linear_provider.py +987 -0
- package/global/lib/linear_provider.py.backup +671 -0
- package/global/lib/local_provider.py +486 -0
- package/global/lib/orient_fast.py +457 -0
- package/global/lib/quality_service.py +470 -0
- package/global/lib/ralph_prompt_generator.py +563 -0
- package/global/lib/ralph_state.py +1202 -0
- package/global/lib/state_manager.py +417 -0
- package/global/lib/transcript_parser.py +597 -0
- package/global/lib/verification_runner.py +557 -0
- package/global/lib/verify_iteration.py +490 -0
- package/global/lib/verify_subagent.py +250 -0
- package/global/skills/README.md +155 -0
- package/global/skills/quality-gates/SKILL.md +252 -0
- package/global/skills/skill-template/SKILL.md +109 -0
- package/global/skills/testing-strategies/SKILL.md +337 -0
- package/global/templates/CHANGE-template.md +105 -0
- package/global/templates/HANDOFF-template.md +63 -0
- package/global/templates/PLAN-template.md +111 -0
- package/global/templates/SPEC-template.md +93 -0
- package/global/templates/ralph/PROMPT.md.template +89 -0
- package/global/templates/ralph/fix_plan.md.template +31 -0
- package/global/templates/ralph/progress.txt.template +23 -0
- package/global/tests/__pycache__/test_doc_coverage.cpython-314.pyc +0 -0
- package/global/tests/test_doc_coverage.py +520 -0
- package/global/tests/test_issue_models.py +299 -0
- package/global/tests/test_local_provider.py +323 -0
- package/global/tools/README.md +178 -0
- package/global/tools/__pycache__/anvil-hud.cpython-314.pyc +0 -0
- package/global/tools/anvil-hud.py +3622 -0
- package/global/tools/anvil-hud.py.bak +3318 -0
- package/global/tools/anvil-issue.py +432 -0
- package/global/tools/anvil-memory/CLAUDE.md +49 -0
- package/global/tools/anvil-memory/README.md +42 -0
- package/global/tools/anvil-memory/bun.lock +25 -0
- package/global/tools/anvil-memory/bunfig.toml +9 -0
- package/global/tools/anvil-memory/package.json +23 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/context-monitor.test.ts +535 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/edge-cases.test.ts +645 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/fixtures.ts +363 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/index.ts +8 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/integration.test.ts +417 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/prompt-generator.test.ts +571 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/ralph-stop.test.ts +440 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/test-utils.ts +252 -0
- package/global/tools/anvil-memory/src/__tests__/commands.test.ts +657 -0
- package/global/tools/anvil-memory/src/__tests__/db.test.ts +641 -0
- package/global/tools/anvil-memory/src/__tests__/hooks.test.ts +272 -0
- package/global/tools/anvil-memory/src/__tests__/performance.test.ts +427 -0
- package/global/tools/anvil-memory/src/__tests__/test-utils.ts +113 -0
- package/global/tools/anvil-memory/src/commands/checkpoint.ts +197 -0
- package/global/tools/anvil-memory/src/commands/get.ts +115 -0
- package/global/tools/anvil-memory/src/commands/init.ts +94 -0
- package/global/tools/anvil-memory/src/commands/observe.ts +163 -0
- package/global/tools/anvil-memory/src/commands/search.ts +112 -0
- package/global/tools/anvil-memory/src/db.ts +638 -0
- package/global/tools/anvil-memory/src/index.ts +205 -0
- package/global/tools/anvil-memory/src/types.ts +122 -0
- package/global/tools/anvil-memory/tsconfig.json +29 -0
- package/global/tools/ralph-loop.sh +359 -0
- package/package.json +45 -0
- package/scripts/anvil +822 -0
- package/scripts/extract_patterns.py +222 -0
- package/scripts/init-project.sh +541 -0
- package/scripts/install.sh +229 -0
- package/scripts/postinstall.js +41 -0
- package/scripts/rollback.sh +188 -0
- package/scripts/sync.sh +623 -0
- package/scripts/test-statusline.sh +248 -0
- package/scripts/update_claude_md.py +224 -0
- package/scripts/verify.sh +255 -0
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# anvil-state.sh - Session state management library for Anvil framework
|
|
3
|
+
# Version: 1.0.0
|
|
4
|
+
#
|
|
5
|
+
# This library provides functions for reading and writing anvil-state.json,
|
|
6
|
+
# which tracks workflow phase, active specs/plans/issues, and cached data.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# source "path/to/anvil-state.sh"
|
|
10
|
+
# anvil_state_init
|
|
11
|
+
# anvil_state_set_phase "implement"
|
|
12
|
+
# anvil_state_set_issue "ANV-42"
|
|
13
|
+
#
|
|
14
|
+
# IMPORTANT: Add .claude/anvil-state.json to your project's .gitignore
|
|
15
|
+
# This file contains session-specific state and should not be committed.
|
|
16
|
+
|
|
17
|
+
set -euo pipefail
|
|
18
|
+
|
|
19
|
+
# ============================================================================
|
|
20
|
+
# Configuration
|
|
21
|
+
# ============================================================================
|
|
22
|
+
|
|
23
|
+
ANVIL_STATE_FILE="${ANVIL_STATE_FILE:-.claude/anvil-state.json}"
|
|
24
|
+
ANVIL_STATE_VERSION="1.0"
|
|
25
|
+
ANVIL_GIT_CACHE_TTL="${ANVIL_GIT_CACHE_TTL:-5}" # seconds
|
|
26
|
+
|
|
27
|
+
# ============================================================================
|
|
28
|
+
# Dependency Check
|
|
29
|
+
# ============================================================================
|
|
30
|
+
|
|
31
|
+
# Check if jq is installed and provide helpful error message
|
|
32
|
+
anvil_check_jq() {
|
|
33
|
+
if ! command -v jq &> /dev/null; then
|
|
34
|
+
echo "ERROR: jq is required but not installed." >&2
|
|
35
|
+
echo "" >&2
|
|
36
|
+
echo "Install jq:" >&2
|
|
37
|
+
echo " macOS: brew install jq" >&2
|
|
38
|
+
echo " Ubuntu: sudo apt-get install jq" >&2
|
|
39
|
+
echo " Fedora: sudo dnf install jq" >&2
|
|
40
|
+
echo " Windows: choco install jq" >&2
|
|
41
|
+
echo "" >&2
|
|
42
|
+
echo "For more info: https://stedolan.github.io/jq/download/" >&2
|
|
43
|
+
return 1
|
|
44
|
+
fi
|
|
45
|
+
return 0
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# ============================================================================
|
|
49
|
+
# State File Management
|
|
50
|
+
# ============================================================================
|
|
51
|
+
|
|
52
|
+
# Get the state file path (creates .claude directory if needed)
|
|
53
|
+
anvil_state_path() {
|
|
54
|
+
local state_dir
|
|
55
|
+
state_dir="$(dirname "$ANVIL_STATE_FILE")"
|
|
56
|
+
|
|
57
|
+
if [[ ! -d "$state_dir" ]]; then
|
|
58
|
+
mkdir -p "$state_dir"
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
echo "$ANVIL_STATE_FILE"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# Initialize state file with default structure if it doesn't exist
|
|
65
|
+
anvil_state_init() {
|
|
66
|
+
anvil_check_jq || return 1
|
|
67
|
+
|
|
68
|
+
local state_file
|
|
69
|
+
state_file="$(anvil_state_path)"
|
|
70
|
+
|
|
71
|
+
if [[ ! -f "$state_file" ]]; then
|
|
72
|
+
jq -n \
|
|
73
|
+
--arg version "$ANVIL_STATE_VERSION" \
|
|
74
|
+
--arg time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
75
|
+
'{
|
|
76
|
+
version: $version,
|
|
77
|
+
session: {
|
|
78
|
+
phase: null,
|
|
79
|
+
activeSpec: null,
|
|
80
|
+
activePlan: null,
|
|
81
|
+
activeIssue: null,
|
|
82
|
+
lastCommand: null,
|
|
83
|
+
lastCommandAt: null
|
|
84
|
+
},
|
|
85
|
+
cache: {
|
|
86
|
+
git: {
|
|
87
|
+
branch: null,
|
|
88
|
+
dirty: null,
|
|
89
|
+
cachedAt: null,
|
|
90
|
+
ttlSeconds: 5
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
meta: {
|
|
94
|
+
createdAt: $time,
|
|
95
|
+
updatedAt: $time
|
|
96
|
+
}
|
|
97
|
+
}' > "$state_file"
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
echo "$state_file"
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# Read the entire state file
|
|
104
|
+
anvil_state_read() {
|
|
105
|
+
anvil_check_jq || return 1
|
|
106
|
+
|
|
107
|
+
local state_file
|
|
108
|
+
state_file="$(anvil_state_path)"
|
|
109
|
+
|
|
110
|
+
if [[ -f "$state_file" ]]; then
|
|
111
|
+
cat "$state_file"
|
|
112
|
+
else
|
|
113
|
+
# Return empty state structure if file doesn't exist
|
|
114
|
+
jq -n '{version: "1.0", session: {}, cache: {}}'
|
|
115
|
+
fi
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
# Read a specific field from state (dot notation: session.phase)
|
|
119
|
+
anvil_state_get() {
|
|
120
|
+
local field="$1"
|
|
121
|
+
anvil_check_jq || return 1
|
|
122
|
+
|
|
123
|
+
local state_file
|
|
124
|
+
state_file="$(anvil_state_path)"
|
|
125
|
+
|
|
126
|
+
if [[ -f "$state_file" ]]; then
|
|
127
|
+
jq -r ".$field // empty" "$state_file" 2>/dev/null || echo ""
|
|
128
|
+
else
|
|
129
|
+
echo ""
|
|
130
|
+
fi
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# ============================================================================
|
|
134
|
+
# State Update Functions
|
|
135
|
+
# ============================================================================
|
|
136
|
+
|
|
137
|
+
# Update a single field in state (creates file if needed)
|
|
138
|
+
anvil_state_set() {
|
|
139
|
+
local field="$1"
|
|
140
|
+
local value="$2"
|
|
141
|
+
anvil_check_jq || return 1
|
|
142
|
+
|
|
143
|
+
local state_file
|
|
144
|
+
state_file="$(anvil_state_init)"
|
|
145
|
+
|
|
146
|
+
local tmp_file
|
|
147
|
+
tmp_file="$(mktemp)"
|
|
148
|
+
|
|
149
|
+
# Update the field and the updatedAt timestamp
|
|
150
|
+
jq --arg value "$value" \
|
|
151
|
+
--arg time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
152
|
+
".$field = \$value | .meta.updatedAt = \$time" \
|
|
153
|
+
"$state_file" > "$tmp_file" && mv "$tmp_file" "$state_file"
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
# Update multiple session fields at once
|
|
157
|
+
anvil_state_update() {
|
|
158
|
+
local phase="${1:-}"
|
|
159
|
+
local spec="${2:-}"
|
|
160
|
+
local plan="${3:-}"
|
|
161
|
+
local issue="${4:-}"
|
|
162
|
+
local command="${5:-}"
|
|
163
|
+
|
|
164
|
+
anvil_check_jq || return 1
|
|
165
|
+
|
|
166
|
+
local state_file
|
|
167
|
+
state_file="$(anvil_state_init)"
|
|
168
|
+
|
|
169
|
+
local tmp_file
|
|
170
|
+
tmp_file="$(mktemp)"
|
|
171
|
+
|
|
172
|
+
local time
|
|
173
|
+
time="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
174
|
+
|
|
175
|
+
# Build jq filter dynamically based on provided values
|
|
176
|
+
local jq_filter=".meta.updatedAt = \$time"
|
|
177
|
+
local jq_args=(--arg time "$time")
|
|
178
|
+
|
|
179
|
+
if [[ -n "$phase" ]]; then
|
|
180
|
+
jq_filter="$jq_filter | .session.phase = \$phase"
|
|
181
|
+
jq_args+=(--arg phase "$phase")
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
if [[ -n "$spec" ]]; then
|
|
185
|
+
if [[ "$spec" == "null" || "$spec" == "clear" ]]; then
|
|
186
|
+
jq_filter="$jq_filter | .session.activeSpec = null"
|
|
187
|
+
else
|
|
188
|
+
jq_filter="$jq_filter | .session.activeSpec = \$spec"
|
|
189
|
+
jq_args+=(--arg spec "$spec")
|
|
190
|
+
fi
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
if [[ -n "$plan" ]]; then
|
|
194
|
+
if [[ "$plan" == "null" || "$plan" == "clear" ]]; then
|
|
195
|
+
jq_filter="$jq_filter | .session.activePlan = null"
|
|
196
|
+
else
|
|
197
|
+
jq_filter="$jq_filter | .session.activePlan = \$plan"
|
|
198
|
+
jq_args+=(--arg plan "$plan")
|
|
199
|
+
fi
|
|
200
|
+
fi
|
|
201
|
+
|
|
202
|
+
if [[ -n "$issue" ]]; then
|
|
203
|
+
if [[ "$issue" == "null" || "$issue" == "clear" ]]; then
|
|
204
|
+
jq_filter="$jq_filter | .session.activeIssue = null"
|
|
205
|
+
else
|
|
206
|
+
jq_filter="$jq_filter | .session.activeIssue = \$issue"
|
|
207
|
+
jq_args+=(--arg issue "$issue")
|
|
208
|
+
fi
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
if [[ -n "$command" ]]; then
|
|
212
|
+
jq_filter="$jq_filter | .session.lastCommand = \$command | .session.lastCommandAt = \$time"
|
|
213
|
+
jq_args+=(--arg command "$command")
|
|
214
|
+
fi
|
|
215
|
+
|
|
216
|
+
jq "${jq_args[@]}" "$jq_filter" "$state_file" > "$tmp_file" && mv "$tmp_file" "$state_file"
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
# ============================================================================
|
|
220
|
+
# Convenience Functions for Common Operations
|
|
221
|
+
# ============================================================================
|
|
222
|
+
|
|
223
|
+
# Set current workflow phase
|
|
224
|
+
anvil_state_set_phase() {
|
|
225
|
+
local phase="$1"
|
|
226
|
+
anvil_state_set "session.phase" "$phase"
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
# Set active spec ID
|
|
230
|
+
anvil_state_set_spec() {
|
|
231
|
+
local spec="$1"
|
|
232
|
+
anvil_state_set "session.activeSpec" "$spec"
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
# Set active plan ID
|
|
236
|
+
anvil_state_set_plan() {
|
|
237
|
+
local plan="$1"
|
|
238
|
+
anvil_state_set "session.activePlan" "$plan"
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
# Set active issue key
|
|
242
|
+
anvil_state_set_issue() {
|
|
243
|
+
local issue="$1"
|
|
244
|
+
anvil_state_set "session.activeIssue" "$issue"
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
# Record command execution
|
|
248
|
+
anvil_state_set_command() {
|
|
249
|
+
local command="$1"
|
|
250
|
+
local time
|
|
251
|
+
time="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
252
|
+
|
|
253
|
+
anvil_check_jq || return 1
|
|
254
|
+
|
|
255
|
+
local state_file
|
|
256
|
+
state_file="$(anvil_state_init)"
|
|
257
|
+
|
|
258
|
+
local tmp_file
|
|
259
|
+
tmp_file="$(mktemp)"
|
|
260
|
+
|
|
261
|
+
jq --arg cmd "$command" --arg time "$time" \
|
|
262
|
+
'.session.lastCommand = $cmd | .session.lastCommandAt = $time | .meta.updatedAt = $time' \
|
|
263
|
+
"$state_file" > "$tmp_file" && mv "$tmp_file" "$state_file"
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
# Clear session state (for /orient reset)
|
|
267
|
+
anvil_state_clear_session() {
|
|
268
|
+
anvil_check_jq || return 1
|
|
269
|
+
|
|
270
|
+
local state_file
|
|
271
|
+
state_file="$(anvil_state_init)"
|
|
272
|
+
|
|
273
|
+
local tmp_file
|
|
274
|
+
tmp_file="$(mktemp)"
|
|
275
|
+
|
|
276
|
+
local time
|
|
277
|
+
time="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
278
|
+
|
|
279
|
+
jq --arg time "$time" \
|
|
280
|
+
'.session.phase = null | .session.activeSpec = null | .session.activePlan = null | .session.activeIssue = null | .meta.updatedAt = $time' \
|
|
281
|
+
"$state_file" > "$tmp_file" && mv "$tmp_file" "$state_file"
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
# ============================================================================
|
|
285
|
+
# Git Cache Functions
|
|
286
|
+
# ============================================================================
|
|
287
|
+
|
|
288
|
+
# Check if git cache is still valid (within TTL)
|
|
289
|
+
anvil_git_cache_valid() {
|
|
290
|
+
anvil_check_jq || return 1
|
|
291
|
+
|
|
292
|
+
local state_file
|
|
293
|
+
state_file="$(anvil_state_path)"
|
|
294
|
+
|
|
295
|
+
if [[ ! -f "$state_file" ]]; then
|
|
296
|
+
return 1
|
|
297
|
+
fi
|
|
298
|
+
|
|
299
|
+
local cached_at
|
|
300
|
+
cached_at="$(jq -r '.cache.git.cachedAt // empty' "$state_file" 2>/dev/null)"
|
|
301
|
+
|
|
302
|
+
if [[ -z "$cached_at" ]]; then
|
|
303
|
+
return 1
|
|
304
|
+
fi
|
|
305
|
+
|
|
306
|
+
local ttl
|
|
307
|
+
ttl="$(jq -r '.cache.git.ttlSeconds // 5' "$state_file" 2>/dev/null)"
|
|
308
|
+
|
|
309
|
+
# Convert ISO timestamp to epoch seconds
|
|
310
|
+
local cached_epoch
|
|
311
|
+
local current_epoch
|
|
312
|
+
|
|
313
|
+
if [[ "$(uname)" == "Darwin" ]]; then
|
|
314
|
+
# macOS: Use TZ=UTC to ensure proper UTC parsing
|
|
315
|
+
cached_epoch="$(TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%SZ" "$cached_at" "+%s" 2>/dev/null || echo 0)"
|
|
316
|
+
else
|
|
317
|
+
# Linux: date -d handles ISO format natively with UTC suffix
|
|
318
|
+
cached_epoch="$(date -d "$cached_at" "+%s" 2>/dev/null || echo 0)"
|
|
319
|
+
fi
|
|
320
|
+
current_epoch="$(date -u "+%s")"
|
|
321
|
+
|
|
322
|
+
local age=$((current_epoch - cached_epoch))
|
|
323
|
+
|
|
324
|
+
if [[ $age -lt $ttl ]]; then
|
|
325
|
+
return 0 # Cache is valid
|
|
326
|
+
else
|
|
327
|
+
return 1 # Cache expired
|
|
328
|
+
fi
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
# Get git branch (from cache if valid, otherwise fresh)
|
|
332
|
+
anvil_git_branch() {
|
|
333
|
+
# Try cache first
|
|
334
|
+
if anvil_git_cache_valid; then
|
|
335
|
+
local cached_branch
|
|
336
|
+
cached_branch="$(anvil_state_get 'cache.git.branch')"
|
|
337
|
+
if [[ -n "$cached_branch" ]]; then
|
|
338
|
+
echo "$cached_branch"
|
|
339
|
+
return 0
|
|
340
|
+
fi
|
|
341
|
+
fi
|
|
342
|
+
|
|
343
|
+
# Get fresh value
|
|
344
|
+
local branch
|
|
345
|
+
branch="$(git branch --show-current 2>/dev/null || echo "")"
|
|
346
|
+
|
|
347
|
+
# Update cache
|
|
348
|
+
anvil_git_cache_update "$branch"
|
|
349
|
+
|
|
350
|
+
echo "$branch"
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
# Update git cache with fresh values
|
|
354
|
+
anvil_git_cache_update() {
|
|
355
|
+
local branch="${1:-}"
|
|
356
|
+
|
|
357
|
+
anvil_check_jq || return 1
|
|
358
|
+
|
|
359
|
+
# Get branch if not provided
|
|
360
|
+
if [[ -z "$branch" ]]; then
|
|
361
|
+
branch="$(git branch --show-current 2>/dev/null || echo "")"
|
|
362
|
+
fi
|
|
363
|
+
|
|
364
|
+
# Check if working tree is dirty
|
|
365
|
+
local dirty="false"
|
|
366
|
+
if [[ -n "$(git status --porcelain 2>/dev/null)" ]]; then
|
|
367
|
+
dirty="true"
|
|
368
|
+
fi
|
|
369
|
+
|
|
370
|
+
local state_file
|
|
371
|
+
state_file="$(anvil_state_init)"
|
|
372
|
+
|
|
373
|
+
local tmp_file
|
|
374
|
+
tmp_file="$(mktemp)"
|
|
375
|
+
|
|
376
|
+
local time
|
|
377
|
+
time="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
378
|
+
|
|
379
|
+
jq --arg branch "$branch" \
|
|
380
|
+
--argjson dirty "$dirty" \
|
|
381
|
+
--arg time "$time" \
|
|
382
|
+
--argjson ttl "$ANVIL_GIT_CACHE_TTL" \
|
|
383
|
+
'.cache.git.branch = $branch | .cache.git.dirty = $dirty | .cache.git.cachedAt = $time | .cache.git.ttlSeconds = $ttl | .meta.updatedAt = $time' \
|
|
384
|
+
"$state_file" > "$tmp_file" && mv "$tmp_file" "$state_file"
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
# ============================================================================
|
|
388
|
+
# Utility Functions
|
|
389
|
+
# ============================================================================
|
|
390
|
+
|
|
391
|
+
# Pretty print the current state (for debugging)
|
|
392
|
+
anvil_state_debug() {
|
|
393
|
+
anvil_check_jq || return 1
|
|
394
|
+
|
|
395
|
+
local state_file
|
|
396
|
+
state_file="$(anvil_state_path)"
|
|
397
|
+
|
|
398
|
+
if [[ -f "$state_file" ]]; then
|
|
399
|
+
echo "=== Anvil State ($state_file) ==="
|
|
400
|
+
jq '.' "$state_file"
|
|
401
|
+
else
|
|
402
|
+
echo "State file does not exist: $state_file"
|
|
403
|
+
fi
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
# Check if state file exists and is valid JSON
|
|
407
|
+
anvil_state_valid() {
|
|
408
|
+
local state_file
|
|
409
|
+
state_file="$(anvil_state_path)"
|
|
410
|
+
|
|
411
|
+
if [[ ! -f "$state_file" ]]; then
|
|
412
|
+
return 1
|
|
413
|
+
fi
|
|
414
|
+
|
|
415
|
+
if jq empty "$state_file" 2>/dev/null; then
|
|
416
|
+
return 0
|
|
417
|
+
else
|
|
418
|
+
return 1
|
|
419
|
+
fi
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
# Repair corrupted state file by recreating it
|
|
423
|
+
anvil_state_repair() {
|
|
424
|
+
local state_file
|
|
425
|
+
state_file="$(anvil_state_path)"
|
|
426
|
+
|
|
427
|
+
if [[ -f "$state_file" ]]; then
|
|
428
|
+
local backup="${state_file}.bak"
|
|
429
|
+
mv "$state_file" "$backup"
|
|
430
|
+
echo "Backed up corrupted state to: $backup" >&2
|
|
431
|
+
fi
|
|
432
|
+
|
|
433
|
+
anvil_state_init
|
|
434
|
+
echo "Created fresh state file: $state_file" >&2
|
|
435
|
+
}
|