fix(6855): the fence keeps its files where no compaction sweeps them, and builds id lists under mawk
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 1m52s
CI & Build / integration (push) Successful in 2m21s
CI & Build / Build & push image (push) Successful in 22s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 1m52s
CI & Build / integration (push) Successful in 2m21s
CI & Build / Build & push image (push) Successful in 22s
The project a session entered now lives in grimoire-fence, which the compaction clear never visits, and session end removes it. Arrays of ids in a tool's input were sent as [,"3","4"] under mawk, which creates an array element as soon as it is assigned to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "grimoire",
|
||||
"description": "Grimoire for Claude Code: connects the grimoire MCP server, adds the hooks that deliver live project state and relevant records at the right moment, ships the shared client-neutral Grimoire skills (using-grimoire, writing-plans, reporting-back, systematic-debugging, verification, brainstorming, reusing-code, shape-accounting, family-canon), and syncs your saved Grimoire Processes as skills (/grimoire:sync).",
|
||||
"version": "2026.11.05.0245",
|
||||
"version": "2026.11.05.0251",
|
||||
"author": {
|
||||
"name": "Bryan Van Deusen"
|
||||
},
|
||||
|
||||
@@ -51,7 +51,9 @@ session_id=$(grimoire_json_pick "$event_flat" '.session_id')
|
||||
tool_name=$(grimoire_json_pick "$event_flat" '.tool_name')
|
||||
hook_event=$(grimoire_json_pick "$event_flat" '.hook_event_name')
|
||||
cwd=$(grimoire_json_pick "$event_flat" '.cwd')
|
||||
state_dir="${TMPDIR:-/tmp}/grimoire-priorart"
|
||||
# Its own directory, which no compaction sweeps: the project a session
|
||||
# entered stays its project for the whole session.
|
||||
state_dir="${TMPDIR:-/tmp}/grimoire-fence"
|
||||
safe_sid=$(printf '%s' "${session_id:-none}" | tr -c 'A-Za-z0-9._-' '_')
|
||||
entered_file="$state_dir/${safe_sid}.entered"
|
||||
|
||||
@@ -135,7 +137,11 @@ fence_tool_input() {
|
||||
}
|
||||
$2 ~ /^\.tool_input\.[A-Za-z_]+\[[0-9]+\]$/ && $3 != "null" && length($3) <= 200 {
|
||||
k = substr($2, 13); sub(/\[.*$/, "", k); add(k)
|
||||
arr[k] = (k in arr) ? arr[k] ",\"" $3 "\"" : "\"" $3 "\""
|
||||
# Tested before assigning: mawk creates arr[k] as soon as it is the
|
||||
# target, so the test inside the assignment would always be true.
|
||||
item = "\"" $3 "\""
|
||||
if (k in arr) item = arr[k] "," item
|
||||
arr[k] = item
|
||||
}
|
||||
END {
|
||||
printf "{"
|
||||
|
||||
@@ -31,11 +31,13 @@ event_flat=$(printf '%s' "$event" | grimoire_json_flat)
|
||||
session_id=$(grimoire_json_pick "$event_flat" '.session_id')
|
||||
[ -n "$session_id" ] || exit 0
|
||||
|
||||
# The moment arm's per-session files (its tool cache and the acts ledger)
|
||||
# describe a session that is over, on any exit — a clear
|
||||
# included, since the next conversation is a new session id.
|
||||
# The moment arm's per-session files (its tool cache and the acts ledger) and
|
||||
# the fence's (the project the session entered, its outage marker) describe a
|
||||
# session that is over, on any exit — a clear included, since the next
|
||||
# conversation is a new session id.
|
||||
safe_sid=$(printf '%s' "$session_id" | tr -c 'A-Za-z0-9._-' '_')
|
||||
rm -f "${TMPDIR:-/tmp}/grimoire-moment/${safe_sid}".* 2>/dev/null || true
|
||||
rm -f "${TMPDIR:-/tmp}/grimoire-moment/${safe_sid}".* \
|
||||
"${TMPDIR:-/tmp}/grimoire-fence/${safe_sid}".* 2>/dev/null || true
|
||||
|
||||
reason=$(grimoire_json_pick "$event_flat" '.reason')
|
||||
[ "$reason" = "clear" ] && exit 0
|
||||
|
||||
@@ -190,3 +190,25 @@ def test_an_unanswered_check_lets_the_act_through_and_says_so_once(tmp_path):
|
||||
ctx = json.loads(first)["hookSpecificOutput"]["additionalContext"]
|
||||
assert ctx.startswith("> Grimoire did not answer the project fence check")
|
||||
assert _run(tmp_path, 1, _edit(other / "b.py", home), home) == ""
|
||||
|
||||
|
||||
def test_the_entered_project_outlives_a_compaction_and_ends_with_the_session(tmp_path):
|
||||
# A compaction loses the context, not the session: the project it entered
|
||||
# stays its project. Session end clears it.
|
||||
loose = tmp_path / "loose"
|
||||
loose.mkdir()
|
||||
_run(tmp_path, 1, {"hook_event_name": "PostToolUse", "cwd": str(loose),
|
||||
"tool_name": "mcp__plugin_grimoire_grimoire__enter_project",
|
||||
"tool_input": {"project_id": 7}}, loose)
|
||||
entered = tmp_path / "tmp" / "grimoire-fence" / "s-fence.entered"
|
||||
assert entered.read_text() == "7"
|
||||
|
||||
env = {"PATH": os.environ["PATH"], "TMPDIR": str(tmp_path / "tmp"), "HOME": str(tmp_path)}
|
||||
hooks = HOOK.parent
|
||||
for hook, event in (("grimoire_session_context.sh", {"source": "compact"}),
|
||||
("grimoire_session_end.sh", {"reason": "other"})):
|
||||
subprocess.run(["bash", str(hooks / hook)], input=json.dumps({"session_id": "s-fence", **event}),
|
||||
capture_output=True, text=True, env=env, timeout=60, check=True)
|
||||
if hook == "grimoire_session_context.sh":
|
||||
assert entered.exists(), "a compaction cleared the project the session entered"
|
||||
assert not entered.exists()
|
||||
|
||||
Reference in New Issue
Block a user