@opendirectory.dev/skills 0.1.43 → 0.1.45
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/package.json +1 -1
- package/registry.json +16 -0
- package/skills/oss-launch-kit/.env.example +2 -0
- package/skills/oss-launch-kit/PRD.md +122 -0
- package/skills/oss-launch-kit/README.md +27 -0
- package/skills/oss-launch-kit/SKILL.md +33 -0
- package/skills/oss-launch-kit/TECHNICAL_DESIGN.md +187 -0
- package/skills/oss-launch-kit/evals/cli-cli.full.md +49 -0
- package/skills/oss-launch-kit/evals/evals.json +44 -0
- package/skills/oss-launch-kit/evals/octocat-hello-world.full.md +53 -0
- package/skills/oss-launch-kit/evals/pydantic-pydantic.full.md +152 -0
- package/skills/oss-launch-kit/evals/vercel-next-js.full.md +152 -0
- package/skills/oss-launch-kit/hero.png +0 -0
- package/skills/oss-launch-kit/references/channel_rules.md +9 -0
- package/skills/oss-launch-kit/references/launch_framework.md +10 -0
- package/skills/oss-launch-kit/references/output_template.md +3 -0
- package/skills/oss-launch-kit/scripts/__pycache__/build_product_brief.cpython-313.pyc +0 -0
- package/skills/oss-launch-kit/scripts/__pycache__/generate_assets.cpython-313.pyc +0 -0
- package/skills/oss-launch-kit/scripts/build_product_brief.py +335 -0
- package/skills/oss-launch-kit/scripts/fetch_repo_context.py +169 -0
- package/skills/oss-launch-kit/scripts/generate_assets.py +526 -0
- package/skills/oss-launch-kit/scripts/run.py +41 -0
- package/skills/oss-launch-kit/scripts/test_logic.py +99 -0
- package/skills/pricing-finder/.env.example +15 -0
- package/skills/pricing-finder/README.md +142 -0
- package/skills/pricing-finder/SKILL.md +748 -0
- package/skills/pricing-finder/evals/evals.json +124 -0
- package/skills/pricing-finder/references/extraction-guide.md +156 -0
- package/skills/pricing-finder/references/positioning-guide.md +114 -0
- package/skills/pricing-finder/references/pricing-models.md +113 -0
- package/skills/pricing-finder/requirements.txt +8 -0
- package/skills/pricing-finder/scripts/research.py +449 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Launch Kit
|
|
2
|
+
|
|
3
|
+
## Repo Summary
|
|
4
|
+
pydantic/pydantic
|
|
5
|
+
|
|
6
|
+
## Audience
|
|
7
|
+
end users
|
|
8
|
+
|
|
9
|
+
## Description
|
|
10
|
+
Data validation using Python type hints
|
|
11
|
+
|
|
12
|
+
# Show HN Draft
|
|
13
|
+
|
|
14
|
+
## Title
|
|
15
|
+
Show HN: pydantic/pydantic - Data validation using Python type hints
|
|
16
|
+
|
|
17
|
+
## Short Intro
|
|
18
|
+
Low-confidence repo context. Title-only draft recommended.
|
|
19
|
+
|
|
20
|
+
## Core Explanation
|
|
21
|
+
Not enough README signal to write a reliable body.
|
|
22
|
+
|
|
23
|
+
## Feedback Ask
|
|
24
|
+
Please review the repo framing and README quality before posting.
|
|
25
|
+
|
|
26
|
+
## Notes
|
|
27
|
+
- Confidence: high
|
|
28
|
+
|
|
29
|
+
# Product Hunt Draft
|
|
30
|
+
|
|
31
|
+
## Tagline
|
|
32
|
+
pydantic/pydantic: open-source project for end users
|
|
33
|
+
|
|
34
|
+
## Description
|
|
35
|
+
Built for end users. Keeps the pitch grounded in repo facts.
|
|
36
|
+
|
|
37
|
+
## Maker Comment
|
|
38
|
+
Draft only. Edit before posting.
|
|
39
|
+
|
|
40
|
+
## Notes
|
|
41
|
+
- Confidence: high
|
|
42
|
+
|
|
43
|
+
# Reddit Drafts
|
|
44
|
+
|
|
45
|
+
## Variant 1
|
|
46
|
+
Subreddit: r/SideProject (general maker context)
|
|
47
|
+
|
|
48
|
+
I built pydantic/pydantic for end users because data validation using python type hints.
|
|
49
|
+
|
|
50
|
+
This is a fit for general maker context; I’m sharing it here to get feedback, not to spam the subreddit.
|
|
51
|
+
|
|
52
|
+
It focuses on the repo metadata and README.
|
|
53
|
+
|
|
54
|
+
I built this myself and will only post where the subreddit rules allow self-promo.
|
|
55
|
+
|
|
56
|
+
If this fits the community, I’d appreciate feedback on the launch angle and any missing context.
|
|
57
|
+
|
|
58
|
+
## Variant 2
|
|
59
|
+
Subreddit: r/opensource (open-source launch context)
|
|
60
|
+
|
|
61
|
+
I built pydantic/pydantic for end users because data validation using python type hints and I think the interesting part is the launch workflow, not the code itself.
|
|
62
|
+
|
|
63
|
+
I’m posting in open-source launch context because it’s an OSS launch question first and a product post second.
|
|
64
|
+
|
|
65
|
+
The repo signals I leaned on were README text and metadata.
|
|
66
|
+
|
|
67
|
+
I built this myself and will only post where the subreddit rules allow self-promo.
|
|
68
|
+
|
|
69
|
+
Curious whether this framing would land with open-source launch context.
|
|
70
|
+
|
|
71
|
+
## Variant 3
|
|
72
|
+
Subreddit: r/programming (developer audience)
|
|
73
|
+
|
|
74
|
+
I built pydantic/pydantic for end users because data validation using python type hints, and the challenge was keeping the launch copy specific without turning it into jargon.
|
|
75
|
+
|
|
76
|
+
For developer audience, I’d mainly want feedback on whether the problem/solution framing is actually useful to builders.
|
|
77
|
+
|
|
78
|
+
The repo cues I used were Data validation using Python type hints and README details.
|
|
79
|
+
|
|
80
|
+
I built this myself and will only post where the subreddit rules allow self-promo.
|
|
81
|
+
|
|
82
|
+
Would this problem/solution framing feel useful to builders in developer audience?
|
|
83
|
+
|
|
84
|
+
## Variant 4
|
|
85
|
+
Subreddit: r/devtools (developer tools audience)
|
|
86
|
+
|
|
87
|
+
I built pydantic/pydantic for end users because data validation using python type hints and I wanted a cleaner way to turn repo facts into launch copy.
|
|
88
|
+
|
|
89
|
+
This angle fits developer tools audience; I’m sharing it for feedback on the workflow, not as a promo blast.
|
|
90
|
+
|
|
91
|
+
The main angle here is Data validation using Python type hints, with README details as supporting context.
|
|
92
|
+
|
|
93
|
+
I built this myself and will only post where the subreddit rules allow self-promo.
|
|
94
|
+
|
|
95
|
+
Would this problem/solution framing feel useful to builders in developer tools audience?
|
|
96
|
+
|
|
97
|
+
## Variant 5
|
|
98
|
+
Subreddit: r/commandline (command-line users)
|
|
99
|
+
|
|
100
|
+
I built pydantic/pydantic for end users because data validation using python type hints.
|
|
101
|
+
|
|
102
|
+
This is a fit for command-line users; I’m sharing it here to get feedback, not to spam the subreddit.
|
|
103
|
+
|
|
104
|
+
It focuses on the repo metadata and README.
|
|
105
|
+
|
|
106
|
+
I built this myself and will only post where the subreddit rules allow self-promo.
|
|
107
|
+
|
|
108
|
+
If this fits the community, I’d appreciate feedback on the launch angle and any missing context.
|
|
109
|
+
|
|
110
|
+
## Notes
|
|
111
|
+
- Confidence: high
|
|
112
|
+
|
|
113
|
+
# Twitter/X Thread
|
|
114
|
+
|
|
115
|
+
## Tweet 1
|
|
116
|
+
The launch story for pydantic/pydantic should start with data validation using python type hints, not the implementation details.
|
|
117
|
+
|
|
118
|
+
## Tweet 2
|
|
119
|
+
The hard part isn’t the code; it’s explaining why data validation using python type hints matters to the right audience.
|
|
120
|
+
|
|
121
|
+
## Tweet 3
|
|
122
|
+
This kit turns the repo’s own signals into a tighter launch story without inventing traction.
|
|
123
|
+
|
|
124
|
+
## Tweet 4
|
|
125
|
+
It reads the README + GitHub metadata, then drafts Show HN, Product Hunt, Reddit, and X posts that stay close to the repo. The supporting detail I’d foreground is readme and metadata.
|
|
126
|
+
|
|
127
|
+
## Tweet 5
|
|
128
|
+
Feedback welcome on the repo angle: add the repo link before posting
|
|
129
|
+
|
|
130
|
+
## Notes
|
|
131
|
+
- Confidence: high
|
|
132
|
+
|
|
133
|
+
# First-Week Launch Plan
|
|
134
|
+
|
|
135
|
+
## Day 0
|
|
136
|
+
Prep the launch copy, verify links, and trim anything that sounds generic for pydantic/pydantic before posting.
|
|
137
|
+
|
|
138
|
+
## Launch day
|
|
139
|
+
Post the Show HN draft, Product Hunt draft, 5 Reddit variants, and the X thread. Keep replies factual and point people to the repo.
|
|
140
|
+
|
|
141
|
+
## Day 1-2
|
|
142
|
+
Respond to feedback, update the README if commenters point out missing context, and tighten the launch framing around data validation using python type hints for end users.
|
|
143
|
+
|
|
144
|
+
## Day 3-7
|
|
145
|
+
Publish a short follow-up post or changelog note, answer questions in communities where you posted, and reuse useful feedback to improve docs, examples, or onboarding.
|
|
146
|
+
|
|
147
|
+
## Notes
|
|
148
|
+
- Confidence: high
|
|
149
|
+
|
|
150
|
+
# Assumptions / Low-Confidence Notes
|
|
151
|
+
|
|
152
|
+
- Confidence: high
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Launch Kit
|
|
2
|
+
|
|
3
|
+
## Repo Summary
|
|
4
|
+
vercel/next.js
|
|
5
|
+
|
|
6
|
+
## Audience
|
|
7
|
+
authors and maintainers
|
|
8
|
+
|
|
9
|
+
## Description
|
|
10
|
+
The React Framework
|
|
11
|
+
|
|
12
|
+
# Show HN Draft
|
|
13
|
+
|
|
14
|
+
## Title
|
|
15
|
+
Show HN: vercel/next.js - The React Framework
|
|
16
|
+
|
|
17
|
+
## Short Intro
|
|
18
|
+
Low-confidence repo context. Title-only draft recommended.
|
|
19
|
+
|
|
20
|
+
## Core Explanation
|
|
21
|
+
Not enough README signal to write a reliable body.
|
|
22
|
+
|
|
23
|
+
## Feedback Ask
|
|
24
|
+
Please review the repo framing and README quality before posting.
|
|
25
|
+
|
|
26
|
+
## Notes
|
|
27
|
+
- Confidence: high
|
|
28
|
+
|
|
29
|
+
# Product Hunt Draft
|
|
30
|
+
|
|
31
|
+
## Tagline
|
|
32
|
+
vercel/next.js: framework for authors and maintainers
|
|
33
|
+
|
|
34
|
+
## Description
|
|
35
|
+
Built for authors and maintainers. Keeps the pitch grounded in repo facts.
|
|
36
|
+
|
|
37
|
+
## Maker Comment
|
|
38
|
+
Draft only. Edit before posting.
|
|
39
|
+
|
|
40
|
+
## Notes
|
|
41
|
+
- Confidence: high
|
|
42
|
+
|
|
43
|
+
# Reddit Drafts
|
|
44
|
+
|
|
45
|
+
## Variant 1
|
|
46
|
+
Subreddit: r/SideProject (general maker context)
|
|
47
|
+
|
|
48
|
+
I built vercel/next.js for authors and maintainers because the react framework.
|
|
49
|
+
|
|
50
|
+
This is a fit for general maker context; I’m sharing it here to get feedback, not to spam the subreddit.
|
|
51
|
+
|
|
52
|
+
It focuses on the repo metadata and README.
|
|
53
|
+
|
|
54
|
+
I built this myself and will only post where the subreddit rules allow self-promo.
|
|
55
|
+
|
|
56
|
+
If this fits the community, I’d appreciate feedback on the launch angle and any missing context.
|
|
57
|
+
|
|
58
|
+
## Variant 2
|
|
59
|
+
Subreddit: r/opensource (open-source launch context)
|
|
60
|
+
|
|
61
|
+
I built vercel/next.js for authors and maintainers because the react framework and I think the interesting part is the launch workflow, not the code itself.
|
|
62
|
+
|
|
63
|
+
I’m posting in open-source launch context because it’s an OSS launch question first and a product post second.
|
|
64
|
+
|
|
65
|
+
The repo signals I leaned on were README text and metadata.
|
|
66
|
+
|
|
67
|
+
I built this myself and will only post where the subreddit rules allow self-promo.
|
|
68
|
+
|
|
69
|
+
Curious whether this framing would land with open-source launch context.
|
|
70
|
+
|
|
71
|
+
## Variant 3
|
|
72
|
+
Subreddit: r/programming (developer audience)
|
|
73
|
+
|
|
74
|
+
I built vercel/next.js for authors and maintainers because the react framework, and the challenge was keeping the launch copy specific without turning it into jargon.
|
|
75
|
+
|
|
76
|
+
For developer audience, I’d mainly want feedback on whether the problem/solution framing is actually useful to builders.
|
|
77
|
+
|
|
78
|
+
The repo cues I used were The React Framework and README details.
|
|
79
|
+
|
|
80
|
+
I built this myself and will only post where the subreddit rules allow self-promo.
|
|
81
|
+
|
|
82
|
+
Would this problem/solution framing feel useful to builders in developer audience?
|
|
83
|
+
|
|
84
|
+
## Variant 4
|
|
85
|
+
Subreddit: r/devtools (developer tools audience)
|
|
86
|
+
|
|
87
|
+
I built vercel/next.js for authors and maintainers because the react framework and I wanted a cleaner way to turn repo facts into launch copy.
|
|
88
|
+
|
|
89
|
+
This angle fits developer tools audience; I’m sharing it for feedback on the workflow, not as a promo blast.
|
|
90
|
+
|
|
91
|
+
The main angle here is The React Framework, with README details as supporting context.
|
|
92
|
+
|
|
93
|
+
I built this myself and will only post where the subreddit rules allow self-promo.
|
|
94
|
+
|
|
95
|
+
Would this problem/solution framing feel useful to builders in developer tools audience?
|
|
96
|
+
|
|
97
|
+
## Variant 5
|
|
98
|
+
Subreddit: r/commandline (command-line users)
|
|
99
|
+
|
|
100
|
+
I built vercel/next.js for authors and maintainers because the react framework.
|
|
101
|
+
|
|
102
|
+
This is a fit for command-line users; I’m sharing it here to get feedback, not to spam the subreddit.
|
|
103
|
+
|
|
104
|
+
It focuses on the repo metadata and README.
|
|
105
|
+
|
|
106
|
+
I built this myself and will only post where the subreddit rules allow self-promo.
|
|
107
|
+
|
|
108
|
+
If this fits the community, I’d appreciate feedback on the launch angle and any missing context.
|
|
109
|
+
|
|
110
|
+
## Notes
|
|
111
|
+
- Confidence: high
|
|
112
|
+
|
|
113
|
+
# Twitter/X Thread
|
|
114
|
+
|
|
115
|
+
## Tweet 1
|
|
116
|
+
The launch story for vercel/next.js should start with the react framework, not the implementation details.
|
|
117
|
+
|
|
118
|
+
## Tweet 2
|
|
119
|
+
The hard part isn’t the code; it’s explaining why the react framework matters to the right audience.
|
|
120
|
+
|
|
121
|
+
## Tweet 3
|
|
122
|
+
This kit turns the repo’s own signals into a tighter launch story without inventing traction.
|
|
123
|
+
|
|
124
|
+
## Tweet 4
|
|
125
|
+
It reads the README + GitHub metadata, then drafts Show HN, Product Hunt, Reddit, and X posts that stay close to the repo. The supporting detail I’d foreground is readme and metadata.
|
|
126
|
+
|
|
127
|
+
## Tweet 5
|
|
128
|
+
Feedback welcome on the repo angle: add the repo link before posting
|
|
129
|
+
|
|
130
|
+
## Notes
|
|
131
|
+
- Confidence: high
|
|
132
|
+
|
|
133
|
+
# First-Week Launch Plan
|
|
134
|
+
|
|
135
|
+
## Day 0
|
|
136
|
+
Prep the launch copy, verify links, and trim anything that sounds generic for vercel/next.js before posting.
|
|
137
|
+
|
|
138
|
+
## Launch day
|
|
139
|
+
Post the Show HN draft, Product Hunt draft, 5 Reddit variants, and the X thread. Keep replies factual and point people to the repo.
|
|
140
|
+
|
|
141
|
+
## Day 1-2
|
|
142
|
+
Respond to feedback, update the README if commenters point out missing context, and tighten the launch framing around the react framework for authors and maintainers.
|
|
143
|
+
|
|
144
|
+
## Day 3-7
|
|
145
|
+
Publish a short follow-up post or changelog note, answer questions in communities where you posted, and reuse useful feedback to improve docs, examples, or onboarding.
|
|
146
|
+
|
|
147
|
+
## Notes
|
|
148
|
+
- Confidence: high
|
|
149
|
+
|
|
150
|
+
# Assumptions / Low-Confidence Notes
|
|
151
|
+
|
|
152
|
+
- Confidence: high
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
"""Build a grounded product brief from GitHub repo context.
|
|
2
|
+
|
|
3
|
+
This is a deterministic Stage A transformer: it extracts factual signals from
|
|
4
|
+
repo metadata and README text, then assembles a concise launch brief that the
|
|
5
|
+
asset generator can safely use.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import re
|
|
12
|
+
import sys
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _clean(text: str | None) -> str:
|
|
17
|
+
return re.sub(r"\s+", " ", (text or "").strip())
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _strip_markdown(text: str | None) -> str:
|
|
21
|
+
cleaned = text or ""
|
|
22
|
+
cleaned = re.sub(r"\[([^\]]+)\]\([^\)]+\)", r"\1", cleaned)
|
|
23
|
+
cleaned = re.sub(r"\[([^\]]+)\]\[\]", r"\1", cleaned)
|
|
24
|
+
cleaned = re.sub(r"[`*_>#]", "", cleaned)
|
|
25
|
+
return _clean(cleaned)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _first_sentence(text: str) -> str:
|
|
29
|
+
cleaned = _strip_markdown(text)
|
|
30
|
+
if not cleaned:
|
|
31
|
+
return ""
|
|
32
|
+
match = re.search(r"^(.+?[.!?])(\s|$)", cleaned)
|
|
33
|
+
return match.group(1).strip() if match else cleaned
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _readme_paragraphs(readme_text: str) -> list[str]:
|
|
37
|
+
blocks = [block.strip() for block in re.split(r"\n\s*\n", readme_text or "")]
|
|
38
|
+
return [block for block in blocks if block]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _readme_bullets(readme_text: str) -> list[str]:
|
|
42
|
+
bullets = []
|
|
43
|
+
for line in (readme_text or "").splitlines():
|
|
44
|
+
if re.match(r"^\s*[-*+]\s+", line):
|
|
45
|
+
bullets.append(_strip_markdown(re.sub(r"^\s*[-*+]\s+", "", line)))
|
|
46
|
+
return bullets
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _infer_audience(repo_context: dict[str, Any], readme_text: str) -> tuple[str, str]:
|
|
50
|
+
text = f"{repo_context.get('description') or ''}\n{readme_text}".lower()
|
|
51
|
+
candidates = [
|
|
52
|
+
("developers", ["developer", "developers", "devs", "engineers", "coding"]),
|
|
53
|
+
("teams", ["team", "teams", "workspace", "collaboration"]),
|
|
54
|
+
("authors and maintainers", ["maintainer", "maintainers", "author", "open source"]),
|
|
55
|
+
("data/infra users", ["api", "cli", "server", "deploy", "infra", "pipeline"]),
|
|
56
|
+
("end users", ["app", "users", "personal", "workflow"]),
|
|
57
|
+
]
|
|
58
|
+
for label, keywords in candidates:
|
|
59
|
+
if any(keyword in text for keyword in keywords):
|
|
60
|
+
return label, "medium"
|
|
61
|
+
return "unknown", "low"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _infer_project_type(repo_context: dict[str, Any], readme_text: str) -> str:
|
|
65
|
+
text = f"{repo_context.get('description') or ''}\n{readme_text}".lower()
|
|
66
|
+
if any(term in text for term in ["framework", "platform"]):
|
|
67
|
+
return "framework"
|
|
68
|
+
if any(term in text for term in ["library", "package", "module", "sdk"]):
|
|
69
|
+
return "library"
|
|
70
|
+
if any(term in text for term in ["cli", "tool", "automation", "workflow"]):
|
|
71
|
+
return "tool"
|
|
72
|
+
if any(term in text for term in ["app", "saas", "dashboard", "interface"]):
|
|
73
|
+
return "app"
|
|
74
|
+
if any(term in text for term in ["template", "boilerplate", "starter"]):
|
|
75
|
+
return "template"
|
|
76
|
+
return "project"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _evaluate_channel_fitness(project_type: str, readiness: str) -> dict[str, str]:
|
|
80
|
+
"""Evaluate fitness for launch channels based on project type and readiness."""
|
|
81
|
+
fitness = {
|
|
82
|
+
"show_hn": "medium",
|
|
83
|
+
"product_hunt": "medium",
|
|
84
|
+
"reddit": "medium",
|
|
85
|
+
"twitter_x": "high",
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# Weak repo / Unready project logic
|
|
89
|
+
if readiness == "low":
|
|
90
|
+
return {
|
|
91
|
+
"show_hn": "low",
|
|
92
|
+
"product_hunt": "low",
|
|
93
|
+
"reddit": "low",
|
|
94
|
+
"twitter_x": "medium"
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# Project type nuances for ready projects
|
|
98
|
+
if project_type in ["library", "tool", "framework"]:
|
|
99
|
+
fitness["product_hunt"] = "low"
|
|
100
|
+
fitness["show_hn"] = "high"
|
|
101
|
+
elif project_type == "app":
|
|
102
|
+
fitness["product_hunt"] = "high"
|
|
103
|
+
fitness["show_hn"] = "medium"
|
|
104
|
+
elif project_type == "template":
|
|
105
|
+
fitness["show_hn"] = "low"
|
|
106
|
+
fitness["product_hunt"] = "low"
|
|
107
|
+
fitness["reddit"] = "high"
|
|
108
|
+
|
|
109
|
+
return fitness
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _infer_problem(repo_context: dict[str, Any], readme_text: str) -> tuple[str, str]:
|
|
113
|
+
description = _strip_markdown(repo_context.get("description"))
|
|
114
|
+
if description:
|
|
115
|
+
return description, "medium"
|
|
116
|
+
|
|
117
|
+
paras = _readme_paragraphs(readme_text)
|
|
118
|
+
if paras:
|
|
119
|
+
return _first_sentence(paras[0]), "low"
|
|
120
|
+
|
|
121
|
+
return "Unknown from available repo metadata.", "low"
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _infer_value_prop(problem: str, key_features: list[str]) -> str:
|
|
125
|
+
if key_features:
|
|
126
|
+
first = key_features[0]
|
|
127
|
+
return f"Solves {problem.lower()} with {first.lower()}."
|
|
128
|
+
return f"Grounds launch copy in the repository's own README and metadata around {problem.lower()}."
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _extract_key_features(readme_text: str, repo_context: dict[str, Any]) -> list[str]:
|
|
132
|
+
bullets = _readme_bullets(readme_text)
|
|
133
|
+
features: list[str] = []
|
|
134
|
+
for bullet in bullets:
|
|
135
|
+
if len(features) >= 5:
|
|
136
|
+
break
|
|
137
|
+
if len(bullet) < 3:
|
|
138
|
+
continue
|
|
139
|
+
features.append(bullet)
|
|
140
|
+
|
|
141
|
+
if not features:
|
|
142
|
+
description = _strip_markdown(repo_context.get("description"))
|
|
143
|
+
if description:
|
|
144
|
+
features.append(description)
|
|
145
|
+
|
|
146
|
+
return features[:5]
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _build_links(repo_context: dict[str, Any]) -> dict[str, str | None]:
|
|
150
|
+
return {
|
|
151
|
+
"repo": repo_context.get("repo_url"),
|
|
152
|
+
"homepage": repo_context.get("homepage") or None,
|
|
153
|
+
"readme_source": repo_context.get("fetched_from", {}).get("readme_api"),
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _check_readiness_signals(readme_text: str, repo_context: dict[str, Any]) -> dict[str, bool]:
|
|
158
|
+
text = readme_text.lower()
|
|
159
|
+
desc = _clean(repo_context.get("description")).lower()
|
|
160
|
+
return {
|
|
161
|
+
"has_license": bool(repo_context.get("license")),
|
|
162
|
+
"has_install": any(term in text for term in ["# install", "npm install", "pip install", "pip3 install", "go get", "setup", "quickstart"]),
|
|
163
|
+
"has_usage_example": any(term in text for term in ["example", "usage", "how to use", "code snippet"]),
|
|
164
|
+
"has_contributing": any(term in text for term in ["contributing", "pull request", "license path", "issue tracker"]),
|
|
165
|
+
"has_quickstart": any(term in text for term in ["# quickstart", "tl;dr", "getting started", "one-command", "fast start"]),
|
|
166
|
+
"has_clear_positioning": len(desc) > 30 and any(term in desc for term in ["built for", "helps", "allows", "solves", "for devs", "for users"]),
|
|
167
|
+
"has_demo_or_proof": any(term in text for term in ["demo", "screenshot", "gif", "preview", "live link", "http", "view it"]),
|
|
168
|
+
"has_description": len(desc) > 20,
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _generate_fix_plan(signals: dict[str, bool]) -> list[dict[str, Any]]:
|
|
173
|
+
"""Generate a prioritized list of fixes for the repository."""
|
|
174
|
+
plan = []
|
|
175
|
+
|
|
176
|
+
if not signals["has_quickstart"]:
|
|
177
|
+
plan.append({
|
|
178
|
+
"id": "add_quickstart_example",
|
|
179
|
+
"severity": "high",
|
|
180
|
+
"reason": "New users need a copy-paste example to reach 'first success' quickly.",
|
|
181
|
+
"suggested_fix": "Add a 'Quickstart' section at the top of README with a single command or minimal code snippet.",
|
|
182
|
+
"likely_files": ["README.md"]
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
if not signals["has_usage_example"]:
|
|
186
|
+
plan.append({
|
|
187
|
+
"id": "add_usage_example",
|
|
188
|
+
"severity": "high",
|
|
189
|
+
"reason": "Without a concrete usage example, users cannot see how to apply the project.",
|
|
190
|
+
"suggested_fix": "Add a minimal 'Usage' section with one realistic example and expected output.",
|
|
191
|
+
"likely_files": ["README.md", "docs/usage.md"]
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
if not signals["has_clear_positioning"]:
|
|
195
|
+
plan.append({
|
|
196
|
+
"id": "improve_positioning",
|
|
197
|
+
"severity": "high",
|
|
198
|
+
"reason": "The project description is too short or lacks clear target audience context.",
|
|
199
|
+
"suggested_fix": "Update the repository description to clearly state 'Who it is for' and 'What problem it solves'.",
|
|
200
|
+
"likely_files": ["GitHub Metadata", "README.md"]
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
if not signals["has_demo_or_proof"]:
|
|
204
|
+
plan.append({
|
|
205
|
+
"id": "add_demo_or_proof",
|
|
206
|
+
"severity": "medium",
|
|
207
|
+
"reason": "Visual proof or live demos significantly increase conversion and trust.",
|
|
208
|
+
"suggested_fix": "Add a screenshot, GIF, or a link to a live demo/sample output in the README.",
|
|
209
|
+
"likely_files": ["README.md"]
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
if not signals["has_license"]:
|
|
213
|
+
plan.append({
|
|
214
|
+
"id": "add_license",
|
|
215
|
+
"severity": "medium",
|
|
216
|
+
"reason": "OSS users need a clear license to feel safe adopting the code.",
|
|
217
|
+
"suggested_fix": "Add an MIT or Apache-2.0 LICENSE file to the repository root.",
|
|
218
|
+
"likely_files": ["LICENSE"]
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
if not signals["has_contributing"]:
|
|
222
|
+
plan.append({
|
|
223
|
+
"id": "add_contribution_guide",
|
|
224
|
+
"severity": "low",
|
|
225
|
+
"reason": "Contributors need clear expectations for how to propose changes.",
|
|
226
|
+
"suggested_fix": "Create a CONTRIBUTING.md with basic rules (how to open PRs, coding style, etc.).",
|
|
227
|
+
"likely_files": ["CONTRIBUTING.md"]
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
return plan
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def build_product_brief(repo_context: dict[str, Any]) -> dict[str, Any]:
|
|
234
|
+
"""Convert fetched repo context into a grounded launch brief."""
|
|
235
|
+
|
|
236
|
+
readme_text = repo_context.get("readme_text") or ""
|
|
237
|
+
repo_name = repo_context.get("full_name") or repo_context.get("name") or "unknown-repo"
|
|
238
|
+
|
|
239
|
+
problem, problem_confidence = _infer_problem(repo_context, readme_text)
|
|
240
|
+
audience, audience_confidence = _infer_audience(repo_context, readme_text)
|
|
241
|
+
key_features = _extract_key_features(readme_text, repo_context)
|
|
242
|
+
value_prop = _infer_value_prop(problem, key_features)
|
|
243
|
+
|
|
244
|
+
summary = _clean(repo_context.get("description"))
|
|
245
|
+
if not summary:
|
|
246
|
+
summary = _first_sentence(readme_text) or f"Open-source project in {repo_context.get('language') or 'unknown language'}."
|
|
247
|
+
|
|
248
|
+
readiness_signals = _check_readiness_signals(readme_text, repo_context)
|
|
249
|
+
signal_count = sum(1 for v in readiness_signals.values() if v)
|
|
250
|
+
|
|
251
|
+
assumptions: list[str] = []
|
|
252
|
+
if repo_context.get("readme_error"):
|
|
253
|
+
assumptions.append(f"README issue: {repo_context['readme_error']}")
|
|
254
|
+
if audience == "unknown":
|
|
255
|
+
assumptions.append("Audience inferred from repo metadata only.")
|
|
256
|
+
if not key_features:
|
|
257
|
+
assumptions.append("No strong feature bullets extracted from README.")
|
|
258
|
+
if not readiness_signals["has_install"]:
|
|
259
|
+
assumptions.append("No clear installation instructions found in README.")
|
|
260
|
+
|
|
261
|
+
confidence = "high"
|
|
262
|
+
if repo_context.get("confidence") == "low" or audience_confidence == "low" or problem_confidence == "low":
|
|
263
|
+
confidence = "low"
|
|
264
|
+
elif assumptions:
|
|
265
|
+
confidence = "medium"
|
|
266
|
+
|
|
267
|
+
proof_points = []
|
|
268
|
+
if repo_context.get("stars") is not None:
|
|
269
|
+
proof_points.append(f"{repo_context['stars']} GitHub stars")
|
|
270
|
+
if repo_context.get("forks") is not None:
|
|
271
|
+
proof_points.append(f"{repo_context['forks']} forks")
|
|
272
|
+
if repo_context.get("topics"):
|
|
273
|
+
proof_points.append("topics: " + ", ".join(repo_context.get("topics", [])[:5]))
|
|
274
|
+
if repo_context.get("language"):
|
|
275
|
+
proof_points.append(f"primary language: {repo_context['language']}")
|
|
276
|
+
if repo_context.get("license"):
|
|
277
|
+
proof_points.append(f"license: {repo_context['license']}")
|
|
278
|
+
|
|
279
|
+
project_type = _infer_project_type(repo_context, readme_text)
|
|
280
|
+
|
|
281
|
+
# Launch Readiness assessment
|
|
282
|
+
stars = repo_context.get("stars", 0)
|
|
283
|
+
readme_len = len(readme_text)
|
|
284
|
+
|
|
285
|
+
# Heuristic scoring
|
|
286
|
+
if stars >= 50 and readme_len > 1500 and signal_count >= 5:
|
|
287
|
+
score = "high"
|
|
288
|
+
elif stars < 10 or readme_len < 500 or signal_count <= 2:
|
|
289
|
+
score = "low"
|
|
290
|
+
else:
|
|
291
|
+
score = "medium"
|
|
292
|
+
|
|
293
|
+
readiness = {
|
|
294
|
+
"score": score,
|
|
295
|
+
"signals": readiness_signals,
|
|
296
|
+
"fix_plan": _generate_fix_plan(readiness_signals)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
channel_fitness = _evaluate_channel_fitness(project_type, score)
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
"repo_name": repo_name,
|
|
303
|
+
"one_line_summary": summary,
|
|
304
|
+
"project_type": project_type,
|
|
305
|
+
"launch_readiness": readiness,
|
|
306
|
+
"audience": audience,
|
|
307
|
+
"problem_solved": problem,
|
|
308
|
+
"value_proposition": value_prop,
|
|
309
|
+
"key_proof_points": proof_points,
|
|
310
|
+
"key_features": key_features,
|
|
311
|
+
"links": _build_links(repo_context),
|
|
312
|
+
"confidence": confidence,
|
|
313
|
+
"assumptions": assumptions,
|
|
314
|
+
"channel_fitness": channel_fitness,
|
|
315
|
+
"source_confidence": {
|
|
316
|
+
"problem": problem_confidence,
|
|
317
|
+
"audience": audience_confidence,
|
|
318
|
+
"repo_context": repo_context.get("confidence", "low"),
|
|
319
|
+
},
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def main() -> None:
|
|
324
|
+
if len(sys.argv) != 2:
|
|
325
|
+
raise SystemExit("Usage: python build_product_brief.py <repo-context-json>")
|
|
326
|
+
|
|
327
|
+
with open(sys.argv[1], "r", encoding="utf-8") as handle:
|
|
328
|
+
repo_context = json.load(handle)
|
|
329
|
+
|
|
330
|
+
brief = build_product_brief(repo_context)
|
|
331
|
+
print(json.dumps(brief, indent=2, ensure_ascii=True))
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
if __name__ == "__main__":
|
|
335
|
+
main()
|