code-context-control 2.44.1__py3-none-any.whl → 2.46.0__py3-none-any.whl

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.
Files changed (42) hide show
  1. cli/c3.py +9 -2
  2. cli/commands/common.py +1 -1
  3. cli/docs.html +3 -3
  4. cli/guide/index.html +31 -1
  5. cli/guide/tools.html +95 -1
  6. cli/hook_artifact.py +67 -0
  7. cli/hook_dispatch.py +1 -0
  8. cli/hub_server.py +376 -0
  9. cli/hub_ui/app.js +20 -8
  10. cli/hub_ui/components/drill_artifacts.js +218 -0
  11. cli/hub_ui/components/drill_panel.js +4 -0
  12. cli/hub_ui/components/drill_tasks.js +325 -0
  13. cli/hub_ui/components/drill_views.js +1 -0
  14. cli/hub_ui/components/project_card.js +7 -0
  15. cli/hub_ui/components/task_board.js +277 -0
  16. cli/hub_ui/components/topbar.js +22 -1
  17. cli/mcp_server.py +81 -3
  18. cli/server.py +248 -4
  19. cli/tools/artifacts.py +160 -0
  20. cli/tools/edit.py +11 -0
  21. cli/tools/tasks.py +244 -0
  22. cli/ui/api.js +4 -1
  23. cli/ui/app.js +4 -0
  24. cli/ui/components/tasks.js +287 -0
  25. cli/ui/pm_shared.js +74 -0
  26. {code_context_control-2.44.1.dist-info → code_context_control-2.46.0.dist-info}/METADATA +6 -4
  27. {code_context_control-2.44.1.dist-info → code_context_control-2.46.0.dist-info}/RECORD +41 -31
  28. core/config.py +7 -4
  29. services/agents.py +44 -44
  30. services/artifact_defs.py +237 -0
  31. services/artifact_store.py +644 -0
  32. services/claude_md.py +19 -1
  33. services/git_context.py +2 -1
  34. services/retention.py +29 -2
  35. services/runtime.py +13 -4
  36. services/session_manager.py +2 -1
  37. services/task_store.py +578 -0
  38. services/version_tracker.py +0 -263
  39. {code_context_control-2.44.1.dist-info → code_context_control-2.46.0.dist-info}/WHEEL +0 -0
  40. {code_context_control-2.44.1.dist-info → code_context_control-2.46.0.dist-info}/entry_points.txt +0 -0
  41. {code_context_control-2.44.1.dist-info → code_context_control-2.46.0.dist-info}/licenses/LICENSE +0 -0
  42. {code_context_control-2.44.1.dist-info → code_context_control-2.46.0.dist-info}/top_level.txt +0 -0
cli/c3.py CHANGED
@@ -85,7 +85,7 @@ console = Console() if HAS_RICH else None
85
85
  # Config
86
86
  CONFIG_DIR = ".c3"
87
87
  CONFIG_FILE = ".c3/config.json"
88
- __version__ = "2.44.1"
88
+ __version__ = "2.46.0"
89
89
 
90
90
 
91
91
  def _command_deps() -> CommandDeps:
@@ -239,7 +239,7 @@ _C3_MCP_ALLOW = [
239
239
  "mcp__c3__c3_memory", "mcp__c3__c3_validate", "mcp__c3__c3_edit",
240
240
  "mcp__c3__c3_agent", "mcp__c3__c3_delegate", "mcp__c3__c3_edits",
241
241
  "mcp__c3__c3_impact", "mcp__c3__c3_shell", "mcp__c3__c3_bitbucket",
242
- "mcp__c3__c3_project",
242
+ "mcp__c3__c3_project", "mcp__c3__c3_task", "mcp__c3__c3_artifacts",
243
243
  ]
244
244
 
245
245
  # Obsolete MCP tool names from earlier C3 versions. `c3 permissions clean`
@@ -4980,6 +4980,11 @@ def cmd_install_mcp(args):
4980
4980
  ) from e
4981
4981
 
4982
4982
  print(f"Wrote {mcp_config_path}")
4983
+ if not profile.config_path_global:
4984
+ # Self-report to the artifact tracker: attribute this write to C3
4985
+ # instead of letting it surface as anonymous out-of-band drift.
4986
+ from services.artifact_defs import note_pending_write
4987
+ note_pending_write(target, profile.config_path, "install_mcp")
4983
4988
  if profile.name in {"codex", "gemini"}:
4984
4989
  _ensure_project_session_configs(target, server_script, primary_profile=profile.name, c3_mcp_exe=c3_mcp_exe)
4985
4990
  _ensure_global_session_fallbacks(server_script, c3_mcp_exe=c3_mcp_exe)
@@ -5181,6 +5186,8 @@ def cmd_install_mcp(args):
5181
5186
  json.dump(settings, f, indent=2)
5182
5187
 
5183
5188
  print(f"Wrote {settings_path}")
5189
+ from services.artifact_defs import note_pending_write
5190
+ note_pending_write(target, profile.settings_path, "install_mcp")
5184
5191
  print(f" Hooks ({hook_event}): dispatcher (1 spawn/event) — filter/ghost/read-guard/ledger/unlock/signal via cli/hook_dispatch.py posttool")
5185
5192
  print(f" Hooks ({pre_event}): dispatcher — {read_matcher}/{grep_matcher}/{glob_matcher}/{edit_matcher}/{write_matcher} (c3 enforcement)")
5186
5193
  print(" Hooks (Stop): dispatcher — session_stats + auto_snapshot + terse_advisor")
cli/commands/common.py CHANGED
@@ -202,7 +202,7 @@ def cmd_claudemd(args, deps: CommandDeps):
202
202
  output_path = Path(project_path) / instructions_file
203
203
  # Wrap in the C3 managed block; preserve user content outside it.
204
204
  from services.claude_md import write_c3_instruction_doc
205
- write_c3_instruction_doc(output_path, content)
205
+ write_c3_instruction_doc(output_path, content, project_path=project_path)
206
206
  print(f"{instructions_file} saved to {output_path} ({tokens} tokens)")
207
207
 
208
208
  elif args.claudemd_cmd == "check":
cli/docs.html CHANGED
@@ -1148,7 +1148,7 @@ python cli/c3.py install-mcp . gemini</code></pre>
1148
1148
 
1149
1149
  <!-- ─── MCP Tools ───────────────────── -->
1150
1150
  <h2 id="mcp-tools">MCP Tools Reference</h2>
1151
- <p>C3 exposes 16 MCP tools. All core tools work without Ollama; delegate requires it. The Bitbucket integration is optional and activated via <code>c3 bitbucket login</code>.</p>
1151
+ <p>C3 exposes 18 MCP tools. All core tools work without Ollama; delegate requires it. The Bitbucket integration is optional and activated via <code>c3 bitbucket login</code>.</p>
1152
1152
 
1153
1153
  <h3>Discovery &amp; Compression</h3>
1154
1154
  <table>
@@ -2672,7 +2672,7 @@ c3 session load | claude --resume</code></pre>
2672
2672
  <strong>MCP Proxy</strong> ──── Dynamic tool filtering + context injection (optional, opt-in via MCP config)
2673
2673
  | Subprocess stdio (NDJSON)
2674
2674
  v
2675
- <strong>C3 MCP Server</strong> ─── 26 tools: search, compress, session, memory, CLAUDE.md, context, hybrid, notifications...
2675
+ <strong>C3 MCP Server</strong> ─── 18 tools: search, compress, session, memory, edits, tasks, artifacts, delegate...
2676
2676
  |
2677
2677
  +── <em>Compression</em> <em>Smart Index</em>
2678
2678
  | AST Summary TF-IDF + Code Structure
@@ -2861,7 +2861,7 @@ c3 session load | claude --resume</code></pre>
2861
2861
  <pre><code>claude-companion/
2862
2862
  cli/
2863
2863
  c3.py # CLI entry point (all commands)
2864
- mcp_server.py # MCP server (30 tools via FastMCP)
2864
+ mcp_server.py # MCP server (18 tools via FastMCP)
2865
2865
  mcp_proxy.py # Optional advanced MCP proxy (tool filtering)
2866
2866
  server.py # Flask web server + REST API
2867
2867
  ui.html # Single-page React dashboard
cli/guide/index.html CHANGED
@@ -309,7 +309,7 @@
309
309
  <a href="tools.html" class="nav-card">
310
310
  <span class="nav-card-icon">🔧</span>
311
311
  <div class="nav-card-title">Tools Reference</div>
312
- <div class="nav-card-desc">Full reference for all 16 MCP tools — params, examples, notes</div>
312
+ <div class="nav-card-desc">Full reference for all 18 MCP tools — params, examples, notes</div>
313
313
  </a>
314
314
  <a href="bitbucket.html" class="nav-card">
315
315
  <span class="nav-card-icon">🪣</span>
@@ -509,6 +509,36 @@
509
509
  <td><span class="badge badge-yellow">Ledger</span></td>
510
510
  <td>Edit ledger: history, versions, audit trail</td>
511
511
  </tr>
512
+ <tr>
513
+ <td><code>c3_impact</code></td>
514
+ <td><span class="badge badge-purple">Analysis</span></td>
515
+ <td>Blast-radius check before editing shared symbols</td>
516
+ </tr>
517
+ <tr>
518
+ <td><code>c3_shell</code></td>
519
+ <td><span class="badge badge-green">Execute</span></td>
520
+ <td>Structured shell exec — tests, git, build (auto-filtered)</td>
521
+ </tr>
522
+ <tr>
523
+ <td><code>c3_task</code></td>
524
+ <td><span class="badge badge-yellow">PM</span></td>
525
+ <td>Durable tasks, milestones, and decision notes per project</td>
526
+ </tr>
527
+ <tr>
528
+ <td><code>c3_artifacts</code></td>
529
+ <td><span class="badge badge-yellow">Config</span></td>
530
+ <td>Agent-config tracking: history, diff &amp; restore for CLAUDE.md, settings, MCP configs, skills</td>
531
+ </tr>
532
+ <tr>
533
+ <td><code>c3_bitbucket</code></td>
534
+ <td><span class="badge badge-orange">SCM</span></td>
535
+ <td>Bitbucket Data Center: PRs, branches, builds, repo admin</td>
536
+ </tr>
537
+ <tr>
538
+ <td><code>c3_project</code></td>
539
+ <td><span class="badge badge-cyan">Multi-project</span></td>
540
+ <td>Discover &amp; operate on other c3-installed projects</td>
541
+ </tr>
512
542
  </tbody>
513
543
  </table>
514
544
 
cli/guide/tools.html CHANGED
@@ -118,9 +118,11 @@
118
118
  <div class="sidebar-section">
119
119
  <div class="sidebar-label">Ledger</div>
120
120
  <a href="#c3_edits" class="sidebar-link"><span class="icon">📝</span> c3_edits</a>
121
+ <a href="#c3_artifacts" class="sidebar-link"><span class="icon">🛡️</span> c3_artifacts</a>
121
122
  </div>
122
123
  <div class="sidebar-section">
123
124
  <div class="sidebar-label">SCM</div>
125
+ <a href="#c3_task" class="sidebar-link"><span class="icon">✅</span> c3_task</a>
124
126
  <a href="#c3_bitbucket" class="sidebar-link"><span class="icon">🪣</span> c3_bitbucket</a>
125
127
  </div>
126
128
  <div class="sidebar-section">
@@ -134,7 +136,7 @@
134
136
 
135
137
  <div class="page-hero">
136
138
  <h1>Tools Reference</h1>
137
- <p>Complete reference for all 16 C3 MCP tools — parameters, actions, examples, and usage notes.</p>
139
+ <p>Complete reference for all 18 C3 MCP tools — parameters, actions, examples, and usage notes.</p>
138
140
  </div>
139
141
 
140
142
  <!-- TOC -->
@@ -155,6 +157,8 @@
155
157
  <a href="#c3_delegate" class="toc-item">c3_delegate <span class="cat">AI</span></a>
156
158
  <a href="#c3_agent" class="toc-item">c3_agent <span class="cat">AI</span></a>
157
159
  <a href="#c3_edits" class="toc-item">c3_edits <span class="cat">Ledger</span></a>
160
+ <a href="#c3_task" class="toc-item">c3_task <span class="cat">PM</span></a>
161
+ <a href="#c3_artifacts" class="toc-item">c3_artifacts <span class="cat">Config</span></a>
158
162
  <a href="#c3_bitbucket" class="toc-item">c3_bitbucket <span class="cat">SCM</span></a>
159
163
  <a href="#c3_project" class="toc-item">c3_project <span class="cat">Multi-project</span></a>
160
164
  </div>
@@ -1090,6 +1094,96 @@ c3_edits(action=<span class="str">'history'</span>, branch=<span class="str">'fe
1090
1094
  </div>
1091
1095
  </div>
1092
1096
 
1097
+ <!-- c3_task -->
1098
+ <div class="tool-card" id="c3_task">
1099
+ <div class="tool-card-header">
1100
+ <span class="tool-name">c3_task</span>
1101
+ <div class="tag-row">
1102
+ <span class="badge badge-purple">pm</span>
1103
+ <span class="badge">v2.45.0</span>
1104
+ </div>
1105
+ <span class="tool-tagline">Durable per-project tasks, milestones, and decision notes — the project-management layer</span>
1106
+ </div>
1107
+ <div class="tool-card-body">
1108
+ <p class="tool-desc">Every C3 project carries a PM store at <code>.c3/pm/pm.json</code>: tasks with status (backlog / in_progress / blocked / done), priority (p0–p3), due dates, tags, and code links (files, commits, edit-ledger entries); milestones with computed progress; and a decision-note log separate from AI memory. The same store powers the Hub's Tasks tab and kanban board, the per-project UI's Tasks tab, and this tool. Task ids accept any unique prefix (≥4 chars); milestones resolve by id or unique name. Read actions are plan-mode-safe. Ephemeral session plans stay in <code>c3_session(action='plan')</code>.</p>
1109
+
1110
+ <h4>Actions</h4>
1111
+ <table class="params-table">
1112
+ <thead><tr><th>Action</th><th>Group</th><th>Description</th></tr></thead>
1113
+ <tbody>
1114
+ <tr><td class="param-name">add</td><td>Write</td><td class="param-desc">Create a task: <code>title</code> + optional description / priority / due_date (YYYY-MM-DD) / tags (CSV) / milestone</td></tr>
1115
+ <tr><td class="param-name">update, done, archive</td><td>Write</td><td class="param-desc"><code>task_id</code> + changed fields; <code>done</code> stamps completion; archive keeps history</td></tr>
1116
+ <tr><td class="param-name">list, get, board</td><td>Read</td><td class="param-desc">Filtered list (status/priority/tags/milestone/query), full detail, kanban columns + milestone progress</td></tr>
1117
+ <tr><td class="param-name">link, unlink</td><td>Write</td><td class="param-desc"><code>task_id</code> + <code>link_type</code> (file | commit | edit) + <code>ref</code> — tie tasks to the code they touch</td></tr>
1118
+ <tr><td class="param-name">milestone_add / _update / _list / _archive</td><td>Both</td><td class="param-desc">Milestones with target dates; list shows progress %; archive detaches its tasks</td></tr>
1119
+ <tr><td class="param-name">note_add, note_list</td><td>Both</td><td class="param-desc">Dated notes; <code>kind='decision'</code> for the decision log</td></tr>
1120
+ </tbody>
1121
+ </table>
1122
+
1123
+ <h4>Examples</h4>
1124
+ <pre><code><span class="com"># Capture a task while coding</span>
1125
+ c3_task(action=<span class="str">'add'</span>, title=<span class="str">'Harden pm.json against concurrent writes'</span>,
1126
+ priority=<span class="str">'p1'</span>, tags=<span class="str">'pm,backend'</span>)
1127
+
1128
+ <span class="com"># Tie it to the code it touches, then finish it</span>
1129
+ c3_task(action=<span class="str">'link'</span>, task_id=<span class="str">'a1b2c3d4'</span>, link_type=<span class="str">'file'</span>, ref=<span class="str">'services/task_store.py'</span>)
1130
+ c3_task(action=<span class="str">'done'</span>, task_id=<span class="str">'a1b2'</span>)
1131
+
1132
+ <span class="com"># Milestones + the board</span>
1133
+ c3_task(action=<span class="str">'milestone_add'</span>, name=<span class="str">'v2.46'</span>, target_date=<span class="str">'2026-08-01'</span>)
1134
+ c3_task(action=<span class="str">'board'</span>)
1135
+
1136
+ <span class="com"># Record a decision</span>
1137
+ c3_task(action=<span class="str">'note_add'</span>, note=<span class="str">'Kanban rank uses float sort keys'</span>, kind=<span class="str">'decision'</span>)</code></pre>
1138
+
1139
+ <p class="tool-desc"><strong>Surfaces:</strong> the Hub shows a Tasks tab per project (drill-in panel), <code>☑ N</code> open-task chips on cards, and a global kanban board behind the Projects | Tasks switcher. Hub mutations audit <code>pm_write</code> events to the project's activity log. Disable with <code>hybrid.pm.enabled=false</code>.</p>
1140
+ </div>
1141
+ </div>
1142
+
1143
+ <!-- c3_artifacts -->
1144
+ <div class="tool-card" id="c3_artifacts">
1145
+ <div class="tool-card-header">
1146
+ <span class="tool-name">c3_artifacts</span>
1147
+ <div class="tag-row">
1148
+ <span class="badge badge-purple">agent config</span>
1149
+ <span class="badge">v2.46.0</span>
1150
+ </div>
1151
+ <span class="tool-tagline">Version history, diff, and restore for the files that shape the agent itself</span>
1152
+ </div>
1153
+ <div class="tool-card-body">
1154
+ <p class="tool-desc">C3 tracks every <em>agent-affecting artifact</em> across all IDEs it knows — instruction docs (<code>CLAUDE.md</code>, <code>AGENTS.md</code>, <code>GEMINI.md</code>, <code>.cursorrules</code>, <code>.github/copilot-instructions.md</code>), settings/hooks (<code>.claude/settings*.json</code>), MCP configs (<code>.mcp.json</code>, <code>.codex/config.toml</code>, <code>.gemini/settings.json</code>, …), and Claude Code extensions (<code>.claude/</code> skills, agents, commands, plugins). Content-addressed snapshots land in <code>.c3/agent_artifacts/</code>; every change is a history event with attribution: <code>c3_edit</code> (this session), <code>hook</code> (native Edit/Write), <code>scan</code> (out-of-band — you edited it in an editor), <code>install_mcp</code> (C3 regenerating its own files), or <code>restore</code>. A background agent scans every ~2 min and warns only when <em>settings or MCP configs</em> change outside C3. Artifact refs accept an id (<code>skill:browcontrol</code>), unique prefix, or plain path (<code>CLAUDE.md</code>). Everything except <code>restore</code> is plan-mode-safe.</p>
1155
+
1156
+ <h4>Actions</h4>
1157
+ <table class="params-table">
1158
+ <thead><tr><th>Action</th><th>Group</th><th>Description</th></tr></thead>
1159
+ <tbody>
1160
+ <tr><td class="param-name">scan</td><td>Read</td><td class="param-desc">Refresh the inventory; captures out-of-band changes immediately (idempotent — unchanged files emit nothing)</td></tr>
1161
+ <tr><td class="param-name">list</td><td>Read</td><td class="param-desc">Inventory with versions; filters: <code>cls</code> (instructions | settings | mcp | skill | agent | command | plugin), <code>provider</code></td></tr>
1162
+ <tr><td class="param-name">history</td><td>Read</td><td class="param-desc">Change events newest-first, with source attribution; <code>artifact</code> optional (all events when omitted)</td></tr>
1163
+ <tr><td class="param-name">show</td><td>Read</td><td class="param-desc">Content at a version (<code>version=0</code> → live file)</td></tr>
1164
+ <tr><td class="param-name">diff</td><td>Read</td><td class="param-desc">Unified diff <code>version</code> → <code>against</code> (omit <code>against</code> to diff vs live)</td></tr>
1165
+ <tr><td class="param-name">restore</td><td>Write</td><td class="param-desc">Write a prior version's exact bytes back. Forward-only (new version + history event, never rewrites), cross-logged to the edit ledger, warns on settings/managed-block files. Resurrects deleted artifacts.</td></tr>
1166
+ <tr><td class="param-name">status</td><td>Read</td><td class="param-desc">Tracked counts by class, out-of-band changes, last scan, pending signals</td></tr>
1167
+ </tbody>
1168
+ </table>
1169
+
1170
+ <h4>Examples</h4>
1171
+ <pre><code><span class="com"># What changed behind my back?</span>
1172
+ c3_artifacts(action=<span class="str">'status'</span>)
1173
+ c3_artifacts(action=<span class="str">'history'</span>, limit=<span class="num">10</span>)
1174
+
1175
+ <span class="com"># Someone (or something) touched the hooks — inspect and roll back</span>
1176
+ c3_artifacts(action=<span class="str">'diff'</span>, artifact=<span class="str">'.claude/settings.local.json'</span>, version=<span class="num">3</span>)
1177
+ c3_artifacts(action=<span class="str">'restore'</span>, artifact=<span class="str">'settings:.claude/settings.local.json'</span>, version=<span class="num">3</span>)
1178
+
1179
+ <span class="com"># Track a skill's evolution</span>
1180
+ c3_artifacts(action=<span class="str">'history'</span>, artifact=<span class="str">'skill:browcontrol'</span>)
1181
+ c3_artifacts(action=<span class="str">'show'</span>, artifact=<span class="str">'skill:browcontrol'</span>, version=<span class="num">2</span>)</code></pre>
1182
+
1183
+ <p class="tool-desc"><strong>Surfaces:</strong> the Hub drill-in panel gets an Artifacts tab (class-grouped inventory, per-version timeline, diff viewer, two-step restore); REST at <code>/api/artifacts*</code> per project and <code>/api/projects/artifacts*</code> on the hub. Changes made through <code>c3_edit</code> and <code>install-mcp</code> self-attribute — only genuinely foreign edits show as <code>scan</code>. Disable with <code>hybrid.agent_artifacts.enabled=false</code>.</p>
1184
+ </div>
1185
+ </div>
1186
+
1093
1187
  <!-- c3_bitbucket -->
1094
1188
  <div class="tool-card" id="c3_bitbucket">
1095
1189
  <div class="tool-card-header">
cli/hook_artifact.py ADDED
@@ -0,0 +1,67 @@
1
+ """PostToolUse hook: signal agent-artifact writes for attributed capture.
2
+
3
+ Fires alongside the edit ledger on Edit/Write/NotebookEdit. When the touched
4
+ file classifies as an agent-affecting artifact (instruction docs, settings/
5
+ hooks, MCP configs, .claude skills/agents/commands — see
6
+ services/artifact_defs), appends a one-line pending signal to
7
+ .c3/agent_artifacts/pending.jsonl.
8
+
9
+ Performance: no hashing, no manifest lock — the ArtifactScanAgent consumes
10
+ signals asynchronously and attributes the resulting history events to
11
+ source='hook'. Silent hook: never emits user-visible output.
12
+ """
13
+
14
+ import json
15
+ import sys
16
+ from pathlib import Path
17
+
18
+ # Add project root to path for imports
19
+ _project_root = str(Path(__file__).resolve().parent.parent)
20
+ if _project_root not in sys.path:
21
+ sys.path.insert(0, _project_root)
22
+
23
+ from cli._hook_utils import get_tool_input_path, log_hook_error, normalize_tool_name # noqa: E402
24
+ from services.artifact_defs import classify_path, note_pending_write # noqa: E402
25
+
26
+
27
+ def run(payload: dict, project_path: Path | None = None) -> None:
28
+ """Core logic — importable by the dispatcher and tests. Always None."""
29
+ tool_name = normalize_tool_name(payload.get("tool_name", ""))
30
+ if tool_name not in ("Edit", "Write", "NotebookEdit"):
31
+ return None
32
+
33
+ file_path = get_tool_input_path(payload)
34
+ if not file_path:
35
+ return None
36
+
37
+ if project_path is None:
38
+ project_path = Path.cwd()
39
+ if not (project_path / ".c3").exists():
40
+ return None # Not a C3 project
41
+
42
+ try:
43
+ rel = str(Path(file_path).resolve().relative_to(project_path.resolve()))
44
+ except (ValueError, OSError):
45
+ return None # outside the project → not a project artifact
46
+
47
+ if classify_path(rel) is None:
48
+ return None
49
+
50
+ note_pending_write(project_path, rel, "hook",
51
+ session_id=payload.get("session_id", "") or "",
52
+ tool=tool_name)
53
+ return None
54
+
55
+
56
+ def main():
57
+ try:
58
+ raw = sys.stdin.read()
59
+ if not raw.strip():
60
+ return
61
+ run(json.loads(raw))
62
+ except Exception as _e:
63
+ log_hook_error("hook_artifact", _e)
64
+
65
+
66
+ if __name__ == "__main__":
67
+ main()
cli/hook_dispatch.py CHANGED
@@ -96,6 +96,7 @@ def _routes(event: str, raw_tool: str, norm_tool: str):
96
96
  yield "hook_c3_signal"
97
97
  if norm_tool in _LEDGER_TOOLS:
98
98
  yield "hook_edit_ledger"
99
+ yield "hook_artifact"
99
100
  if raw_tool in _GHOST_TOOLS:
100
101
  yield "hook_ghost_files"
101
102
  elif event == "stop":