@tamng0905/builder-essential-skills 0.1.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/.claude/agents/lead-builder.md +55 -0
- package/.claude/agents/lead-reviewer.md +39 -0
- package/README.md +276 -0
- package/assets/readme-hero.png +0 -0
- package/assets/skill-cards/async-learning-teacher.svg +7 -0
- package/assets/skill-cards/code-standards.svg +7 -0
- package/assets/skill-cards/lead-research.svg +7 -0
- package/assets/skill-cards/lead.svg +7 -0
- package/assets/skill-cards/orwell-writing.svg +7 -0
- package/assets/skill-cards/session-profiler.svg +7 -0
- package/assets/skill-cards/validate-market.svg +7 -0
- package/assets/skill-cards/write-blog.svg +7 -0
- package/bin/builder-essential-skills.js +134 -0
- package/install.ps1 +58 -0
- package/install.sh +63 -0
- package/package.json +24 -0
- package/skills/async-learning-teacher/README.md +50 -0
- package/skills/async-learning-teacher/SKILL.md +192 -0
- package/skills/async-learning-teacher/agents/openai.yaml +4 -0
- package/skills/code-standards/README.md +27 -0
- package/skills/code-standards/SKILL.md +51 -0
- package/skills/lead/README.md +29 -0
- package/skills/lead/SKILL.md +222 -0
- package/skills/lead/config.py +359 -0
- package/skills/lead/dispatch.md +342 -0
- package/skills/lead/loop.md +103 -0
- package/skills/lead/models.json +34 -0
- package/skills/lead/research.md +76 -0
- package/skills/lead/status.ps1 +114 -0
- package/skills/lead/status.sh +110 -0
- package/skills/lead/watchdog.ps1 +90 -0
- package/skills/lead/watchdog.sh +88 -0
- package/skills/lead-research/README.md +27 -0
- package/skills/lead-research/SKILL.md +157 -0
- package/skills/lead-research/tactics.md +150 -0
- package/skills/orwell-writing/README.md +27 -0
- package/skills/orwell-writing/SKILL.md +44 -0
- package/skills/orwell-writing/agents/openai.yaml +4 -0
- package/skills/session-profiler/README.md +25 -0
- package/skills/session-profiler/SKILL.md +103 -0
- package/skills/session-profiler/agents/openai.yaml +4 -0
- package/skills/session-profiler/scripts/requirements.txt +2 -0
- package/skills/session-profiler/scripts/session_profiler/__init__.py +5 -0
- package/skills/session-profiler/scripts/session_profiler/analyses.py +246 -0
- package/skills/session-profiler/scripts/session_profiler/cli.py +77 -0
- package/skills/session-profiler/scripts/session_profiler/dataset.py +369 -0
- package/skills/session-profiler/scripts/session_profiler/discover.py +191 -0
- package/skills/session-profiler/scripts/session_profiler/toc.py +74 -0
- package/skills/session-profiler/scripts/session_profiler/trace.py +146 -0
- package/skills/session-profiler/scripts/sp +20 -0
- package/skills/session-profiler/scripts/tests/codex_home/sessions/2026/01/01/rollout-child.jsonl +7 -0
- package/skills/session-profiler/scripts/tests/codex_home/sessions/2026/01/01/rollout-root.jsonl +10 -0
- package/skills/session-profiler/scripts/tests/fixture/main/subagents/agent-abc.jsonl +2 -0
- package/skills/session-profiler/scripts/tests/fixture/main/subagents/agent-abc.meta.json +1 -0
- package/skills/session-profiler/scripts/tests/fixture/main.jsonl +7 -0
- package/skills/session-profiler/scripts/tests/test_fixture.py +58 -0
- package/skills/validate-market/README.md +27 -0
- package/skills/validate-market/SKILL.md +202 -0
- package/skills/write-blog/README.md +27 -0
- package/skills/write-blog/SKILL.md +142 -0
- package/skills/write-blog/check.sh +52 -0
- package/skills/write-blog/reference/angles.md +51 -0
- package/skills/write-blog/reference/craft.md +94 -0
- package/skills/write-blog/reference/seo.md +116 -0
- package/skills/write-blog/reference/voice.md +64 -0
- package/skills/write-blog/reference/wiring.md +113 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import gzip
|
|
4
|
+
import json
|
|
5
|
+
import webbrowser
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import pandas as pd
|
|
9
|
+
|
|
10
|
+
from .dataset import load, load_agents, work_dir
|
|
11
|
+
|
|
12
|
+
TOC_COLORS = ["rail_response", "rail_animation", "rail_idle", "cq_build_running", "cq_build_passed", "cq_build_failed"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _us(ts, origin) -> int:
|
|
16
|
+
return int((pd.Timestamp(ts) - origin).total_seconds() * 1_000_000)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _clip(value, limit: int = 90) -> str:
|
|
20
|
+
text = " ".join(str(value or "").split())
|
|
21
|
+
return text[: limit - 3] + "..." if len(text) > limit else text
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _duration_seconds(row) -> float:
|
|
25
|
+
return round(float(row.duration_ms or 0) / 1000, 3)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _token_total(row) -> int:
|
|
29
|
+
return int(row.input_tokens + row.cache_read_input_tokens + row.cache_creation_input_tokens + row.output_tokens)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _event_name(row) -> str:
|
|
33
|
+
if row.event_type == "user_prompt":
|
|
34
|
+
return "Prompt: " + (_clip(row.text, 70) or "user prompt")
|
|
35
|
+
if row.event_type == "tool":
|
|
36
|
+
label = _clip(row.tool_name or "tool", 70)
|
|
37
|
+
return f"Failed: {label}" if bool(row.is_error) else label
|
|
38
|
+
if row.event_type == "inference":
|
|
39
|
+
return "Inference: " + (_clip(row.model, 60) or "model")
|
|
40
|
+
return _clip(row.event_type, 70)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _event_color(row) -> str:
|
|
44
|
+
if bool(row.is_error):
|
|
45
|
+
return "terrible"
|
|
46
|
+
if row.event_type == "user_prompt":
|
|
47
|
+
return "rail_response"
|
|
48
|
+
if row.event_type == "inference":
|
|
49
|
+
return "thread_state_iowait"
|
|
50
|
+
if row.event_type == "tool":
|
|
51
|
+
return "cq_build_running" if bool(row.concurrent) else "cq_build_passed"
|
|
52
|
+
return "generic_work"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def create(data_dir=None) -> Path:
|
|
56
|
+
out = work_dir(data_dir)
|
|
57
|
+
df = load(out)
|
|
58
|
+
metadata = load_agents(out)
|
|
59
|
+
agents = metadata["agents"]
|
|
60
|
+
provider = metadata.get("provider", "unknown").title()
|
|
61
|
+
origin = df.ts.min()
|
|
62
|
+
trace = {"displayTimeUnit": "ms", "traceEvents": []}
|
|
63
|
+
ev = trace["traceEvents"]
|
|
64
|
+
ev.append({"ph": "M", "pid": 1, "tid": 0, "name": "process_name", "args": {"name": f"{provider} session profile"}})
|
|
65
|
+
ev.append({"ph": "M", "pid": 1, "tid": 1, "name": "thread_name", "args": {"name": "Session overview"}})
|
|
66
|
+
tids = {}
|
|
67
|
+
for idx, agent in enumerate(agents, 1):
|
|
68
|
+
tids[agent["id"]] = {"activity": idx * 10, "prompts": idx * 10 + 1, "tokens": idx * 10 + 2}
|
|
69
|
+
for key, suffix in [("activity", "work timeline"), ("prompts", "prompt trail"), ("tokens", "usage tokens + cost")]:
|
|
70
|
+
ev.append({"ph": "M", "pid": 1, "tid": tids[agent["id"]][key], "name": "thread_name", "args": {"name": f"{agent['name']} — {suffix}"}})
|
|
71
|
+
inf = df[df.event_type == "inference"]
|
|
72
|
+
tools = df[df.event_type == "tool"]
|
|
73
|
+
prompts = df[(df.agent_id == "main") & (df.event_type == "user_prompt")]
|
|
74
|
+
overview_name = f"{provider} session: {len(agents)} agents, {len(tools)} tool calls"
|
|
75
|
+
if not tools.empty and tools.is_error.any():
|
|
76
|
+
overview_name += f", {int(tools.is_error.sum())} failures"
|
|
77
|
+
ev.append({
|
|
78
|
+
"ph": "X",
|
|
79
|
+
"pid": 1,
|
|
80
|
+
"tid": 1,
|
|
81
|
+
"ts": 0,
|
|
82
|
+
"dur": max(1, _us(df.end_ts.max(), origin)),
|
|
83
|
+
"name": overview_name,
|
|
84
|
+
"cat": "overview",
|
|
85
|
+
"cname": "rail_idle",
|
|
86
|
+
"args": {
|
|
87
|
+
"provider": metadata.get("provider", "unknown"),
|
|
88
|
+
"session_id": metadata.get("session_id", ""),
|
|
89
|
+
"prompts": int(len(prompts)),
|
|
90
|
+
"agents": int(len(agents)),
|
|
91
|
+
"tool_calls": int(len(tools)),
|
|
92
|
+
"tool_failures": int(tools.is_error.sum()) if not tools.empty else 0,
|
|
93
|
+
"tokens": int(inf[["input_tokens", "cache_read_input_tokens", "cache_creation_input_tokens", "output_tokens"]].sum().sum()) if not inf.empty else 0,
|
|
94
|
+
"estimated_cost_usd": round(float(inf.estimated_cost_usd.sum()), 6) if not inf.empty else 0.0,
|
|
95
|
+
},
|
|
96
|
+
})
|
|
97
|
+
cumulative = {a["id"]: {"tokens": 0, "cost": 0.0} for a in agents}
|
|
98
|
+
for _, row in df.sort_values("ts").iterrows():
|
|
99
|
+
if row.event_type in {"inference", "tool", "user_prompt"}:
|
|
100
|
+
tid = tids[row.agent_id]["prompts" if row.event_type == "user_prompt" else "activity"]
|
|
101
|
+
ev.append({"ph": "X", "pid": 1, "tid": tid, "ts": _us(row.ts, origin), "dur": max(1, int(row.duration_ms * 1000)), "name": _event_name(row), "cat": row.event_type, "cname": _event_color(row), "args": {"wall_utc": row.ts.isoformat(), "agent": row.agent_name, "duration_s": _duration_seconds(row), "input": str(row.input_preview)[:2000], "output": str(row.output_preview)[:2000], "text": str(row.text)[:2000], "error": bool(row.is_error), "concurrent": bool(row.concurrent), "tokens": _token_total(row), "estimated_cost_usd": float(row.estimated_cost_usd)}})
|
|
102
|
+
if row.event_type == "inference":
|
|
103
|
+
tokens = _token_total(row)
|
|
104
|
+
cumulative[row.agent_id]["tokens"] += tokens
|
|
105
|
+
cumulative[row.agent_id]["cost"] += float(row.estimated_cost_usd)
|
|
106
|
+
ev.append({"ph": "C", "pid": 1, "tid": tids[row.agent_id]["tokens"], "ts": _us(row.end_ts, origin), "name": "usage tokens", "args": {"tokens": cumulative[row.agent_id]["tokens"]}})
|
|
107
|
+
ev.append({"ph": "C", "pid": 1, "tid": tids[row.agent_id]["tokens"], "ts": _us(row.end_ts, origin), "name": "usage cost", "args": {"estimated_cost_usd": round(cumulative[row.agent_id]["cost"], 6)}})
|
|
108
|
+
# Connect parent spawn -> child start and child completion -> parent result.
|
|
109
|
+
for idx, agent in enumerate(agents):
|
|
110
|
+
if agent["id"] == "main" or not agent.get("spawn_tool_use_id"):
|
|
111
|
+
continue
|
|
112
|
+
parent = df[(df.event_type == "tool") & (df.tool_use_id == agent["spawn_tool_use_id"])]
|
|
113
|
+
child = df[df.agent_id == agent["id"]]
|
|
114
|
+
if parent.empty or child.empty:
|
|
115
|
+
continue
|
|
116
|
+
spawn, child_start, child_end = parent.iloc[0], child.ts.min(), child.end_ts.max()
|
|
117
|
+
flow_id = f"spawn-{idx}"
|
|
118
|
+
ev.extend([
|
|
119
|
+
{"ph": "s", "pid": 1, "tid": tids[spawn.agent_id]["activity"], "ts": _us(spawn.ts, origin), "name": "spawn", "id": flow_id},
|
|
120
|
+
{"ph": "f", "pid": 1, "tid": tids[agent["id"]]["activity"], "ts": _us(child_start, origin), "name": "spawn", "id": flow_id, "bp": "e"},
|
|
121
|
+
{"ph": "s", "pid": 1, "tid": tids[agent["id"]]["activity"], "ts": _us(child_end, origin), "name": "completion", "id": f"done-{idx}"},
|
|
122
|
+
{"ph": "f", "pid": 1, "tid": tids[spawn.agent_id]["activity"], "ts": _us(spawn.end_ts, origin), "name": "completion", "id": f"done-{idx}", "bp": "e"},
|
|
123
|
+
])
|
|
124
|
+
toc_path = out / "toc.json"
|
|
125
|
+
if toc_path.exists():
|
|
126
|
+
toc = json.loads(toc_path.read_text())
|
|
127
|
+
tid = 2
|
|
128
|
+
ev.append({"ph": "M", "pid": 1, "tid": tid, "name": "thread_name", "args": {"name": "Table of contents"}})
|
|
129
|
+
def add(items, depth=0):
|
|
130
|
+
for item in items:
|
|
131
|
+
try:
|
|
132
|
+
start, end = pd.to_datetime(item["start_ts"], utc=True), pd.to_datetime(item["end_ts"], utc=True)
|
|
133
|
+
except (KeyError, ValueError):
|
|
134
|
+
continue
|
|
135
|
+
ev.append({"ph": "X", "pid": 1, "tid": tid, "ts": _us(start, origin), "dur": max(1, _us(end, start)), "name": item.get("title", "phase"), "cat": "toc", "cname": TOC_COLORS[depth % len(TOC_COLORS)], "args": {"summary": item.get("summary", ""), "duration_s": round(_us(end, start) / 1_000_000, 3)}})
|
|
136
|
+
add(item.get("steps", []), depth + 1)
|
|
137
|
+
add(toc.get("phases", []))
|
|
138
|
+
path = out / "trace.json.gz"
|
|
139
|
+
with gzip.open(path, "wt", encoding="utf-8") as fh:
|
|
140
|
+
json.dump(trace, fh, separators=(",", ":"))
|
|
141
|
+
return path
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def open_ui() -> str:
|
|
145
|
+
webbrowser.open("https://ui.perfetto.dev")
|
|
146
|
+
return "Opened https://ui.perfetto.dev — load trace.json.gz from the parsed session directory. Review the trace for sensitive paths or text before sharing."
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
5
|
+
VENV="${SESSION_PROFILER_VENV:-$HOME/.cache/session-profiler-venv}"
|
|
6
|
+
PY="$VENV/bin/python"
|
|
7
|
+
|
|
8
|
+
if [ ! -x "$PY" ]; then
|
|
9
|
+
mkdir -p "$(dirname "$VENV")"
|
|
10
|
+
if command -v uv >/dev/null 2>&1; then
|
|
11
|
+
uv venv "$VENV"
|
|
12
|
+
uv pip install --python "$PY" -r "$HERE/requirements.txt"
|
|
13
|
+
else
|
|
14
|
+
python3 -m venv "$VENV"
|
|
15
|
+
"$PY" -m pip install -r "$HERE/requirements.txt"
|
|
16
|
+
fi
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
export PYTHONPATH="$HERE${PYTHONPATH:+:$PYTHONPATH}"
|
|
20
|
+
exec "$PY" -m session_profiler.cli "$@"
|
package/skills/session-profiler/scripts/tests/codex_home/sessions/2026/01/01/rollout-child.jsonl
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{"timestamp":"2026-01-01T00:00:01.200Z","type":"session_meta","payload":{"id":"00000000-0000-4000-8000-000000000002","cwd":"/tmp/project","source":{"subagent":{"thread_spawn":{"parent_thread_id":"00000000-0000-4000-8000-000000000001","depth":1,"agent_nickname":"Curie","agent_role":"explorer"}}},"model_provider":"openai"}}
|
|
2
|
+
{"timestamp":"2026-01-01T00:00:01.300Z","type":"event_msg","payload":{"type":"task_started","turn_id":"turn-child"}}
|
|
3
|
+
{"timestamp":"2026-01-01T00:00:01.400Z","type":"turn_context","payload":{"turn_id":"turn-child","model":"gpt-5.6-luna","cwd":"/tmp/project"}}
|
|
4
|
+
{"timestamp":"2026-01-01T00:00:01.500Z","type":"event_msg","payload":{"type":"user_message","message":"Inspect the failure"}}
|
|
5
|
+
{"timestamp":"2026-01-01T00:00:02Z","type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"Found it"}],"internal_chat_message_metadata_passthrough":{"turn_id":"turn-child"}}}
|
|
6
|
+
{"timestamp":"2026-01-01T00:00:02.100Z","type":"event_msg","payload":{"type":"token_count","info":{"last_token_usage":{"input_tokens":50,"cached_input_tokens":10,"output_tokens":5,"total_tokens":55}}}}
|
|
7
|
+
{"timestamp":"2026-01-01T00:00:02.200Z","type":"event_msg","payload":{"type":"task_complete","turn_id":"turn-child","duration_ms":900}}
|
package/skills/session-profiler/scripts/tests/codex_home/sessions/2026/01/01/rollout-root.jsonl
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{"timestamp":"2026-01-01T00:00:00Z","type":"session_meta","payload":{"id":"00000000-0000-4000-8000-000000000001","cwd":"/tmp/project","source":"cli","model_provider":"openai"}}
|
|
2
|
+
{"timestamp":"2026-01-01T00:00:00.100Z","type":"event_msg","payload":{"type":"task_started","turn_id":"turn-1"}}
|
|
3
|
+
{"timestamp":"2026-01-01T00:00:00.200Z","type":"turn_context","payload":{"turn_id":"turn-1","model":"gpt-5.6-sol","cwd":"/tmp/project"}}
|
|
4
|
+
{"timestamp":"2026-01-01T00:00:00.300Z","type":"event_msg","payload":{"type":"user_message","message":"Investigate with a subagent"}}
|
|
5
|
+
{"timestamp":"2026-01-01T00:00:01Z","type":"response_item","payload":{"type":"function_call","name":"collaboration.spawn_agent","call_id":"call-spawn","arguments":"{\"task_name\":\"inspect\"}","internal_chat_message_metadata_passthrough":{"turn_id":"turn-1"}}}
|
|
6
|
+
{"timestamp":"2026-01-01T00:00:01.100Z","type":"event_msg","payload":{"type":"token_count","info":{"last_token_usage":{"input_tokens":100,"cached_input_tokens":40,"output_tokens":20,"total_tokens":120}}}}
|
|
7
|
+
{"timestamp":"2026-01-01T00:00:03Z","type":"response_item","payload":{"type":"function_call_output","call_id":"call-spawn","output":"{\"thread_id\":\"00000000-0000-4000-8000-000000000002\"}","internal_chat_message_metadata_passthrough":{"turn_id":"turn-1"}}}
|
|
8
|
+
{"timestamp":"2026-01-01T00:00:04Z","type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"Inspection complete"}],"internal_chat_message_metadata_passthrough":{"turn_id":"turn-1"}}}
|
|
9
|
+
{"timestamp":"2026-01-01T00:00:04.100Z","type":"event_msg","payload":{"type":"token_count","info":{"last_token_usage":{"input_tokens":120,"cached_input_tokens":50,"output_tokens":10,"total_tokens":130}}}}
|
|
10
|
+
{"timestamp":"2026-01-01T00:00:04.200Z","type":"event_msg","payload":{"type":"task_complete","turn_id":"turn-1","duration_ms":4100}}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
{"type":"assistant","timestamp":"2026-01-01T00:00:02.500Z","message":{"id":"sm1","model":"claude-test","content":[{"type":"text","text":"Inspecting"}],"usage":{"input_tokens":25,"cache_read_input_tokens":0,"cache_creation_input_tokens":0,"output_tokens":3}},"uuid":"sa1","isSidechain":true}
|
|
2
|
+
{"type":"assistant","timestamp":"2026-01-01T00:00:04Z","message":{"id":"sm2","model":"claude-test","content":[{"type":"text","text":"Found it"}],"usage":{"input_tokens":30,"cache_read_input_tokens":0,"cache_creation_input_tokens":0,"output_tokens":4}},"uuid":"sa2","isSidechain":true}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"agentType":"Explore","description":"Inspect failure","name":"build-inspector","toolUseId":"spawn-1","spawnDepth":1}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{"type":"user","timestamp":"2026-01-01T00:00:00Z","message":{"role":"user","content":"Investigate the build"},"uuid":"u1"}
|
|
2
|
+
{"type":"assistant","timestamp":"2026-01-01T00:00:01Z","message":{"id":"m1","model":"claude-test","content":[{"type":"thinking","thinking":"I should inspect it"}],"usage":{"input_tokens":100,"cache_read_input_tokens":20,"cache_creation_input_tokens":10,"output_tokens":5}},"uuid":"a1"}
|
|
3
|
+
{"type":"assistant","timestamp":"2026-01-01T00:00:02Z","message":{"id":"m1","model":"claude-test","content":[{"type":"tool_use","id":"tool-1","name":"Bash","input":{"command":"make test"}}],"usage":{"input_tokens":100,"cache_read_input_tokens":20,"cache_creation_input_tokens":10,"output_tokens":10}},"uuid":"a2"}
|
|
4
|
+
{"type":"assistant","timestamp":"2026-01-01T00:00:02.100Z","message":{"id":"m1","model":"claude-test","content":[{"type":"tool_use","id":"spawn-1","name":"Agent","input":{"description":"Inspect failure"}}],"usage":{"input_tokens":100,"cache_read_input_tokens":20,"cache_creation_input_tokens":10,"output_tokens":15}},"uuid":"a3"}
|
|
5
|
+
{"type":"user","timestamp":"2026-01-01T00:00:05Z","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"tool-1","is_error":true,"content":"compile failed"}]},"toolUseResult":{"isError":true},"uuid":"r1"}
|
|
6
|
+
{"type":"user","timestamp":"2026-01-01T00:00:06Z","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"spawn-1","content":"agent result"}]},"toolUseResult":{},"uuid":"r2"}
|
|
7
|
+
{"type":"assistant","timestamp":"2026-01-01T00:00:07Z","message":{"id":"m2","model":"claude-test","content":[{"type":"text","text":"The build failed."}],"usage":{"input_tokens":50,"cache_read_input_tokens":0,"cache_creation_input_tokens":0,"output_tokens":8}},"uuid":"a4"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import gzip
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import tempfile
|
|
6
|
+
|
|
7
|
+
from session_profiler.analyses import brief
|
|
8
|
+
from session_profiler.dataset import load, load_agents, parse
|
|
9
|
+
from session_profiler.trace import create
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
fixture = Path(__file__).parent / "fixture" / "main.jsonl"
|
|
13
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
14
|
+
out = parse(fixture, tmp)
|
|
15
|
+
df = load(out)
|
|
16
|
+
agents = load_agents(out)["agents"]
|
|
17
|
+
assert len(agents) == 2
|
|
18
|
+
assert agents[1]["parent_agent_id"] == "main"
|
|
19
|
+
m1 = df[(df.event_type == "inference") & (df.message_id == "m1")].iloc[0]
|
|
20
|
+
assert m1.output_tokens == 15, "usage must use final message snapshot exactly once"
|
|
21
|
+
assert len(df[(df.event_type == "tool") & df.concurrent]) == 2
|
|
22
|
+
assert bool(df[(df.event_type == "tool") & (df.tool_use_id == "tool-1")].iloc[0].is_error)
|
|
23
|
+
assert "## At a glance" in brief(out)
|
|
24
|
+
trace_path = create(out)
|
|
25
|
+
assert trace_path.exists()
|
|
26
|
+
with gzip.open(trace_path, "rt", encoding="utf-8") as fh:
|
|
27
|
+
trace = json.load(fh)
|
|
28
|
+
names = {event.get("name") for event in trace["traceEvents"]}
|
|
29
|
+
track_names = {event.get("args", {}).get("name") for event in trace["traceEvents"]}
|
|
30
|
+
assert "Session overview" in track_names
|
|
31
|
+
assert "usage tokens" in names
|
|
32
|
+
print("fixture ok")
|
|
33
|
+
|
|
34
|
+
codex_home = Path(__file__).parent / "codex_home"
|
|
35
|
+
codex_fixture = codex_home / "sessions" / "2026" / "01" / "01" / "rollout-root.jsonl"
|
|
36
|
+
previous = os.environ.get("CODEX_HOME")
|
|
37
|
+
os.environ["CODEX_HOME"] = str(codex_home)
|
|
38
|
+
try:
|
|
39
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
40
|
+
out = parse(codex_fixture, tmp)
|
|
41
|
+
df = load(out)
|
|
42
|
+
metadata = load_agents(out)
|
|
43
|
+
assert metadata["provider"] == "codex"
|
|
44
|
+
assert len(metadata["agents"]) == 2
|
|
45
|
+
assert metadata["agents"][1]["parent_agent_id"] == "main"
|
|
46
|
+
assert metadata["agents"][1]["spawn_tool_use_id"] == "call-spawn"
|
|
47
|
+
first = df[(df.event_type == "inference") & (df.agent_id == "main")].iloc[0]
|
|
48
|
+
assert first.input_tokens == 60 and first.cache_read_input_tokens == 40
|
|
49
|
+
assert first.estimated_cost_usd > 0
|
|
50
|
+
assert df[df.event_type == "tool"].iloc[0].duration_ms == 2000
|
|
51
|
+
assert "Provider: `codex`" in brief(out)
|
|
52
|
+
assert create(out).exists()
|
|
53
|
+
finally:
|
|
54
|
+
if previous is None:
|
|
55
|
+
os.environ.pop("CODEX_HOME", None)
|
|
56
|
+
else:
|
|
57
|
+
os.environ["CODEX_HOME"] = previous
|
|
58
|
+
print("codex fixture ok")
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Validate Market
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="../../assets/skill-cards/validate-market.svg" alt="validate-market skill card" width="100%">
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
Run an honest market-fit and viability audit that compares competitors, tests
|
|
8
|
+
the case for the idea, and ends with clear pass, middle, or kill criteria.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
Install this skill for your user account:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npx @tamng0905/builder-essential-skills --skill validate-market
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Install it into the current repository instead:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx @tamng0905/builder-essential-skills --skill validate-market --project
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Restart Claude Code or Codex, then ask it to audit a product, market, or
|
|
25
|
+
business idea before you invest more time.
|
|
26
|
+
|
|
27
|
+
See the full workflow in [SKILL.md](SKILL.md).
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: validate-market
|
|
3
|
+
description: >-
|
|
4
|
+
Run an honest market-fit and viability audit of any project or idea and produce
|
|
5
|
+
a decision doc, not code. Use when someone asks "is this viable as a business",
|
|
6
|
+
"audit the market fit", "make the case and compare to competitors", "should I
|
|
7
|
+
apply to YC or bootstrap", or wants to validate a project, product, or market
|
|
8
|
+
before investing more time. Gathers verifiable traction data first, researches
|
|
9
|
+
the competitive field, gets an independent cold read, writes a doc with
|
|
10
|
+
pre-committed pass/middle/kill criteria and a concrete week-1 assignment, then
|
|
11
|
+
hardens it with an adversarial review loop.
|
|
12
|
+
compatibility: claude-code
|
|
13
|
+
allowed-tools:
|
|
14
|
+
- Read
|
|
15
|
+
- Write
|
|
16
|
+
- Edit
|
|
17
|
+
- Grep
|
|
18
|
+
- Glob
|
|
19
|
+
- Bash
|
|
20
|
+
- WebSearch
|
|
21
|
+
- WebFetch
|
|
22
|
+
- Agent
|
|
23
|
+
- AskUserQuestion
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
# Validate a project / market
|
|
27
|
+
|
|
28
|
+
You are running a validation audit. The deliverable is one document that answers: is
|
|
29
|
+
there a business here, who else is fighting for it, where are the openings, and what is
|
|
30
|
+
the next concrete action. The audit exists to protect months of the founder's life, so
|
|
31
|
+
honesty outranks encouragement everywhere.
|
|
32
|
+
|
|
33
|
+
**Hard gate: no implementation.** No code, no scaffolding, no renames, no site changes.
|
|
34
|
+
The only output is the doc (plus its adversarial review). If the user asked for a doc in
|
|
35
|
+
a specific place, put it there; otherwise default to `docs/research/<slug>-market-viability-audit.md`
|
|
36
|
+
in a repo, or `./<slug>-market-viability-audit.md` outside one.
|
|
37
|
+
|
|
38
|
+
## Posture (non-negotiable)
|
|
39
|
+
|
|
40
|
+
- **Interest is not demand.** Stars, waitlists, compliments, and "that's interesting"
|
|
41
|
+
count for nothing. Behavior counts: money, panic when it breaks, unprompted return
|
|
42
|
+
usage, someone building their workflow around it.
|
|
43
|
+
- **The status quo is competitor #1.** The cheap workaround (copy-paste, a spreadsheet,
|
|
44
|
+
a config file convention) beats every named startup in the competitor table. Always
|
|
45
|
+
list it first and price what it costs the user today.
|
|
46
|
+
- **Take a position on everything.** Never write "there are many ways to think about
|
|
47
|
+
this" or "that could work". Say what will or won't work on the evidence you have, and
|
|
48
|
+
name what evidence would change your mind.
|
|
49
|
+
- **Papercut vs bleeding wound.** State plainly whether the pain is acute for a small
|
|
50
|
+
population or mild for a large one, and which population is actually reachable.
|
|
51
|
+
- **Lead with the disconfirming evidence.** The doc's first section after the verdict is
|
|
52
|
+
the honest demand baseline, even (especially) when it is embarrassing.
|
|
53
|
+
|
|
54
|
+
## Phase 0: Ground truth before opinion
|
|
55
|
+
|
|
56
|
+
Collect verifiable numbers before writing a single judgment. Do not skip this; it is
|
|
57
|
+
what separates an audit from a vibe.
|
|
58
|
+
|
|
59
|
+
- Repo: age, stars, forks, contributors (`gh repo view <repo> --json stargazerCount,forkCount,createdAt,isPrivate`).
|
|
60
|
+
- Live product: hit real endpoints (`curl` the landing page, any public directory or
|
|
61
|
+
stats API). Distinguish "deployed" from "used".
|
|
62
|
+
- Any analytics, revenue, waitlist, install counts the user can show. Ask if not obvious.
|
|
63
|
+
- Apply the bar: **would anyone outside the project be upset if it disappeared
|
|
64
|
+
tomorrow?** Write the answer down. If it is "no", that fact dominates the whole doc
|
|
65
|
+
and every recommendation must be downstream of fixing it.
|
|
66
|
+
|
|
67
|
+
## Phase 1: Problem and wedge
|
|
68
|
+
|
|
69
|
+
- State the problem in the founder's own words; quote them. The founder's phrasing
|
|
70
|
+
usually reveals the wedge better than the pitch does.
|
|
71
|
+
- Identify the **narrowest wedge**: the one flow someone would adopt this week. One
|
|
72
|
+
sentence, verb first ("move X from A to B in 10 seconds"). If the value story needs
|
|
73
|
+
the whole platform, say that is a red flag, not a roadmap.
|
|
74
|
+
- Name the **target user as a findable human**: role, where they hang out, what they
|
|
75
|
+
already pay for. "Developers" or "enterprises" is a filter, not a person.
|
|
76
|
+
|
|
77
|
+
## Phase 2: Landscape research
|
|
78
|
+
|
|
79
|
+
Use WebSearch with **generalized category terms**, never the product's name or any
|
|
80
|
+
stealth details. Run at least these four searches and read the top results:
|
|
81
|
+
|
|
82
|
+
1. Standards and platform state of the category ("<category> protocol landscape <year>").
|
|
83
|
+
2. Funding and startups in the category ("<category> startups <year> funding").
|
|
84
|
+
3. Direct adjacent products solving the same wedge ("<the wedge, described generically> tool").
|
|
85
|
+
4. What incumbents absorbed recently ("<big platform> <category> features <year>").
|
|
86
|
+
|
|
87
|
+
Synthesize three things explicitly:
|
|
88
|
+
|
|
89
|
+
- **Who owns the lane** the wedge sits in, and whether it is genuinely open.
|
|
90
|
+
- **Feature or company?** Could a platform above (the IDE, the OS, the incumbent chat
|
|
91
|
+
tool, the lab) absorb this in one release? Name the absorption clock honestly.
|
|
92
|
+
- **Is the timing argument already won?** If the market no longer needs educating,
|
|
93
|
+
that cuts both ways: no education cost, but the obvious framing is taken.
|
|
94
|
+
|
|
95
|
+
If WebSearch is unavailable, say so in the doc and proceed on in-distribution knowledge,
|
|
96
|
+
clearly labeled as such.
|
|
97
|
+
|
|
98
|
+
## Phase 3: Premises
|
|
99
|
+
|
|
100
|
+
Write 3 to 5 premises the whole recommendation rests on. Mark each **verified** (with
|
|
101
|
+
the evidence) or **assumed** (with how the founder would check it). Then attack the one
|
|
102
|
+
you are most attached to yourself; if you cannot break it, say what external event
|
|
103
|
+
would.
|
|
104
|
+
|
|
105
|
+
## Phase 4: Independent cold read
|
|
106
|
+
|
|
107
|
+
Get a second opinion that has not seen your reasoning:
|
|
108
|
+
|
|
109
|
+
- If `codex` is on PATH: assemble a structured context block (product, stage and demand
|
|
110
|
+
evidence, founder's stated problem, landscape summary, premises) into a temp file and
|
|
111
|
+
run `codex exec` read-only against it with these asks: (1) steelman the strongest
|
|
112
|
+
version, (2) quote the one detail that reveals what to actually build, (3) name one
|
|
113
|
+
premise that is wrong and the evidence that would prove it, (4) a 48-hour plan with
|
|
114
|
+
channels, metrics, and kill criteria.
|
|
115
|
+
- Otherwise: dispatch a fresh subagent (Agent tool) with the same prompt. Fresh context
|
|
116
|
+
is the point; do not paste your own conclusions.
|
|
117
|
+
|
|
118
|
+
In the doc, quote the cold read's sharpest challenge and state explicitly whether you
|
|
119
|
+
**adopt** it (revise the premise) or **contest** it (and why). Silently agreeing with
|
|
120
|
+
yourself is the failure mode this phase exists to prevent.
|
|
121
|
+
|
|
122
|
+
## Phase 5: Write the doc
|
|
123
|
+
|
|
124
|
+
Structure (adapt names, keep the order; verdict first, evidence before judgment):
|
|
125
|
+
|
|
126
|
+
1. **Header + verdict up front.** One paragraph: is there a business here, what is the
|
|
127
|
+
binding constraint, what to do next. No hedging.
|
|
128
|
+
2. **Problem statement.**
|
|
129
|
+
3. **Demand evidence (the honest part).** External evidence first, internal second,
|
|
130
|
+
clearly separated. Include the "would anyone be upset?" answer.
|
|
131
|
+
4. **Status quo.** The workarounds and what they cost. Papercut-or-wound verdict.
|
|
132
|
+
5. **Target user and narrowest wedge.**
|
|
133
|
+
6. **Market and timing.** The 2-4 numbers that matter, sourced.
|
|
134
|
+
7. **Competitive field.** A table: player, what it is, overlap, real threat (with the
|
|
135
|
+
status quo as row 1 and a one-line "why" in every threat cell). Then 1-3 structural
|
|
136
|
+
observations, including the feature-vs-company question.
|
|
137
|
+
8. **Strengths.** Only real, verifiable ones. "Founder-problem fit" counts; "great
|
|
138
|
+
tech" only counts if it changes distribution or cost structure.
|
|
139
|
+
9. **Weaknesses and risks.** Include the ones the founder will not enjoy reading
|
|
140
|
+
(naming/trademark, platform absorption, solo-founder, undefined pricing). For each,
|
|
141
|
+
say what to do about it and what it costs.
|
|
142
|
+
10. **Premises** (from Phase 3, with the cold-read revision applied).
|
|
143
|
+
11. **Cross-model perspective** (from Phase 4, positions preserved).
|
|
144
|
+
12. **Approaches considered.** 2-3, always including a **minimal validation sprint**
|
|
145
|
+
(weeks, near-zero cost, outreach-first) and usually a **lateral repositioning**.
|
|
146
|
+
Effort, risk, pros, cons, what existing assets each reuses.
|
|
147
|
+
13. **Recommendation.** Pick one. If the honest answer is "sequence, don't choose",
|
|
148
|
+
give the sequence with dates.
|
|
149
|
+
14. **Business model sketch**, explicitly deferred until after validation. Name the
|
|
150
|
+
closest working analogy (e.g. the Tailscale playbook) rather than inventing tiers
|
|
151
|
+
from nothing.
|
|
152
|
+
15. **Open questions**, each with where its answer will come from.
|
|
153
|
+
16. **Success criteria: three bands, pre-committed** (see rigor checklist below).
|
|
154
|
+
17. **The assignment.** One concrete real-world action for this week. Not "go build";
|
|
155
|
+
an outreach or observation action with names and counts.
|
|
156
|
+
|
|
157
|
+
### Rigor checklist for the pre-committed criteria
|
|
158
|
+
|
|
159
|
+
These are the holes adversarial review reliably finds. Close them before review:
|
|
160
|
+
|
|
161
|
+
- **Every branch has a consequence, including kill.** "Kill (or re-frame)" is not a
|
|
162
|
+
decision. Pass, middle, and kill each name the next action and its deadline.
|
|
163
|
+
- **Band precedence is explicit.** Mixed results happen; say which band wins (e.g.
|
|
164
|
+
"kill conditions are evaluated first").
|
|
165
|
+
- **Denominators are fixed.** "Up to 100 asks" plus "fewer than 5 replies" is elastic;
|
|
166
|
+
add a minimum-effort precondition ("kill is valid only if 80+ asks were sent, else
|
|
167
|
+
the verdict is insufficient effort, extend one week").
|
|
168
|
+
- **Terms are defined.** "Serious reply", "real conversation", "active user" each get
|
|
169
|
+
one defining line where they are used as triggers.
|
|
170
|
+
- **Calendar math closes.** If the recommendation targets an external deadline (a
|
|
171
|
+
batch application, a launch window), the sprint end date plus writing time must land
|
|
172
|
+
before it. Do the arithmetic in the doc.
|
|
173
|
+
- **Measurement exists.** If a criterion needs instrumentation, verifying that
|
|
174
|
+
instrumentation is a named week-1 task, with a self-report fallback.
|
|
175
|
+
- **Week 1 is schedulable.** Cold outreach has reply latency; targets for week 1 are
|
|
176
|
+
"scheduled", not "completed". State the founder-availability assumption (full-time
|
|
177
|
+
vs nights-and-weekends) and give the part-time variant.
|
|
178
|
+
- **Outreach measures the market, not the founder's DM skills.** Require 2+ channels
|
|
179
|
+
and absolute count bars, not conversion rates.
|
|
180
|
+
|
|
181
|
+
## Phase 6: Adversarial review loop
|
|
182
|
+
|
|
183
|
+
1. Dispatch a fresh subagent (Agent tool) that reads only the doc, not this
|
|
184
|
+
conversation. Have it review 5 dimensions: completeness (are all promised questions
|
|
185
|
+
answered), consistency (do sections contradict, does the timeline survive its own
|
|
186
|
+
calendar), clarity (could the founder act without asking anything), scope (audit
|
|
187
|
+
only, no padding), feasibility (executable by this founder in the stated time).
|
|
188
|
+
Ask for a numbered issue list and a 1-10 score. Tell it not to fact-check external
|
|
189
|
+
market claims, only internal logic.
|
|
190
|
+
2. Fix every real issue in the doc. Push back in the doc itself where the reviewer is
|
|
191
|
+
wrong, do not silently drop findings.
|
|
192
|
+
3. Re-dispatch once. Stop after two re-reviews or when new findings are cosmetic;
|
|
193
|
+
record any surviving disagreements in the doc under "Reviewer concerns".
|
|
194
|
+
4. Report to the user: rounds run, issues found and fixed, final score.
|
|
195
|
+
|
|
196
|
+
## Closing
|
|
197
|
+
|
|
198
|
+
End with, in the final message: the verdict in two sentences, the doc's location, the
|
|
199
|
+
three-band decision rule, and the week-1 assignment. If the founder profile fits (real
|
|
200
|
+
problem, domain expertise, agency), say so plainly and point at the relevant next step
|
|
201
|
+
(accelerator application, design-partner outreach), but only when the evidence in the
|
|
202
|
+
doc supports it. No congratulations for work the market has not yet validated.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Write Blog
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="../../assets/skill-cards/write-blog.svg" alt="write-blog skill card" width="100%">
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
Write and ship a human-sounding blog post that targets search traffic, avoids
|
|
8
|
+
keyword cannibalization, and connects the finished post to the project site.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
Install this skill for your user account:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npx @tamng0905/builder-essential-skills --skill write-blog
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Install it into the current repository instead:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx @tamng0905/builder-essential-skills --skill write-blog --project
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Restart Claude Code or Codex, then ask it to draft, edit, or publish a blog
|
|
25
|
+
post for the project.
|
|
26
|
+
|
|
27
|
+
See the full workflow in [SKILL.md](SKILL.md).
|