@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.
Files changed (66) hide show
  1. package/.claude/agents/lead-builder.md +55 -0
  2. package/.claude/agents/lead-reviewer.md +39 -0
  3. package/README.md +276 -0
  4. package/assets/readme-hero.png +0 -0
  5. package/assets/skill-cards/async-learning-teacher.svg +7 -0
  6. package/assets/skill-cards/code-standards.svg +7 -0
  7. package/assets/skill-cards/lead-research.svg +7 -0
  8. package/assets/skill-cards/lead.svg +7 -0
  9. package/assets/skill-cards/orwell-writing.svg +7 -0
  10. package/assets/skill-cards/session-profiler.svg +7 -0
  11. package/assets/skill-cards/validate-market.svg +7 -0
  12. package/assets/skill-cards/write-blog.svg +7 -0
  13. package/bin/builder-essential-skills.js +134 -0
  14. package/install.ps1 +58 -0
  15. package/install.sh +63 -0
  16. package/package.json +24 -0
  17. package/skills/async-learning-teacher/README.md +50 -0
  18. package/skills/async-learning-teacher/SKILL.md +192 -0
  19. package/skills/async-learning-teacher/agents/openai.yaml +4 -0
  20. package/skills/code-standards/README.md +27 -0
  21. package/skills/code-standards/SKILL.md +51 -0
  22. package/skills/lead/README.md +29 -0
  23. package/skills/lead/SKILL.md +222 -0
  24. package/skills/lead/config.py +359 -0
  25. package/skills/lead/dispatch.md +342 -0
  26. package/skills/lead/loop.md +103 -0
  27. package/skills/lead/models.json +34 -0
  28. package/skills/lead/research.md +76 -0
  29. package/skills/lead/status.ps1 +114 -0
  30. package/skills/lead/status.sh +110 -0
  31. package/skills/lead/watchdog.ps1 +90 -0
  32. package/skills/lead/watchdog.sh +88 -0
  33. package/skills/lead-research/README.md +27 -0
  34. package/skills/lead-research/SKILL.md +157 -0
  35. package/skills/lead-research/tactics.md +150 -0
  36. package/skills/orwell-writing/README.md +27 -0
  37. package/skills/orwell-writing/SKILL.md +44 -0
  38. package/skills/orwell-writing/agents/openai.yaml +4 -0
  39. package/skills/session-profiler/README.md +25 -0
  40. package/skills/session-profiler/SKILL.md +103 -0
  41. package/skills/session-profiler/agents/openai.yaml +4 -0
  42. package/skills/session-profiler/scripts/requirements.txt +2 -0
  43. package/skills/session-profiler/scripts/session_profiler/__init__.py +5 -0
  44. package/skills/session-profiler/scripts/session_profiler/analyses.py +246 -0
  45. package/skills/session-profiler/scripts/session_profiler/cli.py +77 -0
  46. package/skills/session-profiler/scripts/session_profiler/dataset.py +369 -0
  47. package/skills/session-profiler/scripts/session_profiler/discover.py +191 -0
  48. package/skills/session-profiler/scripts/session_profiler/toc.py +74 -0
  49. package/skills/session-profiler/scripts/session_profiler/trace.py +146 -0
  50. package/skills/session-profiler/scripts/sp +20 -0
  51. package/skills/session-profiler/scripts/tests/codex_home/sessions/2026/01/01/rollout-child.jsonl +7 -0
  52. package/skills/session-profiler/scripts/tests/codex_home/sessions/2026/01/01/rollout-root.jsonl +10 -0
  53. package/skills/session-profiler/scripts/tests/fixture/main/subagents/agent-abc.jsonl +2 -0
  54. package/skills/session-profiler/scripts/tests/fixture/main/subagents/agent-abc.meta.json +1 -0
  55. package/skills/session-profiler/scripts/tests/fixture/main.jsonl +7 -0
  56. package/skills/session-profiler/scripts/tests/test_fixture.py +58 -0
  57. package/skills/validate-market/README.md +27 -0
  58. package/skills/validate-market/SKILL.md +202 -0
  59. package/skills/write-blog/README.md +27 -0
  60. package/skills/write-blog/SKILL.md +142 -0
  61. package/skills/write-blog/check.sh +52 -0
  62. package/skills/write-blog/reference/angles.md +51 -0
  63. package/skills/write-blog/reference/craft.md +94 -0
  64. package/skills/write-blog/reference/seo.md +116 -0
  65. package/skills/write-blog/reference/voice.md +64 -0
  66. 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 "$@"
@@ -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}}
@@ -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).