🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

replen

Package Overview
Dependencies
Maintainers
1
Versions
68
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

replen - npm Package Compare versions

Comparing version
1.5.7
to
1.6.0
+121
-18
dist/inject-instruction.js

@@ -34,5 +34,6 @@ // Auto-inject the "## Replen integration" instruction block into the

import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
import { join, basename } from "node:path";
import { join, basename, dirname } from "node:path";
import { homedir } from "node:os";
import { createInterface } from "node:readline";
export const INTEGRATION_VERSION = "13";
export const INTEGRATION_VERSION = "14";
const SECTION_HEADER = "## Replen integration";

@@ -48,9 +49,19 @@ const MARKER_RE = /<!--\s*replen-integration:\s*v(\d+)\s*-->/;

- **No entry for this repo (not in the \`projects\` list):** it isn't registered
with Replen. Stay SILENT — Replen deliberately doesn't nag in unregistered or
throwaway directories, so \`replen_match\` will also return an empty footnote
here. (Real repos self-register on session start on every host — the MCP
registers the repo's identity in the background the first time you open it, so
matching works from the next session; the user can also force it now with
\`npx replen sync-projects\`.)
- **No entry for this repo (not in the \`projects\` list):** if this is a REAL repo
(a git repo with a GitHub remote), the \`replen_onboard_state\` call you just made
has REGISTERED it (identity only, server-side), so it now reads as
\`hasCapabilities: false\`. ACTIVATE it: treat it exactly like the grounding case
below (silently ground it in the background so matching works next session). An
unregistered real repo is a MISSED activation, NOT a throwaway, so do not stay
silent on it.
- **Throwaway / non-repo directory** (no \`.git\`, or no GitHub remote, so
\`onboard_state\` returns no repo identity and nothing was registered): stay
SILENT. Never activate scratch or temp directories.
- **Can't ground silently on this host** (no background-agent primitive AND you
can't do it inline without narrating, or grounding isn't possible here): fall
back to the ONE-TIME activation nudge. Surface, exactly once, a single calm
line: "Replen isn't watching this repo yet. Run \`npx replen\` once to activate
matching (about ten seconds, no API key)." ONLY when \`onboard_state\` reports
\`nudgedAt: null\` for this repo; if it has already been nudged, stay silent.
Never nudge the same repo twice.
- **\`hasCapabilities\` false AND \`autoground\` true (the default):** ground it

@@ -354,2 +365,85 @@ SILENTLY, IN THE BACKGROUND. Spawn a background subagent (the Task tool on

}
// -- GLOBAL (user-level) inject ---------------------------------------
//
// Per-repo docs only cover repos that existed at setup time. To activate BRAND-
// NEW repos (and non-Claude-Code hosts with no SessionStart hook), we also write
// ONE user-level instruction file per host, loaded in EVERY repo the user opens.
// CC: ~/.claude/rules/replen.md (Replen-owned, no `paths` frontmatter so it loads
// unconditionally). Codex: ~/.codex/AGENTS.md. Gemini: ~/.gemini/GEMINI.md.
// Cursor has no CLI-writable global rule (User Rules are cloud-synced app state),
// so it relies on the per-repo .mdc + the one-time activation nudge.
// CC user rule is Replen-owned → overwrite on version mismatch (no append, so it
// never co-mingles with the user's personal ~/.claude/CLAUDE.md).
const GLOBAL_CLAUDE_RULE = `# Replen (user-level rule)
<!-- Auto-managed by Replen so matching + activation work in every repo, including new ones. Do not edit; run \`npx replen uninstall\` to remove. -->
${SECTION_HEADER}
${SECTION_BODY}`;
function applyOwnedRuleFile(path, content) {
if (existsSync(path)) {
const m = readFileSync(path, "utf8").match(MARKER_RE);
if (m && m[1] === INTEGRATION_VERSION)
return "alreadyCurrent";
writeFileSync(path, content);
return "versionUpdated";
}
writeFileSync(path, content);
return "created";
}
// Codex loads only the FIRST non-empty of ~/.codex/AGENTS.override.md then
// ~/.codex/AGENTS.md. Inject into whichever it will actually read, or a block in
// AGENTS.md is silently dead when a non-empty override exists.
function codexGlobalFile() {
const override = join(homedir(), ".codex", "AGENTS.override.md");
try {
if (existsSync(override) && readFileSync(override, "utf8").trim().length > 0)
return override;
}
catch { /* fall through to AGENTS.md */ }
return join(homedir(), ".codex", "AGENTS.md");
}
// Write the three host global files. Runs ONCE per setup, independent of repo
// discovery (so it fires even when no local repos are found).
export function injectGlobalInstructions() {
const outcome = {
scanned: 0, created: 0, appended: 0, alreadyCurrent: 0, versionUpdated: 0, skipped: [], declined: false,
};
const targets = [
{
label: "~/.claude/rules/replen.md",
run: () => {
const dir = join(homedir(), ".claude", "rules");
mkdirSync(dir, { recursive: true });
return applyOwnedRuleFile(join(dir, "replen.md"), GLOBAL_CLAUDE_RULE);
},
},
{
label: "~/.codex/AGENTS.md",
run: () => {
const f = codexGlobalFile();
mkdirSync(dirname(f), { recursive: true });
return applyToClaudeMd(f); // shared file → append/replace the marked section
},
},
{
label: "~/.gemini/GEMINI.md",
run: () => {
const f = join(homedir(), ".gemini", "GEMINI.md");
mkdirSync(dirname(f), { recursive: true });
return applyToClaudeMd(f);
},
},
];
for (const t of targets) {
try {
outcome[t.run()]++;
}
catch (e) {
outcome.skipped.push({ path: t.label, reason: e.message ?? String(e) });
}
}
return outcome;
}
async function promptYes(question) {

@@ -379,4 +473,3 @@ if (!process.stdin.isTTY)

if (repos.length === 0) {
console.log(" · no git repos with GitHub remotes found — skipping CLAUDE.md inject. Pass --root <path> if your code lives somewhere non-conventional.");
return outcome;
console.log(" · no local git repos with GitHub remotes found; still writing the user-level activation files so new repos work everywhere. Pass --root <path> if your code lives somewhere non-conventional.");
}

@@ -389,3 +482,3 @@ // First-run consent. Shows the count + an example path so the user knows the

outcome.declined = true;
console.log(`\n · non-interactive shell — skipping CLAUDE.md/AGENTS.md/GEMINI.md inject.`);
console.log(`\n · non-interactive shell; skipping the Replen instruction inject (global + per-repo).`);
console.log(` Re-run with \`npx replen inject -y\` to apply without a prompt.`);

@@ -395,6 +488,7 @@ return outcome;

console.log(`\n Found ${repos.length} git repo(s) with GitHub remotes.`);
console.log(` Add a "## Replen integration" section to CLAUDE.md, AGENTS.md, GEMINI.md`);
console.log(` and .cursor/rules/replen.mdc in each (creating any that don't exist) so`);
console.log(` Claude Code / Codex / Gemini CLI / Cursor surface today's matches at session`);
console.log(` start. Idempotent; edit freely above the section. First 3:`);
console.log(` Replen will add a "## Replen integration" section in two places:`);
console.log(` 1. user-level files (~/.claude/rules/replen.md, ~/.codex/AGENTS.md,`);
console.log(` ~/.gemini/GEMINI.md) so EVERY repo, including brand-new ones, activates;`);
console.log(` 2. per-repo CLAUDE.md / AGENTS.md / GEMINI.md / .cursor/rules/replen.mdc.`);
console.log(` Idempotent; edit freely above the section.${repos.length ? " First 3:" : ""}`);
for (const r of repos.slice(0, 3))

@@ -411,4 +505,13 @@ console.log(` • ${r}`);

}
// We write to CLAUDE.md (Claude Code convention), AGENTS.md (Codex
// convention), and GEMINI.md (Gemini CLI convention). Same section
// GLOBAL user-level inject FIRST: runs once, always (even with zero repos),
// so brand-new repos and non-Claude-Code hosts activate without waiting for a
// per-repo file. Merge its counts into the outcome.
const g = injectGlobalInstructions();
outcome.created += g.created;
outcome.appended += g.appended;
outcome.alreadyCurrent += g.alreadyCurrent;
outcome.versionUpdated += g.versionUpdated;
outcome.skipped.push(...g.skipped);
// Then per-repo docs. We write to CLAUDE.md (Claude Code convention), AGENTS.md
// (Codex convention), and GEMINI.md (Gemini CLI convention). Same section
// content; each tool reads its own native file at session start so

@@ -415,0 +518,0 @@ // the proactive replen_match instruction lands wherever the user

@@ -39,2 +39,9 @@ // `npx replen uninstall` — reverse every local change `npx replen` made,

const REPLEN_DIR = join(homedir(), ".replen");
// User-level (global) instruction files written by inject-instruction.ts so
// activation reaches every repo. CC rule file is Replen-owned (delete whole);
// Codex/Gemini are shared (strip the section, or delete if we created the file).
const GLOBAL_CC_RULE = join(homedir(), ".claude", "rules", "replen.md");
const GLOBAL_CODEX = join(homedir(), ".codex", "AGENTS.md");
const GLOBAL_CODEX_OVERRIDE = join(homedir(), ".codex", "AGENTS.override.md");
const GLOBAL_GEMINI = join(homedir(), ".gemini", "GEMINI.md");
// Must match mcp-setup.ts: the tools it adds to permissions.allow and the

@@ -70,2 +77,3 @@ // substring marker on the SessionStart hook command.

await removeDocBlocks(opts);
await removeGlobalInstructions(opts);
await removeLocalConfig(opts);

@@ -237,7 +245,65 @@ console.log("");

// ============================================================================
// Category 4 — ~/.replen (auth token + saved roots + Atlas vault export)
// Category 4. The user-level (global) instruction files
// ============================================================================
async function removeGlobalInstructions(opts) {
const hits = [];
// CC rule file is Replen-owned (we create the whole file): delete it entirely.
if (existsSync(GLOBAL_CC_RULE)) {
try {
if (hasReplenSection(readFileSync(GLOBAL_CC_RULE, "utf8")))
hits.push({ file: GLOBAL_CC_RULE, deleteWhole: true });
}
catch { /* ignore unreadable */ }
}
// Codex + Gemini are shared files: strip our section, or delete if we created it.
for (const file of [GLOBAL_CODEX, GLOBAL_CODEX_OVERRIDE, GLOBAL_GEMINI]) {
if (!existsSync(file))
continue;
let text;
try {
text = readFileSync(file, "utf8");
}
catch {
continue;
}
if (!hasReplenSection(text))
continue;
hits.push({ file, deleteWhole: isStubOnly(text) });
}
if (hits.length === 0) {
console.log("\n ④ Global instruction files (~/.claude/rules, ~/.codex, ~/.gemini): none found, skipping.");
return;
}
console.log(`\n ④ Global Replen instruction files (${hits.length}):`);
for (const h of hits) {
console.log(` • ${h.file}${h.deleteWhole ? " (Replen-created, will delete)" : " (strip section, keep your content)"}`);
}
if (!(await gate(opts, "Remove the global Replen instruction from these files?"))) {
console.log(" · kept.");
return;
}
if (opts.dryRun)
return;
for (const h of hits) {
try {
if (h.deleteWhole) {
rmSync(h.file, { force: true });
console.log(` ✓ deleted: ${h.file}`);
}
else {
writeFileSync(h.file, stripReplenSection(readFileSync(h.file, "utf8")));
console.log(` ✓ stripped section: ${h.file}`);
}
}
catch (e) {
console.warn(` ⚠ ${h.file}: ${e.message}`);
}
}
}
// ============================================================================
// Category 5. ~/.replen (auth token + saved roots + Atlas vault export)
// ============================================================================
async function removeLocalConfig(opts) {
if (!existsSync(REPLEN_DIR)) {
console.log("\n ④ Local config (~/.replen) — none found, skipping.");
console.log("\n ⑤ Local config (~/.replen): none found, skipping.");
return;

@@ -474,4 +540,4 @@ }

console.log("");
console.log(" In scope: MCP wiring (Claude/Codex/Gemini), the /replen skills,");
console.log(" the per-repo \"## Replen integration\" doc blocks, and ~/.replen.");
console.log(" In scope: MCP wiring (Claude/Codex/Gemini), the /replen skills, the");
console.log(" per-repo AND user-level \"## Replen integration\" blocks, and ~/.replen.");
console.log(" NOT in scope: server-side profiles & match history (see note at the end).");

@@ -478,0 +544,0 @@ }

+1
-1
{
"name": "replen",
"version": "1.5.7",
"version": "1.6.0",
"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,0 +5,0 @@ "type": "module",