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.
- package/README.md +25 -20
- package/dist/{agents-RVTQYE6A.js → agents-V2ZOIACP.js} +6 -5
- package/dist/{chunk-P5WGG4FJ.js → chunk-5BWUMAOX.js} +2 -2
- package/dist/chunk-5BWUMAOX.js.map +1 -0
- package/dist/{chunk-KQUVMF27.js → chunk-BFIG2CXM.js} +2 -516
- package/dist/chunk-BFIG2CXM.js.map +1 -0
- package/dist/{chunk-DL5BXZCX.js → chunk-BQY5L3DL.js} +3 -53
- package/dist/chunk-BQY5L3DL.js.map +1 -0
- package/dist/{chunk-F53U6JQG.js → chunk-CQJVM34R.js} +2 -2
- package/dist/chunk-FUBE6KCO.js +124 -0
- package/dist/chunk-FUBE6KCO.js.map +1 -0
- package/dist/chunk-IZBXXAVL.js +524 -0
- package/dist/chunk-IZBXXAVL.js.map +1 -0
- package/dist/{chunk-7JUX4ADQ.js → chunk-IZT6RBHS.js} +1 -1
- package/dist/{chunk-SMIK2YLU.js → chunk-JLQZELHQ.js} +82 -88
- package/dist/chunk-JLQZELHQ.js.map +1 -0
- package/dist/{chunk-TT6ZP4GS.js → chunk-KZXWPG4P.js} +2 -2
- package/dist/{chunk-6BJUYZ43.js → chunk-QIA22IAM.js} +8 -16
- package/dist/chunk-QIA22IAM.js.map +1 -0
- package/dist/{chunk-BGUID5BS.js → chunk-RALBM6HZ.js} +20 -139
- package/dist/chunk-RALBM6HZ.js.map +1 -0
- package/dist/{chunk-TILAKDN6.js → chunk-U5DLLWIC.js} +3 -3
- package/dist/chunk-WL4UE7Q6.js +1386 -0
- package/dist/chunk-WL4UE7Q6.js.map +1 -0
- package/dist/{chunk-GFUB57IT.js → chunk-ZUQN5Y3K.js} +48 -124
- package/dist/chunk-ZUQN5Y3K.js.map +1 -0
- package/dist/{chunk-MRRX4UQB.js → chunk-ZZLLOAI6.js} +3 -3
- package/dist/{cli-CL4ID7EO.js → cli-XWPNARA6.js} +35 -18
- package/dist/cli-XWPNARA6.js.map +1 -0
- package/dist/codealmanac.js +1 -1
- package/dist/{config-ML2RCR7J.js → config-KH3JUMG6.js} +4 -4
- package/dist/doctor-ENJT665Z.js +18 -0
- package/dist/paths-O5CZADP2.js +14 -0
- package/dist/process-KFSLENL3.js +61 -0
- package/dist/{register-commands-FBJ6XQ3L.js → register-commands-LULZUSPO.js} +993 -1015
- package/dist/register-commands-LULZUSPO.js.map +1 -0
- package/dist/uninstall-BD4MMQ7M.js +16 -0
- package/dist/uninstall-BD4MMQ7M.js.map +1 -0
- package/dist/update-XSKPDFMJ.js +11 -0
- package/dist/update-XSKPDFMJ.js.map +1 -0
- package/dist/{wiki-IGNRNLUZ.js → wiki-O4RWMAE6.js} +8 -6
- package/dist/wiki-O4RWMAE6.js.map +1 -0
- package/guides/mini.md +11 -9
- package/guides/reference.md +96 -39
- package/hooks/almanac-capture.sh +7 -8
- package/package.json +1 -1
- package/prompts/agents/.gitkeep +1 -0
- package/prompts/base/notability.md +139 -0
- package/prompts/base/purpose.md +85 -0
- package/prompts/base/syntax.md +114 -0
- package/prompts/operations/absorb.md +43 -0
- package/prompts/operations/build.md +49 -0
- package/prompts/operations/garden.md +51 -0
- package/dist/chunk-6BJUYZ43.js.map +0 -1
- package/dist/chunk-BGUID5BS.js.map +0 -1
- package/dist/chunk-DL5BXZCX.js.map +0 -1
- package/dist/chunk-GFUB57IT.js.map +0 -1
- package/dist/chunk-KQUVMF27.js.map +0 -1
- package/dist/chunk-P5WGG4FJ.js.map +0 -1
- package/dist/chunk-SMIK2YLU.js.map +0 -1
- package/dist/cli-CL4ID7EO.js.map +0 -1
- package/dist/doctor-DOLJRGS4.js +0 -17
- package/dist/register-commands-FBJ6XQ3L.js.map +0 -1
- package/dist/uninstall-DX6LFKMX.js +0 -15
- package/dist/update-P2IPG7RO.js +0 -11
- package/dist/wiki-IGNRNLUZ.js.map +0 -1
- package/prompts/bootstrap.md +0 -176
- package/prompts/reviewer.md +0 -129
- package/prompts/writer.md +0 -134
- /package/dist/{agents-RVTQYE6A.js.map → agents-V2ZOIACP.js.map} +0 -0
- /package/dist/{chunk-F53U6JQG.js.map → chunk-CQJVM34R.js.map} +0 -0
- /package/dist/{chunk-7JUX4ADQ.js.map → chunk-IZT6RBHS.js.map} +0 -0
- /package/dist/{chunk-TT6ZP4GS.js.map → chunk-KZXWPG4P.js.map} +0 -0
- /package/dist/{chunk-TILAKDN6.js.map → chunk-U5DLLWIC.js.map} +0 -0
- /package/dist/{chunk-MRRX4UQB.js.map → chunk-ZZLLOAI6.js.map} +0 -0
- /package/dist/{config-ML2RCR7J.js.map → config-KH3JUMG6.js.map} +0 -0
- /package/dist/{doctor-DOLJRGS4.js.map → doctor-ENJT665Z.js.map} +0 -0
- /package/dist/{uninstall-DX6LFKMX.js.map → paths-O5CZADP2.js.map} +0 -0
- /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-
|
|
31
|
+
} from "./chunk-QIA22IAM.js";
|
|
18
32
|
import {
|
|
19
33
|
runConfigGet,
|
|
20
34
|
runConfigList,
|
|
21
35
|
runConfigSet,
|
|
22
36
|
runConfigUnset
|
|
23
|
-
} from "./chunk-
|
|
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-
|
|
61
|
+
} from "./chunk-BFIG2CXM.js";
|
|
47
62
|
import {
|
|
48
63
|
runDoctor
|
|
49
|
-
} from "./chunk-
|
|
50
|
-
import "./chunk-
|
|
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-
|
|
76
|
+
} from "./chunk-BQY5L3DL.js";
|
|
61
77
|
import {
|
|
62
78
|
runSetup
|
|
63
|
-
} from "./chunk-
|
|
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-
|
|
89
|
+
} from "./chunk-5BWUMAOX.js";
|
|
84
90
|
import {
|
|
85
91
|
findNearestAlmanacDir,
|
|
86
92
|
getRepoAlmanacDir
|
|
87
|
-
} from "./chunk-
|
|
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
|
|
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
|
|
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("
|
|
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
|
|
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
|
|
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/
|
|
1899
|
-
import {
|
|
1900
|
-
|
|
1901
|
-
|
|
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
|
|
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
|
-
"
|
|
1910
|
-
"
|
|
1911
|
-
"
|
|
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
|
|
2222
|
+
return readFile5(resolvePromptPath(dir, name), "utf8");
|
|
1947
2223
|
}
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
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
|
|
1962
|
-
const
|
|
1963
|
-
|
|
1964
|
-
|
|
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/
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
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
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
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
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
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/
|
|
2014
|
-
async function
|
|
2015
|
-
|
|
2016
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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/
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
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
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
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
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
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
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
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
|
|
2348
|
-
const
|
|
2349
|
-
|
|
2350
|
-
const
|
|
2351
|
-
|
|
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/
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
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
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
2805
|
-
|
|
2806
|
-
claudeProjectsDir: options.claudeProjectsDir
|
|
2541
|
+
provider: options.provider,
|
|
2542
|
+
context: options.context
|
|
2807
2543
|
});
|
|
2808
|
-
|
|
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
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
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
|
-
|
|
2969
|
-
|
|
2970
|
-
}
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
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: ${
|
|
2568
|
+
error: `transcript not found: ${missing}`,
|
|
2569
|
+
fix: "pass an existing transcript file"
|
|
2977
2570
|
};
|
|
2978
2571
|
}
|
|
2979
|
-
return { ok: true,
|
|
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 =
|
|
2982
|
-
if (!
|
|
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
|
|
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
|
|
2989
|
-
if (
|
|
2990
|
-
|
|
2991
|
-
|
|
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 ${
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
2677
|
+
topLevel = await readdir2(projectsDir);
|
|
3015
2678
|
} catch {
|
|
3016
2679
|
return out;
|
|
3017
2680
|
}
|
|
3018
2681
|
for (const name of topLevel) {
|
|
3019
|
-
const projectDir =
|
|
2682
|
+
const projectDir = join7(projectsDir, name);
|
|
3020
2683
|
let entries;
|
|
3021
2684
|
try {
|
|
3022
|
-
entries = await
|
|
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 =
|
|
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((
|
|
3043
|
-
const parent = basename3(
|
|
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
|
|
2711
|
+
for (const transcript of transcripts) {
|
|
3050
2712
|
try {
|
|
3051
|
-
const head = await readHead(
|
|
3052
|
-
if (head.includes(needle)) hits.push(
|
|
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
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
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
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
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
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
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
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
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
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
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
|
-
|
|
2943
|
+
if (args.error !== void 0) lines[0] += `: ${args.error}`;
|
|
2944
|
+
return lines.join("\n");
|
|
3112
2945
|
}
|
|
3113
|
-
function
|
|
3114
|
-
const
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
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
|
|
2958
|
+
return renderOutcome({ type: "error", message }, { json });
|
|
3121
2959
|
}
|
|
3122
|
-
function
|
|
3123
|
-
const
|
|
3124
|
-
|
|
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
|
|
3127
|
-
return
|
|
3128
|
-
|
|
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("
|
|
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
|
|
3031
|
+
const result = await runInitCommand({
|
|
3152
3032
|
cwd: process.cwd(),
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3033
|
+
using: opts.using,
|
|
3034
|
+
background: opts.background,
|
|
3035
|
+
json: opts.json,
|
|
3156
3036
|
force: opts.force,
|
|
3157
|
-
|
|
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 [
|
|
3163
|
-
async (
|
|
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
|
|
3046
|
+
const result = await runCaptureCommand({
|
|
3166
3047
|
cwd: process.cwd(),
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
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
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
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
|
-
|
|
3186
|
-
await
|
|
3187
|
-
|
|
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
|
|
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-
|
|
3216
|
+
//# sourceMappingURL=register-commands-LULZUSPO.js.map
|