Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/claude/shim-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,9 @@ if (fs.existsSync(target)) import(pathToFileURL(target).href).then((m) => ${call
// where their learnings are. In the order of the 13 Oct onboarding plan: when
// one shapes a reply, Claude credits it, and only after the first credit does
// it offer, once, to set trail up. Plain Node, no package: it's the one part of
// trail a teammate without it still runs.
// trail a teammate without it still runs. A title may be quoted either way:
// trail writes JSON's double quotes, and a repo's Prettier may rewrite them to
// single ones.
const OFFER_TRAIL = `
else if (process.argv[2] === 'session-start') offerTrail();

Expand All @@ -178,7 +180,10 @@ function offerTrail() {
if (files.length === 0) return;
const field = (text, key) => {
const line = text.split('\\n').find((l) => l.startsWith(key + ':'));
return line ? line.slice(key.length + 1).trim().replace(/^"|"$/g, '') : '';
const v = line ? line.slice(key.length + 1).trim() : '';
if (/^'.*'$/.test(v)) return v.slice(1, -1).replace(/''/g, "'");
if (/^".*"$/.test(v)) { try { return JSON.parse(v); } catch { return v.slice(1, -1); } }
return v;
};
const lines = [];
for (const f of files.slice(0, 12)) {
Expand Down
12 changes: 12 additions & 0 deletions test/claude-shim-resolve.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,18 @@ test('each learning is listed with its author, newest first, so the credit can n
assert.ok(sam >= 0 && anirudh > sam, ctx);
});

test('a learning title reads clean whichever quotes it has, as trail wrote it or as Prettier left it', () => {
const root = tmpRepo('shim-offer-quotes');
const repo = clonedWithLearnings(root, 'Kumar', ['Anirudh']);
const dir = join(repo, '.trail', 'learnings');
writeFileSync(join(dir, '2026-10-15-single.md'), `---\ntitle: 'assign: one PR into dev, it''s the rule'\nauthor: Priya\n---\n`);
writeFileSync(join(dir, '2026-10-16-double.md'), `---\ntitle: ${JSON.stringify('graft: say "trail" now')}\nauthor: Sam\n---\n`);
const ctx: string = JSON.parse(runBare(root, repo, 'session-start')).hookSpecificOutput.additionalContext;
assert.ok(ctx.includes("- assign: one PR into dev, it's the rule (Priya) ·"), ctx);
assert.ok(ctx.includes('- graft: say "trail" now (Sam) ·'), ctx);
assert.ok(ctx.includes('- Retries decision 0 (Anirudh) ·'), ctx);
});

test('without trail, other events and a repo with no learnings stay silent', () => {
const root = tmpRepo('shim-offer-quiet');
const repo = clonedWithLearnings(root, 'Kumar', ['Anirudh']);
Expand Down
Loading