elliot-stack 1.0.33 → 1.0.37
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/LICENSE +21 -21
- package/bin/install.cjs +981 -981
- package/hooks/repo-search-nudge.js +32 -32
- package/package.json +1 -1
- package/skills/estack-active-learning-tutor/SKILL.md +339 -339
- package/skills/estack-better-title/SKILL.md +64 -64
- package/skills/estack-better-title/scripts/rename.sh +55 -55
- package/skills/estack-chris-voss/SKILL.md +80 -80
- package/skills/estack-chris-voss/references/elliot-notes.md +120 -120
- package/skills/estack-chris-voss/references/voss-principles.md +210 -210
- package/skills/estack-customer-discovery/SKILL.md +60 -60
- package/skills/estack-flight-planner/SKILL.md +332 -332
- package/skills/estack-flight-planner/references/config_schema.md +156 -156
- package/skills/estack-flight-planner/references/flight_history_schema.md +97 -97
- package/skills/estack-flight-planner/references/shuttle_schedules.md +98 -98
- package/skills/estack-flight-planner/scripts/check_setup.sh +89 -89
- package/skills/estack-flight-planner/scripts/fetch_flights.py +99 -99
- package/skills/estack-flight-planner/scripts/filter_flights.py +265 -265
- package/skills/estack-flight-planner/scripts/pair_shuttles.py +173 -173
- package/skills/estack-github-issue-tracker/SKILL.md +322 -322
- package/skills/estack-github-issue-tracker/bin/tracker-tools.cjs +1358 -1358
- package/skills/estack-github-issue-tracker/references/gh-cli-patterns.md +124 -124
- package/skills/estack-github-issue-tracker/references/result-file-schema.md +156 -156
- package/skills/estack-github-issue-tracker/references/tracker-schema.md +96 -96
- package/skills/estack-github-issue-tracker/tracker-template.md +58 -58
- package/skills/estack-leadership-coach/SKILL.md +1 -1
- package/skills/estack-leadership-coach/adding-references.md +1 -1
- package/skills/estack-migrate-claude-session-history/SKILL.md +15 -2
- package/skills/estack-pdf-to-md/SKILL.md +1 -2
- package/skills/estack-prompt-builder-coach/SKILL.md +81 -81
- package/skills/estack-prompt-builder-coach/definition-of-done-generator.md +42 -42
- package/skills/estack-prompt-builder-coach/prompt-builder.md +37 -37
- package/skills/estack-prompt-builder-coach/task-shaper.md +36 -36
- package/skills/estack-prompt-builder-coach/vague-ask-auditor.md +37 -37
- package/skills/estack-read-claude-session-history/SKILL.md +224 -204
- package/skills/estack-read-claude-session-history/references/jsonl-schema.md +126 -126
- package/skills/estack-read-claude-session-history/references/modes.md +423 -423
- package/skills/estack-read-claude-session-history/references/recipes.md +271 -271
- package/skills/estack-read-claude-session-history/scripts/lib/__init__.py +1 -1
- package/skills/estack-read-claude-session-history/scripts/lib/parser.py +460 -460
- package/skills/estack-read-claude-session-history/scripts/lib/paths.py +234 -234
- package/skills/estack-read-claude-session-history/scripts/lib/search.py +179 -179
- package/skills/estack-read-claude-session-history/scripts/lib/subagents.py +88 -88
- package/skills/estack-read-claude-session-history/scripts/lib/tools.py +144 -144
- package/skills/estack-read-claude-session-history/scripts/read_transcript.py +1776 -1776
- package/skills/estack-read-claude-session-history/scripts/tests/conftest.py +40 -40
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/README.md +20 -20
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/all-noise.jsonl +4 -4
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/basic-session.jsonl +2 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-gaps.jsonl +9 -9
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-noise.jsonl +7 -7
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-parallel-a.jsonl +3 -3
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-parallel-b.jsonl +3 -3
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-waiting.jsonl +5 -5
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/interrupted.jsonl +2 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/multi-compact.jsonl +8 -8
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/pending-user.jsonl +2 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-no-meta/subagents/agent-aaa.jsonl +2 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-no-meta.jsonl +2 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-parent/subagents/agent-xyz123.jsonl +2 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-parent/subagents/agent-xyz123.meta.json +1 -1
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-parent.jsonl +4 -4
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/time-spread.jsonl +6 -6
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/timeline-day-test.jsonl +5 -5
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/tool-zoo.jsonl +10 -10
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/truncated.jsonl +2 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/unicode.jsonl +2 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/with-advisor.jsonl +3 -3
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/with-compact.jsonl +5 -5
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/with-thinking.jsonl +2 -2
- package/skills/estack-read-claude-session-history/scripts/tests/test_backup_roots.py +56 -56
- package/skills/estack-read-claude-session-history/scripts/tests/test_engagement.py +239 -239
- package/skills/estack-read-claude-session-history/scripts/tests/test_json_format.py +201 -201
- package/skills/estack-read-claude-session-history/scripts/tests/test_modes.py +199 -199
- package/skills/estack-read-claude-session-history/scripts/tests/test_parser.py +195 -195
- package/skills/estack-read-claude-session-history/scripts/tests/test_paths.py +133 -133
- package/skills/estack-read-claude-session-history/scripts/tests/test_search.py +78 -78
- package/skills/estack-read-claude-session-history/scripts/tests/test_subagents.py +43 -43
- package/skills/estack-read-claude-session-history/scripts/tests/test_timeline.py +179 -179
- package/skills/estack-read-claude-session-history/scripts/tests/test_timezone_and_project.py +212 -212
- package/skills/estack-read-claude-session-history/scripts/tests/test_tools.py +80 -80
- package/skills/estack-repo-search/SKILL.md +67 -65
|
@@ -1,144 +1,144 @@
|
|
|
1
|
-
"""Tool-call extraction, per-tool formatting, and tool-result lookup."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import json
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from typing import Optional
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def extract_tool_calls(
|
|
11
|
-
lines: list[dict], tool_filter: Optional[set[str]] = None
|
|
12
|
-
) -> list[dict]:
|
|
13
|
-
"""Every tool_use block with timestamp and parent line index.
|
|
14
|
-
|
|
15
|
-
Returns list of dicts: {name, input, id, timestamp, line_index}.
|
|
16
|
-
"""
|
|
17
|
-
out = []
|
|
18
|
-
for i, obj in enumerate(lines):
|
|
19
|
-
msg = obj.get("message", {})
|
|
20
|
-
if not isinstance(msg, dict):
|
|
21
|
-
continue
|
|
22
|
-
content = msg.get("content")
|
|
23
|
-
if not isinstance(content, list):
|
|
24
|
-
continue
|
|
25
|
-
ts = obj.get("timestamp")
|
|
26
|
-
for block in content:
|
|
27
|
-
if not isinstance(block, dict):
|
|
28
|
-
continue
|
|
29
|
-
if block.get("type") != "tool_use":
|
|
30
|
-
continue
|
|
31
|
-
name = block.get("name", "")
|
|
32
|
-
if tool_filter and name not in tool_filter:
|
|
33
|
-
continue
|
|
34
|
-
out.append({
|
|
35
|
-
"name": name,
|
|
36
|
-
"input": block.get("input", {}) or {},
|
|
37
|
-
"id": block.get("id", ""),
|
|
38
|
-
"timestamp": ts,
|
|
39
|
-
"line_index": i,
|
|
40
|
-
})
|
|
41
|
-
return out
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def _first_line(s: str, n: int = 200) -> str:
|
|
45
|
-
if not s:
|
|
46
|
-
return ""
|
|
47
|
-
line = s.splitlines()[0] if s.splitlines() else s
|
|
48
|
-
return line if len(line) <= n else line[: n - 1] + "…"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def format_tool_call(call: dict) -> str:
|
|
52
|
-
"""Per-tool human-readable one-liner."""
|
|
53
|
-
name = call.get("name", "?")
|
|
54
|
-
inp = call.get("input", {}) or {}
|
|
55
|
-
|
|
56
|
-
if name in ("Bash", "PowerShell"):
|
|
57
|
-
cmd = inp.get("command", "")
|
|
58
|
-
return f"{name}: {_first_line(cmd, 200)}"
|
|
59
|
-
if name == "Read":
|
|
60
|
-
fp = inp.get("file_path", "")
|
|
61
|
-
offset = inp.get("offset")
|
|
62
|
-
limit = inp.get("limit")
|
|
63
|
-
if offset is not None or limit is not None:
|
|
64
|
-
o = offset or 1
|
|
65
|
-
l = limit or 0
|
|
66
|
-
return f"Read {fp} (lines {o}-{o + l if l else '?'})"
|
|
67
|
-
return f"Read {fp}"
|
|
68
|
-
if name == "Edit":
|
|
69
|
-
fp = inp.get("file_path", "")
|
|
70
|
-
edits = inp.get("edits")
|
|
71
|
-
if isinstance(edits, list):
|
|
72
|
-
return f"Edit {fp} ({len(edits)} edits)"
|
|
73
|
-
return f"Edit {fp}"
|
|
74
|
-
if name == "Write":
|
|
75
|
-
fp = inp.get("file_path", "")
|
|
76
|
-
content = inp.get("content", "")
|
|
77
|
-
return f"Write {fp} ({len(content)} chars)"
|
|
78
|
-
if name in ("Agent", "Task"):
|
|
79
|
-
sub = inp.get("subagent_type") or inp.get("agentType") or "?"
|
|
80
|
-
desc = inp.get("description", "")
|
|
81
|
-
return f"{name}[{sub}]: {_first_line(desc, 200)}"
|
|
82
|
-
if name == "Skill":
|
|
83
|
-
sk = inp.get("skill", "")
|
|
84
|
-
args = inp.get("args", "")
|
|
85
|
-
if args:
|
|
86
|
-
return f"Skill: {sk} {_first_line(str(args), 100)}"
|
|
87
|
-
return f"Skill: {sk}"
|
|
88
|
-
if name == "Glob":
|
|
89
|
-
pat = inp.get("pattern", "")
|
|
90
|
-
return f"Glob: {pat}"
|
|
91
|
-
if name == "Grep":
|
|
92
|
-
pat = inp.get("pattern", "")
|
|
93
|
-
return f"Grep: {pat}"
|
|
94
|
-
try:
|
|
95
|
-
preview = json.dumps(inp)[:200]
|
|
96
|
-
except (TypeError, ValueError):
|
|
97
|
-
preview = str(inp)[:200]
|
|
98
|
-
return f"{name}: {preview}"
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def extract_tool_results(
|
|
102
|
-
lines: list[dict], tool_use_id: str
|
|
103
|
-
) -> Optional[str]:
|
|
104
|
-
"""Find the tool_result block whose tool_use_id matches the given id."""
|
|
105
|
-
if not tool_use_id:
|
|
106
|
-
return None
|
|
107
|
-
for obj in lines:
|
|
108
|
-
msg = obj.get("message", {})
|
|
109
|
-
if not isinstance(msg, dict):
|
|
110
|
-
continue
|
|
111
|
-
content = msg.get("content")
|
|
112
|
-
if not isinstance(content, list):
|
|
113
|
-
continue
|
|
114
|
-
for block in content:
|
|
115
|
-
if not isinstance(block, dict):
|
|
116
|
-
continue
|
|
117
|
-
if block.get("type") != "tool_result":
|
|
118
|
-
continue
|
|
119
|
-
if block.get("tool_use_id") != tool_use_id:
|
|
120
|
-
continue
|
|
121
|
-
inner = block.get("content", "")
|
|
122
|
-
if isinstance(inner, str):
|
|
123
|
-
return inner
|
|
124
|
-
if isinstance(inner, list):
|
|
125
|
-
parts = []
|
|
126
|
-
for sub in inner:
|
|
127
|
-
if isinstance(sub, dict) and sub.get("type") == "text":
|
|
128
|
-
parts.append(sub.get("text", ""))
|
|
129
|
-
return "\n".join(parts)
|
|
130
|
-
return None
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
def files_touched(lines: list[dict]) -> dict[Path, list[str]]:
|
|
134
|
-
"""Map of file paths to the list of operations performed on them."""
|
|
135
|
-
out: dict[str, list[str]] = {}
|
|
136
|
-
for call in extract_tool_calls(lines):
|
|
137
|
-
name = call["name"]
|
|
138
|
-
inp = call.get("input", {}) or {}
|
|
139
|
-
if name in ("Edit", "Write", "Read", "NotebookEdit"):
|
|
140
|
-
fp = inp.get("file_path") or inp.get("notebook_path", "")
|
|
141
|
-
if fp:
|
|
142
|
-
out.setdefault(fp, []).append(name)
|
|
143
|
-
# Convert to Path keys
|
|
144
|
-
return {Path(k): v for k, v in out.items()}
|
|
1
|
+
"""Tool-call extraction, per-tool formatting, and tool-result lookup."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def extract_tool_calls(
|
|
11
|
+
lines: list[dict], tool_filter: Optional[set[str]] = None
|
|
12
|
+
) -> list[dict]:
|
|
13
|
+
"""Every tool_use block with timestamp and parent line index.
|
|
14
|
+
|
|
15
|
+
Returns list of dicts: {name, input, id, timestamp, line_index}.
|
|
16
|
+
"""
|
|
17
|
+
out = []
|
|
18
|
+
for i, obj in enumerate(lines):
|
|
19
|
+
msg = obj.get("message", {})
|
|
20
|
+
if not isinstance(msg, dict):
|
|
21
|
+
continue
|
|
22
|
+
content = msg.get("content")
|
|
23
|
+
if not isinstance(content, list):
|
|
24
|
+
continue
|
|
25
|
+
ts = obj.get("timestamp")
|
|
26
|
+
for block in content:
|
|
27
|
+
if not isinstance(block, dict):
|
|
28
|
+
continue
|
|
29
|
+
if block.get("type") != "tool_use":
|
|
30
|
+
continue
|
|
31
|
+
name = block.get("name", "")
|
|
32
|
+
if tool_filter and name not in tool_filter:
|
|
33
|
+
continue
|
|
34
|
+
out.append({
|
|
35
|
+
"name": name,
|
|
36
|
+
"input": block.get("input", {}) or {},
|
|
37
|
+
"id": block.get("id", ""),
|
|
38
|
+
"timestamp": ts,
|
|
39
|
+
"line_index": i,
|
|
40
|
+
})
|
|
41
|
+
return out
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _first_line(s: str, n: int = 200) -> str:
|
|
45
|
+
if not s:
|
|
46
|
+
return ""
|
|
47
|
+
line = s.splitlines()[0] if s.splitlines() else s
|
|
48
|
+
return line if len(line) <= n else line[: n - 1] + "…"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def format_tool_call(call: dict) -> str:
|
|
52
|
+
"""Per-tool human-readable one-liner."""
|
|
53
|
+
name = call.get("name", "?")
|
|
54
|
+
inp = call.get("input", {}) or {}
|
|
55
|
+
|
|
56
|
+
if name in ("Bash", "PowerShell"):
|
|
57
|
+
cmd = inp.get("command", "")
|
|
58
|
+
return f"{name}: {_first_line(cmd, 200)}"
|
|
59
|
+
if name == "Read":
|
|
60
|
+
fp = inp.get("file_path", "")
|
|
61
|
+
offset = inp.get("offset")
|
|
62
|
+
limit = inp.get("limit")
|
|
63
|
+
if offset is not None or limit is not None:
|
|
64
|
+
o = offset or 1
|
|
65
|
+
l = limit or 0
|
|
66
|
+
return f"Read {fp} (lines {o}-{o + l if l else '?'})"
|
|
67
|
+
return f"Read {fp}"
|
|
68
|
+
if name == "Edit":
|
|
69
|
+
fp = inp.get("file_path", "")
|
|
70
|
+
edits = inp.get("edits")
|
|
71
|
+
if isinstance(edits, list):
|
|
72
|
+
return f"Edit {fp} ({len(edits)} edits)"
|
|
73
|
+
return f"Edit {fp}"
|
|
74
|
+
if name == "Write":
|
|
75
|
+
fp = inp.get("file_path", "")
|
|
76
|
+
content = inp.get("content", "")
|
|
77
|
+
return f"Write {fp} ({len(content)} chars)"
|
|
78
|
+
if name in ("Agent", "Task"):
|
|
79
|
+
sub = inp.get("subagent_type") or inp.get("agentType") or "?"
|
|
80
|
+
desc = inp.get("description", "")
|
|
81
|
+
return f"{name}[{sub}]: {_first_line(desc, 200)}"
|
|
82
|
+
if name == "Skill":
|
|
83
|
+
sk = inp.get("skill", "")
|
|
84
|
+
args = inp.get("args", "")
|
|
85
|
+
if args:
|
|
86
|
+
return f"Skill: {sk} {_first_line(str(args), 100)}"
|
|
87
|
+
return f"Skill: {sk}"
|
|
88
|
+
if name == "Glob":
|
|
89
|
+
pat = inp.get("pattern", "")
|
|
90
|
+
return f"Glob: {pat}"
|
|
91
|
+
if name == "Grep":
|
|
92
|
+
pat = inp.get("pattern", "")
|
|
93
|
+
return f"Grep: {pat}"
|
|
94
|
+
try:
|
|
95
|
+
preview = json.dumps(inp)[:200]
|
|
96
|
+
except (TypeError, ValueError):
|
|
97
|
+
preview = str(inp)[:200]
|
|
98
|
+
return f"{name}: {preview}"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def extract_tool_results(
|
|
102
|
+
lines: list[dict], tool_use_id: str
|
|
103
|
+
) -> Optional[str]:
|
|
104
|
+
"""Find the tool_result block whose tool_use_id matches the given id."""
|
|
105
|
+
if not tool_use_id:
|
|
106
|
+
return None
|
|
107
|
+
for obj in lines:
|
|
108
|
+
msg = obj.get("message", {})
|
|
109
|
+
if not isinstance(msg, dict):
|
|
110
|
+
continue
|
|
111
|
+
content = msg.get("content")
|
|
112
|
+
if not isinstance(content, list):
|
|
113
|
+
continue
|
|
114
|
+
for block in content:
|
|
115
|
+
if not isinstance(block, dict):
|
|
116
|
+
continue
|
|
117
|
+
if block.get("type") != "tool_result":
|
|
118
|
+
continue
|
|
119
|
+
if block.get("tool_use_id") != tool_use_id:
|
|
120
|
+
continue
|
|
121
|
+
inner = block.get("content", "")
|
|
122
|
+
if isinstance(inner, str):
|
|
123
|
+
return inner
|
|
124
|
+
if isinstance(inner, list):
|
|
125
|
+
parts = []
|
|
126
|
+
for sub in inner:
|
|
127
|
+
if isinstance(sub, dict) and sub.get("type") == "text":
|
|
128
|
+
parts.append(sub.get("text", ""))
|
|
129
|
+
return "\n".join(parts)
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def files_touched(lines: list[dict]) -> dict[Path, list[str]]:
|
|
134
|
+
"""Map of file paths to the list of operations performed on them."""
|
|
135
|
+
out: dict[str, list[str]] = {}
|
|
136
|
+
for call in extract_tool_calls(lines):
|
|
137
|
+
name = call["name"]
|
|
138
|
+
inp = call.get("input", {}) or {}
|
|
139
|
+
if name in ("Edit", "Write", "Read", "NotebookEdit"):
|
|
140
|
+
fp = inp.get("file_path") or inp.get("notebook_path", "")
|
|
141
|
+
if fp:
|
|
142
|
+
out.setdefault(fp, []).append(name)
|
|
143
|
+
# Convert to Path keys
|
|
144
|
+
return {Path(k): v for k, v in out.items()}
|