replen 1.0.11 → 1.0.13

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.
@@ -137,18 +137,17 @@ const DEP_TO_TAGS = [
137
137
  * repos) and by `githubFullName` (so cloning the same repo to two
138
138
  * paths doesn't create two project rows).
139
139
  *
140
- * Slug = the local directory basename (normalised). When two repos in
141
- * the discovery result would share a slug (e.g. `flight-controller`
142
- * under both `~/projects/drone/` and `~/work/sandbox/`), the second+
143
- * gets `-<owner>` appended to disambiguate. Keeps slugs short for the
144
- * common case while preventing server-side `uniq_profile_user_slug`
145
- * collisions.
140
+ * Slug = the GitHub repo NAME (normalised), so it's stable across local
141
+ * folder renames and org renames. When two distinct repos would share a
142
+ * slug (e.g. two `flight-controller` repos under different owners), the
143
+ * second+ gets `-<owner>` appended to disambiguate, preventing server-side
144
+ * `uniq_profile_user_slug` collisions.
146
145
  *
147
- * Identity is `githubFullName` on the server side; slug is just the
148
- * URL-safe display label. So a local `~/projects/drone/` whose remote
149
- * is `acme/aegis` keeps slug `drone` (matching how you think of it
150
- * locally) while still registering correctly against `acme/aegis` on
151
- * the dashboard.
146
+ * Identity is `githubFullName` on the server side (the bulk endpoint
147
+ * upserts by it); slug is the URL-safe display label derived from the
148
+ * repo name. So a local `~/projects/drone/` whose remote is
149
+ * `acme/palisade-website` registers as slug `palisade-website` — matching
150
+ * the repo, not the local folder.
152
151
  */
153
152
  export function discoverProjects(roots) {
154
153
  const seenPaths = new Set();
@@ -170,10 +169,18 @@ export function discoverProjects(roots) {
170
169
  if (seenGithub.has(githubFullName))
171
170
  continue;
172
171
  seenGithub.add(githubFullName);
173
- const { name, tags, primaryLanguage } = extractMetadata(repoPath, dirName);
172
+ // Slug + display name both anchor on the GitHub repo NAME, not the
173
+ // local folder name. Deriving from the folder meant a folder rename (or
174
+ // an org rename like Covelent→nsokin) minted a fresh slug and the server
175
+ // inserted a duplicate row. `~/code/drone` with remote
176
+ // `nsokin/palisade-website` now registers as slug `palisade-website`.
177
+ // The repo name is also the fallback display name, so a generic
178
+ // package.json name (the Next.js starter's "nextn", etc.) doesn't stick.
179
+ const repoName = githubFullName.split("/").pop() || dirName;
180
+ const { name, tags, primaryLanguage } = extractMetadata(repoPath, repoName);
174
181
  projects.push({
175
182
  localPath: repoPath,
176
- slug: normaliseSlug(dirName),
183
+ slug: normaliseSlug(repoName),
177
184
  name,
178
185
  githubFullName,
179
186
  tags,
@@ -286,6 +293,17 @@ function readGitRemote(repoPath) {
286
293
  return null;
287
294
  return `${m[1]}/${m[2]}`;
288
295
  }
296
+ // Scaffold/template default package.json names that aren't real project
297
+ // names — repos cloned from the same starter all share one (the Next.js
298
+ // starter's "nextn"). Mirrors the server's GENERIC_NAMES guard.
299
+ const GENERIC_PROJECT_NAMES = new Set([
300
+ "nextn", "next-app", "create-next-app", "nextjs", "next", "my-app", "myapp",
301
+ "my-project", "myproject", "app", "web", "webapp", "frontend", "backend",
302
+ "client", "server", "project", "vite-project", "vite-app", "react-app",
303
+ "turborepo", "my-turborepo", "monorepo", "example", "template", "starter",
304
+ "boilerplate", "hello-world", "test", "demo", "untitled",
305
+ ]);
306
+ const isGenericProjectName = (n) => GENERIC_PROJECT_NAMES.has(n.trim().toLowerCase());
289
307
  function extractMetadata(repoPath, fallbackName) {
290
308
  const tags = new Set();
291
309
  let name = fallbackName;
@@ -296,7 +314,7 @@ function extractMetadata(repoPath, fallbackName) {
296
314
  primaryLanguage = "TypeScript"; // updated below if no TS detected
297
315
  try {
298
316
  const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
299
- if (typeof pkg.name === "string" && pkg.name.length > 0)
317
+ if (typeof pkg.name === "string" && pkg.name.length > 0 && !isGenericProjectName(pkg.name))
300
318
  name = pkg.name;
301
319
  if (Array.isArray(pkg.keywords)) {
302
320
  for (const k of pkg.keywords) {
@@ -41,6 +41,17 @@ Parse the JSON response. Note:
41
41
  - `filterMode` — `tags`, `zero-knowledge`, or `fingerprint`
42
42
  - `scopedTo` — confirms the project context the user has open
43
43
  - `candidates[]` — the actual list to triage
44
+ - `candidates[].priorContext` — server-attached MEMORY: the user's earlier
45
+ verdicts on this repo, and whether the matched capability is already
46
+ covered by something they adopted/ported. Trust it — fold it into your
47
+ verdict instead of re-deriving history, and don't push a candidate whose
48
+ capability is covered unless it's materially better than the incumbent.
49
+ - `candidates[].source === "re-checked"` — a repo the user DEFERRED months
50
+ ago that is still actively developed. Re-evaluate it against today's
51
+ state of the project; the original "not now" note is in `priorContext`.
52
+ - `leap` — on quiet days the response may carry ONE portfolio connection
53
+ (a cross-project / adjacency / cross-user leap) instead of candidates.
54
+ `displayText` already words it; relay that and offer to explore it.
44
55
 
45
56
  If `candidates.length === 0`, tell the user "No new candidates today for
46
57
  `<owner/name>`. Calm-cadence working as designed — 1-3 actionable
@@ -85,6 +96,10 @@ Verdicts:
85
96
  candidate's runtime is incompatible, or the candidate's not actively
86
97
  maintained. Honest skips are valuable signal; don't manufacture
87
98
  reasons to keep something.
99
+ - **defer** — genuinely interesting but not NOW (too early / v0.x churn /
100
+ blocked on a milestone the project hasn't hit). Defer is a real promise,
101
+ not a soft skip: Replen automatically re-surfaces a deferred repo after
102
+ ~3 months if it's still actively developed (`source: "re-checked"`).
88
103
 
89
104
  **Watch for word-collisions** — the most common bad match. The candidate
90
105
  shares a *word* with the matched capability but its real domain diverges:
@@ -296,6 +311,21 @@ The user hasn't set up filter-mode B's tag list. Mention it once: "Heads
296
311
  up — you'd get sharper matches if you set project tags at /settings.
297
312
  Continuing with unfiltered for now."
298
313
 
314
+ ## The Atlas vault — local memory for any agent
315
+
316
+ Replen keeps an agent-readable markdown vault at `~/.replen/atlas/` — the
317
+ user's whole portfolio as linked notes: every project and what it does,
318
+ every capability (with how it's known: `grounded`/`extracted`/`inferred`),
319
+ every past decision with its reason code, plus themes and blind spots.
320
+ The MCP server refreshes it in the background (at most twice a day);
321
+ `replen atlas` forces a rewrite.
322
+
323
+ Use it whenever you need CROSS-PROJECT context: "what else does this user
324
+ build?", "have we solved X in another repo?", "what did we decide about
325
+ Y last quarter?". Start at `MAP.md`, or call `replen_recall` for a direct
326
+ query. Reading the vault beats re-deriving the portfolio from scratch —
327
+ it's the memory layer, and it's already on disk.
328
+
299
329
  ## When NOT to run this skill
300
330
 
301
331
  - The user is mid-task and just wants help with the current thing. The
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "replen",
3
- "version": "1.0.11",
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.",
3
+ "version": "1.0.13",
4
+ "description": "Make your AI coding tools smarter. One command, no API keys, free. Replen watches what your projects actually do and surfaces a few things worth bringing in each month. Use one as is, port a piece of another, cherry pick an idea, or build it clean room. The match happens inside your AI tool's session. A few actionable matches a month, by design.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "replen": "dist/index.js"