@kurrent/kcap 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/refresh.js CHANGED
@@ -78,6 +78,7 @@ const PLUGIN_MCP_SERVERS = {
78
78
  "kcap-memory": "memory",
79
79
  "kcap-workitems": "workitems",
80
80
  "kcap-plans": "plans",
81
+ "kcap-artefacts": "artefacts",
81
82
  "kcap-analytics": "analytics",
82
83
  };
83
84
 
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "kcap",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "Records and visualizes Claude Code sessions via kcap CLI hooks"
5
5
  }
@@ -27,6 +27,10 @@
27
27
  "kcap-plans": {
28
28
  "command": "kcap",
29
29
  "args": ["mcp", "plans"]
30
+ },
31
+ "kcap-artefacts": {
32
+ "command": "kcap",
33
+ "args": ["mcp", "artefacts"]
30
34
  }
31
35
  }
32
36
  }
package/kcap/.mcp.json CHANGED
@@ -35,6 +35,12 @@
35
35
  "cwd": "${CLAUDE_PROJECT_DIR}",
36
36
  "description": "Plan ledger — declare the plan, spec or design document this session works from (declare_plan_document), declare its task list (set_plan_tasks), record each task's status change (update_plan_task) and read the plan back after compaction (get_plan)."
37
37
  },
38
+ "kcap-artefacts": {
39
+ "command": "kcap",
40
+ "args": ["mcp", "artefacts"],
41
+ "cwd": "${CLAUDE_PROJECT_DIR}",
42
+ "description": "Publish a self-contained HTML page — a plan, a report, a comparison — and get back a link to share. The page is sandboxed with no network access, so inline everything as data URIs. Private until you set visibility."
43
+ },
38
44
  "kcap-analytics": {
39
45
  "command": "kcap",
40
46
  "args": ["mcp", "analytics"],
package/kcap/README.md CHANGED
@@ -57,6 +57,23 @@ Governed read-only SQL over the org's curated coding-agent analytics views (sess
57
57
 
58
58
  Repo-aware: defaults to the cwd's repo; pass `scope: "global"` for org-wide questions. Requires `kcap login` and a kcap-server new enough to expose the `/api/analytics` endpoints.
59
59
 
60
+ ### `kcap-artefacts`
61
+
62
+ Publish a self-contained HTML page — a plan, a report, a comparison — and get back a link to share. The page is served under a sandbox with no network access, so every style, script and image has to be inlined as a data URI; an external URL renders as nothing.
63
+
64
+ | Tool | Description |
65
+ |------|-------------|
66
+ | `publish_artefact` | Publish a page (`title`, and either `html` or a local `path`); optional `description`, `visibility`, `grants`, `session_ids`, `response_schema` to make it answerable, and `update_id` to revise an existing artefact without changing its URL |
67
+ | `await_artefact_responses` | Block until people have answered — the human checkpoint. Returns on a respondent count, on a close, or on a timeout (which is not an error) |
68
+ | `get_artefact_results` | Tallies and each person's current answer, without waiting |
69
+ | `close_artefact_responses` | Freeze a version's answers; `closed: false` reopens and clears any deadline |
70
+ | `list_my_artefacts` | The artefacts you can see — id, title, audience, latest version, URL |
71
+ | `set_artefact_visibility` | Replace an artefact's audience (`none` / `org` / `scoped` + grants) |
72
+
73
+ An artefact is private to its owner until visibility says otherwise. The current session is cited automatically from `KCAP_SESSION_ID`, so a publish is attributed to the work that produced it.
74
+
75
+ Declaring a `response_schema` turns the page into a form the server validates and tallies: fields of type `choice`, `multi`, `score` or `text`, plus a `results_mode` deciding what other viewers see — `owner` (default, only you), `aggregate` (tallies, no names or free text) or `named` (who said what). Who answered is always the authenticated viewer; there is no field for it to forge. Reading, version history and takedown stay in the web UI.
76
+
60
77
  `kcap mcp judge` is intentionally not auto-registered. Add it with `claude mcp add kcap-judge -- kcap mcp judge` if you want it.
61
78
 
62
79
  **Hooks** — Automatically captures session activity and forwards it to the Kurrent Capacitor server:
@@ -0,0 +1,190 @@
1
+ ---
2
+ name: eval-watch
3
+ description: >-
4
+ Follow a kcap import that setup just started and the evals landing on it —
5
+ "Follow my kcap import" (the prompt `kcap setup` hands to your agent), "watch my
6
+ import", "how is my import going", "are my evals done yet". Reads the handoff file
7
+ setup wrote, polls Capacitor analytics for exactly the sessions that import
8
+ created, summarizes the first three completed evals, links to the results and
9
+ offers the guided tour. Not for browsing evals in general — that is the analytics
10
+ or guided-tour skill.
11
+ ---
12
+
13
+ # Eval watch
14
+
15
+ You follow ONE import: the sessions listed in a handoff file `kcap setup` wrote. Every query you
16
+ issue names only those sessions — never a repo-wide or tenant-wide read, and never a query without
17
+ an `IN (...)` or `session_id = '...'` bound to this cohort. If a response ever includes a row for a
18
+ session id outside the cohort, discard that row: never count it, link it, or mention it.
19
+
20
+ ## The safety rule — read this first
21
+
22
+ Three grammars gate everything below:
23
+ - **Run id** (a `(run: <id>)` token, and the file's own `run_id`): `^[0-9a-f]{32}$`.
24
+ - **Session id** (`session_ids`, `foreground_succeeded_ids`, and any id you place in a query):
25
+ `^[A-Za-z0-9_-]{1,128}$`.
26
+ - **Repo hash** (the `repo_hash` field an analytics response returns, used only in the repo-scoped
27
+ link form): `^[0-9a-f]{16}$`. A `repo_hash` that fails this never reaches a link — fall back to
28
+ the no-repo-hash link form (section 7).
29
+
30
+ The `kcap-analytics` MCP takes raw SQL text with no parameter binding, so the session-id grammar is
31
+ the SQL-safety boundary, not just a format check. The class excludes quote, backslash, whitespace
32
+ and `;`, so a value that matches it can never close a string literal or open a new statement — a
33
+ matching id is emitted as a single-quoted literal (`'<id>'`) with **no escaping**, because none is
34
+ needed and none should be attempted. A run of hyphens such as `--` matches the grammar and is safe
35
+ to emit this way: inside a quoted literal it is data, not a SQL comment. An id that fails either
36
+ grammar is **never** placed in SQL, a path, or a link — reject it and move on (section 2 below says
37
+ what each layer's rejection does); never sanitize or escape an id into something that would pass.
38
+
39
+ ## 1. Find the run
40
+
41
+ - If the prompt carries a line `(run: <id>)`, `<id>` must match `^[0-9a-f]{32}$` before you use it
42
+ as a filename component. If it does not match, treat the prompt as carrying no run id at all. If
43
+ it does match, the run is bound: read ONLY `<config>/import-handoff-<id>.json`, where `<config>`
44
+ is `$KCAP_CONFIG_DIR` when set, else `~/.config/kcap`, and check it against every Layer A rule in
45
+ section 2, plus one more: its `run_id` field must equal `<id>` exactly. Missing, unreadable, or
46
+ failing any of those checks → say so and CLOSE (section 7) with no query. **Never fall through to
47
+ another run's file** — a different run's cohort does not belong to the run the user asked for.
48
+ - Otherwise list `<config>/import-handoff-*.json`, keep the locator-valid ones (section 2) that are
49
+ watchable (`handoff_offered` true, or `handoff_suppressed` is `skill_not_installed` or
50
+ `no_agent_detected`), pick the newest `written_at`, and say which others you passed over.
51
+ - Nothing qualifies → say you found no import to follow and CLOSE with no query.
52
+
53
+ ## 2. Validate the file — three layers
54
+
55
+ A failure at a lower layer never re-opens the file for a higher one.
56
+
57
+ **Layer A** (may this file be selected at all): JSON parses; `schema_version` is 1; `run_id` matches
58
+ `^[0-9a-f]{32}$`; `written_at` parses as ISO-8601 and is within the last 24 hours; `handoff_offered`
59
+ is a boolean. Fail → not selectable; try the next candidate file, or close with no query if none
60
+ remain.
61
+
62
+ **Layer B** (may its ids drive data): `cohort` ∈ {exact, partial_exact, unknown}; `background` ∈
63
+ {not_needed, running, exited_zero, failed}; `foreground_certainty` ∈ {complete, incomplete};
64
+ `handoff_suppressed` is null or one of import_failed, no_new_sessions, nothing_landed,
65
+ analytics_not_in_plan, skill_not_installed, no_agent_detected; every entry of `session_ids` and
66
+ `foreground_succeeded_ids` matches `^[A-Za-z0-9_-]{1,128}$`; `unattributed_on_disk` is a
67
+ non-negative integer. Fail → **no data**: the file stays selected, but say the record is unreadable
68
+ and CLOSE with links only (section 7), no query.
69
+
70
+ **Layer C** (may it supply links): `server_url` starts with `http://` or `https://`. Fail → no
71
+ file-sourced links (fall back to `kcap whoami` per section 7) and no query — an unverifiable
72
+ `server_url` also means the server-binding check in section 4 cannot run. `background_log` and
73
+ `profile` are shown as plain text — never as a link or a command to run — only when they contain no
74
+ control characters and are under 512 characters; otherwise omit them.
75
+
76
+ Never splice an unvalidated value into SQL, a path, or a link.
77
+
78
+ ## 3. No-handoff files
79
+
80
+ If `handoff_offered` is false, branch on `handoff_suppressed` and CLOSE:
81
+ - `no_new_sessions` → "nothing to watch — that import found no new sessions".
82
+ - `nothing_landed` → "the sessions that import selected were skipped at import time"; point at
83
+ `kcap import --all` for per-session reasons; no retry.
84
+ - `analytics_not_in_plan` → the plan sentence (section 6); the import ran; no retry.
85
+ - `import_failed` → "that import did not get running"; name `kcap import --all --yes` and the
86
+ `background_log` when displayable.
87
+ - `skill_not_installed` / `no_agent_detected` → the import ran and you evidently have the skill
88
+ now: continue as if offered, watching the file's cohort as usual (sections 4–5).
89
+
90
+ ## 4. Bind to the server — fail closed
91
+
92
+ If the selected file's `cohort` is `unknown`, skip this check and go straight to section 5 — there
93
+ is no cohort to query either way, and the mismatch remediation below would be misleading when
94
+ watching was never possible.
95
+
96
+ Otherwise, run `kcap whoami` and compare its server URL to `server_url`: lowercase scheme and host,
97
+ drop a default port (80 for http, 443 for https only), trim a trailing slash, compare the path
98
+ as-is. Match → continue to section 5. Mismatch or `whoami` failure → issue **NO** analytics query.
99
+ CLOSE with the file's links and this remediation: start your agent from a shell where `kcap whoami`
100
+ reports `<server_url>` — set `KCAP_PROFILE=<profile>`, unset `KCAP_URL`, and set `KCAP_CONFIG_DIR`
101
+ if kcap uses a custom config directory — then prompt again.
102
+
103
+ ## 5. Watch the cohort
104
+
105
+ Cohort = `session_ids` (`cohort: unknown` or empty → CLOSE with links, no query). Open with:
106
+ "Watching N sessions from this import (K landed before the handoff)" — K is
107
+ `foreground_succeeded_ids.length`; for `partial_exact` add "the 500 most recent; older ones may
108
+ land and evaluate unobserved"; if `unattributed_on_disk` > 0 add the sentence from section 7.
109
+
110
+ Every query uses `query_analytics` with `scope: 'global'` (the MCP defaults to the caller's cwd
111
+ repo and will not widen on its own), **ONE call at a time — never two in flight** — and names only
112
+ cohort ids, each already validated against the grammar above, as single-quoted literals in an
113
+ `IN (...)` list or `session_id = '...'`. If a response ever includes a row whose `session_id` is
114
+ not in `session_ids`, drop that row before using the response for anything.
115
+
116
+ **Request budget:** at most 20 cohort queries per poll. `floor = ceil(N / 20)`. The FIRST poll uses
117
+ batch size `floor` exactly — no probing at a smaller size first. Every batch passes `max_rows`
118
+ equal to its own size:
119
+
120
+ SELECT s.session_id, s.repo_hash, e.eval_run_id, e.evaluated_at, e.overall_score, e.judge_model
121
+ FROM v_an_sessions s LEFT JOIN v_an_eval_summaries e ON e.session_id = s.session_id
122
+ WHERE s.session_id IN ('<id>', '<id>', ...)
123
+
124
+ A row = arrived; a non-null `eval_run_id` = completed. Each response body carries `max_rows`, the
125
+ server's effective cap. After the first poll: if any body reported `max_rows < floor`, fail closed
126
+ at once — say the server's row cap is below what watching this many sessions needs, and CLOSE,
127
+ having spent no more than the 20-query budget. Otherwise raise the batch to `min(100, max_rows)`
128
+ for later polls. Any batch that comes back `truncated: true`, in the first poll or any later one,
129
+ fails that poll — the cap decides, not the truncation flag alone.
130
+
131
+ One poll = every batch it needs, each non-truncated. A failed or truncated batch discards the whole
132
+ poll — nothing from it commits — and counts as one failure toward the two-failure stop rule.
133
+
134
+ A 429 fails the poll. If its text matches `retry after (\d+)s`, wait that many seconds before the
135
+ next poll; otherwise wait 60 seconds. Never poll sooner than the 30-second cadence either way. HTTP
136
+ 403 with `analytics_not_in_plan` → CLOSE immediately with the plan sentence (section 6); no
137
+ polling, no retry, no wait.
138
+
139
+ Progress = cohort ids that arrived / cohort size, computed from the batch rows only — never a
140
+ separate count, never repo- or tenant-wide. Cadence: first snapshot immediately, then every 30
141
+ seconds.
142
+
143
+ **Stop**, evaluated in this order after each poll whose results have already committed:
144
+ 1. Three distinct cohort sessions have a completed eval → summarize the first three, ordered by
145
+ `evaluated_at` then `session_id` (the same tie-break applies to rows already present at the very
146
+ first snapshot, so more than three arriving at once always picks the same three).
147
+ 2. `cohort: exact`, non-empty, and every id in `session_ids` has a completed eval → summarize what
148
+ completed. Never fires for `partial_exact` or an empty cohort.
149
+ 3. Two consecutive failed polls → stop, reporting the failures.
150
+ 4. 10 minutes since the first snapshot → stop. No new poll starts after the deadline, but a poll
151
+ already dispatched before it finishes normally, and its outcome is still checked against rules
152
+ 1–3 first: a third distinct completion in that final poll wins over the deadline summary, and if
153
+ that final poll is itself the second consecutive failure, report it as the deadline stop while
154
+ still naming the failures.
155
+
156
+ No idle early-stop — quiet polls are normal and are not a reason to stop early.
157
+
158
+ **Per-session detail** (one session per query, at most 6 queries across the whole run — enough for
159
+ the three sessions rule 1 summarizes):
160
+
161
+ SELECT category, AVG(score) AS mean FROM v_an_eval_scores WHERE session_id = '<id>' GROUP BY category
162
+ SELECT question_id, score FROM v_an_eval_scores WHERE session_id = '<id>' ORDER BY score ASC, question_id ASC LIMIT 2
163
+
164
+ Strongest = highest mean, weakest = lowest, ties alphabetical by category. A truncated or failed
165
+ detail query, or a session with no score rows, → summarize that session by `overall_score` alone
166
+ and say so; this never affects poll success or the stop rules above. When the `kcap-sessions` MCP is
167
+ available, use it to enrich a session's title; otherwise the session id is enough.
168
+
169
+ ## 6. When little or nothing completes
170
+
171
+ Explain the real gates plainly: auto-eval may be off for a repository; the server needs an eval
172
+ agent configured; sessions under the minimum event count are skipped; already-evaluated sessions
173
+ are not re-run; a recovery sweep runs hourly. If the analytics MCP is absent entirely, close
174
+ immediately with the block below.
175
+
176
+ Plan sentence (used here and from sections 3 and 5): "Insights isn't in this workspace's plan, so
177
+ evals can't be followed from here; your import continues and evals appear in the Capacitor UI."
178
+
179
+ ## 7. CLOSE — always
180
+
181
+ 1. How to keep watching: prompt `Follow my kcap import` again (with the same `(run: <id>)` line
182
+ when you were bound to one).
183
+ 2. Links: with a `repo_hash` matching `^[0-9a-f]{16}$`, `<server_url>/repo/<repo_hash>/sessions/<session_id>?tab=evaluation`;
184
+ otherwise (no `repo_hash`, or one that fails the grammar) `<server_url>/sessions/<session_id>?tab=evaluation`;
185
+ all results `<server_url>/sessions`. No valid file → `server_url` from `kcap whoami`; if that
186
+ fails too, say the results live in the Capacitor server UI and that `kcap whoami` prints the URL
187
+ once logged in.
188
+ 3. When `unattributed_on_disk` > 0: "N sessions on disk had no repository match; any of them that
189
+ imported show without one — `kcap remap` places them."
190
+ 4. Offer the guided tour with the exact prompt `Start kcap guided tour`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kurrent/kcap",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "CLI companion for Kurrent Capacitor — records and visualizes Claude Code sessions",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "repository": {
@@ -14,17 +14,17 @@
14
14
  "postinstall": "node bin/postinstall.js"
15
15
  },
16
16
  "optionalDependencies": {
17
- "@kurrent/kcap-darwin-arm64": "1.1.1",
18
- "@kurrent/kcap-linux-arm64": "1.1.1",
19
- "@kurrent/kcap-linux-musl-arm64": "1.1.1",
20
- "@kurrent/kcap-linux-musl-x64": "1.1.1",
21
- "@kurrent/kcap-linux-x64": "1.1.1",
22
- "@kurrent/kcap-win-x64": "1.1.1"
17
+ "@kurrent/kcap-darwin-arm64": "1.2.0",
18
+ "@kurrent/kcap-linux-arm64": "1.2.0",
19
+ "@kurrent/kcap-linux-musl-arm64": "1.2.0",
20
+ "@kurrent/kcap-linux-musl-x64": "1.2.0",
21
+ "@kurrent/kcap-linux-x64": "1.2.0",
22
+ "@kurrent/kcap-win-x64": "1.2.0"
23
23
  },
24
24
  "files": [
25
25
  "bin/",
26
26
  "kcap/"
27
27
  ],
28
- "kcapReleaseCommit": "c56b47d75d5015098627600e7c893711ba2059d8",
29
- "kcapReleaseTag": "v1.1.1"
28
+ "kcapReleaseCommit": "d7a1d7225e72573766636da70325ba136bb37566",
29
+ "kcapReleaseTag": "v1.2.0"
30
30
  }