claude-code-conductor 2.8.0__tar.gz → 2.10.0__tar.gz
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.
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/hooks/permission_handler.py +5 -0
- claude_code_conductor-2.10.0/.claude/hooks/recall_inject.py +292 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/hooks/record_tier_outcome.py +35 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/hooks/restore_session.py +9 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/hooks/select_tier.py +13 -11
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/hooks/session_utils.py +0 -1
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/hooks/statusline.py +9 -17
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/hooks/stop.py +4 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/hooks/subagent_log.py +2 -2
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/hooks/worktree_guard.py +4 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/settings.json +6 -1
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/skills/dev-workflow/SKILL.md +65 -40
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/skills/init-session/SKILL.md +1 -4
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/skills/parallel-agents/SKILL.md +5 -0
- claude_code_conductor-2.10.0/.claude/skills/recall/SKILL.md +113 -0
- claude_code_conductor-2.10.0/.claude/skills/start/SKILL.md +136 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.gitignore +6 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/CHANGELOG.md +103 -1
- claude_code_conductor-2.10.0/LICENSES/chroma-hnswlib-LICENSE +184 -0
- claude_code_conductor-2.10.0/LICENSES/chroma-hnswlib-NOTICE +9 -0
- claude_code_conductor-2.10.0/LICENSES/fastembed-LICENSE +184 -0
- claude_code_conductor-2.10.0/LICENSES/fastembed-NOTICE +8 -0
- claude_code_conductor-2.10.0/LICENSES/onnxruntime-LICENSE +29 -0
- claude_code_conductor-2.10.0/LICENSES/paraphrase-multilingual-MiniLM-L12-v2-LICENSE +142 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/PKG-INFO +39 -1
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/README.md +36 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/hatch_build.py +2 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/pyproject.toml +4 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/src/c3/__init__.py +1 -1
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/src/c3/_excludes.py +2 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/src/c3/_terminal.py +1 -1
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/src/c3/cli.py +36 -2
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/src/c3/cli_plan.py +11 -7
- claude_code_conductor-2.10.0/src/c3/cli_recall.py +464 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/src/c3/cli_tier.py +1 -1
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/src/c3/db.py +14 -7
- claude_code_conductor-2.10.0/src/c3/embedding.py +145 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/src/c3/mcp_server.py +13 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/src/c3/plan_validator.py +2 -1
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/src/c3/question.py +3 -0
- claude_code_conductor-2.10.0/src/c3/recall_chunker.py +161 -0
- claude_code_conductor-2.10.0/src/c3/recall_index.py +515 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/hooks/test_consolidate_memory.py +9 -12
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/hooks/test_permission_handler.py +55 -0
- claude_code_conductor-2.10.0/tests/hooks/test_recall_inject.py +477 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/hooks/test_restore_session.py +9 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/hooks/test_session_stop.py +2 -1
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/hooks/test_session_utils.py +9 -13
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/hooks/test_statusline.py +13 -34
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/hooks/test_statusline_template_sync.py +6 -0
- claude_code_conductor-2.10.0/tests/skills/_skill_helpers.py +137 -0
- claude_code_conductor-2.10.0/tests/skills/test_dev_workflow_no_task_type.py +151 -0
- claude_code_conductor-2.10.0/tests/skills/test_init_session_no_task_type.py +47 -0
- claude_code_conductor-2.10.0/tests/skills/test_recall_skill.py +98 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/skills/test_session_backlog_reconciliation.py +23 -35
- claude_code_conductor-2.10.0/tests/skills/test_start_skill_bugfix_flow.py +50 -0
- claude_code_conductor-2.10.0/tests/skills/test_start_skill_new_flow.py +128 -0
- claude_code_conductor-2.10.0/tests/skills/test_start_skill_security_audit_phase.py +77 -0
- claude_code_conductor-2.10.0/tests/test_cli_entry.py +73 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/test_cli_list.py +5 -5
- claude_code_conductor-2.10.0/tests/test_cli_recall.py +494 -0
- claude_code_conductor-2.10.0/tests/test_embedding.py +176 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/test_pre_compact.py +4 -6
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/test_pre_tool_hook.py +14 -15
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/test_precompact_toctou_fixes.py +4 -3
- claude_code_conductor-2.10.0/tests/test_recall_chunker.py +129 -0
- claude_code_conductor-2.10.0/tests/test_recall_index.py +592 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/test_session_utils_additional.py +14 -15
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/test_statusline.py +27 -62
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/test_stop_additional.py +9 -9
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/test_stop_hook.py +8 -8
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/test_stop_precompact_fixes.py +4 -3
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/test_sync_template_stop.py +1 -1
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/test_template_pre_tool_hook.py +6 -7
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/test_worktree_guard.py +15 -2
- claude_code_conductor-2.8.0/.claude/skills/start/SKILL.md +0 -355
- claude_code_conductor-2.8.0/.claude/skills/task-routing/SKILL.md +0 -201
- claude_code_conductor-2.8.0/tests/skills/test_start_skill_bugfix_flow.py +0 -56
- claude_code_conductor-2.8.0/tests/skills/test_start_skill_security_audit_phase.py +0 -419
- claude_code_conductor-2.8.0/tests/skills/test_task_routing_skill.py +0 -77
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/CLAUDE.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/agents/architect.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/agents/code-reviewer.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/agents/developer.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/agents/doc-writer.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/agents/interviewer.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/agents/planner.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/agents/project-setup.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/agents/security-reviewer.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/agents/summarize-memory.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/agents/systematic-debugger.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/agents/tester.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/agents/wt_developer.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/agents/wt_systematic-debugger.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/agents/wt_tester.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/docs/platform-adapters.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/docs/settings.json.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/hooks/consolidate_memory.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/hooks/permission_handler_toast.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/hooks/post_tool.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/hooks/pre_compact.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/hooks/pre_tool.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/hooks/record_review_decision.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/hooks/review_hint_inject.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/hooks/schema.sql +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/hooks/session_start.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/hooks/session_stop.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/memory/.gitkeep +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/permission_rules.json +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/rules/code-review-checklist.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/rules/promoted/index.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/rules/security-review-checklist.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/skills/code-review/SKILL.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/skills/codex-review/SKILL.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/skills/develop/SKILL.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/skills/doc/SKILL.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/skills/extract-lib/SKILL.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/skills/mcp-config/SKILL.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/skills/pattern-status/SKILL.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/skills/promote-pattern/SKILL.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/skills/report-timestamp/SKILL.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/skills/report-timestamp/scripts/get_timestamp.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/skills/setup/SKILL.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/skills/summarize-memory/SKILL.md +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/state/.gitkeep +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/LICENSE +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/src/c3/__main__.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/src/c3/adapters.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/src/c3/cli_ask.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/src/c3/cli_doctor.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/src/c3/cli_init.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/src/c3/cli_list.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/src/c3/cli_update.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/src/c3/paths.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/src/c3/platforms.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/__init__.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/conftest.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/hooks/__init__.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/hooks/test_permission_handler_toast.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/hooks/test_pip_reinstall_reminder.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/hooks/test_planner_check.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/hooks/test_post_tool.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/hooks/test_pre_tool.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/hooks/test_record_tier_outcome.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/hooks/test_review_hint_inject.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/hooks/test_select_tier.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/hooks/test_select_tier_escalation.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/hooks/test_session_start.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/hooks/test_settings_local_absolute_paths.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/hooks/test_similarity_boost.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/hooks/test_subagent_log.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/hooks/test_sync_check.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/hooks/test_template_guard.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/skills/__init__.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/test_adapters.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/test_cli_ask.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/test_cli_init.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/test_cli_plan.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/test_cli_tier.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/test_docstring_consistency.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/test_excludes.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/test_mcp_server_elicit.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/test_paths.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/test_plan_validator.py +0 -0
- {claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/tests/test_precompact_additional.py +0 -0
{claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/hooks/permission_handler.py
RENAMED
|
@@ -369,6 +369,11 @@ def main() -> None:
|
|
|
369
369
|
rules = load_rules()
|
|
370
370
|
description = describe_tool(tool_name, tool_input)
|
|
371
371
|
|
|
372
|
+
# AskUserQuestion は通知のみ。自動承認対象外として扱う。
|
|
373
|
+
if tool_name == 'AskUserQuestion':
|
|
374
|
+
notify(f'-> 質問: {description}')
|
|
375
|
+
return
|
|
376
|
+
|
|
372
377
|
for pattern in rules.get('auto_allow', []):
|
|
373
378
|
if matches_pattern(tool_name, tool_input, pattern):
|
|
374
379
|
if rules.get('notify_on_auto', True):
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""UserPromptSubmit hook: inject semantically-similar past context (α design).
|
|
3
|
+
|
|
4
|
+
The hook runs ``c3 recall search`` against the local HNSW index and
|
|
5
|
+
returns the top hits as ``additionalContext`` for the parent Claude to
|
|
6
|
+
consider. The preface explicitly asks Claude to evaluate the relevance
|
|
7
|
+
of each hit and ignore unrelated ones — i.e. *AI judges, hook does not
|
|
8
|
+
filter aggressively*.
|
|
9
|
+
|
|
10
|
+
Skip conditions (all silent no-ops, exit 0):
|
|
11
|
+
- ``C3_RECALL_HOOK_DISABLE=1`` env var set
|
|
12
|
+
- Prompt shorter than :data:`_MIN_PROMPT_CHARS` (default 15)
|
|
13
|
+
- Prompt starts with ``/`` (slash command) or ``@`` (file mention)
|
|
14
|
+
- No ``.claude/state/recall_meta.json`` / ``recall.hnsw`` (index not built)
|
|
15
|
+
- ``c3.cli`` subprocess fails or times out
|
|
16
|
+
- Zero hits above ``--min-score``
|
|
17
|
+
|
|
18
|
+
Output protocol:
|
|
19
|
+
``{"hookSpecificOutput": {"hookEventName": "UserPromptSubmit",
|
|
20
|
+
"additionalContext": "..."}}``
|
|
21
|
+
|
|
22
|
+
Performance: each invocation runs a fresh Python subprocess that loads
|
|
23
|
+
fastembed + onnxruntime + the MiniLM model. Cold-start is ~2-3 seconds,
|
|
24
|
+
warm cache ~1-2 seconds. Users can disable this hook entirely by setting
|
|
25
|
+
``C3_RECALL_HOOK_DISABLE=1`` in the shell or in ``.env``.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import json
|
|
31
|
+
import os
|
|
32
|
+
import re
|
|
33
|
+
import subprocess
|
|
34
|
+
import sys
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
from typing import Iterable
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
sys.stdin.reconfigure(encoding="utf-8")
|
|
40
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
41
|
+
sys.stderr.reconfigure(encoding="utf-8")
|
|
42
|
+
except AttributeError:
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Minimum prompt length (chars) to bother running recall. Short messages
|
|
47
|
+
# like "yes" / "ok" / "go" rarely benefit from semantic recall and the
|
|
48
|
+
# subprocess overhead is wasteful.
|
|
49
|
+
_MIN_PROMPT_CHARS = 15
|
|
50
|
+
|
|
51
|
+
# SR-L-1: cap the prompt passed to the subprocess to avoid passing huge
|
|
52
|
+
# context windows through command-line arguments (OS arg-length limits and
|
|
53
|
+
# unnecessary embedding overhead).
|
|
54
|
+
_MAX_PROMPT_CHARS = 2000
|
|
55
|
+
|
|
56
|
+
_TOP_K = 3
|
|
57
|
+
|
|
58
|
+
# Slightly stricter than the CLI default (0.3) because the parent Claude
|
|
59
|
+
# pays a context cost for every injected line; surfacing weak matches
|
|
60
|
+
# isn't worth the noise.
|
|
61
|
+
_MIN_SCORE = 0.4
|
|
62
|
+
|
|
63
|
+
# Generous timeout to accommodate the fastembed cold-start the first
|
|
64
|
+
# time a Claude session warms up the cache.
|
|
65
|
+
_TIMEOUT_SEC = 8
|
|
66
|
+
|
|
67
|
+
_DISABLE_ENV_VAR = "C3_RECALL_HOOK_DISABLE"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# SR-M-1: strip control characters (except \t) and newlines from fields
|
|
71
|
+
# that are embedded inline into the additionalContext string. Unescaped
|
|
72
|
+
# newlines in path / chunk_label would allow a malicious file path or
|
|
73
|
+
# heading to inject extra lines (including header-like strings) into the
|
|
74
|
+
# context block seen by the parent LLM.
|
|
75
|
+
_CONTROL_RE = re.compile(r"[\x00-\x08\x0b-\x1f\x7f]")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _sanitize_field(s: str) -> str:
|
|
79
|
+
"""Remove newlines and non-printable control chars from inline fields.
|
|
80
|
+
|
|
81
|
+
Any text following the first newline is stripped entirely (not just the
|
|
82
|
+
newline character) so that a malicious path like
|
|
83
|
+
``normal/path\\nX-Injected: evil-header`` cannot smuggle arbitrary text
|
|
84
|
+
into the output after the newline is collapsed.
|
|
85
|
+
"""
|
|
86
|
+
if not s:
|
|
87
|
+
return ""
|
|
88
|
+
# Take only text before the first newline.
|
|
89
|
+
first_line = s.split("\n")[0].split("\r")[0]
|
|
90
|
+
# Strip remaining control chars (but preserve tab for readability).
|
|
91
|
+
return _CONTROL_RE.sub("", first_line)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def should_skip_prompt(prompt: str) -> bool:
|
|
95
|
+
"""Pure-logic check used by main() and the unit tests."""
|
|
96
|
+
if not prompt:
|
|
97
|
+
return True
|
|
98
|
+
stripped = prompt.strip()
|
|
99
|
+
if len(stripped) < _MIN_PROMPT_CHARS:
|
|
100
|
+
return True
|
|
101
|
+
if stripped.startswith("/"):
|
|
102
|
+
return True
|
|
103
|
+
if stripped.startswith("@"):
|
|
104
|
+
return True
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def format_additional_context(hits: Iterable[dict], *, stale: bool = False) -> str:
|
|
109
|
+
"""Render hits into the ``additionalContext`` block.
|
|
110
|
+
|
|
111
|
+
The preface is the key design lever for α: it tells the parent Claude
|
|
112
|
+
that these are *candidates*, not *facts*, and asks it to discard
|
|
113
|
+
unrelated entries.
|
|
114
|
+
|
|
115
|
+
When ``stale`` is True, a directive is prepended that asks the parent
|
|
116
|
+
Claude to surface ``AskUserQuestion`` so the user can choose whether
|
|
117
|
+
to ``c3 recall rebuild``. The hook itself cannot invoke
|
|
118
|
+
``AskUserQuestion`` — only the parent LLM can — so we route the
|
|
119
|
+
decision through context injection.
|
|
120
|
+
"""
|
|
121
|
+
lines: list[str] = []
|
|
122
|
+
if stale:
|
|
123
|
+
lines.extend(
|
|
124
|
+
[
|
|
125
|
+
"[recall] ⚠️ インデックスが古い可能性があります(最新ソースの mtime > index mtime)。",
|
|
126
|
+
"次に進む前に、AskUserQuestion で以下の 3 択をユーザーに提示してください:",
|
|
127
|
+
" - 今すぐ rebuild する → Bash で `c3 recall rebuild` を実行(約 1〜2 分)",
|
|
128
|
+
" - 後で / 今は不要 → 検索結果は古いままという前提で続行",
|
|
129
|
+
" - 無視 → このまま現タスクを進める",
|
|
130
|
+
"ユーザーが同一セッション中にすでに「後で」「無視」と答えた場合は再度尋ねないでください。",
|
|
131
|
+
"",
|
|
132
|
+
]
|
|
133
|
+
)
|
|
134
|
+
lines.append(
|
|
135
|
+
"[recall] 過去の類似情報の検索結果(参考のみ。**現タスクと無関係なら無視**してください):"
|
|
136
|
+
)
|
|
137
|
+
for i, hit in enumerate(hits, start=1):
|
|
138
|
+
score = hit.get("score")
|
|
139
|
+
# SR-M-1: sanitize inline fields to prevent newline / control-char
|
|
140
|
+
# injection from malicious file paths or headings.
|
|
141
|
+
path = _sanitize_field(hit.get("path") or "")
|
|
142
|
+
label = _sanitize_field(hit.get("chunk_label") or "")
|
|
143
|
+
snippet = (hit.get("snippet") or "").strip().replace("\n", " ")
|
|
144
|
+
if len(snippet) > 220:
|
|
145
|
+
snippet = snippet[:220] + "..."
|
|
146
|
+
score_str = f"{score:.2f}" if isinstance(score, (int, float)) else str(score)
|
|
147
|
+
lines.append(f" [{i}] score={score_str} {path} :: {label}")
|
|
148
|
+
if snippet:
|
|
149
|
+
lines.append(f" {snippet}")
|
|
150
|
+
return "\n".join(lines)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def find_repo_root() -> Path | None:
|
|
154
|
+
"""Return the nearest ancestor containing ``.claude/`` (or None)."""
|
|
155
|
+
here = Path(os.getenv("CLAUDE_PROJECT_DIR") or Path.cwd()).resolve()
|
|
156
|
+
for candidate in [here, *here.parents]:
|
|
157
|
+
if (candidate / ".claude").is_dir():
|
|
158
|
+
return candidate
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def index_exists(repo_root: Path) -> bool:
|
|
163
|
+
meta = repo_root / ".claude" / "state" / "recall_meta.json"
|
|
164
|
+
index = repo_root / ".claude" / "state" / "recall.hnsw"
|
|
165
|
+
return meta.exists() and index.exists()
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# Source directories scanned to decide if the recall index is older than
|
|
169
|
+
# at least one of its inputs. Mirrors :mod:`c3.recall_index.collect_sources`
|
|
170
|
+
# but kept local to the hook so it can run without importing the c3
|
|
171
|
+
# package (the hook may execute in environments where ``c3`` is not yet
|
|
172
|
+
# importable, e.g. immediately after ``c3 init``).
|
|
173
|
+
# CR-L-02: Keep in sync with c3.recall_index.collect_sources when adding
|
|
174
|
+
# or removing source kinds.
|
|
175
|
+
_STALE_SOURCE_GLOBS = (
|
|
176
|
+
(Path(".claude") / "memory" / "sessions", "*.tmp"),
|
|
177
|
+
(Path(".claude") / "agent-memory", "*.md"),
|
|
178
|
+
(Path(".claude") / "reports" / "archive", "*.md"),
|
|
179
|
+
)
|
|
180
|
+
_STALE_PATTERNS_JSON = Path(".claude") / "memory" / "patterns.json"
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def index_is_stale(repo_root: Path) -> bool:
|
|
184
|
+
"""Return True if any recall source is newer than the index file."""
|
|
185
|
+
index_path = repo_root / ".claude" / "state" / "recall.hnsw"
|
|
186
|
+
if not index_path.exists():
|
|
187
|
+
return False
|
|
188
|
+
index_mtime = index_path.stat().st_mtime
|
|
189
|
+
for rel_dir, pattern in _STALE_SOURCE_GLOBS:
|
|
190
|
+
absolute = repo_root / rel_dir
|
|
191
|
+
if not absolute.is_dir():
|
|
192
|
+
continue
|
|
193
|
+
for path in absolute.rglob(pattern):
|
|
194
|
+
# Cycle2-L-1 [SR-V-002]: skip symlinks to avoid reading mtime of
|
|
195
|
+
# files outside the C3 source tree (matches the analogous guard in
|
|
196
|
+
# c3.recall_index._collect_markdown_glob).
|
|
197
|
+
if not path.is_file() or path.name == ".gitkeep" or path.is_symlink():
|
|
198
|
+
continue
|
|
199
|
+
try:
|
|
200
|
+
if path.stat().st_mtime > index_mtime:
|
|
201
|
+
return True
|
|
202
|
+
except OSError:
|
|
203
|
+
continue
|
|
204
|
+
patterns_path = repo_root / _STALE_PATTERNS_JSON
|
|
205
|
+
if patterns_path.is_file():
|
|
206
|
+
try:
|
|
207
|
+
if patterns_path.stat().st_mtime > index_mtime:
|
|
208
|
+
return True
|
|
209
|
+
except OSError:
|
|
210
|
+
pass
|
|
211
|
+
return False
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def run_recall(prompt: str, repo_root: Path) -> list[dict]:
|
|
215
|
+
"""Invoke ``python -m c3.cli recall search`` and return ``hits`` list.
|
|
216
|
+
|
|
217
|
+
Any error path (subprocess failure, timeout, malformed JSON) returns
|
|
218
|
+
an empty list so the hook stays silent rather than surfacing errors
|
|
219
|
+
to the user mid-prompt.
|
|
220
|
+
"""
|
|
221
|
+
# SR-L-1: truncate prompt to avoid OS arg-length limits and pass only
|
|
222
|
+
# the most relevant context to the embedding model.
|
|
223
|
+
prompt = prompt[:_MAX_PROMPT_CHARS]
|
|
224
|
+
try:
|
|
225
|
+
result = subprocess.run(
|
|
226
|
+
[
|
|
227
|
+
sys.executable,
|
|
228
|
+
"-m",
|
|
229
|
+
"c3.cli",
|
|
230
|
+
"recall",
|
|
231
|
+
"search",
|
|
232
|
+
prompt,
|
|
233
|
+
"--top",
|
|
234
|
+
str(_TOP_K),
|
|
235
|
+
"--min-score",
|
|
236
|
+
str(_MIN_SCORE),
|
|
237
|
+
"--json",
|
|
238
|
+
"--target",
|
|
239
|
+
str(repo_root),
|
|
240
|
+
],
|
|
241
|
+
capture_output=True,
|
|
242
|
+
text=True,
|
|
243
|
+
encoding="utf-8",
|
|
244
|
+
timeout=_TIMEOUT_SEC,
|
|
245
|
+
)
|
|
246
|
+
except (subprocess.TimeoutExpired, OSError):
|
|
247
|
+
return []
|
|
248
|
+
if result.returncode != 0:
|
|
249
|
+
return []
|
|
250
|
+
try:
|
|
251
|
+
data = json.loads(result.stdout)
|
|
252
|
+
except (json.JSONDecodeError, ValueError):
|
|
253
|
+
return []
|
|
254
|
+
hits = data.get("hits") or []
|
|
255
|
+
return hits if isinstance(hits, list) else []
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def main() -> int:
|
|
259
|
+
if os.environ.get(_DISABLE_ENV_VAR) == "1":
|
|
260
|
+
return 0
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
payload = json.load(sys.stdin)
|
|
264
|
+
except (json.JSONDecodeError, ValueError):
|
|
265
|
+
return 0
|
|
266
|
+
|
|
267
|
+
prompt = payload.get("prompt", "")
|
|
268
|
+
if not isinstance(prompt, str) or should_skip_prompt(prompt):
|
|
269
|
+
return 0
|
|
270
|
+
|
|
271
|
+
repo_root = find_repo_root()
|
|
272
|
+
if repo_root is None or not index_exists(repo_root):
|
|
273
|
+
return 0
|
|
274
|
+
|
|
275
|
+
hits = run_recall(prompt, repo_root)
|
|
276
|
+
if not hits:
|
|
277
|
+
return 0
|
|
278
|
+
|
|
279
|
+
stale = index_is_stale(repo_root)
|
|
280
|
+
|
|
281
|
+
output = {
|
|
282
|
+
"hookSpecificOutput": {
|
|
283
|
+
"hookEventName": "UserPromptSubmit",
|
|
284
|
+
"additionalContext": format_additional_context(hits, stale=stale),
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
print(json.dumps(output, ensure_ascii=False))
|
|
288
|
+
return 0
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
if __name__ == "__main__":
|
|
292
|
+
sys.exit(main())
|
{claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/hooks/record_tier_outcome.py
RENAMED
|
@@ -87,6 +87,40 @@ def _delete_tier_selection() -> None:
|
|
|
87
87
|
)
|
|
88
88
|
|
|
89
89
|
|
|
90
|
+
# prompt-history.jsonl の上限サイズ(バイト)。超過時は末尾 _PROMPT_HISTORY_TRUNCATE_LINES 行
|
|
91
|
+
# だけを残してローテーションする。読み込み側 (select_tier._PROMPT_HISTORY_SCAN_LINES=1000) と
|
|
92
|
+
# 同じオーダーで保持し、ディスク消費を抑える [SR-V-001]。
|
|
93
|
+
_PROMPT_HISTORY_MAX_BYTES = 10 * 1024 * 1024 # 10 MB
|
|
94
|
+
_PROMPT_HISTORY_TRUNCATE_LINES = 2000
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _rotate_prompt_history_if_needed() -> None:
|
|
98
|
+
"""prompt-history.jsonl が上限超過なら末尾 N 行を残して切り詰める。
|
|
99
|
+
|
|
100
|
+
書き込み側のサイズ無制限成長を防ぐシンプルなローテーション。失敗時は警告のみ。
|
|
101
|
+
"""
|
|
102
|
+
try:
|
|
103
|
+
size = os.path.getsize(PROMPT_HISTORY_PATH)
|
|
104
|
+
except OSError:
|
|
105
|
+
return
|
|
106
|
+
if size <= _PROMPT_HISTORY_MAX_BYTES:
|
|
107
|
+
return
|
|
108
|
+
try:
|
|
109
|
+
# 末尾 N 行のみ deque で保持して上書きする(ファイル全体は走査するが I/O のみ)
|
|
110
|
+
import collections as _c
|
|
111
|
+
with open(PROMPT_HISTORY_PATH, "r", encoding="utf-8") as f:
|
|
112
|
+
tail = list(_c.deque(f, maxlen=_PROMPT_HISTORY_TRUNCATE_LINES))
|
|
113
|
+
tmp_path = PROMPT_HISTORY_PATH + ".tmp"
|
|
114
|
+
with open(tmp_path, "w", encoding="utf-8") as f:
|
|
115
|
+
f.writelines(tail)
|
|
116
|
+
os.replace(tmp_path, PROMPT_HISTORY_PATH)
|
|
117
|
+
except OSError as exc:
|
|
118
|
+
print(
|
|
119
|
+
f"[record_tier_outcome] prompt-history rotate skipped: {exc}",
|
|
120
|
+
file=sys.stderr,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
90
124
|
def _append_prompt_history(selection: dict, success: bool) -> None:
|
|
91
125
|
"""Phase 2-C: prompt-history.jsonl に 1 行追記する。
|
|
92
126
|
|
|
@@ -108,6 +142,7 @@ def _append_prompt_history(selection: dict, success: bool) -> None:
|
|
|
108
142
|
}
|
|
109
143
|
try:
|
|
110
144
|
os.makedirs(os.path.dirname(PROMPT_HISTORY_PATH), exist_ok=True)
|
|
145
|
+
_rotate_prompt_history_if_needed()
|
|
111
146
|
with open(PROMPT_HISTORY_PATH, "a", encoding="utf-8") as f:
|
|
112
147
|
f.write(json.dumps(record, ensure_ascii=False) + "\n")
|
|
113
148
|
except OSError as exc:
|
{claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/hooks/restore_session.py
RENAMED
|
@@ -41,6 +41,15 @@ def find_latest_session() -> str | None:
|
|
|
41
41
|
return os.path.join(SESSIONS_DIR, max(files))
|
|
42
42
|
|
|
43
43
|
|
|
44
|
+
def extract_section(content: str, heading: str) -> str:
|
|
45
|
+
"""``session_utils.extract_section`` への薄いラッパー(後方互換用)。
|
|
46
|
+
|
|
47
|
+
過去にこのモジュール直下にあった ``extract_section`` を呼び出すテスト・スクリプトとの
|
|
48
|
+
互換維持のため、モジュールレベルで公開する。実体は :mod:`session_utils` 側にある。
|
|
49
|
+
"""
|
|
50
|
+
return _load_session_utils().extract_section(content, heading)
|
|
51
|
+
|
|
52
|
+
|
|
44
53
|
def main():
|
|
45
54
|
path = find_latest_session()
|
|
46
55
|
if not path or not os.path.exists(path):
|
|
@@ -94,11 +94,15 @@ def _mask_secrets(text: str) -> str:
|
|
|
94
94
|
|
|
95
95
|
キー名やプレフィックスは残し、値のみを置換することで
|
|
96
96
|
「何が含まれていたか」は伝わらないようにする。
|
|
97
|
-
PEM
|
|
97
|
+
PEM ブロックは開始タグ + *** + 終了タグ に置換する。
|
|
98
98
|
"""
|
|
99
99
|
result = text
|
|
100
100
|
for pattern in _MASK_PATTERNS:
|
|
101
|
-
|
|
101
|
+
# group(2) があれば PEM ブロック (BEGIN...END)、なければプレフィックス系
|
|
102
|
+
result = pattern.sub(
|
|
103
|
+
lambda m: m.group(1) + "***" + (m.group(2) if m.lastindex and m.lastindex >= 2 else ""),
|
|
104
|
+
result,
|
|
105
|
+
)
|
|
102
106
|
return result
|
|
103
107
|
|
|
104
108
|
# prompt-history.jsonl の末尾から読む最大行数(パフォーマンス対策)
|
|
@@ -325,11 +329,11 @@ def write_tier_selection(
|
|
|
325
329
|
record_tier_outcome.py がこの json を読んで α/β を更新する。
|
|
326
330
|
既存ファイルは上書きされる(最新 1 件のみ保持)。
|
|
327
331
|
|
|
328
|
-
|
|
329
|
-
PO
|
|
330
|
-
|
|
332
|
+
``suggested_model`` を併せて書く。tier 名と model の短縮名は同一とする。
|
|
333
|
+
(PO 廃止前は runner.py が読んで ``claude --agents`` 用に使っていたが、v2.0.0 以降は
|
|
334
|
+
記録目的のみ。将来再利用する余地のために維持する。)
|
|
331
335
|
|
|
332
|
-
|
|
336
|
+
``escalated`` / ``escalation_reason`` を任意で含める。
|
|
333
337
|
failure rate に基づく昇格が起きた場合のみ True / 文字列が入る。
|
|
334
338
|
"""
|
|
335
339
|
os.makedirs(os.path.dirname(TIER_SELECTION_PATH), exist_ok=True)
|
|
@@ -337,7 +341,7 @@ def write_tier_selection(
|
|
|
337
341
|
"complexity": complexity,
|
|
338
342
|
"tier": tier,
|
|
339
343
|
"mode": mode,
|
|
340
|
-
#
|
|
344
|
+
# tier はそのまま claude --agents の model 短縮名として使える
|
|
341
345
|
"suggested_model": tier,
|
|
342
346
|
}
|
|
343
347
|
if escalated:
|
|
@@ -383,10 +387,8 @@ def build_additional_context(
|
|
|
383
387
|
|
|
384
388
|
return (
|
|
385
389
|
f"[tier-routing 推奨] 複雑度: {complexity} / 推奨 Tier: {tier}({confidence})。"
|
|
386
|
-
f"
|
|
387
|
-
f"
|
|
388
|
-
f" frontmatter 指定が優先されるため、コスト最適化したい場合は手動切替"
|
|
389
|
-
f" してください。{suffix}"
|
|
390
|
+
f" 親 Claude の Agent ツール経由ではエージェント定義の frontmatter 指定が"
|
|
391
|
+
f" 優先されるため、コスト最適化したい場合は手動切替してください。{suffix}"
|
|
390
392
|
)
|
|
391
393
|
|
|
392
394
|
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
|
|
2
|
+
"""Statusline script for Claude Code.
|
|
3
|
+
|
|
4
|
+
Displays: [model display name] effort | ctx used X% | 5h lim X% | 7d lim X%
|
|
5
|
+
|
|
6
|
+
context_window_size (200K / 1M) は ctx used X% と情報重複のため表示しない。
|
|
7
|
+
gauge バー描画も省スペース優先で表示しない。
|
|
4
8
|
"""
|
|
5
9
|
|
|
6
10
|
import json
|
|
@@ -35,14 +39,6 @@ def pct_color(pct: int) -> str:
|
|
|
35
39
|
return GREEN
|
|
36
40
|
|
|
37
41
|
|
|
38
|
-
def format_context_size(size: int) -> str:
|
|
39
|
-
if size >= 900_000:
|
|
40
|
-
return '1M'
|
|
41
|
-
elif size >= 100_000:
|
|
42
|
-
return '200K'
|
|
43
|
-
return str(size)
|
|
44
|
-
|
|
45
|
-
|
|
46
42
|
def format_reset_time(resets_at) -> str:
|
|
47
43
|
if not resets_at:
|
|
48
44
|
return ''
|
|
@@ -82,18 +78,13 @@ def render_output(raw: str) -> None:
|
|
|
82
78
|
header: list[str] = []
|
|
83
79
|
metrics: list[str] = []
|
|
84
80
|
|
|
85
|
-
# [model display name]
|
|
81
|
+
# [model display name] effort — スペース区切り
|
|
82
|
+
# (context_window_size は ctx used X% と情報重複のため表示しない)
|
|
86
83
|
model = data.get('model') or {}
|
|
87
84
|
display_name = model.get('display_name', '')
|
|
88
85
|
if display_name:
|
|
89
86
|
header.append(f'[{display_name}]')
|
|
90
87
|
|
|
91
|
-
# context window size: 200K / 1M
|
|
92
|
-
ctx_window = data.get('context_window') or {}
|
|
93
|
-
ctx_size = ctx_window.get('context_window_size')
|
|
94
|
-
if ctx_size:
|
|
95
|
-
header.append(format_context_size(int(ctx_size)))
|
|
96
|
-
|
|
97
88
|
# effort level
|
|
98
89
|
effort = data.get('effort') or {}
|
|
99
90
|
effort_level = effort.get('level', '')
|
|
@@ -101,6 +92,7 @@ def render_output(raw: str) -> None:
|
|
|
101
92
|
header.append(effort_level)
|
|
102
93
|
|
|
103
94
|
# ctx usg %
|
|
95
|
+
ctx_window = data.get('context_window') or {}
|
|
104
96
|
ctx_pct = round(ctx_window.get('used_percentage') or 0)
|
|
105
97
|
metrics.append('ctx used ' + pct_color(ctx_pct) + str(ctx_pct) + '%' + RESET)
|
|
106
98
|
|
|
@@ -251,6 +251,10 @@ def update_patterns(date_str: str) -> None:
|
|
|
251
251
|
continue
|
|
252
252
|
|
|
253
253
|
registered = _parse_session_date(pattern['registered_date'])
|
|
254
|
+
if registered is None:
|
|
255
|
+
# registered_date が parse 不能ならパターンを保持して継続(クラッシュ回避)
|
|
256
|
+
active.append(pattern)
|
|
257
|
+
continue
|
|
254
258
|
days_elapsed = (today - registered).days
|
|
255
259
|
|
|
256
260
|
if days_elapsed >= EXPIRY_DAYS:
|
|
@@ -78,8 +78,8 @@ _SAFE_PAYLOAD_FIELDS = frozenset({
|
|
|
78
78
|
# U+2028 (LINE SEPARATOR) / U+2029 (PARAGRAPH SEPARATOR) の定数 (sec-H-1)
|
|
79
79
|
# ensure_ascii=False の json.dumps はこれらをエスケープしないため、
|
|
80
80
|
# _append_log で明示的に \\u2028 / \\u2029 へ置換する。
|
|
81
|
-
_U2028 = '
'
|
|
82
|
-
_U2029 = '
'
|
|
81
|
+
_U2028 = '
' # LINE SEPARATOR
|
|
82
|
+
_U2029 = '
' # PARAGRAPH SEPARATOR
|
|
83
83
|
|
|
84
84
|
|
|
85
85
|
def _now_iso() -> str:
|
{claude_code_conductor-2.8.0 → claude_code_conductor-2.10.0}/.claude/hooks/worktree_guard.py
RENAMED
|
@@ -5,6 +5,10 @@ PO_WORKTREE_GUARD=1 が設定されている場合のみ動作する。
|
|
|
5
5
|
worktree 内で実装タスクを実行するワークフロー(parallel-agents skill が
|
|
6
6
|
isolation:"worktree" 付きで起動する agent など)が事前にこの env を設定して有効化する。
|
|
7
7
|
Write / Edit ツールの対象パスが CWD(worktree ルート)外であればブロックする。
|
|
8
|
+
|
|
9
|
+
NOTE [SR-V-002]: env 未設定時にガードが無効化されるリスクは parallel-agents/SKILL.md
|
|
10
|
+
で `PO_WORKTREE_GUARD=1` 設定を必須化することで運用上対処する。CWD parts チェック
|
|
11
|
+
単独で自動有効化する設計変更は次回 major bump で検討予定。
|
|
8
12
|
"""
|
|
9
13
|
|
|
10
14
|
import json
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
},
|
|
64
64
|
"statusLine": {
|
|
65
65
|
"type": "command",
|
|
66
|
-
"command": "python \"$CLAUDE_PROJECT_DIR/.claude/hooks/statusline.py\""
|
|
66
|
+
"command": "python \"${CLAUDE_PROJECT_DIR}/.claude/hooks/statusline.py\""
|
|
67
67
|
},
|
|
68
68
|
"hooks": {
|
|
69
69
|
"PreToolUse": [
|
|
@@ -140,6 +140,11 @@
|
|
|
140
140
|
"type": "command",
|
|
141
141
|
"command": "python",
|
|
142
142
|
"args": ["${CLAUDE_PROJECT_DIR}/.claude/hooks/select_tier.py"]
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"type": "command",
|
|
146
|
+
"command": "python",
|
|
147
|
+
"args": ["${CLAUDE_PROJECT_DIR}/.claude/hooks/recall_inject.py"]
|
|
143
148
|
}
|
|
144
149
|
]
|
|
145
150
|
}
|