replen 1.0.10 → 1.0.12

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/dist/commands.js CHANGED
@@ -547,3 +547,36 @@ async function fetchInventoryStatus(cfg, repo) {
547
547
  clearTimeout(timer);
548
548
  }
549
549
  }
550
+ // `replen atlas` — write your knowledge graph as an owned, Obsidian-compatible
551
+ // markdown vault to ~/.replen/atlas/. Fetches the rendered files from the server
552
+ // (one source of truth) and writes them locally; you own and can open them.
553
+ export async function runAtlas(argv) {
554
+ const { mkdirSync, writeFileSync, rmSync } = await import("node:fs");
555
+ const { join, dirname } = await import("node:path");
556
+ const { homedir } = await import("node:os");
557
+ const cfg = await loadConfigOrExit();
558
+ const dir = getFlag(argv, "--out") ?? join(homedir(), ".replen", "atlas");
559
+ const data = await apiGet(cfg, "/api/graph/atlas");
560
+ if (!data.files?.length) {
561
+ console.log("No Atlas yet — run /replen-onboard and a pipeline run first so the graph has something to map.");
562
+ return;
563
+ }
564
+ // Fresh write: clear the managed subdirs, then write.
565
+ for (const sub of ["projects", "capabilities", "candidates", "themes"]) {
566
+ try {
567
+ rmSync(join(dir, sub), { recursive: true, force: true });
568
+ }
569
+ catch { /* */ }
570
+ }
571
+ for (const f of data.files) {
572
+ const full = join(dir, f.path);
573
+ mkdirSync(dirname(full), { recursive: true });
574
+ writeFileSync(full, f.content);
575
+ }
576
+ if (hasFlag(argv, "--json")) {
577
+ console.log(JSON.stringify({ dir, count: data.files.length }));
578
+ return;
579
+ }
580
+ console.log(`Atlas written: ${data.files.length} notes → ${dir}`);
581
+ console.log(`Open ${dir} in Obsidian (or any markdown editor) to explore the graph view of your projects, capabilities, and decisions.`);
582
+ }
@@ -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();
@@ -171,9 +170,16 @@ export function discoverProjects(roots) {
171
170
  continue;
172
171
  seenGithub.add(githubFullName);
173
172
  const { name, tags, primaryLanguage } = extractMetadata(repoPath, dirName);
173
+ // Slug from the GitHub repo NAME, not the local folder name. The repo
174
+ // name is the stable identity the server keys on; deriving the slug
175
+ // from the folder meant a folder rename (or an org rename like
176
+ // Covelent→nsokin) minted a fresh slug and the server inserted a
177
+ // duplicate row. `~/code/drone` with remote `nsokin/palisade-website`
178
+ // now registers as slug `palisade-website`, matching the dashboard.
179
+ const repoName = githubFullName.split("/").pop() || dirName;
174
180
  projects.push({
175
181
  localPath: repoPath,
176
- slug: normaliseSlug(dirName),
182
+ slug: normaliseSlug(repoName),
177
183
  name,
178
184
  githubFullName,
179
185
  tags,
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { runInit } from "./init.js";
3
3
  import { setupMcp } from "./mcp-setup.js";
4
4
  import { readConfig, configPath } from "./config.js";
5
5
  import { runProjectInit } from "./project-init.js";
6
- import { runCheckNew, runFeed, runHandoff, runProgress, runRun, runSearch, runStarred, runWatch } from "./commands.js";
6
+ import { runCheckNew, runFeed, runHandoff, runProgress, runRun, runSearch, runStarred, runWatch, runAtlas } from "./commands.js";
7
7
  const HELP = `replen: Smarter AI Development workflows
8
8
 
9
9
  Usage:
@@ -21,6 +21,10 @@ Usage:
21
21
  [--root PATH ...] and register them with Replen. Run after
22
22
  cloning a new repo, or pass --root to point
23
23
  at a non-conventional layout.
24
+ npx replen atlas Write your knowledge graph as an owned,
25
+ Obsidian-compatible markdown vault to
26
+ ~/.replen/atlas/ (projects, capabilities,
27
+ decisions, themes, all cross-linked)
24
28
  npx replen logout Forget saved auth
25
29
  npx replen --help This help
26
30
 
@@ -146,6 +150,8 @@ async function main() {
146
150
  return runStarred(argv);
147
151
  if (cmd === "handoff")
148
152
  return runHandoff(argv);
153
+ if (cmd === "atlas")
154
+ return runAtlas(argv);
149
155
  if (cmd === undefined) {
150
156
  // Default: if already signed in, just rerun mcp setup. Otherwise, full flow.
151
157
  const cfg = await readConfig();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "replen",
3
- "version": "1.0.10",
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.12",
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"