diff --git a/blog/src/_data/media.json b/blog/src/_data/media.json index 41251f004..084ea056c 100644 --- a/blog/src/_data/media.json +++ b/blog/src/_data/media.json @@ -4956,5 +4956,27 @@ "status": "ready" }, "inline": {} + }, + "opencode-alternatives": { + "title": "OpenCode Alternatives: 7 Coding Agents to Switch To", + "category": "comparisons", + "hero": { + "file": "assets/media/opencode-alternatives/hero.png", + "alt": "Title card on white. \"OpenCode Alternatives\" in large ink type under the kicker \"7 coding agents to switch to\", a \"Checked 15 Oct 2026\" pill, and a snow globe holding a tiny terminal window and two pine trees while snow falls.", + "prompt": "Code drawn title card, motion standard (15 Oct 2026).", + "status": "ready" + }, + "inline": {} + }, + "claude-code-best-practices": { + "title": "Claude Code Best Practices: 9 Habits From Anthropic's Docs", + "category": "guides", + "hero": { + "file": "assets/media/claude-code-best-practices/hero.png", + "alt": "Title card on white. 'Claude Code Best Practices' in large ink type, the kicker '9 Habits From Anthropic's Docs', a 'Checked 15 Oct 2026' pill, and a small bonsai tree in a blue pot.", + "prompt": "Code drawn title card, motion standard (15 Oct 2026).", + "status": "ready" + }, + "inline": {} } } diff --git a/blog/src/assets/media/claude-code-best-practices/clipboard.png b/blog/src/assets/media/claude-code-best-practices/clipboard.png new file mode 100644 index 000000000..68f505a94 Binary files /dev/null and b/blog/src/assets/media/claude-code-best-practices/clipboard.png differ diff --git a/blog/src/assets/media/claude-code-best-practices/hero.png b/blog/src/assets/media/claude-code-best-practices/hero.png new file mode 100644 index 000000000..efcabdb6e Binary files /dev/null and b/blog/src/assets/media/claude-code-best-practices/hero.png differ diff --git a/blog/src/assets/media/claude-code-best-practices/map.png b/blog/src/assets/media/claude-code-best-practices/map.png new file mode 100644 index 000000000..6f780fc5a Binary files /dev/null and b/blog/src/assets/media/claude-code-best-practices/map.png differ diff --git a/blog/src/assets/media/claude-code-best-practices/motion.css b/blog/src/assets/media/claude-code-best-practices/motion.css new file mode 100644 index 000000000..1dea8486c --- /dev/null +++ b/blog/src/assets/media/claude-code-best-practices/motion.css @@ -0,0 +1,13 @@ +/* Bright coded scenes for "Claude Code Best Practices". Pairs with motion.js. */ +.prose figure.mg { margin: 42px 0; } +.prose figure.mg img, .mg .mg-svg { display: block; width: 100%; height: auto; border: 1px solid #1A1320; border-radius: 18px; background: #fff; } +.mg figcaption { font: 500 13px/1.5 "Space Grotesk", system-ui, sans-serif; color: #6B5878; padding-top: 10px; text-align: center; } +.mg .mg-svg { touch-action: manipulation; -webkit-tap-highlight-color: transparent; } +.mg .mg-svg text { -webkit-user-select: none; user-select: none; pointer-events: none; } +.mg .mg-hit { cursor: pointer; outline: none; } +.mg [role="slider"] { cursor: grab; touch-action: none; } +.mg .mg-ring { stroke-opacity: 0; transition: stroke-opacity 0.15s; } +.mg .mg-hit:focus-visible .mg-ring { stroke-opacity: 1; } +.mg [role="slider"]:focus-visible circle:nth-child(2) { stroke-width: 5; } +@media (hover: hover) { .mg .mg-hit:hover .mg-ring { stroke-opacity: 0.5; } } +@media (prefers-reduced-motion: reduce) { .mg .mg-ring { transition: none; } } diff --git a/blog/src/assets/media/claude-code-best-practices/motion.js b/blog/src/assets/media/claude-code-best-practices/motion.js new file mode 100644 index 000000000..1b6e72981 --- /dev/null +++ b/blog/src/assets/media/claude-code-best-practices/motion.js @@ -0,0 +1,297 @@ +/* Bright coded scenes for "Claude Code Best Practices". Vanilla JS, no library. +
holds a still image; this script swaps in a live SVG. + Plays only in view, honours reduced motion, "?mgstill" freezes each scene on its still frame. */ +(() => { + const NS = "http://www.w3.org/2000/svg"; + const INK = "#1A1320", YEL = "#FFCA54", BLUE = "#6C8EF5", LIL = "#B69CFF", MINT = "#7FD8AE", SKY = "#9AD8FF", SOFT = "#F1ECF9", MUTE = "#7A6A88"; + const SANS = '"Space Grotesk", system-ui, sans-serif', MONO = '"JetBrains Mono", ui-monospace, monospace'; + const el = (t, a = {}, p) => { const n = document.createElementNS(NS, t); for (const k in a) n.setAttribute(k, a[k]); if (p) p.appendChild(n); return n; }; + const txt = (p, x, y, s, a = {}) => { const n = el("text", Object.assign({ x, y, "font-family": SANS, "font-size": 24, "font-weight": 600, fill: INK }, a), p); n.textContent = s; return n; }; + const cl = (v, a = 0, b = 1) => Math.max(a, Math.min(b, v)); + const seg = (t, a, b) => cl((t - a) / (b - a)); + const oc = (x) => 1 - Math.pow(1 - x, 3); + const ob = (x) => { const c = 1.70158; return 1 + (c + 1) * Math.pow(x - 1, 3) + c * Math.pow(x - 1, 2); }; + const io = (x) => (x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2); + const tf = (n, x, y, s = 1, r = 0) => n.setAttribute("transform", `translate(${x} ${y}) rotate(${r}) scale(${s})`); + const button = (g, label, fn) => { + g.setAttribute("role", "button"); g.setAttribute("tabindex", "0"); g.setAttribute("aria-label", label); g.classList.add("mg-hit"); + g.addEventListener("click", fn); + g.addEventListener("keydown", (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); fn(); } }); + }; + + let UID = 0; + /* shrink a line of text until it fits its box */ + const fit = (n, max) => { const w = n.getComputedTextLength(); if (w > max) n.setAttribute("font-size", ((parseFloat(n.getAttribute("font-size")) * max) / w).toFixed(1)); }; + /* the soft caption strip along the bottom; returns a setter */ + const strip = (svg, y, x0 = 30, w = 740) => { + el("rect", { x: x0, y, width: w, height: 48, rx: 24, fill: SOFT }, svg); + const n = txt(svg, x0 + w / 2, y + 31, "", { "text-anchor": "middle", "font-size": 21, "font-weight": 500 }); + return (s) => { if (n.textContent !== s) { n.textContent = s; n.setAttribute("font-size", 21); fit(n, w - 44); } }; + }; + /* a rounded label sized to its words, centred on 0,0 of its group */ + const pill = (g, s, fill, size = 22) => { + const r = el("rect", { y: -21, height: 42, rx: 21, fill, stroke: INK, "stroke-width": 3 }, g), t = txt(g, 0, size * 0.36, s, { "text-anchor": "middle", "font-size": size, "font-weight": 700 }); + const w = t.getComputedTextLength() + 32; r.setAttribute("x", -w / 2); r.setAttribute("width", w); + return w; + }; + + const SC = {}; + + /* ---- 1. a sieve: the check shakes, sand falls through, the three bad pebbles stay caught ---- */ + SC.sieve = { + dur: 12, still: 9.8, + build(svg, api) { + const id = "mgsv" + ++UID, defs = el("defs", {}, svg); + txt(svg, 44, 62, "Give Claude a way to verify its work", { "font-size": 32, "font-weight": 700 }); + const CX = 380, CY = 228, RX = 268, RY = 102, TY = 452; + const UNDER = `M${CX - RX} ${CY} C${CX - RX + 14} ${CY + 190} ${CX + RX - 14} ${CY + 190} ${CX + RX} ${CY} Z`; + const IN = `M${CX - RX} ${CY} A${RX} ${RY} 0 1 0 ${CX + RX} ${CY} A${RX} ${RY} 0 1 0 ${CX - RX} ${CY} Z`; + el("path", { d: UNDER }, el("clipPath", { id: id + "u" }, defs)); + el("path", { d: IN }, el("clipPath", { id: id + "i" }, defs)); + /* the tray and the pile of sand that grows on it */ + el("rect", { x: 170, y: TY, width: 420, height: 30, rx: 12, fill: BLUE, stroke: INK, "stroke-width": 3 }, svg); + const pile = el("path", { fill: YEL, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, svg); + const heap = [0.16, 0.27, 0.36, 0.45, 0.55, 0.64, 0.73, 0.84].map((u, i) => ({ u, d: 6 + (i % 3) * 5, n: el("circle", { r: 3 + (i % 2), fill: "#fff", stroke: INK, "stroke-width": 1.5 }, svg) })); + const fall = el("g", {}, svg); + const grains = Array.from({ length: 44 }, (_, i) => ({ n: el("circle", { r: 3 + (i % 3), fill: YEL, stroke: INK, "stroke-width": 1.5 }, fall), x: 262 + ((i * 53) % 236), ph: (i * 0.379) % 1 })); + /* the sieve itself */ + const sv = el("g", {}, svg); + const hand = el("g", { transform: `translate(${CX + RX - 8} ${CY - 8}) rotate(-12)` }, sv); + el("rect", { x: 0, y: -12, width: 130, height: 24, rx: 12, fill: YEL, stroke: INK, "stroke-width": 3 }, hand); + el("circle", { cx: 112, r: 6, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, hand); + const und = el("g", {}, sv); + el("path", { d: UNDER, fill: SOFT }, und); + let hd = ""; + for (let x = CX - RX - 200; x <= CX + RX + 40; x += 18) hd += `M${x} ${CY} l200 200 M${x + 220} ${CY} l-200 200 `; + el("path", { d: hd, stroke: LIL, "stroke-width": 2, fill: "none", "clip-path": `url(#${id}u)` }, und); + el("path", { d: UNDER, fill: "none", stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, und); + const ins = el("g", {}, sv); + el("path", { d: IN, fill: "#fff" }, ins); + let md = ""; + for (let x = CX - RX + 22; x < CX + RX; x += 22) md += `M${x} ${CY - RY} V${CY + RY} `; + for (let y = CY - RY + 14; y < CY + RY; y += 14) md += `M${CX - RX} ${y} H${CX + RX} `; + el("path", { d: md, stroke: LIL, "stroke-width": 1.5, fill: "none", "clip-path": `url(#${id}i)` }, ins); + const sand = Array.from({ length: 54 }, (_, i) => { + const a = i * 2.39996, r = Math.sqrt((i + 0.5) / 54); + return el("circle", { cx: (CX + Math.cos(a) * r * RX * 0.86).toFixed(1), cy: (CY + Math.sin(a) * r * RY * 0.78).toFixed(1), r: 4 + (i % 2), fill: YEL, stroke: INK, "stroke-width": 1.5 }, ins); + }); + const pebs = [["failing test", 262, 200, -5], ["build error", 502, 198, 4], ["eslint", 382, 282, -2]].map(([s, x, y, r], i) => { + const g = el("g", {}, sv); + el("path", { d: "M-92 4 C-96 -26 -50 -36 0 -35 C52 -36 96 -24 92 2 C90 30 44 37 -4 36 C-52 36 -90 30 -92 4 Z", fill: MUTE, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, g); + txt(g, 0, 8, s, { "text-anchor": "middle", "font-size": 23, "font-weight": 700, fill: "#fff" }); + return { g, x, y, r, i }; + }); + el("ellipse", { cx: CX, cy: CY, rx: RX, ry: RY, fill: "none", stroke: INK, "stroke-width": 18 }, sv); + el("ellipse", { cx: CX, cy: CY, rx: RX, ry: RY, fill: "none", stroke: YEL, "stroke-width": 12 }, sv); + const ring = el("ellipse", { cx: CX, cy: CY + 40, rx: RX + 24, ry: RY + 104, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 7", class: "mg-ring" }, sv); + ring.parentNode.insertBefore(ring, ring.parentNode.firstChild); + let g0 = -99; + button(sv, "Shake the check again: the failing test, the build error and eslint stay caught", () => { g0 = api.now(); api.poke(); }); + const cap = strip(svg, 518); + return (t, now) => { + const out = seg(t, 11.3, 11.8), dg = now - g0, gust = dg >= 0 && dg < 1.4 ? Math.sin((Math.PI * dg) / 1.4) : 0; + const env = seg(t, 1.9, 2.3) * (1 - seg(t, 8.2, 8.7)), sh = Math.max(env, gust); + const dx = 11 * env * Math.sin(t * 22) + 18 * gust * Math.sin(now * 24); + sv.setAttribute("transform", `translate(${dx.toFixed(2)} 0) rotate(${(1.2 * env * Math.sin(t * 22 + 1) + 2 * gust * Math.sin(now * 24 + 1)).toFixed(2)} ${CX} ${CY})`); + const dr = seg(t, 2.2, 8.4), fin = seg(t, 0.3, 0.7) * (1 - out); + sand.forEach((s, i) => s.setAttribute("opacity", i / sand.length >= dr ? fin : 0)); + grains.forEach((g) => { + const p = (t * 0.95 + g.ph) % 1, land = TY - 4 - 50 * io(seg(t, 2.3, 8.6)) * (1 - Math.abs(g.x - CX) / 210); + g.n.setAttribute("cx", (g.x + dx * (1 - p) * 0.6).toFixed(1)); + g.n.setAttribute("cy", (CY + 130 + p * p * (land - CY - 130)).toFixed(1)); + g.n.setAttribute("opacity", env * (p < 0.9 ? 1 : (1 - p) * 10)); + }); + const h = 28 * io(seg(t, 2.3, 8.6)); + pile.setAttribute("d", `M196 ${TY} Q${CX} ${TY - 2 * h} 564 ${TY} Z`); + pile.setAttribute("opacity", h > 0.5 ? 1 - out : 0); + heap.forEach((g) => { const u = g.u, x = (1 - u) * (1 - u) * 196 + 2 * u * (1 - u) * CX + u * u * 564, y = TY - 4 * h * u * (1 - u) + g.d; g.n.setAttribute("cx", x.toFixed(1)); g.n.setAttribute("cy", y.toFixed(1)); g.n.setAttribute("opacity", h > 14 && y < TY - 5 ? 1 - out : 0); }); + pebs.forEach((p) => { + const a = 0.5 + p.i * 0.25, k = ob(seg(t, a, a + 0.6)), b = 8.9 + p.i * 0.3; + const pop = 1 + 0.14 * Math.sin(Math.PI * seg(t, b, b + 0.35)); + tf(p.g, p.x + sh * 3 * Math.sin(now * 19 + p.i * 2), p.y - 200 * (1 - k), pop, p.r + sh * 5 * Math.sin(now * 21 + p.i)); + p.g.setAttribute("opacity", seg(t, a, a + 0.12) * (1 - out)); + }); + cap(t < 2.2 || t >= 11.2 ? "Give Claude a check it can run" : t < 6 ? "tests, a build, or a screenshot to compare" : "show evidence, such as test output, rather than assert success"); + }; + }, + }; + + /* ---- 2. a treasure map: the dotted route is drawn first, then a solid line walks it step by step ---- */ + SC.map = { + dur: 12, still: 9.8, + build(svg, api) { + const id = "mgmp" + ++UID, defs = el("defs", {}, svg); + txt(svg, 44, 62, "Explore first, then plan, then code", { "font-size": 28, "font-weight": 700 }); + const key = el("g", {}, svg); + el("rect", { x: 598, y: 26, width: 172, height: 62, rx: 16, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 6", class: "mg-ring" }, key); + const kc = el("g", {}, key); + el("rect", { x: 606, y: 34, width: 156, height: 46, rx: 12, fill: SKY, stroke: INK, "stroke-width": 3 }, kc); + txt(kc, 684, 64, "Shift+Tab", { "text-anchor": "middle", "font-family": MONO, "font-size": 21, "font-weight": 700 }); + let pr = -99; + button(key, "Shift+Tab: plan mode on, the route is drawn again", () => { pr = api.now(); api.seek(2.2); }); + const PX0 = 62, PX1 = 738, PY0 = 108, PY1 = 486, PW = PX1 - PX0, MID = 400; + const clipR = el("rect", { y: PY0 - 4, height: PY1 - PY0 + 8 }, el("clipPath", { id: id + "c" }, defs)); + const paper = el("g", { "clip-path": `url(#${id}c)` }, svg); + el("rect", { x: PX0, y: PY0, width: PW, height: PY1 - PY0, fill: SOFT, stroke: INK, "stroke-width": 3 }, paper); + /* a little land to cross: waves, a lake, hills, trees */ + [[104, 150], [150, 176], [96, 204]].forEach(([x, y]) => el("path", { d: `M${x} ${y} q12 -10 24 0 t24 0 t24 0`, stroke: SKY, "stroke-width": 4, fill: "none", "stroke-linecap": "round" }, paper)); + el("ellipse", { cx: 440, cy: 178, rx: 64, ry: 30, fill: SKY, stroke: INK, "stroke-width": 3 }, paper); + [[628, 468, 38, 54], [688, 468, 30, 40]].forEach(([x, y, w, hh]) => el("path", { d: `M${x - w} ${y} L${x} ${y - hh} L${x + w} ${y} Z`, fill: MINT, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, paper)); + [[250, 190], [282, 206], [520, 142]].forEach(([x, y]) => { el("path", { d: `M${x} ${y} V${y + 22}`, stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, paper); el("circle", { cx: x, cy: y - 4, r: 15, fill: MINT, stroke: INK, "stroke-width": 3 }, paper); }); + const D = "M132 420 C190 330 240 290 300 300 C380 312 410 420 500 404 C580 390 600 262 652 194"; + const route = el("path", { d: D, fill: "none", stroke: "none" }, paper); + const L = route.getTotalLength(), N = Math.floor(L / 17); + const solid = el("path", { d: D, fill: "none", stroke: BLUE, "stroke-width": 11, "stroke-linecap": "round", "stroke-dasharray": `${L} ${L}` }, paper); + const dots = Array.from({ length: N }, (_, i) => { const p = route.getPointAtLength(((i + 0.5) * L) / N); return el("circle", { cx: p.x.toFixed(1), cy: p.y.toFixed(1), r: 3.6, fill: INK }, paper); }); + const ex = el("g", {}, paper); + el("circle", { r: 15, fill: "none", stroke: LIL, "stroke-width": 3, "stroke-dasharray": "4 6" }, ex); + const start = el("g", {}, paper); + el("circle", { r: 13, fill: MINT, stroke: INK, "stroke-width": 3 }, start); + const X = el("g", {}, paper); + el("path", { d: "M-17 -17 L17 17 M17 -17 L-17 17", stroke: INK, "stroke-width": 9, "stroke-linecap": "round" }, X); + const LB = [["explore", 222, 452, 1.5, YEL], ["plan", 300, 256, 2.6, "#fff"], ["implement", 482, 454, 5.7, "#fff"], ["commit", 652, 140, 8.9, MINT]].map(([s, x, y, a, f]) => { + const g = el("g", {}, paper); pill(g, s, f, 24); return { g, x, y, a }; + }); + const stops = [[300, 300, 2.6], [500, 404, 5.7]].map(([x, y, a]) => ({ g: el("circle", { cx: x, cy: y, r: 9, fill: "#fff", stroke: INK, "stroke-width": 3 }, paper), a })); + const rolls = [0, 1].map(() => { + const g = el("g", {}, svg); + el("rect", { x: -16, y: PY0 - 14, width: 32, height: PY1 - PY0 + 28, rx: 16, fill: YEL, stroke: INK, "stroke-width": 3 }, g); + el("path", { d: `M0 ${PY0 + 2} V${PY1 - 2}`, stroke: INK, "stroke-width": 2, "stroke-linecap": "round" }, g); + return g; + }); + const cap = strip(svg, 520); + return (t, now) => { + const out = seg(t, 11.3, 11.8), w = PW * (io(seg(t, 0.15, 1.3)) - io(seg(t, 11.2, 11.85))); + clipR.setAttribute("x", MID - w / 2); clipR.setAttribute("width", Math.max(0, w)); + rolls.forEach((g, i) => tf(g, MID + (i ? 1 : -1) * Math.max(w / 2, 16), 0)); + const S0 = route.getPointAtLength(0), E = route.getPointAtLength(L); + tf(start, S0.x, S0.y, ob(seg(t, 1.3, 1.7))); + const ep = ((t - 1.4) % 0.9) / 0.9, eo = t > 1.4 && t < 3.2 ? 1 - ep : 0; + tf(ex, S0.x, S0.y, 1 + 2.4 * ep); ex.setAttribute("opacity", eo); + const kd = io(seg(t, 2.2, 5)); + dots.forEach((d, i) => d.setAttribute("opacity", (i + 1) / N <= kd + 0.001 ? 1 : 0)); + tf(X, E.x, E.y, cl(ob(seg(t, 4.9, 5.3)), 0, 1.3) * (1 + 0.15 * Math.sin(Math.PI * seg(t, 8.8, 9.2)))); + const p = seg(t, 5.6, 8.8), st = 6, k2 = p >= 1 ? 1 : (Math.floor(p * st) + io(seg((p * st) % 1, 0, 0.7))) / st; + solid.setAttribute("stroke-dashoffset", (L * (1 - k2)).toFixed(1)); solid.setAttribute("opacity", k2 > 0.002 ? 1 : 0); + LB.forEach((b) => { const k = seg(t, b.a, b.a + 0.4); tf(b.g, b.x, b.y, 0.5 + 0.5 * cl(ob(k), 0, 1.2)); b.g.setAttribute("opacity", k > 0 ? 1 : 0); }); + stops.forEach((s) => s.g.setAttribute("opacity", seg(t, s.a, s.a + 0.2))); + const pk = now - pr; kc.setAttribute("transform", `translate(0 ${pk >= 0 && pk < 0.22 ? 3 : 0})`); + cap(t < 2.2 || t >= 11.2 ? "Separate research and planning from implementation" : t < 5.6 ? "Shift+Tab until the status bar shows plan mode on" : t < 9 ? "explore, plan, implement, commit" : "If you could describe the diff in one sentence, skip the plan"); + }; + }, + }; + + /* ---- 3. a clipboard checklist: four short lines tick, the long fifth line is struck out ---- */ + SC.clipboard = { + dur: 12, still: 10, + build(svg) { + txt(svg, 44, 62, "What should go in CLAUDE.md?", { "font-size": 32, "font-weight": 700 }); + el("rect", { x: 96, y: 98, width: 608, height: 470, rx: 26, fill: YEL, stroke: INK, "stroke-width": 3 }, svg); + el("rect", { x: 124, y: 134, width: 552, height: 408, rx: 12, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + el("rect", { x: 320, y: 82, width: 160, height: 46, rx: 14, fill: BLUE, stroke: INK, "stroke-width": 3 }, svg); + el("circle", { cx: 400, cy: 100, r: 8, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + const RY = [188, 246, 304, 362, 420]; + const rows = ["Bash commands", "code style", "testing instructions", "repo etiquette", null].map((s, i) => { + const y = RY[i], g = el("g", {}, svg); + const box = el("rect", { x: 152, y: y - 19, width: 38, height: 38, rx: 9, fill: "#fff", stroke: INK, "stroke-width": 3 }, g); + const tick = el("path", { d: `M${161} ${y + 1} L${168} ${y + 9} L${182} ${y - 9}`, stroke: INK, "stroke-width": 4, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-dasharray": "34 34", "stroke-dashoffset": 34 }, g); + const body = el("g", {}, g); + if (s) txt(body, 214, y + 10, s, { "font-size": 29, "font-weight": 700 }); + else [[214, 118], [346, 92], [452, 132], [598, 44]].forEach(([x, w]) => el("rect", { x, y: y - 9, width: w, height: 18, rx: 9, fill: LIL }, body)); + return { g, box, tick, body, y }; + }); + const halo = el("rect", { x: 140, y: RY[4] - 31, width: 524, height: 62, rx: 16, fill: "none", stroke: LIL, "stroke-width": 3, "stroke-dasharray": "5 7" }, svg); + const strike = el("path", { d: `M144 ${RY[4]} H660`, stroke: INK, "stroke-width": 5, "stroke-linecap": "round", "stroke-dasharray": "516 516" }, svg); + const qs = el("rect", { x: 148, y: 462, width: 504, height: 56, rx: 16, fill: SOFT }, svg); + const q = txt(svg, 400, 497, "Would removing this cause Claude to make mistakes?", { "text-anchor": "middle", "font-size": 22, "font-weight": 600 }); + fit(q, 470); + const a = txt(svg, 400, 500, "under 200 lines per file", { "text-anchor": "middle", "font-size": 28, "font-weight": 700 }); + return (t) => { + const out = seg(t, 11.3, 11.8); + rows.forEach((r, i) => { + const k = oc(seg(t, 0.8 + i * 0.45, 1.25 + i * 0.45)); + r.body.setAttribute("transform", `translate(${-24 * (1 - k)} 0)`); + r.body.setAttribute("opacity", k); + r.box.setAttribute("opacity", k); + if (i < 4) { + const tk = seg(t, 3.6 + i * 0.6, 3.95 + i * 0.6); + r.tick.setAttribute("stroke-dashoffset", (34 * (1 - tk)).toFixed(1)); + r.box.setAttribute("fill", tk > 0.3 ? MINT : "#fff"); + r.g.setAttribute("opacity", 1 - out); + } else r.tick.setAttribute("opacity", 0); + }); + const qa = seg(t, 6.2, 6.5) * (1 - seg(t, 8.5, 8.7)), ans = seg(t, 8.7, 9) * (1 - out); + q.setAttribute("opacity", qa); a.setAttribute("opacity", ans); qs.setAttribute("opacity", Math.max(qa, ans)); + halo.setAttribute("opacity", t > 6.2 && t < 7.4 ? 0.55 + 0.45 * Math.sin((t - 6.2) * 9) : 0); + const sk = io(seg(t, 7.3, 7.8)); + strike.setAttribute("stroke-dashoffset", (516 * (1 - sk)).toFixed(1)); strike.setAttribute("opacity", sk > 0 ? 1 - out : 0); + rows[4].g.setAttribute("opacity", (1 - 0.72 * seg(t, 7.8, 8.6)) * (1 - out)); + }; + }, + }; + + /* ---- title card, 16 by 9: a small bonsai, kept short by pruning ---- */ + SC.hero = { + w: 960, h: 540, dur: 1, still: 0, + build(svg) { + txt(svg, 66, 130, "9 Habits From Anthropic's Docs", { "font-size": 27, "font-weight": 500, fill: MUTE }); + txt(svg, 62, 218, "Claude Code", { "font-size": 70, "font-weight": 700 }); + txt(svg, 62, 298, "Best Practices", { "font-size": 70, "font-weight": 700 }); + el("path", { d: "M68 332 H500", stroke: YEL, "stroke-width": 14, "stroke-linecap": "round" }, svg); + el("rect", { x: 62, y: 374, width: 318, height: 56, rx: 28, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + txt(svg, 221, 411, "Checked 15 Oct 2026", { "text-anchor": "middle", "font-size": 25, "font-weight": 700 }); + /* the bonsai */ + const limb = (d, w) => { el("path", { d, stroke: INK, "stroke-width": w + 6, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, svg); el("path", { d, stroke: MUTE, "stroke-width": w, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, svg); }; + limb("M780 412 C764 364 706 346 722 296 C734 258 800 266 810 222 C815 198 800 182 792 168", 24); + limb("M724 300 C712 290 704 292 692 282", 11); + limb("M806 236 C826 232 842 240 858 232", 10); + const pad = (cx, cy, cs, f) => { + [0, 1].forEach((pass) => { + cs.forEach(([x, y, r]) => el("circle", { cx: cx + x, cy: cy + y, r: pass ? r : r + 3, fill: pass ? f : INK }, svg)); + el("rect", { x: cx - 58 - (pass ? 0 : 3), y: cy - (pass ? 0 : 3), width: 116 + (pass ? 0 : 6), height: 26 + (pass ? 0 : 6), rx: 13 + (pass ? 0 : 3), fill: pass ? f : INK }, svg); + }); + }; + pad(690, 252, [[-34, 4, 24], [-4, -12, 31], [30, 0, 25]], MINT); + pad(858, 206, [[-30, 6, 22], [0, -10, 28], [32, 4, 22]], MINT); + pad(790, 140, [[-44, 8, 28], [-8, -14, 38], [34, 2, 30]], MINT); + el("rect", { x: 666, y: 404, width: 228, height: 22, rx: 8, fill: BLUE, stroke: INK, "stroke-width": 3 }, svg); + el("path", { d: "M680 426 H880 L860 480 H700 Z", fill: BLUE, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, svg); + [712, 832].forEach((x) => el("rect", { x, y: 480, width: 18, height: 12, rx: 3, fill: INK }, svg)); + el("ellipse", { cx: 780, cy: 404, rx: 96, ry: 10, fill: MINT, stroke: INK, "stroke-width": 3 }, svg); + el("path", { d: "M624 500 H906", stroke: LIL, "stroke-width": 3, "stroke-linecap": "round" }, svg); + [[620, 128, 15, SKY], [904, 336, 17, YEL], [600, 400, 12, LIL]].forEach(([x, y, r, col]) => el("path", { d: `M0 ${-r} Q0 0 ${r} 0 Q0 0 0 ${r} Q0 0 ${-r} 0 Q0 0 0 ${-r} Z`, fill: col, stroke: INK, "stroke-width": 2.5, "stroke-linejoin": "round", transform: `translate(${x} ${y})` }, svg)); + return () => {}; + }, + }; + + /* ---- player ---- */ + const q = new URLSearchParams(location.search), frozen = q.has("mgstill"), fixed = parseFloat(q.get("mgstill")); + const calm = matchMedia("(prefers-reduced-motion: reduce)").matches; + const mount = (fig) => { + const sc = SC[fig.dataset.scene], img = fig.querySelector("img"); if (!sc || !img) return; + const W = sc.w || 800, H = sc.h || 600; + const svg = el("svg", { viewBox: `0 0 ${W} ${H}`, class: "mg-svg", role: "group", "aria-label": img.alt || "" }); + svg.style.aspectRatio = `${W} / ${H}`; + el("rect", { width: W, height: H, fill: "#fff" }, svg); + img.replaceWith(svg); + let off = 0, raf = 0, seen = false, held = calm; + const base = performance.now(), clock = () => (performance.now() - base) / 1000; + let draw = () => {}; + const frame = () => { const now = clock(); draw(held ? sc.still : (((now + off) % sc.dur) + sc.dur) % sc.dur, now); }; + const tick = () => { cancelAnimationFrame(raf); frame(); if (seen && !held) raf = requestAnimationFrame(tick); }; + const api = { now: clock, seek: (s) => { off = s - clock(); held = false; tick(); }, poke: () => { held = false; tick(); } }; + draw = sc.build(svg, api); + if (frozen) { draw(isNaN(fixed) ? sc.still : fixed, 0); return; } + new IntersectionObserver(([e]) => { + seen = e.isIntersecting; + if (seen) { tick(); if (!fig.dataset.sent && window.posthog) { fig.dataset.sent = 1; window.posthog.capture("blog_scene_view", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } } + else cancelAnimationFrame(raf); + }, { threshold: 0.25 }).observe(fig); + svg.addEventListener("click", () => { if (!fig.dataset.hit && window.posthog) { fig.dataset.hit = 1; window.posthog.capture("blog_scene_interact", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } }); + frame(); + }; + const go = () => document.querySelectorAll("figure.mg[data-scene]").forEach(mount); + const fonts = document.fonts && document.fonts.load ? Promise.all([document.fonts.load('700 32px "Space Grotesk"'), document.fonts.load('500 20px "Space Grotesk"'), document.fonts.load('700 20px "JetBrains Mono"')]).catch(() => {}) : Promise.resolve(); + const start = () => fonts.then(go, go); + if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", start); else start(); +})(); diff --git a/blog/src/assets/media/claude-code-best-practices/sieve.png b/blog/src/assets/media/claude-code-best-practices/sieve.png new file mode 100644 index 000000000..b0c315e08 Binary files /dev/null and b/blog/src/assets/media/claude-code-best-practices/sieve.png differ diff --git a/blog/src/assets/media/opencode-alternatives/door.png b/blog/src/assets/media/opencode-alternatives/door.png new file mode 100644 index 000000000..419867396 Binary files /dev/null and b/blog/src/assets/media/opencode-alternatives/door.png differ diff --git a/blog/src/assets/media/opencode-alternatives/hero.png b/blog/src/assets/media/opencode-alternatives/hero.png new file mode 100644 index 000000000..10b953e16 Binary files /dev/null and b/blog/src/assets/media/opencode-alternatives/hero.png differ diff --git a/blog/src/assets/media/opencode-alternatives/motion.css b/blog/src/assets/media/opencode-alternatives/motion.css new file mode 100644 index 000000000..11a9a8f2e --- /dev/null +++ b/blog/src/assets/media/opencode-alternatives/motion.css @@ -0,0 +1,13 @@ +/* Bright coded scenes for "OpenCode Alternatives: 7 Coding Agents to Switch To". Pairs with motion.js. */ +.prose figure.mg { margin: 42px 0; } +.prose figure.mg img, .mg .mg-svg { display: block; width: 100%; height: auto; border: 1px solid #1A1320; border-radius: 18px; background: #fff; } +.mg figcaption { font: 500 13px/1.5 "Space Grotesk", system-ui, sans-serif; color: #6B5878; padding-top: 10px; text-align: center; } +.mg .mg-svg { touch-action: manipulation; -webkit-tap-highlight-color: transparent; } +.mg .mg-svg text { -webkit-user-select: none; user-select: none; pointer-events: none; } +.mg .mg-hit { cursor: pointer; outline: none; } +.mg [role="slider"] { cursor: grab; touch-action: none; } +.mg .mg-ring { stroke-opacity: 0; transition: stroke-opacity 0.15s; } +.mg .mg-hit:focus-visible .mg-ring { stroke-opacity: 1; } +.mg [role="slider"]:focus-visible circle:nth-child(2) { stroke-width: 5; } +@media (hover: hover) { .mg .mg-hit:hover .mg-ring { stroke-opacity: 0.5; } } +@media (prefers-reduced-motion: reduce) { .mg .mg-ring { transition: none; } } diff --git a/blog/src/assets/media/opencode-alternatives/motion.js b/blog/src/assets/media/opencode-alternatives/motion.js new file mode 100644 index 000000000..a4679debd --- /dev/null +++ b/blog/src/assets/media/opencode-alternatives/motion.js @@ -0,0 +1,311 @@ +/* Bright coded scenes for "OpenCode Alternatives: 7 Coding Agents to Switch To". Vanilla JS, no library. +
holds a still image; this script swaps in a live SVG. + Plays only in view, honours reduced motion, "?mgstill" freezes each scene on its still frame. */ +(() => { + const NS = "http://www.w3.org/2000/svg"; + const INK = "#1A1320", YEL = "#FFCA54", BLUE = "#6C8EF5", LIL = "#B69CFF", MINT = "#7FD8AE", SKY = "#9AD8FF", SOFT = "#F1ECF9", MUTE = "#7A6A88"; + const SANS = '"Space Grotesk", system-ui, sans-serif', MONO = '"JetBrains Mono", ui-monospace, monospace'; + const el = (t, a = {}, p) => { const n = document.createElementNS(NS, t); for (const k in a) n.setAttribute(k, a[k]); if (p) p.appendChild(n); return n; }; + const txt = (p, x, y, s, a = {}) => { const n = el("text", Object.assign({ x, y, "font-family": SANS, "font-size": 24, "font-weight": 600, fill: INK }, a), p); n.textContent = s; return n; }; + const cl = (v, a = 0, b = 1) => Math.max(a, Math.min(b, v)); + const seg = (t, a, b) => cl((t - a) / (b - a)); + const oc = (x) => 1 - Math.pow(1 - x, 3); + const ob = (x) => { const c = 1.70158; return 1 + (c + 1) * Math.pow(x - 1, 3) + c * Math.pow(x - 1, 2); }; + const io = (x) => (x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2); + const tf = (n, x, y, s = 1, r = 0) => n.setAttribute("transform", `translate(${x} ${y}) rotate(${r}) scale(${s})`); + const button = (g, label, fn) => { + g.setAttribute("role", "button"); g.setAttribute("tabindex", "0"); g.setAttribute("aria-label", label); g.classList.add("mg-hit"); + g.addEventListener("click", fn); + g.addEventListener("keydown", (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); fn(); } }); + }; + const strip = (svg, y, w = 680) => { + el("rect", { x: (800 - w) / 2, y, width: w, height: 48, rx: 24, fill: SOFT }, svg); + return txt(svg, 400, y + 32, "", { "text-anchor": "middle", "font-size": 22, "font-weight": 600 }); + }; + const lerp = (a, b, k) => a + (b - a) * k; + + const SC = {}; + + /* ---- 1. a glass revolving door: OpenCode goes in on the left, seven alternatives come round on the right ---- */ + SC.door = { + dur: 12, still: 10.4, + build(svg, api) { + txt(svg, 40, 62, "Why look for an OpenCode alternative now?", { "font-size": 30, "font-weight": 700 }); + const DX = 332, R = 146, RY = 30, RT = 12, TOP = 170, BOT = 440, CY = 316, W = Math.PI / 4; + const pyT = (a) => TOP - RT * Math.sin(a), pyB = (a) => BOT + RY * Math.sin(a); + const poly = (a0, a1, f) => { let d = ""; for (let i = 0; i <= 16; i++) { const a = ((a0 + ((a1 - a0) * i) / 16) * Math.PI) / 180; d += `${i ? "L" : ""}${(DX + R * Math.cos(a)).toFixed(1)} ${f(a).toFixed(1)} `; } return d; }; + el("path", { d: `M30 ${BOT + 36} H770`, stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + /* the ghost of the card that went in */ + const ghost = el("g", { transform: `translate(104 ${CY})` }, svg); + el("rect", { x: -72, y: -22, width: 144, height: 44, rx: 10, fill: "#fff", stroke: LIL, "stroke-width": 3, "stroke-dasharray": "6 8", "stroke-linecap": "round" }, ghost); + txt(ghost, 0, 8, "OpenCode", { "text-anchor": "middle", "font-size": 23, "font-weight": 700, fill: MUTE }); + const door = el("g", {}, svg); + const CR = R + 14, CH = 38; + el("ellipse", { cx: DX, cy: BOT, rx: CR, ry: RY + 5, fill: SOFT, stroke: INK, "stroke-width": 3 }, door); + el("ellipse", { cx: DX, cy: TOP, rx: CR, ry: RT + 3, fill: SOFT, stroke: INK, "stroke-width": 3 }, door); + el("path", { d: `M${poly(225, 315, pyT)}L${poly(315, 225, pyB)}Z`, fill: SKY, "fill-opacity": 0.22 }, door); + el("path", { d: `M${poly(200, 340, pyB)}`, fill: "none", stroke: LIL, "stroke-width": 2.5, "stroke-linecap": "round" }, door); + const back = el("g", {}, door); + el("rect", { x: DX - 7, y: TOP - 4, width: 14, height: BOT - TOP + 4, rx: 7, fill: BLUE, stroke: INK, "stroke-width": 3 }, door); + const cards = el("g", { "pointer-events": "none" }, door); + const front = el("g", {}, door); + /* the canopy, seen from just below */ + el("path", { d: `M${DX - CR} ${TOP - CH} A${CR} ${RT + 3} 0 0 1 ${DX + CR} ${TOP - CH} V${TOP} A${CR} ${RT + 3} 0 0 0 ${DX - CR} ${TOP} Z`, fill: YEL, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, door); + const wings = [0, 1, 2, 3].map(() => { + const g = el("g", {}); + const pane = el("path", { fill: SKY, "fill-opacity": 0.34, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, g); + const edge = [el("path", { stroke: INK, "stroke-width": 11, "stroke-linecap": "round" }, g), el("path", { stroke: BLUE, "stroke-width": 5, "stroke-linecap": "round" }, g)]; + return { g, pane, edge }; + }); + el("rect", { x: DX - CR - 12, y: TOP - CH - RT - 16, width: 2 * CR + 24, height: BOT + RY + 22 - (TOP - CH - RT - 16), rx: 24, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, class: "mg-ring" }, door); + let pushes = 0, lastPush = -99; + button(door, "Push the revolving door round", () => { pushes++; lastPush = api.now(); api.poke(); }); + /* the cards */ + const card = (s, fill, w) => { + const g = el("g", {}, cards); + el("rect", { x: -w / 2, y: -21, width: w, height: 42, rx: 10, fill, stroke: INK, "stroke-width": 3 }, g); + txt(g, 0, 8, s, { "text-anchor": "middle", "font-size": 23, "font-weight": 700 }); + return g; + }; + const oc0 = card("OpenCode", LIL, 144); + const ALT = [["Claude Code", YEL], ["Codex CLI", SKY], ["Gemini CLI", MINT], ["Kilo Code", LIL], ["Crush", YEL], ["Pi", SKY], ["Goose", MINT]]; + const SX = 628, SY0 = 120, SP = 54; + const alts = ALT.map(([s, col], i) => ({ g: card(s, col, 210), t0: 2.4 + i * 1.0, y: SY0 + i * SP })); + const cap = strip(svg, 514, 700); + return (t, now) => { + const out = seg(t, 11.4, 11.9); + const pk = seg(now - lastPush, 0, 0.8), push = pushes ? (pushes - 1 + oc(pk)) * (Math.PI / 2) : 0; + const th = W * t + push + 0.35; + wings.forEach((w, i) => { + const ang = th + (i * Math.PI) / 2, c = Math.cos(ang), s = Math.sin(ang); + const xe = DX + R * c, yt = TOP - RT * s, yb = BOT + RY * s; + w.pane.setAttribute("d", `M${DX} ${TOP} L${xe.toFixed(1)} ${yt.toFixed(1)} L${xe.toFixed(1)} ${yb.toFixed(1)} L${DX} ${BOT} Z`); + const ed = `M${xe.toFixed(1)} ${(yt + 4).toFixed(1)} V${(yb - 4).toFixed(1)}`; + w.edge.forEach((p) => p.setAttribute("d", ed)); + (s > 0 ? front : back).appendChild(w.g); + }); + /* OpenCode walks in on the left */ + const k = io(seg(t, 0.6, 2.1)); + tf(oc0, lerp(104, DX, k), CY, 1 - 0.25 * k); + oc0.setAttribute("opacity", seg(t, 0, 0.3) * (1 - seg(k, 0.55, 0.95))); + ghost.setAttribute("opacity", seg(t, 1.2, 1.8) * (1 - out)); + /* the alternatives come round one at a time */ + alts.forEach((c) => { + const a = seg(t, c.t0, c.t0 + 0.45), b = io(seg(t, c.t0 + 0.4, c.t0 + 0.95)); + const x0 = lerp(DX, DX + R + 46, oc(a)), x = lerp(x0, SX, b), y = lerp(CY, c.y, b) - 34 * Math.sin(Math.PI * b); + tf(c.g, x, y, 0.72 + 0.28 * oc(a)); + c.g.setAttribute("opacity", seg(t, c.t0, c.t0 + 0.25) * (1 - out)); + }); + cap.textContent = t < 2.4 || t >= 11.4 ? "OpenCode 2.0: “1.0 plugins will not work in 2.0”" : "Seven alternatives, checked 15 Oct 2026"; + }; + }, + }; + + /* ---- 2. a vending machine: eight buttons, and the licence box each one drops into the tray ---- */ + SC.vending = { + dur: 12, still: 10.3, + build(svg, api) { + txt(svg, 40, 58, "Which OpenCode alternatives are open source?", { "font-size": 28, "font-weight": 700 }); + const TOOLS = [["OpenCode", "MIT"], ["Claude Code", "All rights reserved"], ["Codex CLI", "Apache 2.0"], ["Gemini CLI", "Apache 2.0"], ["Kilo Code", "MIT"], ["Crush", "FSL 1.1, MIT future licence"], ["Pi", "MIT"], ["Goose", "Apache 2.0"]]; + const COL = { MIT: MINT, "Apache 2.0": SKY, "All rights reserved": LIL, "FSL 1.1, MIT future licence": YEL }; + const WX = 74, WY = 104, WW = 380, WH = 372, PX = 474, PW = 252, RH = WH / 8, TY = 494, TH = 54; + const TCX = WX + WW / 2, TCY = TY + TH / 2; + el("rect", { x: 50, y: 82, width: 700, height: 486, rx: 24, fill: BLUE, stroke: INK, "stroke-width": 3 }, svg); + el("rect", { x: WX, y: WY, width: WW, height: WH, rx: 14, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + el("rect", { x: PX, y: WY, width: PW, height: WH, rx: 14, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + el("rect", { x: WX, y: TY, width: WW, height: TH, rx: 12, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + el("path", { d: `M${WX + 14} ${TY + 12} H${WX + WW - 14}`, stroke: LIL, "stroke-width": 3, "stroke-linecap": "round" }, svg); + el("rect", { x: PX, y: TY, width: PW, height: TH, rx: 12, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + const read = txt(svg, PX + PW / 2, TY + 35, "", { "text-anchor": "middle", "font-size": 22, "font-weight": 700, "font-family": MONO }); + const clip = el("clipPath", { id: "mg-oca-vclip" }, el("defs", {}, svg)); + el("rect", { x: WX + 2, y: WY + 2, width: WW - 4, height: WH - 4, rx: 12 }, clip); + el("rect", { x: WX + 2, y: TY + 2, width: WW - 4, height: TH - 4, rx: 10 }, clip); + const box = (p, s, fill) => { + const g = el("g", {}, p); + const r = el("rect", { y: -16, height: 32, rx: 6, fill, stroke: INK, "stroke-width": 3 }, g); + const tx = txt(g, 0, 7, s, { "text-anchor": "middle", "font-size": 19, "font-weight": 700 }); + const w = tx.getComputedTextLength() + 28; r.setAttribute("x", -w / 2); r.setAttribute("width", w); + return { g, w }; + }; + let user = null; + const rows = TOOLS.map(([name, lic], i) => { + const top = WY + RH * i, cy = top + 24, col = COL[lic]; + let d = `M${WX + 12} ${top + RH - 3}`; + for (let x = WX + 12; x < WX + WW - 18; x += 12) d += " q3 -7 6 0 t6 0"; + el("path", { d, stroke: LIL, "stroke-width": 2.5, fill: "none", "stroke-linecap": "round" }, svg); + const back = box(svg, lic, col), fr = box(svg, lic, col), bx = WX + 16 + fr.w / 2; + tf(back.g, bx + 10, cy - 5); tf(fr.g, bx, cy); + const by = WY + RH * i + RH / 2, g = el("g", {}, svg); + el("rect", { x: PX + 6, y: by - 22, width: PW - 12, height: 44, rx: 22, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, class: "mg-ring" }, g); + const btn = el("rect", { x: PX + 12, y: by - 18, width: PW - 24, height: 36, rx: 18, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, g); + const lamp = el("circle", { cx: PX + 32, cy: by, r: 9, fill: SOFT, stroke: INK, "stroke-width": 2.5 }, g); + txt(g, PX + 50, by + 7, name, { "font-size": 19, "font-weight": 700 }); + button(g, `${name}: ${lic}`, () => { user = { i, t0: api.now() }; api.poke(); }); + return { btn, lamp, x: bx, y: cy, t0: 1.0 + i * 1.15 }; + }); + const fly = el("g", { "clip-path": "url(#mg-oca-vclip)", "pointer-events": "none" }, svg); + const drops = TOOLS.map(([, lic]) => box(fly, lic, COL[lic]).g); + el("rect", { x: WX, y: WY, width: WW, height: WH, rx: 14, fill: SKY, "fill-opacity": 0.14, "pointer-events": "none" }, svg); + el("path", { d: `M${WX + 362} ${WY + 16} L${WX + 326} ${WY + 92} M${WX + 364} ${WY + 52} L${WX + 344} ${WY + 94}`, stroke: "#fff", "stroke-width": 6, "stroke-linecap": "round", opacity: 0.9, "pointer-events": "none" }, svg); + const place = (g, r, k) => { + if (k < 0) { g.setAttribute("opacity", 0); return; } + g.setAttribute("opacity", 1); + if (k < 0.25) { tf(g, r.x, r.y, 1 + 0.08 * oc(k / 0.25)); return; } + const f = cl((k - 0.25) / 0.5), y = lerp(r.y, TCY, f * f), x = lerp(r.x, TCX, f), bo = k > 0.75 ? -7 * Math.sin(Math.PI * seg(k, 0.75, 0.98)) : 0; + tf(g, x, y + bo, 1.08 - 0.08 * f); + }; + return (t, now) => { + const out = seg(t, 11.4, 11.9); + let cur = -1; + rows.forEach((r, i) => { if (t >= r.t0) cur = i; }); + const pick = user ? user.i : cur; + rows.forEach((r, i) => { + const lit = user ? i === user.i : i === cur && !out, done = t >= r.t0 && !out; + r.btn.setAttribute("fill", lit ? YEL : "#fff"); + r.lamp.setAttribute("fill", lit ? YEL : done ? MINT : SOFT); + }); + drops.forEach((g, i) => { + if (user) { place(g, rows[i], i === user.i ? now - user.t0 : -1); return; } + const r = rows[i], nx = rows[i + 1], k = t - r.t0, gone = nx && t >= nx.t0 + 0.75; + place(g, r, gone ? -1 : k); + if (k >= 0 && !gone) g.setAttribute("opacity", 1 - out); + }); + read.textContent = pick >= 0 && !(out && !user) ? TOOLS[pick][0] : "15 Oct 2026"; + }; + }, + }; + + /* ---- 3. a bookshelf fills one book at a time; each latest release slides out on a tag under its spine ---- */ + SC.shelf = { + dur: 12, still: 10, + build(svg, api) { + txt(svg, 40, 60, "Latest release", { "font-size": 32, "font-weight": 700 }); + txt(svg, 760, 60, "15 Oct 2026", { "text-anchor": "end", "font-size": 24, "font-weight": 600, fill: MUTE }); + const B = [["OpenCode", "v1.19.0", "11 Oct", LIL, 68, 230], ["Claude Code", "v2.1.300", "14 Oct", SKY, 74, 214], ["Codex CLI", "rust-v0.162.1", "9 Oct", MINT, 66, 240], ["Gemini CLI", "v0.64.0", "13 Oct", BLUE, 72, 222], + ["Kilo Code", "v7.8.12", "15 Oct", YEL, 70, 236], ["Crush", "v0.98.1", "9 Oct", LIL, 64, 206], ["Pi", "v1.2.0", "12 Oct", SKY, 60, 196], ["Goose", "v1.54.0", "8 Oct", MINT, 70, 226]]; + const SH = 356, BD = SH + 22; + el("rect", { x: 58, y: 96, width: 684, height: SH - 96, fill: SOFT }, svg); + const tagLayer = el("g", { "clip-path": "url(#mg-oca-tclip)" }, svg); + const clip = el("clipPath", { id: "mg-oca-tclip" }, el("defs", {}, svg)); + el("rect", { x: 0, y: BD, width: 800, height: 600 - BD }, clip); + el("rect", { x: 40, y: 80, width: 720, height: 20, rx: 4, fill: YEL, stroke: INK, "stroke-width": 3 }, svg); + [40, 740].forEach((x) => el("rect", { x, y: 80, width: 20, height: BD - 80 + 4, rx: 4, fill: YEL, stroke: INK, "stroke-width": 3 }, svg)); + const books = el("g", {}, svg); + el("rect", { x: 40, y: SH, width: 720, height: 22, rx: 4, fill: YEL, stroke: INK, "stroke-width": 3 }, svg); + [[120, 367, 60], [420, 368, 80], [640, 366, 50], [250, 90, 70], [560, 89, 60]].forEach(([x, y, w]) => el("path", { d: `M${x} ${y} h${w}`, stroke: INK, "stroke-width": 2, "stroke-linecap": "round", opacity: 0.5 }, svg)); + let lift = -1, liftT = -99; + const items = B.map(([name, ver, date, col, w, h], i) => { + const cx = 102.5 + 85 * i, g = el("g", {}, books), inner = el("g", {}, g); + el("rect", { x: -w / 2 - 8, y: -h - 8, width: w + 16, height: h + 16, rx: 12, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, class: "mg-ring" }, inner); + el("rect", { x: -w / 2, y: -h, width: w, height: h, rx: 6, fill: col, stroke: INK, "stroke-width": 3 }, inner); + el("path", { d: `M${-w / 2 + 3} ${-h + 20} H${w / 2 - 3} M${-w / 2 + 3} -20 H${w / 2 - 3}`, stroke: INK, "stroke-width": 2.5 }, inner); + txt(inner, 0, 0, name, { "text-anchor": "middle", "font-size": 21, "font-weight": 700, transform: `translate(8 ${-h / 2}) rotate(-90)` }); + button(g, `${name}: latest release ${ver}, ${date}`, () => { lift = i; liftT = api.now(); api.poke(); }); + const L = i % 2 ? 18 : 96, tg = el("g", {}, tagLayer), sw = el("g", {}, tg); + el("path", { d: `M0 ${-L - 70} V${L}`, stroke: INK, "stroke-width": 2.5 }, sw); + const r = el("rect", { y: L, height: 66, rx: 10, fill: col, stroke: INK, "stroke-width": 3 }, sw); + el("circle", { cy: L + 12, r: 5, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, sw); + const v = txt(sw, 0, L + 39, ver, { "text-anchor": "middle", "font-size": 18, "font-weight": 700, "font-family": MONO }); + txt(sw, 0, L + 59, date, { "text-anchor": "middle", "font-size": 18, "font-weight": 600 }); + const tw = Math.max(v.getComputedTextLength(), 60) + 26; r.setAttribute("x", -tw / 2); r.setAttribute("width", tw); + return { g, inner, tg, sw, cx, L, t0: 0.6 + i * 0.95 }; + }); + return (t, now) => { + const out = seg(t, 11.4, 11.9); + items.forEach((b, i) => { + const k = seg(t, b.t0, b.t0 + 0.55), e = ob(k); + const lw = i === lift ? now - liftT : 99, up = lw >= 0 && lw < 1.4 ? 22 * Math.sin(Math.PI * cl(lw / 1.4)) : 0; + tf(b.g, b.cx, SH - 14 * (1 - e) - up, 1.1 - 0.1 * e); + b.g.setAttribute("opacity", seg(t, b.t0, b.t0 + 0.15) * (1 - out)); + const tt = t - b.t0 - 0.5, s = oc(seg(tt, 0, 0.5)); + const sw0 = tt < 0 ? 0 : 9 * Math.exp(-2.4 * tt) * Math.sin(7 * tt), sw1 = lw >= 0 && lw < 2 ? 12 * Math.exp(-2.2 * lw) * Math.sin(8 * lw) : 0; + b.tg.setAttribute("transform", `translate(${b.cx} ${BD - (b.L + 70) * (1 - s)})`); + b.sw.setAttribute("transform", `rotate(${sw0 + sw1})`); + b.tg.setAttribute("opacity", (tt < 0 ? 0 : 1) * (1 - out)); + }); + }; + }, + }; + + /* ---- title card, 16 by 9: a snow globe ---- */ + SC.hero = { + w: 960, h: 540, dur: 8, still: 4, + build(svg) { + txt(svg, 64, 116, "7 coding agents to switch to", { "font-size": 27, "font-weight": 500, fill: MUTE }); + txt(svg, 60, 208, "OpenCode", { "font-size": 82, "font-weight": 700 }); + txt(svg, 60, 296, "Alternatives", { "font-size": 82, "font-weight": 700 }); + el("path", { d: "M66 326 H552", stroke: YEL, "stroke-width": 14, "stroke-linecap": "round" }, svg); + el("rect", { x: 60, y: 362, width: 316, height: 56, rx: 28, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + txt(svg, 218, 399, "Checked 15 Oct 2026", { "text-anchor": "middle", "font-size": 25, "font-weight": 700 }); + el("path", { d: "M72 474 C190 430 300 500 420 462 S560 432 606 416", stroke: LIL, "stroke-width": 5, fill: "none", "stroke-linecap": "round", "stroke-dasharray": "1 16" }, svg); + el("circle", { cx: 72, cy: 474, r: 11, fill: MINT, stroke: INK, "stroke-width": 3 }, svg); + const GX = 794, GY = 246, GR = 124; + const clip = el("clipPath", { id: "mg-oca-gclip" }, el("defs", {}, svg)); + el("circle", { cx: GX, cy: GY, r: GR - 2 }, clip); + el("circle", { cx: GX, cy: GY, r: GR, fill: SKY, "fill-opacity": 0.25 }, svg); + const inside = el("g", { "clip-path": "url(#mg-oca-gclip)" }, svg); + el("ellipse", { cx: GX, cy: GY + 112, rx: 140, ry: 52, fill: "#fff", stroke: INK, "stroke-width": 3 }, inside); + const tree = (x, y, s) => { const g = el("g", { transform: `translate(${x} ${y}) scale(${s})` }, inside); el("rect", { x: -5, y: -6, width: 10, height: 16, fill: YEL, stroke: INK, "stroke-width": 3 }, g); el("path", { d: "M0 -74 L22 -40 H12 L30 -8 H-30 L-12 -40 H-22 Z", fill: MINT, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, g); }; + tree(GX - 78, GY + 74, 0.9); tree(GX + 84, GY + 70, 0.75); + const term = el("g", { transform: `translate(${GX} ${GY + 16})` }, inside); + el("rect", { x: -54, y: -44, width: 108, height: 84, rx: 10, fill: "#fff", stroke: INK, "stroke-width": 3 }, term); + el("path", { d: "M-54 -24 V-34 a10 10 0 0 1 10 -10 H44 a10 10 0 0 1 10 10 V-24 Z", fill: LIL, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, term); + [-40, -28, -16].forEach((x) => el("circle", { cx: x, cy: -34, r: 3.5, fill: "#fff", stroke: INK, "stroke-width": 2 }, term)); + el("path", { d: "M-34 -6 L-18 6 L-34 18", stroke: INK, "stroke-width": 5, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, term); + const cur = el("path", { d: "M-6 20 H22", stroke: BLUE, "stroke-width": 5, "stroke-linecap": "round" }, term); + const flakes = Array.from({ length: 26 }, (_, i) => { + const h = (k) => { const v = Math.sin(i * 12.9898 + k * 78.233) * 43758.5453; return v - Math.floor(v); }; + const x = GX - 106 + h(1) * 212, ph = h(2), sp = [1, 2][i % 2] / 8, r = 4 + (i % 3) * 1.5; + const n = el("g", {}, inside); + if (i % 2) el("path", { d: `M0 ${-r - 2} V${r + 2} M${-(r + 2) * 0.87} ${-(r + 2) / 2} L${(r + 2) * 0.87} ${(r + 2) / 2} M${-(r + 2) * 0.87} ${(r + 2) / 2} L${(r + 2) * 0.87} ${-(r + 2) / 2}`, stroke: BLUE, "stroke-width": 2, "stroke-linecap": "round" }, n); + else el("circle", { r: r * 0.7, fill: "#fff", stroke: LIL, "stroke-width": 1.5 }, n); + return { n, x, ph, sp, w: i * 1.7 }; + }); + el("circle", { cx: GX, cy: GY, r: GR, fill: "none", stroke: INK, "stroke-width": 3 }, svg); + el("path", { d: `M${GX - 92} ${GY - 40} A100 100 0 0 1 ${GX - 40} ${GY - 92}`, stroke: "#fff", "stroke-width": 8, fill: "none", "stroke-linecap": "round" }, svg); + el("path", { d: `M${GX - 104} ${GY - 4} A106 106 0 0 1 ${GX - 101} ${GY - 22}`, stroke: "#fff", "stroke-width": 8, fill: "none", "stroke-linecap": "round" }, svg); + el("path", { d: `M${GX - 98} ${GY + 104} L${GX + 98} ${GY + 104} L${GX + 124} ${GY + 196} L${GX - 124} ${GY + 196} Z`, fill: BLUE, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, svg); + el("path", { d: `M${GX - 107} ${GY + 136} L${GX + 107} ${GY + 136} L${GX + 113} ${GY + 158} L${GX - 113} ${GY + 158} Z`, fill: YEL, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, svg); + el("rect", { x: GX - 136, y: GY + 196, width: 272, height: 16, rx: 8, fill: LIL, stroke: INK, "stroke-width": 3 }, svg); + [[620, 132, 15, SKY], [918, 112, 14, YEL], [640, 476, 12, MINT]].forEach(([x, y, r, col]) => el("path", { d: `M0 ${-r} Q0 0 ${r} 0 Q0 0 0 ${r} Q0 0 ${-r} 0 Q0 0 0 ${-r} Z`, fill: col, stroke: INK, "stroke-width": 2.5, "stroke-linejoin": "round", transform: `translate(${x} ${y})` }, svg)); + return (t) => { + flakes.forEach((f) => { + const p = (f.ph + t * f.sp) % 1, y = GY - GR + 6 + p * (GR * 2 - 30), x = f.x + 7 * Math.sin(t * 0.785 * 2 + f.w); + f.n.setAttribute("transform", `translate(${x.toFixed(1)} ${y.toFixed(1)}) rotate(${(t * 45 + f.w * 20) % 360})`); + }); + cur.setAttribute("opacity", Math.floor(t * 2) % 2 ? 0.15 : 1); + }; + }, + }; + + /* ---- player ---- */ + const q = new URLSearchParams(location.search), frozen = q.has("mgstill"), fixed = parseFloat(q.get("mgstill")); + const calm = matchMedia("(prefers-reduced-motion: reduce)").matches; + const mount = (fig) => { + const sc = SC[fig.dataset.scene], img = fig.querySelector("img"); if (!sc || !img) return; + const W = sc.w || 800, H = sc.h || 600; + const svg = el("svg", { viewBox: `0 0 ${W} ${H}`, class: "mg-svg", role: "group", "aria-label": img.alt || "" }); + svg.style.aspectRatio = `${W} / ${H}`; + el("rect", { width: W, height: H, fill: "#fff" }, svg); + img.replaceWith(svg); + let off = 0, raf = 0, seen = false, held = calm; + const base = performance.now(), clock = () => (performance.now() - base) / 1000; + let draw = () => {}; + const frame = () => { const now = clock(); draw(held ? sc.still : (((now + off) % sc.dur) + sc.dur) % sc.dur, now); }; + const tick = () => { cancelAnimationFrame(raf); frame(); if (seen && !held) raf = requestAnimationFrame(tick); }; + const api = { now: clock, seek: (s) => { off = s - clock(); held = false; tick(); }, poke: () => { held = false; tick(); } }; + draw = sc.build(svg, api); + if (frozen) { draw(isNaN(fixed) ? sc.still : fixed, 0); return; } + new IntersectionObserver(([e]) => { + seen = e.isIntersecting; + if (seen) { tick(); if (!fig.dataset.sent && window.posthog) { fig.dataset.sent = 1; window.posthog.capture("blog_scene_view", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } } + else cancelAnimationFrame(raf); + }, { threshold: 0.25 }).observe(fig); + svg.addEventListener("click", () => { if (!fig.dataset.hit && window.posthog) { fig.dataset.hit = 1; window.posthog.capture("blog_scene_interact", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } }); + frame(); + }; + const go = () => document.querySelectorAll("figure.mg[data-scene]").forEach(mount); + const fonts = document.fonts && document.fonts.load ? Promise.all([document.fonts.load('700 32px "Space Grotesk"'), document.fonts.load('500 20px "Space Grotesk"'), document.fonts.load('700 20px "JetBrains Mono"')]).catch(() => {}) : Promise.resolve(); + const start = () => fonts.then(go, go); + if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", start); else start(); +})(); diff --git a/blog/src/assets/media/opencode-alternatives/shelf.png b/blog/src/assets/media/opencode-alternatives/shelf.png new file mode 100644 index 000000000..fa79be95f Binary files /dev/null and b/blog/src/assets/media/opencode-alternatives/shelf.png differ diff --git a/blog/src/assets/media/opencode-alternatives/vending.png b/blog/src/assets/media/opencode-alternatives/vending.png new file mode 100644 index 000000000..eb8a3e1dd Binary files /dev/null and b/blog/src/assets/media/opencode-alternatives/vending.png differ diff --git a/blog/src/posts/claude-code-best-practices.md b/blog/src/posts/claude-code-best-practices.md new file mode 100644 index 000000000..500914ac7 --- /dev/null +++ b/blog/src/posts/claude-code-best-practices.md @@ -0,0 +1,122 @@ +--- +title: "Claude Code Best Practices: 9 Habits From Anthropic's Docs" +description: "Nine Claude Code best practices from Anthropic's docs, checked 15 Oct 2026: give Claude a check, plan first, keep memory short, clear context, run in parallel." +date: 2026-10-15 +category: guides +categoryLabel: Guides +type: Technical +primaryKeyword: "claude code best practices" +secondaryKeywords: ["claude code tips", "claude code tips and tricks", "how to use claude code effectively", "claude code workflow", "claude.md best practices"] +tags: ["Guides", "Claude Code", "Getting Started"] +faq: + - q: "What are the most important Claude Code best practices?" + a: "Anthropic's best practices page opens with giving Claude a way to verify its work, then exploring and planning before coding. It says most best practices are based on one constraint: the context window fills up fast and performance degrades as it fills. So keep `CLAUDE.md` short and run /clear between unrelated tasks." + - q: "How long should `CLAUDE.md` be?" + a: "Short. Anthropic's memory docs say to target under 200 lines per `CLAUDE.md` file, because longer files consume more context and reduce adherence. The best practices page suggests asking of each line whether removing it would cause Claude to make mistakes, and cutting it if not." + - q: "When should I use plan mode in Claude Code?" + a: "Anthropic's best practices page says planning is most useful when you are uncertain about the approach, when the change touches multiple files, or when you are unfamiliar with the code. For small, clear fixes it says to ask Claude directly. Its rule of thumb: if you could describe the diff in one sentence, skip the plan." + - q: "How do I get fewer permission prompts in Claude Code?" + a: "Anthropic's best practices page says to pre-approve the tools you trust with /permissions and let sandboxed commands run without asking with /sandbox. With Claude Code v2.1.283 or later, auto mode is the built-in starting permission mode for interactive terminal and VS Code sessions, per the permission modes docs." + - q: "Can I run more than one Claude Code session at once?" + a: "Yes. Anthropic's best practices page lists worktrees, the desktop app, cloud sessions, agent view (a research preview) and agent teams (experimental and disabled by default). Munder Difflin is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer." +--- + +The core Claude Code best practices, from Anthropic's own docs, are to give Claude a check it can run, plan before coding, keep `CLAUDE.md` short and clear context between unrelated tasks. Checked 15 Oct 2026. + +To run several sessions at once, you can also use [Munder Difflin](https://harnessmd.com/download), free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. Our [install guide](/blog/how-to-install-and-use-munder-difflin/) covers it. New to the tool itself? Read [how to use Claude Code](/blog/how-to-use-claude-code/) first, or browse the [Guides hub](/blog/topics/guides/). + +Every tip below comes from [Anthropic's best practices page](https://code.claude.com/docs/en/best-practices) unless it names another Anthropic page or says "Our view". + +## Why does the context window shape every tip? + +Because Claude gets worse as its context fills. Anthropic's best practices page says "Claude's context window fills up fast, and performance degrades as it fills", and calls the context window "the most important resource to manage". Most of the habits below either save context or replace your attention with a check. + +## 1. Give Claude a way to verify its work + +Give Claude a check it can run: tests, a build, or a screenshot to compare. Anthropic's best practices page puts it as "the difference between a session you watch and one you walk away from." Put the check in the prompt, as in the page's example: + +```txt +write a validateEmail function. example test cases: user@example.com is true, invalid is false, user@.com is false. run the tests after implementing +``` + +For harder gates, the same page lists a `/goal` condition, a Stop hook that blocks the turn until the check passes, and a verification subagent. It also says to have Claude show evidence, such as test output, rather than assert success. + +
Animation. A round sieve shakes over a tray. Fine sand falls through, while three pebbles marked failing test, build error and eslint stay caught in the mesh.
A check catches the bad pebbles before you do. Anthropic's best practices page, checked 15 Oct 2026.
+ +## 2. Explore first, then plan, then code + +Separate research and planning from implementation, so Claude does not solve the wrong problem. Anthropic's best practices page gives four phases: explore, plan, implement, commit. Enter plan mode by pressing `Shift+Tab` until the status bar shows plan mode on, or start with: + +```bash +claude --permission-mode plan +``` + +Press `Ctrl+G` to edit the plan in your text editor before Claude proceeds. The page also says "If you could describe the diff in one sentence, skip the plan." More in our [plan mode guide](/blog/how-to-use-claude-code-plan-mode/). + +
Animation. A paper treasure map unrolls. A dotted route draws itself from a start mark to a cross, and only then does a solid line follow the dots, step by step.
Draw the route, then walk it: explore, plan, implement, commit, from Anthropic's best practices page.
+ +## 3. Be specific in your prompts + +Reference specific files, mention constraints and point to example patterns. Anthropic's best practices page contrasts "fix the login bug" with a prompt that names the symptom, the likely location in `src/auth/` and a failing test to write first. Use `@` to reference files, paste screenshots, or pipe data in: + +```bash +cat error.log | claude -p "explain this error" +``` + +## 4. What should go in `CLAUDE.md`? + +Only what Claude can't infer from code and needs in every session: Bash commands, code style that differs from defaults, testing instructions, repo etiquette. Run `/init` for a starter file and `/context` to confirm Claude loaded it. For each line, Anthropic's best practices page asks "Would removing this cause Claude to make mistakes?" If not, cut it. Anthropic's [memory docs](https://code.claude.com/docs/en/memory) say to target under 200 lines per file. Our view: a `CLAUDE.md` that reads like a policy manual gets read like one. See [give Claude Code long term memory](/blog/give-claude-code-long-term-memory/). + +
Animation. A clipboard holds a short checklist of four lines. Each box ticks itself in turn, and a fifth, longer line fades out and is struck through before it can be ticked.
Short beats long: Anthropic's memory docs say to target under 200 lines per file.
+ +## 5. Set permissions once, not per prompt + +Pre-approve the tools you trust with `/permissions` and let sandboxed commands run without asking with `/sandbox`. Anthropic's best practices page notes that with Claude Code v2.1.283 or later, auto mode is the built-in starting permission mode for interactive terminal and VS Code sessions, where a classifier model blocks only what looks risky. On earlier versions that applies only on Pro, Max and Team plans. See [why Claude Code keeps asking for permission](/blog/why-does-claude-code-keep-asking-for-permission/). + +## 6. Use hooks for rules with zero exceptions + +Use hooks for anything that must happen every time. Anthropic's best practices page says "Unlike `CLAUDE.md` instructions which are advisory, hooks are deterministic and guarantee the action happens." Ask Claude to "Write a hook that runs eslint after every file edit", edit `.claude/settings.json` by hand, and run `/hooks` to see what is set. Our [hooks explainer](/blog/claude-code-hooks-explained/) goes further. + +## 7. Send research to subagents + +Delegate investigation to subagents so the reading happens outside your main context. Anthropic's best practices page says subagents "run in separate context windows and report back summaries", and suggests prompts like "use subagents to investigate X". Custom subagents live in `.claude/agents/`. The same page suggests a subagent review of the diff in a fresh context before you call a task done. + +## 8. Clear context between tasks + +Run `/clear` between unrelated tasks. Anthropic's best practices page says that if you have corrected Claude more than twice on the same issue in one session, the context is cluttered with failed approaches, so clear it and write a better prompt. For finer control: + +```txt +/compact Focus on the API changes +``` + +`Esc` stops Claude mid action, and `Esc + Esc` or `/rewind` restores earlier code or conversation. Anthropic's best practices page warns that checkpoints are not "a replacement for git". + +## 9. Run several sessions side by side + +Run several sessions in parallel. Anthropic's best practices page lists worktrees, the desktop app, cloud sessions, agent view (research preview) and agent teams (experimental and disabled by default). It also suggests a Writer and Reviewer pair, because "a fresh context improves code review". Our [multiple sessions guide](/blog/manage-multiple-claude-code-sessions/) covers the setups. + +## Claude Code best practices at a glance, from Anthropic's docs, checked 15 Oct 2026 + +| Practice | What to do | Where in the docs | +| --- | --- | --- | +| Verify | Give a test, build or screenshot check | Give Claude a way to verify its work | +| Plan | `Shift+Tab` to plan mode, then implement | Explore first, then plan, then code | +| Be specific | Name files, constraints, patterns; use `@` | Provide specific context in your prompts | +| `CLAUDE.md` | `/init`, then prune; under 200 lines | Write an effective `CLAUDE.md`; memory docs | +| Permissions | `/permissions` and `/sandbox` | Configure permissions | +| Hooks | Script what must always happen | Set up hooks | +| Subagents | "use subagents to investigate X" | Use subagents for investigation | +| Context | `/clear`, `/compact`, `/rewind` | Manage your session | +| Parallel | Worktrees, desktop app, agent view | Run multiple Claude sessions | + +## What mistakes does Anthropic warn about? + +Anthropic's best practices page names five failure patterns: + +- **The kitchen sink session:** unrelated tasks in one context. Fix: `/clear`. +- **Correcting over and over:** after two failed corrections, clear and rewrite the prompt. +- **The over-specified `CLAUDE.md`:** prune it, or turn a rule into a hook. +- **The trust-then-verify gap:** "If you can't verify it, don't ship it." +- **The infinite exploration:** scope investigations narrowly or use subagents. + + diff --git a/blog/src/posts/how-to-use-claude-code.md b/blog/src/posts/how-to-use-claude-code.md index d31b214b5..9863c21d0 100644 --- a/blog/src/posts/how-to-use-claude-code.md +++ b/blog/src/posts/how-to-use-claude-code.md @@ -135,6 +135,8 @@ In our experience, most beginner trouble is a full context window. Everything Cl * **Shipping what you can't verify.** Give it tests, scripts or screenshots. * **Treating rewind as version control.** Checkpoints only track edits made through Claude's file editing tools. Commit first. +The fuller list is in [Claude Code best practices](/blog/claude-code-best-practices/). + ## Where to go next Once the basics feel boring, pick one: diff --git a/blog/src/posts/open-source-ai-agents.md b/blog/src/posts/open-source-ai-agents.md index d59b80b4f..adc4e580f 100644 --- a/blog/src/posts/open-source-ai-agents.md +++ b/blog/src/posts/open-source-ai-agents.md @@ -42,7 +42,7 @@ It is a program that calls a language model in a loop and uses tools to finish a ### Coding agents -**5. OpenCode.** OpenCode is an MIT terminal coding agent that works with many model providers, local models included. Our [what is OpenCode](/blog/what-is-opencode/) explainer walks through it. Best for: a Claude Code style agent you can fork. +**5. OpenCode.** OpenCode is an MIT terminal coding agent that works with many model providers, local models included. Our [what is OpenCode](/blog/what-is-opencode/) explainer walks through it. Leaving it? See our [OpenCode alternatives](/blog/opencode-alternatives/). Best for: a Claude Code style agent you can fork. **6. Cline.** Cline (Apache 2.0) calls itself the open source coding agent in your IDE, terminal and desktop. Best for: working inside your editor. diff --git a/blog/src/posts/opencode-alternatives.md b/blog/src/posts/opencode-alternatives.md new file mode 100644 index 000000000..43e46f552 --- /dev/null +++ b/blog/src/posts/opencode-alternatives.md @@ -0,0 +1,100 @@ +--- +title: "OpenCode Alternatives: 7 Coding Agents to Switch To" +seoTitle: "7 OpenCode Alternatives After the 2.0 Plugin Break" +description: "Seven OpenCode alternatives checked on 15 Oct 2026: Claude Code, Codex CLI, Gemini CLI, Kilo Code, Crush, Pi and Goose, by licence, models and release." +date: 2026-10-15 +category: comparisons +categoryLabel: Comparisons +type: Technical +primaryKeyword: "opencode alternatives" +secondaryKeywords: ["opencode alternative", "open source opencode alternative", "tools like opencode", "opencode 2.0 plugins", "opencode vs kilo code"] +tags: ["Comparisons", "CLI Agents", "Open Source", "Engines"] +faq: + - q: "What is the closest alternative to OpenCode?" + a: "Kilo Code, in our view. Kilo's README on GitHub says \"Kilo CLI is a fork of OpenCode, enhanced to work within the Kilo agentic engineering platform.\" It is MIT licensed and also runs in VS Code and JetBrains." + - q: "Is there an open source OpenCode alternative?" + a: "Yes. On 15 Oct 2026 GitHub listed Codex CLI, Gemini CLI and Goose under Apache 2.0, and Kilo Code and Pi under MIT. Crush uses the Functional Source License with an MIT future licence. Claude Code's licence file says \"All rights reserved\"." + - q: "Will my OpenCode 1.x plugins work in OpenCode 2.0?" + a: "No. Anomaly's OpenCode 2.0 post lists \"1.0 plugins will not work in 2.0\" as a breaking change. OpenCode's migration guide says to port plugins, because V1 plugin implementations do not run in V2." + - q: "Can I use OpenCode and Claude Code together?" + a: "Yes. They are separate CLIs, so you can install both. Munder Difflin is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer, and it can start the OpenCode CLI you installed as one of them." +--- + +In our view the closest OpenCode alternative is Kilo Code, whose CLI is a fork of OpenCode. Claude Code, Codex CLI and Gemini CLI are the big vendor agents, and Crush, Pi and Goose are agents that take many models. Checked 15 Oct 2026. + +This list is for people weighing a move off OpenCode this week. If you are new to it, read [what is OpenCode](/blog/what-is-opencode/) first. The [Comparisons hub](/blog/topics/comparisons/) has the head to head posts. + +## Why look for an OpenCode alternative now? + +Because OpenCode 2.0 breaks old setups. [Anomaly's OpenCode 2.0 post](https://anoma.ly/notes/opencode-2-0/), dated 12 Oct 2026, lists three breaking changes: "1.0 plugins will not work in 2.0", "Server API is completely different" and the move of the `tui.json` config. The post's upgrade command is `opencode upgrade --major`. OpenCode's [migration guide](https://opencode.ai/v2/docs/migrate-v1/) says "Port plugins, because V1 plugin implementations do not run in V2." + +
Animation. A glass revolving door turns slowly. A card marked OpenCode goes in on the left, and as the door turns, cards marked Claude Code, Codex CLI, Gemini CLI, Kilo Code, Crush, Pi and Goose come round one at a time.
OpenCode 2.0 breaks 1.0 plugins, per Anomaly's OpenCode 2.0 post of 12 Oct 2026.
+ +If your setup has no 1.x plugins, the upgrade may be all you need, and OpenCode is still MIT with more than 213,000 stars on [GitHub](https://github.com/anomalyco/opencode). On 15 Oct 2026 its latest GitHub release was v1.19.0 (11 Oct), while the [@opencode/cli package on npm](https://www.npmjs.com/package/@opencode/cli) was at 2.0.29. + +## Seven alternatives, plus a different kind of tool + +Here are seven coding agents and one desktop app for running agents; the "Best for" lines are our view. Stars and releases are from each GitHub repo on 15 Oct 2026. + +**1. Claude Code.** [Claude Code](https://code.claude.com/docs/en/overview) is, in Anthropic's words, "an agentic coding tool that reads your codebase, edits files, runs commands, and integrates with your development tools". Its [licence file](https://github.com/anthropics/claude-code/blob/main/LICENSE.md) says "All rights reserved", so the code is not open source ([more here](/blog/is-claude-code-open-source/)). Latest release v2.1.300 (14 Oct), more than 151,000 stars. Best for: Claude models. See [OpenCode vs Claude Code](/blog/opencode-vs-claude-code/) and [Claude Code alternatives](/blog/claude-code-alternatives/). + +**2. Munder Difflin.** [Munder Difflin](https://harnessmd.com/download) is what we make, and it is not a replacement for OpenCode. It is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. OpenCode can be one of those agents, because it starts the CLI you installed. Best for: keeping OpenCode and adding others. Start with [how to install and use Munder Difflin](/blog/how-to-install-and-use-munder-difflin/). + +**3. Codex CLI.** OpenAI's [README](https://github.com/openai/codex) calls it "a coding agent from OpenAI that runs locally on your computer". It is Apache 2.0. You sign in with ChatGPT to use your Plus, Pro, Business, Edu or Enterprise plan, or use an API key with extra setup. Latest release rust-v0.162.1 (9 Oct), more than 129,000 stars. Best for: OpenAI models. + +**4. Gemini CLI.** Google's [README](https://github.com/google-gemini/gemini-cli) says it "brings the power of Gemini directly into your terminal". It is Apache 2.0, and the README lists a free tier of "60 requests/min and 1,000 requests/day with personal Google account". Latest release v0.64.0 (13 Oct), more than 107,000 stars. Best for: a free tier. + +**5. Kilo Code.** Kilo's [README](https://github.com/Kilo-Org/kilocode) says "Kilo CLI is a fork of OpenCode, enhanced to work within the Kilo agentic engineering platform." It also runs in VS Code and JetBrains, is MIT, and lists "500+ models". Latest release v7.8.12 (15 Oct), more than 27,000 stars. Best for: OpenCode habits under a different maintainer. + +**6. Crush.** Charm's [Crush](https://github.com/charmbracelet/crush) promises "your workflows, wired into your LLM of choice": a wide range of LLMs, or your own through OpenAI or Anthropic compatible APIs. Its [licence](https://github.com/charmbracelet/crush/blob/main/LICENSE.md) is the "Functional Source License, Version 1.1, MIT Future License". Latest release v0.98.1 (9 Oct), more than 28,000 stars. Best for: a polished terminal UI. + +**7. Pi.** The [README](https://github.com/earendil-works/pi) says "Pi is a minimal, extensible agent harness that you can make your own", and that it "skips features like sub-agents and plan mode". It is MIT; badlogic/pi-mono now redirects to earendil-works/pi. Latest release v1.2.0 (12 Oct), more than 115,000 stars. Best for: building your own agent. + +**8. Goose.** The [README](https://github.com/aaif-goose/goose) says "goose is a general-purpose AI agent that runs on your machine", with a desktop app, a CLI and "15+ providers". It is Apache 2.0 and part of the Agentic AI Foundation at the Linux Foundation; block/goose now redirects there. Latest release v1.54.0 (8 Oct), more than 55,000 stars. Best for: work beyond code. + +
Animation. A vending machine with eight buttons, each labelled with a tool name. The buttons light up in turn and a small box marked with the tool's licence drops into the tray each time.
Eight buttons, eight licences. From each tool's GitHub repo, 15 Oct 2026.
+ +## OpenCode alternatives at a glance, checked 15 Oct 2026 + +Every cell comes from the tool's GitHub repo, README or docs, opened on 15 Oct 2026. + +| Tool | Maker | Licence | Models it can use | Latest release | +| --- | --- | --- | --- | --- | +| OpenCode | Anomaly | MIT | 75+ providers, local models | v1.19.0, 11 Oct | +| Claude Code | Anthropic | All rights reserved | Claude, direct or via cloud providers | v2.1.300, 14 Oct | +| Munder Difflin | Munder Difflin | Free and open source | Whatever your agents run | See the download page | +| Codex CLI | OpenAI | Apache 2.0 | OpenAI, ChatGPT sign in or API key | rust-v0.162.1, 9 Oct | +| Gemini CLI | Google | Apache 2.0 | Gemini | v0.64.0, 13 Oct | +| Kilo Code | Kilo | MIT | 500+ models | v7.8.12, 15 Oct | +| Crush | Charm | FSL 1.1, MIT future licence | Many providers, compatible APIs | v0.98.1, 9 Oct | +| Pi | earendil-works | MIT | OpenAI, Anthropic, Google and more | v1.2.0, 12 Oct | +| Goose | Part of the Agentic AI Foundation | Apache 2.0 | 15+ providers | v1.54.0, 8 Oct | + +OpenCode's model count is from [opencode.ai](https://opencode.ai/), Claude Code's cloud providers from Anthropic's [enterprise deployment page](https://code.claude.com/docs/en/third-party-integrations), and Pi's providers from the README's pi-ai package line. + +
Animation. A wooden bookshelf fills one book at a time. Eight spines carry tool names, from OpenCode to Goose, and a small tag with each latest release slides out under its spine.
OpenCode and seven alternatives, with release tags read from GitHub on 15 Oct 2026.
+ +## Which OpenCode alternatives are open source? + +Five of the seven carry a standard open source licence on GitHub: Codex CLI, Gemini CLI and Goose under Apache 2.0, and Kilo Code and Pi under MIT. Crush is different. Its licence bars a "Competing Use" and grants MIT "on the second anniversary of the date we make the Software available", so each version turns MIT two years after release. Claude Code is "All rights reserved". + +## Which one is closest to OpenCode? + +Kilo Code, because its CLI is a fork of OpenCode, per Kilo's README. Our view: that makes it the shortest move. Whether your 1.x plugins carry over is not stated in Kilo's README, so check before you rely on it. + +## Who should pick what? + +Our view: pick by why you are leaving. + +- **You want the shortest move:** Kilo Code. +- **You want to keep OpenCode and add other agents:** Munder Difflin. +- **You want Claude models:** Claude Code. +- **You pay for ChatGPT already:** Codex CLI. +- **You want a free tier:** Gemini CLI. +- **You want a terminal UI with LSP context:** Crush. +- **You want to build your own agent:** Pi. +- **You want a desktop app for more than code:** Goose. + +If none of that applies, run `opencode upgrade --major`, port your plugins and stay put. Switching tools to avoid porting a few plugins is a lot of moving boxes. + + diff --git a/docs/blog/agent-security-and-sandboxing/index.html b/docs/blog/agent-security-and-sandboxing/index.html index 4a166b17b..c9f32b6a2 100644 --- a/docs/blog/agent-security-and-sandboxing/index.html +++ b/docs/blog/agent-security-and-sandboxing/index.html @@ -327,6 +327,22 @@

FAQ

Related in Guides

+ -
diff --git a/docs/blog/agent-tools-today-2026-10-15/index.html b/docs/blog/agent-tools-today-2026-10-15/index.html index 97a79200e..a5a4dd28b 100644 --- a/docs/blog/agent-tools-today-2026-10-15/index.html +++ b/docs/blog/agent-tools-today-2026-10-15/index.html @@ -235,7 +235,7 @@

How this list is built diff --git a/docs/blog/assets/media/claude-code-best-practices/clipboard.png b/docs/blog/assets/media/claude-code-best-practices/clipboard.png new file mode 100644 index 000000000..68f505a94 Binary files /dev/null and b/docs/blog/assets/media/claude-code-best-practices/clipboard.png differ diff --git a/docs/blog/assets/media/claude-code-best-practices/hero.png b/docs/blog/assets/media/claude-code-best-practices/hero.png new file mode 100644 index 000000000..efcabdb6e Binary files /dev/null and b/docs/blog/assets/media/claude-code-best-practices/hero.png differ diff --git a/docs/blog/assets/media/claude-code-best-practices/map.png b/docs/blog/assets/media/claude-code-best-practices/map.png new file mode 100644 index 000000000..6f780fc5a Binary files /dev/null and b/docs/blog/assets/media/claude-code-best-practices/map.png differ diff --git a/docs/blog/assets/media/claude-code-best-practices/motion.css b/docs/blog/assets/media/claude-code-best-practices/motion.css new file mode 100644 index 000000000..1dea8486c --- /dev/null +++ b/docs/blog/assets/media/claude-code-best-practices/motion.css @@ -0,0 +1,13 @@ +/* Bright coded scenes for "Claude Code Best Practices". Pairs with motion.js. */ +.prose figure.mg { margin: 42px 0; } +.prose figure.mg img, .mg .mg-svg { display: block; width: 100%; height: auto; border: 1px solid #1A1320; border-radius: 18px; background: #fff; } +.mg figcaption { font: 500 13px/1.5 "Space Grotesk", system-ui, sans-serif; color: #6B5878; padding-top: 10px; text-align: center; } +.mg .mg-svg { touch-action: manipulation; -webkit-tap-highlight-color: transparent; } +.mg .mg-svg text { -webkit-user-select: none; user-select: none; pointer-events: none; } +.mg .mg-hit { cursor: pointer; outline: none; } +.mg [role="slider"] { cursor: grab; touch-action: none; } +.mg .mg-ring { stroke-opacity: 0; transition: stroke-opacity 0.15s; } +.mg .mg-hit:focus-visible .mg-ring { stroke-opacity: 1; } +.mg [role="slider"]:focus-visible circle:nth-child(2) { stroke-width: 5; } +@media (hover: hover) { .mg .mg-hit:hover .mg-ring { stroke-opacity: 0.5; } } +@media (prefers-reduced-motion: reduce) { .mg .mg-ring { transition: none; } } diff --git a/docs/blog/assets/media/claude-code-best-practices/motion.js b/docs/blog/assets/media/claude-code-best-practices/motion.js new file mode 100644 index 000000000..1b6e72981 --- /dev/null +++ b/docs/blog/assets/media/claude-code-best-practices/motion.js @@ -0,0 +1,297 @@ +/* Bright coded scenes for "Claude Code Best Practices". Vanilla JS, no library. +
holds a still image; this script swaps in a live SVG. + Plays only in view, honours reduced motion, "?mgstill" freezes each scene on its still frame. */ +(() => { + const NS = "http://www.w3.org/2000/svg"; + const INK = "#1A1320", YEL = "#FFCA54", BLUE = "#6C8EF5", LIL = "#B69CFF", MINT = "#7FD8AE", SKY = "#9AD8FF", SOFT = "#F1ECF9", MUTE = "#7A6A88"; + const SANS = '"Space Grotesk", system-ui, sans-serif', MONO = '"JetBrains Mono", ui-monospace, monospace'; + const el = (t, a = {}, p) => { const n = document.createElementNS(NS, t); for (const k in a) n.setAttribute(k, a[k]); if (p) p.appendChild(n); return n; }; + const txt = (p, x, y, s, a = {}) => { const n = el("text", Object.assign({ x, y, "font-family": SANS, "font-size": 24, "font-weight": 600, fill: INK }, a), p); n.textContent = s; return n; }; + const cl = (v, a = 0, b = 1) => Math.max(a, Math.min(b, v)); + const seg = (t, a, b) => cl((t - a) / (b - a)); + const oc = (x) => 1 - Math.pow(1 - x, 3); + const ob = (x) => { const c = 1.70158; return 1 + (c + 1) * Math.pow(x - 1, 3) + c * Math.pow(x - 1, 2); }; + const io = (x) => (x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2); + const tf = (n, x, y, s = 1, r = 0) => n.setAttribute("transform", `translate(${x} ${y}) rotate(${r}) scale(${s})`); + const button = (g, label, fn) => { + g.setAttribute("role", "button"); g.setAttribute("tabindex", "0"); g.setAttribute("aria-label", label); g.classList.add("mg-hit"); + g.addEventListener("click", fn); + g.addEventListener("keydown", (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); fn(); } }); + }; + + let UID = 0; + /* shrink a line of text until it fits its box */ + const fit = (n, max) => { const w = n.getComputedTextLength(); if (w > max) n.setAttribute("font-size", ((parseFloat(n.getAttribute("font-size")) * max) / w).toFixed(1)); }; + /* the soft caption strip along the bottom; returns a setter */ + const strip = (svg, y, x0 = 30, w = 740) => { + el("rect", { x: x0, y, width: w, height: 48, rx: 24, fill: SOFT }, svg); + const n = txt(svg, x0 + w / 2, y + 31, "", { "text-anchor": "middle", "font-size": 21, "font-weight": 500 }); + return (s) => { if (n.textContent !== s) { n.textContent = s; n.setAttribute("font-size", 21); fit(n, w - 44); } }; + }; + /* a rounded label sized to its words, centred on 0,0 of its group */ + const pill = (g, s, fill, size = 22) => { + const r = el("rect", { y: -21, height: 42, rx: 21, fill, stroke: INK, "stroke-width": 3 }, g), t = txt(g, 0, size * 0.36, s, { "text-anchor": "middle", "font-size": size, "font-weight": 700 }); + const w = t.getComputedTextLength() + 32; r.setAttribute("x", -w / 2); r.setAttribute("width", w); + return w; + }; + + const SC = {}; + + /* ---- 1. a sieve: the check shakes, sand falls through, the three bad pebbles stay caught ---- */ + SC.sieve = { + dur: 12, still: 9.8, + build(svg, api) { + const id = "mgsv" + ++UID, defs = el("defs", {}, svg); + txt(svg, 44, 62, "Give Claude a way to verify its work", { "font-size": 32, "font-weight": 700 }); + const CX = 380, CY = 228, RX = 268, RY = 102, TY = 452; + const UNDER = `M${CX - RX} ${CY} C${CX - RX + 14} ${CY + 190} ${CX + RX - 14} ${CY + 190} ${CX + RX} ${CY} Z`; + const IN = `M${CX - RX} ${CY} A${RX} ${RY} 0 1 0 ${CX + RX} ${CY} A${RX} ${RY} 0 1 0 ${CX - RX} ${CY} Z`; + el("path", { d: UNDER }, el("clipPath", { id: id + "u" }, defs)); + el("path", { d: IN }, el("clipPath", { id: id + "i" }, defs)); + /* the tray and the pile of sand that grows on it */ + el("rect", { x: 170, y: TY, width: 420, height: 30, rx: 12, fill: BLUE, stroke: INK, "stroke-width": 3 }, svg); + const pile = el("path", { fill: YEL, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, svg); + const heap = [0.16, 0.27, 0.36, 0.45, 0.55, 0.64, 0.73, 0.84].map((u, i) => ({ u, d: 6 + (i % 3) * 5, n: el("circle", { r: 3 + (i % 2), fill: "#fff", stroke: INK, "stroke-width": 1.5 }, svg) })); + const fall = el("g", {}, svg); + const grains = Array.from({ length: 44 }, (_, i) => ({ n: el("circle", { r: 3 + (i % 3), fill: YEL, stroke: INK, "stroke-width": 1.5 }, fall), x: 262 + ((i * 53) % 236), ph: (i * 0.379) % 1 })); + /* the sieve itself */ + const sv = el("g", {}, svg); + const hand = el("g", { transform: `translate(${CX + RX - 8} ${CY - 8}) rotate(-12)` }, sv); + el("rect", { x: 0, y: -12, width: 130, height: 24, rx: 12, fill: YEL, stroke: INK, "stroke-width": 3 }, hand); + el("circle", { cx: 112, r: 6, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, hand); + const und = el("g", {}, sv); + el("path", { d: UNDER, fill: SOFT }, und); + let hd = ""; + for (let x = CX - RX - 200; x <= CX + RX + 40; x += 18) hd += `M${x} ${CY} l200 200 M${x + 220} ${CY} l-200 200 `; + el("path", { d: hd, stroke: LIL, "stroke-width": 2, fill: "none", "clip-path": `url(#${id}u)` }, und); + el("path", { d: UNDER, fill: "none", stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, und); + const ins = el("g", {}, sv); + el("path", { d: IN, fill: "#fff" }, ins); + let md = ""; + for (let x = CX - RX + 22; x < CX + RX; x += 22) md += `M${x} ${CY - RY} V${CY + RY} `; + for (let y = CY - RY + 14; y < CY + RY; y += 14) md += `M${CX - RX} ${y} H${CX + RX} `; + el("path", { d: md, stroke: LIL, "stroke-width": 1.5, fill: "none", "clip-path": `url(#${id}i)` }, ins); + const sand = Array.from({ length: 54 }, (_, i) => { + const a = i * 2.39996, r = Math.sqrt((i + 0.5) / 54); + return el("circle", { cx: (CX + Math.cos(a) * r * RX * 0.86).toFixed(1), cy: (CY + Math.sin(a) * r * RY * 0.78).toFixed(1), r: 4 + (i % 2), fill: YEL, stroke: INK, "stroke-width": 1.5 }, ins); + }); + const pebs = [["failing test", 262, 200, -5], ["build error", 502, 198, 4], ["eslint", 382, 282, -2]].map(([s, x, y, r], i) => { + const g = el("g", {}, sv); + el("path", { d: "M-92 4 C-96 -26 -50 -36 0 -35 C52 -36 96 -24 92 2 C90 30 44 37 -4 36 C-52 36 -90 30 -92 4 Z", fill: MUTE, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, g); + txt(g, 0, 8, s, { "text-anchor": "middle", "font-size": 23, "font-weight": 700, fill: "#fff" }); + return { g, x, y, r, i }; + }); + el("ellipse", { cx: CX, cy: CY, rx: RX, ry: RY, fill: "none", stroke: INK, "stroke-width": 18 }, sv); + el("ellipse", { cx: CX, cy: CY, rx: RX, ry: RY, fill: "none", stroke: YEL, "stroke-width": 12 }, sv); + const ring = el("ellipse", { cx: CX, cy: CY + 40, rx: RX + 24, ry: RY + 104, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 7", class: "mg-ring" }, sv); + ring.parentNode.insertBefore(ring, ring.parentNode.firstChild); + let g0 = -99; + button(sv, "Shake the check again: the failing test, the build error and eslint stay caught", () => { g0 = api.now(); api.poke(); }); + const cap = strip(svg, 518); + return (t, now) => { + const out = seg(t, 11.3, 11.8), dg = now - g0, gust = dg >= 0 && dg < 1.4 ? Math.sin((Math.PI * dg) / 1.4) : 0; + const env = seg(t, 1.9, 2.3) * (1 - seg(t, 8.2, 8.7)), sh = Math.max(env, gust); + const dx = 11 * env * Math.sin(t * 22) + 18 * gust * Math.sin(now * 24); + sv.setAttribute("transform", `translate(${dx.toFixed(2)} 0) rotate(${(1.2 * env * Math.sin(t * 22 + 1) + 2 * gust * Math.sin(now * 24 + 1)).toFixed(2)} ${CX} ${CY})`); + const dr = seg(t, 2.2, 8.4), fin = seg(t, 0.3, 0.7) * (1 - out); + sand.forEach((s, i) => s.setAttribute("opacity", i / sand.length >= dr ? fin : 0)); + grains.forEach((g) => { + const p = (t * 0.95 + g.ph) % 1, land = TY - 4 - 50 * io(seg(t, 2.3, 8.6)) * (1 - Math.abs(g.x - CX) / 210); + g.n.setAttribute("cx", (g.x + dx * (1 - p) * 0.6).toFixed(1)); + g.n.setAttribute("cy", (CY + 130 + p * p * (land - CY - 130)).toFixed(1)); + g.n.setAttribute("opacity", env * (p < 0.9 ? 1 : (1 - p) * 10)); + }); + const h = 28 * io(seg(t, 2.3, 8.6)); + pile.setAttribute("d", `M196 ${TY} Q${CX} ${TY - 2 * h} 564 ${TY} Z`); + pile.setAttribute("opacity", h > 0.5 ? 1 - out : 0); + heap.forEach((g) => { const u = g.u, x = (1 - u) * (1 - u) * 196 + 2 * u * (1 - u) * CX + u * u * 564, y = TY - 4 * h * u * (1 - u) + g.d; g.n.setAttribute("cx", x.toFixed(1)); g.n.setAttribute("cy", y.toFixed(1)); g.n.setAttribute("opacity", h > 14 && y < TY - 5 ? 1 - out : 0); }); + pebs.forEach((p) => { + const a = 0.5 + p.i * 0.25, k = ob(seg(t, a, a + 0.6)), b = 8.9 + p.i * 0.3; + const pop = 1 + 0.14 * Math.sin(Math.PI * seg(t, b, b + 0.35)); + tf(p.g, p.x + sh * 3 * Math.sin(now * 19 + p.i * 2), p.y - 200 * (1 - k), pop, p.r + sh * 5 * Math.sin(now * 21 + p.i)); + p.g.setAttribute("opacity", seg(t, a, a + 0.12) * (1 - out)); + }); + cap(t < 2.2 || t >= 11.2 ? "Give Claude a check it can run" : t < 6 ? "tests, a build, or a screenshot to compare" : "show evidence, such as test output, rather than assert success"); + }; + }, + }; + + /* ---- 2. a treasure map: the dotted route is drawn first, then a solid line walks it step by step ---- */ + SC.map = { + dur: 12, still: 9.8, + build(svg, api) { + const id = "mgmp" + ++UID, defs = el("defs", {}, svg); + txt(svg, 44, 62, "Explore first, then plan, then code", { "font-size": 28, "font-weight": 700 }); + const key = el("g", {}, svg); + el("rect", { x: 598, y: 26, width: 172, height: 62, rx: 16, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 6", class: "mg-ring" }, key); + const kc = el("g", {}, key); + el("rect", { x: 606, y: 34, width: 156, height: 46, rx: 12, fill: SKY, stroke: INK, "stroke-width": 3 }, kc); + txt(kc, 684, 64, "Shift+Tab", { "text-anchor": "middle", "font-family": MONO, "font-size": 21, "font-weight": 700 }); + let pr = -99; + button(key, "Shift+Tab: plan mode on, the route is drawn again", () => { pr = api.now(); api.seek(2.2); }); + const PX0 = 62, PX1 = 738, PY0 = 108, PY1 = 486, PW = PX1 - PX0, MID = 400; + const clipR = el("rect", { y: PY0 - 4, height: PY1 - PY0 + 8 }, el("clipPath", { id: id + "c" }, defs)); + const paper = el("g", { "clip-path": `url(#${id}c)` }, svg); + el("rect", { x: PX0, y: PY0, width: PW, height: PY1 - PY0, fill: SOFT, stroke: INK, "stroke-width": 3 }, paper); + /* a little land to cross: waves, a lake, hills, trees */ + [[104, 150], [150, 176], [96, 204]].forEach(([x, y]) => el("path", { d: `M${x} ${y} q12 -10 24 0 t24 0 t24 0`, stroke: SKY, "stroke-width": 4, fill: "none", "stroke-linecap": "round" }, paper)); + el("ellipse", { cx: 440, cy: 178, rx: 64, ry: 30, fill: SKY, stroke: INK, "stroke-width": 3 }, paper); + [[628, 468, 38, 54], [688, 468, 30, 40]].forEach(([x, y, w, hh]) => el("path", { d: `M${x - w} ${y} L${x} ${y - hh} L${x + w} ${y} Z`, fill: MINT, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, paper)); + [[250, 190], [282, 206], [520, 142]].forEach(([x, y]) => { el("path", { d: `M${x} ${y} V${y + 22}`, stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, paper); el("circle", { cx: x, cy: y - 4, r: 15, fill: MINT, stroke: INK, "stroke-width": 3 }, paper); }); + const D = "M132 420 C190 330 240 290 300 300 C380 312 410 420 500 404 C580 390 600 262 652 194"; + const route = el("path", { d: D, fill: "none", stroke: "none" }, paper); + const L = route.getTotalLength(), N = Math.floor(L / 17); + const solid = el("path", { d: D, fill: "none", stroke: BLUE, "stroke-width": 11, "stroke-linecap": "round", "stroke-dasharray": `${L} ${L}` }, paper); + const dots = Array.from({ length: N }, (_, i) => { const p = route.getPointAtLength(((i + 0.5) * L) / N); return el("circle", { cx: p.x.toFixed(1), cy: p.y.toFixed(1), r: 3.6, fill: INK }, paper); }); + const ex = el("g", {}, paper); + el("circle", { r: 15, fill: "none", stroke: LIL, "stroke-width": 3, "stroke-dasharray": "4 6" }, ex); + const start = el("g", {}, paper); + el("circle", { r: 13, fill: MINT, stroke: INK, "stroke-width": 3 }, start); + const X = el("g", {}, paper); + el("path", { d: "M-17 -17 L17 17 M17 -17 L-17 17", stroke: INK, "stroke-width": 9, "stroke-linecap": "round" }, X); + const LB = [["explore", 222, 452, 1.5, YEL], ["plan", 300, 256, 2.6, "#fff"], ["implement", 482, 454, 5.7, "#fff"], ["commit", 652, 140, 8.9, MINT]].map(([s, x, y, a, f]) => { + const g = el("g", {}, paper); pill(g, s, f, 24); return { g, x, y, a }; + }); + const stops = [[300, 300, 2.6], [500, 404, 5.7]].map(([x, y, a]) => ({ g: el("circle", { cx: x, cy: y, r: 9, fill: "#fff", stroke: INK, "stroke-width": 3 }, paper), a })); + const rolls = [0, 1].map(() => { + const g = el("g", {}, svg); + el("rect", { x: -16, y: PY0 - 14, width: 32, height: PY1 - PY0 + 28, rx: 16, fill: YEL, stroke: INK, "stroke-width": 3 }, g); + el("path", { d: `M0 ${PY0 + 2} V${PY1 - 2}`, stroke: INK, "stroke-width": 2, "stroke-linecap": "round" }, g); + return g; + }); + const cap = strip(svg, 520); + return (t, now) => { + const out = seg(t, 11.3, 11.8), w = PW * (io(seg(t, 0.15, 1.3)) - io(seg(t, 11.2, 11.85))); + clipR.setAttribute("x", MID - w / 2); clipR.setAttribute("width", Math.max(0, w)); + rolls.forEach((g, i) => tf(g, MID + (i ? 1 : -1) * Math.max(w / 2, 16), 0)); + const S0 = route.getPointAtLength(0), E = route.getPointAtLength(L); + tf(start, S0.x, S0.y, ob(seg(t, 1.3, 1.7))); + const ep = ((t - 1.4) % 0.9) / 0.9, eo = t > 1.4 && t < 3.2 ? 1 - ep : 0; + tf(ex, S0.x, S0.y, 1 + 2.4 * ep); ex.setAttribute("opacity", eo); + const kd = io(seg(t, 2.2, 5)); + dots.forEach((d, i) => d.setAttribute("opacity", (i + 1) / N <= kd + 0.001 ? 1 : 0)); + tf(X, E.x, E.y, cl(ob(seg(t, 4.9, 5.3)), 0, 1.3) * (1 + 0.15 * Math.sin(Math.PI * seg(t, 8.8, 9.2)))); + const p = seg(t, 5.6, 8.8), st = 6, k2 = p >= 1 ? 1 : (Math.floor(p * st) + io(seg((p * st) % 1, 0, 0.7))) / st; + solid.setAttribute("stroke-dashoffset", (L * (1 - k2)).toFixed(1)); solid.setAttribute("opacity", k2 > 0.002 ? 1 : 0); + LB.forEach((b) => { const k = seg(t, b.a, b.a + 0.4); tf(b.g, b.x, b.y, 0.5 + 0.5 * cl(ob(k), 0, 1.2)); b.g.setAttribute("opacity", k > 0 ? 1 : 0); }); + stops.forEach((s) => s.g.setAttribute("opacity", seg(t, s.a, s.a + 0.2))); + const pk = now - pr; kc.setAttribute("transform", `translate(0 ${pk >= 0 && pk < 0.22 ? 3 : 0})`); + cap(t < 2.2 || t >= 11.2 ? "Separate research and planning from implementation" : t < 5.6 ? "Shift+Tab until the status bar shows plan mode on" : t < 9 ? "explore, plan, implement, commit" : "If you could describe the diff in one sentence, skip the plan"); + }; + }, + }; + + /* ---- 3. a clipboard checklist: four short lines tick, the long fifth line is struck out ---- */ + SC.clipboard = { + dur: 12, still: 10, + build(svg) { + txt(svg, 44, 62, "What should go in CLAUDE.md?", { "font-size": 32, "font-weight": 700 }); + el("rect", { x: 96, y: 98, width: 608, height: 470, rx: 26, fill: YEL, stroke: INK, "stroke-width": 3 }, svg); + el("rect", { x: 124, y: 134, width: 552, height: 408, rx: 12, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + el("rect", { x: 320, y: 82, width: 160, height: 46, rx: 14, fill: BLUE, stroke: INK, "stroke-width": 3 }, svg); + el("circle", { cx: 400, cy: 100, r: 8, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + const RY = [188, 246, 304, 362, 420]; + const rows = ["Bash commands", "code style", "testing instructions", "repo etiquette", null].map((s, i) => { + const y = RY[i], g = el("g", {}, svg); + const box = el("rect", { x: 152, y: y - 19, width: 38, height: 38, rx: 9, fill: "#fff", stroke: INK, "stroke-width": 3 }, g); + const tick = el("path", { d: `M${161} ${y + 1} L${168} ${y + 9} L${182} ${y - 9}`, stroke: INK, "stroke-width": 4, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-dasharray": "34 34", "stroke-dashoffset": 34 }, g); + const body = el("g", {}, g); + if (s) txt(body, 214, y + 10, s, { "font-size": 29, "font-weight": 700 }); + else [[214, 118], [346, 92], [452, 132], [598, 44]].forEach(([x, w]) => el("rect", { x, y: y - 9, width: w, height: 18, rx: 9, fill: LIL }, body)); + return { g, box, tick, body, y }; + }); + const halo = el("rect", { x: 140, y: RY[4] - 31, width: 524, height: 62, rx: 16, fill: "none", stroke: LIL, "stroke-width": 3, "stroke-dasharray": "5 7" }, svg); + const strike = el("path", { d: `M144 ${RY[4]} H660`, stroke: INK, "stroke-width": 5, "stroke-linecap": "round", "stroke-dasharray": "516 516" }, svg); + const qs = el("rect", { x: 148, y: 462, width: 504, height: 56, rx: 16, fill: SOFT }, svg); + const q = txt(svg, 400, 497, "Would removing this cause Claude to make mistakes?", { "text-anchor": "middle", "font-size": 22, "font-weight": 600 }); + fit(q, 470); + const a = txt(svg, 400, 500, "under 200 lines per file", { "text-anchor": "middle", "font-size": 28, "font-weight": 700 }); + return (t) => { + const out = seg(t, 11.3, 11.8); + rows.forEach((r, i) => { + const k = oc(seg(t, 0.8 + i * 0.45, 1.25 + i * 0.45)); + r.body.setAttribute("transform", `translate(${-24 * (1 - k)} 0)`); + r.body.setAttribute("opacity", k); + r.box.setAttribute("opacity", k); + if (i < 4) { + const tk = seg(t, 3.6 + i * 0.6, 3.95 + i * 0.6); + r.tick.setAttribute("stroke-dashoffset", (34 * (1 - tk)).toFixed(1)); + r.box.setAttribute("fill", tk > 0.3 ? MINT : "#fff"); + r.g.setAttribute("opacity", 1 - out); + } else r.tick.setAttribute("opacity", 0); + }); + const qa = seg(t, 6.2, 6.5) * (1 - seg(t, 8.5, 8.7)), ans = seg(t, 8.7, 9) * (1 - out); + q.setAttribute("opacity", qa); a.setAttribute("opacity", ans); qs.setAttribute("opacity", Math.max(qa, ans)); + halo.setAttribute("opacity", t > 6.2 && t < 7.4 ? 0.55 + 0.45 * Math.sin((t - 6.2) * 9) : 0); + const sk = io(seg(t, 7.3, 7.8)); + strike.setAttribute("stroke-dashoffset", (516 * (1 - sk)).toFixed(1)); strike.setAttribute("opacity", sk > 0 ? 1 - out : 0); + rows[4].g.setAttribute("opacity", (1 - 0.72 * seg(t, 7.8, 8.6)) * (1 - out)); + }; + }, + }; + + /* ---- title card, 16 by 9: a small bonsai, kept short by pruning ---- */ + SC.hero = { + w: 960, h: 540, dur: 1, still: 0, + build(svg) { + txt(svg, 66, 130, "9 Habits From Anthropic's Docs", { "font-size": 27, "font-weight": 500, fill: MUTE }); + txt(svg, 62, 218, "Claude Code", { "font-size": 70, "font-weight": 700 }); + txt(svg, 62, 298, "Best Practices", { "font-size": 70, "font-weight": 700 }); + el("path", { d: "M68 332 H500", stroke: YEL, "stroke-width": 14, "stroke-linecap": "round" }, svg); + el("rect", { x: 62, y: 374, width: 318, height: 56, rx: 28, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + txt(svg, 221, 411, "Checked 15 Oct 2026", { "text-anchor": "middle", "font-size": 25, "font-weight": 700 }); + /* the bonsai */ + const limb = (d, w) => { el("path", { d, stroke: INK, "stroke-width": w + 6, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, svg); el("path", { d, stroke: MUTE, "stroke-width": w, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, svg); }; + limb("M780 412 C764 364 706 346 722 296 C734 258 800 266 810 222 C815 198 800 182 792 168", 24); + limb("M724 300 C712 290 704 292 692 282", 11); + limb("M806 236 C826 232 842 240 858 232", 10); + const pad = (cx, cy, cs, f) => { + [0, 1].forEach((pass) => { + cs.forEach(([x, y, r]) => el("circle", { cx: cx + x, cy: cy + y, r: pass ? r : r + 3, fill: pass ? f : INK }, svg)); + el("rect", { x: cx - 58 - (pass ? 0 : 3), y: cy - (pass ? 0 : 3), width: 116 + (pass ? 0 : 6), height: 26 + (pass ? 0 : 6), rx: 13 + (pass ? 0 : 3), fill: pass ? f : INK }, svg); + }); + }; + pad(690, 252, [[-34, 4, 24], [-4, -12, 31], [30, 0, 25]], MINT); + pad(858, 206, [[-30, 6, 22], [0, -10, 28], [32, 4, 22]], MINT); + pad(790, 140, [[-44, 8, 28], [-8, -14, 38], [34, 2, 30]], MINT); + el("rect", { x: 666, y: 404, width: 228, height: 22, rx: 8, fill: BLUE, stroke: INK, "stroke-width": 3 }, svg); + el("path", { d: "M680 426 H880 L860 480 H700 Z", fill: BLUE, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, svg); + [712, 832].forEach((x) => el("rect", { x, y: 480, width: 18, height: 12, rx: 3, fill: INK }, svg)); + el("ellipse", { cx: 780, cy: 404, rx: 96, ry: 10, fill: MINT, stroke: INK, "stroke-width": 3 }, svg); + el("path", { d: "M624 500 H906", stroke: LIL, "stroke-width": 3, "stroke-linecap": "round" }, svg); + [[620, 128, 15, SKY], [904, 336, 17, YEL], [600, 400, 12, LIL]].forEach(([x, y, r, col]) => el("path", { d: `M0 ${-r} Q0 0 ${r} 0 Q0 0 0 ${r} Q0 0 ${-r} 0 Q0 0 0 ${-r} Z`, fill: col, stroke: INK, "stroke-width": 2.5, "stroke-linejoin": "round", transform: `translate(${x} ${y})` }, svg)); + return () => {}; + }, + }; + + /* ---- player ---- */ + const q = new URLSearchParams(location.search), frozen = q.has("mgstill"), fixed = parseFloat(q.get("mgstill")); + const calm = matchMedia("(prefers-reduced-motion: reduce)").matches; + const mount = (fig) => { + const sc = SC[fig.dataset.scene], img = fig.querySelector("img"); if (!sc || !img) return; + const W = sc.w || 800, H = sc.h || 600; + const svg = el("svg", { viewBox: `0 0 ${W} ${H}`, class: "mg-svg", role: "group", "aria-label": img.alt || "" }); + svg.style.aspectRatio = `${W} / ${H}`; + el("rect", { width: W, height: H, fill: "#fff" }, svg); + img.replaceWith(svg); + let off = 0, raf = 0, seen = false, held = calm; + const base = performance.now(), clock = () => (performance.now() - base) / 1000; + let draw = () => {}; + const frame = () => { const now = clock(); draw(held ? sc.still : (((now + off) % sc.dur) + sc.dur) % sc.dur, now); }; + const tick = () => { cancelAnimationFrame(raf); frame(); if (seen && !held) raf = requestAnimationFrame(tick); }; + const api = { now: clock, seek: (s) => { off = s - clock(); held = false; tick(); }, poke: () => { held = false; tick(); } }; + draw = sc.build(svg, api); + if (frozen) { draw(isNaN(fixed) ? sc.still : fixed, 0); return; } + new IntersectionObserver(([e]) => { + seen = e.isIntersecting; + if (seen) { tick(); if (!fig.dataset.sent && window.posthog) { fig.dataset.sent = 1; window.posthog.capture("blog_scene_view", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } } + else cancelAnimationFrame(raf); + }, { threshold: 0.25 }).observe(fig); + svg.addEventListener("click", () => { if (!fig.dataset.hit && window.posthog) { fig.dataset.hit = 1; window.posthog.capture("blog_scene_interact", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } }); + frame(); + }; + const go = () => document.querySelectorAll("figure.mg[data-scene]").forEach(mount); + const fonts = document.fonts && document.fonts.load ? Promise.all([document.fonts.load('700 32px "Space Grotesk"'), document.fonts.load('500 20px "Space Grotesk"'), document.fonts.load('700 20px "JetBrains Mono"')]).catch(() => {}) : Promise.resolve(); + const start = () => fonts.then(go, go); + if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", start); else start(); +})(); diff --git a/docs/blog/assets/media/claude-code-best-practices/sieve.png b/docs/blog/assets/media/claude-code-best-practices/sieve.png new file mode 100644 index 000000000..b0c315e08 Binary files /dev/null and b/docs/blog/assets/media/claude-code-best-practices/sieve.png differ diff --git a/docs/blog/assets/media/opencode-alternatives/door.png b/docs/blog/assets/media/opencode-alternatives/door.png new file mode 100644 index 000000000..419867396 Binary files /dev/null and b/docs/blog/assets/media/opencode-alternatives/door.png differ diff --git a/docs/blog/assets/media/opencode-alternatives/hero.png b/docs/blog/assets/media/opencode-alternatives/hero.png new file mode 100644 index 000000000..10b953e16 Binary files /dev/null and b/docs/blog/assets/media/opencode-alternatives/hero.png differ diff --git a/docs/blog/assets/media/opencode-alternatives/motion.css b/docs/blog/assets/media/opencode-alternatives/motion.css new file mode 100644 index 000000000..11a9a8f2e --- /dev/null +++ b/docs/blog/assets/media/opencode-alternatives/motion.css @@ -0,0 +1,13 @@ +/* Bright coded scenes for "OpenCode Alternatives: 7 Coding Agents to Switch To". Pairs with motion.js. */ +.prose figure.mg { margin: 42px 0; } +.prose figure.mg img, .mg .mg-svg { display: block; width: 100%; height: auto; border: 1px solid #1A1320; border-radius: 18px; background: #fff; } +.mg figcaption { font: 500 13px/1.5 "Space Grotesk", system-ui, sans-serif; color: #6B5878; padding-top: 10px; text-align: center; } +.mg .mg-svg { touch-action: manipulation; -webkit-tap-highlight-color: transparent; } +.mg .mg-svg text { -webkit-user-select: none; user-select: none; pointer-events: none; } +.mg .mg-hit { cursor: pointer; outline: none; } +.mg [role="slider"] { cursor: grab; touch-action: none; } +.mg .mg-ring { stroke-opacity: 0; transition: stroke-opacity 0.15s; } +.mg .mg-hit:focus-visible .mg-ring { stroke-opacity: 1; } +.mg [role="slider"]:focus-visible circle:nth-child(2) { stroke-width: 5; } +@media (hover: hover) { .mg .mg-hit:hover .mg-ring { stroke-opacity: 0.5; } } +@media (prefers-reduced-motion: reduce) { .mg .mg-ring { transition: none; } } diff --git a/docs/blog/assets/media/opencode-alternatives/motion.js b/docs/blog/assets/media/opencode-alternatives/motion.js new file mode 100644 index 000000000..a4679debd --- /dev/null +++ b/docs/blog/assets/media/opencode-alternatives/motion.js @@ -0,0 +1,311 @@ +/* Bright coded scenes for "OpenCode Alternatives: 7 Coding Agents to Switch To". Vanilla JS, no library. +
holds a still image; this script swaps in a live SVG. + Plays only in view, honours reduced motion, "?mgstill" freezes each scene on its still frame. */ +(() => { + const NS = "http://www.w3.org/2000/svg"; + const INK = "#1A1320", YEL = "#FFCA54", BLUE = "#6C8EF5", LIL = "#B69CFF", MINT = "#7FD8AE", SKY = "#9AD8FF", SOFT = "#F1ECF9", MUTE = "#7A6A88"; + const SANS = '"Space Grotesk", system-ui, sans-serif', MONO = '"JetBrains Mono", ui-monospace, monospace'; + const el = (t, a = {}, p) => { const n = document.createElementNS(NS, t); for (const k in a) n.setAttribute(k, a[k]); if (p) p.appendChild(n); return n; }; + const txt = (p, x, y, s, a = {}) => { const n = el("text", Object.assign({ x, y, "font-family": SANS, "font-size": 24, "font-weight": 600, fill: INK }, a), p); n.textContent = s; return n; }; + const cl = (v, a = 0, b = 1) => Math.max(a, Math.min(b, v)); + const seg = (t, a, b) => cl((t - a) / (b - a)); + const oc = (x) => 1 - Math.pow(1 - x, 3); + const ob = (x) => { const c = 1.70158; return 1 + (c + 1) * Math.pow(x - 1, 3) + c * Math.pow(x - 1, 2); }; + const io = (x) => (x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2); + const tf = (n, x, y, s = 1, r = 0) => n.setAttribute("transform", `translate(${x} ${y}) rotate(${r}) scale(${s})`); + const button = (g, label, fn) => { + g.setAttribute("role", "button"); g.setAttribute("tabindex", "0"); g.setAttribute("aria-label", label); g.classList.add("mg-hit"); + g.addEventListener("click", fn); + g.addEventListener("keydown", (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); fn(); } }); + }; + const strip = (svg, y, w = 680) => { + el("rect", { x: (800 - w) / 2, y, width: w, height: 48, rx: 24, fill: SOFT }, svg); + return txt(svg, 400, y + 32, "", { "text-anchor": "middle", "font-size": 22, "font-weight": 600 }); + }; + const lerp = (a, b, k) => a + (b - a) * k; + + const SC = {}; + + /* ---- 1. a glass revolving door: OpenCode goes in on the left, seven alternatives come round on the right ---- */ + SC.door = { + dur: 12, still: 10.4, + build(svg, api) { + txt(svg, 40, 62, "Why look for an OpenCode alternative now?", { "font-size": 30, "font-weight": 700 }); + const DX = 332, R = 146, RY = 30, RT = 12, TOP = 170, BOT = 440, CY = 316, W = Math.PI / 4; + const pyT = (a) => TOP - RT * Math.sin(a), pyB = (a) => BOT + RY * Math.sin(a); + const poly = (a0, a1, f) => { let d = ""; for (let i = 0; i <= 16; i++) { const a = ((a0 + ((a1 - a0) * i) / 16) * Math.PI) / 180; d += `${i ? "L" : ""}${(DX + R * Math.cos(a)).toFixed(1)} ${f(a).toFixed(1)} `; } return d; }; + el("path", { d: `M30 ${BOT + 36} H770`, stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + /* the ghost of the card that went in */ + const ghost = el("g", { transform: `translate(104 ${CY})` }, svg); + el("rect", { x: -72, y: -22, width: 144, height: 44, rx: 10, fill: "#fff", stroke: LIL, "stroke-width": 3, "stroke-dasharray": "6 8", "stroke-linecap": "round" }, ghost); + txt(ghost, 0, 8, "OpenCode", { "text-anchor": "middle", "font-size": 23, "font-weight": 700, fill: MUTE }); + const door = el("g", {}, svg); + const CR = R + 14, CH = 38; + el("ellipse", { cx: DX, cy: BOT, rx: CR, ry: RY + 5, fill: SOFT, stroke: INK, "stroke-width": 3 }, door); + el("ellipse", { cx: DX, cy: TOP, rx: CR, ry: RT + 3, fill: SOFT, stroke: INK, "stroke-width": 3 }, door); + el("path", { d: `M${poly(225, 315, pyT)}L${poly(315, 225, pyB)}Z`, fill: SKY, "fill-opacity": 0.22 }, door); + el("path", { d: `M${poly(200, 340, pyB)}`, fill: "none", stroke: LIL, "stroke-width": 2.5, "stroke-linecap": "round" }, door); + const back = el("g", {}, door); + el("rect", { x: DX - 7, y: TOP - 4, width: 14, height: BOT - TOP + 4, rx: 7, fill: BLUE, stroke: INK, "stroke-width": 3 }, door); + const cards = el("g", { "pointer-events": "none" }, door); + const front = el("g", {}, door); + /* the canopy, seen from just below */ + el("path", { d: `M${DX - CR} ${TOP - CH} A${CR} ${RT + 3} 0 0 1 ${DX + CR} ${TOP - CH} V${TOP} A${CR} ${RT + 3} 0 0 0 ${DX - CR} ${TOP} Z`, fill: YEL, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, door); + const wings = [0, 1, 2, 3].map(() => { + const g = el("g", {}); + const pane = el("path", { fill: SKY, "fill-opacity": 0.34, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, g); + const edge = [el("path", { stroke: INK, "stroke-width": 11, "stroke-linecap": "round" }, g), el("path", { stroke: BLUE, "stroke-width": 5, "stroke-linecap": "round" }, g)]; + return { g, pane, edge }; + }); + el("rect", { x: DX - CR - 12, y: TOP - CH - RT - 16, width: 2 * CR + 24, height: BOT + RY + 22 - (TOP - CH - RT - 16), rx: 24, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, class: "mg-ring" }, door); + let pushes = 0, lastPush = -99; + button(door, "Push the revolving door round", () => { pushes++; lastPush = api.now(); api.poke(); }); + /* the cards */ + const card = (s, fill, w) => { + const g = el("g", {}, cards); + el("rect", { x: -w / 2, y: -21, width: w, height: 42, rx: 10, fill, stroke: INK, "stroke-width": 3 }, g); + txt(g, 0, 8, s, { "text-anchor": "middle", "font-size": 23, "font-weight": 700 }); + return g; + }; + const oc0 = card("OpenCode", LIL, 144); + const ALT = [["Claude Code", YEL], ["Codex CLI", SKY], ["Gemini CLI", MINT], ["Kilo Code", LIL], ["Crush", YEL], ["Pi", SKY], ["Goose", MINT]]; + const SX = 628, SY0 = 120, SP = 54; + const alts = ALT.map(([s, col], i) => ({ g: card(s, col, 210), t0: 2.4 + i * 1.0, y: SY0 + i * SP })); + const cap = strip(svg, 514, 700); + return (t, now) => { + const out = seg(t, 11.4, 11.9); + const pk = seg(now - lastPush, 0, 0.8), push = pushes ? (pushes - 1 + oc(pk)) * (Math.PI / 2) : 0; + const th = W * t + push + 0.35; + wings.forEach((w, i) => { + const ang = th + (i * Math.PI) / 2, c = Math.cos(ang), s = Math.sin(ang); + const xe = DX + R * c, yt = TOP - RT * s, yb = BOT + RY * s; + w.pane.setAttribute("d", `M${DX} ${TOP} L${xe.toFixed(1)} ${yt.toFixed(1)} L${xe.toFixed(1)} ${yb.toFixed(1)} L${DX} ${BOT} Z`); + const ed = `M${xe.toFixed(1)} ${(yt + 4).toFixed(1)} V${(yb - 4).toFixed(1)}`; + w.edge.forEach((p) => p.setAttribute("d", ed)); + (s > 0 ? front : back).appendChild(w.g); + }); + /* OpenCode walks in on the left */ + const k = io(seg(t, 0.6, 2.1)); + tf(oc0, lerp(104, DX, k), CY, 1 - 0.25 * k); + oc0.setAttribute("opacity", seg(t, 0, 0.3) * (1 - seg(k, 0.55, 0.95))); + ghost.setAttribute("opacity", seg(t, 1.2, 1.8) * (1 - out)); + /* the alternatives come round one at a time */ + alts.forEach((c) => { + const a = seg(t, c.t0, c.t0 + 0.45), b = io(seg(t, c.t0 + 0.4, c.t0 + 0.95)); + const x0 = lerp(DX, DX + R + 46, oc(a)), x = lerp(x0, SX, b), y = lerp(CY, c.y, b) - 34 * Math.sin(Math.PI * b); + tf(c.g, x, y, 0.72 + 0.28 * oc(a)); + c.g.setAttribute("opacity", seg(t, c.t0, c.t0 + 0.25) * (1 - out)); + }); + cap.textContent = t < 2.4 || t >= 11.4 ? "OpenCode 2.0: “1.0 plugins will not work in 2.0”" : "Seven alternatives, checked 15 Oct 2026"; + }; + }, + }; + + /* ---- 2. a vending machine: eight buttons, and the licence box each one drops into the tray ---- */ + SC.vending = { + dur: 12, still: 10.3, + build(svg, api) { + txt(svg, 40, 58, "Which OpenCode alternatives are open source?", { "font-size": 28, "font-weight": 700 }); + const TOOLS = [["OpenCode", "MIT"], ["Claude Code", "All rights reserved"], ["Codex CLI", "Apache 2.0"], ["Gemini CLI", "Apache 2.0"], ["Kilo Code", "MIT"], ["Crush", "FSL 1.1, MIT future licence"], ["Pi", "MIT"], ["Goose", "Apache 2.0"]]; + const COL = { MIT: MINT, "Apache 2.0": SKY, "All rights reserved": LIL, "FSL 1.1, MIT future licence": YEL }; + const WX = 74, WY = 104, WW = 380, WH = 372, PX = 474, PW = 252, RH = WH / 8, TY = 494, TH = 54; + const TCX = WX + WW / 2, TCY = TY + TH / 2; + el("rect", { x: 50, y: 82, width: 700, height: 486, rx: 24, fill: BLUE, stroke: INK, "stroke-width": 3 }, svg); + el("rect", { x: WX, y: WY, width: WW, height: WH, rx: 14, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + el("rect", { x: PX, y: WY, width: PW, height: WH, rx: 14, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + el("rect", { x: WX, y: TY, width: WW, height: TH, rx: 12, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + el("path", { d: `M${WX + 14} ${TY + 12} H${WX + WW - 14}`, stroke: LIL, "stroke-width": 3, "stroke-linecap": "round" }, svg); + el("rect", { x: PX, y: TY, width: PW, height: TH, rx: 12, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + const read = txt(svg, PX + PW / 2, TY + 35, "", { "text-anchor": "middle", "font-size": 22, "font-weight": 700, "font-family": MONO }); + const clip = el("clipPath", { id: "mg-oca-vclip" }, el("defs", {}, svg)); + el("rect", { x: WX + 2, y: WY + 2, width: WW - 4, height: WH - 4, rx: 12 }, clip); + el("rect", { x: WX + 2, y: TY + 2, width: WW - 4, height: TH - 4, rx: 10 }, clip); + const box = (p, s, fill) => { + const g = el("g", {}, p); + const r = el("rect", { y: -16, height: 32, rx: 6, fill, stroke: INK, "stroke-width": 3 }, g); + const tx = txt(g, 0, 7, s, { "text-anchor": "middle", "font-size": 19, "font-weight": 700 }); + const w = tx.getComputedTextLength() + 28; r.setAttribute("x", -w / 2); r.setAttribute("width", w); + return { g, w }; + }; + let user = null; + const rows = TOOLS.map(([name, lic], i) => { + const top = WY + RH * i, cy = top + 24, col = COL[lic]; + let d = `M${WX + 12} ${top + RH - 3}`; + for (let x = WX + 12; x < WX + WW - 18; x += 12) d += " q3 -7 6 0 t6 0"; + el("path", { d, stroke: LIL, "stroke-width": 2.5, fill: "none", "stroke-linecap": "round" }, svg); + const back = box(svg, lic, col), fr = box(svg, lic, col), bx = WX + 16 + fr.w / 2; + tf(back.g, bx + 10, cy - 5); tf(fr.g, bx, cy); + const by = WY + RH * i + RH / 2, g = el("g", {}, svg); + el("rect", { x: PX + 6, y: by - 22, width: PW - 12, height: 44, rx: 22, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, class: "mg-ring" }, g); + const btn = el("rect", { x: PX + 12, y: by - 18, width: PW - 24, height: 36, rx: 18, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, g); + const lamp = el("circle", { cx: PX + 32, cy: by, r: 9, fill: SOFT, stroke: INK, "stroke-width": 2.5 }, g); + txt(g, PX + 50, by + 7, name, { "font-size": 19, "font-weight": 700 }); + button(g, `${name}: ${lic}`, () => { user = { i, t0: api.now() }; api.poke(); }); + return { btn, lamp, x: bx, y: cy, t0: 1.0 + i * 1.15 }; + }); + const fly = el("g", { "clip-path": "url(#mg-oca-vclip)", "pointer-events": "none" }, svg); + const drops = TOOLS.map(([, lic]) => box(fly, lic, COL[lic]).g); + el("rect", { x: WX, y: WY, width: WW, height: WH, rx: 14, fill: SKY, "fill-opacity": 0.14, "pointer-events": "none" }, svg); + el("path", { d: `M${WX + 362} ${WY + 16} L${WX + 326} ${WY + 92} M${WX + 364} ${WY + 52} L${WX + 344} ${WY + 94}`, stroke: "#fff", "stroke-width": 6, "stroke-linecap": "round", opacity: 0.9, "pointer-events": "none" }, svg); + const place = (g, r, k) => { + if (k < 0) { g.setAttribute("opacity", 0); return; } + g.setAttribute("opacity", 1); + if (k < 0.25) { tf(g, r.x, r.y, 1 + 0.08 * oc(k / 0.25)); return; } + const f = cl((k - 0.25) / 0.5), y = lerp(r.y, TCY, f * f), x = lerp(r.x, TCX, f), bo = k > 0.75 ? -7 * Math.sin(Math.PI * seg(k, 0.75, 0.98)) : 0; + tf(g, x, y + bo, 1.08 - 0.08 * f); + }; + return (t, now) => { + const out = seg(t, 11.4, 11.9); + let cur = -1; + rows.forEach((r, i) => { if (t >= r.t0) cur = i; }); + const pick = user ? user.i : cur; + rows.forEach((r, i) => { + const lit = user ? i === user.i : i === cur && !out, done = t >= r.t0 && !out; + r.btn.setAttribute("fill", lit ? YEL : "#fff"); + r.lamp.setAttribute("fill", lit ? YEL : done ? MINT : SOFT); + }); + drops.forEach((g, i) => { + if (user) { place(g, rows[i], i === user.i ? now - user.t0 : -1); return; } + const r = rows[i], nx = rows[i + 1], k = t - r.t0, gone = nx && t >= nx.t0 + 0.75; + place(g, r, gone ? -1 : k); + if (k >= 0 && !gone) g.setAttribute("opacity", 1 - out); + }); + read.textContent = pick >= 0 && !(out && !user) ? TOOLS[pick][0] : "15 Oct 2026"; + }; + }, + }; + + /* ---- 3. a bookshelf fills one book at a time; each latest release slides out on a tag under its spine ---- */ + SC.shelf = { + dur: 12, still: 10, + build(svg, api) { + txt(svg, 40, 60, "Latest release", { "font-size": 32, "font-weight": 700 }); + txt(svg, 760, 60, "15 Oct 2026", { "text-anchor": "end", "font-size": 24, "font-weight": 600, fill: MUTE }); + const B = [["OpenCode", "v1.19.0", "11 Oct", LIL, 68, 230], ["Claude Code", "v2.1.300", "14 Oct", SKY, 74, 214], ["Codex CLI", "rust-v0.162.1", "9 Oct", MINT, 66, 240], ["Gemini CLI", "v0.64.0", "13 Oct", BLUE, 72, 222], + ["Kilo Code", "v7.8.12", "15 Oct", YEL, 70, 236], ["Crush", "v0.98.1", "9 Oct", LIL, 64, 206], ["Pi", "v1.2.0", "12 Oct", SKY, 60, 196], ["Goose", "v1.54.0", "8 Oct", MINT, 70, 226]]; + const SH = 356, BD = SH + 22; + el("rect", { x: 58, y: 96, width: 684, height: SH - 96, fill: SOFT }, svg); + const tagLayer = el("g", { "clip-path": "url(#mg-oca-tclip)" }, svg); + const clip = el("clipPath", { id: "mg-oca-tclip" }, el("defs", {}, svg)); + el("rect", { x: 0, y: BD, width: 800, height: 600 - BD }, clip); + el("rect", { x: 40, y: 80, width: 720, height: 20, rx: 4, fill: YEL, stroke: INK, "stroke-width": 3 }, svg); + [40, 740].forEach((x) => el("rect", { x, y: 80, width: 20, height: BD - 80 + 4, rx: 4, fill: YEL, stroke: INK, "stroke-width": 3 }, svg)); + const books = el("g", {}, svg); + el("rect", { x: 40, y: SH, width: 720, height: 22, rx: 4, fill: YEL, stroke: INK, "stroke-width": 3 }, svg); + [[120, 367, 60], [420, 368, 80], [640, 366, 50], [250, 90, 70], [560, 89, 60]].forEach(([x, y, w]) => el("path", { d: `M${x} ${y} h${w}`, stroke: INK, "stroke-width": 2, "stroke-linecap": "round", opacity: 0.5 }, svg)); + let lift = -1, liftT = -99; + const items = B.map(([name, ver, date, col, w, h], i) => { + const cx = 102.5 + 85 * i, g = el("g", {}, books), inner = el("g", {}, g); + el("rect", { x: -w / 2 - 8, y: -h - 8, width: w + 16, height: h + 16, rx: 12, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, class: "mg-ring" }, inner); + el("rect", { x: -w / 2, y: -h, width: w, height: h, rx: 6, fill: col, stroke: INK, "stroke-width": 3 }, inner); + el("path", { d: `M${-w / 2 + 3} ${-h + 20} H${w / 2 - 3} M${-w / 2 + 3} -20 H${w / 2 - 3}`, stroke: INK, "stroke-width": 2.5 }, inner); + txt(inner, 0, 0, name, { "text-anchor": "middle", "font-size": 21, "font-weight": 700, transform: `translate(8 ${-h / 2}) rotate(-90)` }); + button(g, `${name}: latest release ${ver}, ${date}`, () => { lift = i; liftT = api.now(); api.poke(); }); + const L = i % 2 ? 18 : 96, tg = el("g", {}, tagLayer), sw = el("g", {}, tg); + el("path", { d: `M0 ${-L - 70} V${L}`, stroke: INK, "stroke-width": 2.5 }, sw); + const r = el("rect", { y: L, height: 66, rx: 10, fill: col, stroke: INK, "stroke-width": 3 }, sw); + el("circle", { cy: L + 12, r: 5, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, sw); + const v = txt(sw, 0, L + 39, ver, { "text-anchor": "middle", "font-size": 18, "font-weight": 700, "font-family": MONO }); + txt(sw, 0, L + 59, date, { "text-anchor": "middle", "font-size": 18, "font-weight": 600 }); + const tw = Math.max(v.getComputedTextLength(), 60) + 26; r.setAttribute("x", -tw / 2); r.setAttribute("width", tw); + return { g, inner, tg, sw, cx, L, t0: 0.6 + i * 0.95 }; + }); + return (t, now) => { + const out = seg(t, 11.4, 11.9); + items.forEach((b, i) => { + const k = seg(t, b.t0, b.t0 + 0.55), e = ob(k); + const lw = i === lift ? now - liftT : 99, up = lw >= 0 && lw < 1.4 ? 22 * Math.sin(Math.PI * cl(lw / 1.4)) : 0; + tf(b.g, b.cx, SH - 14 * (1 - e) - up, 1.1 - 0.1 * e); + b.g.setAttribute("opacity", seg(t, b.t0, b.t0 + 0.15) * (1 - out)); + const tt = t - b.t0 - 0.5, s = oc(seg(tt, 0, 0.5)); + const sw0 = tt < 0 ? 0 : 9 * Math.exp(-2.4 * tt) * Math.sin(7 * tt), sw1 = lw >= 0 && lw < 2 ? 12 * Math.exp(-2.2 * lw) * Math.sin(8 * lw) : 0; + b.tg.setAttribute("transform", `translate(${b.cx} ${BD - (b.L + 70) * (1 - s)})`); + b.sw.setAttribute("transform", `rotate(${sw0 + sw1})`); + b.tg.setAttribute("opacity", (tt < 0 ? 0 : 1) * (1 - out)); + }); + }; + }, + }; + + /* ---- title card, 16 by 9: a snow globe ---- */ + SC.hero = { + w: 960, h: 540, dur: 8, still: 4, + build(svg) { + txt(svg, 64, 116, "7 coding agents to switch to", { "font-size": 27, "font-weight": 500, fill: MUTE }); + txt(svg, 60, 208, "OpenCode", { "font-size": 82, "font-weight": 700 }); + txt(svg, 60, 296, "Alternatives", { "font-size": 82, "font-weight": 700 }); + el("path", { d: "M66 326 H552", stroke: YEL, "stroke-width": 14, "stroke-linecap": "round" }, svg); + el("rect", { x: 60, y: 362, width: 316, height: 56, rx: 28, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + txt(svg, 218, 399, "Checked 15 Oct 2026", { "text-anchor": "middle", "font-size": 25, "font-weight": 700 }); + el("path", { d: "M72 474 C190 430 300 500 420 462 S560 432 606 416", stroke: LIL, "stroke-width": 5, fill: "none", "stroke-linecap": "round", "stroke-dasharray": "1 16" }, svg); + el("circle", { cx: 72, cy: 474, r: 11, fill: MINT, stroke: INK, "stroke-width": 3 }, svg); + const GX = 794, GY = 246, GR = 124; + const clip = el("clipPath", { id: "mg-oca-gclip" }, el("defs", {}, svg)); + el("circle", { cx: GX, cy: GY, r: GR - 2 }, clip); + el("circle", { cx: GX, cy: GY, r: GR, fill: SKY, "fill-opacity": 0.25 }, svg); + const inside = el("g", { "clip-path": "url(#mg-oca-gclip)" }, svg); + el("ellipse", { cx: GX, cy: GY + 112, rx: 140, ry: 52, fill: "#fff", stroke: INK, "stroke-width": 3 }, inside); + const tree = (x, y, s) => { const g = el("g", { transform: `translate(${x} ${y}) scale(${s})` }, inside); el("rect", { x: -5, y: -6, width: 10, height: 16, fill: YEL, stroke: INK, "stroke-width": 3 }, g); el("path", { d: "M0 -74 L22 -40 H12 L30 -8 H-30 L-12 -40 H-22 Z", fill: MINT, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, g); }; + tree(GX - 78, GY + 74, 0.9); tree(GX + 84, GY + 70, 0.75); + const term = el("g", { transform: `translate(${GX} ${GY + 16})` }, inside); + el("rect", { x: -54, y: -44, width: 108, height: 84, rx: 10, fill: "#fff", stroke: INK, "stroke-width": 3 }, term); + el("path", { d: "M-54 -24 V-34 a10 10 0 0 1 10 -10 H44 a10 10 0 0 1 10 10 V-24 Z", fill: LIL, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, term); + [-40, -28, -16].forEach((x) => el("circle", { cx: x, cy: -34, r: 3.5, fill: "#fff", stroke: INK, "stroke-width": 2 }, term)); + el("path", { d: "M-34 -6 L-18 6 L-34 18", stroke: INK, "stroke-width": 5, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, term); + const cur = el("path", { d: "M-6 20 H22", stroke: BLUE, "stroke-width": 5, "stroke-linecap": "round" }, term); + const flakes = Array.from({ length: 26 }, (_, i) => { + const h = (k) => { const v = Math.sin(i * 12.9898 + k * 78.233) * 43758.5453; return v - Math.floor(v); }; + const x = GX - 106 + h(1) * 212, ph = h(2), sp = [1, 2][i % 2] / 8, r = 4 + (i % 3) * 1.5; + const n = el("g", {}, inside); + if (i % 2) el("path", { d: `M0 ${-r - 2} V${r + 2} M${-(r + 2) * 0.87} ${-(r + 2) / 2} L${(r + 2) * 0.87} ${(r + 2) / 2} M${-(r + 2) * 0.87} ${(r + 2) / 2} L${(r + 2) * 0.87} ${-(r + 2) / 2}`, stroke: BLUE, "stroke-width": 2, "stroke-linecap": "round" }, n); + else el("circle", { r: r * 0.7, fill: "#fff", stroke: LIL, "stroke-width": 1.5 }, n); + return { n, x, ph, sp, w: i * 1.7 }; + }); + el("circle", { cx: GX, cy: GY, r: GR, fill: "none", stroke: INK, "stroke-width": 3 }, svg); + el("path", { d: `M${GX - 92} ${GY - 40} A100 100 0 0 1 ${GX - 40} ${GY - 92}`, stroke: "#fff", "stroke-width": 8, fill: "none", "stroke-linecap": "round" }, svg); + el("path", { d: `M${GX - 104} ${GY - 4} A106 106 0 0 1 ${GX - 101} ${GY - 22}`, stroke: "#fff", "stroke-width": 8, fill: "none", "stroke-linecap": "round" }, svg); + el("path", { d: `M${GX - 98} ${GY + 104} L${GX + 98} ${GY + 104} L${GX + 124} ${GY + 196} L${GX - 124} ${GY + 196} Z`, fill: BLUE, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, svg); + el("path", { d: `M${GX - 107} ${GY + 136} L${GX + 107} ${GY + 136} L${GX + 113} ${GY + 158} L${GX - 113} ${GY + 158} Z`, fill: YEL, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, svg); + el("rect", { x: GX - 136, y: GY + 196, width: 272, height: 16, rx: 8, fill: LIL, stroke: INK, "stroke-width": 3 }, svg); + [[620, 132, 15, SKY], [918, 112, 14, YEL], [640, 476, 12, MINT]].forEach(([x, y, r, col]) => el("path", { d: `M0 ${-r} Q0 0 ${r} 0 Q0 0 0 ${r} Q0 0 ${-r} 0 Q0 0 0 ${-r} Z`, fill: col, stroke: INK, "stroke-width": 2.5, "stroke-linejoin": "round", transform: `translate(${x} ${y})` }, svg)); + return (t) => { + flakes.forEach((f) => { + const p = (f.ph + t * f.sp) % 1, y = GY - GR + 6 + p * (GR * 2 - 30), x = f.x + 7 * Math.sin(t * 0.785 * 2 + f.w); + f.n.setAttribute("transform", `translate(${x.toFixed(1)} ${y.toFixed(1)}) rotate(${(t * 45 + f.w * 20) % 360})`); + }); + cur.setAttribute("opacity", Math.floor(t * 2) % 2 ? 0.15 : 1); + }; + }, + }; + + /* ---- player ---- */ + const q = new URLSearchParams(location.search), frozen = q.has("mgstill"), fixed = parseFloat(q.get("mgstill")); + const calm = matchMedia("(prefers-reduced-motion: reduce)").matches; + const mount = (fig) => { + const sc = SC[fig.dataset.scene], img = fig.querySelector("img"); if (!sc || !img) return; + const W = sc.w || 800, H = sc.h || 600; + const svg = el("svg", { viewBox: `0 0 ${W} ${H}`, class: "mg-svg", role: "group", "aria-label": img.alt || "" }); + svg.style.aspectRatio = `${W} / ${H}`; + el("rect", { width: W, height: H, fill: "#fff" }, svg); + img.replaceWith(svg); + let off = 0, raf = 0, seen = false, held = calm; + const base = performance.now(), clock = () => (performance.now() - base) / 1000; + let draw = () => {}; + const frame = () => { const now = clock(); draw(held ? sc.still : (((now + off) % sc.dur) + sc.dur) % sc.dur, now); }; + const tick = () => { cancelAnimationFrame(raf); frame(); if (seen && !held) raf = requestAnimationFrame(tick); }; + const api = { now: clock, seek: (s) => { off = s - clock(); held = false; tick(); }, poke: () => { held = false; tick(); } }; + draw = sc.build(svg, api); + if (frozen) { draw(isNaN(fixed) ? sc.still : fixed, 0); return; } + new IntersectionObserver(([e]) => { + seen = e.isIntersecting; + if (seen) { tick(); if (!fig.dataset.sent && window.posthog) { fig.dataset.sent = 1; window.posthog.capture("blog_scene_view", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } } + else cancelAnimationFrame(raf); + }, { threshold: 0.25 }).observe(fig); + svg.addEventListener("click", () => { if (!fig.dataset.hit && window.posthog) { fig.dataset.hit = 1; window.posthog.capture("blog_scene_interact", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } }); + frame(); + }; + const go = () => document.querySelectorAll("figure.mg[data-scene]").forEach(mount); + const fonts = document.fonts && document.fonts.load ? Promise.all([document.fonts.load('700 32px "Space Grotesk"'), document.fonts.load('500 20px "Space Grotesk"'), document.fonts.load('700 20px "JetBrains Mono"')]).catch(() => {}) : Promise.resolve(); + const start = () => fonts.then(go, go); + if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", start); else start(); +})(); diff --git a/docs/blog/assets/media/opencode-alternatives/shelf.png b/docs/blog/assets/media/opencode-alternatives/shelf.png new file mode 100644 index 000000000..fa79be95f Binary files /dev/null and b/docs/blog/assets/media/opencode-alternatives/shelf.png differ diff --git a/docs/blog/assets/media/opencode-alternatives/vending.png b/docs/blog/assets/media/opencode-alternatives/vending.png new file mode 100644 index 000000000..eb8a3e1dd Binary files /dev/null and b/docs/blog/assets/media/opencode-alternatives/vending.png differ diff --git a/docs/blog/best-ai-coding-agents/index.html b/docs/blog/best-ai-coding-agents/index.html index 911b40f7c..7401dba3c 100644 --- a/docs/blog/best-ai-coding-agents/index.html +++ b/docs/blog/best-ai-coding-agents/index.html @@ -468,6 +468,22 @@

FAQ

Related in Comparisons

+ -
diff --git a/docs/blog/best-claude-code-multi-agent-tools/index.html b/docs/blog/best-claude-code-multi-agent-tools/index.html index c2ec1a87a..eb03a7e96 100644 --- a/docs/blog/best-claude-code-multi-agent-tools/index.html +++ b/docs/blog/best-claude-code-multi-agent-tools/index.html @@ -436,6 +436,22 @@

FAQ

Related in Comparisons

+ -
diff --git a/docs/blog/build-your-personal-agi-on-your-computer/index.html b/docs/blog/build-your-personal-agi-on-your-computer/index.html index 046d7973a..2c8cd02b9 100644 --- a/docs/blog/build-your-personal-agi-on-your-computer/index.html +++ b/docs/blog/build-your-personal-agi-on-your-computer/index.html @@ -342,6 +342,22 @@

FAQ

Related in Guides

+ -
diff --git a/docs/blog/building-reliable-ai-agents/index.html b/docs/blog/building-reliable-ai-agents/index.html index 023965fb0..1d288704a 100644 --- a/docs/blog/building-reliable-ai-agents/index.html +++ b/docs/blog/building-reliable-ai-agents/index.html @@ -358,6 +358,22 @@

FAQ

Related in Guides

+ -
diff --git a/docs/blog/chatgpt-dots-alternatives/index.html b/docs/blog/chatgpt-dots-alternatives/index.html index b6ed02b8b..4f1d97609 100644 --- a/docs/blog/chatgpt-dots-alternatives/index.html +++ b/docs/blog/chatgpt-dots-alternatives/index.html @@ -350,6 +350,22 @@

FAQ

Related in Comparisons

+ -
diff --git a/docs/blog/claude-code-alternatives/index.html b/docs/blog/claude-code-alternatives/index.html index 6ff388d9d..4ba77a8d7 100644 --- a/docs/blog/claude-code-alternatives/index.html +++ b/docs/blog/claude-code-alternatives/index.html @@ -360,6 +360,22 @@

FAQ

Related in Comparisons

+ -
diff --git a/docs/blog/claude-code-automation-while-you-sleep/index.html b/docs/blog/claude-code-automation-while-you-sleep/index.html index d7b290b72..de0e7862e 100644 --- a/docs/blog/claude-code-automation-while-you-sleep/index.html +++ b/docs/blog/claude-code-automation-while-you-sleep/index.html @@ -346,6 +346,22 @@

FAQ

Related in Guides

+ -
diff --git a/docs/blog/claude-code-best-practices/index.html b/docs/blog/claude-code-best-practices/index.html new file mode 100644 index 000000000..b819d42bc --- /dev/null +++ b/docs/blog/claude-code-best-practices/index.html @@ -0,0 +1,457 @@ + + + + + + + + +Claude Code Best Practices: 9 Habits From Anthropic's Docs — Munder Difflin Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +

Claude Code Best Practices: 9 Habits From Anthropic's Docs

+

Nine Claude Code best practices from Anthropic's docs, checked 15 Oct 2026: give Claude a check, plan first, keep memory short, clear context, run in parallel.

+ +
+ +
Title card on white. 'Claude Code Best Practices' in large ink type, the kicker '9 Habits From Anthropic's Docs', a 'Checked 15 Oct 2026' pill, and a small bonsai tree in a blue pot.
+ +
+
+

The core Claude Code best practices, from Anthropic’s own docs, are to give Claude a check it can run, plan before coding, keep CLAUDE.md short and clear context between unrelated tasks. Checked 15 Oct 2026.

+

To run several sessions at once, you can also use Munder Difflin, free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. Our install guide covers it. New to the tool itself? Read how to use Claude Code first, or browse the Guides hub.

+

Every tip below comes from Anthropic’s best practices page unless it names another Anthropic page or says “Our view”.

+

Why does the context window shape every tip?

+

Because Claude gets worse as its context fills. Anthropic’s best practices page says “Claude’s context window fills up fast, and performance degrades as it fills”, and calls the context window “the most important resource to manage”. Most of the habits below either save context or replace your attention with a check.

+

1. Give Claude a way to verify its work

+

Give Claude a check it can run: tests, a build, or a screenshot to compare. Anthropic’s best practices page puts it as “the difference between a session you watch and one you walk away from.” Put the check in the prompt, as in the page’s example:

+
write a validateEmail function. example test cases: user@example.com is true, invalid is false, user@.com is false. run the tests after implementing
+

For harder gates, the same page lists a /goal condition, a Stop hook that blocks the turn until the check passes, and a verification subagent. It also says to have Claude show evidence, such as test output, rather than assert success.

+
Animation. A round sieve shakes over a tray. Fine sand falls through, while three pebbles marked failing test, build error and eslint stay caught in the mesh.
A check catches the bad pebbles before you do. Anthropic's best practices page, checked 15 Oct 2026.
+

2. Explore first, then plan, then code

+

Separate research and planning from implementation, so Claude does not solve the wrong problem. Anthropic’s best practices page gives four phases: explore, plan, implement, commit. Enter plan mode by pressing Shift+Tab until the status bar shows plan mode on, or start with:

+
claude --permission-mode plan
+

Press Ctrl+G to edit the plan in your text editor before Claude proceeds. The page also says “If you could describe the diff in one sentence, skip the plan.” More in our plan mode guide.

+
Animation. A paper treasure map unrolls. A dotted route draws itself from a start mark to a cross, and only then does a solid line follow the dots, step by step.
Draw the route, then walk it: explore, plan, implement, commit, from Anthropic's best practices page.
+

3. Be specific in your prompts

+

Reference specific files, mention constraints and point to example patterns. Anthropic’s best practices page contrasts “fix the login bug” with a prompt that names the symptom, the likely location in src/auth/ and a failing test to write first. Use @ to reference files, paste screenshots, or pipe data in:

+
cat error.log | claude -p "explain this error"
+

4. What should go in CLAUDE.md?

+

Only what Claude can’t infer from code and needs in every session: Bash commands, code style that differs from defaults, testing instructions, repo etiquette. Run /init for a starter file and /context to confirm Claude loaded it. For each line, Anthropic’s best practices page asks “Would removing this cause Claude to make mistakes?” If not, cut it. Anthropic’s memory docs say to target under 200 lines per file. Our view: a CLAUDE.md that reads like a policy manual gets read like one. See give Claude Code long term memory.

+
Animation. A clipboard holds a short checklist of four lines. Each box ticks itself in turn, and a fifth, longer line fades out and is struck through before it can be ticked.
Short beats long: Anthropic's memory docs say to target under 200 lines per file.
+

5. Set permissions once, not per prompt

+

Pre-approve the tools you trust with /permissions and let sandboxed commands run without asking with /sandbox. Anthropic’s best practices page notes that with Claude Code v2.1.283 or later, auto mode is the built-in starting permission mode for interactive terminal and VS Code sessions, where a classifier model blocks only what looks risky. On earlier versions that applies only on Pro, Max and Team plans. See why Claude Code keeps asking for permission.

+

6. Use hooks for rules with zero exceptions

+

Use hooks for anything that must happen every time. Anthropic’s best practices page says “Unlike CLAUDE.md instructions which are advisory, hooks are deterministic and guarantee the action happens.” Ask Claude to “Write a hook that runs eslint after every file edit”, edit .claude/settings.json by hand, and run /hooks to see what is set. Our hooks explainer goes further.

+

7. Send research to subagents

+

Delegate investigation to subagents so the reading happens outside your main context. Anthropic’s best practices page says subagents “run in separate context windows and report back summaries”, and suggests prompts like “use subagents to investigate X”. Custom subagents live in .claude/agents/. The same page suggests a subagent review of the diff in a fresh context before you call a task done.

+

8. Clear context between tasks

+

Run /clear between unrelated tasks. Anthropic’s best practices page says that if you have corrected Claude more than twice on the same issue in one session, the context is cluttered with failed approaches, so clear it and write a better prompt. For finer control:

+
/compact Focus on the API changes
+

Esc stops Claude mid action, and Esc + Esc or /rewind restores earlier code or conversation. Anthropic’s best practices page warns that checkpoints are not “a replacement for git”.

+

9. Run several sessions side by side

+

Run several sessions in parallel. Anthropic’s best practices page lists worktrees, the desktop app, cloud sessions, agent view (research preview) and agent teams (experimental and disabled by default). It also suggests a Writer and Reviewer pair, because “a fresh context improves code review”. Our multiple sessions guide covers the setups.

+

Claude Code best practices at a glance, from Anthropic’s docs, checked 15 Oct 2026

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PracticeWhat to doWhere in the docs
VerifyGive a test, build or screenshot checkGive Claude a way to verify its work
PlanShift+Tab to plan mode, then implementExplore first, then plan, then code
Be specificName files, constraints, patterns; use @Provide specific context in your prompts
CLAUDE.md/init, then prune; under 200 linesWrite an effective CLAUDE.md; memory docs
Permissions/permissions and /sandboxConfigure permissions
HooksScript what must always happenSet up hooks
Subagents“use subagents to investigate X”Use subagents for investigation
Context/clear, /compact, /rewindManage your session
ParallelWorktrees, desktop app, agent viewRun multiple Claude sessions
+
+

What mistakes does Anthropic warn about?

+

Anthropic’s best practices page names five failure patterns:

+
    +
  • The kitchen sink session: unrelated tasks in one context. Fix: /clear.
  • +
  • Correcting over and over: after two failed corrections, clear and rewrite the prompt.
  • +
  • The over-specified CLAUDE.md: prune it, or turn a rule into a hook.
  • +
  • The trust-then-verify gap: “If you can’t verify it, don’t ship it.”
  • +
  • The infinite exploration: scope investigations narrowly or use subagents.
  • +
+ + + + +
+

FAQ

+ +
+

What are the most important Claude Code best practices?

+

Anthropic's best practices page opens with giving Claude a way to verify its work, then exploring and planning before coding. It says most best practices are based on one constraint: the context window fills up fast and performance degrades as it fills. So keep `CLAUDE.md` short and run /clear between unrelated tasks.

+
+ +
+

How long should `CLAUDE.md` be?

+

Short. Anthropic's memory docs say to target under 200 lines per `CLAUDE.md` file, because longer files consume more context and reduce adherence. The best practices page suggests asking of each line whether removing it would cause Claude to make mistakes, and cutting it if not.

+
+ +
+

When should I use plan mode in Claude Code?

+

Anthropic's best practices page says planning is most useful when you are uncertain about the approach, when the change touches multiple files, or when you are unfamiliar with the code. For small, clear fixes it says to ask Claude directly. Its rule of thumb: if you could describe the diff in one sentence, skip the plan.

+
+ +
+

How do I get fewer permission prompts in Claude Code?

+

Anthropic's best practices page says to pre-approve the tools you trust with /permissions and let sandboxed commands run without asking with /sandbox. With Claude Code v2.1.283 or later, auto mode is the built-in starting permission mode for interactive terminal and VS Code sessions, per the permission modes docs.

+
+ +
+

Can I run more than one Claude Code session at once?

+

Yes. Anthropic's best practices page lists worktrees, the desktop app, cloud sessions, agent view (a research preview) and agent teams (experimental and disabled by default). Munder Difflin is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer.

+
+ +
+ + + +
+ + +
+ + + + +
+ + +
+ +
+ + + + + + diff --git a/docs/blog/claude-code-max-plan-tips/index.html b/docs/blog/claude-code-max-plan-tips/index.html index 0f3b11842..64e34aefe 100644 --- a/docs/blog/claude-code-max-plan-tips/index.html +++ b/docs/blog/claude-code-max-plan-tips/index.html @@ -279,6 +279,22 @@

FAQ

Related in Guides

+ -
diff --git a/docs/blog/claude-code-multi-agent-setup-tutorial/index.html b/docs/blog/claude-code-multi-agent-setup-tutorial/index.html index 6f8c96cdc..b089cd08b 100644 --- a/docs/blog/claude-code-multi-agent-setup-tutorial/index.html +++ b/docs/blog/claude-code-multi-agent-setup-tutorial/index.html @@ -335,6 +335,22 @@

FAQ

Related in Guides

+ -
diff --git a/docs/blog/claude-code-orchestration-tools-compared/index.html b/docs/blog/claude-code-orchestration-tools-compared/index.html index a439042f5..71d00a9e4 100644 --- a/docs/blog/claude-code-orchestration-tools-compared/index.html +++ b/docs/blog/claude-code-orchestration-tools-compared/index.html @@ -334,6 +334,22 @@

Matching tools to priorities

Related in Comparisons

+ -
diff --git a/docs/blog/claude-code-skills/index.html b/docs/blog/claude-code-skills/index.html index c24e228c0..b861df8d7 100644 --- a/docs/blog/claude-code-skills/index.html +++ b/docs/blog/claude-code-skills/index.html @@ -330,50 +330,50 @@

FAQ

Related in Guides

- +
- + - 8 minPinned + 5 min
-

AI Agent Use Cases From a Real Company: Every Agent on Our Floor and the Job It Does

-

We run our company on Munder Difflin. Every seat on our floor: what each AI agent does, how its work starts, what comes out, and how to copy it.

+

Claude Code Best Practices: 9 Habits From Anthropic's Docs

+

Nine Claude Code best practices from Anthropic's docs, checked 15 Oct 2026: give Claude a check, plan first, keep memory short, clear context, run in parallel.

Guides - Read → + Read →
- +
- + - 4 min + 8 minPinned
-

Gemini 4 Argon Pricing: Half Price Until 30 November, Then the Real Bill Arrives

-

Gemini 4 Argon pricing, checked 13 Oct 2026: $2 input and $10 output per 1M tokens through 30 Nov, then $4 and $20. No free tier. Worked examples inside.

+

AI Agent Use Cases From a Real Company: Every Agent on Our Floor and the Job It Does

+

We run our company on Munder Difflin. Every seat on our floor: what each AI agent does, how its work starts, what comes out, and how to copy it.

Guides - Read → + Read →
- +
4 min
-

How to Use Gemini 4 Argon: Five Ways In, and Free Is Not One of Them

-

How to use Gemini 4 Argon on 13 Oct 2026: the Gemini app, AI Mode, AI Studio, the API with model ID gemini-4-argon, and Antigravity. What each route needs.

+

Gemini 4 Argon Pricing: Half Price Until 30 November, Then the Real Bill Arrives

+

Gemini 4 Argon pricing, checked 13 Oct 2026: $2 input and $10 output per 1M tokens through 30 Nov, then $4 and $20. No free tier. Worked examples inside.

Guides - Read → + Read →
diff --git a/docs/blog/claude-code-subagents-vs-multi-agent-harness/index.html b/docs/blog/claude-code-subagents-vs-multi-agent-harness/index.html index dffee1b36..fcf8d5c8d 100644 --- a/docs/blog/claude-code-subagents-vs-multi-agent-harness/index.html +++ b/docs/blog/claude-code-subagents-vs-multi-agent-harness/index.html @@ -342,6 +342,22 @@

FAQ

Related in Guides

+ -
diff --git a/docs/blog/claude-code-vs-antigravity/index.html b/docs/blog/claude-code-vs-antigravity/index.html index 1acf7564c..1d1b4d740 100644 --- a/docs/blog/claude-code-vs-antigravity/index.html +++ b/docs/blog/claude-code-vs-antigravity/index.html @@ -275,6 +275,22 @@

FAQ

Related in Comparisons

+ -
diff --git a/docs/blog/claude-code-vs-cursor/index.html b/docs/blog/claude-code-vs-cursor/index.html index 1db07671f..5a890cb37 100644 --- a/docs/blog/claude-code-vs-cursor/index.html +++ b/docs/blog/claude-code-vs-cursor/index.html @@ -353,6 +353,22 @@

FAQ

Related in Comparisons

+ -
diff --git a/docs/blog/claude-cowork-vs-claude-code/index.html b/docs/blog/claude-cowork-vs-claude-code/index.html index 2d6cb3a61..22e459651 100644 --- a/docs/blog/claude-cowork-vs-claude-code/index.html +++ b/docs/blog/claude-cowork-vs-claude-code/index.html @@ -346,6 +346,22 @@

FAQ

Related in Comparisons

+ -
diff --git a/docs/blog/claude-squad-alternative/index.html b/docs/blog/claude-squad-alternative/index.html index 5c6879a98..9b1777781 100644 --- a/docs/blog/claude-squad-alternative/index.html +++ b/docs/blog/claude-squad-alternative/index.html @@ -270,6 +270,22 @@

Which should you pick? + -

diff --git a/docs/blog/claude-squad-vs-munder-difflin/index.html b/docs/blog/claude-squad-vs-munder-difflin/index.html index 77776c182..6d342c26e 100644 --- a/docs/blog/claude-squad-vs-munder-difflin/index.html +++ b/docs/blog/claude-squad-vs-munder-difflin/index.html @@ -359,6 +359,22 @@

FAQ

Related in Comparisons

+ -
diff --git a/docs/blog/cline-vs-munder-difflin/index.html b/docs/blog/cline-vs-munder-difflin/index.html index b3548875d..32a44811d 100644 --- a/docs/blog/cline-vs-munder-difflin/index.html +++ b/docs/blog/cline-vs-munder-difflin/index.html @@ -350,6 +350,22 @@

FAQ

Related in Comparisons

+ -
diff --git a/docs/blog/codex-cli-vs-claude-code/index.html b/docs/blog/codex-cli-vs-claude-code/index.html index ca0727e28..26812c542 100644 --- a/docs/blog/codex-cli-vs-claude-code/index.html +++ b/docs/blog/codex-cli-vs-claude-code/index.html @@ -407,6 +407,22 @@

FAQ

Related in Comparisons

+ -
diff --git a/docs/blog/codex-max-plan-tips/index.html b/docs/blog/codex-max-plan-tips/index.html index 14949376e..de8461d47 100644 --- a/docs/blog/codex-max-plan-tips/index.html +++ b/docs/blog/codex-max-plan-tips/index.html @@ -288,6 +288,22 @@

FAQ

Related in Guides

+ -
diff --git a/docs/blog/codex-pricing/index.html b/docs/blog/codex-pricing/index.html index a2b203b7d..4a712b97b 100644 --- a/docs/blog/codex-pricing/index.html +++ b/docs/blog/codex-pricing/index.html @@ -440,6 +440,22 @@

FAQ

Related in Guides

+ -
diff --git a/docs/blog/codex-vs-claude-code-usage-limits/index.html b/docs/blog/codex-vs-claude-code-usage-limits/index.html index 704708e9e..24f2c8cc1 100644 --- a/docs/blog/codex-vs-claude-code-usage-limits/index.html +++ b/docs/blog/codex-vs-claude-code-usage-limits/index.html @@ -341,6 +341,22 @@

FAQ

Related in Comparisons

+ -
diff --git a/docs/blog/codex-vs-cursor/index.html b/docs/blog/codex-vs-cursor/index.html index 64d05a161..597926ec2 100644 --- a/docs/blog/codex-vs-cursor/index.html +++ b/docs/blog/codex-vs-cursor/index.html @@ -347,6 +347,22 @@

FAQ

Related in Comparisons

+ -
diff --git a/docs/blog/command-center-guide/index.html b/docs/blog/command-center-guide/index.html index e4a2015f4..f8684804a 100644 --- a/docs/blog/command-center-guide/index.html +++ b/docs/blog/command-center-guide/index.html @@ -327,6 +327,22 @@

FAQ

Related in Guides

+ -
diff --git a/docs/blog/conductor-claude-code-alternative/index.html b/docs/blog/conductor-claude-code-alternative/index.html index 79fc9d53f..67c8ef228 100644 --- a/docs/blog/conductor-claude-code-alternative/index.html +++ b/docs/blog/conductor-claude-code-alternative/index.html @@ -272,6 +272,22 @@

Conductor vs an orchest

Related in Comparisons

+ -
diff --git a/docs/blog/connect-slack-to-munder-difflin/index.html b/docs/blog/connect-slack-to-munder-difflin/index.html index 5df102a9e..18b061a9a 100644 --- a/docs/blog/connect-slack-to-munder-difflin/index.html +++ b/docs/blog/connect-slack-to-munder-difflin/index.html @@ -334,6 +334,22 @@

FAQ

Related in Guides

+ -
diff --git a/docs/blog/context-engineering-for-ai-agents/index.html b/docs/blog/context-engineering-for-ai-agents/index.html index 6c13c7fb6..5d4a3fb73 100644 --- a/docs/blog/context-engineering-for-ai-agents/index.html +++ b/docs/blog/context-engineering-for-ai-agents/index.html @@ -364,6 +364,22 @@

FAQ

Related in Guides

+ -
diff --git a/docs/blog/crewai-autogen-vs-a-local-agent-harness/index.html b/docs/blog/crewai-autogen-vs-a-local-agent-harness/index.html index fb63aa84b..683ab7d1f 100644 --- a/docs/blog/crewai-autogen-vs-a-local-agent-harness/index.html +++ b/docs/blog/crewai-autogen-vs-a-local-agent-harness/index.html @@ -296,6 +296,22 @@

FAQ

Related in Comparisons

+ -
diff --git a/docs/blog/crystal-claude-code-alternative/index.html b/docs/blog/crystal-claude-code-alternative/index.html index ad511bc31..055049610 100644 --- a/docs/blog/crystal-claude-code-alternative/index.html +++ b/docs/blog/crystal-claude-code-alternative/index.html @@ -268,6 +268,22 @@

Crystal vs a memory-backe

Related in Comparisons

+ -
diff --git a/docs/blog/cursor-alternatives/index.html b/docs/blog/cursor-alternatives/index.html index 1ec3e34db..b041832c1 100644 --- a/docs/blog/cursor-alternatives/index.html +++ b/docs/blog/cursor-alternatives/index.html @@ -367,6 +367,22 @@

FAQ

Related in Comparisons

+ -
diff --git a/docs/blog/debugging-multi-agent-systems/index.html b/docs/blog/debugging-multi-agent-systems/index.html index 2ddd6def1..8eba871c2 100644 --- a/docs/blog/debugging-multi-agent-systems/index.html +++ b/docs/blog/debugging-multi-agent-systems/index.html @@ -326,6 +326,22 @@

FAQ

Related in Guides

+ -
diff --git a/docs/blog/does-the-june-2026-agent-sdk-change-affect-munder-difflin/index.html b/docs/blog/does-the-june-2026-agent-sdk-change-affect-munder-difflin/index.html index e2979d675..f7fddc10f 100644 --- a/docs/blog/does-the-june-2026-agent-sdk-change-affect-munder-difflin/index.html +++ b/docs/blog/does-the-june-2026-agent-sdk-change-affect-munder-difflin/index.html @@ -322,6 +322,22 @@

FAQ

Related in Guides

+ -
diff --git a/docs/blog/evaluating-ai-agent-reliability/index.html b/docs/blog/evaluating-ai-agent-reliability/index.html index 99a5d4dfc..0e472daa5 100644 --- a/docs/blog/evaluating-ai-agent-reliability/index.html +++ b/docs/blog/evaluating-ai-agent-reliability/index.html @@ -329,6 +329,22 @@

FAQ

Related in Guides

+ -
diff --git a/docs/blog/feed.xml b/docs/blog/feed.xml index c73b18014..93bc81d63 100644 --- a/docs/blog/feed.xml +++ b/docs/blog/feed.xml @@ -15,6 +15,14 @@ 2026-10-15T00:00:00.000Z The top launches, skills and MCP servers, multi agent moves, rising repos and arguments in coding agents on 15 Oct 2026, each with its source. + + Claude Code Best Practices: 9 Habits From Anthropic's Docs + + https://munderdiffl.in/blog/claude-code-best-practices/ + 2026-10-15T00:00:00.000Z + 2026-10-15T00:00:00.000Z + Nine Claude Code best practices from Anthropic's docs, checked 15 Oct 2026: give Claude a check, plan first, keep memory short, clear context, run in parallel. + OpenCode 2.0: what changed and how to upgrade @@ -23,6 +31,14 @@ 2026-10-15T00:00:00.000Z OpenCode 2.0 from Anomaly: the background service, Code Mode, background subagents, the new plugin API and SDK, and how to upgrade. Checked 15 Oct 2026. + + OpenCode Alternatives: 7 Coding Agents to Switch To + + https://munderdiffl.in/blog/opencode-alternatives/ + 2026-10-15T00:00:00.000Z + 2026-10-15T00:00:00.000Z + Seven OpenCode alternatives checked on 15 Oct 2026: Claude Code, Codex CLI, Gemini CLI, Kilo Code, Crush, Pi and Goose, by licence, models and release. + AI Agent Use Cases From a Real Company: Every Agent on Our Floor and the Job It Does @@ -471,20 +487,4 @@ 2026-09-26T00:00:00.000Z Grok Bot has no plan of its own. The cheapest way in is a $20 a month Cursor plan, SuperGrok is $30, and there is a seven day trial, not a free plan. - - Meta Muse Alternatives: 7 Picks for People Who Can't (or Won't) Use Muse - - https://munderdiffl.in/blog/meta-muse-alternatives/ - 2026-09-26T00:00:00.000Z - 2026-09-26T00:00:00.000Z - Meta Muse is US only and trains on your chats by default. Seven alternatives, from open source agents that run on your own computer to hosted ones from OpenAI, Google and SpaceXAI. - - - Muse and Grok Bot Want Your Passwords. Here Is Who Can See Them - - https://munderdiffl.in/blog/meta-muse-grok-bot-privacy/ - 2026-09-26T00:00:00.000Z - 2026-09-26T00:00:00.000Z - Meta Muse and Grok Bot both ask for your logins. We read both companies' security docs: who sees your passwords, what trains on your chats, and how to keep AI agents on your own computer instead. - diff --git a/docs/blog/gemini-4-argon-pricing/index.html b/docs/blog/gemini-4-argon-pricing/index.html index 983cd77e5..2d986ce4a 100644 --- a/docs/blog/gemini-4-argon-pricing/index.html +++ b/docs/blog/gemini-4-argon-pricing/index.html @@ -378,6 +378,22 @@

FAQ

Related in Guides

+ -
diff --git a/docs/blog/gemini-4-argon-vs-claude-opus-5-5/index.html b/docs/blog/gemini-4-argon-vs-claude-opus-5-5/index.html index 43fb772c0..71829350b 100644 --- a/docs/blog/gemini-4-argon-vs-claude-opus-5-5/index.html +++ b/docs/blog/gemini-4-argon-vs-claude-opus-5-5/index.html @@ -452,6 +452,22 @@

FAQ

Related in Comparisons

+ -
diff --git a/docs/blog/gemini-cli-vs-claude-code/index.html b/docs/blog/gemini-cli-vs-claude-code/index.html index 562a6669c..dbbd2bd0d 100644 --- a/docs/blog/gemini-cli-vs-claude-code/index.html +++ b/docs/blog/gemini-cli-vs-claude-code/index.html @@ -335,6 +335,22 @@

FAQ

Related in Comparisons

+ -
diff --git a/docs/blog/github-copilot-cli-vs-claude-code-for-hive-work/index.html b/docs/blog/github-copilot-cli-vs-claude-code-for-hive-work/index.html index c397cd31b..0a208342e 100644 --- a/docs/blog/github-copilot-cli-vs-claude-code-for-hive-work/index.html +++ b/docs/blog/github-copilot-cli-vs-claude-code-for-hive-work/index.html @@ -397,6 +397,22 @@

FAQ

Related in Comparisons

+ -
diff --git a/docs/blog/grok-bot-alternatives/index.html b/docs/blog/grok-bot-alternatives/index.html index fd420816e..6046b06ca 100644 --- a/docs/blog/grok-bot-alternatives/index.html +++ b/docs/blog/grok-bot-alternatives/index.html @@ -387,6 +387,22 @@

FAQ

Related in Comparisons

+ -
diff --git a/docs/blog/grok-bot-pricing/index.html b/docs/blog/grok-bot-pricing/index.html index e605903e3..aa3c41504 100644 --- a/docs/blog/grok-bot-pricing/index.html +++ b/docs/blog/grok-bot-pricing/index.html @@ -370,6 +370,22 @@

FAQ

Related in Comparisons

+ -
diff --git a/docs/blog/herdr-alternatives/index.html b/docs/blog/herdr-alternatives/index.html index 06ebfbba9..44f320098 100644 --- a/docs/blog/herdr-alternatives/index.html +++ b/docs/blog/herdr-alternatives/index.html @@ -286,6 +286,22 @@

FAQ

Related in Comparisons

+ -
diff --git a/docs/blog/hermes-agent-vs-openclaw/index.html b/docs/blog/hermes-agent-vs-openclaw/index.html index be7d885d9..7454775f4 100644 --- a/docs/blog/hermes-agent-vs-openclaw/index.html +++ b/docs/blog/hermes-agent-vs-openclaw/index.html @@ -353,6 +353,22 @@

FAQ

Related in Comparisons

+ -
diff --git a/docs/blog/how-ai-answer-engines-choose-sources/index.html b/docs/blog/how-ai-answer-engines-choose-sources/index.html index ba156393c..433f19516 100644 --- a/docs/blog/how-ai-answer-engines-choose-sources/index.html +++ b/docs/blog/how-ai-answer-engines-choose-sources/index.html @@ -322,6 +322,22 @@

FAQ

Related in Comparisons

+ -
diff --git a/docs/blog/how-to-add-a-github-copilot-cli-agent/index.html b/docs/blog/how-to-add-a-github-copilot-cli-agent/index.html index 4af01cdf9..53ff88f74 100644 --- a/docs/blog/how-to-add-a-github-copilot-cli-agent/index.html +++ b/docs/blog/how-to-add-a-github-copilot-cli-agent/index.html @@ -302,6 +302,22 @@

FAQ

Related in Guides

+ -
diff --git a/docs/blog/how-to-add-an-mcp-server-to-claude-code/index.html b/docs/blog/how-to-add-an-mcp-server-to-claude-code/index.html index c68796bb2..b4f7e1ec3 100644 --- a/docs/blog/how-to-add-an-mcp-server-to-claude-code/index.html +++ b/docs/blog/how-to-add-an-mcp-server-to-claude-code/index.html @@ -457,6 +457,22 @@

FAQ

Related in Guides

+ -
diff --git a/docs/blog/how-to-choose-a-multi-agent-tool/index.html b/docs/blog/how-to-choose-a-multi-agent-tool/index.html index 9a6cd023c..0dfd3a18c 100644 --- a/docs/blog/how-to-choose-a-multi-agent-tool/index.html +++ b/docs/blog/how-to-choose-a-multi-agent-tool/index.html @@ -337,6 +337,22 @@

Run the checklist Related in Comparisons

+ -
diff --git a/docs/blog/how-to-connect-ollama-to-claude-code/index.html b/docs/blog/how-to-connect-ollama-to-claude-code/index.html index d81782b4e..fb3520c85 100644 --- a/docs/blog/how-to-connect-ollama-to-claude-code/index.html +++ b/docs/blog/how-to-connect-ollama-to-claude-code/index.html @@ -298,6 +298,22 @@

FAQ

Related in Guides

+ -
diff --git a/docs/blog/how-to-hire-from-the-agent-gallery/index.html b/docs/blog/how-to-hire-from-the-agent-gallery/index.html index 62679c9c6..95bbf8f82 100644 --- a/docs/blog/how-to-hire-from-the-agent-gallery/index.html +++ b/docs/blog/how-to-hire-from-the-agent-gallery/index.html @@ -313,6 +313,22 @@

FAQ

Related in Guides

+ -
diff --git a/docs/blog/how-to-install-and-use-munder-difflin/index.html b/docs/blog/how-to-install-and-use-munder-difflin/index.html index 5ab76ff2e..3d07b1797 100644 --- a/docs/blog/how-to-install-and-use-munder-difflin/index.html +++ b/docs/blog/how-to-install-and-use-munder-difflin/index.html @@ -495,6 +495,22 @@

FAQ

Related in Guides

+ -
diff --git a/docs/blog/how-to-install-codex-cli/index.html b/docs/blog/how-to-install-codex-cli/index.html index f40ddf754..398a40d86 100644 --- a/docs/blog/how-to-install-codex-cli/index.html +++ b/docs/blog/how-to-install-codex-cli/index.html @@ -347,6 +347,22 @@

FAQ

Related in Guides

+ -
diff --git a/docs/blog/how-to-install-gemini-cli/index.html b/docs/blog/how-to-install-gemini-cli/index.html index 6dcf1983c..d2ad7c05f 100644 --- a/docs/blog/how-to-install-gemini-cli/index.html +++ b/docs/blog/how-to-install-gemini-cli/index.html @@ -304,6 +304,22 @@

FAQ

Related in Guides

+ -
diff --git a/docs/blog/how-to-run-multiple-claude-code-agents/index.html b/docs/blog/how-to-run-multiple-claude-code-agents/index.html index 6866c2a9f..41b8d1a2b 100644 --- a/docs/blog/how-to-run-multiple-claude-code-agents/index.html +++ b/docs/blog/how-to-run-multiple-claude-code-agents/index.html @@ -270,6 +270,22 @@

Next steps + - diff --git a/docs/blog/how-to-talk-to-your-orchestrator/index.html b/docs/blog/how-to-talk-to-your-orchestrator/index.html index ee01349ea..1f84b3ee3 100644 --- a/docs/blog/how-to-talk-to-your-orchestrator/index.html +++ b/docs/blog/how-to-talk-to-your-orchestrator/index.html @@ -293,6 +293,22 @@

FAQ

Related in Guides

+ -
diff --git a/docs/blog/how-to-use-claude-code-plan-mode/index.html b/docs/blog/how-to-use-claude-code-plan-mode/index.html index a7632050b..25422457b 100644 --- a/docs/blog/how-to-use-claude-code-plan-mode/index.html +++ b/docs/blog/how-to-use-claude-code-plan-mode/index.html @@ -413,6 +413,22 @@

FAQ

Related in Guides

+ -
diff --git a/docs/blog/how-to-use-claude-code/index.html b/docs/blog/how-to-use-claude-code/index.html index 3539c2f67..347d678f3 100644 --- a/docs/blog/how-to-use-claude-code/index.html +++ b/docs/blog/how-to-use-claude-code/index.html @@ -304,6 +304,7 @@

Common mistakes Claude Code best practices.

Where to go next

Once the basics feel boring, pick one:

    @@ -374,6 +375,22 @@

    FAQ

    Related in Guides

    + -
    diff --git a/docs/blog/how-to-use-gemini-4-argon/index.html b/docs/blog/how-to-use-gemini-4-argon/index.html index 4e6370f8e..da2723b71 100644 --- a/docs/blog/how-to-use-gemini-4-argon/index.html +++ b/docs/blog/how-to-use-gemini-4-argon/index.html @@ -316,6 +316,22 @@

    FAQ

    Related in Guides

    + -
    diff --git a/docs/blog/how-to-use-the-built-in-monaco-ide/index.html b/docs/blog/how-to-use-the-built-in-monaco-ide/index.html index d4db208e2..e60654e53 100644 --- a/docs/blog/how-to-use-the-built-in-monaco-ide/index.html +++ b/docs/blog/how-to-use-the-built-in-monaco-ide/index.html @@ -291,6 +291,22 @@

    FAQ

    Related in Guides

    + -
    diff --git a/docs/blog/human-in-the-loop-approving-ai-agents/index.html b/docs/blog/human-in-the-loop-approving-ai-agents/index.html index 412fbd86b..12939b63e 100644 --- a/docs/blog/human-in-the-loop-approving-ai-agents/index.html +++ b/docs/blog/human-in-the-loop-approving-ai-agents/index.html @@ -326,6 +326,22 @@

    FAQ

    Related in Guides

    + -
    diff --git a/docs/blog/index.html b/docs/blog/index.html index 641f9d2b4..344e8a2fa 100644 --- a/docs/blog/index.html +++ b/docs/blog/index.html @@ -135,13 +135,13 @@

    The Munder Difflin Blog.

    building a self-coordinating hive of agents with a GOD orchestrator you talk to.

    + -
    + + + + + + + + + + +
    +
    + - -
    -
    - - - - - - - -
    -
    -
    +
    diff --git a/docs/blog/indian-open-source-ai-projects-vs-big-tech/index.html b/docs/blog/indian-open-source-ai-projects-vs-big-tech/index.html index dd08d4d96..eb2e8f7ca 100644 --- a/docs/blog/indian-open-source-ai-projects-vs-big-tech/index.html +++ b/docs/blog/indian-open-source-ai-projects-vs-big-tech/index.html @@ -628,6 +628,22 @@

    FAQ

    Related in Comparisons

    + -
    diff --git a/docs/blog/local-first-vs-cloud-agent-sdks/index.html b/docs/blog/local-first-vs-cloud-agent-sdks/index.html index 05630ec29..0e1c10e8e 100644 --- a/docs/blog/local-first-vs-cloud-agent-sdks/index.html +++ b/docs/blog/local-first-vs-cloud-agent-sdks/index.html @@ -368,6 +368,22 @@

    FAQ

    Related in Comparisons

    + -
    diff --git a/docs/blog/manage-multiple-claude-code-sessions/index.html b/docs/blog/manage-multiple-claude-code-sessions/index.html index 9a3f78b34..63e19b80f 100644 --- a/docs/blog/manage-multiple-claude-code-sessions/index.html +++ b/docs/blog/manage-multiple-claude-code-sessions/index.html @@ -324,6 +324,22 @@

    FAQ

    Related in Guides

    + -
    diff --git a/docs/blog/mcp-and-skills-in-a-hive/index.html b/docs/blog/mcp-and-skills-in-a-hive/index.html index a7b13c708..137f6dd4d 100644 --- a/docs/blog/mcp-and-skills-in-a-hive/index.html +++ b/docs/blog/mcp-and-skills-in-a-hive/index.html @@ -339,6 +339,22 @@

    FAQ

    Related in Guides

    + -
    diff --git a/docs/blog/mcp-security-tool-poisoning/index.html b/docs/blog/mcp-security-tool-poisoning/index.html index fb22d849f..f8cdb1723 100644 --- a/docs/blog/mcp-security-tool-poisoning/index.html +++ b/docs/blog/mcp-security-tool-poisoning/index.html @@ -328,6 +328,22 @@

    FAQ

    Related in Guides

    + -
    diff --git a/docs/blog/meta-muse-alternatives/index.html b/docs/blog/meta-muse-alternatives/index.html index 7743676b3..964982b43 100644 --- a/docs/blog/meta-muse-alternatives/index.html +++ b/docs/blog/meta-muse-alternatives/index.html @@ -345,6 +345,22 @@

    FAQ

    Related in Comparisons

    + -
    diff --git a/docs/blog/meta-muse-grok-bot-privacy/index.html b/docs/blog/meta-muse-grok-bot-privacy/index.html index 6d0ccbe1c..299f7ecfc 100644 --- a/docs/blog/meta-muse-grok-bot-privacy/index.html +++ b/docs/blog/meta-muse-grok-bot-privacy/index.html @@ -332,6 +332,22 @@

    FAQ

    Related in Comparisons

    + -
    diff --git a/docs/blog/meta-muse-vs-grok-bot/index.html b/docs/blog/meta-muse-vs-grok-bot/index.html index 7db017bea..2860a088f 100644 --- a/docs/blog/meta-muse-vs-grok-bot/index.html +++ b/docs/blog/meta-muse-vs-grok-bot/index.html @@ -336,6 +336,22 @@

    FAQ

    Related in Comparisons

    + -
    diff --git a/docs/blog/munder-difflin-v0-2-4-feature-walkthrough/index.html b/docs/blog/munder-difflin-v0-2-4-feature-walkthrough/index.html index 54545e943..718bcac4a 100644 --- a/docs/blog/munder-difflin-v0-2-4-feature-walkthrough/index.html +++ b/docs/blog/munder-difflin-v0-2-4-feature-walkthrough/index.html @@ -304,6 +304,22 @@

    FAQ

    Related in Guides

    + -
    diff --git a/docs/blog/munder-difflin-vs-chatgpt-dots/index.html b/docs/blog/munder-difflin-vs-chatgpt-dots/index.html index 95a104961..dfbc2e38e 100644 --- a/docs/blog/munder-difflin-vs-chatgpt-dots/index.html +++ b/docs/blog/munder-difflin-vs-chatgpt-dots/index.html @@ -358,6 +358,22 @@

    FAQ

    Related in Comparisons

    + -
    diff --git a/docs/blog/muse-code-vs-claude-code/index.html b/docs/blog/muse-code-vs-claude-code/index.html index 45edf7962..a6613b2c8 100644 --- a/docs/blog/muse-code-vs-claude-code/index.html +++ b/docs/blog/muse-code-vs-claude-code/index.html @@ -331,6 +331,22 @@

    FAQ

    Related in Comparisons

    + -
    diff --git a/docs/blog/omniagent-alternatives/index.html b/docs/blog/omniagent-alternatives/index.html index 5f18f76d0..c4a998b97 100644 --- a/docs/blog/omniagent-alternatives/index.html +++ b/docs/blog/omniagent-alternatives/index.html @@ -307,6 +307,22 @@

    FAQ

    Related in Comparisons

    + -
    diff --git a/docs/blog/open-source-ai-agents/index.html b/docs/blog/open-source-ai-agents/index.html index 1cb123fcf..8bc81b52b 100644 --- a/docs/blog/open-source-ai-agents/index.html +++ b/docs/blog/open-source-ai-agents/index.html @@ -197,7 +197,7 @@

    Personal assistants a

    3. Hermes Agent. Hermes Agent from Nous Research is built around a learning loop: it curates its own memory, writes skills after complex tasks and searches its past sessions. It has a built in cron scheduler, talks over Telegram, Slack, Signal and more, and runs on a laptop, a VPS or serverless backends. Best for: a server agent that improves with use. Our Hermes Agent explainer covers the install command, the models it takes and what it costs.

    4. NanoClaw. NanoClaw is a lightweight alternative to OpenClaw that runs each agent in its own Linux container, so isolation comes from the operating system rather than permission checks. Its author writes that they couldn’t have slept after giving software they didn’t understand full access to their life, which is a fair review of most agents. Best for: reading every line of the thing that touches your accounts.

    Coding agents

    -

    5. OpenCode. OpenCode is an MIT terminal coding agent that works with many model providers, local models included. Our what is OpenCode explainer walks through it. Best for: a Claude Code style agent you can fork.

    +

    5. OpenCode. OpenCode is an MIT terminal coding agent that works with many model providers, local models included. Our what is OpenCode explainer walks through it. Leaving it? See our OpenCode alternatives. Best for: a Claude Code style agent you can fork.

    6. Cline. Cline (Apache 2.0) calls itself the open source coding agent in your IDE, terminal and desktop. Best for: working inside your editor.

    7. Aider. Aider (Apache 2.0) is AI pair programming in your terminal, with git built into the loop. Its latest release is from August 2025. Best for: a small, proven tool, if slow updates are fine.

    One more for the terminal: Pi, Earendil’s MIT licensed agent harness, which leaves out plan mode and subagents on purpose. Our what is Pi agent explainer covers its 1.0 release.

    @@ -374,6 +374,22 @@

    FAQ

    Related in Comparisons

    + -
    diff --git a/docs/blog/openclaw-alternatives/index.html b/docs/blog/openclaw-alternatives/index.html index 6fe25c5ca..7f1ad54b2 100644 --- a/docs/blog/openclaw-alternatives/index.html +++ b/docs/blog/openclaw-alternatives/index.html @@ -359,6 +359,22 @@

    FAQ

    Related in Comparisons

    + -
    diff --git a/docs/blog/opencode-2-0/index.html b/docs/blog/opencode-2-0/index.html index 6e90affec..3658d7429 100644 --- a/docs/blog/opencode-2-0/index.html +++ b/docs/blog/opencode-2-0/index.html @@ -329,8 +329,8 @@

    FAQ

    diff --git a/docs/blog/orca-vs-munder-difflin/index.html b/docs/blog/orca-vs-munder-difflin/index.html index a90aea526..5d548d07c 100644 --- a/docs/blog/orca-vs-munder-difflin/index.html +++ b/docs/blog/orca-vs-munder-difflin/index.html @@ -346,6 +346,22 @@

    FAQ

    Related in Comparisons

    + -
    diff --git a/docs/blog/prompt-caching-for-ai-agents/index.html b/docs/blog/prompt-caching-for-ai-agents/index.html index 99d47f099..83a228005 100644 --- a/docs/blog/prompt-caching-for-ai-agents/index.html +++ b/docs/blog/prompt-caching-for-ai-agents/index.html @@ -358,6 +358,22 @@

    FAQ

    Related in Guides

    + -
    diff --git a/docs/blog/qm-vs-munder-difflin/index.html b/docs/blog/qm-vs-munder-difflin/index.html index 52c7fc8f0..a1966bc95 100644 --- a/docs/blog/qm-vs-munder-difflin/index.html +++ b/docs/blog/qm-vs-munder-difflin/index.html @@ -357,6 +357,22 @@

    FAQ

    Related in Comparisons

    + -
    diff --git a/docs/blog/run-a-mixed-engine-office/index.html b/docs/blog/run-a-mixed-engine-office/index.html index 9a41c2b1f..360159d47 100644 --- a/docs/blog/run-a-mixed-engine-office/index.html +++ b/docs/blog/run-a-mixed-engine-office/index.html @@ -286,6 +286,22 @@

    FAQ

    Related in Guides

    + -
    diff --git a/docs/blog/run-ai-agent-hive-from-slack-setup/index.html b/docs/blog/run-ai-agent-hive-from-slack-setup/index.html index f20a023b0..24d982672 100644 --- a/docs/blog/run-ai-agent-hive-from-slack-setup/index.html +++ b/docs/blog/run-ai-agent-hive-from-slack-setup/index.html @@ -385,6 +385,22 @@

    FAQ

    Related in Guides

    + -
    diff --git a/docs/blog/run-munder-difflin-on-a-mac-mini/index.html b/docs/blog/run-munder-difflin-on-a-mac-mini/index.html index dd4e82015..1c144428b 100644 --- a/docs/blog/run-munder-difflin-on-a-mac-mini/index.html +++ b/docs/blog/run-munder-difflin-on-a-mac-mini/index.html @@ -419,6 +419,22 @@

    FAQ

    Related in Guides

    + -
    diff --git a/docs/blog/run-munder-difflin-on-open-models/index.html b/docs/blog/run-munder-difflin-on-open-models/index.html index 94f041f4b..4f978277d 100644 --- a/docs/blog/run-munder-difflin-on-open-models/index.html +++ b/docs/blog/run-munder-difflin-on-open-models/index.html @@ -535,6 +535,22 @@

    FAQ

    Related in Guides

    + -
    diff --git a/docs/blog/security-for-ai-coding-agents/index.html b/docs/blog/security-for-ai-coding-agents/index.html index 45c8ab71a..5c6f248f3 100644 --- a/docs/blog/security-for-ai-coding-agents/index.html +++ b/docs/blog/security-for-ai-coding-agents/index.html @@ -323,6 +323,22 @@

    FAQ

    Related in Guides

    + -
    diff --git a/docs/blog/small-team-ai-agents-playbook/index.html b/docs/blog/small-team-ai-agents-playbook/index.html index 184ae92d8..5566a1c73 100644 --- a/docs/blog/small-team-ai-agents-playbook/index.html +++ b/docs/blog/small-team-ai-agents-playbook/index.html @@ -328,6 +328,22 @@

    FAQ

    Related in Guides

    + -
    diff --git a/docs/blog/tags/claude-code/index.html b/docs/blog/tags/claude-code/index.html index 95c5bf006..84aa7fcd5 100644 --- a/docs/blog/tags/claude-code/index.html +++ b/docs/blog/tags/claude-code/index.html @@ -105,7 +105,7 @@

    Tag

    #Claude Code

    -

    90 posts tagged Claude Code.

    +

    91 posts tagged Claude Code.

    @@ -125,6 +125,22 @@

    Agent Tools Today, 15 Oct 2026

    +
    diff --git a/docs/blog/tags/cli-agents/index.html b/docs/blog/tags/cli-agents/index.html index 936466fd3..98848393c 100644 --- a/docs/blog/tags/cli-agents/index.html +++ b/docs/blog/tags/cli-agents/index.html @@ -105,7 +105,7 @@

    Tag

    #CLI Agents

    -

    21 posts tagged CLI Agents.

    +

    22 posts tagged CLI Agents.

    @@ -125,6 +125,22 @@

    OpenCode 2.0: what changed and how to upgrade<

    +
    diff --git a/docs/blog/tags/comparisons/index.html b/docs/blog/tags/comparisons/index.html index b997d3dd4..7e2d71e76 100644 --- a/docs/blog/tags/comparisons/index.html +++ b/docs/blog/tags/comparisons/index.html @@ -105,11 +105,27 @@

    Tag

    #Comparisons

    -

    43 posts tagged Comparisons.

    +

    44 posts tagged Comparisons.

    +
    diff --git a/docs/blog/tags/engines/index.html b/docs/blog/tags/engines/index.html index 59b946c86..a71a157fe 100644 --- a/docs/blog/tags/engines/index.html +++ b/docs/blog/tags/engines/index.html @@ -105,7 +105,7 @@

    Tag

    #Engines

    -

    18 posts tagged Engines.

    +

    19 posts tagged Engines.

    @@ -125,6 +125,22 @@

    OpenCode 2.0: what changed and how to upgrade<

    +
    diff --git a/docs/blog/tags/getting-started/index.html b/docs/blog/tags/getting-started/index.html index 925dd56e0..8771ee59b 100644 --- a/docs/blog/tags/getting-started/index.html +++ b/docs/blog/tags/getting-started/index.html @@ -105,11 +105,27 @@

    Tag

    #Getting Started

    -

    20 posts tagged Getting Started.

    +

    21 posts tagged Getting Started.

    +
    diff --git a/docs/blog/tags/guides/index.html b/docs/blog/tags/guides/index.html index d0612c1b2..5563decb2 100644 --- a/docs/blog/tags/guides/index.html +++ b/docs/blog/tags/guides/index.html @@ -105,11 +105,27 @@

    Tag

    #Guides

    -

    40 posts tagged Guides.

    +

    41 posts tagged Guides.

    +
    diff --git a/docs/blog/tags/open-source/index.html b/docs/blog/tags/open-source/index.html index 6b099dac9..f9334cb95 100644 --- a/docs/blog/tags/open-source/index.html +++ b/docs/blog/tags/open-source/index.html @@ -105,7 +105,7 @@

    Tag

    #Open Source

    -

    65 posts tagged Open Source.

    +

    66 posts tagged Open Source.

    @@ -141,6 +141,22 @@

    OpenCode 2.0: what changed and how to upgrade<

    +
    diff --git a/docs/blog/the-lethal-trifecta-for-coding-agents/index.html b/docs/blog/the-lethal-trifecta-for-coding-agents/index.html index 6fd0deed4..472bc9bc3 100644 --- a/docs/blog/the-lethal-trifecta-for-coding-agents/index.html +++ b/docs/blog/the-lethal-trifecta-for-coding-agents/index.html @@ -346,6 +346,22 @@

    FAQ

    Related in Guides

    + -
    diff --git a/docs/blog/the-multi-agent-cost-playbook/index.html b/docs/blog/the-multi-agent-cost-playbook/index.html index 074e823ed..9db9bbc7e 100644 --- a/docs/blog/the-multi-agent-cost-playbook/index.html +++ b/docs/blog/the-multi-agent-cost-playbook/index.html @@ -328,6 +328,22 @@

    FAQ

    Related in Guides

    + -
    diff --git a/docs/blog/tmux-and-scripts-vs-an-agent-harness/index.html b/docs/blog/tmux-and-scripts-vs-an-agent-harness/index.html index 388e3c79e..2972f946e 100644 --- a/docs/blog/tmux-and-scripts-vs-an-agent-harness/index.html +++ b/docs/blog/tmux-and-scripts-vs-an-agent-harness/index.html @@ -283,6 +283,22 @@

    FAQ

    Related in Comparisons

    + -
    diff --git a/docs/blog/topics/comparisons/index.html b/docs/blog/topics/comparisons/index.html index 27518cc75..733dd53e3 100644 --- a/docs/blog/topics/comparisons/index.html +++ b/docs/blog/topics/comparisons/index.html @@ -114,11 +114,11 @@

    comparisons

    Honest side-by-sides with the other ways to

    +
    diff --git a/docs/blog/topics/concepts/index.html b/docs/blog/topics/concepts/index.html index f2bc06272..0dcdb5946 100644 --- a/docs/blog/topics/concepts/index.html +++ b/docs/blog/topics/concepts/index.html @@ -114,11 +114,11 @@

    concepts

    The ideas underneath the product — harnesses,

    +
    diff --git a/docs/blog/topics/index.html b/docs/blog/topics/index.html index fc4f10e3a..89918fb33 100644 --- a/docs/blog/topics/index.html +++ b/docs/blog/topics/index.html @@ -113,19 +113,19 @@

    Topics

    - - + +
    -

    concepts

    -

    51 posts.

    +

    guides

    +

    52 posts.

    View topic →
    - - + +
    -

    guides

    +

    concepts

    51 posts.

    View topic →
    @@ -135,7 +135,7 @@

    guides

    comparisons

    -

    43 posts.

    +

    44 posts.

    View topic →
    @@ -200,17 +200,17 @@

    memory

    Tags - Claude Code90 + Claude Code91 Multi-Agent89 - Open Source65 + Open Source66 Concepts48 - Comparisons43 + Comparisons44 - Guides40 + Guides41 AI Agents29 @@ -220,9 +220,9 @@

    memory

    Local-First22 - CLI Agents21 + CLI Agents22 - Getting Started20 + Getting Started21 Story20 @@ -230,7 +230,7 @@

    memory

    Codex19 - Engines18 + Engines19 Release16 diff --git a/docs/blog/topics/internals/index.html b/docs/blog/topics/internals/index.html index 7b0b8d92b..abef1d728 100644 --- a/docs/blog/topics/internals/index.html +++ b/docs/blog/topics/internals/index.html @@ -114,11 +114,11 @@

    internals

    How the machine actually works: postmortems, a

    diff --git a/docs/blog/trigger-ai-agents-from-slack/index.html b/docs/blog/trigger-ai-agents-from-slack/index.html index fa6e85af1..b69a9db51 100644 --- a/docs/blog/trigger-ai-agents-from-slack/index.html +++ b/docs/blog/trigger-ai-agents-from-slack/index.html @@ -324,6 +324,22 @@

    FAQ

    Related in Guides

    + -
    diff --git a/docs/blog/vibe-kanban-alternative/index.html b/docs/blog/vibe-kanban-alternative/index.html index df1cc9dd9..4979d0c4a 100644 --- a/docs/blog/vibe-kanban-alternative/index.html +++ b/docs/blog/vibe-kanban-alternative/index.html @@ -273,6 +273,22 @@

    Board or hive? How to choose

    Related in Comparisons

    + -
    diff --git a/docs/blog/whats-new-in-agentic-ai-june-2026/index.html b/docs/blog/whats-new-in-agentic-ai-june-2026/index.html index 0870ea17f..f41bafc1f 100644 --- a/docs/blog/whats-new-in-agentic-ai-june-2026/index.html +++ b/docs/blog/whats-new-in-agentic-ai-june-2026/index.html @@ -383,6 +383,22 @@

    FAQ

    Related in Guides

    + -
    diff --git a/docs/blog/why-does-claude-code-keep-asking-for-permission/index.html b/docs/blog/why-does-claude-code-keep-asking-for-permission/index.html index 9f3bb4a58..99a15b2a3 100644 --- a/docs/blog/why-does-claude-code-keep-asking-for-permission/index.html +++ b/docs/blog/why-does-claude-code-keep-asking-for-permission/index.html @@ -289,6 +289,22 @@

    FAQ

    Related in Guides

    + -
    diff --git a/docs/blog/your-first-hour-with-munder-difflin/index.html b/docs/blog/your-first-hour-with-munder-difflin/index.html index 5106f484d..5c07a5b9e 100644 --- a/docs/blog/your-first-hour-with-munder-difflin/index.html +++ b/docs/blog/your-first-hour-with-munder-difflin/index.html @@ -348,6 +348,22 @@

    FAQ

    Related in Guides

    + -
    diff --git a/docs/llms.txt b/docs/llms.txt index fba85c99f..225dfe0a9 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -68,61 +68,7 @@ Saturday 24 October 2026. ## Blog -218 posts, grouped by topic, newest first in each topic. Topic hubs: https://munderdiffl.in/blog/topics/ - -### concepts - -- [OpenCode 2.0: what changed and how to upgrade](https://munderdiffl.in/blog/opencode-2-0/): OpenCode 2.0 from Anomaly: the background service, Code Mode, background subagents, the new plugin API and SDK, and how to upgrade. Checked 15 Oct 2026. -- [Claude Code Router: What It Is, How It Works and How to Install It](https://munderdiffl.in/blog/claude-code-router/): Claude Code Router, checked 14 Oct 2026: what the local gateway does, how to install it, which providers it lists, and what Anthropic says about it. -- [Google Let Gemini 4 Argon Loose on Its Own Coding Backlog. Here Are the Receipts.](https://munderdiffl.in/blog/gemini-4-argon-inside-google/): Google's own numbers for Gemini 4 Argon on real code: 134 bugs triaged in under 3 hours, 30% fewer rollbacks, a framework tuned overnight. Checked 13 Oct 2026. -- [Gemini 4 Argon: Google's Flagship Model Shares a Name With a Gas That Barely Reacts](https://munderdiffl.in/blog/gemini-4-argon/): Gemini 4 Argon went on general release on 13 Oct 2026: $2 input and $10 output per 1M tokens through 30 Nov, a 1M token window, and where you can use it. -- [Muse Wonder and Muse Code: Meta's new models, prices and scores](https://munderdiffl.in/blog/muse-wonder/): Meta's Muse Wonder and Muse Wonder Full: API price, Muse Code plans, context window and Meta's own coding scores, checked 13 Oct 2026. -- [Claude routines: what they are, triggers, cost and limits](https://munderdiffl.in/blog/claude-code-routines/): Claude routines run a saved Claude Code prompt in the cloud on a schedule, an API call or a GitHub event. Plans, steps and limits, checked 12 Oct 2026. -- [Claude Code Projects: what they are and who can use them](https://munderdiffl.in/blog/claude-code-projects/): Claude Code Projects, checked 11 Oct 2026: a public beta on Pro and Max where Claude runs your tasks as parallel threads. Access, limits and plan use. -- [Claude Managed Agents dynamic workflows: limits and cost](https://munderdiffl.in/blog/claude-managed-agents-dynamic-workflows/): Dynamic workflows let a Claude Managed Agents agent write a program that runs many agents. How to turn them on, beta limits and cost, checked 11 Oct 2026. -- [Claude Code agent teams: what they are, how to use them](https://munderdiffl.in/blog/claude-code-agent-teams/): Claude Code agent teams, checked 8 Oct 2026: what they are, the experimental setting that turns them on, how they differ from subagents, token cost and limits. -- [Claude Haiku 5.5: price, benchmarks and where it fits](https://munderdiffl.in/blog/claude-haiku-5-5/): Claude Haiku 5.5 is Anthropic's new small model. Its tiered price, Anthropic's own scores and how to pick it in Claude Code, checked 8 Oct 2026. -- [GPT-6 in ChatGPT: who gets it, when, and Intelligent UI](https://munderdiffl.in/blog/gpt-6-intelligent-ui/): GPT-6 with Intelligent UI in ChatGPT: Plus, Pro, Business and Enterprise from 7 Oct 2026, then Free and Go. Codex models are not changing in this release. -- [Claude Agent SDK: what it is, languages, cost and limits](https://munderdiffl.in/blog/what-is-claude-agent-sdk/): The Claude Agent SDK is Claude Code as a Python and TypeScript library. Install commands, features, billing, licence and limits, checked 8 Oct 2026. -- [ZCode: Z.ai's coding agent harness, explained](https://munderdiffl.in/blog/what-is-zcode/): ZCode is Z.ai's Apache-2.0 coding workspace: desktop app, browser view and terminal agent. How to install it, which models it lists, and what it costs. -- [OpenAI Decisions API: what it is, price and limits](https://munderdiffl.in/blog/openai-decisions-api/): The OpenAI Decisions API is a beta endpoint that returns a probability, a pick or a score. Price, limits and how to call it, checked 7 Oct 2026. -- [Mistral Large 4 (Le Chonk): size, price, scores and weights](https://munderdiffl.in/blog/mistral-large-4/): Mistral Large 4 is a 1 trillion parameter open weight model in public preview. Context window, API price, Mistral's own scores and weights, checked 6 Oct 2026. -- [OpenAI text watermarking: textGrain, who gets it, limits](https://munderdiffl.in/blog/openai-text-watermarking/): OpenAI text watermarking (textGrain) is opt in for the API now and is coming to eligible ChatGPT and Codex text in the EU. Limits checked 6 Oct 2026. -- [Reflection AI Beam: the 501B open weight model, explained](https://munderdiffl.in/blog/reflection-beam/): Reflection AI Beam is a 501B parameter open weight model. Size, Reflection's own scores, licence and when you can get it, checked 6 Oct 2026. -- [What Is Claude Cowork? Plans, Prices and How to Use It](https://munderdiffl.in/blog/what-is-claude-cowork/): Claude Cowork is Anthropic's agent for multi-step office work. Which plans include it, what it costs, where it runs and how to start, checked 3 Oct 2026. -- [Gemini Spark: what it is, what it costs and who can get it](https://munderdiffl.in/blog/what-is-gemini-spark/): Gemini Spark is Google's personal AI agent in the Gemini app. Plans, US prices, countries, limits and how it compares with Grok Bot, checked 3 Oct 2026. -- [Hermes Agent: what it is, how to install it and what it costs](https://munderdiffl.in/blog/what-is-hermes-agent/): Hermes Agent is Nous Research's MIT licensed AI agent with memory, skills and chat channels. Install command, models, pricing and how it compares. -- [What Is Codex? OpenAI's Coding Agent in the CLI, App and Cloud](https://munderdiffl.in/blog/what-is-codex/): What is Codex? OpenAI's coding agent runs in your terminal, the ChatGPT desktop app, your IDE and the cloud. Which plans include it, checked 2 Oct 2026. -- [What is Pi agent? The open source coding agent harness, explained](https://munderdiffl.in/blog/what-is-pi-agent/): Pi agent is Earendil's MIT licensed coding agent harness. What it is, how to install it, what Pi 1.0 and Pi Durable add, and how it compares. -- [What Is ChatGPT Dots? OpenAI's Always On Agents Explained](https://munderdiffl.in/blog/what-is-chatgpt-dots/): A ChatGPT dot is an always on agent with its own cloud computer. Needs Pro (from $100 a month) or Business Premium, not Plus. Not on Pro in the UK or EU. -- [What Is OpenCode? Free, MIT Licensed, and What You Pay For](https://munderdiffl.in/blog/what-is-opencode/): OpenCode is a free, MIT licensed coding agent from Anomaly. The tool costs nothing; you pay for the model you connect, unless it is local or free. -- [Is Ollama Good for Coding? Yes, for Scoped Work on the Right Model](https://munderdiffl.in/blog/is-ollama-good-for-coding/): Ollama is good for coding on scoped, private work if the model fits your memory. Which models to pull, how much RAM, and where Claude still wins. -- [Claude Max Plan: Is It Worth It? Max 5x vs 20x vs Pro](https://munderdiffl.in/blog/is-claude-code-max-worth-it/): Is the Claude Max plan worth it? Max 5x and 20x against Pro, checked 29 Sep 2026: price, five hour and weekly limits, Fable, and the API break even. -- [Is Claude Code Open Source?](https://munderdiffl.in/blog/is-claude-code-open-source/): No. Claude Code is proprietary under Anthropic's terms. What its GitHub repo holds, what the 2026 leak changed, and which open tools sit around it. -- [What Is Vibe Coding? Meaning, Risks and Safer Habits](https://munderdiffl.in/blog/what-is-vibe-coding/): Vibe coding means building software from AI prompts without reading the code. Where the term came from, when it is fine, where it bites, and what to check. -- [What Is an MCP Server?](https://munderdiffl.in/blog/what-is-an-mcp-server/): An MCP server gives an AI agent tools, data and prompts over one open protocol. What it is, the host, client and server roles, and where Claude Code plugs in. -- [Claude Code Pricing (Sep 2026): Every Plan, and Is It Free?](https://munderdiffl.in/blog/how-much-does-claude-code-cost/): Claude Code pricing checked 29 Sep 2026: Pro, Max, Team, Enterprise and API rates, usage limits, what changed this year, and whether Claude Code is free. -- [Loop Engineering: Designing Agent Loops That Converge](https://munderdiffl.in/blog/loop-engineering-for-ai-agents/): Everyone engineers the prompt; almost nobody engineers the loop around it. Stop conditions, drain loops, retry with backoff, compaction cycles, breaker escalation, budgets, and human gates — the outer-loop mechanics that make an agent converge instead of run away. -- [Review Agent Work Where It Happens](https://munderdiffl.in/blog/review-agent-work-where-it-happens/): AI agents produce diffs faster than humans can review them, and alt-tabbing to an external editor breaks supervision flow. The case for review-in-place: Munder Difflin's built-in Monaco IDE puts a CHANGES rail and side-by-side diffs vs HEAD right on the office floor. -- [Voice Is a Control Plane, Not a Gimmick](https://munderdiffl.in/blog/voice-as-a-control-plane-for-agent-fleets/): Voice is a terrible way to write code and a great way to run a fleet. Why low-bandwidth commands over high-bandwidth work is the right split — with Munder Difflin's Talk mode (echo-back confirmation, spend caps, michael-voice attribution) as the case study. -- [What Is an Agent Development Environment (ADE)?](https://munderdiffl.in/blog/what-is-an-agent-development-environment/): ADE means two things in 2026: a platform for building agents (Letta's sense) and a workspace for shipping code with fleets of coding agents (Orca's sense). Here's the full tooling map — chat IDEs, agent CLIs, agent IDEs, and agent harnesses — and how to pick. -- [What Is Harness Engineering?](https://munderdiffl.in/blog/what-is-harness-engineering/): Harness engineering is the discipline of building everything around the model — PTY plumbing, lifecycle hooks, mailboxes, memory, budgets, human gates, observability — because that's where agent reliability actually comes from. A definition, and four case studies from inside Munder Difflin. -- [Shareable Agent Roles: A Portable Format for Hiring AI Coworkers](https://munderdiffl.in/blog/shareable-agent-roles/): Why agent roles should be portable: what a hire manifest encodes, how one-click hiring stays safe, and how The Hiring Fair turns tacit setup into a shareable artifact. -- [The Clone Army Is a Trap: Why a Mixed Swarm Beats Ten Identical Agents](https://munderdiffl.in/blog/the-clone-army-trap-mixed-swarm-vs-identical-agents/): Spinning up tens of identical top-tier CLI agents feels powerful and burns tokens on work a cheaper agent could do. A mixed-capability swarm with shared memory wins on cost — and often on quality. -- [Why CLI Agents Are So Powerful — and How Munder Difflin Spends Fewer Tokens Doing the Same Work](https://munderdiffl.in/blog/why-cli-agents-are-powerful/): CLI agents are powerful because they have terminal-level access: they run builds, tests, and git, and verify their own work by executing it. Here's why that matters — and the concrete ways Munder Difflin cuts token consumption while doing it. -- [Generative Engine Optimization: The Techniques the Research Backs](https://munderdiffl.in/blog/generative-engine-optimization-techniques/): What the first peer-reviewed GEO study found works to get content cited by AI — statistics, source citations, quotes — and why keyword stuffing backfires. -- [The Agents Rule of Two: A Simple Safety Rule for Coding Agents](https://munderdiffl.in/blog/agents-rule-of-two/): Meta's Agents Rule of Two for coding agents: don't let one session combine untrusted input, private data, and external reach at once — or supervise. -- [Can Claude Code Agents Talk to Each Other?](https://munderdiffl.in/blog/can-claude-code-agents-talk-to-each-other/): Can Claude Code agents talk to each other? By default they report to their launcher — but a small coordination layer lets them message peer-to-peer. -- [Do More With Less: Smart Model Routing Across Agents](https://munderdiffl.in/blog/do-more-with-less-model-routing/): Why a hive of agents shouldn't all run the biggest model — and how routing the right task to the right model cuts cost and latency without losing quality. -- [How AI Agents Verify Their Own Work (Before Saying 'Done')](https://munderdiffl.in/blog/how-ai-agents-verify-their-own-work/): The discipline that makes an autonomous coding agent trustworthy: prove every claim, reproduce the green, and check the fix — not just the intent. -- [Long-Running Agents: The 2026 Shift from Minutes to Hours](https://munderdiffl.in/blog/long-running-agents-the-2026-shift/): Agents used to answer in seconds; now they run for hours. The data behind the shift — and why a longer agent is a different problem, not just a bigger one. -- [Building AI Dev Tools in the Open, On Purpose](https://munderdiffl.in/blog/open-source-ai-tools-on-purpose/): Agents touch your code, keys, and memory. That's why agent tooling should be open source and local-first — so you can verify it, not just trust it. -- [AEO for Dev Tools: Getting Cited by ChatGPT and Claude](https://munderdiffl.in/blog/what-is-aeo-for-dev-tools/): Answer Engine Optimization for dev tools: how to get cited by ChatGPT, Claude, and Perplexity with the right robots.txt, JSON-LD, and writing patterns. -- [When Do AI Agents Actually Need an Agent Protocol?](https://munderdiffl.in/blog/when-do-agents-need-an-agent-protocol/): When do AI agents need a protocol like MCP or A2A? Agents you own coordinate fine with file mailboxes; protocols earn their keep across boundaries. -- [The AI Coding Agent Glossary: Harness, Orchestrator, Hive, Subagent](https://munderdiffl.in/blog/ai-agent-glossary/): Plain-English definitions of the AI coding agent terms everyone trips over — harness, orchestrator, hive, subagent, agent memory — each in one line. -- [Munder Difflin FAQ: Everything People Ask](https://munderdiffl.in/blog/munder-difflin-faq/): Answers to the top Munder Difflin questions — what it is, is it free, does it run locally, which platforms, and how it differs from many terminals. -- [What Are Claude Code Agents? And How to Use Many at Once](https://munderdiffl.in/blog/what-are-claude-code-agents/): Claude Code agents explained in plain English — what an agent actually is, how subagents differ, and the leap from one agent to a coordinated team. -- [What Is an AI Agent Harness? Single vs Multi-Agent Harness Explained](https://munderdiffl.in/blog/what-is-a-multi-agent-harness/): An agent harness is the code around an AI model that runs its tool loop, memory and permissions. Single vs multi-agent harnesses, compared and dated. +220 posts, grouped by topic, newest first in each topic. Topic hubs: https://munderdiffl.in/blog/topics/ ### guides @@ -131,6 +77,7 @@ Saturday 24 October 2026. - [AI Agent Use Cases From a Real Company: Every Agent on Our Floor and the Job It Does](https://munderdiffl.in/blog/ai-agent-use-cases-our-office/): We run our company on Munder Difflin. Every seat on our floor: what each AI agent does, how its work starts, what comes out, and how to copy it. - [How to Get the Most Out of Your Claude Code Max Plan](https://munderdiffl.in/blog/claude-code-max-plan-tips/): Stretch a Claude Code Max plan: model choice, context hygiene, prompt caching, subagents and unattended runs, checked against Anthropic's own pages. - [How to Make the Most of Your Codex Max Plan](https://munderdiffl.in/blog/codex-max-plan-tips/): There is no Codex Max plan. What ChatGPT Pro 5x and 20x give you in Codex, how the weekly limit works, and how to stretch it. +- [Claude Code Best Practices: 9 Habits From Anthropic's Docs](https://munderdiffl.in/blog/claude-code-best-practices/): Nine Claude Code best practices from Anthropic's docs, checked 15 Oct 2026: give Claude a check, plan first, keep memory short, clear context, run in parallel. - [Claude Code Skills: What They Are, Where They Live and How to Write One](https://munderdiffl.in/blog/claude-code-skills/): Claude Code skills, checked 14 Oct 2026: what a skill is, where skill folders live, a first skill file, how Claude picks one and how to share them. - [Gemini 4 Argon Pricing: Half Price Until 30 November, Then the Real Bill Arrives](https://munderdiffl.in/blog/gemini-4-argon-pricing/): Gemini 4 Argon pricing, checked 13 Oct 2026: $2 input and $10 output per 1M tokens through 30 Nov, then $4 and $20. No free tier. Worked examples inside. - [How to Use Gemini 4 Argon: Five Ways In, and Free Is Not One of Them](https://munderdiffl.in/blog/how-to-use-gemini-4-argon/): How to use Gemini 4 Argon on 13 Oct 2026: the Gemini app, AI Mode, AI Studio, the API with model ID gemini-4-argon, and Antigravity. What each route needs. @@ -178,11 +125,66 @@ Saturday 24 October 2026. - [Set Up a Claude Code Multi-Agent Workflow in 10 Minutes](https://munderdiffl.in/blog/claude-code-multi-agent-setup-tutorial/): A from-zero Claude Code multi-agent setup: install Munder Difflin, onboard, spawn a few agents, and watch the GOD orchestrator route your first task. - [Claude Code Subagents: How to Create and Use Them (vs Agent Teams)](https://munderdiffl.in/blog/claude-code-subagents-vs-multi-agent-harness/): Claude Code subagents in 2026: the .claude/agents file, built-in Explore and Plan, models, nesting, and when agent teams or a harness fit better. +### concepts + +- [OpenCode 2.0: what changed and how to upgrade](https://munderdiffl.in/blog/opencode-2-0/): OpenCode 2.0 from Anomaly: the background service, Code Mode, background subagents, the new plugin API and SDK, and how to upgrade. Checked 15 Oct 2026. +- [Claude Code Router: What It Is, How It Works and How to Install It](https://munderdiffl.in/blog/claude-code-router/): Claude Code Router, checked 14 Oct 2026: what the local gateway does, how to install it, which providers it lists, and what Anthropic says about it. +- [Google Let Gemini 4 Argon Loose on Its Own Coding Backlog. Here Are the Receipts.](https://munderdiffl.in/blog/gemini-4-argon-inside-google/): Google's own numbers for Gemini 4 Argon on real code: 134 bugs triaged in under 3 hours, 30% fewer rollbacks, a framework tuned overnight. Checked 13 Oct 2026. +- [Gemini 4 Argon: Google's Flagship Model Shares a Name With a Gas That Barely Reacts](https://munderdiffl.in/blog/gemini-4-argon/): Gemini 4 Argon went on general release on 13 Oct 2026: $2 input and $10 output per 1M tokens through 30 Nov, a 1M token window, and where you can use it. +- [Muse Wonder and Muse Code: Meta's new models, prices and scores](https://munderdiffl.in/blog/muse-wonder/): Meta's Muse Wonder and Muse Wonder Full: API price, Muse Code plans, context window and Meta's own coding scores, checked 13 Oct 2026. +- [Claude routines: what they are, triggers, cost and limits](https://munderdiffl.in/blog/claude-code-routines/): Claude routines run a saved Claude Code prompt in the cloud on a schedule, an API call or a GitHub event. Plans, steps and limits, checked 12 Oct 2026. +- [Claude Code Projects: what they are and who can use them](https://munderdiffl.in/blog/claude-code-projects/): Claude Code Projects, checked 11 Oct 2026: a public beta on Pro and Max where Claude runs your tasks as parallel threads. Access, limits and plan use. +- [Claude Managed Agents dynamic workflows: limits and cost](https://munderdiffl.in/blog/claude-managed-agents-dynamic-workflows/): Dynamic workflows let a Claude Managed Agents agent write a program that runs many agents. How to turn them on, beta limits and cost, checked 11 Oct 2026. +- [Claude Code agent teams: what they are, how to use them](https://munderdiffl.in/blog/claude-code-agent-teams/): Claude Code agent teams, checked 8 Oct 2026: what they are, the experimental setting that turns them on, how they differ from subagents, token cost and limits. +- [Claude Haiku 5.5: price, benchmarks and where it fits](https://munderdiffl.in/blog/claude-haiku-5-5/): Claude Haiku 5.5 is Anthropic's new small model. Its tiered price, Anthropic's own scores and how to pick it in Claude Code, checked 8 Oct 2026. +- [GPT-6 in ChatGPT: who gets it, when, and Intelligent UI](https://munderdiffl.in/blog/gpt-6-intelligent-ui/): GPT-6 with Intelligent UI in ChatGPT: Plus, Pro, Business and Enterprise from 7 Oct 2026, then Free and Go. Codex models are not changing in this release. +- [Claude Agent SDK: what it is, languages, cost and limits](https://munderdiffl.in/blog/what-is-claude-agent-sdk/): The Claude Agent SDK is Claude Code as a Python and TypeScript library. Install commands, features, billing, licence and limits, checked 8 Oct 2026. +- [ZCode: Z.ai's coding agent harness, explained](https://munderdiffl.in/blog/what-is-zcode/): ZCode is Z.ai's Apache-2.0 coding workspace: desktop app, browser view and terminal agent. How to install it, which models it lists, and what it costs. +- [OpenAI Decisions API: what it is, price and limits](https://munderdiffl.in/blog/openai-decisions-api/): The OpenAI Decisions API is a beta endpoint that returns a probability, a pick or a score. Price, limits and how to call it, checked 7 Oct 2026. +- [Mistral Large 4 (Le Chonk): size, price, scores and weights](https://munderdiffl.in/blog/mistral-large-4/): Mistral Large 4 is a 1 trillion parameter open weight model in public preview. Context window, API price, Mistral's own scores and weights, checked 6 Oct 2026. +- [OpenAI text watermarking: textGrain, who gets it, limits](https://munderdiffl.in/blog/openai-text-watermarking/): OpenAI text watermarking (textGrain) is opt in for the API now and is coming to eligible ChatGPT and Codex text in the EU. Limits checked 6 Oct 2026. +- [Reflection AI Beam: the 501B open weight model, explained](https://munderdiffl.in/blog/reflection-beam/): Reflection AI Beam is a 501B parameter open weight model. Size, Reflection's own scores, licence and when you can get it, checked 6 Oct 2026. +- [What Is Claude Cowork? Plans, Prices and How to Use It](https://munderdiffl.in/blog/what-is-claude-cowork/): Claude Cowork is Anthropic's agent for multi-step office work. Which plans include it, what it costs, where it runs and how to start, checked 3 Oct 2026. +- [Gemini Spark: what it is, what it costs and who can get it](https://munderdiffl.in/blog/what-is-gemini-spark/): Gemini Spark is Google's personal AI agent in the Gemini app. Plans, US prices, countries, limits and how it compares with Grok Bot, checked 3 Oct 2026. +- [Hermes Agent: what it is, how to install it and what it costs](https://munderdiffl.in/blog/what-is-hermes-agent/): Hermes Agent is Nous Research's MIT licensed AI agent with memory, skills and chat channels. Install command, models, pricing and how it compares. +- [What Is Codex? OpenAI's Coding Agent in the CLI, App and Cloud](https://munderdiffl.in/blog/what-is-codex/): What is Codex? OpenAI's coding agent runs in your terminal, the ChatGPT desktop app, your IDE and the cloud. Which plans include it, checked 2 Oct 2026. +- [What is Pi agent? The open source coding agent harness, explained](https://munderdiffl.in/blog/what-is-pi-agent/): Pi agent is Earendil's MIT licensed coding agent harness. What it is, how to install it, what Pi 1.0 and Pi Durable add, and how it compares. +- [What Is ChatGPT Dots? OpenAI's Always On Agents Explained](https://munderdiffl.in/blog/what-is-chatgpt-dots/): A ChatGPT dot is an always on agent with its own cloud computer. Needs Pro (from $100 a month) or Business Premium, not Plus. Not on Pro in the UK or EU. +- [What Is OpenCode? Free, MIT Licensed, and What You Pay For](https://munderdiffl.in/blog/what-is-opencode/): OpenCode is a free, MIT licensed coding agent from Anomaly. The tool costs nothing; you pay for the model you connect, unless it is local or free. +- [Is Ollama Good for Coding? Yes, for Scoped Work on the Right Model](https://munderdiffl.in/blog/is-ollama-good-for-coding/): Ollama is good for coding on scoped, private work if the model fits your memory. Which models to pull, how much RAM, and where Claude still wins. +- [Claude Max Plan: Is It Worth It? Max 5x vs 20x vs Pro](https://munderdiffl.in/blog/is-claude-code-max-worth-it/): Is the Claude Max plan worth it? Max 5x and 20x against Pro, checked 29 Sep 2026: price, five hour and weekly limits, Fable, and the API break even. +- [Is Claude Code Open Source?](https://munderdiffl.in/blog/is-claude-code-open-source/): No. Claude Code is proprietary under Anthropic's terms. What its GitHub repo holds, what the 2026 leak changed, and which open tools sit around it. +- [What Is Vibe Coding? Meaning, Risks and Safer Habits](https://munderdiffl.in/blog/what-is-vibe-coding/): Vibe coding means building software from AI prompts without reading the code. Where the term came from, when it is fine, where it bites, and what to check. +- [What Is an MCP Server?](https://munderdiffl.in/blog/what-is-an-mcp-server/): An MCP server gives an AI agent tools, data and prompts over one open protocol. What it is, the host, client and server roles, and where Claude Code plugs in. +- [Claude Code Pricing (Sep 2026): Every Plan, and Is It Free?](https://munderdiffl.in/blog/how-much-does-claude-code-cost/): Claude Code pricing checked 29 Sep 2026: Pro, Max, Team, Enterprise and API rates, usage limits, what changed this year, and whether Claude Code is free. +- [Loop Engineering: Designing Agent Loops That Converge](https://munderdiffl.in/blog/loop-engineering-for-ai-agents/): Everyone engineers the prompt; almost nobody engineers the loop around it. Stop conditions, drain loops, retry with backoff, compaction cycles, breaker escalation, budgets, and human gates — the outer-loop mechanics that make an agent converge instead of run away. +- [Review Agent Work Where It Happens](https://munderdiffl.in/blog/review-agent-work-where-it-happens/): AI agents produce diffs faster than humans can review them, and alt-tabbing to an external editor breaks supervision flow. The case for review-in-place: Munder Difflin's built-in Monaco IDE puts a CHANGES rail and side-by-side diffs vs HEAD right on the office floor. +- [Voice Is a Control Plane, Not a Gimmick](https://munderdiffl.in/blog/voice-as-a-control-plane-for-agent-fleets/): Voice is a terrible way to write code and a great way to run a fleet. Why low-bandwidth commands over high-bandwidth work is the right split — with Munder Difflin's Talk mode (echo-back confirmation, spend caps, michael-voice attribution) as the case study. +- [What Is an Agent Development Environment (ADE)?](https://munderdiffl.in/blog/what-is-an-agent-development-environment/): ADE means two things in 2026: a platform for building agents (Letta's sense) and a workspace for shipping code with fleets of coding agents (Orca's sense). Here's the full tooling map — chat IDEs, agent CLIs, agent IDEs, and agent harnesses — and how to pick. +- [What Is Harness Engineering?](https://munderdiffl.in/blog/what-is-harness-engineering/): Harness engineering is the discipline of building everything around the model — PTY plumbing, lifecycle hooks, mailboxes, memory, budgets, human gates, observability — because that's where agent reliability actually comes from. A definition, and four case studies from inside Munder Difflin. +- [Shareable Agent Roles: A Portable Format for Hiring AI Coworkers](https://munderdiffl.in/blog/shareable-agent-roles/): Why agent roles should be portable: what a hire manifest encodes, how one-click hiring stays safe, and how The Hiring Fair turns tacit setup into a shareable artifact. +- [The Clone Army Is a Trap: Why a Mixed Swarm Beats Ten Identical Agents](https://munderdiffl.in/blog/the-clone-army-trap-mixed-swarm-vs-identical-agents/): Spinning up tens of identical top-tier CLI agents feels powerful and burns tokens on work a cheaper agent could do. A mixed-capability swarm with shared memory wins on cost — and often on quality. +- [Why CLI Agents Are So Powerful — and How Munder Difflin Spends Fewer Tokens Doing the Same Work](https://munderdiffl.in/blog/why-cli-agents-are-powerful/): CLI agents are powerful because they have terminal-level access: they run builds, tests, and git, and verify their own work by executing it. Here's why that matters — and the concrete ways Munder Difflin cuts token consumption while doing it. +- [Generative Engine Optimization: The Techniques the Research Backs](https://munderdiffl.in/blog/generative-engine-optimization-techniques/): What the first peer-reviewed GEO study found works to get content cited by AI — statistics, source citations, quotes — and why keyword stuffing backfires. +- [The Agents Rule of Two: A Simple Safety Rule for Coding Agents](https://munderdiffl.in/blog/agents-rule-of-two/): Meta's Agents Rule of Two for coding agents: don't let one session combine untrusted input, private data, and external reach at once — or supervise. +- [Can Claude Code Agents Talk to Each Other?](https://munderdiffl.in/blog/can-claude-code-agents-talk-to-each-other/): Can Claude Code agents talk to each other? By default they report to their launcher — but a small coordination layer lets them message peer-to-peer. +- [Do More With Less: Smart Model Routing Across Agents](https://munderdiffl.in/blog/do-more-with-less-model-routing/): Why a hive of agents shouldn't all run the biggest model — and how routing the right task to the right model cuts cost and latency without losing quality. +- [How AI Agents Verify Their Own Work (Before Saying 'Done')](https://munderdiffl.in/blog/how-ai-agents-verify-their-own-work/): The discipline that makes an autonomous coding agent trustworthy: prove every claim, reproduce the green, and check the fix — not just the intent. +- [Long-Running Agents: The 2026 Shift from Minutes to Hours](https://munderdiffl.in/blog/long-running-agents-the-2026-shift/): Agents used to answer in seconds; now they run for hours. The data behind the shift — and why a longer agent is a different problem, not just a bigger one. +- [Building AI Dev Tools in the Open, On Purpose](https://munderdiffl.in/blog/open-source-ai-tools-on-purpose/): Agents touch your code, keys, and memory. That's why agent tooling should be open source and local-first — so you can verify it, not just trust it. +- [AEO for Dev Tools: Getting Cited by ChatGPT and Claude](https://munderdiffl.in/blog/what-is-aeo-for-dev-tools/): Answer Engine Optimization for dev tools: how to get cited by ChatGPT, Claude, and Perplexity with the right robots.txt, JSON-LD, and writing patterns. +- [When Do AI Agents Actually Need an Agent Protocol?](https://munderdiffl.in/blog/when-do-agents-need-an-agent-protocol/): When do AI agents need a protocol like MCP or A2A? Agents you own coordinate fine with file mailboxes; protocols earn their keep across boundaries. +- [The AI Coding Agent Glossary: Harness, Orchestrator, Hive, Subagent](https://munderdiffl.in/blog/ai-agent-glossary/): Plain-English definitions of the AI coding agent terms everyone trips over — harness, orchestrator, hive, subagent, agent memory — each in one line. +- [Munder Difflin FAQ: Everything People Ask](https://munderdiffl.in/blog/munder-difflin-faq/): Answers to the top Munder Difflin questions — what it is, is it free, does it run locally, which platforms, and how it differs from many terminals. +- [What Are Claude Code Agents? And How to Use Many at Once](https://munderdiffl.in/blog/what-are-claude-code-agents/): Claude Code agents explained in plain English — what an agent actually is, how subagents differ, and the leap from one agent to a coordinated team. +- [What Is an AI Agent Harness? Single vs Multi-Agent Harness Explained](https://munderdiffl.in/blog/what-is-a-multi-agent-harness/): An agent harness is the code around an AI model that runs its tool loop, memory and permissions. Single vs multi-agent harnesses, compared and dated. + ### comparisons - [Grok Bot Alternatives: 9 Picks, Hosted or on Your Computer](https://munderdiffl.in/blog/grok-bot-alternatives/): Grok Bot runs every Bot on one shared cloud computer in the US. Nine alternatives checked on 30 Sep 2026, from ChatGPT dots to open source agents you run. - [Herdr Alternatives: What to Pick and Who Each One Fits](https://munderdiffl.in/blog/herdr-alternatives/): Herdr alternatives checked on 14 Sep 2026: tmux, Munder Difflin, Claude Squad, cmux, Orca, Conductor, Superset and Emdash, and who each fits. - [OmniAgent Alternatives: What to Use Instead of Databricks Omnigent](https://munderdiffl.in/blog/omniagent-alternatives/): OmniAgent is really Omnigent, the open source meta harness from Databricks. Alternatives checked on 14 Sep 2026, and who each one fits. +- [OpenCode Alternatives: 7 Coding Agents to Switch To](https://munderdiffl.in/blog/opencode-alternatives/): Seven OpenCode alternatives checked on 15 Oct 2026: Claude Code, Codex CLI, Gemini CLI, Kilo Code, Crush, Pi and Goose, by licence, models and release. - [Muse Code vs Claude Code: Plans, Prices, Models and Where They Run](https://munderdiffl.in/blog/muse-code-vs-claude-code/): Muse Code vs Claude Code, checked 14 Oct 2026: the main plans and monthly prices, the models each runs, where each installs, and which one to pick. - [Gemini 4 Argon vs Claude Opus 5.5 vs GPT-6 Astra: Google Published a Table It Does Not Fully Win](https://munderdiffl.in/blog/gemini-4-argon-vs-claude-opus-5-5/): Google's own table: Gemini 4 Argon tops 13 of 19 rows, GPT-6 Astra 3 and Claude Opus 5.5 2. The coding rows split. List prices compared. - [Codex Usage Limits vs Claude Code: What Each Plan Gives You](https://munderdiffl.in/blog/codex-vs-claude-code-usage-limits/): Codex usage limits vs Claude Code usage limits, checked 10 Oct 2026: the five hour window, weekly limits, credits and how to check usage on each plan. diff --git a/docs/sitemap.xml b/docs/sitemap.xml index ab05f462d..b3d21f4ca 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -27,12 +27,24 @@ monthly 0.8 + + https://munderdiffl.in/blog/claude-code-best-practices/ + 2026-10-15 + monthly + 0.8 + https://munderdiffl.in/blog/opencode-2-0/ 2026-10-15 monthly 0.8 + + https://munderdiffl.in/blog/opencode-alternatives/ + 2026-10-15 + monthly + 0.8 + https://munderdiffl.in/blog/ai-agent-use-cases-our-office/ 2026-10-14 @@ -1329,8 +1341,8 @@ monthly 0.8 - https://munderdiffl.in/blog/topics/concepts/weekly0.5 https://munderdiffl.in/blog/topics/guides/weekly0.5 + https://munderdiffl.in/blog/topics/concepts/weekly0.5 https://munderdiffl.in/blog/topics/comparisons/weekly0.5 https://munderdiffl.in/blog/topics/internals/weekly0.5 https://munderdiffl.in/blog/topics/story/weekly0.5