diffwiki 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { spawn as spawn4 } from "child_process";
5
+ import path4 from "path";
6
+ import { fileURLToPath as fileURLToPath4 } from "url";
5
7
  import { Command, Option } from "commander";
6
8
  import {
7
9
  addExternalCollection,
@@ -23,7 +25,13 @@ import {
23
25
  registerPlugin,
24
26
  removeArticle,
25
27
  removePlugin,
28
+ repairPlugins,
26
29
  removeTags,
30
+ seedTemplates,
31
+ listTemplates,
32
+ readContextCollections,
33
+ setContextCollections,
34
+ resolveDefaultSearchMode,
27
35
  resolveCwdCollection,
28
36
  resolveEditorCommand,
29
37
  resolveEditTarget,
@@ -350,7 +358,8 @@ async function tagsAction(fn, target, tags) {
350
358
  console.log(`${article.path} \u2192 tags: ${article.tags.join(", ") || "(none)"}`);
351
359
  }
352
360
  async function indexAction(opts) {
353
- const provider = await loadSearchProvider();
361
+ const { engine } = await resolveDefaultSearchMode();
362
+ const provider = await loadSearchProvider(engine);
354
363
  if (!provider) {
355
364
  const installable = (await listSearchModes()).filter((m) => m.plugin !== "native");
356
365
  console.log(
@@ -363,10 +372,16 @@ async function indexAction(opts) {
363
372
  }
364
373
  async function queryAction(term, opts) {
365
374
  const names = opts.collection ? opts.collection.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
375
+ const expansions = [
376
+ ...(opts.lex ?? []).map((query) => ({ type: "lex", query })),
377
+ ...(opts.vec ?? []).map((query) => ({ type: "vec", query })),
378
+ ...(opts.hyde ?? []).map((query) => ({ type: "hyde", query }))
379
+ ];
366
380
  const queryOpts = {
367
381
  engine: opts.engine,
368
382
  collection: names && names.length === 1 ? names[0] : names,
369
- type: opts.type
383
+ type: opts.type,
384
+ ...expansions.length > 0 ? { expansions } : {}
370
385
  };
371
386
  const outcome = await search(term, queryOpts);
372
387
  const hits = outcome.hits;
@@ -456,7 +471,19 @@ async function configGetAction(key) {
456
471
  const value = await getConfigValue(key);
457
472
  console.log(value ?? "(unset)");
458
473
  }
459
- async function doctorAction() {
474
+ async function doctorAction(opts) {
475
+ if (opts.fix) {
476
+ console.log("Repairing plugins (installing missing tools)\u2026");
477
+ const repairs = await repairPlugins();
478
+ for (const r of repairs) {
479
+ for (const step of r.setup.steps) {
480
+ console.log(
481
+ `${icon(step.ok ? "ok" : "warn")} ${r.name} \xB7 ${step.step}${step.message ? ` \u2014 ${step.message}` : ""}`
482
+ );
483
+ }
484
+ }
485
+ console.log("");
486
+ }
460
487
  const diagnostics = await doctor();
461
488
  for (const d of diagnostics) {
462
489
  console.log(`${icon(d.level)} ${d.message}`);
@@ -465,6 +492,69 @@ async function doctorAction() {
465
492
  process.exitCode = 1;
466
493
  }
467
494
  }
495
+ function bundledTemplatesDir() {
496
+ return path4.resolve(path4.dirname(fileURLToPath4(import.meta.url)), "..", "templates");
497
+ }
498
+ async function templatesInitAction() {
499
+ const { seeded, skipped, dir } = await seedTemplates(bundledTemplatesDir());
500
+ console.log(`Templates \u2192 ${dir}`);
501
+ if (seeded.length > 0) console.log(` seeded: ${seeded.join(", ")}`);
502
+ if (skipped.length > 0) console.log(` kept existing (user overrides): ${skipped.join(", ")}`);
503
+ if (seeded.length === 0 && skipped.length === 0) console.log(" (no bundled templates found)");
504
+ }
505
+ async function templatesListAction(opts) {
506
+ const infos = await listTemplates(bundledTemplatesDir());
507
+ if (infos.length === 0) {
508
+ console.log("No bundled templates found.");
509
+ return;
510
+ }
511
+ if (opts.json) {
512
+ console.log(JSON.stringify(infos, null, 2));
513
+ return;
514
+ }
515
+ for (const t of infos) {
516
+ const budget = t.budget ? ` [${t.budget[0]}\u2013${t.budget[1]} tok]` : "";
517
+ console.log(`${t.shape} ${t.purpose}${budget}`);
518
+ }
519
+ }
520
+ var parseNames = (csv) => csv.split(",").map((s) => s.trim()).filter(Boolean);
521
+ async function warnUnknownCollections(names) {
522
+ const known = new Set((await listCollections2()).map((c) => c.name));
523
+ const unknown = names.filter((n) => !known.has(n));
524
+ if (unknown.length > 0) {
525
+ console.error(`! not in your collections: ${unknown.join(", ")} (see: diffwiki collection list)`);
526
+ }
527
+ }
528
+ function reportContext(collections, file) {
529
+ console.log(`context.collections = [${collections.join(", ")}] \u2192 ${file}`);
530
+ }
531
+ async function contextShowAction() {
532
+ const cols = await readContextCollections(process.cwd());
533
+ if (cols.length === 0) {
534
+ console.log("No recall context set. Set one: diffwiki context set <a,b,c>");
535
+ return;
536
+ }
537
+ for (const c of cols) console.log(c);
538
+ }
539
+ async function contextSetAction(names) {
540
+ const cols = parseNames(names);
541
+ const file = await setContextCollections(process.cwd(), cols);
542
+ await warnUnknownCollections(cols);
543
+ reportContext(cols, file);
544
+ }
545
+ async function contextAddAction(names) {
546
+ const merged = await readContextCollections(process.cwd());
547
+ for (const n of parseNames(names)) if (!merged.includes(n)) merged.push(n);
548
+ const file = await setContextCollections(process.cwd(), merged);
549
+ await warnUnknownCollections(merged);
550
+ reportContext(merged, file);
551
+ }
552
+ async function contextRemoveAction(names) {
553
+ const drop = new Set(parseNames(names));
554
+ const kept = (await readContextCollections(process.cwd())).filter((c) => !drop.has(c));
555
+ const file = await setContextCollections(process.cwd(), kept);
556
+ reportContext(kept, file);
557
+ }
468
558
  async function lintAction(opts) {
469
559
  const result = await lintCollections(opts.collections);
470
560
  for (const file of result.files) {
@@ -573,7 +663,7 @@ function createProgram() {
573
663
  registerTagsSub("remove", "Remove from an article's tags", removeTags);
574
664
  registerTagsSub("set", "Replace an article's tags", setTags);
575
665
  const searchCmd = program.command("search").description("Search articles and manage the index");
576
- searchCmd.command("query").alias("q").description("Search articles via the resolved search engine (native fallback if none)").option("--engine <name>", "search engine/plugin (default: config defaultSearch, else native)").option("-c, --collection <names>", "limit to a collection (comma-separated for a set)").option("-t, --type <type>", "search type (engine-defined, e.g. keyword|semantic|hybrid)").argument("[term]", "search term", "").action(
666
+ searchCmd.command("query").alias("q").description("Search articles via the resolved search engine (native fallback if none)").option("--engine <name>", "search engine/plugin (default: config defaultSearch, else native)").option("-c, --collection <names>", "limit to a collection (comma-separated for a set)").option("-t, --type <type>", "search type (engine-defined, e.g. keyword|semantic|hybrid)").option("--lex <query>", "BM25 keyword variant (repeatable)", collect, []).option("--vec <query>", "semantic variant \u2014 a natural-language question (repeatable)", collect, []).option("--hyde <query>", "hypothetical-answer passage variant (repeatable)", collect, []).argument("[term]", "search term (used as ranking intent when variants are given)", "").action(
577
667
  withErrors(
578
668
  (term, opts) => queryAction(term, opts)
579
669
  )
@@ -600,10 +690,18 @@ function createProgram() {
600
690
  ui.command("down").description("Stop the running diffwiki UI").action(withErrors(downAction));
601
691
  ui.command("status").description("Show whether the diffwiki UI is running").action(withErrors(statusAction));
602
692
  ui.command("open").description("Open the diffwiki UI in the browser (starting it if needed)").option("-p, --port <port>", "port to serve on", "4321").action(withErrors((opts) => openAction(opts)));
693
+ const context = program.command("context").description("Manage recall/search context (diffwiki.yaml \u2192 context.collections)");
694
+ context.command("show").description("Print the current recall-context collections").action(withErrors(contextShowAction));
695
+ context.command("set").description("Replace the recall-context collections").argument("<names>", "comma-separated collection names").action(withErrors((names) => contextSetAction(names)));
696
+ context.command("add").description("Add collections to the recall context").argument("<names>", "comma-separated collection names").action(withErrors((names) => contextAddAction(names)));
697
+ context.command("remove").description("Remove collections from the recall context").argument("<names>", "comma-separated collection names").action(withErrors((names) => contextRemoveAction(names)));
698
+ const templates = program.command("templates").description("Manage authoring templates (~/.diffwiki/templates)");
699
+ templates.command("init").description("Seed ~/.diffwiki/templates from the bundled templates (never overwrites your own)").action(withErrors(templatesInitAction));
700
+ templates.command("list").description("List templates with their shape + purpose (read from each template\u2019s frontmatter)").option("--json", "output as JSON").action(withErrors((opts) => templatesListAction(opts)));
603
701
  const config = program.command("config").description("Read and write diffwiki config");
604
702
  config.command("set").description("Set a config value (e.g. defaultCollection, or defaultSearch <engine[:type]>)").argument("<key>", "config key").argument("<value>", "config value").action(withErrors((key, value) => configSetAction(key, value)));
605
703
  config.command("get").description("Read a config value").argument("<key>", "config key").action(withErrors((key) => configGetAction(key)));
606
- program.command("doctor").description("Diagnose configuration issues").action(withErrors(doctorAction));
704
+ program.command("doctor").description("Diagnose configuration issues").option("--fix", "attempt to repair issues (re-provision plugins, auto-installing missing tools)").action(withErrors((opts) => doctorAction(opts)));
607
705
  program.command("lint").description("Lint articles against the template shapes (frontmatter, budgets, references)").option("-c, --collections <scope>", "current | all | comma-separated names", "current").action(withErrors((opts) => lintAction(opts)));
608
706
  return program;
609
707
  }
@@ -611,14 +709,14 @@ function createProgram() {
611
709
  // src/logging.ts
612
710
  import { mkdirSync } from "fs";
613
711
  import os from "os";
614
- import path4 from "path";
712
+ import path5 from "path";
615
713
  import { configureSync, getConfig } from "@logtape/logtape";
616
714
  import { getRotatingFileSink } from "@logtape/file";
617
715
  import { resolveLogLevel } from "diffwiki-core";
618
716
  var configured = false;
619
717
  function diffwikiHome() {
620
718
  const override = process.env.DIFFWIKI_HOME;
621
- return override && override.length > 0 ? override : path4.join(os.homedir(), ".diffwiki");
719
+ return override && override.length > 0 ? override : path5.join(os.homedir(), ".diffwiki");
622
720
  }
623
721
  function resolveCliLevel(argv) {
624
722
  if (argv.includes("-v") || argv.includes("--verbose")) return "debug";
@@ -635,9 +733,9 @@ function configureCliLogging(argv = process.argv) {
635
733
  if (configured || getConfig() != null) return;
636
734
  configured = true;
637
735
  const level = resolveCliLevel(argv);
638
- const logsDir = path4.join(diffwikiHome(), "logs");
736
+ const logsDir = path5.join(diffwikiHome(), "logs");
639
737
  mkdirSync(logsDir, { recursive: true });
640
- const logFile = path4.join(logsDir, "diffwiki.log");
738
+ const logFile = path5.join(logsDir, "diffwiki.log");
641
739
  const stderrSink = (record) => {
642
740
  process.stderr.write(formatLine(record) + "\n");
643
741
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "diffwiki",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "diffwiki command-line interface",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -8,7 +8,8 @@
8
8
  "diffwiki": "./dist/bin.js"
9
9
  },
10
10
  "files": [
11
- "dist"
11
+ "dist",
12
+ "templates"
12
13
  ],
13
14
  "publishConfig": {
14
15
  "access": "public"
@@ -17,7 +18,7 @@
17
18
  "@logtape/logtape": "^2.2.4",
18
19
  "@logtape/file": "^2.2.4",
19
20
  "commander": "^13.0.0",
20
- "diffwiki-core": "0.14.0"
21
+ "diffwiki-core": "0.15.0"
21
22
  },
22
23
  "devDependencies": {
23
24
  "@types/node": "^22.0.0"
@@ -0,0 +1,68 @@
1
+ ---
2
+ shape: guide
3
+ purpose: Teaches the reasoning behind a topic — connects related patterns and explains why they work.
4
+ budget: [1500, 5000]
5
+ ---
6
+
7
+ <!--
8
+ Use this shape to teach. A guide explains the reasoning, trade-offs, and mental model behind a set of related patterns. It answers "why" and "how does this fit together" — not "what steps do I follow". If the reader needs a procedure, use recipe-with-steps. If they need a lookup, use reference.
9
+
10
+ Authoring rules:
11
+ - Adhere to the template structure
12
+ - One H2 per concept or insight; each section covers one idea
13
+ - State trade-offs and limits, not just the happy path
14
+ - Show, don't tell: replace prose explanations with Mermaid diagrams, tables, and code examples wherever possible. A sequence diagram beats three paragraphs of data-flow description. A comparison table beats a list of trade-offs.
15
+ - Soft-wrap prose
16
+ - Use inline references as needed for other wiki articles and external sources
17
+ - Do not copy over html comments or the template specific yaml frontmatter above this section. Write a diffwiki article yaml frontmatter as shown below this comment.
18
+ - Use concise descriptive titles in article frontmatter
19
+ - Tag programming languages, frameworks, topics of relevance to support vector embedding/search
20
+ - Default to public audience and draft status unless user specifies otherwise
21
+ -->
22
+
23
+ ---
24
+
25
+ title: TITLE
26
+ tags:
27
+
28
+ - guide
29
+ - TAG1
30
+ - TAG2
31
+ - ...
32
+ audience: public
33
+ status: draft
34
+
35
+ ---
36
+
37
+ One to three sentences. State the insight or principle this guide teaches and name the concrete systems or patterns it covers. State what the guide does not cover and link to the adjacent recipe or reference: see [<related>](./<related>.md).
38
+
39
+ ## <The core concept — concrete noun, not "Overview">
40
+
41
+ State what this concept is and why it matters. Name the constraint or trade-off that makes it non-obvious.
42
+
43
+ ```<lang>
44
+ <illustrative example — under ~40 lines>
45
+ ```
46
+
47
+ Explain what the example demonstrates. Point to the part that carries the lesson.
48
+
49
+ ## <Second concept or pattern>
50
+
51
+ State how this concept relates to the previous one. Describe the trade-off or decision point a reader faces between the two.
52
+
53
+ If the relationship needs a diagram:
54
+
55
+ ```mermaid
56
+ graph LR
57
+ A[Concept A] --> B[Concept B]
58
+ B --> C[Outcome]
59
+ ```
60
+
61
+ ## <When this breaks / limits>
62
+
63
+ State the condition under which the pattern fails. One paragraph per failure mode. Link to the recipe that handles each case: see [<recipe>](../<collection>/<slug>.md).
64
+
65
+ ## References
66
+
67
+ - [<Primary source>](<url>) — <one-line reason>
68
+ - [<Related recipe or guide>](../<collection>/<slug>.md) — <one-line reason>
@@ -0,0 +1,58 @@
1
+ ---
2
+ shape: recipe
3
+ purpose: One contained pattern with a single canonical example.
4
+ budget: [300, 1000]
5
+ ---
6
+
7
+ <!--
8
+ Use this shape when the recipe is one contained pattern: intro + a single canonical example + notes. No ordered steps. If the recipe has ordered steps, use `recipe-with-steps-template.md` instead.
9
+
10
+ Authoring rules:
11
+ - Adhere to the template structure
12
+ - Soft-wrap prose
13
+ - Use inline references as needed for other wiki articles and external sources
14
+ - Do not copy over html comments or the template specific yaml frontmatter above this section. Write a diffwiki article yaml frontmatter as shown below this comment.
15
+ - Use concise descriptive titles in article frontmatter
16
+ - Tag programming languages, frameworks, topics of relevance to support vector embedding/search
17
+ - Default to public audience and draft status unless user specifies otherwise
18
+ -->
19
+
20
+ ---
21
+
22
+ title: TITLE
23
+ tags:
24
+
25
+ - recipe
26
+ - TAG1
27
+ - TAG2
28
+ - ...
29
+ audience: public
30
+ status: draft
31
+
32
+ ---
33
+
34
+ Write two to four sentences of intro prose. Describe what the recipe covers, utilizing concrete symbols and verbs a reader would search for. Mention adjacent phrasings when possible so vector search hits from alternative wording.
35
+
36
+ ## Recipe
37
+
38
+ State the pattern in one sentence — what the code does and when to reach for it.
39
+
40
+ ```<lang>
41
+ <code — the canonical example, under ~70 lines>
42
+ ```
43
+
44
+ Follow-up notes only if the code isn't self-explanatory. Cover variations, edge cases, related recipes, etc.
45
+
46
+ ## Troubleshooting
47
+
48
+ <!-- Optional. Include only if there are real, specific problems users hit. Skip if you'd otherwise write generic warnings. -->
49
+
50
+ - <Symptom> — <root cause> — <fix>.
51
+ - <Symptom> — <root cause> — <fix>.
52
+
53
+ ## References
54
+
55
+ <!-- Do not omit even if references are also inline -->
56
+
57
+ - [<Name>](url) — <one-line reason (optional)>
58
+ - [<Related recipe>](../<collection>/<slug>.md) — <one-line reason (optional)>
@@ -0,0 +1,76 @@
1
+ ---
2
+ shape: recipe-with-steps
3
+ purpose: An ordered sequence — do X, then Y, then Z.
4
+ budget: [500, 1500]
5
+ ---
6
+
7
+ <!--
8
+ Use this shape when the recipe is an ordered sequence — install X, then configure Y, then run Z. If there are no ordered steps, use `recipe-template.md` (simple) instead.
9
+
10
+ Authoring rules:
11
+ - Adhere to the template structure
12
+ - Number every step: `### Step 1 — <verb + object>`, and keep each step self-contained
13
+ - If the sequence grows past ~7 steps, split into two recipes
14
+ - Soft-wrap prose
15
+ - Use inline references as needed for other wiki articles and external sources
16
+ - Do not copy over html comments or the template specific yaml frontmatter above this section. Write a diffwiki article yaml frontmatter as shown below this comment.
17
+ - Use concise descriptive titles in article frontmatter
18
+ - Tag programming languages, frameworks, topics of relevance to support vector embedding/search
19
+ - Default to public audience and draft status unless user specifies otherwise
20
+ -->
21
+
22
+ ---
23
+
24
+ title: TITLE
25
+ tags:
26
+
27
+ - recipe
28
+ - TAG1
29
+ - TAG2
30
+ - ...
31
+ audience: public
32
+ status: draft
33
+
34
+ ---
35
+
36
+ Write two to four sentences of intro prose. State what the recipe accomplishes end-to-end, utilizing concrete tools and commands a reader would search for. Mention adjacent phrasings when possible so vector search hits from alternative wording.
37
+
38
+ ## Recipe
39
+
40
+ One sentence framing the whole sequence — what state the reader is in when they start, and what state they're in when the last step succeeds.
41
+
42
+ ### Step 1 — <verb + object>
43
+
44
+ One-sentence goal for the step.
45
+
46
+ ```<lang>
47
+ <code — under ~70 lines>
48
+ ```
49
+
50
+ Optional one-line verification (`<cmd> should print <result>`) or an inline link to the sub-topic.
51
+
52
+ ### Step 2 — <verb + object>
53
+
54
+ One-sentence goal.
55
+
56
+ ```<lang>
57
+ <code>
58
+ ```
59
+
60
+ ### Step 3 — <verb + object>
61
+
62
+ <...>
63
+
64
+ ## Troubleshooting
65
+
66
+ <!-- Optional. Only real, specific problems. Cross-link to steps by name so retrieval keeps the connection. -->
67
+
68
+ - <Symptom during step N> — <root cause> — <fix>.
69
+ - <Symptom during step N> — <root cause> — <fix>.
70
+
71
+ ## References
72
+
73
+ <!-- Do not omit even if references are also inline -->
74
+
75
+ - [<Name>](url) — <one-line reason (optional)>
76
+ - [<Related recipe>](../<collection>/<slug>.md) — <one-line reason (optional)>
@@ -0,0 +1,70 @@
1
+ ---
2
+ shape: reference
3
+ purpose: Authoritative description of commands, flags, config keys, types, or API endpoints — consult while working, not read linearly.
4
+ budget: [300, 2000]
5
+ ---
6
+
7
+ <!--
8
+ Use this shape for lookup tables: CLI flags, config keys, environment variables, type definitions, API endpoints, error codes. A reference entry describes one item — its name, type, default, and effect. It does not explain why, teach, or walk through steps.
9
+
10
+ Authoring rules:
11
+ - Adhere to the template structure
12
+ - Organize entries by the system's own structure (group flags by command, keys by section) — not by frequency of use
13
+ - One entry per item; keep descriptions to one sentence
14
+ - Mark required entries explicitly; note all defaults
15
+ - Add a minimal example only when the effect is not clear from the description alone
16
+ - Do not add prose narration between entries
17
+ - Soft-wrap prose in the intro and Notes sections only
18
+ - Do not copy over html comments or the template specific yaml frontmatter above this section. Write a diffwiki article yaml frontmatter as shown below this comment.
19
+ - Use concise descriptive titles in article frontmatter
20
+ - Tag the system, language, or framework this reference covers
21
+ - Default to public audience and draft status unless user specifies otherwise
22
+ -->
23
+
24
+ ---
25
+
26
+ title: TITLE
27
+ tags:
28
+
29
+ - reference
30
+ - TAG1
31
+ - TAG2
32
+ - ...
33
+ audience: public
34
+ status: draft
35
+
36
+ ---
37
+
38
+ One sentence: what system this page covers and the version or scope it applies to. Link to the upstream docs: [<System name>](<official-url>).
39
+
40
+ ## <Command / Section / Group>
41
+
42
+ One sentence on what this command or group does.
43
+
44
+ | Flag / Key | Type | Default | Description |
45
+ |---|---|---|---|
46
+ | `<name>` | `<type>` | `<default>` | <one-sentence description> |
47
+ | `<name>` | `<type>` | — | **Required.** <one-sentence description> |
48
+
49
+ Minimal example where the flag combination is not obvious from the table:
50
+
51
+ ```<lang>
52
+ <example>
53
+ ```
54
+
55
+ ## <Second command / section>
56
+
57
+ <same pattern>
58
+
59
+ ## Notes
60
+
61
+ <!-- Optional. Warnings, known limits, version-specific behavior, or gotchas that apply across the whole reference — not to a single entry. -->
62
+
63
+ - <Warning or limit> — <what breaks and what to do instead>.
64
+
65
+ ## References
66
+
67
+ <!-- Do not omit even if references are also inline -->
68
+
69
+ - [<Official docs>](<url>) — <one-line reason (optional)>
70
+ - [<Related recipe>](../<collection>/<slug>.md) — <one-line reason (optional)>
@@ -0,0 +1,26 @@
1
+ ---
2
+ shape: session-transcript
3
+ purpose: dw-learn capture into the hidden transcripts collection (not linted).
4
+ ---
5
+ <!-- SESSION TRANSCRIPT. The agent fills this by introspecting its own session.
6
+ Goes into the hidden `transcripts` collection with `--audience system`; NOT linted
7
+ as a knowledge entry. Terse and factual — raw material for dw-learn, not a doc.
8
+ Title (passed to the CLI) is "<project>/<date>-<short-id>"; tags are
9
+ [transcript, <tool>, <project>]. Do not write frontmatter — the CLI owns it. -->
10
+
11
+ **Tool:** <claude-code|codex|opencode|goose|pi> · **Project:** <repo-or-path> · **Date:** <YYYY-MM-DD> · **Session:** <id-if-known>
12
+
13
+ ## Worked on
14
+ - <goal — what was attempted and the outcome>
15
+
16
+ ## Issues & learnings
17
+ - <problem — root cause — how it was resolved (or that it's unresolved)>
18
+
19
+ ## Decisions & patterns
20
+ - <architectural decision or reusable pattern observed — and why>
21
+
22
+ ## Key tool actions
23
+ - <notable commands, files created/changed, workflows — the reproducible bits>
24
+
25
+ ## Open threads
26
+ - <unfinished work, follow-ups, questions for next time>