codealmanac 0.2.5 → 0.2.7

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.
Files changed (79) hide show
  1. package/README.md +25 -20
  2. package/dist/{agents-RVTQYE6A.js → agents-V2ZOIACP.js} +6 -5
  3. package/dist/{chunk-P5WGG4FJ.js → chunk-5BWUMAOX.js} +2 -2
  4. package/dist/chunk-5BWUMAOX.js.map +1 -0
  5. package/dist/{chunk-KQUVMF27.js → chunk-BFIG2CXM.js} +2 -516
  6. package/dist/chunk-BFIG2CXM.js.map +1 -0
  7. package/dist/{chunk-DL5BXZCX.js → chunk-BQY5L3DL.js} +3 -53
  8. package/dist/chunk-BQY5L3DL.js.map +1 -0
  9. package/dist/{chunk-F53U6JQG.js → chunk-CQJVM34R.js} +2 -2
  10. package/dist/chunk-FUBE6KCO.js +124 -0
  11. package/dist/chunk-FUBE6KCO.js.map +1 -0
  12. package/dist/chunk-IZBXXAVL.js +524 -0
  13. package/dist/chunk-IZBXXAVL.js.map +1 -0
  14. package/dist/{chunk-7JUX4ADQ.js → chunk-IZT6RBHS.js} +1 -1
  15. package/dist/{chunk-SMIK2YLU.js → chunk-JLQZELHQ.js} +82 -88
  16. package/dist/chunk-JLQZELHQ.js.map +1 -0
  17. package/dist/{chunk-TT6ZP4GS.js → chunk-KZXWPG4P.js} +2 -2
  18. package/dist/{chunk-6BJUYZ43.js → chunk-QIA22IAM.js} +8 -16
  19. package/dist/chunk-QIA22IAM.js.map +1 -0
  20. package/dist/{chunk-BGUID5BS.js → chunk-RALBM6HZ.js} +20 -139
  21. package/dist/chunk-RALBM6HZ.js.map +1 -0
  22. package/dist/{chunk-TILAKDN6.js → chunk-U5DLLWIC.js} +3 -3
  23. package/dist/chunk-WL4UE7Q6.js +1386 -0
  24. package/dist/chunk-WL4UE7Q6.js.map +1 -0
  25. package/dist/{chunk-GFUB57IT.js → chunk-ZUQN5Y3K.js} +48 -124
  26. package/dist/chunk-ZUQN5Y3K.js.map +1 -0
  27. package/dist/{chunk-MRRX4UQB.js → chunk-ZZLLOAI6.js} +3 -3
  28. package/dist/{cli-CL4ID7EO.js → cli-XWPNARA6.js} +35 -18
  29. package/dist/cli-XWPNARA6.js.map +1 -0
  30. package/dist/codealmanac.js +1 -1
  31. package/dist/{config-ML2RCR7J.js → config-KH3JUMG6.js} +4 -4
  32. package/dist/doctor-ENJT665Z.js +18 -0
  33. package/dist/paths-O5CZADP2.js +14 -0
  34. package/dist/process-KFSLENL3.js +61 -0
  35. package/dist/{register-commands-FBJ6XQ3L.js → register-commands-LULZUSPO.js} +993 -1015
  36. package/dist/register-commands-LULZUSPO.js.map +1 -0
  37. package/dist/uninstall-BD4MMQ7M.js +16 -0
  38. package/dist/uninstall-BD4MMQ7M.js.map +1 -0
  39. package/dist/update-XSKPDFMJ.js +11 -0
  40. package/dist/update-XSKPDFMJ.js.map +1 -0
  41. package/dist/{wiki-IGNRNLUZ.js → wiki-O4RWMAE6.js} +8 -6
  42. package/dist/wiki-O4RWMAE6.js.map +1 -0
  43. package/guides/mini.md +11 -9
  44. package/guides/reference.md +96 -39
  45. package/hooks/almanac-capture.sh +7 -8
  46. package/package.json +1 -1
  47. package/prompts/agents/.gitkeep +1 -0
  48. package/prompts/base/notability.md +139 -0
  49. package/prompts/base/purpose.md +85 -0
  50. package/prompts/base/syntax.md +114 -0
  51. package/prompts/operations/absorb.md +43 -0
  52. package/prompts/operations/build.md +49 -0
  53. package/prompts/operations/garden.md +51 -0
  54. package/dist/chunk-6BJUYZ43.js.map +0 -1
  55. package/dist/chunk-BGUID5BS.js.map +0 -1
  56. package/dist/chunk-DL5BXZCX.js.map +0 -1
  57. package/dist/chunk-GFUB57IT.js.map +0 -1
  58. package/dist/chunk-KQUVMF27.js.map +0 -1
  59. package/dist/chunk-P5WGG4FJ.js.map +0 -1
  60. package/dist/chunk-SMIK2YLU.js.map +0 -1
  61. package/dist/cli-CL4ID7EO.js.map +0 -1
  62. package/dist/doctor-DOLJRGS4.js +0 -17
  63. package/dist/register-commands-FBJ6XQ3L.js.map +0 -1
  64. package/dist/uninstall-DX6LFKMX.js +0 -15
  65. package/dist/update-P2IPG7RO.js +0 -11
  66. package/dist/wiki-IGNRNLUZ.js.map +0 -1
  67. package/prompts/bootstrap.md +0 -176
  68. package/prompts/reviewer.md +0 -129
  69. package/prompts/writer.md +0 -134
  70. /package/dist/{agents-RVTQYE6A.js.map → agents-V2ZOIACP.js.map} +0 -0
  71. /package/dist/{chunk-F53U6JQG.js.map → chunk-CQJVM34R.js.map} +0 -0
  72. /package/dist/{chunk-7JUX4ADQ.js.map → chunk-IZT6RBHS.js.map} +0 -0
  73. /package/dist/{chunk-TT6ZP4GS.js.map → chunk-KZXWPG4P.js.map} +0 -0
  74. /package/dist/{chunk-TILAKDN6.js.map → chunk-U5DLLWIC.js.map} +0 -0
  75. /package/dist/{chunk-MRRX4UQB.js.map → chunk-ZZLLOAI6.js.map} +0 -0
  76. /package/dist/{config-ML2RCR7J.js.map → config-KH3JUMG6.js.map} +0 -0
  77. /package/dist/{doctor-DOLJRGS4.js.map → doctor-ENJT665Z.js.map} +0 -0
  78. /package/dist/{uninstall-DX6LFKMX.js.map → paths-O5CZADP2.js.map} +0 -0
  79. /package/dist/{update-P2IPG7RO.js.map → process-KFSLENL3.js.map} +0 -0
@@ -1,4 +1,18 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ runUpdate
4
+ } from "./chunk-ZZLLOAI6.js";
5
+ import {
6
+ finishRunRecord,
7
+ listRunRecords,
8
+ markRunCancelled,
9
+ readRunRecord,
10
+ runRecordPath,
11
+ startBackgroundProcess,
12
+ startForegroundProcess,
13
+ toRunView,
14
+ writeRunRecord
15
+ } from "./chunk-WL4UE7Q6.js";
2
16
  import {
3
17
  collectOption,
4
18
  deprecationWarning,
@@ -14,40 +28,42 @@ import {
14
28
  runAgentsUse,
15
29
  runDeprecatedSetAgentModel,
16
30
  runDeprecatedSetDefaultAgent
17
- } from "./chunk-6BJUYZ43.js";
31
+ } from "./chunk-QIA22IAM.js";
18
32
  import {
19
33
  runConfigGet,
20
34
  runConfigList,
21
35
  runConfigSet,
22
36
  runConfigUnset
23
- } from "./chunk-TT6ZP4GS.js";
37
+ } from "./chunk-KZXWPG4P.js";
24
38
  import {
25
39
  addEntry,
26
40
  ancestorsInFile,
27
41
  descendantsInDb,
28
42
  dropEntry,
29
- ensureFreshIndex,
30
43
  ensureGlobalDir,
44
+ parseDuration,
45
+ readRegistry,
46
+ resolveWikiRoot,
47
+ runHealth
48
+ } from "./chunk-IZBXXAVL.js";
49
+ import {
50
+ ensureFreshIndex,
31
51
  ensureTopic,
32
52
  findTopic,
33
53
  loadTopicsFile,
34
54
  looksLikeDir,
35
55
  normalizePath,
36
56
  openIndex,
37
- parseDuration,
38
- parseFrontmatter,
39
- readRegistry,
40
- resolveWikiRoot,
41
- runHealth,
42
57
  runIndexer,
43
58
  titleCase,
44
59
  toKebabCase,
45
60
  writeTopicsFile
46
- } from "./chunk-KQUVMF27.js";
61
+ } from "./chunk-BFIG2CXM.js";
47
62
  import {
48
63
  runDoctor
49
- } from "./chunk-SMIK2YLU.js";
50
- import "./chunk-TILAKDN6.js";
64
+ } from "./chunk-JLQZELHQ.js";
65
+ import "./chunk-U5DLLWIC.js";
66
+ import "./chunk-CQJVM34R.js";
51
67
  import "./chunk-4CODZRHH.js";
52
68
  import {
53
69
  BLUE,
@@ -57,34 +73,24 @@ import {
57
73
  } from "./chunk-FM3VRDK7.js";
58
74
  import {
59
75
  runUninstall
60
- } from "./chunk-DL5BXZCX.js";
76
+ } from "./chunk-BQY5L3DL.js";
61
77
  import {
62
78
  runSetup
63
- } from "./chunk-GFUB57IT.js";
79
+ } from "./chunk-ZUQN5Y3K.js";
64
80
  import {
65
81
  runHookInstall,
66
82
  runHookStatus,
67
83
  runHookUninstall
68
84
  } from "./chunk-447U3GQJ.js";
85
+ import "./chunk-RALBM6HZ.js";
86
+ import "./chunk-FUBE6KCO.js";
69
87
  import {
70
- DEFAULT_AGENT_MODEL,
71
- assertAgentAuth,
72
- getAgentProvider,
73
- getProviderDefaultModel,
74
- parseAgentSelection
75
- } from "./chunk-BGUID5BS.js";
76
- import {
77
- runUpdate
78
- } from "./chunk-MRRX4UQB.js";
79
- import "./chunk-F53U6JQG.js";
80
- import {
81
- isAgentProviderId,
82
88
  readConfig
83
- } from "./chunk-P5WGG4FJ.js";
89
+ } from "./chunk-5BWUMAOX.js";
84
90
  import {
85
91
  findNearestAlmanacDir,
86
92
  getRepoAlmanacDir
87
- } from "./chunk-7JUX4ADQ.js";
93
+ } from "./chunk-IZT6RBHS.js";
88
94
 
89
95
  // src/topics/frontmatter-rewrite.ts
90
96
  import { readFile, rename, writeFile } from "fs/promises";
@@ -1183,7 +1189,7 @@ function isReachable(entry) {
1183
1189
  }
1184
1190
  function formatPretty(entries) {
1185
1191
  if (entries.length === 0) {
1186
- return `${DIM}no wikis registered. run \`almanac bootstrap\` in a repo to create one.${RST}
1192
+ return `${DIM}no wikis registered. run \`almanac init\` in a repo to create one.${RST}
1187
1193
  `;
1188
1194
  }
1189
1195
  const nameWidth = Math.min(
@@ -1790,7 +1796,7 @@ function registerQueryCommands(program) {
1790
1796
  // src/cli/register-setup-commands.ts
1791
1797
  function registerSetupCommands(program) {
1792
1798
  const agents = program.command("agents").description("list supported AI agent providers and readiness");
1793
- agents.command("list").description("show supported provider status, defaults, and models").action(async () => {
1799
+ agents.command("list").description("show Claude, Codex, and Cursor provider status").action(async () => {
1794
1800
  emit(await runAgentsList());
1795
1801
  });
1796
1802
  agents.command("doctor").description("diagnose supported AI agent providers").action(async () => {
@@ -1837,7 +1843,7 @@ function registerSetupCommands(program) {
1837
1843
  exitCode: 1
1838
1844
  });
1839
1845
  });
1840
- program.command("setup").description("choose provider/model and install provider integrations").option("-y, --yes", "skip prompts; install everything").option("--agent <agent>", "default provider: claude, codex, or cursor").option("--model <model>", "default model for the selected agent").option("--skip-hook", "opt out of auto-capture hooks").option("--skip-guides", "opt out of provider guide install").action(
1846
+ program.command("setup").description("install the hook + CLAUDE.md guides (bare codealmanac alias)").option("-y, --yes", "skip prompts; install everything").option("--agent <agent>", "default agent: claude, codex, or cursor").option("--model <model>", "default model for the selected agent").option("--skip-hook", "opt out of the SessionEnd hook").option("--skip-guides", "opt out of the CLAUDE.md guides").action(
1841
1847
  async (opts) => {
1842
1848
  const result = await runSetup({
1843
1849
  yes: opts.yes,
@@ -1880,9 +1886,9 @@ function registerSetupCommands(program) {
1880
1886
  emit(result);
1881
1887
  }
1882
1888
  );
1883
- program.command("uninstall").description("remove the hook + guides + import line").option("-y, --yes", "skip confirmations; remove everything").option("--keep-hook", "don't remove auto-capture hooks (guides still prompted unless --yes)").option(
1889
+ program.command("uninstall").description("remove the hook + guides + import line").option("-y, --yes", "skip confirmations; remove everything").option("--keep-hook", "don't remove the SessionEnd hook (guides still prompted unless --yes)").option(
1884
1890
  "--keep-guides",
1885
- "don't remove guides or provider imports/rules (hook still prompted unless --yes)"
1891
+ "don't remove the guides or CLAUDE.md import (hook still prompted unless --yes)"
1886
1892
  ).action(
1887
1893
  async (opts) => {
1888
1894
  const result = await runUninstall({
@@ -1895,20 +1901,290 @@ function registerSetupCommands(program) {
1895
1901
  );
1896
1902
  }
1897
1903
 
1898
- // src/commands/bootstrap.ts
1899
- import { createWriteStream, existsSync as existsSync5 } from "fs";
1900
- import { mkdir as mkdir2, readdir } from "fs/promises";
1901
- import { join as join6, relative } from "path";
1904
+ // src/commands/jobs.ts
1905
+ import { readFile as readFile4 } from "fs/promises";
1906
+
1907
+ // src/cli/outcome.ts
1908
+ function renderOutcome(outcome, opts = {}) {
1909
+ const exitCode = opts.exitCode ?? defaultExitCode(outcome);
1910
+ if (opts.json === true) {
1911
+ return {
1912
+ stdout: `${JSON.stringify(outcome, null, 2)}
1913
+ `,
1914
+ stderr: "",
1915
+ exitCode
1916
+ };
1917
+ }
1918
+ if (outcome.type === "needs-action") {
1919
+ return {
1920
+ stdout: opts.stdout ?? "",
1921
+ stderr: `almanac: ${outcome.message}
1922
+ ${outcome.fix}
1923
+ `,
1924
+ exitCode
1925
+ };
1926
+ }
1927
+ if (outcome.type === "error") {
1928
+ return {
1929
+ stdout: opts.stdout ?? "",
1930
+ stderr: `almanac: ${outcome.message}
1931
+ `,
1932
+ exitCode
1933
+ };
1934
+ }
1935
+ return {
1936
+ stdout: opts.stdout ?? `${outcome.message}
1937
+ `,
1938
+ stderr: "",
1939
+ exitCode
1940
+ };
1941
+ }
1942
+ function defaultExitCode(outcome) {
1943
+ switch (outcome.type) {
1944
+ case "success":
1945
+ case "noop":
1946
+ return 0;
1947
+ case "needs-action":
1948
+ case "error":
1949
+ return 1;
1950
+ }
1951
+ }
1952
+
1953
+ // src/commands/jobs.ts
1954
+ async function runJobsList(options) {
1955
+ const repoRoot = resolveWikiOrResult(options.cwd, options.json);
1956
+ if (typeof repoRoot !== "string") return repoRoot;
1957
+ const views = await listRunViews(repoRoot, options);
1958
+ if (options.json === true) {
1959
+ return {
1960
+ stdout: `${JSON.stringify({ runs: views }, null, 2)}
1961
+ `,
1962
+ stderr: "",
1963
+ exitCode: 0
1964
+ };
1965
+ }
1966
+ if (views.length === 0) {
1967
+ return { stdout: "Jobs\n\nNo jobs found.\n", stderr: "", exitCode: 0 };
1968
+ }
1969
+ const lines = ["Jobs", ""];
1970
+ for (const view of views) {
1971
+ lines.push(
1972
+ `${view.id} ${view.operation} ${view.displayStatus} ${formatMs(view.elapsedMs)}`
1973
+ );
1974
+ }
1975
+ return { stdout: `${lines.join("\n")}
1976
+ `, stderr: "", exitCode: 0 };
1977
+ }
1978
+ async function runJobsShow(options) {
1979
+ const repoRoot = resolveWikiOrResult(options.cwd, options.json);
1980
+ if (typeof repoRoot !== "string") return repoRoot;
1981
+ const view = await readRunView(repoRoot, options);
1982
+ if (view === null) return missingRun(options.runId, options.json);
1983
+ if (options.json === true) {
1984
+ return {
1985
+ stdout: `${JSON.stringify(view, null, 2)}
1986
+ `,
1987
+ stderr: "",
1988
+ exitCode: 0
1989
+ };
1990
+ }
1991
+ return {
1992
+ stdout: [
1993
+ `Run: ${view.id}`,
1994
+ `Operation: ${view.operation}`,
1995
+ `Status: ${view.displayStatus}`,
1996
+ `Provider: ${view.provider}${view.model !== void 0 ? `/${view.model}` : ""}`,
1997
+ `Elapsed: ${formatMs(view.elapsedMs)}`,
1998
+ `Log: ${view.logPath}`,
1999
+ view.failure !== void 0 ? `Reason: ${view.failure.message}` : view.error !== void 0 ? `Error: ${view.error}` : void 0,
2000
+ view.failure?.fix !== void 0 ? `Fix: ${view.failure.fix}` : void 0
2001
+ ].filter((line) => line !== void 0).join("\n") + "\n",
2002
+ stderr: "",
2003
+ exitCode: 0
2004
+ };
2005
+ }
2006
+ async function runJobsLogs(options) {
2007
+ const repoRoot = resolveWikiOrResult(options.cwd, options.json);
2008
+ if (typeof repoRoot !== "string") return repoRoot;
2009
+ const record = await readRunRecord(runRecordPath(repoRoot, options.runId));
2010
+ if (record === null) return missingRun(options.runId, options.json);
2011
+ try {
2012
+ return {
2013
+ stdout: await readFile4(record.logPath, "utf8"),
2014
+ stderr: "",
2015
+ exitCode: 0
2016
+ };
2017
+ } catch (err) {
2018
+ return renderOutcome(
2019
+ {
2020
+ type: "error",
2021
+ message: err instanceof Error ? err.message : String(err)
2022
+ },
2023
+ { json: options.json }
2024
+ );
2025
+ }
2026
+ }
2027
+ async function streamJobsAttach(options) {
2028
+ const repoRoot = resolveWikiOrResult(options.cwd, options.json);
2029
+ if (typeof repoRoot !== "string") return repoRoot;
2030
+ const initial = await readRunRecord(runRecordPath(repoRoot, options.runId));
2031
+ if (initial === null) return missingRun(options.runId, options.json);
2032
+ const write = options.write ?? ((chunk) => process.stdout.write(chunk));
2033
+ let offset = 0;
2034
+ while (true) {
2035
+ const record = await readRunRecord(runRecordPath(repoRoot, options.runId));
2036
+ if (record === null) return missingRun(options.runId, options.json);
2037
+ offset = await writeLogChunk(record.logPath, offset, write);
2038
+ const view = toRunView({
2039
+ record,
2040
+ now: options.now?.() ?? /* @__PURE__ */ new Date(),
2041
+ isPidAlive: options.isPidAlive ?? isPidAlive
2042
+ });
2043
+ if (view.displayStatus === "done" || view.displayStatus === "failed" || view.displayStatus === "cancelled" || view.displayStatus === "stale") {
2044
+ const summary = terminalAttachSummary(view);
2045
+ if (summary.length > 0) write(summary);
2046
+ return { stdout: "", stderr: "", exitCode: 0 };
2047
+ }
2048
+ await sleep(options.pollMs ?? 500);
2049
+ }
2050
+ }
2051
+ async function runJobsCancel(options) {
2052
+ const repoRoot = resolveWikiOrResult(options.cwd, options.json);
2053
+ if (typeof repoRoot !== "string") return repoRoot;
2054
+ const path2 = runRecordPath(repoRoot, options.runId);
2055
+ const record = await readRunRecord(path2);
2056
+ if (record === null) return missingRun(options.runId, options.json);
2057
+ if (record.status === "done" || record.status === "failed" || record.status === "cancelled") {
2058
+ return renderOutcome(
2059
+ {
2060
+ type: "noop",
2061
+ message: `job already ${record.status}: ${record.id}`,
2062
+ data: { runId: record.id, status: record.status }
2063
+ },
2064
+ { json: options.json }
2065
+ );
2066
+ }
2067
+ await markRunCancelled(repoRoot, record.id);
2068
+ if (record.pid > 0) {
2069
+ try {
2070
+ process.kill(record.pid, "SIGTERM");
2071
+ } catch {
2072
+ }
2073
+ }
2074
+ const cancelled = finishRunRecord({
2075
+ record,
2076
+ status: "cancelled",
2077
+ finishedAt: options.now?.() ?? /* @__PURE__ */ new Date()
2078
+ });
2079
+ await writeRunRecord(path2, cancelled);
2080
+ return renderOutcome(
2081
+ {
2082
+ type: "success",
2083
+ message: `cancelled job: ${record.id}`,
2084
+ data: { runId: record.id, status: "cancelled" }
2085
+ },
2086
+ { json: options.json }
2087
+ );
2088
+ }
2089
+ async function listRunViews(repoRoot, options) {
2090
+ const records = await listRunRecords(repoRoot);
2091
+ return records.map(
2092
+ (record) => toRunView({
2093
+ record,
2094
+ now: options.now?.() ?? /* @__PURE__ */ new Date(),
2095
+ isPidAlive: options.isPidAlive ?? isPidAlive
2096
+ })
2097
+ );
2098
+ }
2099
+ async function readRunView(repoRoot, options) {
2100
+ const record = await readRunRecord(runRecordPath(repoRoot, options.runId));
2101
+ if (record === null) return null;
2102
+ return toRunView({
2103
+ record,
2104
+ now: options.now?.() ?? /* @__PURE__ */ new Date(),
2105
+ isPidAlive: options.isPidAlive ?? isPidAlive
2106
+ });
2107
+ }
2108
+ function resolveWikiOrResult(cwd, json) {
2109
+ const repoRoot = findNearestAlmanacDir(cwd);
2110
+ if (repoRoot !== null) return repoRoot;
2111
+ return renderOutcome(
2112
+ {
2113
+ type: "needs-action",
2114
+ message: "no .almanac/ found in this directory or any parent",
2115
+ fix: "run: almanac init"
2116
+ },
2117
+ { json }
2118
+ );
2119
+ }
2120
+ function missingRun(runId, json) {
2121
+ return renderOutcome(
2122
+ { type: "error", message: `run not found: ${runId}` },
2123
+ { json }
2124
+ );
2125
+ }
2126
+ async function writeLogChunk(path2, offset, write) {
2127
+ let text = "";
2128
+ try {
2129
+ text = await readFile4(path2, "utf8");
2130
+ } catch {
2131
+ return offset;
2132
+ }
2133
+ if (text.length <= offset) return offset;
2134
+ const chunk = text.slice(offset);
2135
+ write(chunk);
2136
+ return text.length;
2137
+ }
2138
+ function sleep(ms) {
2139
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
2140
+ }
2141
+ function isPidAlive(pid) {
2142
+ if (pid <= 0) return false;
2143
+ try {
2144
+ process.kill(pid, 0);
2145
+ return true;
2146
+ } catch {
2147
+ return false;
2148
+ }
2149
+ }
2150
+ function formatMs(ms) {
2151
+ if (ms < 1e3) return `${ms}ms`;
2152
+ const seconds = Math.round(ms / 1e3);
2153
+ if (seconds < 60) return `${seconds}s`;
2154
+ const minutes = Math.round(seconds / 60);
2155
+ if (minutes < 60) return `${minutes}m`;
2156
+ return `${Math.round(minutes / 60)}h`;
2157
+ }
2158
+ function terminalAttachSummary(view) {
2159
+ if (view.displayStatus !== "failed" && view.displayStatus !== "stale") {
2160
+ return "";
2161
+ }
2162
+ const lines = [`job ${view.displayStatus}: ${view.id}`];
2163
+ if (view.failure !== void 0) {
2164
+ lines.push(`Reason: ${view.failure.message}`);
2165
+ if (view.failure.fix !== void 0) lines.push(`Fix: ${view.failure.fix}`);
2166
+ } else if (view.error !== void 0) {
2167
+ lines.push(`Error: ${view.error}`);
2168
+ }
2169
+ return `${lines.join("\n")}
2170
+ `;
2171
+ }
2172
+
2173
+ // src/commands/operations.ts
2174
+ import { resolve as resolve2 } from "path";
1902
2175
 
1903
2176
  // src/agent/prompts.ts
1904
2177
  import { existsSync as existsSync3 } from "fs";
1905
- import { readFile as readFile4 } from "fs/promises";
2178
+ import { readFile as readFile5 } from "fs/promises";
1906
2179
  import path from "path";
1907
2180
  import { fileURLToPath } from "url";
1908
2181
  var PROMPT_NAMES = [
1909
- "bootstrap",
1910
- "writer",
1911
- "reviewer"
2182
+ "base/purpose",
2183
+ "base/notability",
2184
+ "base/syntax",
2185
+ "operations/build",
2186
+ "operations/absorb",
2187
+ "operations/garden"
1912
2188
  ];
1913
2189
  var overrideDir = null;
1914
2190
  var resolvedDir = null;
@@ -1943,82 +2219,132 @@ function isPromptsDir(dir) {
1943
2219
  }
1944
2220
  async function loadPrompt(name) {
1945
2221
  const dir = resolvePromptsDir();
1946
- return readFile4(path.join(dir, `${name}.md`), "utf8");
2222
+ return readFile5(resolvePromptPath(dir, name), "utf8");
1947
2223
  }
1948
-
1949
- // src/agent/selection.ts
1950
- async function resolveAgentSelection(args) {
1951
- const config = await readConfig({ cwd: args.cwd });
1952
- const rawAgent = args.agent ?? process.env.ALMANAC_AGENT ?? config.agent.default;
1953
- const agentSource = args.agent !== void 0 ? "flag" : process.env.ALMANAC_AGENT !== void 0 ? "env" : "config";
1954
- const parsed = parseAgentSelection(rawAgent);
1955
- if (parsed.provider === null || !isAgentProviderId(parsed.provider)) {
1956
- return {
1957
- ok: false,
1958
- error: `unknown agent '${rawAgent}'. Expected one of: claude, codex, cursor.`
1959
- };
2224
+ function resolvePromptPath(dir, name) {
2225
+ if (name.length === 0 || path.isAbsolute(name) || name.includes("\\") || name.split("/").some((part) => part === "" || part === "." || part === "..")) {
2226
+ throw new Error(`invalid prompt name: ${name}`);
1960
2227
  }
1961
- const provider = parsed.provider;
1962
- const configuredModel = config.agent.models[provider] ?? void 0;
1963
- const model = args.model !== void 0 ? args.model : parsed.model !== void 0 && agentSource === "flag" ? parsed.model : process.env.ALMANAC_MODEL !== void 0 ? process.env.ALMANAC_MODEL : parsed.model !== void 0 ? parsed.model : configuredModel === null ? void 0 : configuredModel;
1964
- return { ok: true, provider, model };
2228
+ const file = path.resolve(dir, `${name}.md`);
2229
+ const relative = path.relative(dir, file);
2230
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
2231
+ throw new Error(`invalid prompt name: ${name}`);
2232
+ }
2233
+ return file;
2234
+ }
2235
+ function joinPrompts(parts) {
2236
+ return parts.map((part) => part?.trim()).filter((part) => part !== void 0 && part.length > 0).join("\n\n---\n\n");
1965
2237
  }
1966
2238
 
1967
- // src/cli/outcome.ts
1968
- function renderOutcome(outcome, opts = {}) {
1969
- const exitCode = opts.exitCode ?? defaultExitCode(outcome);
1970
- if (opts.json === true) {
1971
- return {
1972
- stdout: `${JSON.stringify(outcome, null, 2)}
1973
- `,
1974
- stderr: "",
1975
- exitCode
1976
- };
1977
- }
1978
- if (outcome.type === "needs-action") {
1979
- return {
1980
- stdout: opts.stdout ?? "",
1981
- stderr: `almanac: ${outcome.message}
1982
- ${outcome.fix}
1983
- `,
1984
- exitCode
1985
- };
1986
- }
1987
- if (outcome.type === "error") {
1988
- return {
1989
- stdout: opts.stdout ?? "",
1990
- stderr: `almanac: ${outcome.message}
1991
- `,
1992
- exitCode
1993
- };
1994
- }
2239
+ // src/operations/run.ts
2240
+ var DEFAULT_MAX_TURNS = 150;
2241
+ var BASE_OPERATION_TOOLS = [
2242
+ { id: "read" },
2243
+ { id: "write" },
2244
+ { id: "edit" },
2245
+ { id: "search" },
2246
+ { id: "shell" }
2247
+ ];
2248
+ var BASE_PROMPTS = [
2249
+ "base/purpose",
2250
+ "base/notability",
2251
+ "base/syntax"
2252
+ ];
2253
+ async function createOperationRunSpec(args) {
2254
+ const basePrompts = await Promise.all(
2255
+ BASE_PROMPTS.map((name) => loadPrompt(name))
2256
+ );
2257
+ const operationPrompt = await loadPrompt(args.promptName);
2258
+ const prompt = joinPrompts([
2259
+ ...basePrompts,
2260
+ operationPrompt,
2261
+ operationRuntimeContext(args.repoRoot),
2262
+ args.context
2263
+ ]);
1995
2264
  return {
1996
- stdout: opts.stdout ?? `${outcome.message}
1997
- `,
1998
- stderr: "",
1999
- exitCode
2265
+ provider: args.provider ?? { id: "claude" },
2266
+ cwd: args.repoRoot,
2267
+ prompt,
2268
+ tools: BASE_OPERATION_TOOLS,
2269
+ limits: {
2270
+ maxTurns: DEFAULT_MAX_TURNS
2271
+ },
2272
+ metadata: {
2273
+ operation: args.operation,
2274
+ targetKind: args.targetKind,
2275
+ targetPaths: args.targetPaths
2276
+ }
2000
2277
  };
2001
2278
  }
2002
- function defaultExitCode(outcome) {
2003
- switch (outcome.type) {
2004
- case "success":
2005
- case "noop":
2006
- return 0;
2007
- case "needs-action":
2008
- case "error":
2009
- return 1;
2279
+ async function runOperationProcess(args) {
2280
+ if (args.background) {
2281
+ const background = await (args.startBackground ?? startBackgroundProcess)({
2282
+ repoRoot: args.repoRoot,
2283
+ spec: args.spec,
2284
+ runId: args.runId
2285
+ });
2286
+ return { mode: "background", runId: background.runId, background };
2010
2287
  }
2288
+ const foreground = await (args.startForeground ?? startForegroundProcess)({
2289
+ repoRoot: args.repoRoot,
2290
+ spec: args.spec,
2291
+ runId: args.runId,
2292
+ onEvent: args.onEvent
2293
+ });
2294
+ return { mode: "foreground", runId: foreground.runId, foreground };
2295
+ }
2296
+ function operationRuntimeContext(repoRoot) {
2297
+ return [
2298
+ "Runtime context:",
2299
+ `- Repository root: ${repoRoot}`,
2300
+ `- Almanac directory: ${repoRoot}/.almanac`,
2301
+ `- Wiki pages directory: ${repoRoot}/.almanac/pages`
2302
+ ].join("\n");
2011
2303
  }
2012
2304
 
2013
- // src/agent/sdk.ts
2014
- async function runAgent(opts) {
2015
- const provider = opts.provider ?? "claude";
2016
- return await getAgentProvider(provider).run(opts);
2305
+ // src/operations/absorb.ts
2306
+ async function createAbsorbRunSpec(args) {
2307
+ return createOperationRunSpec({
2308
+ operation: "absorb",
2309
+ promptName: "operations/absorb",
2310
+ provider: args.provider ?? { id: "claude" },
2311
+ repoRoot: args.repoRoot,
2312
+ context: args.context,
2313
+ targetKind: args.targetKind,
2314
+ targetPaths: args.targetPaths
2315
+ });
2316
+ }
2317
+ async function runAbsorbOperation(options) {
2318
+ const repoRoot = findNearestAlmanacDir(options.cwd);
2319
+ if (repoRoot === null) {
2320
+ throw new Error("no .almanac/ found in this directory or any parent");
2321
+ }
2322
+ const spec = await createAbsorbRunSpec({
2323
+ repoRoot,
2324
+ provider: options.provider,
2325
+ context: options.context,
2326
+ targetKind: options.targetKind,
2327
+ targetPaths: options.targetPaths
2328
+ });
2329
+ return runOperationProcess({
2330
+ repoRoot,
2331
+ spec,
2332
+ background: options.background !== false,
2333
+ runId: options.runId,
2334
+ onEvent: options.onEvent,
2335
+ startForeground: options.startForeground,
2336
+ startBackground: options.startBackground
2337
+ });
2017
2338
  }
2018
2339
 
2340
+ // src/operations/build.ts
2341
+ import { existsSync as existsSync5 } from "fs";
2342
+ import { readdir } from "fs/promises";
2343
+ import { join as join6 } from "path";
2344
+
2019
2345
  // src/commands/init.ts
2020
2346
  import { existsSync as existsSync4 } from "fs";
2021
- import { mkdir, readFile as readFile5, writeFile as writeFile2 } from "fs/promises";
2347
+ import { mkdir, readFile as readFile6, writeFile as writeFile2 } from "fs/promises";
2022
2348
  import { basename as basename2, join as join5 } from "path";
2023
2349
  async function initWiki(options) {
2024
2350
  const repoRoot = findNearestAlmanacDir(options.cwd) ?? options.cwd;
@@ -2030,7 +2356,7 @@ async function initWiki(options) {
2030
2356
  if (!existsSync4(readmePath)) {
2031
2357
  await writeFile2(readmePath, starterReadme(), "utf8");
2032
2358
  }
2033
- await ensureGitignoreHasIndexDb(repoRoot);
2359
+ await ensureGitignoreHasRuntimeArtifacts(repoRoot);
2034
2360
  const name = toKebabCase(options.name ?? basename2(repoRoot));
2035
2361
  if (name.length === 0) {
2036
2362
  throw new Error(
@@ -2048,23 +2374,17 @@ async function initWiki(options) {
2048
2374
  await addEntry(entry);
2049
2375
  return { entry, almanacDir, created: !alreadyExisted };
2050
2376
  }
2051
- async function ensureGitignoreHasIndexDb(cwd) {
2377
+ async function ensureGitignoreHasRuntimeArtifacts(cwd) {
2052
2378
  const path2 = join5(cwd, ".gitignore");
2053
2379
  const targets = [
2054
2380
  ".almanac/index.db",
2055
2381
  ".almanac/index.db-wal",
2056
2382
  ".almanac/index.db-shm",
2057
- // Runtime logs written by the AI pipeline. These can be multi-megabyte
2058
- // JSONL files and should never be committed. Keep the old root-level
2059
- // globs too so repos initialized by earlier releases stay quiet.
2060
- ".almanac/logs/",
2061
- ".almanac/.capture-*",
2062
- ".almanac/.bootstrap-*",
2063
- ".almanac/.ingest-*"
2383
+ ".almanac/runs/"
2064
2384
  ];
2065
2385
  let existing = "";
2066
2386
  if (existsSync4(path2)) {
2067
- existing = await readFile5(path2, "utf8");
2387
+ existing = await readFile6(path2, "utf8");
2068
2388
  }
2069
2389
  const lines = existing.split(/\r?\n/).map((l) => l.trim());
2070
2390
  const missing = targets.filter((t) => !lines.includes(t));
@@ -2156,882 +2476,224 @@ and optional \`files:\`. The rest is prose.
2156
2476
  `;
2157
2477
  }
2158
2478
 
2159
- // src/commands/bootstrap.ts
2160
- var BOOTSTRAP_TOOLS = ["Read", "Write", "Edit", "Glob", "Grep", "Bash"];
2161
- async function runBootstrap(options) {
2162
- const repoRoot = findNearestAlmanacDir(options.cwd) ?? options.cwd;
2163
- const providerResolution = await resolveAgentSelection({
2164
- agent: options.agent,
2165
- model: options.model,
2166
- cwd: repoRoot
2479
+ // src/operations/build.ts
2480
+ async function createBuildRunSpec(args) {
2481
+ return createOperationRunSpec({
2482
+ operation: "build",
2483
+ promptName: "operations/build",
2484
+ provider: args.provider ?? { id: "claude" },
2485
+ repoRoot: args.repoRoot,
2486
+ context: args.context,
2487
+ targetKind: "repo",
2488
+ targetPaths: [args.repoRoot]
2167
2489
  });
2168
- if (!providerResolution.ok) {
2169
- return renderOutcome(
2170
- { type: "error", message: providerResolution.error },
2171
- { json: options.json }
2172
- );
2173
- }
2174
- const { provider, model } = providerResolution;
2175
- try {
2176
- await assertAgentAuth({ provider, spawnCli: options.spawnCli });
2177
- } catch (err) {
2178
- const msg = err instanceof Error ? err.message : String(err);
2179
- return renderOutcome(
2180
- {
2181
- type: "needs-action",
2182
- message: msg,
2183
- fix: authFixFor(provider),
2184
- data: { provider }
2185
- },
2186
- { json: options.json }
2490
+ }
2491
+ async function runBuildOperation(options) {
2492
+ const init = await initWiki({ cwd: options.cwd });
2493
+ const repoRoot = init.entry.path;
2494
+ const pageCount = await countWikiPages(repoRoot);
2495
+ if (pageCount > 0 && options.force !== true) {
2496
+ throw new Error(
2497
+ `.almanac/ already initialized with ${pageCount} page${pageCount === 1 ? "" : "s"}; pass --force to rebuild`
2187
2498
  );
2188
2499
  }
2189
- const almanacDir = getRepoAlmanacDir(repoRoot);
2190
- const pagesDir = join6(almanacDir, "pages");
2191
- if (options.force !== true && existsSync5(pagesDir)) {
2192
- const existing = await countMarkdownPages(pagesDir);
2193
- if (existing > 0) {
2194
- return renderOutcome(
2195
- {
2196
- type: "needs-action",
2197
- message: `.almanac/ already initialized with ${existing} page${existing === 1 ? "" : "s"}.`,
2198
- fix: "run: almanac capture (or pass --force to overwrite)",
2199
- data: { pages: existing }
2200
- },
2201
- { json: options.json }
2202
- );
2203
- }
2204
- }
2205
- if (!existsSync5(almanacDir)) {
2206
- try {
2207
- await initWiki({ cwd: repoRoot });
2208
- } catch (err) {
2209
- const msg = err instanceof Error ? err.message : String(err);
2210
- return renderOutcome(
2211
- { type: "error", message: `init failed during bootstrap: ${msg}` },
2212
- { json: options.json }
2213
- );
2214
- }
2215
- }
2216
- const systemPrompt = await loadPrompt("bootstrap");
2217
- const now = options.now?.() ?? /* @__PURE__ */ new Date();
2218
- const logsDir = join6(almanacDir, "logs");
2219
- await mkdir2(logsDir, { recursive: true });
2220
- const logName = `.bootstrap-${formatTimestamp(now)}.log`;
2221
- const logPath = join6(logsDir, logName);
2222
- const logStream = createWriteStream(logPath, { flags: "w" });
2223
- const out = process.stdout;
2224
- const formatter = new StreamingFormatter({
2225
- write: (line) => {
2226
- if (options.quiet !== true && options.json !== true) out.write(line);
2227
- }
2500
+ const spec = await createBuildRunSpec({
2501
+ repoRoot,
2502
+ provider: options.provider,
2503
+ context: options.context
2228
2504
  });
2229
- const onMessage = (msg) => {
2230
- try {
2231
- logStream.write(`${JSON.stringify(msg)}
2232
- `);
2233
- } catch {
2234
- }
2235
- formatter.handle(msg);
2236
- };
2237
- const runner = options.runAgent ?? runAgent;
2238
- const userPrompt = `Begin the bootstrap now. Working directory: ${repoRoot}.`;
2239
- let result;
2240
- try {
2241
- result = await runner({
2242
- systemPrompt,
2243
- prompt: userPrompt,
2244
- allowedTools: BOOTSTRAP_TOOLS,
2245
- cwd: repoRoot,
2246
- provider,
2247
- model,
2248
- onMessage
2249
- });
2250
- } finally {
2251
- await closeStream(logStream);
2252
- }
2253
- const finalLine = formatFinalLine(result, logPath, repoRoot);
2254
- if (result.success) {
2255
- return renderOutcome(
2256
- {
2257
- type: "success",
2258
- message: finalLine,
2259
- data: runData(result, logPath, repoRoot)
2260
- },
2261
- { json: options.json }
2262
- );
2263
- }
2264
- return renderOutcome(
2265
- {
2266
- type: "error",
2267
- message: `bootstrap failed: ${result.error ?? "unknown error"}`,
2268
- data: runData(result, logPath, repoRoot)
2269
- },
2270
- {
2271
- json: options.json,
2272
- stdout: options.quiet === true || options.json === true ? "" : `${finalLine}
2273
- `
2274
- }
2275
- );
2276
- }
2277
- function authFixFor(provider) {
2278
- switch (provider) {
2279
- case "claude":
2280
- return "run: claude auth login --claudeai (or export ANTHROPIC_API_KEY)";
2281
- case "codex":
2282
- return "run: codex login";
2283
- case "cursor":
2284
- return "run: cursor-agent login";
2285
- default:
2286
- return "run: almanac agents doctor";
2287
- }
2288
- }
2289
- function runData(result, logPath, repoRoot) {
2290
- return {
2291
- transcript: relative(repoRoot, logPath),
2292
- cost: result.cost,
2293
- turns: result.turns,
2294
- sessionId: result.sessionId
2295
- };
2296
- }
2297
- function formatFinalLine(result, logPath, repoRoot) {
2298
- const status = result.success ? "done" : "failed";
2299
- const rel = relative(repoRoot, logPath);
2300
- const usage = formatRunUsage(result);
2301
- return `[${status}] ${usage} (transcript: ${rel})`;
2302
- }
2303
- function formatRunUsage(result) {
2304
- const parts = [];
2305
- if (result.cost > 0 || result.usage === void 0) {
2306
- parts.push(`cost: $${result.cost.toFixed(3)}`);
2307
- }
2308
- parts.push(`turns: ${result.turns}`);
2309
- const usage = result.usage;
2310
- if (usage !== void 0) {
2311
- const tokenParts = [];
2312
- if (usage.inputTokens !== void 0) {
2313
- tokenParts.push(`${usage.inputTokens.toLocaleString("en-US")} in`);
2314
- }
2315
- if (usage.outputTokens !== void 0) {
2316
- tokenParts.push(`${usage.outputTokens.toLocaleString("en-US")} out`);
2317
- }
2318
- if (usage.cachedInputTokens !== void 0) {
2319
- tokenParts.push(
2320
- `${usage.cachedInputTokens.toLocaleString("en-US")} cached`
2321
- );
2322
- }
2323
- if (usage.reasoningOutputTokens !== void 0) {
2324
- tokenParts.push(
2325
- `${usage.reasoningOutputTokens.toLocaleString("en-US")} reasoning`
2326
- );
2327
- }
2328
- if (tokenParts.length > 0) {
2329
- parts.push(`tokens: ${tokenParts.join(", ")}`);
2330
- }
2331
- }
2332
- return parts.join(", ");
2333
- }
2334
- async function countMarkdownPages(pagesDir) {
2335
- try {
2336
- const entries = await readdir(pagesDir, { withFileTypes: true });
2337
- return entries.filter((e) => e.isFile() && e.name.endsWith(".md")).length;
2338
- } catch {
2339
- return 0;
2340
- }
2341
- }
2342
- function closeStream(stream) {
2343
- return new Promise((resolve) => {
2344
- stream.end(() => resolve());
2505
+ return runOperationProcess({
2506
+ repoRoot,
2507
+ spec,
2508
+ background: options.background === true,
2509
+ runId: options.runId,
2510
+ onEvent: options.onEvent,
2511
+ startForeground: options.startForeground,
2512
+ startBackground: options.startBackground
2345
2513
  });
2346
2514
  }
2347
- function formatTimestamp(d) {
2348
- const pad = (n) => n.toString().padStart(2, "0");
2349
- const y = d.getFullYear();
2350
- const mo = pad(d.getMonth() + 1);
2351
- const da = pad(d.getDate());
2352
- const h = pad(d.getHours());
2353
- const mi = pad(d.getMinutes());
2354
- const s = pad(d.getSeconds());
2355
- return `${y}${mo}${da}-${h}${mi}${s}`;
2356
- }
2357
- var StreamingFormatter = class {
2358
- sink;
2359
- /**
2360
- * Current agent label. Starts as "bootstrap"; switches when we see an
2361
- * `Agent` tool-use (slice 5 will exercise this). We still track it here
2362
- * so the formatter can stay shared between bootstrap and capture.
2363
- */
2364
- currentAgent = "bootstrap";
2365
- constructor(sink) {
2366
- this.sink = sink;
2367
- }
2368
- /**
2369
- * Swap the top-level agent label. `capture` uses this to relabel from
2370
- * the default "bootstrap" to "writer" — otherwise the writer's tool-use
2371
- * output would render as `[bootstrap] …`, which is confusing when you're
2372
- * reading capture logs.
2373
- */
2374
- setAgent(name) {
2375
- this.currentAgent = name;
2376
- }
2377
- handle(msg) {
2378
- if (!isRecord(msg)) return;
2379
- if (msg.type === "assistant" && isRecord(msg.message)) {
2380
- const content = msg.message.content;
2381
- if (!Array.isArray(content)) return;
2382
- for (const block of msg.message.content) {
2383
- if (!isRecord(block) || block.type !== "tool_use") continue;
2384
- if (typeof block.name !== "string") continue;
2385
- this.handleToolUse(block.name, block.input);
2386
- }
2387
- return;
2388
- }
2389
- if (msg.type === "item.started" && isRecord(msg.item)) {
2390
- this.handleCodexItemStarted(msg.item);
2391
- return;
2392
- }
2393
- if (msg.type === "item.completed" && isRecord(msg.item)) {
2394
- this.handleCodexItemCompleted(msg.item);
2395
- return;
2396
- }
2397
- if (msg.type === "tool_call") {
2398
- this.handleCursorToolCall(msg);
2399
- return;
2400
- }
2401
- }
2402
- handleToolUse(name, rawInput) {
2403
- const input = normalizeToolInput(rawInput);
2404
- if (name === "Agent") {
2405
- const sub = typeof input.subagent_type === "string" ? input.subagent_type : "subagent";
2406
- this.currentAgent = sub;
2407
- this.sink.write(`[${sub}] starting
2408
- `);
2409
- return;
2410
- }
2411
- const summary = formatToolSummary(name, input);
2412
- this.sink.write(`[${this.currentAgent}] ${summary}
2413
- `);
2414
- }
2415
- handleCodexItemStarted(item) {
2416
- if (item.type !== "command_execution") return;
2417
- const command = stringField(item, "command") ?? "?";
2418
- this.sink.write(`[${this.currentAgent}] bash ${truncate(command, 80)}
2419
- `);
2420
- }
2421
- handleCodexItemCompleted(item) {
2422
- if (item.type !== "file_change") return;
2423
- const changes = Array.isArray(item.changes) ? item.changes : [];
2424
- for (const change of changes) {
2425
- if (!isRecord(change)) continue;
2426
- const rawPath = stringField(change, "path") ?? "?";
2427
- const kind = stringField(change, "kind") ?? "edit";
2428
- const verb = kind === "add" ? "writing" : kind === "delete" ? "deleting" : "editing";
2429
- this.sink.write(
2430
- `[${this.currentAgent}] ${verb} ${formatCodexPath(rawPath)}
2431
- `
2432
- );
2433
- }
2434
- }
2435
- handleCursorToolCall(msg) {
2436
- if (msg.subtype !== "started" || !isRecord(msg.tool_call)) return;
2437
- const summary = formatCursorToolSummary(msg.tool_call);
2438
- if (summary === void 0) return;
2439
- this.sink.write(`[${this.currentAgent}] ${summary}
2440
- `);
2441
- }
2442
- };
2443
- function normalizeToolInput(raw) {
2444
- if (typeof raw === "string") {
2445
- try {
2446
- const parsed = JSON.parse(raw);
2447
- if (parsed !== null && typeof parsed === "object") {
2448
- return parsed;
2449
- }
2450
- } catch {
2451
- }
2452
- return {};
2453
- }
2454
- if (raw !== null && typeof raw === "object") {
2455
- return raw;
2456
- }
2457
- return {};
2515
+ async function countWikiPages(repoRoot) {
2516
+ const pagesDir = join6(repoRoot, ".almanac", "pages");
2517
+ if (!existsSync5(pagesDir)) return 0;
2518
+ const entries = await readdir(pagesDir);
2519
+ return entries.filter((entry) => entry.endsWith(".md")).length;
2458
2520
  }
2459
- function formatToolSummary(name, input) {
2460
- switch (name) {
2461
- case "Read": {
2462
- const target = stringField(input, "file_path") ?? "?";
2463
- return `reading ${target}`;
2464
- }
2465
- case "Write": {
2466
- const target = stringField(input, "file_path") ?? "?";
2467
- return `writing ${target}`;
2468
- }
2469
- case "Edit": {
2470
- const target = stringField(input, "file_path") ?? "?";
2471
- return `editing ${target}`;
2472
- }
2473
- case "Glob": {
2474
- const pattern = stringField(input, "pattern") ?? "?";
2475
- return `glob ${pattern}`;
2476
- }
2477
- case "Grep": {
2478
- const pattern = stringField(input, "pattern") ?? "?";
2479
- return `grep ${pattern}`;
2480
- }
2481
- case "Bash": {
2482
- const command = stringField(input, "command") ?? "?";
2483
- return `bash ${truncate(command, 80)}`;
2484
- }
2485
- default: {
2486
- return name;
2487
- }
2488
- }
2489
- }
2490
- function formatCursorToolSummary(toolCall) {
2491
- if (isRecord(toolCall.readToolCall)) {
2492
- const args = recordField(toolCall.readToolCall, "args");
2493
- return `reading ${stringField(args, "path") ?? "?"}`;
2494
- }
2495
- if (isRecord(toolCall.editToolCall)) {
2496
- const args = recordField(toolCall.editToolCall, "args");
2497
- return `editing ${formatCodexPath(stringField(args, "path") ?? "?")}`;
2498
- }
2499
- if (isRecord(toolCall.globToolCall)) {
2500
- const args = recordField(toolCall.globToolCall, "args");
2501
- return `glob ${stringField(args, "globPattern") ?? "?"}`;
2502
- }
2503
- if (isRecord(toolCall.grepToolCall)) {
2504
- const args = recordField(toolCall.grepToolCall, "args");
2505
- return `grep ${stringField(args, "pattern") ?? "?"}`;
2506
- }
2507
- if (isRecord(toolCall.shellToolCall)) {
2508
- const args = recordField(toolCall.shellToolCall, "args");
2509
- const description = stringField(toolCall.shellToolCall, "description");
2510
- const command = stringField(args, "command") ?? description ?? "?";
2511
- return `bash ${truncate(command, 80)}`;
2512
- }
2513
- return void 0;
2514
- }
2515
- function truncate(value, max) {
2516
- return value.length > max ? `${value.slice(0, max - 3)}...` : value;
2517
- }
2518
- function formatCodexPath(value) {
2519
- const marker = "/.almanac/";
2520
- const idx = value.indexOf(marker);
2521
- if (idx !== -1) {
2522
- return `.almanac/${value.slice(idx + marker.length)}`;
2523
- }
2524
- return value;
2525
- }
2526
- function stringField(input, key) {
2527
- const value = input[key];
2528
- return typeof value === "string" ? value : void 0;
2529
- }
2530
- function recordField(input, key) {
2531
- const value = input[key];
2532
- return isRecord(value) ? value : {};
2533
- }
2534
- function isRecord(value) {
2535
- return value !== null && typeof value === "object";
2536
- }
2537
-
2538
- // src/commands/capture.ts
2539
- import { createHash } from "crypto";
2540
- import {
2541
- createWriteStream as createWriteStream2,
2542
- existsSync as existsSync7,
2543
- statSync
2544
- } from "fs";
2545
- import { mkdir as mkdir4, readFile as readFile7, readdir as readdir3, stat } from "fs/promises";
2546
- import { homedir } from "os";
2547
- import { basename as basename3, join as join8, relative as relative3 } from "path";
2548
2521
 
2549
- // src/commands/captureStatus.ts
2550
- import { existsSync as existsSync6 } from "fs";
2551
- import { mkdir as mkdir3, readFile as readFile6, readdir as readdir2, rename as rename2, writeFile as writeFile3 } from "fs/promises";
2552
- import { dirname, join as join7, relative as relative2 } from "path";
2553
- function captureStatePath(dir, stem) {
2554
- return join7(dir, `.capture-${stem}.state.json`);
2555
- }
2556
- async function writeCaptureRunRecord(path2, record) {
2557
- await mkdir3(dirname(path2), { recursive: true });
2558
- const tmp = `${path2}.tmp-${process.pid}`;
2559
- await writeFile3(tmp, `${JSON.stringify(record, null, 2)}
2560
- `, "utf8");
2561
- await rename2(tmp, path2);
2562
- }
2563
- function buildStartedCaptureRecord(args) {
2564
- return {
2565
- version: 1,
2566
- kind: "capture",
2567
- status: "running",
2568
- sessionId: args.sessionId ?? args.stem,
2522
+ // src/operations/garden.ts
2523
+ async function createGardenRunSpec(args) {
2524
+ return createOperationRunSpec({
2525
+ operation: "garden",
2526
+ promptName: "operations/garden",
2527
+ provider: args.provider ?? { id: "claude" },
2569
2528
  repoRoot: args.repoRoot,
2570
- pid: process.pid,
2571
- model: args.model ?? DEFAULT_AGENT_MODEL,
2572
- transcriptPath: args.transcriptPath,
2573
- startedAt: args.startedAt.toISOString(),
2574
- logPath: join7(args.almanacDir, `.capture-${args.stem}.log`),
2575
- jsonlPath: join7(args.almanacDir, `.capture-${args.stem}.jsonl`)
2576
- };
2577
- }
2578
- function finishCaptureRecord(args) {
2579
- const started = Date.parse(args.record.startedAt);
2580
- const finished = args.finishedAt.getTime();
2581
- return {
2582
- ...args.record,
2583
- status: args.status,
2584
- finishedAt: args.finishedAt.toISOString(),
2585
- durationMs: Number.isFinite(started) ? Math.max(0, finished - started) : void 0,
2586
- summary: args.summary,
2587
- error: args.error
2588
- };
2589
- }
2590
- async function runCaptureStatus(options) {
2591
- const repoRoot = findNearestAlmanacDir(options.cwd);
2592
- if (repoRoot === null) {
2593
- return {
2594
- stdout: "",
2595
- stderr: "almanac: no .almanac/ found in this directory or any parent. Run 'almanac bootstrap' first.\n",
2596
- exitCode: 1
2597
- };
2598
- }
2599
- const almanacDir = getRepoAlmanacDir(repoRoot);
2600
- const records = await readCaptureRecords(almanacDir);
2601
- const now = options.now?.() ?? /* @__PURE__ */ new Date();
2602
- const isPidAlive = options.isPidAlive ?? defaultIsPidAlive;
2603
- const views = records.map((record) => toView(record, repoRoot, now, isPidAlive)).sort((a, b) => b.sortTime - a.sortTime);
2604
- if (options.json === true) {
2605
- return {
2606
- stdout: `${JSON.stringify(
2607
- {
2608
- repo: repoRoot,
2609
- captures: views.map(({ sortTime: _sortTime, ...v }) => v)
2610
- },
2611
- null,
2612
- 2
2613
- )}
2614
- `,
2615
- stderr: "",
2616
- exitCode: 0
2617
- };
2618
- }
2619
- return {
2620
- stdout: formatCaptureStatus(views),
2621
- stderr: "",
2622
- exitCode: 0
2623
- };
2624
- }
2625
- async function readCaptureRecords(almanacDir) {
2626
- if (!existsSync6(almanacDir)) return [];
2627
- const out = [];
2628
- const dirs = [join7(almanacDir, "logs"), almanacDir];
2629
- for (const dir of dirs) {
2630
- let entries;
2631
- try {
2632
- entries = await readdir2(dir);
2633
- } catch {
2634
- continue;
2635
- }
2636
- for (const entry of entries) {
2637
- if (!entry.startsWith(".capture-") || !entry.endsWith(".state.json")) {
2638
- continue;
2639
- }
2640
- try {
2641
- const parsed = JSON.parse(await readFile6(join7(dir, entry), "utf8"));
2642
- if (isCaptureRunRecord(parsed)) out.push(parsed);
2643
- } catch {
2644
- continue;
2645
- }
2646
- }
2647
- }
2648
- return out;
2649
- }
2650
- function isCaptureRunRecord(value) {
2651
- if (value === null || typeof value !== "object") return false;
2652
- const v = value;
2653
- 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";
2654
- }
2655
- function toView(record, repoRoot, now, isPidAlive) {
2656
- const started = Date.parse(record.startedAt);
2657
- const finished = record.finishedAt !== void 0 ? Date.parse(record.finishedAt) : void 0;
2658
- const elapsedMs = record.durationMs ?? (Number.isFinite(started) ? Math.max(0, (finished ?? now.getTime()) - started) : 0);
2659
- const status = record.status === "running" && !isPidAlive(record.pid) ? "stale" : record.status;
2660
- return {
2661
- status,
2662
- sessionId: record.sessionId,
2663
- model: record.model,
2664
- elapsedMs,
2665
- startedAt: record.startedAt,
2666
- finishedAt: record.finishedAt,
2667
- pid: record.pid,
2668
- logPath: relative2(repoRoot, record.logPath),
2669
- jsonlPath: relative2(repoRoot, record.jsonlPath),
2670
- summary: record.summary,
2671
- error: status === "stale" ? "process ended without a final status" : record.error,
2672
- sortTime: finished ?? (Number.isFinite(started) ? started : 0)
2673
- };
2674
- }
2675
- function formatCaptureStatus(views) {
2676
- const lines = ["Capture jobs", ""];
2677
- if (views.length === 0) {
2678
- lines.push("No capture jobs found.");
2679
- return `${lines.join("\n")}
2680
- `;
2681
- }
2682
- const active = views.filter((v) => v.status === "running" || v.status === "stale");
2683
- const finished = views.filter((v) => v.status === "done" || v.status === "failed");
2684
- if (active.length === 0) {
2685
- lines.push("No active captures.", "");
2686
- } else {
2687
- for (const view of active) {
2688
- lines.push(formatRow(view));
2689
- lines.push(` log: ${view.logPath}`);
2690
- if (view.error !== void 0) lines.push(` error: ${view.error}`);
2691
- lines.push("");
2692
- }
2693
- }
2694
- if (finished.length > 0) {
2695
- lines.push(active.length === 0 ? "Last capture:" : "Last finished:");
2696
- for (const view of finished.slice(0, 3)) {
2697
- lines.push(formatRow(view));
2698
- if (view.status === "failed") {
2699
- lines.push(` log: ${view.logPath}`);
2700
- if (view.error !== void 0) lines.push(` error: ${view.error}`);
2701
- }
2702
- }
2703
- }
2704
- return `${trimTrailingBlank(lines).join("\n")}
2705
- `;
2706
- }
2707
- function formatRow(view) {
2708
- const status = view.status.padEnd(7, " ");
2709
- const session = view.sessionId.padEnd(12, " ");
2710
- const model = view.model.padEnd(17, " ");
2711
- const elapsed = formatDuration(view.elapsedMs);
2712
- const summary = formatSummary(view);
2713
- return `${status} ${session} ${model} ${elapsed}${summary.length > 0 ? ` ${summary}` : ""}`;
2714
- }
2715
- function formatSummary(view) {
2716
- if (view.status === "failed") return "failed; see log";
2717
- if (view.summary === void 0) return "";
2718
- const parts = [];
2719
- if (view.summary.updated > 0) {
2720
- parts.push(`${view.summary.updated} updated`);
2721
- }
2722
- if (view.summary.created > 0) {
2723
- parts.push(`${view.summary.created} created`);
2724
- }
2725
- if (view.summary.archived > 0) {
2726
- parts.push(`${view.summary.archived} archived`);
2727
- }
2728
- return parts.length > 0 ? parts.join(", ") : "0 pages written";
2729
- }
2730
- function formatDuration(ms) {
2731
- const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
2732
- const minutes = Math.floor(totalSeconds / 60);
2733
- const seconds = totalSeconds % 60;
2734
- if (minutes < 60) return `${minutes}m${seconds.toString().padStart(2, "0")}s`;
2735
- const hours = Math.floor(minutes / 60);
2736
- const restMinutes = minutes % 60;
2737
- return `${hours}h${restMinutes.toString().padStart(2, "0")}m`;
2738
- }
2739
- function trimTrailingBlank(lines) {
2740
- while (lines.length > 0 && lines[lines.length - 1] === "") {
2741
- lines.pop();
2742
- }
2743
- return lines;
2744
- }
2745
- function defaultIsPidAlive(pid) {
2746
- try {
2747
- process.kill(pid, 0);
2748
- return true;
2749
- } catch {
2750
- return false;
2751
- }
2529
+ context: args.context,
2530
+ targetKind: "wiki",
2531
+ targetPaths: [`${args.repoRoot}/.almanac`]
2532
+ });
2752
2533
  }
2753
-
2754
- // src/commands/capture.ts
2755
- var WRITER_TOOLS = ["Read", "Write", "Edit", "Glob", "Grep", "Bash", "Agent"];
2756
- var REVIEWER_TOOLS = ["Read", "Grep", "Glob", "Bash"];
2757
- var REVIEWER_DESCRIPTION = "Reviews proposed wiki changes against the full knowledge base for cohesion, duplication, missing links, notability, and writing conventions.";
2758
- async function runCapture(options) {
2534
+ async function runGardenOperation(options) {
2759
2535
  const repoRoot = findNearestAlmanacDir(options.cwd);
2760
2536
  if (repoRoot === null) {
2761
- return renderOutcome(
2762
- {
2763
- type: "needs-action",
2764
- message: "no .almanac/ found in this directory or any parent.",
2765
- fix: "run: almanac bootstrap"
2766
- },
2767
- { json: options.json }
2768
- );
2537
+ throw new Error("no .almanac/ found in this directory or any parent");
2769
2538
  }
2770
- const providerResolution = await resolveAgentSelection({
2771
- agent: options.agent,
2772
- model: options.model,
2773
- cwd: repoRoot
2774
- });
2775
- if (!providerResolution.ok) {
2776
- return renderOutcome(
2777
- { type: "error", message: providerResolution.error },
2778
- { json: options.json }
2779
- );
2780
- }
2781
- const { provider, model } = providerResolution;
2782
- const statusModel = model ?? getProviderDefaultModel(provider) ?? "provider default";
2783
- try {
2784
- await (options.assertAgentAuthFn ?? assertAgentAuth)({
2785
- provider,
2786
- spawnCli: options.spawnCli
2787
- });
2788
- } catch (err) {
2789
- const msg = err instanceof Error ? err.message : String(err);
2790
- return renderOutcome(
2791
- {
2792
- type: "needs-action",
2793
- message: msg,
2794
- fix: authFixFor2(provider),
2795
- data: { provider }
2796
- },
2797
- { json: options.json }
2798
- );
2799
- }
2800
- const almanacDir = getRepoAlmanacDir(repoRoot);
2801
- const pagesDir = join8(almanacDir, "pages");
2802
- const transcriptResolution = await resolveTranscript({
2539
+ const spec = await createGardenRunSpec({
2803
2540
  repoRoot,
2804
- explicit: options.transcriptPath,
2805
- sessionId: options.sessionId,
2806
- claudeProjectsDir: options.claudeProjectsDir
2541
+ provider: options.provider,
2542
+ context: options.context
2807
2543
  });
2808
- if (!transcriptResolution.ok) {
2809
- return renderOutcome(
2810
- {
2811
- type: "needs-action",
2812
- message: transcriptResolution.error,
2813
- fix: transcriptFix(transcriptResolution.error)
2814
- },
2815
- { json: options.json }
2816
- );
2817
- }
2818
- const transcriptPath = transcriptResolution.path;
2819
- const snapshotBefore = await snapshotPages(pagesDir);
2820
- const systemPrompt = await loadPrompt("writer");
2821
- const reviewerPrompt = await loadPrompt("reviewer");
2822
- const agents = {
2823
- reviewer: {
2824
- description: REVIEWER_DESCRIPTION,
2825
- prompt: reviewerPrompt,
2826
- tools: REVIEWER_TOOLS
2827
- }
2828
- };
2829
- const startedAt = options.now?.() ?? /* @__PURE__ */ new Date();
2830
- const logStem = options.sessionId !== void 0 && options.sessionId.length > 0 ? options.sessionId : formatTimestamp2(startedAt);
2831
- const logsDir = join8(almanacDir, "logs");
2832
- await mkdir4(logsDir, { recursive: true });
2833
- const logName = `.capture-${logStem}.jsonl`;
2834
- const logPath = join8(logsDir, logName);
2835
- const statePath = captureStatePath(logsDir, logStem);
2836
- const stateRecord = buildStartedCaptureRecord({
2544
+ return runOperationProcess({
2837
2545
  repoRoot,
2838
- almanacDir: logsDir,
2839
- stem: logStem,
2840
- sessionId: options.sessionId,
2841
- transcriptPath,
2842
- model: statusModel,
2843
- startedAt
2844
- });
2845
- await writeCaptureRunRecord(statePath, stateRecord).catch(() => {
2846
- });
2847
- const logStream = createWriteStream2(logPath, { flags: "w" });
2848
- const out = process.stdout;
2849
- const formatter = new StreamingFormatter({
2850
- write: (line) => {
2851
- if (options.quiet !== true && options.json !== true) out.write(line);
2852
- }
2546
+ spec,
2547
+ background: options.background !== false,
2548
+ runId: options.runId,
2549
+ onEvent: options.onEvent,
2550
+ startForeground: options.startForeground,
2551
+ startBackground: options.startBackground
2853
2552
  });
2854
- formatter.setAgent("writer");
2855
- const onMessage = (msg) => {
2856
- try {
2857
- logStream.write(`${JSON.stringify(msg)}
2858
- `);
2859
- } catch {
2860
- }
2861
- formatter.handle(msg);
2862
- };
2863
- const userPrompt = `Capture this coding session.
2864
- Transcript: ${transcriptPath}.
2865
- Working directory: ${repoRoot}.`;
2866
- const runner = options.runAgent ?? runAgent;
2867
- let result;
2868
- try {
2869
- result = await runner({
2870
- systemPrompt,
2871
- prompt: userPrompt,
2872
- allowedTools: WRITER_TOOLS,
2873
- agents,
2874
- cwd: repoRoot,
2875
- provider,
2876
- model,
2877
- // Capture sessions can touch many pages; give it more headroom than
2878
- // bootstrap. The SDK treats `maxTurns` as a hard stop — better to
2879
- // overshoot than to cut off mid-review.
2880
- maxTurns: 150,
2881
- onMessage
2882
- });
2883
- } finally {
2884
- await closeStream2(logStream);
2885
- }
2886
- const snapshotAfter = await snapshotPages(pagesDir);
2887
- const delta = diffSnapshots(snapshotBefore, snapshotAfter);
2888
- const finishedAt = options.now?.() ?? /* @__PURE__ */ new Date();
2889
- const captureSummary = {
2890
- ...delta,
2891
- cost: result.cost,
2892
- turns: result.turns
2893
- };
2894
- if (!result.success) {
2895
- await writeCaptureRunRecord(
2896
- statePath,
2897
- finishCaptureRecord({
2898
- record: stateRecord,
2899
- status: "failed",
2900
- finishedAt,
2901
- summary: captureSummary,
2902
- error: result.error ?? "unknown error"
2903
- })
2904
- ).catch(() => {
2905
- });
2906
- return renderOutcome(
2907
- {
2908
- type: "error",
2909
- message: `capture failed: ${result.error ?? "unknown error"}
2910
- (transcript: ${relative3(repoRoot, logPath)})`,
2911
- data: captureOutcomeData(result, delta, logPath, repoRoot)
2912
- },
2913
- {
2914
- json: options.json,
2915
- stdout: ""
2916
- }
2917
- );
2918
- }
2919
- await writeCaptureRunRecord(
2920
- statePath,
2921
- finishCaptureRecord({
2922
- record: stateRecord,
2923
- status: "done",
2924
- finishedAt,
2925
- summary: captureSummary
2926
- })
2927
- ).catch(() => {
2928
- });
2929
- const summary = formatSummary2(result, delta, logPath, repoRoot);
2930
- return renderOutcome(
2931
- {
2932
- type: isNoopDelta(delta) ? "noop" : "success",
2933
- message: summary,
2934
- data: captureOutcomeData(result, delta, logPath, repoRoot)
2935
- },
2936
- { json: options.json }
2937
- );
2938
- }
2939
- function transcriptFix(error) {
2940
- if (error.includes("transcript not found")) {
2941
- return "pass a valid <transcript-path>";
2942
- }
2943
- return "pass --session <id> or <transcript-path>";
2944
- }
2945
- function authFixFor2(provider) {
2946
- switch (provider) {
2947
- case "claude":
2948
- return "run: claude auth login --claudeai (or export ANTHROPIC_API_KEY)";
2949
- case "codex":
2950
- return "run: codex login";
2951
- case "cursor":
2952
- return "run: cursor-agent login";
2953
- default:
2954
- return "run: almanac agents doctor";
2955
- }
2956
- }
2957
- function captureOutcomeData(result, delta, logPath, repoRoot) {
2958
- return {
2959
- transcript: relative3(repoRoot, logPath),
2960
- updated: delta.updated,
2961
- created: delta.created,
2962
- archived: delta.archived,
2963
- cost: result.cost,
2964
- turns: result.turns,
2965
- sessionId: result.sessionId
2966
- };
2967
2553
  }
2968
- function isNoopDelta(delta) {
2969
- return delta.created === 0 && delta.updated === 0 && delta.archived === 0;
2970
- }
2971
- async function resolveTranscript(args) {
2972
- if (args.explicit !== void 0 && args.explicit.length > 0) {
2973
- if (!existsSync7(args.explicit)) {
2554
+
2555
+ // src/commands/session-transcripts.ts
2556
+ import { existsSync as existsSync6 } from "fs";
2557
+ import { readFile as readFile7, readdir as readdir2, stat } from "fs/promises";
2558
+ import { homedir } from "os";
2559
+ import { basename as basename3, join as join7, resolve } from "path";
2560
+ async function resolveCaptureTranscripts(options) {
2561
+ const explicit = options.files ?? [];
2562
+ if (explicit.length > 0) {
2563
+ const paths = explicit.map((path2) => resolve(options.cwd, path2));
2564
+ const missing = paths.find((path2) => !existsSync6(path2));
2565
+ if (missing !== void 0) {
2974
2566
  return {
2975
2567
  ok: false,
2976
- error: `transcript not found: ${args.explicit}`
2568
+ error: `transcript not found: ${missing}`,
2569
+ fix: "pass an existing transcript file"
2977
2570
  };
2978
2571
  }
2979
- return { ok: true, path: args.explicit };
2572
+ return { ok: true, paths, app: "file" };
2573
+ }
2574
+ const app = options.app ?? "claude";
2575
+ if (options.allApps === true) {
2576
+ return {
2577
+ ok: false,
2578
+ error: "capture --all-apps discovery is not implemented yet",
2579
+ fix: "run capture per app, pass transcript files explicitly, or use almanac ingest <file-or-folder>"
2580
+ };
2581
+ }
2582
+ if (app !== "claude") {
2583
+ return {
2584
+ ok: false,
2585
+ error: `capture discovery for ${app} sessions is not implemented yet`,
2586
+ fix: "pass one or more transcript files, or use almanac ingest <file-or-folder>"
2587
+ };
2980
2588
  }
2981
- const projectsDir = args.claudeProjectsDir ?? join8(homedir(), ".claude", "projects");
2982
- if (!existsSync7(projectsDir)) {
2589
+ const projectsDir = options.claudeProjectsDir ?? join7(homedir(), ".claude", "projects");
2590
+ if (!existsSync6(projectsDir)) {
2983
2591
  return {
2984
2592
  ok: false,
2985
- error: `could not auto-resolve transcript; ${projectsDir} does not exist. Pass --session <id> or <transcript-path>.`
2593
+ error: `could not auto-resolve Claude transcript; ${projectsDir} does not exist`,
2594
+ fix: "pass --session <id> with a Claude transcript available, or pass a transcript file"
2986
2595
  };
2987
2596
  }
2988
- const allTranscripts = await collectTranscripts(projectsDir);
2989
- if (args.sessionId !== void 0 && args.sessionId.length > 0) {
2990
- const expected = `${args.sessionId}.jsonl`;
2991
- const match = allTranscripts.find((t) => basename3(t.path) === expected);
2597
+ const transcripts = await collectTranscripts(projectsDir);
2598
+ if (options.session !== void 0 && options.session.length > 0) {
2599
+ if (hasBulkScope(options)) {
2600
+ return {
2601
+ ok: false,
2602
+ error: "capture --session cannot be combined with --since, --limit, --all, or --all-apps",
2603
+ fix: "use --session for one transcript, or remove --session to capture a filtered set"
2604
+ };
2605
+ }
2606
+ const expected = `${options.session}.jsonl`;
2607
+ const match = transcripts.find((entry) => basename3(entry.path) === expected);
2992
2608
  if (match === void 0) {
2993
2609
  return {
2994
2610
  ok: false,
2995
- error: `no transcript found for session ${args.sessionId} under ${projectsDir}`
2611
+ error: `no Claude transcript found for session ${options.session}`,
2612
+ fix: "pass an existing transcript file"
2996
2613
  };
2997
2614
  }
2998
- return { ok: true, path: match.path };
2615
+ return { ok: true, paths: [match.path], app: "claude" };
2616
+ }
2617
+ let matches = await filterTranscriptsByCwd(transcripts, options.repoRoot);
2618
+ const cutoff = parseSinceCutoff(options.since, options.now?.() ?? /* @__PURE__ */ new Date());
2619
+ if (!cutoff.ok) return cutoff;
2620
+ const cutoffMtime = cutoff.mtime;
2621
+ if (cutoffMtime !== void 0) {
2622
+ matches = matches.filter((entry) => entry.mtime >= cutoffMtime);
2999
2623
  }
3000
- const matches = await filterTranscriptsByCwd(allTranscripts, args.repoRoot);
3001
2624
  if (matches.length === 0) {
3002
2625
  return {
3003
2626
  ok: false,
3004
- error: `could not auto-resolve transcript under ${projectsDir}; no session matches cwd ${args.repoRoot}. Pass --session <id> or <transcript-path>.`
2627
+ error: `could not auto-resolve Claude transcript for cwd ${options.repoRoot}`,
2628
+ fix: "pass --session <id> or a transcript file"
3005
2629
  };
3006
2630
  }
3007
2631
  matches.sort((a, b) => b.mtime - a.mtime);
3008
- return { ok: true, path: matches[0].path };
2632
+ const limit = normalizeLimit(options.limit);
2633
+ if (!limit.ok) return limit;
2634
+ const count = options.all === true ? limit.value ?? matches.length : limit.value ?? 1;
2635
+ return {
2636
+ ok: true,
2637
+ paths: matches.slice(0, count).map((entry) => entry.path),
2638
+ app: "claude"
2639
+ };
2640
+ }
2641
+ function hasBulkScope(options) {
2642
+ return options.since !== void 0 || options.limit !== void 0 || options.all === true || options.allApps === true;
2643
+ }
2644
+ function normalizeLimit(limit) {
2645
+ if (limit === void 0) return { ok: true };
2646
+ if (Number.isInteger(limit) && limit > 0) {
2647
+ return { ok: true, value: limit };
2648
+ }
2649
+ return {
2650
+ ok: false,
2651
+ error: "capture --limit must be a positive integer",
2652
+ fix: "pass --limit 1 or higher"
2653
+ };
2654
+ }
2655
+ function parseSinceCutoff(since, now) {
2656
+ if (since === void 0 || since.trim().length === 0) return { ok: true };
2657
+ const trimmed = since.trim();
2658
+ const parsedDate = Date.parse(trimmed);
2659
+ if (!Number.isNaN(parsedDate)) return { ok: true, mtime: parsedDate };
2660
+ try {
2661
+ return {
2662
+ ok: true,
2663
+ mtime: now.getTime() - parseDuration(trimmed) * 1e3
2664
+ };
2665
+ } catch {
2666
+ return {
2667
+ ok: false,
2668
+ error: `invalid --since "${since}"`,
2669
+ fix: "pass a date or a duration like 2w, 30d, 12h, or 45m"
2670
+ };
2671
+ }
3009
2672
  }
3010
2673
  async function collectTranscripts(projectsDir) {
3011
2674
  const out = [];
3012
2675
  let topLevel;
3013
2676
  try {
3014
- topLevel = await readdir3(projectsDir);
2677
+ topLevel = await readdir2(projectsDir);
3015
2678
  } catch {
3016
2679
  return out;
3017
2680
  }
3018
2681
  for (const name of topLevel) {
3019
- const projectDir = join8(projectsDir, name);
2682
+ const projectDir = join7(projectsDir, name);
3020
2683
  let entries;
3021
2684
  try {
3022
- entries = await readdir3(projectDir);
2685
+ entries = await readdir2(projectDir);
3023
2686
  } catch {
3024
2687
  continue;
3025
2688
  }
3026
2689
  for (const entry of entries) {
3027
2690
  if (!entry.endsWith(".jsonl")) continue;
3028
- const full = join8(projectDir, entry);
2691
+ const full = join7(projectDir, entry);
3029
2692
  try {
3030
2693
  const st = await stat(full);
3031
- if (st.isFile()) {
3032
- out.push({ path: full, mtime: st.mtimeMs });
3033
- }
2694
+ if (st.isFile()) out.push({ path: full, mtime: st.mtimeMs });
3034
2695
  } catch {
2696
+ continue;
3035
2697
  }
3036
2698
  }
3037
2699
  }
@@ -3039,17 +2701,17 @@ async function collectTranscripts(projectsDir) {
3039
2701
  }
3040
2702
  async function filterTranscriptsByCwd(transcripts, repoRoot) {
3041
2703
  const dirHash = `-${repoRoot.replace(/^\/+/, "").replace(/\//g, "-")}`;
3042
- const byDirName = transcripts.filter((t) => {
3043
- const parent = basename3(join8(t.path, ".."));
2704
+ const byDirName = transcripts.filter((entry) => {
2705
+ const parent = basename3(join7(entry.path, ".."));
3044
2706
  return parent === dirHash || parent.endsWith(dirHash);
3045
2707
  });
3046
2708
  if (byDirName.length > 0) return byDirName;
3047
2709
  const needle = `"cwd":"${repoRoot}"`;
3048
2710
  const hits = [];
3049
- for (const t of transcripts) {
2711
+ for (const transcript of transcripts) {
3050
2712
  try {
3051
- const head = await readHead(t.path, 4096);
3052
- if (head.includes(needle)) hits.push(t);
2713
+ const head = await readHead(transcript.path, 4096);
2714
+ if (head.includes(needle)) hits.push(transcript);
3053
2715
  } catch {
3054
2716
  continue;
3055
2717
  }
@@ -3060,73 +2722,293 @@ async function readHead(path2, bytes) {
3060
2722
  const content = await readFile7(path2, "utf8");
3061
2723
  return content.length > bytes ? content.slice(0, bytes) : content;
3062
2724
  }
3063
- async function snapshotPages(pagesDir) {
3064
- const out = /* @__PURE__ */ new Map();
3065
- if (!existsSync7(pagesDir)) return out;
3066
- let entries;
2725
+
2726
+ // src/commands/operations.ts
2727
+ async function runInitCommand(options) {
2728
+ const provider = await resolveProviderOrOutcome(options);
2729
+ if ("error" in provider) return provider.error;
2730
+ const background = options.background === true;
2731
+ if (options.json === true && !background) return jsonForegroundError(options.json);
3067
2732
  try {
3068
- entries = await readdir3(pagesDir);
3069
- } catch {
3070
- return out;
2733
+ const result = await runBuildOperation({
2734
+ cwd: options.cwd,
2735
+ provider: provider.value,
2736
+ background,
2737
+ context: initContext(options),
2738
+ force: options.force,
2739
+ onEvent: options.onEvent,
2740
+ startForeground: options.startForeground,
2741
+ startBackground: options.startBackground
2742
+ });
2743
+ return renderOperationResult("init", result, options.json);
2744
+ } catch (err) {
2745
+ return renderOperationError(err, options.json);
3071
2746
  }
3072
- for (const entry of entries) {
3073
- if (!entry.endsWith(".md")) continue;
3074
- const slug = entry.slice(0, -3);
3075
- const full = join8(pagesDir, entry);
3076
- try {
3077
- const st = statSync(full);
3078
- if (!st.isFile()) continue;
3079
- const content = await readFile7(full, "utf8");
3080
- const hash = createHash("sha256").update(content).digest("hex");
3081
- const fm = parseFrontmatter(content);
3082
- out.set(slug, {
3083
- slug,
3084
- hash,
3085
- archived: fm.archived_at !== null
3086
- });
3087
- } catch {
3088
- continue;
2747
+ }
2748
+ async function runCaptureCommand(options) {
2749
+ const provider = await resolveProviderOrOutcome(options);
2750
+ if ("error" in provider) return provider.error;
2751
+ if (options.json === true && options.foreground === true) {
2752
+ return jsonForegroundError(options.json);
2753
+ }
2754
+ try {
2755
+ const repoRoot = await resolveCaptureRepoRoot(options.cwd, options.json);
2756
+ if (typeof repoRoot !== "string") return repoRoot;
2757
+ const resolved = await resolveCaptureTranscripts({
2758
+ repoRoot,
2759
+ cwd: options.cwd,
2760
+ files: options.sessionFiles,
2761
+ app: options.app,
2762
+ session: options.session,
2763
+ since: options.since,
2764
+ limit: options.limit,
2765
+ all: options.all,
2766
+ allApps: options.allApps,
2767
+ claudeProjectsDir: options.claudeProjectsDir
2768
+ });
2769
+ if (!resolved.ok) {
2770
+ return renderOutcome(
2771
+ {
2772
+ type: "needs-action",
2773
+ message: resolved.error,
2774
+ fix: resolved.fix,
2775
+ data: {
2776
+ app: options.app,
2777
+ session: options.session,
2778
+ since: options.since,
2779
+ limit: options.limit,
2780
+ all: options.all,
2781
+ allApps: options.allApps
2782
+ }
2783
+ },
2784
+ { json: options.json }
2785
+ );
3089
2786
  }
2787
+ const paths = resolved.paths;
2788
+ const result = await runAbsorbOperation({
2789
+ cwd: options.cwd,
2790
+ provider: provider.value,
2791
+ background: options.foreground !== true,
2792
+ context: captureContext({ ...options, sessionFiles: paths }),
2793
+ targetKind: "session",
2794
+ targetPaths: paths,
2795
+ onEvent: options.onEvent,
2796
+ startForeground: options.startForeground,
2797
+ startBackground: options.startBackground
2798
+ });
2799
+ return renderOperationResult("capture", result, options.json);
2800
+ } catch (err) {
2801
+ return renderOperationError(err, options.json);
3090
2802
  }
3091
- return out;
3092
2803
  }
3093
- function diffSnapshots(before, after) {
3094
- let created = 0;
3095
- let updated = 0;
3096
- let archived = 0;
3097
- for (const [slug, entry] of after) {
3098
- const prev = before.get(slug);
3099
- if (prev === void 0) {
3100
- created += 1;
3101
- continue;
2804
+ async function runIngestCommand(options) {
2805
+ const provider = await resolveProviderOrOutcome(options);
2806
+ if ("error" in provider) return provider.error;
2807
+ if (options.paths.length === 0) {
2808
+ return renderOutcome(
2809
+ { type: "error", message: "ingest requires at least one file or folder" },
2810
+ { json: options.json }
2811
+ );
2812
+ }
2813
+ if (options.json === true && options.foreground === true) {
2814
+ return jsonForegroundError(options.json);
2815
+ }
2816
+ try {
2817
+ const paths = options.paths.map((path2) => resolve2(options.cwd, path2));
2818
+ const result = await runAbsorbOperation({
2819
+ cwd: options.cwd,
2820
+ provider: provider.value,
2821
+ background: options.foreground !== true,
2822
+ context: ingestContext(paths),
2823
+ targetKind: "path",
2824
+ targetPaths: paths,
2825
+ onEvent: options.onEvent,
2826
+ startForeground: options.startForeground,
2827
+ startBackground: options.startBackground
2828
+ });
2829
+ return renderOperationResult("ingest", result, options.json);
2830
+ } catch (err) {
2831
+ return renderOperationError(err, options.json);
2832
+ }
2833
+ }
2834
+ async function runGardenCommand(options) {
2835
+ const provider = await resolveProviderOrOutcome(options);
2836
+ if ("error" in provider) return provider.error;
2837
+ if (options.json === true && options.foreground === true) {
2838
+ return jsonForegroundError(options.json);
2839
+ }
2840
+ try {
2841
+ const result = await runGardenOperation({
2842
+ cwd: options.cwd,
2843
+ provider: provider.value,
2844
+ background: options.foreground !== true,
2845
+ onEvent: options.onEvent,
2846
+ startForeground: options.startForeground,
2847
+ startBackground: options.startBackground
2848
+ });
2849
+ return renderOperationResult("garden", result, options.json);
2850
+ } catch (err) {
2851
+ return renderOperationError(err, options.json);
2852
+ }
2853
+ }
2854
+ function parseUsing(value) {
2855
+ if (value === void 0 || value.trim().length === 0) {
2856
+ return { id: "claude" };
2857
+ }
2858
+ const [rawProvider, ...modelParts] = value.split("/");
2859
+ if (!isProviderId(rawProvider)) {
2860
+ throw new Error(
2861
+ `invalid --using "${value}" (expected claude, codex, or cursor)`
2862
+ );
2863
+ }
2864
+ const model = modelParts.join("/");
2865
+ return {
2866
+ id: rawProvider,
2867
+ model: model.length > 0 ? model : void 0
2868
+ };
2869
+ }
2870
+ async function resolveProviderOrOutcome(options) {
2871
+ try {
2872
+ if (options.using !== void 0) {
2873
+ return { value: parseUsing(options.using) };
3102
2874
  }
3103
- if (prev.hash !== entry.hash) {
3104
- if (!prev.archived && entry.archived) {
3105
- archived += 1;
3106
- } else {
3107
- updated += 1;
2875
+ const config = await readConfig({ cwd: options.cwd });
2876
+ const id = config.agent.default;
2877
+ const model = config.agent.models[id] ?? void 0;
2878
+ return { value: { id, model: model ?? void 0 } };
2879
+ } catch (err) {
2880
+ return {
2881
+ error: renderOutcome(
2882
+ { type: "error", message: err instanceof Error ? err.message : String(err) },
2883
+ { json: options.json }
2884
+ )
2885
+ };
2886
+ }
2887
+ }
2888
+ function isProviderId(value) {
2889
+ return value === "claude" || value === "codex" || value === "cursor";
2890
+ }
2891
+ function renderOperationResult(operation, result, json) {
2892
+ const record = result.background?.record ?? result.foreground?.record;
2893
+ const status = record?.status;
2894
+ const foregroundResult = result.foreground?.result;
2895
+ if (result.mode === "foreground" && (foregroundResult?.success === false || status === "failed")) {
2896
+ const failure = foregroundResult?.failure ?? record?.failure;
2897
+ return renderOutcome(
2898
+ {
2899
+ type: "error",
2900
+ message: renderOperationFailureMessage({
2901
+ operation,
2902
+ runId: result.runId,
2903
+ error: foregroundResult?.error,
2904
+ failure
2905
+ }),
2906
+ data: {
2907
+ operation,
2908
+ runId: result.runId,
2909
+ mode: result.mode,
2910
+ status,
2911
+ pid: record?.pid,
2912
+ logPath: record?.logPath,
2913
+ error: foregroundResult?.error,
2914
+ failure
2915
+ }
2916
+ },
2917
+ { json }
2918
+ );
2919
+ }
2920
+ return renderOutcome(
2921
+ {
2922
+ type: "success",
2923
+ message: result.mode === "background" ? `${operation} started: ${result.runId}` : `${operation} finished: ${result.runId}`,
2924
+ data: {
2925
+ operation,
2926
+ runId: result.runId,
2927
+ mode: result.mode,
2928
+ status,
2929
+ pid: record?.pid,
2930
+ logPath: record?.logPath
3108
2931
  }
3109
- }
2932
+ },
2933
+ { json }
2934
+ );
2935
+ }
2936
+ function renderOperationFailureMessage(args) {
2937
+ const lines = [`${args.operation} failed: ${args.runId}`];
2938
+ if (args.failure !== void 0) {
2939
+ lines.push(`Reason: ${args.failure.message}`);
2940
+ if (args.failure.fix !== void 0) lines.push(`Fix: ${args.failure.fix}`);
2941
+ return lines.join("\n");
3110
2942
  }
3111
- return { created, updated, archived };
2943
+ if (args.error !== void 0) lines[0] += `: ${args.error}`;
2944
+ return lines.join("\n");
3112
2945
  }
3113
- function formatSummary2(result, delta, logPath, repoRoot) {
3114
- const rel = relative3(repoRoot, logPath);
3115
- const usage = formatRunUsage(result);
3116
- const { created, updated, archived } = delta;
3117
- if (created === 0 && updated === 0 && archived === 0) {
3118
- return `[capture] no new knowledge met the notability bar (0 pages written), ${usage} (transcript: ${rel})`;
2946
+ function renderOperationError(err, json) {
2947
+ const message = err instanceof Error ? err.message : String(err);
2948
+ if (message.includes("no .almanac/")) {
2949
+ return renderOutcome(
2950
+ {
2951
+ type: "needs-action",
2952
+ message,
2953
+ fix: "run: almanac init"
2954
+ },
2955
+ { json }
2956
+ );
3119
2957
  }
3120
- return `[done] ${updated} page${updated === 1 ? "" : "s"} updated, ${created} created, ${archived} archived, ${usage} (transcript: ${rel})`;
2958
+ return renderOutcome({ type: "error", message }, { json });
3121
2959
  }
3122
- function formatTimestamp2(d) {
3123
- const pad = (n) => n.toString().padStart(2, "0");
3124
- return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
2960
+ async function resolveCaptureRepoRoot(cwd, json) {
2961
+ const { findNearestAlmanacDir: findNearestAlmanacDir2 } = await import("./paths-O5CZADP2.js");
2962
+ const repoRoot = findNearestAlmanacDir2(cwd);
2963
+ if (repoRoot !== null) return repoRoot;
2964
+ return renderOutcome(
2965
+ {
2966
+ type: "needs-action",
2967
+ message: "no .almanac/ found in this directory or any parent",
2968
+ fix: "run: almanac init"
2969
+ },
2970
+ { json }
2971
+ );
3125
2972
  }
3126
- function closeStream2(stream) {
3127
- return new Promise((resolve) => {
3128
- stream.end(() => resolve());
3129
- });
2973
+ function jsonForegroundError(json) {
2974
+ return renderOutcome({
2975
+ type: "error",
2976
+ message: "--json is only supported for background job start responses"
2977
+ }, { json });
2978
+ }
2979
+ function initContext(options) {
2980
+ return [
2981
+ "Command context:",
2982
+ `- Command: init`,
2983
+ `- Force requested: ${options.force === true ? "yes" : "no"}`,
2984
+ `- Non-interactive confirmation: ${options.yes === true ? "yes" : "no"}`
2985
+ ].join("\n");
2986
+ }
2987
+ function captureContext(options) {
2988
+ const lines = ["Command context:", "- Command: capture"];
2989
+ if (options.app !== void 0) lines.push(`- App: ${options.app}`);
2990
+ if (options.session !== void 0) lines.push(`- Session id: ${options.session}`);
2991
+ if (options.since !== void 0) lines.push(`- Since: ${options.since}`);
2992
+ if (options.limit !== void 0) lines.push(`- Limit: ${options.limit}`);
2993
+ if (options.all === true) lines.push("- Capture all matching sessions");
2994
+ if (options.allApps === true) lines.push("- Capture all supported apps");
2995
+ const paths = options.sessionFiles ?? [];
2996
+ if (paths.length > 0) {
2997
+ lines.push("- Session/transcript files:");
2998
+ for (const path2 of paths) lines.push(` - ${path2}`);
2999
+ }
3000
+ if (paths.length === 0 && options.session === void 0) {
3001
+ lines.push("- No explicit session file or session id was provided.");
3002
+ }
3003
+ return lines.join("\n");
3004
+ }
3005
+ function ingestContext(paths) {
3006
+ return [
3007
+ "Command context:",
3008
+ "- Command: ingest",
3009
+ "- Paths:",
3010
+ ...paths.map((path2) => ` - ${path2}`)
3011
+ ].join("\n");
3130
3012
  }
3131
3013
 
3132
3014
  // src/commands/reindex.ts
@@ -3144,53 +3026,128 @@ async function runReindex(options) {
3144
3026
 
3145
3027
  // src/cli/register-wiki-lifecycle-commands.ts
3146
3028
  function registerWikiLifecycleCommands(program) {
3147
- program.command("bootstrap").description(
3148
- "scaffold a wiki in this repo via an AI agent (requires ANTHROPIC_API_KEY or Claude subscription)"
3149
- ).option("--quiet", "suppress per-tool streaming; print only the final line").option("--agent <agent>", "agent provider: claude, codex, or cursor").option("--model <model>", "override the agent model").option("--force", "overwrite an existing populated wiki (default: refuse)").option("--json", "emit structured CommandOutcome JSON").action(
3029
+ program.command("init").description("initialize and build this repo's CodeAlmanac wiki").option("--using <provider[/model]>", "provider and optional model").option("--background", "start as a background job").option("--json", "emit structured JSON for background job start").option("--force", "allow rebuilding an existing wiki").option("-y, --yes", "confirm non-interactively").action(
3150
3030
  async (opts) => {
3151
- const result = await runBootstrap({
3031
+ const result = await runInitCommand({
3152
3032
  cwd: process.cwd(),
3153
- quiet: opts.quiet,
3154
- agent: opts.agent,
3155
- model: opts.model,
3033
+ using: opts.using,
3034
+ background: opts.background,
3035
+ json: opts.json,
3156
3036
  force: opts.force,
3157
- json: opts.json
3037
+ yes: opts.yes,
3038
+ onEvent: opts.background === true ? void 0 : writeForegroundEvent
3158
3039
  });
3159
3040
  emit(result);
3160
3041
  }
3161
3042
  );
3162
- 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").option("--json", "emit structured CommandOutcome JSON").action(
3163
- async (transcript, opts) => {
3043
+ const capture = program.command("capture [sessionFiles...]").alias("c").description("absorb coding-session knowledge into the wiki").option("--app <app>", "source app: claude, codex, cursor, or generic").option("--session <id>", "target a specific session by ID").option("--since <duration-or-date>", "capture sessions since a time").option("--limit <n>", "maximum sessions to capture", parsePositiveInt).option("--all", "capture all matching sessions").option("--all-apps", "capture from all supported apps").option("--using <provider[/model]>", "provider and optional model").option("--foreground", "run now instead of starting a background job").option("--json", "emit structured JSON for background job start").option("-y, --yes", "confirm non-interactively").action(
3044
+ async (sessionFiles, opts) => {
3164
3045
  await autoRegisterIfNeeded(process.cwd());
3165
- const result = await runCapture({
3046
+ const result = await runCaptureCommand({
3166
3047
  cwd: process.cwd(),
3167
- transcriptPath: transcript,
3168
- sessionId: opts.session,
3169
- quiet: opts.quiet,
3170
- agent: opts.agent,
3171
- model: opts.model,
3172
- json: opts.json
3048
+ sessionFiles,
3049
+ app: opts.app,
3050
+ session: opts.session,
3051
+ since: opts.since,
3052
+ limit: opts.limit,
3053
+ all: opts.all,
3054
+ allApps: opts.allApps,
3055
+ using: opts.using,
3056
+ foreground: opts.foreground,
3057
+ json: opts.json,
3058
+ yes: opts.yes,
3059
+ onEvent: opts.foreground === true ? writeForegroundEvent : void 0
3173
3060
  });
3174
3061
  emit(result);
3175
3062
  }
3176
3063
  );
3177
- capture.command("status").description("show running and recent capture jobs").option("--json", "emit structured JSON").action(async (opts) => {
3178
- await autoRegisterIfNeeded(process.cwd());
3179
- const result = await runCaptureStatus({
3064
+ program.command("ingest <paths...>").description("absorb knowledge from one or more files or folders").option("--using <provider[/model]>", "provider and optional model").option("--foreground", "run now instead of starting a background job").option("--json", "emit structured JSON for background job start").option("-y, --yes", "confirm non-interactively").action(
3065
+ async (paths, opts) => {
3066
+ await autoRegisterIfNeeded(process.cwd());
3067
+ const result = await runIngestCommand({
3068
+ cwd: process.cwd(),
3069
+ paths,
3070
+ using: opts.using,
3071
+ foreground: opts.foreground,
3072
+ json: opts.json,
3073
+ yes: opts.yes,
3074
+ onEvent: opts.foreground === true ? writeForegroundEvent : void 0
3075
+ });
3076
+ emit(result);
3077
+ }
3078
+ );
3079
+ program.command("garden").description("clean up, reconcile, and improve the wiki").option("--using <provider[/model]>", "provider and optional model").option("--foreground", "run now instead of starting a background job").option("--json", "emit structured JSON for background job start").option("-y, --yes", "confirm non-interactively").action(
3080
+ async (opts) => {
3081
+ await autoRegisterIfNeeded(process.cwd());
3082
+ const result = await runGardenCommand({
3083
+ cwd: process.cwd(),
3084
+ using: opts.using,
3085
+ foreground: opts.foreground,
3086
+ json: opts.json,
3087
+ yes: opts.yes,
3088
+ onEvent: opts.foreground === true ? writeForegroundEvent : void 0
3089
+ });
3090
+ emit(result);
3091
+ }
3092
+ );
3093
+ const jobs = program.command("jobs").description("show and manage CodeAlmanac background jobs");
3094
+ jobs.command("list", { isDefault: true }).description("list runs for this wiki").option("--json", "emit structured JSON").action(async (opts) => {
3095
+ const result = await runJobsList({
3180
3096
  cwd: process.cwd(),
3181
3097
  json: opts.json
3182
3098
  });
3183
3099
  emit(result);
3184
3100
  });
3185
- program.command("ps").description("deprecated alias for capture status").option("--json", "emit structured JSON").action(async (opts) => {
3186
- await autoRegisterIfNeeded(process.cwd());
3187
- const result = await runCaptureStatus({
3101
+ jobs.command("show <run-id>").description("show one run record").option("--json", "emit structured JSON").action(async (runId, opts) => {
3102
+ const result = await runJobsShow({
3103
+ cwd: process.cwd(),
3104
+ runId,
3105
+ json: opts.json
3106
+ });
3107
+ emit(result);
3108
+ });
3109
+ jobs.command("logs <run-id>").description("print a run's JSONL event log").option("--json", "emit structured errors as JSON").action(async (runId, opts) => {
3110
+ const result = await runJobsLogs({
3111
+ cwd: process.cwd(),
3112
+ runId,
3113
+ json: opts.json
3114
+ });
3115
+ emit(result);
3116
+ });
3117
+ jobs.command("attach <run-id>").description("stream a run log until the job exits").option("--json", "emit structured errors as JSON").action(async (runId, opts) => {
3118
+ const result = await streamJobsAttach({
3119
+ cwd: process.cwd(),
3120
+ runId,
3121
+ json: opts.json
3122
+ });
3123
+ emit(result);
3124
+ });
3125
+ jobs.command("cancel <run-id>").description("cancel a running or queued job").option("--json", "emit structured JSON").action(async (runId, opts) => {
3126
+ const result = await runJobsCancel({
3127
+ cwd: process.cwd(),
3128
+ runId,
3129
+ json: opts.json
3130
+ });
3131
+ emit(result);
3132
+ });
3133
+ capture.command("status").description("deprecated alias for jobs").option("--json", "emit structured JSON").action(async (opts) => {
3134
+ const result = await runJobsList({
3135
+ cwd: process.cwd(),
3136
+ json: opts.json
3137
+ });
3138
+ emit(withWarning(
3139
+ result,
3140
+ deprecationWarning("almanac capture status", "almanac jobs")
3141
+ ));
3142
+ });
3143
+ program.command("ps").description("deprecated alias for jobs").option("--json", "emit structured JSON").action(async (opts) => {
3144
+ const result = await runJobsList({
3188
3145
  cwd: process.cwd(),
3189
3146
  json: opts.json
3190
3147
  });
3191
3148
  emit(withWarning(
3192
3149
  result,
3193
- deprecationWarning("almanac ps", "almanac capture status")
3150
+ deprecationWarning("almanac ps", "almanac jobs")
3194
3151
  ));
3195
3152
  });
3196
3153
  const hook = program.command("hook").description("manage the SessionEnd auto-capture hook");
@@ -3218,6 +3175,27 @@ function registerWikiLifecycleCommands(program) {
3218
3175
  if (result.exitCode !== 0) process.exitCode = result.exitCode;
3219
3176
  });
3220
3177
  }
3178
+ function writeForegroundEvent(event) {
3179
+ const line = formatForegroundEvent(event);
3180
+ if (line !== null) process.stdout.write(`${line}
3181
+ `);
3182
+ }
3183
+ function formatForegroundEvent(event) {
3184
+ switch (event.type) {
3185
+ case "text":
3186
+ return event.content.trim().length > 0 ? event.content.trim() : null;
3187
+ case "tool_use":
3188
+ return `[tool] ${event.tool}`;
3189
+ case "tool_summary":
3190
+ return `[tool] ${event.summary}`;
3191
+ case "error":
3192
+ return null;
3193
+ case "done":
3194
+ return event.error !== void 0 ? null : "[done]";
3195
+ default:
3196
+ return null;
3197
+ }
3198
+ }
3221
3199
  function normalizeHookSource(source) {
3222
3200
  if (source === "claude" || source === "codex" || source === "cursor" || source === "all") {
3223
3201
  return source;
@@ -3235,4 +3213,4 @@ function registerCommands(program) {
3235
3213
  export {
3236
3214
  registerCommands
3237
3215
  };
3238
- //# sourceMappingURL=register-commands-FBJ6XQ3L.js.map
3216
+ //# sourceMappingURL=register-commands-LULZUSPO.js.map