replen 0.4.1 → 1.0.1
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/README.md +42 -20
- package/dist/commands.js +83 -0
- package/dist/discover-projects.js +391 -0
- package/dist/discover-roots.js +169 -0
- package/dist/first-ingest.js +140 -0
- package/dist/index.js +56 -0
- package/dist/init.js +23 -3
- package/dist/inject-instruction.js +327 -0
- package/dist/mcp-setup.js +163 -45
- package/dist/skill-install.js +58 -0
- package/dist/sync-projects.js +195 -0
- package/extras/skills/replen-match/SKILL.md +230 -0
- package/package.json +6 -2
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// Orchestrates project discovery + server registration. Tries layered
|
|
2
|
+
// inference strategies (cwd / Claude Code config / hardcoded roots /
|
|
3
|
+
// interactive prompt) until at least one git repo is found, then walks
|
|
4
|
+
// each root recursively to find every repo, then POSTs to
|
|
5
|
+
// /api/projects/bulk for upsert.
|
|
6
|
+
//
|
|
7
|
+
// Persists the user's chosen root(s) to ~/.replen/config.json so future
|
|
8
|
+
// `npx replen sync-projects` runs use the same set without re-prompting.
|
|
9
|
+
import { readConfig, writeConfig } from "./config.js";
|
|
10
|
+
import { discoverProjects } from "./discover-projects.js";
|
|
11
|
+
import { promptForRoot, rootsFromClaudeJson, rootsFromConfig, rootsFromCwdWalkUp, rootsFromEnv, rootsFromFlag, rootsFromHardcoded, } from "./discover-roots.js";
|
|
12
|
+
export async function syncDiscoveredProjects({ token, base, explicitRoots = [], }) {
|
|
13
|
+
const { result, source, prompted } = await resolveAndWalk(explicitRoots);
|
|
14
|
+
// Tell the user what we did, regardless of outcome.
|
|
15
|
+
reportDiscovery(result, source);
|
|
16
|
+
if (result.projects.length === 0) {
|
|
17
|
+
console.log(` · No git repos with GitHub remotes were found. Run \`npx replen sync-projects --root <path>\` to point at a specific dir, or set REPLEN_PROJECT_ROOTS=...`);
|
|
18
|
+
return { discovered: 0, created: 0, updated: 0 };
|
|
19
|
+
}
|
|
20
|
+
// Persist ONLY when the user came in via the interactive prompt.
|
|
21
|
+
// Flags and env vars are deliberate per-invocation overrides — saving
|
|
22
|
+
// them to config creates a confusing dual source of truth and, worse,
|
|
23
|
+
// can lock subsequent commands (like `inject` with no args) into the
|
|
24
|
+
// narrow scope the user chose for a one-off `--root` use.
|
|
25
|
+
if (prompted) {
|
|
26
|
+
await persistRoots(result.scannedRoots);
|
|
27
|
+
}
|
|
28
|
+
const payload = {
|
|
29
|
+
projects: result.projects.map((p) => ({
|
|
30
|
+
slug: p.slug,
|
|
31
|
+
githubFullName: p.githubFullName, // null is impossible here — we filter above
|
|
32
|
+
name: p.name,
|
|
33
|
+
tags: p.tags,
|
|
34
|
+
primaryLanguage: p.primaryLanguage ?? undefined,
|
|
35
|
+
})),
|
|
36
|
+
};
|
|
37
|
+
let res;
|
|
38
|
+
try {
|
|
39
|
+
res = await fetch(`${base}/api/projects/bulk`, {
|
|
40
|
+
method: "POST",
|
|
41
|
+
headers: {
|
|
42
|
+
"x-digest-token": token,
|
|
43
|
+
"content-type": "application/json",
|
|
44
|
+
},
|
|
45
|
+
body: JSON.stringify(payload),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
console.warn(` ✗ Failed to reach ${base}/api/projects/bulk: ${e.message}`);
|
|
50
|
+
console.warn(` Skipping project registration. Run \`npx replen sync-projects\` later to retry.`);
|
|
51
|
+
return { discovered: result.projects.length, created: 0, updated: 0 };
|
|
52
|
+
}
|
|
53
|
+
if (!res.ok) {
|
|
54
|
+
const text = await res.text().catch(() => "");
|
|
55
|
+
console.warn(` ✗ Project registration failed (HTTP ${res.status}): ${text.slice(0, 200)}`);
|
|
56
|
+
return { discovered: result.projects.length, created: 0, updated: 0 };
|
|
57
|
+
}
|
|
58
|
+
const body = (await res.json());
|
|
59
|
+
const created = body.created ?? 0;
|
|
60
|
+
const updated = body.updated ?? 0;
|
|
61
|
+
if (created > 0 || updated > 0) {
|
|
62
|
+
const parts = [];
|
|
63
|
+
if (created > 0)
|
|
64
|
+
parts.push(`${created} new`);
|
|
65
|
+
if (updated > 0)
|
|
66
|
+
parts.push(`${updated} updated`);
|
|
67
|
+
console.log(` ✓ Registered with Replen: ${parts.join(", ")}`);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
console.log(` · All ${result.projects.length} projects already up to date with Replen`);
|
|
71
|
+
}
|
|
72
|
+
return { discovered: result.projects.length, created, updated };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Try discovery strategies in priority order. The first strategy that
|
|
76
|
+
* yields ≥1 project's worth of repos wins; if none do, falls back to
|
|
77
|
+
* the interactive prompt.
|
|
78
|
+
*
|
|
79
|
+
* Returns the walked result, the strategy that supplied the roots,
|
|
80
|
+
* and a flag indicating whether the interactive prompt was used.
|
|
81
|
+
*/
|
|
82
|
+
async function resolveAndWalk(explicitRoots) {
|
|
83
|
+
// Strategies that take precedence and short-circuit on hit.
|
|
84
|
+
const ordered = [
|
|
85
|
+
{ source: "flag", roots: () => rootsFromFlag(explicitRoots) },
|
|
86
|
+
{ source: "env", roots: () => rootsFromEnv() },
|
|
87
|
+
{ source: "config", roots: () => rootsFromConfig() },
|
|
88
|
+
];
|
|
89
|
+
for (const { source, roots } of ordered) {
|
|
90
|
+
const r = await roots();
|
|
91
|
+
if (r.length > 0) {
|
|
92
|
+
const result = discoverProjects(r);
|
|
93
|
+
return { result, source, prompted: false };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Inference strategies combine: walk the union of cwd / Claude / hardcoded.
|
|
97
|
+
// Any one of them surfacing repos is enough; we union to maximise the
|
|
98
|
+
// chance of catching the user's repos no matter where they live.
|
|
99
|
+
const inferred = Array.from(new Set([
|
|
100
|
+
...rootsFromCwdWalkUp(),
|
|
101
|
+
...rootsFromClaudeJson(),
|
|
102
|
+
...rootsFromHardcoded(),
|
|
103
|
+
]));
|
|
104
|
+
if (inferred.length > 0) {
|
|
105
|
+
const result = discoverProjects(inferred);
|
|
106
|
+
if (result.projects.length > 0) {
|
|
107
|
+
return { result, source: pickInferredSource(inferred), prompted: false };
|
|
108
|
+
}
|
|
109
|
+
// Inferred roots existed but contained no GitHub repos — fall
|
|
110
|
+
// through to prompt rather than reporting empty.
|
|
111
|
+
}
|
|
112
|
+
// Last resort: ask.
|
|
113
|
+
const chosen = await promptForRoot();
|
|
114
|
+
if (!chosen) {
|
|
115
|
+
return {
|
|
116
|
+
result: { projects: [], nonGithubSkipped: 0, scannedRoots: inferred },
|
|
117
|
+
source: "prompt",
|
|
118
|
+
prompted: true,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const result = discoverProjects([chosen]);
|
|
122
|
+
return { result, source: "prompt", prompted: true };
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* If the inferred-roots union turned up projects, pick the most
|
|
126
|
+
* specific source for the user-facing message. Order matches priority:
|
|
127
|
+
* cwd is most specific, hardcoded least.
|
|
128
|
+
*/
|
|
129
|
+
function pickInferredSource(roots) {
|
|
130
|
+
if (rootsFromCwdWalkUp().some((r) => roots.includes(r)))
|
|
131
|
+
return "cwd";
|
|
132
|
+
if (rootsFromClaudeJson().some((r) => roots.includes(r)))
|
|
133
|
+
return "claude-json";
|
|
134
|
+
return "hardcoded";
|
|
135
|
+
}
|
|
136
|
+
function reportDiscovery(result, source) {
|
|
137
|
+
const sourceLabel = {
|
|
138
|
+
flag: "from --root flag",
|
|
139
|
+
env: "from REPLEN_PROJECT_ROOTS env",
|
|
140
|
+
config: "from saved config",
|
|
141
|
+
cwd: "via cwd walk-up",
|
|
142
|
+
"claude-json": "via Claude Code's tracked projects",
|
|
143
|
+
hardcoded: "via conventional ~/github, ~/code, ~/projects",
|
|
144
|
+
prompt: "you specified",
|
|
145
|
+
};
|
|
146
|
+
console.log(` · Scanning ${result.scannedRoots.length} root(s) (${sourceLabel[source]}):`);
|
|
147
|
+
for (const r of result.scannedRoots.slice(0, 5)) {
|
|
148
|
+
console.log(` ${r}`);
|
|
149
|
+
}
|
|
150
|
+
if (result.scannedRoots.length > 5) {
|
|
151
|
+
console.log(` … and ${result.scannedRoots.length - 5} more`);
|
|
152
|
+
}
|
|
153
|
+
if (result.projects.length === 0)
|
|
154
|
+
return;
|
|
155
|
+
console.log(` ✓ Found ${result.projects.length} git repo(s) with GitHub remotes:`);
|
|
156
|
+
for (const p of result.projects.slice(0, 5)) {
|
|
157
|
+
const sampleTags = p.tags.slice(0, 3).join(", ") || "(no auto-tags)";
|
|
158
|
+
const localHint = pathHint(p);
|
|
159
|
+
console.log(` • ${p.githubFullName} ${localHint} → ${sampleTags}`);
|
|
160
|
+
}
|
|
161
|
+
if (result.projects.length > 5) {
|
|
162
|
+
console.log(` … and ${result.projects.length - 5} more`);
|
|
163
|
+
}
|
|
164
|
+
if (result.nonGithubSkipped > 0) {
|
|
165
|
+
console.log(` · Skipped ${result.nonGithubSkipped} local repo(s) without a GitHub remote (nothing to match against)`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/** Render "(at ~/projects/drone)" only when the dirname differs from the
|
|
169
|
+
* repo name — i.e. only when there's actual signal worth showing. */
|
|
170
|
+
function pathHint(p) {
|
|
171
|
+
const repoName = (p.githubFullName ?? "").split("/")[1] ?? "";
|
|
172
|
+
const dirName = p.localPath.split("/").pop() ?? "";
|
|
173
|
+
if (!repoName || dirName.toLowerCase() === repoName.toLowerCase())
|
|
174
|
+
return "";
|
|
175
|
+
// Compress home prefix for readability.
|
|
176
|
+
const home = process.env.HOME ?? "";
|
|
177
|
+
const display = home && p.localPath.startsWith(home) ? "~" + p.localPath.slice(home.length) : p.localPath;
|
|
178
|
+
return ` (at ${display})`;
|
|
179
|
+
}
|
|
180
|
+
async function persistRoots(roots) {
|
|
181
|
+
const cfg = await readConfig();
|
|
182
|
+
if (!cfg)
|
|
183
|
+
return;
|
|
184
|
+
// Don't overwrite if config already had a roots list — leave the
|
|
185
|
+
// user's previous choice alone unless they re-prompt.
|
|
186
|
+
if (cfg.projectRoots && cfg.projectRoots.length > 0)
|
|
187
|
+
return;
|
|
188
|
+
await writeConfig({ ...cfg, projectRoots: roots });
|
|
189
|
+
}
|
|
190
|
+
// Returns just the discovered list without sending. Useful for the
|
|
191
|
+
// CLI's `replen list-projects` subcommand (preview mode).
|
|
192
|
+
export async function previewDiscovery(explicitRoots = []) {
|
|
193
|
+
const { result } = await resolveAndWalk(explicitRoots);
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: replen-match
|
|
3
|
+
description: Triage today's Replen candidates against the open codebase, in-session, using subscription tokens. Pulls the day's inventory from Replen, reads the user's local source, produces per-candidate writeups with verdict + effort + concrete file-level impact, then captures star/hide/handoff actions back to Replen. Invoke with `/replen-match` or by saying "run replen on this repo" / "what's new from replen?".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Replen Match — in-session candidate triage
|
|
7
|
+
|
|
8
|
+
You are running the matching loop locally. Replen has fetched a list of
|
|
9
|
+
plausible OSS candidates from the wider ecosystem; you've got the user's
|
|
10
|
+
codebase open. Your job is to decide which (if any) are worth their
|
|
11
|
+
attention, write up the strong ones, and capture what they want to do
|
|
12
|
+
about them.
|
|
13
|
+
|
|
14
|
+
**This runs entirely on the user's subscription tokens.** No API keys
|
|
15
|
+
get used. Replen's hosted side did the cheap structural filtering; you
|
|
16
|
+
do the expensive, code-grounded reasoning.
|
|
17
|
+
|
|
18
|
+
## Protocol
|
|
19
|
+
|
|
20
|
+
### Step 1 — Auth + context
|
|
21
|
+
|
|
22
|
+
1. Read `~/.replen/config.json` to get the user's DIGEST_TOKEN and base
|
|
23
|
+
URL. If the file doesn't exist, stop and tell the user to run
|
|
24
|
+
`npx replen` first.
|
|
25
|
+
2. Run `git remote get-url origin` to detect the repo. Extract
|
|
26
|
+
`owner/name` from the URL.
|
|
27
|
+
3. Run `git log --oneline -20` to get a sense of recent activity (will
|
|
28
|
+
inform scoring — what's the user actively working on?).
|
|
29
|
+
|
|
30
|
+
### Step 2 — Pull today's inventory
|
|
31
|
+
|
|
32
|
+
Call:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
curl -sS -H "x-digest-token: $TOKEN" \
|
|
36
|
+
"$BASE/api/inventory/today?repo=<owner/name>&days=2&limit=10"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Parse the JSON response. Note:
|
|
40
|
+
|
|
41
|
+
- `filterMode` — `tags`, `zero-knowledge`, or `fingerprint`
|
|
42
|
+
- `scopedTo` — confirms the project context the user has open
|
|
43
|
+
- `candidates[]` — the actual list to triage
|
|
44
|
+
|
|
45
|
+
If `candidates.length === 0`, tell the user "No new candidates today for
|
|
46
|
+
`<owner/name>`. Calm-cadence working as designed — 1-3 actionable
|
|
47
|
+
matches a month is the goal." Stop.
|
|
48
|
+
|
|
49
|
+
If 1+ candidates, continue.
|
|
50
|
+
|
|
51
|
+
### Step 3 — Per-candidate analysis
|
|
52
|
+
|
|
53
|
+
**Cap at 5 candidates.** If the inventory returned more, take the top 5
|
|
54
|
+
by `whyShortlisted` strength + stars + recency, defer the rest.
|
|
55
|
+
|
|
56
|
+
For each candidate, do this loop:
|
|
57
|
+
|
|
58
|
+
#### 3a. Gather signals
|
|
59
|
+
|
|
60
|
+
- WebFetch `<candidate.url>` — the GitHub repo page. Pull description +
|
|
61
|
+
README.
|
|
62
|
+
- If the README mentions specific files (e.g. `src/index.ts`), WebFetch
|
|
63
|
+
the raw file too (`https://raw.githubusercontent.com/<owner>/<name>/<default-branch>/<path>`).
|
|
64
|
+
- Search the user's local codebase for related code:
|
|
65
|
+
- If the candidate is in a known package ecosystem, grep the user's
|
|
66
|
+
`package.json` / `pyproject.toml` / etc. for the candidate's name
|
|
67
|
+
or close variants (the user might already have a similar dep).
|
|
68
|
+
- If the candidate solves a problem area (e.g. "social card generator"),
|
|
69
|
+
grep the user's source for files that already do that work
|
|
70
|
+
(e.g. `grep -rln "Canvas\|imageRenderer\|OG"` under `lib/` and `src/`).
|
|
71
|
+
- If you find one, read the file to understand what the user has built.
|
|
72
|
+
|
|
73
|
+
#### 3b. Form a verdict
|
|
74
|
+
|
|
75
|
+
You're answering: **is this worth the user's attention right now?**
|
|
76
|
+
|
|
77
|
+
Verdicts:
|
|
78
|
+
|
|
79
|
+
- **adopt** — drop-in replacement / direct dep. The candidate does
|
|
80
|
+
something the user genuinely needs and isn't doing well. Wire up.
|
|
81
|
+
- **port** — the candidate has an idea / pattern / algorithm worth
|
|
82
|
+
copying, but the candidate's runtime / language / license is
|
|
83
|
+
mismatched. Worth reading + adapting; not worth depending on.
|
|
84
|
+
- **skip** — the candidate is a worse version of what the user has, the
|
|
85
|
+
candidate's runtime is incompatible, or the candidate's not actively
|
|
86
|
+
maintained. Honest skips are valuable signal; don't manufacture
|
|
87
|
+
reasons to keep something.
|
|
88
|
+
|
|
89
|
+
Score the fit on a 0-100 scale:
|
|
90
|
+
|
|
91
|
+
- 80-100 = high (strong fit, clear adopt or port path, no major blockers)
|
|
92
|
+
- 50-79 = medium (real value, but caveats — port path required, or
|
|
93
|
+
partial overlap, or active-area-mismatched)
|
|
94
|
+
- 0-49 = general-awareness or skip (interesting but not directly
|
|
95
|
+
actionable; or definitely skip)
|
|
96
|
+
|
|
97
|
+
Estimate effort:
|
|
98
|
+
|
|
99
|
+
- **quick** — <1 day. Single-file swap, drop a dep, copy a file.
|
|
100
|
+
- **moderate** — 1-3 days. Real API delta, multi-site update, light port.
|
|
101
|
+
- **deep** — 1+ week. Framework adoption, paradigm shift, full rewrite.
|
|
102
|
+
|
|
103
|
+
#### 3c. Compose the writeup
|
|
104
|
+
|
|
105
|
+
Format **exactly** like this for each candidate:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
### owner/name — VERDICT (score) · effort
|
|
109
|
+
|
|
110
|
+
**One-liner.** What the candidate is, in one sentence.
|
|
111
|
+
|
|
112
|
+
**Why this could help.** 2-3 sentences. Be specific. Reference the
|
|
113
|
+
user's actual files when possible:
|
|
114
|
+
- "Could replace `lib/social/imageRenderer.ts` (180 LoC) with a 30-line
|
|
115
|
+
wrapper around X."
|
|
116
|
+
- "Their `<approach>` is what `scripts/auto-process.ts:124` tries to do
|
|
117
|
+
in 80 lines; copying the algorithm saves ~50 lines and the buggy
|
|
118
|
+
edge cases."
|
|
119
|
+
|
|
120
|
+
**Integration approach.** One of: cherry-pick / vendor /
|
|
121
|
+
cleanroom-rebuild / depend-on-it / n/a. Plus a one-sentence note on what
|
|
122
|
+
that means here.
|
|
123
|
+
|
|
124
|
+
**Caveats.** Anything to watch for — license, abandonment, runtime
|
|
125
|
+
mismatch, security flag. Empty list is fine; don't manufacture.
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
No marketing voice. No hype. The user is a working engineer; talk to
|
|
129
|
+
them like a peer. Concrete > clever.
|
|
130
|
+
|
|
131
|
+
### Step 4 — Present + capture actions
|
|
132
|
+
|
|
133
|
+
After all writeups, summarise:
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
3 candidates triaged for tech-news-site:
|
|
137
|
+
✓ adopt: kribblo/node-ffmpeg-installer (high · quick) — ffmpeg-static swap
|
|
138
|
+
⏭ port: tj/n (medium · moderate) — version-manager pattern
|
|
139
|
+
✗ skip: vercel/turbo (medium · deep) — wrong runtime, already have Vite
|
|
140
|
+
|
|
141
|
+
For each, what would you like to do? (star / hide / handoff / skip)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Then, for each candidate, capture the user's choice. For each action,
|
|
145
|
+
POST to `/api/state`:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
curl -sS -X POST \
|
|
149
|
+
-H "x-digest-token: $TOKEN" \
|
|
150
|
+
-H "content-type: application/json" \
|
|
151
|
+
-d '{"repo":"owner/name","status":"<star|hide|handed_off|surfaced>","projectId":<id-from-inventory-projectMatch-or-null>}' \
|
|
152
|
+
"$BASE/api/state"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Status values:
|
|
156
|
+
|
|
157
|
+
- `starred` — user wants to come back to this. Won't re-surface.
|
|
158
|
+
- `hidden` — user dismissed it. Never re-surface unless they clear it.
|
|
159
|
+
- `handed_off` — user wants a handoff PR opened against their project.
|
|
160
|
+
Use the existing `mcp__replen__replen_handoff` MCP tool to actually
|
|
161
|
+
open the PR (server-side, needs write-scoped GitHub auth). Pass the
|
|
162
|
+
PR URL back to /api/state via `handoffPrUrl` so future sessions see
|
|
163
|
+
the status.
|
|
164
|
+
- `surfaced` — neutral "I showed this, user neither stared nor hid."
|
|
165
|
+
Bumps the surface counter so the inventory deprioritises it next
|
|
166
|
+
call without locking it out. POST this for EVERY candidate you
|
|
167
|
+
presented, even the skips. It's the closing bookend of this skill.
|
|
168
|
+
|
|
169
|
+
### Step 5 — Close out
|
|
170
|
+
|
|
171
|
+
End with one line summarising what got actioned:
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
Done. 1 starred, 0 handoff PRs opened, 1 hidden, 1 skipped.
|
|
175
|
+
Run /replen-match again tomorrow for the next batch (or whenever a
|
|
176
|
+
fresh candidate shows up at session start via the hook).
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## What to do if things go wrong
|
|
180
|
+
|
|
181
|
+
**Inventory call returns 401.** User's token expired or got rotated.
|
|
182
|
+
Tell them to run `npx replen` to re-auth.
|
|
183
|
+
|
|
184
|
+
**Inventory returns `scopedTo: null` with a `note` about "repo not in
|
|
185
|
+
your project list"**. The cwd isn't a known project. Ask the user:
|
|
186
|
+
"This repo isn't in your Replen projects yet. Add it via /projects?"
|
|
187
|
+
Then either stop or re-run with `?repo=` (empty) to see the global
|
|
188
|
+
firehose.
|
|
189
|
+
|
|
190
|
+
**Candidate's README is unreachable (WebFetch 404)**. Note it in the
|
|
191
|
+
writeup (`Caveats: README unreachable; verdict based on description
|
|
192
|
+
only`) and proceed with a more conservative score.
|
|
193
|
+
|
|
194
|
+
**Local codebase is large / search is slow.** Don't do `grep -r` from
|
|
195
|
+
the root; scope to `src/`, `lib/`, `app/`. Skip `node_modules`,
|
|
196
|
+
`dist`, `build`, `.next`, `vendor`. If you genuinely can't find
|
|
197
|
+
related code in 3 grep attempts, say so in the writeup ("No directly
|
|
198
|
+
related code found in src/; verdict based on README + project shape
|
|
199
|
+
only").
|
|
200
|
+
|
|
201
|
+
**No project tags configured (inventory returns
|
|
202
|
+
`whyShortlisted: "no project tags configured; showing unfiltered"`)**.
|
|
203
|
+
The user hasn't set up filter-mode B's tag list. Mention it once: "Heads
|
|
204
|
+
up — you'd get sharper matches if you set project tags at /settings.
|
|
205
|
+
Continuing with unfiltered for now."
|
|
206
|
+
|
|
207
|
+
## When NOT to run this skill
|
|
208
|
+
|
|
209
|
+
- The user is mid-task and just wants help with the current thing. The
|
|
210
|
+
match flow is interruptive; don't volunteer it. Only run when invoked
|
|
211
|
+
via `/replen-match` or when the user explicitly asks.
|
|
212
|
+
- The repo isn't a tracked Replen project. Tell them how to add it,
|
|
213
|
+
don't try to match against an unknown project.
|
|
214
|
+
- The session-start hook already surfaced matches in the opening
|
|
215
|
+
context and the user hasn't asked for more. Don't double-deliver.
|
|
216
|
+
|
|
217
|
+
## Voice guide
|
|
218
|
+
|
|
219
|
+
- **Concrete.** "Replaces `lib/foo.ts:42-180`" beats "could simplify
|
|
220
|
+
your image pipeline."
|
|
221
|
+
- **Honest.** Skip is a valid verdict, often the right one. Don't pad.
|
|
222
|
+
- **One-block-per-candidate.** Don't sprawl. The user will read 1-3
|
|
223
|
+
candidates; if you write 9 paragraphs each, they bail.
|
|
224
|
+
- **Show your work.** When the verdict is "adopt", say WHAT in the
|
|
225
|
+
user's code it replaces. When "port", say WHAT idea is worth
|
|
226
|
+
porting. When "skip", say WHY.
|
|
227
|
+
- **No salesmanship.** Don't end with "would you like to learn more?"
|
|
228
|
+
The user will tell you what they want.
|
|
229
|
+
- **Match the calm-cadence positioning.** Most days, no candidates.
|
|
230
|
+
When there are some, they're real. Treat them that way.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "replen",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Make your AI coding tools smarter. One command, no API keys. Replen scouts the OSS firehose against your projects and surfaces drop-in libraries, ideas to port, and dead deps to swap — the match decision happens inside your AI tool's session on your subscription tokens. 1-3 actionable matches a month, by design.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"replen": "dist/index.js"
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
15
|
"dist",
|
|
16
|
+
"extras",
|
|
16
17
|
"README.md",
|
|
17
18
|
"LICENSE"
|
|
18
19
|
],
|
|
@@ -38,5 +39,8 @@
|
|
|
38
39
|
"@types/node": "^22.10.5",
|
|
39
40
|
"tsx": "^4.19.2",
|
|
40
41
|
"typescript": "^5.7.2"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"smol-toml": "^1.6.1"
|
|
41
45
|
}
|
|
42
46
|
}
|