codealmanac 0.2.0 → 0.2.1

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.
@@ -33,10 +33,10 @@ import {
33
33
  titleCase,
34
34
  toKebabCase,
35
35
  writeTopicsFile
36
- } from "./chunk-3C5SY5SE.js";
36
+ } from "./chunk-KQUVMF27.js";
37
37
  import {
38
38
  runDoctor
39
- } from "./chunk-D3B2EEHL.js";
39
+ } from "./chunk-B2AGSRXL.js";
40
40
  import "./chunk-V3QOQSXI.js";
41
41
  import "./chunk-4CODZRHH.js";
42
42
  import {
@@ -1905,6 +1905,7 @@ async function loadPrompt(name) {
1905
1905
  // src/agent/sdk.ts
1906
1906
  import { spawn } from "child_process";
1907
1907
  import { query } from "@anthropic-ai/claude-agent-sdk";
1908
+ var DEFAULT_AGENT_MODEL = "claude-sonnet-4-6";
1908
1909
  async function runAgent(opts) {
1909
1910
  const provider = opts.provider ?? "claude";
1910
1911
  if (provider === "codex") {
@@ -1924,7 +1925,7 @@ async function runClaudeAgent(opts) {
1924
1925
  allowedTools: opts.allowedTools,
1925
1926
  agents: opts.agents ?? {},
1926
1927
  cwd: opts.cwd,
1927
- model: opts.model ?? "claude-sonnet-4-6",
1928
+ model: opts.model ?? DEFAULT_AGENT_MODEL,
1928
1929
  maxTurns: opts.maxTurns ?? 100,
1929
1930
  ...claudeExecutable !== void 0 ? { pathToClaudeCodeExecutable: claudeExecutable } : {},
1930
1931
  env: {
@@ -2558,12 +2559,219 @@ function isRecord(value) {
2558
2559
  import { createHash } from "crypto";
2559
2560
  import {
2560
2561
  createWriteStream as createWriteStream2,
2561
- existsSync as existsSync6,
2562
+ existsSync as existsSync7,
2562
2563
  statSync
2563
2564
  } from "fs";
2564
- import { mkdir as mkdir3, readFile as readFile6, readdir as readdir2, stat } from "fs/promises";
2565
+ import { mkdir as mkdir4, readFile as readFile7, readdir as readdir3, stat } from "fs/promises";
2565
2566
  import { homedir } from "os";
2566
- import { basename as basename3, join as join7, relative as relative2 } from "path";
2567
+ import { basename as basename3, join as join8, relative as relative3 } from "path";
2568
+
2569
+ // src/commands/captureStatus.ts
2570
+ import { existsSync as existsSync6 } from "fs";
2571
+ import { mkdir as mkdir3, readFile as readFile6, readdir as readdir2, rename as rename2, writeFile as writeFile3 } from "fs/promises";
2572
+ import { dirname, join as join7, relative as relative2 } from "path";
2573
+ function captureStatePath(dir, stem) {
2574
+ return join7(dir, `.capture-${stem}.state.json`);
2575
+ }
2576
+ async function writeCaptureRunRecord(path2, record) {
2577
+ await mkdir3(dirname(path2), { recursive: true });
2578
+ const tmp = `${path2}.tmp-${process.pid}`;
2579
+ await writeFile3(tmp, `${JSON.stringify(record, null, 2)}
2580
+ `, "utf8");
2581
+ await rename2(tmp, path2);
2582
+ }
2583
+ function buildStartedCaptureRecord(args) {
2584
+ return {
2585
+ version: 1,
2586
+ kind: "capture",
2587
+ status: "running",
2588
+ sessionId: args.sessionId ?? args.stem,
2589
+ repoRoot: args.repoRoot,
2590
+ pid: process.pid,
2591
+ model: args.model ?? DEFAULT_AGENT_MODEL,
2592
+ transcriptPath: args.transcriptPath,
2593
+ startedAt: args.startedAt.toISOString(),
2594
+ logPath: join7(args.almanacDir, `.capture-${args.stem}.log`),
2595
+ jsonlPath: join7(args.almanacDir, `.capture-${args.stem}.jsonl`)
2596
+ };
2597
+ }
2598
+ function finishCaptureRecord(args) {
2599
+ const started = Date.parse(args.record.startedAt);
2600
+ const finished = args.finishedAt.getTime();
2601
+ return {
2602
+ ...args.record,
2603
+ status: args.status,
2604
+ finishedAt: args.finishedAt.toISOString(),
2605
+ durationMs: Number.isFinite(started) ? Math.max(0, finished - started) : void 0,
2606
+ summary: args.summary,
2607
+ error: args.error
2608
+ };
2609
+ }
2610
+ async function runCaptureStatus(options) {
2611
+ const repoRoot = findNearestAlmanacDir(options.cwd);
2612
+ if (repoRoot === null) {
2613
+ return {
2614
+ stdout: "",
2615
+ stderr: "almanac: no .almanac/ found in this directory or any parent. Run 'almanac bootstrap' first.\n",
2616
+ exitCode: 1
2617
+ };
2618
+ }
2619
+ const almanacDir = getRepoAlmanacDir(repoRoot);
2620
+ const records = await readCaptureRecords(almanacDir);
2621
+ const now = options.now?.() ?? /* @__PURE__ */ new Date();
2622
+ const isPidAlive = options.isPidAlive ?? defaultIsPidAlive;
2623
+ const views = records.map((record) => toView(record, repoRoot, now, isPidAlive)).sort((a, b) => b.sortTime - a.sortTime);
2624
+ if (options.json === true) {
2625
+ return {
2626
+ stdout: `${JSON.stringify(
2627
+ {
2628
+ repo: repoRoot,
2629
+ captures: views.map(({ sortTime: _sortTime, ...v }) => v)
2630
+ },
2631
+ null,
2632
+ 2
2633
+ )}
2634
+ `,
2635
+ stderr: "",
2636
+ exitCode: 0
2637
+ };
2638
+ }
2639
+ return {
2640
+ stdout: formatCaptureStatus(views),
2641
+ stderr: "",
2642
+ exitCode: 0
2643
+ };
2644
+ }
2645
+ async function readCaptureRecords(almanacDir) {
2646
+ if (!existsSync6(almanacDir)) return [];
2647
+ const out = [];
2648
+ const dirs = [join7(almanacDir, "logs"), almanacDir];
2649
+ for (const dir of dirs) {
2650
+ let entries;
2651
+ try {
2652
+ entries = await readdir2(dir);
2653
+ } catch {
2654
+ continue;
2655
+ }
2656
+ for (const entry of entries) {
2657
+ if (!entry.startsWith(".capture-") || !entry.endsWith(".state.json")) {
2658
+ continue;
2659
+ }
2660
+ try {
2661
+ const parsed = JSON.parse(await readFile6(join7(dir, entry), "utf8"));
2662
+ if (isCaptureRunRecord(parsed)) out.push(parsed);
2663
+ } catch {
2664
+ continue;
2665
+ }
2666
+ }
2667
+ }
2668
+ return out;
2669
+ }
2670
+ function isCaptureRunRecord(value) {
2671
+ if (value === null || typeof value !== "object") return false;
2672
+ const v = value;
2673
+ return v.version === 1 && v.kind === "capture" && (v.status === "running" || v.status === "done" || v.status === "failed") && typeof v.sessionId === "string" && typeof v.repoRoot === "string" && typeof v.pid === "number" && typeof v.model === "string" && typeof v.transcriptPath === "string" && typeof v.startedAt === "string" && typeof v.logPath === "string" && typeof v.jsonlPath === "string";
2674
+ }
2675
+ function toView(record, repoRoot, now, isPidAlive) {
2676
+ const started = Date.parse(record.startedAt);
2677
+ const finished = record.finishedAt !== void 0 ? Date.parse(record.finishedAt) : void 0;
2678
+ const elapsedMs = record.durationMs ?? (Number.isFinite(started) ? Math.max(0, (finished ?? now.getTime()) - started) : 0);
2679
+ const status = record.status === "running" && !isPidAlive(record.pid) ? "stale" : record.status;
2680
+ return {
2681
+ status,
2682
+ sessionId: record.sessionId,
2683
+ model: record.model,
2684
+ elapsedMs,
2685
+ startedAt: record.startedAt,
2686
+ finishedAt: record.finishedAt,
2687
+ pid: record.pid,
2688
+ logPath: relative2(repoRoot, record.logPath),
2689
+ jsonlPath: relative2(repoRoot, record.jsonlPath),
2690
+ summary: record.summary,
2691
+ error: status === "stale" ? "process ended without a final status" : record.error,
2692
+ sortTime: finished ?? (Number.isFinite(started) ? started : 0)
2693
+ };
2694
+ }
2695
+ function formatCaptureStatus(views) {
2696
+ const lines = ["Capture jobs", ""];
2697
+ if (views.length === 0) {
2698
+ lines.push("No capture jobs found.");
2699
+ return `${lines.join("\n")}
2700
+ `;
2701
+ }
2702
+ const active = views.filter((v) => v.status === "running" || v.status === "stale");
2703
+ const finished = views.filter((v) => v.status === "done" || v.status === "failed");
2704
+ if (active.length === 0) {
2705
+ lines.push("No active captures.", "");
2706
+ } else {
2707
+ for (const view of active) {
2708
+ lines.push(formatRow(view));
2709
+ lines.push(` log: ${view.logPath}`);
2710
+ if (view.error !== void 0) lines.push(` error: ${view.error}`);
2711
+ lines.push("");
2712
+ }
2713
+ }
2714
+ if (finished.length > 0) {
2715
+ lines.push(active.length === 0 ? "Last capture:" : "Last finished:");
2716
+ for (const view of finished.slice(0, 3)) {
2717
+ lines.push(formatRow(view));
2718
+ if (view.status === "failed") {
2719
+ lines.push(` log: ${view.logPath}`);
2720
+ if (view.error !== void 0) lines.push(` error: ${view.error}`);
2721
+ }
2722
+ }
2723
+ }
2724
+ return `${trimTrailingBlank(lines).join("\n")}
2725
+ `;
2726
+ }
2727
+ function formatRow(view) {
2728
+ const status = view.status.padEnd(7, " ");
2729
+ const session = view.sessionId.padEnd(12, " ");
2730
+ const model = view.model.padEnd(17, " ");
2731
+ const elapsed = formatDuration(view.elapsedMs);
2732
+ const summary = formatSummary(view);
2733
+ return `${status} ${session} ${model} ${elapsed}${summary.length > 0 ? ` ${summary}` : ""}`;
2734
+ }
2735
+ function formatSummary(view) {
2736
+ if (view.status === "failed") return "failed; see log";
2737
+ if (view.summary === void 0) return "";
2738
+ const parts = [];
2739
+ if (view.summary.updated > 0) {
2740
+ parts.push(`${view.summary.updated} updated`);
2741
+ }
2742
+ if (view.summary.created > 0) {
2743
+ parts.push(`${view.summary.created} created`);
2744
+ }
2745
+ if (view.summary.archived > 0) {
2746
+ parts.push(`${view.summary.archived} archived`);
2747
+ }
2748
+ return parts.length > 0 ? parts.join(", ") : "0 pages written";
2749
+ }
2750
+ function formatDuration(ms) {
2751
+ const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
2752
+ const minutes = Math.floor(totalSeconds / 60);
2753
+ const seconds = totalSeconds % 60;
2754
+ if (minutes < 60) return `${minutes}m${seconds.toString().padStart(2, "0")}s`;
2755
+ const hours = Math.floor(minutes / 60);
2756
+ const restMinutes = minutes % 60;
2757
+ return `${hours}h${restMinutes.toString().padStart(2, "0")}m`;
2758
+ }
2759
+ function trimTrailingBlank(lines) {
2760
+ while (lines.length > 0 && lines[lines.length - 1] === "") {
2761
+ lines.pop();
2762
+ }
2763
+ return lines;
2764
+ }
2765
+ function defaultIsPidAlive(pid) {
2766
+ try {
2767
+ process.kill(pid, 0);
2768
+ return true;
2769
+ } catch {
2770
+ return false;
2771
+ }
2772
+ }
2773
+
2774
+ // src/commands/capture.ts
2567
2775
  var WRITER_TOOLS = ["Read", "Write", "Edit", "Glob", "Grep", "Bash", "Agent"];
2568
2776
  var REVIEWER_TOOLS = ["Read", "Grep", "Glob", "Bash"];
2569
2777
  var REVIEWER_DESCRIPTION = "Reviews proposed wiki changes against the full knowledge base for cohesion, duplication, missing links, notability, and writing conventions.";
@@ -2601,7 +2809,7 @@ async function runCapture(options) {
2601
2809
  };
2602
2810
  }
2603
2811
  const almanacDir = getRepoAlmanacDir(repoRoot);
2604
- const pagesDir = join7(almanacDir, "pages");
2812
+ const pagesDir = join8(almanacDir, "pages");
2605
2813
  const transcriptResolution = await resolveTranscript({
2606
2814
  repoRoot,
2607
2815
  explicit: options.transcriptPath,
@@ -2627,12 +2835,24 @@ async function runCapture(options) {
2627
2835
  tools: REVIEWER_TOOLS
2628
2836
  }
2629
2837
  };
2630
- const now = options.now?.() ?? /* @__PURE__ */ new Date();
2631
- const logStem = options.sessionId !== void 0 && options.sessionId.length > 0 ? options.sessionId : formatTimestamp2(now);
2632
- const logsDir = join7(almanacDir, "logs");
2633
- await mkdir3(logsDir, { recursive: true });
2838
+ const startedAt = options.now?.() ?? /* @__PURE__ */ new Date();
2839
+ const logStem = options.sessionId !== void 0 && options.sessionId.length > 0 ? options.sessionId : formatTimestamp2(startedAt);
2840
+ const logsDir = join8(almanacDir, "logs");
2841
+ await mkdir4(logsDir, { recursive: true });
2634
2842
  const logName = `.capture-${logStem}.jsonl`;
2635
- const logPath = join7(logsDir, logName);
2843
+ const logPath = join8(logsDir, logName);
2844
+ const statePath = captureStatePath(logsDir, logStem);
2845
+ const stateRecord = buildStartedCaptureRecord({
2846
+ repoRoot,
2847
+ almanacDir: logsDir,
2848
+ stem: logStem,
2849
+ sessionId: options.sessionId,
2850
+ transcriptPath,
2851
+ model: options.model,
2852
+ startedAt
2853
+ });
2854
+ await writeCaptureRunRecord(statePath, stateRecord).catch(() => {
2855
+ });
2636
2856
  const logStream = createWriteStream2(logPath, { flags: "w" });
2637
2857
  const out = process.stdout;
2638
2858
  const formatter = new StreamingFormatter({
@@ -2674,16 +2894,43 @@ Working directory: ${repoRoot}.`;
2674
2894
  }
2675
2895
  const snapshotAfter = await snapshotPages(pagesDir);
2676
2896
  const delta = diffSnapshots(snapshotBefore, snapshotAfter);
2897
+ const finishedAt = options.now?.() ?? /* @__PURE__ */ new Date();
2898
+ const captureSummary = {
2899
+ ...delta,
2900
+ cost: result.cost,
2901
+ turns: result.turns
2902
+ };
2677
2903
  if (!result.success) {
2904
+ await writeCaptureRunRecord(
2905
+ statePath,
2906
+ finishCaptureRecord({
2907
+ record: stateRecord,
2908
+ status: "failed",
2909
+ finishedAt,
2910
+ summary: captureSummary,
2911
+ error: result.error ?? "unknown error"
2912
+ })
2913
+ ).catch(() => {
2914
+ });
2678
2915
  return {
2679
2916
  stdout: "",
2680
2917
  stderr: `almanac: capture failed: ${result.error ?? "unknown error"}
2681
- (transcript: ${relative2(repoRoot, logPath)})
2918
+ (transcript: ${relative3(repoRoot, logPath)})
2682
2919
  `,
2683
2920
  exitCode: 1
2684
2921
  };
2685
2922
  }
2686
- const summary = formatSummary(result, delta, logPath, repoRoot);
2923
+ await writeCaptureRunRecord(
2924
+ statePath,
2925
+ finishCaptureRecord({
2926
+ record: stateRecord,
2927
+ status: "done",
2928
+ finishedAt,
2929
+ summary: captureSummary
2930
+ })
2931
+ ).catch(() => {
2932
+ });
2933
+ const summary = formatSummary2(result, delta, logPath, repoRoot);
2687
2934
  return {
2688
2935
  stdout: `${summary}
2689
2936
  `,
@@ -2706,7 +2953,7 @@ async function resolveAgentSelection2(args) {
2706
2953
  }
2707
2954
  async function resolveTranscript(args) {
2708
2955
  if (args.explicit !== void 0 && args.explicit.length > 0) {
2709
- if (!existsSync6(args.explicit)) {
2956
+ if (!existsSync7(args.explicit)) {
2710
2957
  return {
2711
2958
  ok: false,
2712
2959
  error: `transcript not found: ${args.explicit}`
@@ -2714,8 +2961,8 @@ async function resolveTranscript(args) {
2714
2961
  }
2715
2962
  return { ok: true, path: args.explicit };
2716
2963
  }
2717
- const projectsDir = args.claudeProjectsDir ?? join7(homedir(), ".claude", "projects");
2718
- if (!existsSync6(projectsDir)) {
2964
+ const projectsDir = args.claudeProjectsDir ?? join8(homedir(), ".claude", "projects");
2965
+ if (!existsSync7(projectsDir)) {
2719
2966
  return {
2720
2967
  ok: false,
2721
2968
  error: `could not auto-resolve transcript; ${projectsDir} does not exist. Pass --session <id> or <transcript-path>.`
@@ -2747,21 +2994,21 @@ async function collectTranscripts(projectsDir) {
2747
2994
  const out = [];
2748
2995
  let topLevel;
2749
2996
  try {
2750
- topLevel = await readdir2(projectsDir);
2997
+ topLevel = await readdir3(projectsDir);
2751
2998
  } catch {
2752
2999
  return out;
2753
3000
  }
2754
3001
  for (const name of topLevel) {
2755
- const projectDir = join7(projectsDir, name);
3002
+ const projectDir = join8(projectsDir, name);
2756
3003
  let entries;
2757
3004
  try {
2758
- entries = await readdir2(projectDir);
3005
+ entries = await readdir3(projectDir);
2759
3006
  } catch {
2760
3007
  continue;
2761
3008
  }
2762
3009
  for (const entry of entries) {
2763
3010
  if (!entry.endsWith(".jsonl")) continue;
2764
- const full = join7(projectDir, entry);
3011
+ const full = join8(projectDir, entry);
2765
3012
  try {
2766
3013
  const st = await stat(full);
2767
3014
  if (st.isFile()) {
@@ -2776,7 +3023,7 @@ async function collectTranscripts(projectsDir) {
2776
3023
  async function filterTranscriptsByCwd(transcripts, repoRoot) {
2777
3024
  const dirHash = `-${repoRoot.replace(/^\/+/, "").replace(/\//g, "-")}`;
2778
3025
  const byDirName = transcripts.filter((t) => {
2779
- const parent = basename3(join7(t.path, ".."));
3026
+ const parent = basename3(join8(t.path, ".."));
2780
3027
  return parent === dirHash || parent.endsWith(dirHash);
2781
3028
  });
2782
3029
  if (byDirName.length > 0) return byDirName;
@@ -2793,26 +3040,26 @@ async function filterTranscriptsByCwd(transcripts, repoRoot) {
2793
3040
  return hits;
2794
3041
  }
2795
3042
  async function readHead(path2, bytes) {
2796
- const content = await readFile6(path2, "utf8");
3043
+ const content = await readFile7(path2, "utf8");
2797
3044
  return content.length > bytes ? content.slice(0, bytes) : content;
2798
3045
  }
2799
3046
  async function snapshotPages(pagesDir) {
2800
3047
  const out = /* @__PURE__ */ new Map();
2801
- if (!existsSync6(pagesDir)) return out;
3048
+ if (!existsSync7(pagesDir)) return out;
2802
3049
  let entries;
2803
3050
  try {
2804
- entries = await readdir2(pagesDir);
3051
+ entries = await readdir3(pagesDir);
2805
3052
  } catch {
2806
3053
  return out;
2807
3054
  }
2808
3055
  for (const entry of entries) {
2809
3056
  if (!entry.endsWith(".md")) continue;
2810
3057
  const slug = entry.slice(0, -3);
2811
- const full = join7(pagesDir, entry);
3058
+ const full = join8(pagesDir, entry);
2812
3059
  try {
2813
3060
  const st = statSync(full);
2814
3061
  if (!st.isFile()) continue;
2815
- const content = await readFile6(full, "utf8");
3062
+ const content = await readFile7(full, "utf8");
2816
3063
  const hash = createHash("sha256").update(content).digest("hex");
2817
3064
  const fm = parseFrontmatter(content);
2818
3065
  out.set(slug, {
@@ -2846,8 +3093,8 @@ function diffSnapshots(before, after) {
2846
3093
  }
2847
3094
  return { created, updated, archived };
2848
3095
  }
2849
- function formatSummary(result, delta, logPath, repoRoot) {
2850
- const rel = relative2(repoRoot, logPath);
3096
+ function formatSummary2(result, delta, logPath, repoRoot) {
3097
+ const rel = relative3(repoRoot, logPath);
2851
3098
  const cost = `$${result.cost.toFixed(3)}`;
2852
3099
  const { created, updated, archived } = delta;
2853
3100
  if (created === 0 && updated === 0 && archived === 0) {
@@ -2894,7 +3141,7 @@ function registerWikiLifecycleCommands(program) {
2894
3141
  emit(result);
2895
3142
  }
2896
3143
  );
2897
- program.command("capture [transcript]").description("run the writer/reviewer pipeline on a session (usually automatic)").option("--session <id>", "target a specific session by ID").option("--quiet", "suppress per-tool streaming; print only the final summary").option("--agent <agent>", "agent provider: claude, codex, or cursor").option("--model <model>", "override the agent model").action(
3144
+ const capture = program.command("capture [transcript]").alias("c").description("run the writer/reviewer pipeline on a session (usually automatic)").option("--session <id>", "target a specific session by ID").option("--quiet", "suppress per-tool streaming; print only the final summary").option("--agent <agent>", "agent provider: claude, codex, or cursor").option("--model <model>", "override the agent model").action(
2898
3145
  async (transcript, opts) => {
2899
3146
  await autoRegisterIfNeeded(process.cwd());
2900
3147
  const result = await runCapture({
@@ -2908,6 +3155,22 @@ function registerWikiLifecycleCommands(program) {
2908
3155
  emit(result);
2909
3156
  }
2910
3157
  );
3158
+ capture.command("status").description("show running and recent capture jobs").option("--json", "emit structured JSON").action(async (opts) => {
3159
+ await autoRegisterIfNeeded(process.cwd());
3160
+ const result = await runCaptureStatus({
3161
+ cwd: process.cwd(),
3162
+ json: opts.json
3163
+ });
3164
+ emit(result);
3165
+ });
3166
+ program.command("ps").description("show running and recent capture jobs").option("--json", "emit structured JSON").action(async (opts) => {
3167
+ await autoRegisterIfNeeded(process.cwd());
3168
+ const result = await runCaptureStatus({
3169
+ cwd: process.cwd(),
3170
+ json: opts.json
3171
+ });
3172
+ emit(result);
3173
+ });
2911
3174
  const hook = program.command("hook").description("manage the SessionEnd auto-capture hook");
2912
3175
  hook.command("install").description("add a SessionEnd entry that runs 'almanac capture' on session end").option("--source <source>", "claude, codex, cursor, or all").action(async (opts) => {
2913
3176
  const result = await runHookInstall({
@@ -2950,4 +3213,4 @@ function registerCommands(program) {
2950
3213
  export {
2951
3214
  registerCommands
2952
3215
  };
2953
- //# sourceMappingURL=register-commands-7SKQLQW4.js.map
3216
+ //# sourceMappingURL=register-commands-DPH4ZWEE.js.map