@youtyan/code-viewer 0.6.3 → 0.6.5
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 +14 -12
- package/dist/code-viewer.js +2395 -203
- package/package.json +4 -3
- package/skills/code-viewer-journal/SKILL.md +131 -0
- package/web/app.js +2609 -265
- package/web/index.html +1 -0
- package/web/style.css +1653 -169
- package/web/vendor/README.md +10 -6
- package/web/vendor/THIRD_PARTY_NOTICES.txt +41 -1
- package/web/vendor/highlight.js/highlight.min.js +47301 -1213
package/dist/code-viewer.js
CHANGED
|
@@ -823,7 +823,7 @@ function isCommandNotFoundMessage(command, message) {
|
|
|
823
823
|
}
|
|
824
824
|
var EXTERNAL_COMMAND_NAMES, commandNameSet, activeOverrides;
|
|
825
825
|
var init_command_resolver = __esm(() => {
|
|
826
|
-
EXTERNAL_COMMAND_NAMES = ["git", "rg", "docker"];
|
|
826
|
+
EXTERNAL_COMMAND_NAMES = ["git", "rg", "docker", "gh"];
|
|
827
827
|
commandNameSet = new Set(EXTERNAL_COMMAND_NAMES);
|
|
828
828
|
activeOverrides = new Map;
|
|
829
829
|
});
|
|
@@ -2231,6 +2231,15 @@ function takeValue(argv, index, flag) {
|
|
|
2231
2231
|
function shellSingleQuote(value) {
|
|
2232
2232
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
2233
2233
|
}
|
|
2234
|
+
async function readStdin() {
|
|
2235
|
+
if (process.stdin.isTTY)
|
|
2236
|
+
return "";
|
|
2237
|
+
const chunks = [];
|
|
2238
|
+
for await (const chunk of process.stdin) {
|
|
2239
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
2240
|
+
}
|
|
2241
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
2242
|
+
}
|
|
2234
2243
|
function isUnsafeText(value) {
|
|
2235
2244
|
if (value.includes("\x00"))
|
|
2236
2245
|
return true;
|
|
@@ -2704,15 +2713,6 @@ function parseAnnotateArgs(argv) {
|
|
|
2704
2713
|
}
|
|
2705
2714
|
return { ok: false, error: `unknown annotate command: ${subcommand}` };
|
|
2706
2715
|
}
|
|
2707
|
-
async function readStdin() {
|
|
2708
|
-
if (process.stdin.isTTY)
|
|
2709
|
-
return "";
|
|
2710
|
-
const chunks = [];
|
|
2711
|
-
for await (const chunk of process.stdin) {
|
|
2712
|
-
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
2713
|
-
}
|
|
2714
|
-
return Buffer.concat(chunks).toString("utf8");
|
|
2715
|
-
}
|
|
2716
2716
|
async function annotateRequest(serverUrl, method, action, body) {
|
|
2717
2717
|
return requestJson(serverUrl, "/_annotations", method, body, action);
|
|
2718
2718
|
}
|
|
@@ -3016,8 +3016,8 @@ location and renders your explanation directly under the annotated lines.
|
|
|
3016
3016
|
- The body is Markdown. Code spans, fenced blocks, and links work. Long
|
|
3017
3017
|
bodies: use --body-file <path> or pipe via stdin instead of --body.
|
|
3018
3018
|
- Give every annotation a short --title; it becomes the inline heading.
|
|
3019
|
-
- Annotating unchanged code is fine: the
|
|
3020
|
-
|
|
3019
|
+
- Annotating unchanged code is fine: when the selected lines are not an
|
|
3020
|
+
after-side diff change, the viewer falls back to the full source view.
|
|
3021
3021
|
|
|
3022
3022
|
## Sessions
|
|
3023
3023
|
|
|
@@ -3080,7 +3080,7 @@ bunch of probing commands or open the \uD83E\uDE7A browser panel.
|
|
|
3080
3080
|
## When to use
|
|
3081
3081
|
|
|
3082
3082
|
- A previous command failed; you want to confirm Node / Bun / SQLite /
|
|
3083
|
-
Docker / git state before suggesting a fix.
|
|
3083
|
+
Docker / git / gh state before suggesting a fix.
|
|
3084
3084
|
- The human asks "is my environment OK", "why does install fail", or
|
|
3085
3085
|
similar; \`code-viewer doctor\` is the single source of truth.
|
|
3086
3086
|
- Pre-flight before a destructive step (DB snapshot reset, npx upgrade)
|
|
@@ -3111,7 +3111,7 @@ Stable JSON contract (see core/doctor-types.ts):
|
|
|
3111
3111
|
"generation": number, // monotonic, restart resets to 1
|
|
3112
3112
|
"worstStatus": "ok"|"warn"|"error",
|
|
3113
3113
|
"groups": [
|
|
3114
|
-
{ "id": "runtime"|"package"|"sqlite"|"snapshot"|"git"
|
|
3114
|
+
{ "id": "runtime"|"package"|"sqlite"|"snapshot"|"git"|"github"
|
|
3115
3115
|
|"discovery"|"datastore"|"docker"|"server",
|
|
3116
3116
|
"title": string,
|
|
3117
3117
|
"rows": [
|
|
@@ -3142,6 +3142,9 @@ The exit code is 1 iff \`worstStatus === "error"\` — never on \`warn\`.
|
|
|
3142
3142
|
- \`hint\` is the human-readable fix; quote it verbatim when reporting back.
|
|
3143
3143
|
- A \`runtime.node\` row failing means Node < 20 — almost everything else
|
|
3144
3144
|
is downstream of that. Fix it first.
|
|
3145
|
+
- A \`github.gh\` row warning means GitHub Issue listing/linking cannot use
|
|
3146
|
+
the GitHub CLI yet. Install \`gh\` or pass \`--bin gh=/absolute/path\`.
|
|
3147
|
+
Doctor intentionally does not inspect GitHub auth metadata.
|
|
3145
3148
|
- A \`sqlite.*\` row failing usually points at npx cache; the hint shows
|
|
3146
3149
|
the rm -rf ~/.npm/_npx workaround.
|
|
3147
3150
|
- Docker rows are advisory — \`code-viewer\` works without Docker; warnings
|
|
@@ -4045,123 +4048,1291 @@ Diff Viewer views.
|
|
|
4045
4048
|
- Git must be available on PATH, or supplied with --bin git=/absolute/path
|
|
4046
4049
|
/ CODE_VIEWER_BIN_GIT.
|
|
4047
4050
|
|
|
4048
|
-
## How to call
|
|
4051
|
+
## How to call
|
|
4052
|
+
|
|
4053
|
+
code-viewer file blame --path src/sample.ts --json
|
|
4054
|
+
code-viewer file blame --path src/sample.ts --base HEAD --json
|
|
4055
|
+
code-viewer file history --path src/sample.ts --limit 10 --json
|
|
4056
|
+
code-viewer file history --path src/sample.ts --query "author:tester" --json
|
|
4057
|
+
code-viewer file show --path src/sample.ts --json
|
|
4058
|
+
code-viewer file show --path src/sample.ts --start 100 --end 150 --json
|
|
4059
|
+
code-viewer file show --path src/sample.ts --ref main --json
|
|
4060
|
+
code-viewer file diff --path src/sample.ts --json
|
|
4061
|
+
code-viewer file diff --path src/sample.ts --from main --to HEAD --json
|
|
4062
|
+
code-viewer file diff --path src/sample.ts --full --json
|
|
4063
|
+
code-viewer file diff --path new_sample.ts --untracked --json
|
|
4064
|
+
code-viewer file diff --path renamed.ts --old-path original.ts --from HEAD~1 --to HEAD --json
|
|
4065
|
+
|
|
4066
|
+
## Output contract
|
|
4067
|
+
|
|
4068
|
+
--json shapes (mirroring git.ts DTOs verbatim where possible):
|
|
4069
|
+
|
|
4070
|
+
blame:
|
|
4071
|
+
{
|
|
4072
|
+
"path": string,
|
|
4073
|
+
"ref": string, // the literal ref the CLI resolved to
|
|
4074
|
+
"base": "worktree"|"HEAD",
|
|
4075
|
+
"result": GitBlameResult // { lines, commits, isUntracked?, isSynthetic?, error? }
|
|
4076
|
+
}
|
|
4077
|
+
history:
|
|
4078
|
+
{
|
|
4079
|
+
"path": string,
|
|
4080
|
+
"ref": string,
|
|
4081
|
+
"limit": number,
|
|
4082
|
+
"skip": number,
|
|
4083
|
+
"query"?: string,
|
|
4084
|
+
"result": { commits: GitHistoryCommit[], hasMore: boolean, error?: string }
|
|
4085
|
+
}
|
|
4086
|
+
show:
|
|
4087
|
+
{
|
|
4088
|
+
"path": string,
|
|
4089
|
+
"ref": string,
|
|
4090
|
+
"start"?: number,
|
|
4091
|
+
"end"?: number,
|
|
4092
|
+
"totalLines": number, // total lines in the file at <ref>
|
|
4093
|
+
"complete": boolean, // true when the returned text covers the whole file
|
|
4094
|
+
"text": string
|
|
4095
|
+
}
|
|
4096
|
+
diff:
|
|
4097
|
+
{
|
|
4098
|
+
"path": string,
|
|
4099
|
+
"old_path"?: string,
|
|
4100
|
+
"from": string, // "/dev/null" when untracked is true
|
|
4101
|
+
"to": string,
|
|
4102
|
+
"untracked": boolean,
|
|
4103
|
+
"ignore_ws": boolean,
|
|
4104
|
+
"ignore_blank": boolean,
|
|
4105
|
+
"mode": "preview"|"full",
|
|
4106
|
+
"max_hunks": number|null, // null in full mode
|
|
4107
|
+
"max_lines": number|null, // null in full mode
|
|
4108
|
+
"diff": string, // unified diff text
|
|
4109
|
+
"hunk_count": number, // total hunks before truncation
|
|
4110
|
+
"rendered_hunk_count": number, // hunks actually included in diff
|
|
4111
|
+
"line_count": number, // total newlines in the returned diff
|
|
4112
|
+
"truncated": boolean, // true when preview dropped hunks/lines
|
|
4113
|
+
"binary": boolean, // git emitted "Binary files ..."
|
|
4114
|
+
"error"?: string // git stderr (also exits 1)
|
|
4115
|
+
}
|
|
4116
|
+
|
|
4117
|
+
Default (non --json) output:
|
|
4118
|
+
|
|
4119
|
+
blame: one line per source line → <line><TAB><shortSha or "worktree"><TAB><summary>
|
|
4120
|
+
uncommitted edits show as "worktree" and "<uncommitted>".
|
|
4121
|
+
history: one line per commit → <shortSha><TAB><whenISO><TAB><author><TAB><subject>
|
|
4122
|
+
0 commits prints "no history" to stderr, exit 0.
|
|
4123
|
+
show: the file (or sliced lines) as text. 0-byte files succeed (exit 0).
|
|
4124
|
+
diff: unified diff text (the same bytes the JSON "diff" field carries).
|
|
4125
|
+
Empty (or worktree==worktree) returns nothing on stdout, exit 0.
|
|
4126
|
+
|
|
4127
|
+
## Tips
|
|
4128
|
+
|
|
4129
|
+
- Prefer --json. blame DTO carries author / authorMail / authorTime per
|
|
4130
|
+
commit; the plain-text format only shows summary.
|
|
4131
|
+
- For huge files, slice with --start/--end before piping to a model
|
|
4132
|
+
context — "totalLines" / "complete" tell you what was dropped.
|
|
4133
|
+
- file show reads the worktree by default. Pass --ref HEAD / --ref <branch>
|
|
4134
|
+
when you need a committed snapshot.
|
|
4135
|
+
- A non-fatal git failure (e.g. unknown ref, unsafe path) returns the
|
|
4136
|
+
error string inside the JSON "result.error" / "error" field AND exits 1.
|
|
4137
|
+
- file diff defaults to preview mode (${FILE_DIFF_DEFAULT_MAX_HUNKS} hunks /
|
|
4138
|
+
${FILE_DIFF_DEFAULT_MAX_LINES} lines). Use --full only when you need every
|
|
4139
|
+
hunk — preview matches the browser's initial paint and is almost always
|
|
4140
|
+
enough context for an LLM. In full mode, max_hunks / max_lines are null in
|
|
4141
|
+
JSON because no preview cap is applied. Worktree == Worktree range returns
|
|
4142
|
+
"" instantly.
|
|
4143
|
+
- Use "code-viewer search code" to discover the path first, then drill
|
|
4144
|
+
in with blame / history / show / diff.
|
|
4145
|
+
`;
|
|
4146
|
+
VALUE_FLAGS = new Set([
|
|
4147
|
+
"--path",
|
|
4148
|
+
"--ref",
|
|
4149
|
+
"--base",
|
|
4150
|
+
"--limit",
|
|
4151
|
+
"--skip",
|
|
4152
|
+
"--query",
|
|
4153
|
+
"--start",
|
|
4154
|
+
"--end",
|
|
4155
|
+
"--from",
|
|
4156
|
+
"--to",
|
|
4157
|
+
"--old-path",
|
|
4158
|
+
"--max-hunks",
|
|
4159
|
+
"--max-lines"
|
|
4160
|
+
]);
|
|
4161
|
+
BOOL_FLAGS = new Set([
|
|
4162
|
+
"--json",
|
|
4163
|
+
"--untracked",
|
|
4164
|
+
"--ignore-ws",
|
|
4165
|
+
"--ignore-blank",
|
|
4166
|
+
"--full"
|
|
4167
|
+
]);
|
|
4168
|
+
});
|
|
4169
|
+
|
|
4170
|
+
// web-src/core/journal.ts
|
|
4171
|
+
function isJournalTaskStatus(value) {
|
|
4172
|
+
return JOURNAL_TASK_STATUSES.includes(value);
|
|
4173
|
+
}
|
|
4174
|
+
function isJournalTaskPriority(value) {
|
|
4175
|
+
return JOURNAL_TASK_PRIORITIES.includes(value);
|
|
4176
|
+
}
|
|
4177
|
+
function isIsoDate(value) {
|
|
4178
|
+
if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value))
|
|
4179
|
+
return false;
|
|
4180
|
+
const date = new Date(`${value}T00:00:00.000Z`);
|
|
4181
|
+
return Number.isFinite(date.getTime()) && date.toISOString().slice(0, 10) === value;
|
|
4182
|
+
}
|
|
4183
|
+
function todayIsoDate(now = new Date) {
|
|
4184
|
+
const year = now.getFullYear();
|
|
4185
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
4186
|
+
const date = String(now.getDate()).padStart(2, "0");
|
|
4187
|
+
return `${year}-${month}-${date}`;
|
|
4188
|
+
}
|
|
4189
|
+
function taskClaimActive(task, now = Date.now()) {
|
|
4190
|
+
if (!task.claim)
|
|
4191
|
+
return false;
|
|
4192
|
+
const expires = Date.parse(task.claim.lease_expires_at);
|
|
4193
|
+
return Number.isFinite(expires) && expires > now;
|
|
4194
|
+
}
|
|
4195
|
+
function normalizeJournalLabel(value) {
|
|
4196
|
+
if (typeof value !== "string")
|
|
4197
|
+
return null;
|
|
4198
|
+
const label = value.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^\p{L}\p{N}._:-]+/gu, "").slice(0, JOURNAL_LABEL_MAX_CHARS).replace(/^[-_.:]+|[-_.:]+$/g, "");
|
|
4199
|
+
return label || null;
|
|
4200
|
+
}
|
|
4201
|
+
function normalizeJournalLabels(raw) {
|
|
4202
|
+
if (!Array.isArray(raw))
|
|
4203
|
+
return [];
|
|
4204
|
+
const labels = [];
|
|
4205
|
+
const seen = new Set;
|
|
4206
|
+
for (const item of raw) {
|
|
4207
|
+
const label = normalizeJournalLabel(item);
|
|
4208
|
+
if (!label || seen.has(label))
|
|
4209
|
+
continue;
|
|
4210
|
+
seen.add(label);
|
|
4211
|
+
labels.push(label);
|
|
4212
|
+
if (labels.length >= JOURNAL_MAX_LABELS)
|
|
4213
|
+
break;
|
|
4214
|
+
}
|
|
4215
|
+
return labels;
|
|
4216
|
+
}
|
|
4217
|
+
function journalIssueLabel(issueNumber) {
|
|
4218
|
+
return `issue-${issueNumber}`;
|
|
4219
|
+
}
|
|
4220
|
+
function journalIssueRepoLabel(repo) {
|
|
4221
|
+
if (typeof repo !== "string")
|
|
4222
|
+
return null;
|
|
4223
|
+
return normalizeJournalLabel(`repo-${repo.replace(/[\\/]+/g, "-")}`);
|
|
4224
|
+
}
|
|
4225
|
+
function collectJournalLabels(journal, tasks) {
|
|
4226
|
+
const labels = new Set;
|
|
4227
|
+
for (const entry of journal.entries) {
|
|
4228
|
+
for (const label of entry.labels)
|
|
4229
|
+
labels.add(label);
|
|
4230
|
+
}
|
|
4231
|
+
for (const task of tasks.tasks) {
|
|
4232
|
+
for (const label of task.labels)
|
|
4233
|
+
labels.add(label);
|
|
4234
|
+
}
|
|
4235
|
+
return [...labels].sort((a, b) => a.localeCompare(b));
|
|
4236
|
+
}
|
|
4237
|
+
function filterJournalTasks(state, filter = {}) {
|
|
4238
|
+
const statuses = Array.isArray(filter.status) ? filter.status : filter.status ? [filter.status] : undefined;
|
|
4239
|
+
const labels = filter.labels?.filter(Boolean) || [];
|
|
4240
|
+
return state.tasks.filter((task) => {
|
|
4241
|
+
if (statuses && !statuses.includes(task.status))
|
|
4242
|
+
return false;
|
|
4243
|
+
if (labels.length && !labels.every((label) => task.labels.includes(label))) {
|
|
4244
|
+
return false;
|
|
4245
|
+
}
|
|
4246
|
+
if (!filter.includeClaimed && taskClaimActive(task, filter.now))
|
|
4247
|
+
return false;
|
|
4248
|
+
return true;
|
|
4249
|
+
});
|
|
4250
|
+
}
|
|
4251
|
+
function selectNextJournalTasks(state, filter = {}, limit = 1) {
|
|
4252
|
+
const status = filter.status || "todo";
|
|
4253
|
+
const indexed = filterJournalTasks(state, { ...filter, status }).map((task, index) => ({ task, index }));
|
|
4254
|
+
indexed.sort((a, b) => {
|
|
4255
|
+
const priority = PRIORITY_SCORE[a.task.priority] - PRIORITY_SCORE[b.task.priority];
|
|
4256
|
+
if (priority !== 0)
|
|
4257
|
+
return priority;
|
|
4258
|
+
if (a.index !== b.index)
|
|
4259
|
+
return a.index - b.index;
|
|
4260
|
+
return a.task.created_at.localeCompare(b.task.created_at);
|
|
4261
|
+
});
|
|
4262
|
+
return indexed.slice(0, Math.max(0, limit)).map((item) => item.task);
|
|
4263
|
+
}
|
|
4264
|
+
var JOURNAL_TASK_STATUSES, JOURNAL_TASK_PRIORITIES, JOURNAL_LABEL_MAX_CHARS = 48, JOURNAL_MAX_LABELS = 24, PRIORITY_SCORE;
|
|
4265
|
+
var init_journal = __esm(() => {
|
|
4266
|
+
JOURNAL_TASK_STATUSES = [
|
|
4267
|
+
"draft",
|
|
4268
|
+
"todo",
|
|
4269
|
+
"doing",
|
|
4270
|
+
"blocked",
|
|
4271
|
+
"done"
|
|
4272
|
+
];
|
|
4273
|
+
JOURNAL_TASK_PRIORITIES = ["p0", "p1", "p2", "p3"];
|
|
4274
|
+
PRIORITY_SCORE = {
|
|
4275
|
+
p0: 0,
|
|
4276
|
+
p1: 1,
|
|
4277
|
+
p2: 2,
|
|
4278
|
+
p3: 3
|
|
4279
|
+
};
|
|
4280
|
+
});
|
|
4281
|
+
|
|
4282
|
+
// web-src/server/github-issues.ts
|
|
4283
|
+
function normalizeGithubIssueListState(value) {
|
|
4284
|
+
return value === "closed" || value === "all" ? value : "open";
|
|
4285
|
+
}
|
|
4286
|
+
function normalizeGithubIssueListLimit(value) {
|
|
4287
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 1) {
|
|
4288
|
+
return 30;
|
|
4289
|
+
}
|
|
4290
|
+
return Math.min(Math.floor(value), 100);
|
|
4291
|
+
}
|
|
4292
|
+
function singleLineGithubOption(value) {
|
|
4293
|
+
if (!value)
|
|
4294
|
+
return;
|
|
4295
|
+
const trimmed = value.trim();
|
|
4296
|
+
if (!trimmed || trimmed.includes("\x00") || /[\r\n]/.test(trimmed)) {
|
|
4297
|
+
return;
|
|
4298
|
+
}
|
|
4299
|
+
return trimmed.slice(0, 200);
|
|
4300
|
+
}
|
|
4301
|
+
function githubSearchHasStateQualifier(value) {
|
|
4302
|
+
const search = singleLineGithubOption(value);
|
|
4303
|
+
return !!search && /(^|\s)(is|state):(open|closed|all)(?=\s|$)/i.test(search);
|
|
4304
|
+
}
|
|
4305
|
+
function extractGithubIssueLabels(raw) {
|
|
4306
|
+
if (!Array.isArray(raw))
|
|
4307
|
+
return [];
|
|
4308
|
+
const labels = [];
|
|
4309
|
+
for (const item of raw) {
|
|
4310
|
+
if (typeof item === "string") {
|
|
4311
|
+
const label = singleLineGithubOption(item);
|
|
4312
|
+
if (label)
|
|
4313
|
+
labels.push(label);
|
|
4314
|
+
} else if (item && typeof item === "object" && "name" in item) {
|
|
4315
|
+
const label = singleLineGithubOption(item.name);
|
|
4316
|
+
if (label)
|
|
4317
|
+
labels.push(label);
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4320
|
+
return labels.slice(0, 12);
|
|
4321
|
+
}
|
|
4322
|
+
function normalizeGithubIssueListItem(raw) {
|
|
4323
|
+
if (!raw || typeof raw !== "object")
|
|
4324
|
+
return null;
|
|
4325
|
+
const issue = raw;
|
|
4326
|
+
const number = issue.number;
|
|
4327
|
+
const title = issue.title;
|
|
4328
|
+
if (typeof number !== "number" || !Number.isInteger(number) || number <= 0 || typeof title !== "string" || !title.trim()) {
|
|
4329
|
+
return null;
|
|
4330
|
+
}
|
|
4331
|
+
const url = singleLineGithubOption(issue.url);
|
|
4332
|
+
const state = typeof issue.state === "string" && issue.state.trim() ? issue.state.trim().toLowerCase() : "open";
|
|
4333
|
+
return {
|
|
4334
|
+
number,
|
|
4335
|
+
title: title.trim().slice(0, 200),
|
|
4336
|
+
state,
|
|
4337
|
+
...url ? { url } : {},
|
|
4338
|
+
labels: extractGithubIssueLabels(issue.labels)
|
|
4339
|
+
};
|
|
4340
|
+
}
|
|
4341
|
+
function parseGithubIssueListOutput(stdout) {
|
|
4342
|
+
const parsed = JSON.parse(stdout);
|
|
4343
|
+
if (!Array.isArray(parsed))
|
|
4344
|
+
return [];
|
|
4345
|
+
return parsed.map(normalizeGithubIssueListItem).filter((issue) => issue !== null);
|
|
4346
|
+
}
|
|
4347
|
+
function parseGithubIssueViewOutput(stdout) {
|
|
4348
|
+
const issue = normalizeGithubIssueListItem(JSON.parse(stdout));
|
|
4349
|
+
if (!issue)
|
|
4350
|
+
throw new GithubIssueListError("failed to parse gh issue output");
|
|
4351
|
+
return issue;
|
|
4352
|
+
}
|
|
4353
|
+
function buildGithubIssueListArgs(options) {
|
|
4354
|
+
const search = singleLineGithubOption(options.search);
|
|
4355
|
+
const args = [
|
|
4356
|
+
commandForExternal("gh"),
|
|
4357
|
+
"issue",
|
|
4358
|
+
"list",
|
|
4359
|
+
"--json",
|
|
4360
|
+
"number,title,state,labels,url",
|
|
4361
|
+
"--limit",
|
|
4362
|
+
String(normalizeGithubIssueListLimit(options.limit)),
|
|
4363
|
+
"--state",
|
|
4364
|
+
search && githubSearchHasStateQualifier(search) ? "all" : normalizeGithubIssueListState(options.state)
|
|
4365
|
+
];
|
|
4366
|
+
const repo = singleLineGithubOption(options.repo);
|
|
4367
|
+
if (repo)
|
|
4368
|
+
args.push("--repo", repo);
|
|
4369
|
+
if (search)
|
|
4370
|
+
args.push("--search", search);
|
|
4371
|
+
const seenLabels = new Set;
|
|
4372
|
+
for (const rawLabel of options.labels || []) {
|
|
4373
|
+
const label = singleLineGithubOption(rawLabel);
|
|
4374
|
+
if (!label || seenLabels.has(label))
|
|
4375
|
+
continue;
|
|
4376
|
+
seenLabels.add(label);
|
|
4377
|
+
args.push("--label", label);
|
|
4378
|
+
if (seenLabels.size >= GITHUB_ISSUE_LABEL_FILTER_LIMIT)
|
|
4379
|
+
break;
|
|
4380
|
+
}
|
|
4381
|
+
return args;
|
|
4382
|
+
}
|
|
4383
|
+
function buildGithubIssueViewArgs(options) {
|
|
4384
|
+
const args = [
|
|
4385
|
+
commandForExternal("gh"),
|
|
4386
|
+
"issue",
|
|
4387
|
+
"view",
|
|
4388
|
+
String(options.number),
|
|
4389
|
+
"--json",
|
|
4390
|
+
"number,title,state,labels,url"
|
|
4391
|
+
];
|
|
4392
|
+
const repo = singleLineGithubOption(options.repo);
|
|
4393
|
+
if (repo)
|
|
4394
|
+
args.push("--repo", repo);
|
|
4395
|
+
return args;
|
|
4396
|
+
}
|
|
4397
|
+
function readGithubIssueList(options) {
|
|
4398
|
+
const proc = runSync(buildGithubIssueListArgs(options), options.cwd, {
|
|
4399
|
+
timeout: 30000
|
|
4400
|
+
});
|
|
4401
|
+
if (proc.code !== 0) {
|
|
4402
|
+
const detail = isCommandNotFoundResult("gh", proc) ? commandNotFoundDetail("gh") : proc.stderr.trim() || `gh issue list exited with code ${proc.code}`;
|
|
4403
|
+
throw new GithubIssueListError(detail);
|
|
4404
|
+
}
|
|
4405
|
+
try {
|
|
4406
|
+
return parseGithubIssueListOutput(proc.stdout);
|
|
4407
|
+
} catch {
|
|
4408
|
+
throw new GithubIssueListError("failed to parse gh issue list output");
|
|
4409
|
+
}
|
|
4410
|
+
}
|
|
4411
|
+
function readGithubIssue(options) {
|
|
4412
|
+
const proc = runSync(buildGithubIssueViewArgs(options), options.cwd, {
|
|
4413
|
+
timeout: 30000
|
|
4414
|
+
});
|
|
4415
|
+
if (proc.code !== 0) {
|
|
4416
|
+
const detail = isCommandNotFoundResult("gh", proc) ? commandNotFoundDetail("gh") : proc.stderr.trim() || `gh issue view exited with code ${proc.code}`;
|
|
4417
|
+
throw new GithubIssueListError(detail);
|
|
4418
|
+
}
|
|
4419
|
+
try {
|
|
4420
|
+
return parseGithubIssueViewOutput(proc.stdout);
|
|
4421
|
+
} catch (error) {
|
|
4422
|
+
if (error instanceof GithubIssueListError)
|
|
4423
|
+
throw error;
|
|
4424
|
+
throw new GithubIssueListError("failed to parse gh issue output");
|
|
4425
|
+
}
|
|
4426
|
+
}
|
|
4427
|
+
var GITHUB_ISSUE_LABEL_FILTER_LIMIT = 24, GithubIssueListError;
|
|
4428
|
+
var init_github_issues = __esm(() => {
|
|
4429
|
+
init_command_resolver();
|
|
4430
|
+
init_runtime();
|
|
4431
|
+
GithubIssueListError = class GithubIssueListError extends Error {
|
|
4432
|
+
status;
|
|
4433
|
+
constructor(message, status = 502) {
|
|
4434
|
+
super(message);
|
|
4435
|
+
this.status = status;
|
|
4436
|
+
}
|
|
4437
|
+
};
|
|
4438
|
+
});
|
|
4439
|
+
|
|
4440
|
+
// web-src/server/journal-cli.ts
|
|
4441
|
+
var exports_journal_cli = {};
|
|
4442
|
+
__export(exports_journal_cli, {
|
|
4443
|
+
runJournalCli: () => runJournalCli,
|
|
4444
|
+
parseJournalArgs: () => parseJournalArgs,
|
|
4445
|
+
JOURNAL_HELP: () => JOURNAL_HELP,
|
|
4446
|
+
JOURNAL_AGENT_HELP: () => JOURNAL_AGENT_HELP
|
|
4447
|
+
});
|
|
4448
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
4449
|
+
function parsePositiveInteger(value, flag) {
|
|
4450
|
+
if (value === undefined)
|
|
4451
|
+
return;
|
|
4452
|
+
const n = Number(value);
|
|
4453
|
+
if (!Number.isInteger(n) || n < 1)
|
|
4454
|
+
throw new Error(`${flag} must be a positive integer`);
|
|
4455
|
+
return n;
|
|
4456
|
+
}
|
|
4457
|
+
function parseDateValue(value, flag) {
|
|
4458
|
+
if (value === undefined)
|
|
4459
|
+
return;
|
|
4460
|
+
if (value === "today")
|
|
4461
|
+
return todayIsoDate();
|
|
4462
|
+
if (value === "none")
|
|
4463
|
+
return "";
|
|
4464
|
+
if (!isIsoDate(value))
|
|
4465
|
+
throw new Error(`${flag} must be YYYY-MM-DD or today`);
|
|
4466
|
+
return value;
|
|
4467
|
+
}
|
|
4468
|
+
function parseStatus(value) {
|
|
4469
|
+
if (value === undefined)
|
|
4470
|
+
return;
|
|
4471
|
+
if (isJournalTaskStatus(value))
|
|
4472
|
+
return value;
|
|
4473
|
+
throw new Error("--status must be draft, todo, doing, blocked, or done");
|
|
4474
|
+
}
|
|
4475
|
+
function parsePriority(value) {
|
|
4476
|
+
if (value === undefined)
|
|
4477
|
+
return;
|
|
4478
|
+
if (isJournalTaskPriority(value))
|
|
4479
|
+
return value;
|
|
4480
|
+
throw new Error("--priority must be p0, p1, p2, or p3");
|
|
4481
|
+
}
|
|
4482
|
+
function parseGithubIssueState(value) {
|
|
4483
|
+
if (value === undefined)
|
|
4484
|
+
return "open";
|
|
4485
|
+
if (value === "open" || value === "closed" || value === "all")
|
|
4486
|
+
return value;
|
|
4487
|
+
throw new Error("--state must be open, closed, or all");
|
|
4488
|
+
}
|
|
4489
|
+
function parseGithubOption(value, flag) {
|
|
4490
|
+
if (value === undefined)
|
|
4491
|
+
return;
|
|
4492
|
+
const normalized = singleLineGithubOption(value);
|
|
4493
|
+
if (!normalized)
|
|
4494
|
+
throw new Error(`${flag} must be a non-empty single line`);
|
|
4495
|
+
return normalized;
|
|
4496
|
+
}
|
|
4497
|
+
function parseJournalArgs(argv) {
|
|
4498
|
+
const rest = [];
|
|
4499
|
+
let cwd;
|
|
4500
|
+
let server;
|
|
4501
|
+
const options = new Map;
|
|
4502
|
+
const multiOptions = new Map;
|
|
4503
|
+
const flags = new Set;
|
|
4504
|
+
const commandOverrides = [];
|
|
4505
|
+
const valueFlags = new Set([
|
|
4506
|
+
"--date",
|
|
4507
|
+
"--title",
|
|
4508
|
+
"--body",
|
|
4509
|
+
"--body-file",
|
|
4510
|
+
"--note",
|
|
4511
|
+
"--note-file",
|
|
4512
|
+
"--label",
|
|
4513
|
+
"--status",
|
|
4514
|
+
"--priority",
|
|
4515
|
+
"--due",
|
|
4516
|
+
"--source-date",
|
|
4517
|
+
"--before",
|
|
4518
|
+
"--after",
|
|
4519
|
+
"--position",
|
|
4520
|
+
"--by",
|
|
4521
|
+
"--lease-minutes",
|
|
4522
|
+
"--wip-limit",
|
|
4523
|
+
"--limit",
|
|
4524
|
+
"--repo",
|
|
4525
|
+
"--gh-label",
|
|
4526
|
+
"--search",
|
|
4527
|
+
"--state"
|
|
4528
|
+
]);
|
|
4529
|
+
try {
|
|
4530
|
+
for (let i = 0;i < argv.length; i++) {
|
|
4531
|
+
const arg = argv[i];
|
|
4532
|
+
if (arg === "--help" || arg === "-h")
|
|
4533
|
+
return { ok: true, args: { command: { kind: "help" }, dryRun: false } };
|
|
4534
|
+
if (arg === "--cwd" || arg === "--server") {
|
|
4535
|
+
const taken = takeValue(argv, i, arg);
|
|
4536
|
+
if ("error" in taken)
|
|
4537
|
+
return { ok: false, error: taken.error };
|
|
4538
|
+
if (arg === "--cwd")
|
|
4539
|
+
cwd = taken.value;
|
|
4540
|
+
else
|
|
4541
|
+
server = taken.value;
|
|
4542
|
+
i = taken.next;
|
|
4543
|
+
} else if (arg === "--bin") {
|
|
4544
|
+
const taken = takeValue(argv, i, arg);
|
|
4545
|
+
if ("error" in taken)
|
|
4546
|
+
return { ok: false, error: taken.error };
|
|
4547
|
+
const parsed = parseExternalCommandOverride(taken.value, "--bin", [
|
|
4548
|
+
"gh"
|
|
4549
|
+
]);
|
|
4550
|
+
if (parsed.ok === false)
|
|
4551
|
+
return { ok: false, error: parsed.error };
|
|
4552
|
+
commandOverrides.push(parsed.override);
|
|
4553
|
+
i = taken.next;
|
|
4554
|
+
} else if (valueFlags.has(arg)) {
|
|
4555
|
+
const taken = takeValue(argv, i, arg);
|
|
4556
|
+
if ("error" in taken)
|
|
4557
|
+
return { ok: false, error: taken.error };
|
|
4558
|
+
options.set(arg, taken.value);
|
|
4559
|
+
const values = multiOptions.get(arg) || [];
|
|
4560
|
+
values.push(taken.value);
|
|
4561
|
+
multiOptions.set(arg, values);
|
|
4562
|
+
i = taken.next;
|
|
4563
|
+
} else if (arg === "--json" || arg === "--dry-run" || arg === "--clear-labels") {
|
|
4564
|
+
flags.add(arg);
|
|
4565
|
+
} else if (arg.startsWith("-")) {
|
|
4566
|
+
return { ok: false, error: `unknown option: ${arg}` };
|
|
4567
|
+
} else {
|
|
4568
|
+
rest.push(arg);
|
|
4569
|
+
}
|
|
4570
|
+
}
|
|
4571
|
+
const labels = multiOptions.get("--label") || [];
|
|
4572
|
+
const subcommand = rest[0];
|
|
4573
|
+
const dryRun = flags.has("--dry-run");
|
|
4574
|
+
if (!subcommand)
|
|
4575
|
+
return { ok: true, args: { command: { kind: "help" }, dryRun } };
|
|
4576
|
+
if (subcommand === "agent-help")
|
|
4577
|
+
return { ok: true, args: { command: { kind: "agent-help" }, dryRun } };
|
|
4578
|
+
if (commandOverrides.length > 0 && subcommand !== "github-issues" && subcommand !== "task-link-issue") {
|
|
4579
|
+
return {
|
|
4580
|
+
ok: false,
|
|
4581
|
+
error: "--bin is only supported with github-issues or task-link-issue"
|
|
4582
|
+
};
|
|
4583
|
+
}
|
|
4584
|
+
if (subcommand === "list") {
|
|
4585
|
+
return {
|
|
4586
|
+
ok: true,
|
|
4587
|
+
args: {
|
|
4588
|
+
command: {
|
|
4589
|
+
kind: "list",
|
|
4590
|
+
date: parseDateValue(options.get("--date"), "--date"),
|
|
4591
|
+
limit: parsePositiveInteger(options.get("--limit"), "--limit"),
|
|
4592
|
+
json: flags.has("--json")
|
|
4593
|
+
},
|
|
4594
|
+
cwd,
|
|
4595
|
+
server,
|
|
4596
|
+
dryRun
|
|
4597
|
+
}
|
|
4598
|
+
};
|
|
4599
|
+
}
|
|
4600
|
+
if (subcommand === "add") {
|
|
4601
|
+
const date = parseDateValue(options.get("--date"), "--date");
|
|
4602
|
+
if (!date)
|
|
4603
|
+
return { ok: false, error: "add requires --date <YYYY-MM-DD|today>" };
|
|
4604
|
+
return {
|
|
4605
|
+
ok: true,
|
|
4606
|
+
args: {
|
|
4607
|
+
command: {
|
|
4608
|
+
kind: "add",
|
|
4609
|
+
date,
|
|
4610
|
+
title: options.get("--title"),
|
|
4611
|
+
labels,
|
|
4612
|
+
body: options.get("--body"),
|
|
4613
|
+
bodyFile: options.get("--body-file")
|
|
4614
|
+
},
|
|
4615
|
+
cwd,
|
|
4616
|
+
server,
|
|
4617
|
+
dryRun
|
|
4618
|
+
}
|
|
4619
|
+
};
|
|
4620
|
+
}
|
|
4621
|
+
if (subcommand === "edit") {
|
|
4622
|
+
const id = rest[1];
|
|
4623
|
+
if (!id)
|
|
4624
|
+
return { ok: false, error: "edit requires a journal entry id" };
|
|
4625
|
+
return {
|
|
4626
|
+
ok: true,
|
|
4627
|
+
args: {
|
|
4628
|
+
command: {
|
|
4629
|
+
kind: "edit",
|
|
4630
|
+
id,
|
|
4631
|
+
date: parseDateValue(options.get("--date"), "--date"),
|
|
4632
|
+
title: options.get("--title"),
|
|
4633
|
+
labels: multiOptions.has("--label") ? labels : undefined,
|
|
4634
|
+
body: options.get("--body"),
|
|
4635
|
+
bodyFile: options.get("--body-file")
|
|
4636
|
+
},
|
|
4637
|
+
cwd,
|
|
4638
|
+
server,
|
|
4639
|
+
dryRun
|
|
4640
|
+
}
|
|
4641
|
+
};
|
|
4642
|
+
}
|
|
4643
|
+
if (subcommand === "tasks") {
|
|
4644
|
+
return {
|
|
4645
|
+
ok: true,
|
|
4646
|
+
args: {
|
|
4647
|
+
command: {
|
|
4648
|
+
kind: "tasks",
|
|
4649
|
+
status: parseStatus(options.get("--status")),
|
|
4650
|
+
labels,
|
|
4651
|
+
json: flags.has("--json")
|
|
4652
|
+
},
|
|
4653
|
+
cwd,
|
|
4654
|
+
server,
|
|
4655
|
+
dryRun
|
|
4656
|
+
}
|
|
4657
|
+
};
|
|
4658
|
+
}
|
|
4659
|
+
if (subcommand === "task-add") {
|
|
4660
|
+
const title = options.get("--title");
|
|
4661
|
+
if (!title)
|
|
4662
|
+
return { ok: false, error: "task-add requires --title <text>" };
|
|
4663
|
+
return {
|
|
4664
|
+
ok: true,
|
|
4665
|
+
args: {
|
|
4666
|
+
command: {
|
|
4667
|
+
kind: "task-add",
|
|
4668
|
+
title,
|
|
4669
|
+
status: parseStatus(options.get("--status")),
|
|
4670
|
+
priority: parsePriority(options.get("--priority")),
|
|
4671
|
+
labels,
|
|
4672
|
+
dueDate: parseDateValue(options.get("--due"), "--due"),
|
|
4673
|
+
sourceDate: parseDateValue(options.get("--source-date"), "--source-date"),
|
|
4674
|
+
before: options.get("--before"),
|
|
4675
|
+
after: options.get("--after"),
|
|
4676
|
+
position: parsePositiveInteger(options.get("--position"), "--position"),
|
|
4677
|
+
body: options.get("--body"),
|
|
4678
|
+
bodyFile: options.get("--body-file")
|
|
4679
|
+
},
|
|
4680
|
+
cwd,
|
|
4681
|
+
server,
|
|
4682
|
+
dryRun
|
|
4683
|
+
}
|
|
4684
|
+
};
|
|
4685
|
+
}
|
|
4686
|
+
if (subcommand === "task-update") {
|
|
4687
|
+
const id = rest[1];
|
|
4688
|
+
if (!id)
|
|
4689
|
+
return { ok: false, error: "task-update requires a task id" };
|
|
4690
|
+
return {
|
|
4691
|
+
ok: true,
|
|
4692
|
+
args: {
|
|
4693
|
+
command: {
|
|
4694
|
+
kind: "task-update",
|
|
4695
|
+
id,
|
|
4696
|
+
title: options.get("--title"),
|
|
4697
|
+
status: parseStatus(options.get("--status")),
|
|
4698
|
+
priority: parsePriority(options.get("--priority")),
|
|
4699
|
+
labels: multiOptions.has("--label") ? labels : undefined,
|
|
4700
|
+
clearLabels: flags.has("--clear-labels"),
|
|
4701
|
+
dueDate: parseDateValue(options.get("--due"), "--due") ?? undefined,
|
|
4702
|
+
sourceDate: parseDateValue(options.get("--source-date"), "--source-date") ?? undefined,
|
|
4703
|
+
body: options.get("--body"),
|
|
4704
|
+
bodyFile: options.get("--body-file")
|
|
4705
|
+
},
|
|
4706
|
+
cwd,
|
|
4707
|
+
server,
|
|
4708
|
+
dryRun
|
|
4709
|
+
}
|
|
4710
|
+
};
|
|
4711
|
+
}
|
|
4712
|
+
if (subcommand === "task-next") {
|
|
4713
|
+
return {
|
|
4714
|
+
ok: true,
|
|
4715
|
+
args: {
|
|
4716
|
+
command: {
|
|
4717
|
+
kind: "task-next",
|
|
4718
|
+
status: parseStatus(options.get("--status")),
|
|
4719
|
+
labels,
|
|
4720
|
+
limit: parsePositiveInteger(options.get("--limit"), "--limit") || 1,
|
|
4721
|
+
json: flags.has("--json")
|
|
4722
|
+
},
|
|
4723
|
+
cwd,
|
|
4724
|
+
server,
|
|
4725
|
+
dryRun
|
|
4726
|
+
}
|
|
4727
|
+
};
|
|
4728
|
+
}
|
|
4729
|
+
if (subcommand === "github-issues") {
|
|
4730
|
+
if (multiOptions.has("--label")) {
|
|
4731
|
+
return {
|
|
4732
|
+
ok: false,
|
|
4733
|
+
error: "github-issues uses --gh-label, not --label"
|
|
4734
|
+
};
|
|
4735
|
+
}
|
|
4736
|
+
return {
|
|
4737
|
+
ok: true,
|
|
4738
|
+
args: {
|
|
4739
|
+
command: {
|
|
4740
|
+
kind: "github-issues",
|
|
4741
|
+
repo: parseGithubOption(options.get("--repo"), "--repo"),
|
|
4742
|
+
ghState: parseGithubIssueState(options.get("--state")),
|
|
4743
|
+
ghLabels: (multiOptions.get("--gh-label") || []).map((label) => parseGithubOption(label, "--gh-label")),
|
|
4744
|
+
search: parseGithubOption(options.get("--search"), "--search"),
|
|
4745
|
+
limit: parsePositiveInteger(options.get("--limit"), "--limit") || 30,
|
|
4746
|
+
json: flags.has("--json")
|
|
4747
|
+
},
|
|
4748
|
+
cwd,
|
|
4749
|
+
server,
|
|
4750
|
+
commandOverrides,
|
|
4751
|
+
dryRun
|
|
4752
|
+
}
|
|
4753
|
+
};
|
|
4754
|
+
}
|
|
4755
|
+
if (subcommand === "task-link-issue") {
|
|
4756
|
+
if (multiOptions.has("--gh-label")) {
|
|
4757
|
+
return {
|
|
4758
|
+
ok: false,
|
|
4759
|
+
error: "task-link-issue uses --label for local labels"
|
|
4760
|
+
};
|
|
4761
|
+
}
|
|
4762
|
+
const issueNumber = parsePositiveInteger(rest[1], "issue number");
|
|
4763
|
+
if (!issueNumber) {
|
|
4764
|
+
return {
|
|
4765
|
+
ok: false,
|
|
4766
|
+
error: "task-link-issue requires <number>"
|
|
4767
|
+
};
|
|
4768
|
+
}
|
|
4769
|
+
return {
|
|
4770
|
+
ok: true,
|
|
4771
|
+
args: {
|
|
4772
|
+
command: {
|
|
4773
|
+
kind: "task-link-issue",
|
|
4774
|
+
issueNumber,
|
|
4775
|
+
repo: parseGithubOption(options.get("--repo"), "--repo"),
|
|
4776
|
+
status: parseStatus(options.get("--status")),
|
|
4777
|
+
priority: parsePriority(options.get("--priority")),
|
|
4778
|
+
labels,
|
|
4779
|
+
before: options.get("--before"),
|
|
4780
|
+
after: options.get("--after"),
|
|
4781
|
+
position: parsePositiveInteger(options.get("--position"), "--position"),
|
|
4782
|
+
json: flags.has("--json")
|
|
4783
|
+
},
|
|
4784
|
+
cwd,
|
|
4785
|
+
server,
|
|
4786
|
+
commandOverrides,
|
|
4787
|
+
dryRun
|
|
4788
|
+
}
|
|
4789
|
+
};
|
|
4790
|
+
}
|
|
4791
|
+
if (subcommand === "task-claim") {
|
|
4792
|
+
const id = rest[1];
|
|
4793
|
+
if (!id)
|
|
4794
|
+
return { ok: false, error: "task-claim requires a task id" };
|
|
4795
|
+
return {
|
|
4796
|
+
ok: true,
|
|
4797
|
+
args: {
|
|
4798
|
+
command: {
|
|
4799
|
+
kind: "task-claim",
|
|
4800
|
+
id,
|
|
4801
|
+
by: options.get("--by"),
|
|
4802
|
+
leaseMinutes: parsePositiveInteger(options.get("--lease-minutes"), "--lease-minutes"),
|
|
4803
|
+
wipLimit: parsePositiveInteger(options.get("--wip-limit"), "--wip-limit")
|
|
4804
|
+
},
|
|
4805
|
+
cwd,
|
|
4806
|
+
server,
|
|
4807
|
+
dryRun
|
|
4808
|
+
}
|
|
4809
|
+
};
|
|
4810
|
+
}
|
|
4811
|
+
if (subcommand === "task-done") {
|
|
4812
|
+
const id = rest[1];
|
|
4813
|
+
if (!id)
|
|
4814
|
+
return { ok: false, error: "task-done requires a task id" };
|
|
4815
|
+
if (!options.get("--by"))
|
|
4816
|
+
return { ok: false, error: "task-done requires --by <agent>" };
|
|
4817
|
+
return {
|
|
4818
|
+
ok: true,
|
|
4819
|
+
args: {
|
|
4820
|
+
command: {
|
|
4821
|
+
kind: "task-done",
|
|
4822
|
+
id,
|
|
4823
|
+
by: options.get("--by"),
|
|
4824
|
+
note: options.get("--note"),
|
|
4825
|
+
noteFile: options.get("--note-file")
|
|
4826
|
+
},
|
|
4827
|
+
cwd,
|
|
4828
|
+
server,
|
|
4829
|
+
dryRun
|
|
4830
|
+
}
|
|
4831
|
+
};
|
|
4832
|
+
}
|
|
4833
|
+
if (subcommand === "task-delete") {
|
|
4834
|
+
const id = rest[1];
|
|
4835
|
+
if (!id)
|
|
4836
|
+
return { ok: false, error: "task-delete requires a task id" };
|
|
4837
|
+
return {
|
|
4838
|
+
ok: true,
|
|
4839
|
+
args: { command: { kind: "task-delete", id }, cwd, server, dryRun }
|
|
4840
|
+
};
|
|
4841
|
+
}
|
|
4842
|
+
return { ok: false, error: `unknown journal command: ${subcommand}` };
|
|
4843
|
+
} catch (error) {
|
|
4844
|
+
return {
|
|
4845
|
+
ok: false,
|
|
4846
|
+
error: error instanceof Error ? error.message : "invalid journal arguments"
|
|
4847
|
+
};
|
|
4848
|
+
}
|
|
4849
|
+
}
|
|
4850
|
+
async function textFromBody(command, required) {
|
|
4851
|
+
if (command.body !== undefined && command.bodyFile !== undefined) {
|
|
4852
|
+
console.error("use either --body or --body-file");
|
|
4853
|
+
process.exit(1);
|
|
4854
|
+
}
|
|
4855
|
+
if (command.body !== undefined)
|
|
4856
|
+
return command.body;
|
|
4857
|
+
if (command.bodyFile !== undefined) {
|
|
4858
|
+
try {
|
|
4859
|
+
return readFileSync5(command.bodyFile, "utf8");
|
|
4860
|
+
} catch {
|
|
4861
|
+
console.error(`could not read --body-file: ${command.bodyFile}`);
|
|
4862
|
+
process.exit(1);
|
|
4863
|
+
}
|
|
4864
|
+
}
|
|
4865
|
+
const stdin = await readStdin();
|
|
4866
|
+
if (stdin.trim())
|
|
4867
|
+
return stdin;
|
|
4868
|
+
if (required) {
|
|
4869
|
+
console.error("body is empty. Pass --body, --body-file, or pipe stdin.");
|
|
4870
|
+
process.exit(1);
|
|
4871
|
+
}
|
|
4872
|
+
return;
|
|
4873
|
+
}
|
|
4874
|
+
function textFromNote(command) {
|
|
4875
|
+
if (command.note !== undefined && command.noteFile !== undefined) {
|
|
4876
|
+
console.error("use either --note or --note-file");
|
|
4877
|
+
process.exit(1);
|
|
4878
|
+
}
|
|
4879
|
+
if (command.note !== undefined)
|
|
4880
|
+
return command.note;
|
|
4881
|
+
if (command.noteFile !== undefined) {
|
|
4882
|
+
try {
|
|
4883
|
+
return readFileSync5(command.noteFile, "utf8");
|
|
4884
|
+
} catch {
|
|
4885
|
+
console.error(`could not read --note-file: ${command.noteFile}`);
|
|
4886
|
+
process.exit(1);
|
|
4887
|
+
}
|
|
4888
|
+
}
|
|
4889
|
+
return;
|
|
4890
|
+
}
|
|
4891
|
+
async function journalRequest(serverUrl, method, action, body) {
|
|
4892
|
+
return requestJson(serverUrl, "/_journal", method, body, action);
|
|
4893
|
+
}
|
|
4894
|
+
function selectEntries(data, date, limit) {
|
|
4895
|
+
const entries = date ? data.journal.entries.filter((entry) => entry.date === date) : data.journal.entries;
|
|
4896
|
+
if (limit === undefined)
|
|
4897
|
+
return entries;
|
|
4898
|
+
return [...entries].sort((a, b) => b.date.localeCompare(a.date) || b.updated_at.localeCompare(a.updated_at) || b.created_at.localeCompare(a.created_at)).slice(0, limit);
|
|
4899
|
+
}
|
|
4900
|
+
function printEntries(data, date, limit) {
|
|
4901
|
+
const entries = selectEntries(data, date, limit);
|
|
4902
|
+
if (!entries.length) {
|
|
4903
|
+
console.log("no journal entries");
|
|
4904
|
+
return;
|
|
4905
|
+
}
|
|
4906
|
+
for (const entry of entries) {
|
|
4907
|
+
const labels = entry.labels.length ? ` #${entry.labels.join(" #")}` : "";
|
|
4908
|
+
const title = entry.title ? ` ${entry.title}` : "";
|
|
4909
|
+
console.log(`${entry.date} [${entry.id}]${title}${labels}`);
|
|
4910
|
+
console.log(` ${entry.body.split(`
|
|
4911
|
+
`)[0].slice(0, 120)}`);
|
|
4912
|
+
}
|
|
4913
|
+
}
|
|
4914
|
+
function printTasks(tasks) {
|
|
4915
|
+
if (!tasks.length) {
|
|
4916
|
+
console.log("no tasks");
|
|
4917
|
+
return;
|
|
4918
|
+
}
|
|
4919
|
+
for (const task of tasks) {
|
|
4920
|
+
const labels = task.labels.length ? ` #${task.labels.join(" #")}` : "";
|
|
4921
|
+
console.log(`[${task.id}] ${task.status} ${task.priority} ${task.title}${labels}`);
|
|
4922
|
+
}
|
|
4923
|
+
}
|
|
4924
|
+
function printGithubIssues(issues) {
|
|
4925
|
+
if (!issues.length) {
|
|
4926
|
+
console.log("no GitHub issues");
|
|
4927
|
+
return;
|
|
4928
|
+
}
|
|
4929
|
+
for (const issue of issues) {
|
|
4930
|
+
const labels = issue.labels.length ? ` #${issue.labels.join(" #")}` : "";
|
|
4931
|
+
const url = issue.url ? ` ${issue.url}` : "";
|
|
4932
|
+
console.log(`#${issue.number} ${issue.state} ${issue.title}${labels}${url}`);
|
|
4933
|
+
}
|
|
4934
|
+
}
|
|
4935
|
+
function taskLinkIssuePayload(command, issue) {
|
|
4936
|
+
return {
|
|
4937
|
+
action: "link-github-issue",
|
|
4938
|
+
issue_number: issue.number,
|
|
4939
|
+
repo: command.repo,
|
|
4940
|
+
title: issue.title,
|
|
4941
|
+
url: issue.url,
|
|
4942
|
+
memo_label: "Memo:",
|
|
4943
|
+
status: command.status,
|
|
4944
|
+
priority: command.priority,
|
|
4945
|
+
labels: command.labels,
|
|
4946
|
+
before_id: command.before,
|
|
4947
|
+
after_id: command.after,
|
|
4948
|
+
position: command.position
|
|
4949
|
+
};
|
|
4950
|
+
}
|
|
4951
|
+
function writePayload(payload) {
|
|
4952
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
4953
|
+
}
|
|
4954
|
+
async function getJournalData(serverUrl) {
|
|
4955
|
+
return await journalRequest(serverUrl, "GET", "journal list");
|
|
4956
|
+
}
|
|
4957
|
+
async function runJournalCli(argv) {
|
|
4958
|
+
const parsed = parseJournalArgs(argv);
|
|
4959
|
+
if (parsed.ok === false) {
|
|
4960
|
+
console.error(parsed.error);
|
|
4961
|
+
console.error('Run "code-viewer journal --help" for usage.');
|
|
4962
|
+
process.exit(1);
|
|
4963
|
+
}
|
|
4964
|
+
const { command, cwd, server, commandOverrides = [], dryRun } = parsed.args;
|
|
4965
|
+
if (command.kind === "help") {
|
|
4966
|
+
console.log(JOURNAL_HELP);
|
|
4967
|
+
return;
|
|
4968
|
+
}
|
|
4969
|
+
if (command.kind === "agent-help") {
|
|
4970
|
+
console.log(JOURNAL_AGENT_HELP);
|
|
4971
|
+
return;
|
|
4972
|
+
}
|
|
4973
|
+
const dryRunPayload = async (action, body) => {
|
|
4974
|
+
if (!dryRun)
|
|
4975
|
+
return false;
|
|
4976
|
+
writePayload({ action, ...body });
|
|
4977
|
+
return true;
|
|
4978
|
+
};
|
|
4979
|
+
if (dryRun) {
|
|
4980
|
+
if (command.kind === "add") {
|
|
4981
|
+
writePayload({
|
|
4982
|
+
action: "add-entry",
|
|
4983
|
+
date: command.date,
|
|
4984
|
+
title: command.title,
|
|
4985
|
+
labels: command.labels,
|
|
4986
|
+
body: await textFromBody(command, true)
|
|
4987
|
+
});
|
|
4988
|
+
return;
|
|
4989
|
+
}
|
|
4990
|
+
if (command.kind === "edit") {
|
|
4991
|
+
writePayload({
|
|
4992
|
+
action: "update-entry",
|
|
4993
|
+
id: command.id,
|
|
4994
|
+
date: command.date,
|
|
4995
|
+
title: command.title,
|
|
4996
|
+
labels: command.labels,
|
|
4997
|
+
body: await textFromBody(command, false)
|
|
4998
|
+
});
|
|
4999
|
+
return;
|
|
5000
|
+
}
|
|
5001
|
+
if (command.kind === "task-add") {
|
|
5002
|
+
writePayload({
|
|
5003
|
+
action: "add-task",
|
|
5004
|
+
title: command.title,
|
|
5005
|
+
status: command.status,
|
|
5006
|
+
priority: command.priority,
|
|
5007
|
+
labels: command.labels,
|
|
5008
|
+
due_date: command.dueDate === "" ? null : command.dueDate,
|
|
5009
|
+
source_date: command.sourceDate === "" ? null : command.sourceDate,
|
|
5010
|
+
before_id: command.before,
|
|
5011
|
+
after_id: command.after,
|
|
5012
|
+
position: command.position,
|
|
5013
|
+
body: await textFromBody(command, false)
|
|
5014
|
+
});
|
|
5015
|
+
return;
|
|
5016
|
+
}
|
|
5017
|
+
if (command.kind === "task-update") {
|
|
5018
|
+
writePayload({
|
|
5019
|
+
action: "update-task",
|
|
5020
|
+
id: command.id,
|
|
5021
|
+
title: command.title,
|
|
5022
|
+
status: command.status,
|
|
5023
|
+
priority: command.priority,
|
|
5024
|
+
labels: command.clearLabels ? [] : command.labels,
|
|
5025
|
+
due_date: command.dueDate === "" ? null : command.dueDate,
|
|
5026
|
+
source_date: command.sourceDate === "" ? null : command.sourceDate,
|
|
5027
|
+
body: await textFromBody(command, false)
|
|
5028
|
+
});
|
|
5029
|
+
return;
|
|
5030
|
+
}
|
|
5031
|
+
if (command.kind === "task-claim") {
|
|
5032
|
+
writePayload({
|
|
5033
|
+
action: "claim-task",
|
|
5034
|
+
id: command.id,
|
|
5035
|
+
by: command.by,
|
|
5036
|
+
lease_minutes: command.leaseMinutes,
|
|
5037
|
+
wip_limit: command.wipLimit
|
|
5038
|
+
});
|
|
5039
|
+
return;
|
|
5040
|
+
}
|
|
5041
|
+
if (command.kind === "task-done") {
|
|
5042
|
+
writePayload({
|
|
5043
|
+
action: "complete-task",
|
|
5044
|
+
id: command.id,
|
|
5045
|
+
by: command.by,
|
|
5046
|
+
note: textFromNote(command)
|
|
5047
|
+
});
|
|
5048
|
+
return;
|
|
5049
|
+
}
|
|
5050
|
+
if (command.kind === "task-delete") {
|
|
5051
|
+
writePayload({ action: "delete-task", id: command.id });
|
|
5052
|
+
return;
|
|
5053
|
+
}
|
|
5054
|
+
}
|
|
5055
|
+
const root = resolveRepoRoot(cwd);
|
|
5056
|
+
if (command.kind === "github-issues") {
|
|
5057
|
+
const commandConfig = configureExternalCommands({
|
|
5058
|
+
cwd: root,
|
|
5059
|
+
cliOverrides: commandOverrides,
|
|
5060
|
+
allowedNames: ["gh"]
|
|
5061
|
+
});
|
|
5062
|
+
if (commandConfig.ok === false) {
|
|
5063
|
+
console.error(commandConfig.error);
|
|
5064
|
+
process.exit(1);
|
|
5065
|
+
}
|
|
5066
|
+
const issues = readGithubIssueList({
|
|
5067
|
+
cwd: root,
|
|
5068
|
+
repo: command.repo,
|
|
5069
|
+
labels: command.ghLabels,
|
|
5070
|
+
search: command.search,
|
|
5071
|
+
state: command.ghState,
|
|
5072
|
+
limit: command.limit
|
|
5073
|
+
});
|
|
5074
|
+
if (command.json)
|
|
5075
|
+
console.log(JSON.stringify({ issues }, null, 2));
|
|
5076
|
+
else
|
|
5077
|
+
printGithubIssues(issues);
|
|
5078
|
+
return;
|
|
5079
|
+
}
|
|
5080
|
+
if (command.kind === "task-link-issue") {
|
|
5081
|
+
const commandConfig = configureExternalCommands({
|
|
5082
|
+
cwd: root,
|
|
5083
|
+
cliOverrides: commandOverrides,
|
|
5084
|
+
allowedNames: ["gh"]
|
|
5085
|
+
});
|
|
5086
|
+
if (commandConfig.ok === false) {
|
|
5087
|
+
console.error(commandConfig.error);
|
|
5088
|
+
process.exit(1);
|
|
5089
|
+
}
|
|
5090
|
+
const issue = readGithubIssue({
|
|
5091
|
+
cwd: root,
|
|
5092
|
+
number: command.issueNumber,
|
|
5093
|
+
repo: command.repo
|
|
5094
|
+
});
|
|
5095
|
+
if (dryRun) {
|
|
5096
|
+
writePayload(taskLinkIssuePayload(command, issue));
|
|
5097
|
+
return;
|
|
5098
|
+
}
|
|
5099
|
+
const serverUrl2 = await ensureServerUrl(root, server, "/_journal");
|
|
5100
|
+
const result2 = await journalRequest(serverUrl2, "POST", "journal task-link-issue", taskLinkIssuePayload(command, issue));
|
|
5101
|
+
if (command.json)
|
|
5102
|
+
console.log(JSON.stringify({
|
|
5103
|
+
issue,
|
|
5104
|
+
task: result2.task,
|
|
5105
|
+
action: result2.created ? "created" : result2.moved ? "moved" : "existing"
|
|
5106
|
+
}, null, 2));
|
|
5107
|
+
else if (result2.created)
|
|
5108
|
+
console.log(`linked issue #${issue.number} to task ${result2.task.id}`);
|
|
5109
|
+
else if (result2.moved)
|
|
5110
|
+
console.log(`moved linked issue #${issue.number} task ${result2.task.id}`);
|
|
5111
|
+
else
|
|
5112
|
+
console.log(`issue #${issue.number} is linked to task ${result2.task.id}`);
|
|
5113
|
+
return;
|
|
5114
|
+
}
|
|
5115
|
+
const serverUrl = await ensureServerUrl(root, server, "/_journal");
|
|
5116
|
+
if (command.kind === "list") {
|
|
5117
|
+
const data = await getJournalData(serverUrl);
|
|
5118
|
+
const entries = selectEntries(data, command.date, command.limit);
|
|
5119
|
+
if (command.json)
|
|
5120
|
+
console.log(JSON.stringify({ version: data.journal.version, entries }, null, 2));
|
|
5121
|
+
else
|
|
5122
|
+
printEntries(data, command.date, command.limit);
|
|
5123
|
+
return;
|
|
5124
|
+
}
|
|
5125
|
+
if (command.kind === "add") {
|
|
5126
|
+
const body = await textFromBody(command, true);
|
|
5127
|
+
const result2 = await journalRequest(serverUrl, "POST", "journal add", {
|
|
5128
|
+
action: "add-entry",
|
|
5129
|
+
date: command.date,
|
|
5130
|
+
title: command.title,
|
|
5131
|
+
labels: command.labels,
|
|
5132
|
+
body
|
|
5133
|
+
});
|
|
5134
|
+
console.log(`added journal entry ${result2.entry.id} for ${result2.entry.date}`);
|
|
5135
|
+
return;
|
|
5136
|
+
}
|
|
5137
|
+
if (command.kind === "edit") {
|
|
5138
|
+
const body = await textFromBody(command, false);
|
|
5139
|
+
const payload2 = {
|
|
5140
|
+
action: "update-entry",
|
|
5141
|
+
id: command.id,
|
|
5142
|
+
date: command.date,
|
|
5143
|
+
title: command.title,
|
|
5144
|
+
labels: command.labels,
|
|
5145
|
+
body
|
|
5146
|
+
};
|
|
5147
|
+
if (await dryRunPayload("update-entry", payload2))
|
|
5148
|
+
return;
|
|
5149
|
+
await journalRequest(serverUrl, "POST", "journal edit", payload2);
|
|
5150
|
+
console.log(`updated journal entry ${command.id}`);
|
|
5151
|
+
return;
|
|
5152
|
+
}
|
|
5153
|
+
if (command.kind === "tasks") {
|
|
5154
|
+
const data = await getJournalData(serverUrl);
|
|
5155
|
+
const tasks = filterJournalTasks(data.tasks, {
|
|
5156
|
+
status: command.status,
|
|
5157
|
+
labels: command.labels,
|
|
5158
|
+
includeClaimed: true
|
|
5159
|
+
});
|
|
5160
|
+
if (command.json)
|
|
5161
|
+
console.log(JSON.stringify({ version: 1, tasks }, null, 2));
|
|
5162
|
+
else
|
|
5163
|
+
printTasks(tasks);
|
|
5164
|
+
return;
|
|
5165
|
+
}
|
|
5166
|
+
if (command.kind === "task-add") {
|
|
5167
|
+
const body = await textFromBody(command, false);
|
|
5168
|
+
const payload2 = {
|
|
5169
|
+
action: "add-task",
|
|
5170
|
+
title: command.title,
|
|
5171
|
+
status: command.status,
|
|
5172
|
+
priority: command.priority,
|
|
5173
|
+
labels: command.labels,
|
|
5174
|
+
due_date: command.dueDate === "" ? null : command.dueDate,
|
|
5175
|
+
source_date: command.sourceDate === "" ? null : command.sourceDate,
|
|
5176
|
+
before_id: command.before,
|
|
5177
|
+
after_id: command.after,
|
|
5178
|
+
position: command.position,
|
|
5179
|
+
body
|
|
5180
|
+
};
|
|
5181
|
+
if (await dryRunPayload("add-task", payload2))
|
|
5182
|
+
return;
|
|
5183
|
+
const result2 = await journalRequest(serverUrl, "POST", "journal task-add", payload2);
|
|
5184
|
+
console.log(`added task ${result2.task.id} ${result2.task.title}`);
|
|
5185
|
+
return;
|
|
5186
|
+
}
|
|
5187
|
+
if (command.kind === "task-update") {
|
|
5188
|
+
const body = await textFromBody(command, false);
|
|
5189
|
+
const payload2 = {
|
|
5190
|
+
action: "update-task",
|
|
5191
|
+
id: command.id,
|
|
5192
|
+
title: command.title,
|
|
5193
|
+
status: command.status,
|
|
5194
|
+
priority: command.priority,
|
|
5195
|
+
labels: command.clearLabels ? [] : command.labels,
|
|
5196
|
+
due_date: command.dueDate === "" ? null : command.dueDate,
|
|
5197
|
+
source_date: command.sourceDate === "" ? null : command.sourceDate,
|
|
5198
|
+
body
|
|
5199
|
+
};
|
|
5200
|
+
if (await dryRunPayload("update-task", payload2))
|
|
5201
|
+
return;
|
|
5202
|
+
await journalRequest(serverUrl, "POST", "journal task-update", payload2);
|
|
5203
|
+
console.log(`updated task ${command.id}`);
|
|
5204
|
+
return;
|
|
5205
|
+
}
|
|
5206
|
+
if (command.kind === "task-next") {
|
|
5207
|
+
const data = await getJournalData(serverUrl);
|
|
5208
|
+
const tasks = selectNextJournalTasks(data.tasks, {
|
|
5209
|
+
status: command.status || "todo",
|
|
5210
|
+
labels: command.labels
|
|
5211
|
+
}, command.limit);
|
|
5212
|
+
if (command.json)
|
|
5213
|
+
console.log(JSON.stringify({ tasks }, null, 2));
|
|
5214
|
+
else
|
|
5215
|
+
printTasks(tasks);
|
|
5216
|
+
return;
|
|
5217
|
+
}
|
|
5218
|
+
if (command.kind === "task-claim") {
|
|
5219
|
+
const payload2 = {
|
|
5220
|
+
action: "claim-task",
|
|
5221
|
+
id: command.id,
|
|
5222
|
+
by: command.by,
|
|
5223
|
+
lease_minutes: command.leaseMinutes,
|
|
5224
|
+
wip_limit: command.wipLimit
|
|
5225
|
+
};
|
|
5226
|
+
if (await dryRunPayload("claim-task", payload2))
|
|
5227
|
+
return;
|
|
5228
|
+
const result2 = await journalRequest(serverUrl, "POST", "journal task-claim", payload2);
|
|
5229
|
+
console.log(`claimed task ${result2.task.id} until ${result2.task.claim?.lease_expires_at}`);
|
|
5230
|
+
return;
|
|
5231
|
+
}
|
|
5232
|
+
if (command.kind === "task-done") {
|
|
5233
|
+
const payload2 = {
|
|
5234
|
+
action: "complete-task",
|
|
5235
|
+
id: command.id,
|
|
5236
|
+
by: command.by,
|
|
5237
|
+
note: textFromNote(command)
|
|
5238
|
+
};
|
|
5239
|
+
if (await dryRunPayload("complete-task", payload2))
|
|
5240
|
+
return;
|
|
5241
|
+
await journalRequest(serverUrl, "POST", "journal task-done", payload2);
|
|
5242
|
+
console.log(`completed task ${command.id}`);
|
|
5243
|
+
return;
|
|
5244
|
+
}
|
|
5245
|
+
const payload = { action: "delete-task", id: command.id };
|
|
5246
|
+
if (await dryRunPayload("delete-task", payload))
|
|
5247
|
+
return;
|
|
5248
|
+
const result = await journalRequest(serverUrl, "POST", "journal task-delete", payload);
|
|
5249
|
+
if (!result.removed) {
|
|
5250
|
+
console.error(`task not found: ${command.id}`);
|
|
5251
|
+
process.exit(1);
|
|
5252
|
+
}
|
|
5253
|
+
console.log(`deleted task ${command.id}`);
|
|
5254
|
+
}
|
|
5255
|
+
var JOURNAL_HELP = `code-viewer journal — daily work journal and task queue
|
|
5256
|
+
|
|
5257
|
+
The journal is stored in <repo>/.code-viewer/daily-journal.json and tasks are
|
|
5258
|
+
stored in <repo>/.code-viewer/tasks.json. A running code-viewer server for the
|
|
5259
|
+
repository is required unless you pass --dry-run for a write command.
|
|
5260
|
+
The github-issues command is read-only and runs gh directly without a server.
|
|
4049
5261
|
|
|
4050
|
-
|
|
4051
|
-
code-viewer file blame --path src/sample.ts --base HEAD --json
|
|
4052
|
-
code-viewer file history --path src/sample.ts --limit 10 --json
|
|
4053
|
-
code-viewer file history --path src/sample.ts --query "author:tester" --json
|
|
4054
|
-
code-viewer file show --path src/sample.ts --json
|
|
4055
|
-
code-viewer file show --path src/sample.ts --start 100 --end 150 --json
|
|
4056
|
-
code-viewer file show --path src/sample.ts --ref main --json
|
|
4057
|
-
code-viewer file diff --path src/sample.ts --json
|
|
4058
|
-
code-viewer file diff --path src/sample.ts --from main --to HEAD --json
|
|
4059
|
-
code-viewer file diff --path src/sample.ts --full --json
|
|
4060
|
-
code-viewer file diff --path new_sample.ts --untracked --json
|
|
4061
|
-
code-viewer file diff --path renamed.ts --old-path original.ts --from HEAD~1 --to HEAD --json
|
|
5262
|
+
Run "code-viewer journal agent-help" for an AI-agent oriented guide.
|
|
4062
5263
|
|
|
4063
|
-
|
|
5264
|
+
Usage:
|
|
5265
|
+
code-viewer journal list [--date <YYYY-MM-DD|today>] [--limit <n>] [--json]
|
|
5266
|
+
code-viewer journal add --date <YYYY-MM-DD|today> [--title <text>]
|
|
5267
|
+
[--label <label>...] [--body <markdown> | --body-file <path>]
|
|
5268
|
+
code-viewer journal edit <id> [--date <YYYY-MM-DD|today>] [--title <text>]
|
|
5269
|
+
[--label <label>...] [--body <markdown> | --body-file <path>]
|
|
5270
|
+
code-viewer journal tasks [--status <draft|todo|doing|blocked|done>]
|
|
5271
|
+
[--label <label>...] [--json]
|
|
5272
|
+
code-viewer journal task-add --title <text>
|
|
5273
|
+
[--status <status>] [--priority <p0|p1|p2|p3>] [--label <label>...]
|
|
5274
|
+
[--due <YYYY-MM-DD|none>] [--source-date <YYYY-MM-DD|today|none>]
|
|
5275
|
+
[--before <id> | --after <id> | --position <n>]
|
|
5276
|
+
[--body <markdown> | --body-file <path>]
|
|
5277
|
+
code-viewer journal task-update <id> [--title <text>] [--status <status>]
|
|
5278
|
+
[--priority <p0|p1|p2|p3>] [--label <label>...] [--clear-labels]
|
|
5279
|
+
[--due <YYYY-MM-DD|none>] [--source-date <YYYY-MM-DD|today|none>]
|
|
5280
|
+
[--body <markdown> | --body-file <path>]
|
|
5281
|
+
code-viewer journal task-next [--label <label>...] [--status <status>]
|
|
5282
|
+
[--limit <n>] [--json]
|
|
5283
|
+
code-viewer journal github-issues [--repo <owner/repo>]
|
|
5284
|
+
[--state <open|closed|all>] [--gh-label <label>...]
|
|
5285
|
+
[--search <query>] [--limit <n>] [--json] [--bin gh=<path>]
|
|
5286
|
+
code-viewer journal task-link-issue <number> [--repo <owner/repo>]
|
|
5287
|
+
[--status <status>] [--priority <p0|p1|p2|p3>] [--label <label>...]
|
|
5288
|
+
[--before <id> | --after <id> | --position <n>] [--json]
|
|
5289
|
+
[--dry-run] [--bin gh=<path>]
|
|
5290
|
+
code-viewer journal task-claim <id> [--by <agent>] [--lease-minutes <n>]
|
|
5291
|
+
[--wip-limit <n>]
|
|
5292
|
+
code-viewer journal task-done <id> --by <agent>
|
|
5293
|
+
[--note <markdown> | --note-file <path>]
|
|
5294
|
+
code-viewer journal task-delete <id>
|
|
4064
5295
|
|
|
4065
|
-
|
|
5296
|
+
Global options:
|
|
5297
|
+
--cwd <dir> repository root (default: current directory)
|
|
5298
|
+
--server <url> code-viewer server URL (default: auto-discovered)
|
|
5299
|
+
--bin gh=<p> override gh executable path for github-issues
|
|
5300
|
+
--dry-run print the write payload without sending it
|
|
5301
|
+
`, JOURNAL_AGENT_HELP = `code-viewer journal — agent guide
|
|
4066
5302
|
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
"path": string,
|
|
4070
|
-
"ref": string, // the literal ref the CLI resolved to
|
|
4071
|
-
"base": "worktree"|"HEAD",
|
|
4072
|
-
"result": GitBlameResult // { lines, commits, isUntracked?, isSynthetic?, error? }
|
|
4073
|
-
}
|
|
4074
|
-
history:
|
|
4075
|
-
{
|
|
4076
|
-
"path": string,
|
|
4077
|
-
"ref": string,
|
|
4078
|
-
"limit": number,
|
|
4079
|
-
"skip": number,
|
|
4080
|
-
"query"?: string,
|
|
4081
|
-
"result": { commits: GitHistoryCommit[], hasMore: boolean, error?: string }
|
|
4082
|
-
}
|
|
4083
|
-
show:
|
|
4084
|
-
{
|
|
4085
|
-
"path": string,
|
|
4086
|
-
"ref": string,
|
|
4087
|
-
"start"?: number,
|
|
4088
|
-
"end"?: number,
|
|
4089
|
-
"totalLines": number, // total lines in the file at <ref>
|
|
4090
|
-
"complete": boolean, // true when the returned text covers the whole file
|
|
4091
|
-
"text": string
|
|
4092
|
-
}
|
|
4093
|
-
diff:
|
|
4094
|
-
{
|
|
4095
|
-
"path": string,
|
|
4096
|
-
"old_path"?: string,
|
|
4097
|
-
"from": string, // "/dev/null" when untracked is true
|
|
4098
|
-
"to": string,
|
|
4099
|
-
"untracked": boolean,
|
|
4100
|
-
"ignore_ws": boolean,
|
|
4101
|
-
"ignore_blank": boolean,
|
|
4102
|
-
"mode": "preview"|"full",
|
|
4103
|
-
"max_hunks": number|null, // null in full mode
|
|
4104
|
-
"max_lines": number|null, // null in full mode
|
|
4105
|
-
"diff": string, // unified diff text
|
|
4106
|
-
"hunk_count": number, // total hunks before truncation
|
|
4107
|
-
"rendered_hunk_count": number, // hunks actually included in diff
|
|
4108
|
-
"line_count": number, // total newlines in the returned diff
|
|
4109
|
-
"truncated": boolean, // true when preview dropped hunks/lines
|
|
4110
|
-
"binary": boolean, // git emitted "Binary files ..."
|
|
4111
|
-
"error"?: string // git stderr (also exits 1)
|
|
4112
|
-
}
|
|
5303
|
+
You are an AI coding agent. Use this tool to write daily work notes and process
|
|
5304
|
+
explicit task queues without guessing from memory.
|
|
4113
5305
|
|
|
4114
|
-
|
|
5306
|
+
## Workflow
|
|
4115
5307
|
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
5308
|
+
1. Check the queue before doing label-scoped work:
|
|
5309
|
+
code-viewer journal task-next --label ai-ready --limit 5 --json
|
|
5310
|
+
2. Claim exactly one task before editing code:
|
|
5311
|
+
code-viewer journal task-claim <task-id> --by agent --wip-limit 1
|
|
5312
|
+
3. When finished, mark it done with a short note:
|
|
5313
|
+
code-viewer journal task-done <task-id> --by agent --note "Implemented and verified."
|
|
5314
|
+
4. Add a daily journal entry for work that is not already represented by a task:
|
|
5315
|
+
code-viewer journal add --date today --label ai --body "..."
|
|
5316
|
+
5. Inspect GitHub issues read-only before deciding what local task to create:
|
|
5317
|
+
code-viewer journal github-issues --repo owner/repo --json
|
|
5318
|
+
6. Link a GitHub issue to the local board without updating GitHub:
|
|
5319
|
+
code-viewer journal task-link-issue 123 --repo owner/repo --status draft --label ai-ready
|
|
4123
5320
|
|
|
4124
|
-
##
|
|
5321
|
+
## Rules
|
|
4125
5322
|
|
|
4126
|
-
-
|
|
4127
|
-
|
|
4128
|
-
-
|
|
4129
|
-
|
|
4130
|
-
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
error string inside the JSON "result.error" / "error" field AND exits 1.
|
|
4134
|
-
- file diff defaults to preview mode (${FILE_DIFF_DEFAULT_MAX_HUNKS} hunks /
|
|
4135
|
-
${FILE_DIFF_DEFAULT_MAX_LINES} lines). Use --full only when you need every
|
|
4136
|
-
hunk — preview matches the browser's initial paint and is almost always
|
|
4137
|
-
enough context for an LLM. In full mode, max_hunks / max_lines are null in
|
|
4138
|
-
JSON because no preview cap is applied. Worktree == Worktree range returns
|
|
4139
|
-
"" instantly.
|
|
4140
|
-
- Use "code-viewer search code" to discover the path first, then drill
|
|
4141
|
-
in with blame / history / show / diff.
|
|
5323
|
+
- Treat labels as filters, priority as importance, and card order as human order.
|
|
5324
|
+
- Do not process draft tasks unless the human asked for drafts.
|
|
5325
|
+
- Use task-next for ordering; do not sort the JSON yourself unless asked.
|
|
5326
|
+
- Use --dry-run before large generated entries.
|
|
5327
|
+
- GitHub issue listing is read-only. Create or update local tasks explicitly.
|
|
5328
|
+
- task-link-issue reads issue metadata only and stores a local task link plus
|
|
5329
|
+
your local labels. It does not copy the issue body or update GitHub.
|
|
4142
5330
|
`;
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
"--skip",
|
|
4149
|
-
"--query",
|
|
4150
|
-
"--start",
|
|
4151
|
-
"--end",
|
|
4152
|
-
"--from",
|
|
4153
|
-
"--to",
|
|
4154
|
-
"--old-path",
|
|
4155
|
-
"--max-hunks",
|
|
4156
|
-
"--max-lines"
|
|
4157
|
-
]);
|
|
4158
|
-
BOOL_FLAGS = new Set([
|
|
4159
|
-
"--json",
|
|
4160
|
-
"--untracked",
|
|
4161
|
-
"--ignore-ws",
|
|
4162
|
-
"--ignore-blank",
|
|
4163
|
-
"--full"
|
|
4164
|
-
]);
|
|
5331
|
+
var init_journal_cli = __esm(() => {
|
|
5332
|
+
init_journal();
|
|
5333
|
+
init_cli_helpers();
|
|
5334
|
+
init_command_resolver();
|
|
5335
|
+
init_github_issues();
|
|
4165
5336
|
});
|
|
4166
5337
|
|
|
4167
5338
|
// web-src/core/routes.ts
|
|
@@ -4214,6 +5385,19 @@ function buildRoute(route) {
|
|
|
4214
5385
|
const qs = params.toString();
|
|
4215
5386
|
return `/history${qs ? `?${qs}` : ""}`;
|
|
4216
5387
|
}
|
|
5388
|
+
case "journal": {
|
|
5389
|
+
const params = new URLSearchParams;
|
|
5390
|
+
if (route.tab && route.tab !== "journal")
|
|
5391
|
+
params.set("tab", route.tab);
|
|
5392
|
+
if (route.date)
|
|
5393
|
+
params.set("date", route.date);
|
|
5394
|
+
if (route.label)
|
|
5395
|
+
params.set("label", route.label);
|
|
5396
|
+
if (route.task)
|
|
5397
|
+
params.set("task", route.task);
|
|
5398
|
+
const qs = params.toString();
|
|
5399
|
+
return `/journal${qs ? `?${qs}` : ""}`;
|
|
5400
|
+
}
|
|
4217
5401
|
case "database": {
|
|
4218
5402
|
const params = new URLSearchParams;
|
|
4219
5403
|
if (route.db)
|
|
@@ -4245,6 +5429,7 @@ var init_routes = __esm(() => {
|
|
|
4245
5429
|
"/file",
|
|
4246
5430
|
"/help",
|
|
4247
5431
|
"/history",
|
|
5432
|
+
"/journal",
|
|
4248
5433
|
"/database",
|
|
4249
5434
|
"/doctor"
|
|
4250
5435
|
];
|
|
@@ -7494,9 +8679,10 @@ missing bundled skills).
|
|
|
7494
8679
|
## What gets installed
|
|
7495
8680
|
|
|
7496
8681
|
The bundled set covers code-viewer's own AI workflows (annotation
|
|
7497
|
-
walkthroughs, doctor introspection,
|
|
7498
|
-
install, ask the AI to use
|
|
7499
|
-
|
|
8682
|
+
walkthroughs, Work Log task queues, doctor introspection, and
|
|
8683
|
+
query/snapshot inspection). After install, ask the AI to use
|
|
8684
|
+
\`code-viewer annotate\`, \`code-viewer journal\`, or the related
|
|
8685
|
+
commands and the skills will guide it.
|
|
7500
8686
|
`;
|
|
7501
8687
|
var init_skill_cli = __esm(() => {
|
|
7502
8688
|
init_root();
|
|
@@ -7954,6 +9140,7 @@ var AGENT_GUIDES;
|
|
|
7954
9140
|
var init_agent_help = __esm(() => {
|
|
7955
9141
|
init_annotate_cli();
|
|
7956
9142
|
init_file_cli();
|
|
9143
|
+
init_journal_cli();
|
|
7957
9144
|
init_query_cli();
|
|
7958
9145
|
init_search_cli();
|
|
7959
9146
|
init_skill_cli();
|
|
@@ -7974,6 +9161,11 @@ var init_agent_help = __esm(() => {
|
|
|
7974
9161
|
signature: firstLine(ANNOTATE_AGENT_HELP),
|
|
7975
9162
|
rerun: "code-viewer annotate agent-help"
|
|
7976
9163
|
},
|
|
9164
|
+
{
|
|
9165
|
+
name: "journal",
|
|
9166
|
+
signature: firstLine(JOURNAL_AGENT_HELP),
|
|
9167
|
+
rerun: "code-viewer journal agent-help"
|
|
9168
|
+
},
|
|
7977
9169
|
{
|
|
7978
9170
|
name: "search",
|
|
7979
9171
|
signature: firstLine(SEARCH_AGENT_HELP),
|
|
@@ -10597,9 +11789,22 @@ var init_source_meta = __esm(() => {
|
|
|
10597
11789
|
hcl: "terraform",
|
|
10598
11790
|
xml: "xml",
|
|
10599
11791
|
html: "xml",
|
|
10600
|
-
vue: "
|
|
11792
|
+
vue: "vue",
|
|
11793
|
+
svelte: "svelte",
|
|
11794
|
+
astro: "astro",
|
|
11795
|
+
erb: "erb",
|
|
11796
|
+
rhtml: "erb",
|
|
11797
|
+
ejs: "html",
|
|
11798
|
+
hbs: "handlebars",
|
|
11799
|
+
mustache: "handlebars",
|
|
11800
|
+
liquid: "liquid",
|
|
11801
|
+
pug: "pug",
|
|
11802
|
+
twig: "twig",
|
|
11803
|
+
haml: "haml",
|
|
10601
11804
|
css: "css",
|
|
10602
11805
|
scss: "scss",
|
|
11806
|
+
sass: "sass",
|
|
11807
|
+
less: "less",
|
|
10603
11808
|
md: "markdown",
|
|
10604
11809
|
dockerfile: "dockerfile",
|
|
10605
11810
|
proto: "protobuf",
|
|
@@ -10642,7 +11847,26 @@ var init_source_meta = __esm(() => {
|
|
|
10642
11847
|
mm: "objective-cpp",
|
|
10643
11848
|
tex: "tex",
|
|
10644
11849
|
bib: "bibtex",
|
|
10645
|
-
rst: "rst"
|
|
11850
|
+
rst: "rst",
|
|
11851
|
+
graphql: "graphql",
|
|
11852
|
+
graphqls: "graphql",
|
|
11853
|
+
gql: "graphql",
|
|
11854
|
+
ps1: "powershell",
|
|
11855
|
+
psm1: "powershell",
|
|
11856
|
+
psd1: "powershell",
|
|
11857
|
+
ini: "ini",
|
|
11858
|
+
conf: "ini",
|
|
11859
|
+
env: "dotenv",
|
|
11860
|
+
prisma: "prisma",
|
|
11861
|
+
pas: "pascal",
|
|
11862
|
+
adoc: "asciidoc",
|
|
11863
|
+
asciidoc: "asciidoc",
|
|
11864
|
+
jsonc: "jsonc",
|
|
11865
|
+
mts: "typescript",
|
|
11866
|
+
cts: "typescript",
|
|
11867
|
+
kts: "kotlin",
|
|
11868
|
+
cxx: "cpp",
|
|
11869
|
+
hxx: "cpp"
|
|
10646
11870
|
};
|
|
10647
11871
|
TEXT_SOURCE_EXTENSIONS = new Set([
|
|
10648
11872
|
...Object.keys(EXT_TO_LANG),
|
|
@@ -10794,6 +12018,12 @@ var init_source_meta = __esm(() => {
|
|
|
10794
12018
|
"rakefile",
|
|
10795
12019
|
"procfile",
|
|
10796
12020
|
"brewfile",
|
|
12021
|
+
"guardfile",
|
|
12022
|
+
"capfile",
|
|
12023
|
+
"vagrantfile",
|
|
12024
|
+
"podfile",
|
|
12025
|
+
"fastfile",
|
|
12026
|
+
"berksfile",
|
|
10797
12027
|
"gnumakefile",
|
|
10798
12028
|
"bsdmakefile",
|
|
10799
12029
|
".gitattributes",
|
|
@@ -17135,7 +18365,7 @@ var init_handle = __esm(() => {
|
|
|
17135
18365
|
});
|
|
17136
18366
|
|
|
17137
18367
|
// web-src/server/doctor.ts
|
|
17138
|
-
import { accessSync as accessSync2, constants as constants2, readFileSync as
|
|
18368
|
+
import { accessSync as accessSync2, constants as constants2, readFileSync as readFileSync6, statSync as statSync5 } from "node:fs";
|
|
17139
18369
|
import { dirname as dirname4, join as join14, relative as relative5 } from "node:path";
|
|
17140
18370
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
17141
18371
|
function statusWorse(a, b) {
|
|
@@ -17232,7 +18462,7 @@ function findCodeViewerPackageJson() {
|
|
|
17232
18462
|
for (let depth = 0;depth < 8; depth += 1) {
|
|
17233
18463
|
const candidate = join14(cursor, "package.json");
|
|
17234
18464
|
try {
|
|
17235
|
-
const raw =
|
|
18465
|
+
const raw = readFileSync6(candidate, "utf8");
|
|
17236
18466
|
const pkg = JSON.parse(raw);
|
|
17237
18467
|
if (pkg.name === "@youtyan/code-viewer") {
|
|
17238
18468
|
return { version: pkg.version, path: candidate };
|
|
@@ -17439,6 +18669,36 @@ async function checkGit(cwd, signal) {
|
|
|
17439
18669
|
}
|
|
17440
18670
|
return { id: "git", title: "Git", rows };
|
|
17441
18671
|
}
|
|
18672
|
+
async function checkGithubCli(signal) {
|
|
18673
|
+
const versionRes = await runCached(versionCache, TTL.version, commandForExternal("gh"), ["--version"], TIMEOUT.version, signal);
|
|
18674
|
+
if (!versionRes || versionRes.code !== 0) {
|
|
18675
|
+
return {
|
|
18676
|
+
id: "github",
|
|
18677
|
+
title: "GitHub CLI",
|
|
18678
|
+
rows: [
|
|
18679
|
+
{
|
|
18680
|
+
id: "github.gh",
|
|
18681
|
+
title: "gh binary",
|
|
18682
|
+
status: "warn",
|
|
18683
|
+
detail: versionRes && isCommandNotFoundResult("gh", versionRes) ? commandNotFoundDetail("gh") : "gh --version failed",
|
|
18684
|
+
hint: "GitHub issue listing and issue-to-task linking require gh. Install GitHub CLI or pass --bin gh=/absolute/path."
|
|
18685
|
+
}
|
|
18686
|
+
]
|
|
18687
|
+
};
|
|
18688
|
+
}
|
|
18689
|
+
return {
|
|
18690
|
+
id: "github",
|
|
18691
|
+
title: "GitHub CLI",
|
|
18692
|
+
rows: [
|
|
18693
|
+
{
|
|
18694
|
+
id: "github.gh",
|
|
18695
|
+
title: "gh binary",
|
|
18696
|
+
status: "ok",
|
|
18697
|
+
detail: firstLine2(versionRes.stdout)
|
|
18698
|
+
}
|
|
18699
|
+
]
|
|
18700
|
+
};
|
|
18701
|
+
}
|
|
17442
18702
|
async function detectComposeBinary(signal) {
|
|
17443
18703
|
const v2 = await runCached(versionCache, TTL.version, commandForExternal("docker"), ["compose", "version", "--short"], TIMEOUT.version, signal);
|
|
17444
18704
|
if (v2 && v2.code === 0) {
|
|
@@ -18020,6 +19280,7 @@ async function buildDoctorReport(ctx) {
|
|
|
18020
19280
|
const sqlite = await checkSqlite(ctx.cwd);
|
|
18021
19281
|
const snapshot = checkSnapshotStore(ctx.cwd);
|
|
18022
19282
|
const git = await checkGit(ctx.cwd, ctx.signal);
|
|
19283
|
+
const github = await checkGithubCli(ctx.signal);
|
|
18023
19284
|
const discovery = await checkDiscovery(ctx.cwd, ctx.scopeOmitDirNames, ctx.signal);
|
|
18024
19285
|
const docker = await checkDocker(ctx.signal, discovery.dockerResult);
|
|
18025
19286
|
const datastore = await checkDatastoreConnectivity(ctx.cwd, ctx.scopeOmitDirNames, ctx.signal);
|
|
@@ -18030,6 +19291,7 @@ async function buildDoctorReport(ctx) {
|
|
|
18030
19291
|
sqlite,
|
|
18031
19292
|
snapshot,
|
|
18032
19293
|
git,
|
|
19294
|
+
github,
|
|
18033
19295
|
discovery.group,
|
|
18034
19296
|
datastore,
|
|
18035
19297
|
docker,
|
|
@@ -18139,7 +19401,8 @@ function parseDoctorCliArgs(argv) {
|
|
|
18139
19401
|
}
|
|
18140
19402
|
const parsed = parseExternalCommandOverride(next, "--bin", [
|
|
18141
19403
|
"git",
|
|
18142
|
-
"docker"
|
|
19404
|
+
"docker",
|
|
19405
|
+
"gh"
|
|
18143
19406
|
]);
|
|
18144
19407
|
if (parsed.ok === false)
|
|
18145
19408
|
return { kind: "error", message: parsed.error };
|
|
@@ -18225,7 +19488,7 @@ async function runDoctorCli(argv) {
|
|
|
18225
19488
|
const commandConfig = configureExternalCommands({
|
|
18226
19489
|
cwd,
|
|
18227
19490
|
cliOverrides: commandOverrides,
|
|
18228
|
-
allowedNames: ["git", "docker"]
|
|
19491
|
+
allowedNames: ["git", "docker", "gh"]
|
|
18229
19492
|
});
|
|
18230
19493
|
if (commandConfig.ok === false) {
|
|
18231
19494
|
process.stderr.write(`code-viewer doctor: ${commandConfig.error}
|
|
@@ -18251,13 +19514,13 @@ async function runDoctorCli(argv) {
|
|
|
18251
19514
|
var DOCTOR_HELP = `code-viewer doctor — diagnose the current environment
|
|
18252
19515
|
|
|
18253
19516
|
Usage:
|
|
18254
|
-
code-viewer doctor [--cwd <path>] [--port <N>] [--json] [--bin <git|docker>=<path>]
|
|
19517
|
+
code-viewer doctor [--cwd <path>] [--port <N>] [--json] [--bin <git|docker|gh>=<path>]
|
|
18255
19518
|
code-viewer doctor agent-help
|
|
18256
19519
|
|
|
18257
19520
|
Options:
|
|
18258
19521
|
--cwd <path> Working directory to inspect (default: process.cwd()).
|
|
18259
19522
|
--port <N> Listening port to mention in the report (default: 0 = no server).
|
|
18260
|
-
--bin <n>=<p> Override git/docker executable path. Repeatable.
|
|
19523
|
+
--bin <n>=<p> Override git/docker/gh executable path. Repeatable.
|
|
18261
19524
|
--json Print the full DoctorReport as JSON instead of a summary.
|
|
18262
19525
|
--help, -h Show this help.
|
|
18263
19526
|
|
|
@@ -18302,65 +19565,702 @@ import { join as join15 } from "node:path";
|
|
|
18302
19565
|
function cacheFresh(cached, now = Date.now(), ttlMs = CACHE_TTL_MS) {
|
|
18303
19566
|
return !!cached && now - cached.storedAt <= ttlMs;
|
|
18304
19567
|
}
|
|
18305
|
-
function setTimedCacheEntry(cache, key, value, now = Date.now(), maxEntries = MAX_TIMED_CACHE_ENTRIES) {
|
|
18306
|
-
cache.set(key, { ...value, storedAt: now });
|
|
18307
|
-
while (cache.size > maxEntries) {
|
|
18308
|
-
const oldest = cache.keys().next().value;
|
|
18309
|
-
if (oldest === undefined)
|
|
19568
|
+
function setTimedCacheEntry(cache, key, value, now = Date.now(), maxEntries = MAX_TIMED_CACHE_ENTRIES) {
|
|
19569
|
+
cache.set(key, { ...value, storedAt: now });
|
|
19570
|
+
while (cache.size > maxEntries) {
|
|
19571
|
+
const oldest = cache.keys().next().value;
|
|
19572
|
+
if (oldest === undefined)
|
|
19573
|
+
break;
|
|
19574
|
+
cache.delete(oldest);
|
|
19575
|
+
}
|
|
19576
|
+
}
|
|
19577
|
+
function worktreeFileSignature(path, cwd) {
|
|
19578
|
+
try {
|
|
19579
|
+
const stats = lstatSync3(join15(cwd, path));
|
|
19580
|
+
const inode = "ino" in stats ? stats.ino : 0;
|
|
19581
|
+
return `state:file|size:${stats.size}|mtime:${stats.mtimeMs}|ctime:${stats.ctimeMs}|ino:${inode}`;
|
|
19582
|
+
} catch {
|
|
19583
|
+
return "state:missing";
|
|
19584
|
+
}
|
|
19585
|
+
}
|
|
19586
|
+
function fileDiffCacheKey(options) {
|
|
19587
|
+
const worktreeTarget = options.range.from === "worktree" || !options.range.to || options.range.to === "worktree";
|
|
19588
|
+
if (options.isUntracked && !worktreeTarget) {
|
|
19589
|
+
throw new Error("untracked file diffs require a worktree range");
|
|
19590
|
+
}
|
|
19591
|
+
const signature = worktreeTarget ? `\x00${worktreeFileSignature(options.path, options.cwd)}` : "";
|
|
19592
|
+
if (options.isUntracked) {
|
|
19593
|
+
return `u\x00${options.path}${signature}\x00${options.extras.join("\x00")}`;
|
|
19594
|
+
}
|
|
19595
|
+
return `t\x00${options.path}\x00${options.oldPath || ""}${signature}\x00${[...options.extras, ...options.args].join("\x00")}`;
|
|
19596
|
+
}
|
|
19597
|
+
var CACHE_TTL_MS = 1500, MAX_TIMED_CACHE_ENTRIES = 200;
|
|
19598
|
+
var init_cache = () => {};
|
|
19599
|
+
|
|
19600
|
+
// web-src/server/dev-assets.ts
|
|
19601
|
+
import { basename as basename2 } from "node:path";
|
|
19602
|
+
function startDevAssetReload(options) {
|
|
19603
|
+
if (!options.enabled)
|
|
19604
|
+
return false;
|
|
19605
|
+
const watched = new Set(options.watchedFiles);
|
|
19606
|
+
const setTimer = options.setTimeoutFn || setTimeout;
|
|
19607
|
+
const clearTimer = options.clearTimeoutFn || clearTimeout;
|
|
19608
|
+
const debounceMs = options.debounceMs ?? 150;
|
|
19609
|
+
let timer = null;
|
|
19610
|
+
options.watch(options.webRoot, { persistent: false }, (_event, filename) => {
|
|
19611
|
+
if (!filename || !watched.has(basename2(filename.toString())))
|
|
19612
|
+
return;
|
|
19613
|
+
if (timer)
|
|
19614
|
+
clearTimer(timer);
|
|
19615
|
+
timer = setTimer(() => {
|
|
19616
|
+
timer = null;
|
|
19617
|
+
options.sendReload();
|
|
19618
|
+
}, debounceMs);
|
|
19619
|
+
});
|
|
19620
|
+
return true;
|
|
19621
|
+
}
|
|
19622
|
+
var init_dev_assets = () => {};
|
|
19623
|
+
|
|
19624
|
+
// web-src/server/journal.ts
|
|
19625
|
+
import { join as join16 } from "node:path";
|
|
19626
|
+
function dailyJournalFilePath(root) {
|
|
19627
|
+
return join16(root, CODE_VIEWER_DIR, DAILY_JOURNAL_FILE_NAME);
|
|
19628
|
+
}
|
|
19629
|
+
function journalTasksFilePath(root) {
|
|
19630
|
+
return join16(root, CODE_VIEWER_DIR, JOURNAL_TASKS_FILE_NAME);
|
|
19631
|
+
}
|
|
19632
|
+
function emptyDailyJournalState() {
|
|
19633
|
+
return { version: 1, entries: [] };
|
|
19634
|
+
}
|
|
19635
|
+
function emptyJournalTaskState() {
|
|
19636
|
+
return { version: 1, tasks: [] };
|
|
19637
|
+
}
|
|
19638
|
+
function makeJournalId(prefix) {
|
|
19639
|
+
const random = Math.random().toString(36).slice(2, 8);
|
|
19640
|
+
const time = Date.now().toString(36);
|
|
19641
|
+
return `${prefix}-${time}${random}`;
|
|
19642
|
+
}
|
|
19643
|
+
function optionalString3(value, maxLen) {
|
|
19644
|
+
if (typeof value !== "string")
|
|
19645
|
+
return;
|
|
19646
|
+
if (value.includes("\x00"))
|
|
19647
|
+
return;
|
|
19648
|
+
const trimmed = value.trim();
|
|
19649
|
+
if (!trimmed)
|
|
19650
|
+
return;
|
|
19651
|
+
return trimmed.slice(0, maxLen);
|
|
19652
|
+
}
|
|
19653
|
+
function optionalBody(value, maxBytes) {
|
|
19654
|
+
if (typeof value !== "string")
|
|
19655
|
+
return;
|
|
19656
|
+
if (value.includes("\x00"))
|
|
19657
|
+
return;
|
|
19658
|
+
if (Buffer.byteLength(value, "utf8") > maxBytes)
|
|
19659
|
+
return;
|
|
19660
|
+
return value;
|
|
19661
|
+
}
|
|
19662
|
+
function normalizeSource(value) {
|
|
19663
|
+
return value === "ai" || value === "imported" ? value : "user";
|
|
19664
|
+
}
|
|
19665
|
+
function normalizeJournalEntry(raw) {
|
|
19666
|
+
if (!raw || typeof raw !== "object")
|
|
19667
|
+
return null;
|
|
19668
|
+
const entry = raw;
|
|
19669
|
+
const id = optionalString3(entry.id, 128);
|
|
19670
|
+
const date = isIsoDate(entry.date) ? entry.date : undefined;
|
|
19671
|
+
const body = optionalBody(entry.body, JOURNAL_ENTRY_BODY_MAX_BYTES);
|
|
19672
|
+
if (!id || !date || body === undefined)
|
|
19673
|
+
return null;
|
|
19674
|
+
const title = optionalString3(entry.title, JOURNAL_TITLE_MAX_CHARS);
|
|
19675
|
+
return {
|
|
19676
|
+
id,
|
|
19677
|
+
date,
|
|
19678
|
+
...title ? { title } : {},
|
|
19679
|
+
body,
|
|
19680
|
+
labels: normalizeJournalLabels(entry.labels),
|
|
19681
|
+
source: normalizeSource(entry.source),
|
|
19682
|
+
created_at: optionalString3(entry.created_at, 64) ?? new Date(0).toISOString(),
|
|
19683
|
+
updated_at: optionalString3(entry.updated_at, 64) ?? new Date(0).toISOString()
|
|
19684
|
+
};
|
|
19685
|
+
}
|
|
19686
|
+
function normalizeDailyJournalState(raw) {
|
|
19687
|
+
if (!raw || typeof raw !== "object")
|
|
19688
|
+
return emptyDailyJournalState();
|
|
19689
|
+
const entriesRaw = raw.entries;
|
|
19690
|
+
if (!Array.isArray(entriesRaw))
|
|
19691
|
+
return emptyDailyJournalState();
|
|
19692
|
+
const entries = [];
|
|
19693
|
+
for (const rawEntry of entriesRaw) {
|
|
19694
|
+
if (entries.length >= MAX_ENTRIES3)
|
|
19695
|
+
break;
|
|
19696
|
+
const entry = normalizeJournalEntry(rawEntry);
|
|
19697
|
+
if (entry)
|
|
19698
|
+
entries.push(entry);
|
|
19699
|
+
}
|
|
19700
|
+
entries.sort((a, b) => a.date.localeCompare(b.date));
|
|
19701
|
+
return { version: 1, entries };
|
|
19702
|
+
}
|
|
19703
|
+
function normalizeTaskNote(raw) {
|
|
19704
|
+
if (!raw || typeof raw !== "object")
|
|
19705
|
+
return null;
|
|
19706
|
+
const note = raw;
|
|
19707
|
+
const id = optionalString3(note.id, 128);
|
|
19708
|
+
const body = optionalBody(note.body, JOURNAL_TASK_NOTE_MAX_BYTES);
|
|
19709
|
+
if (!id || body === undefined)
|
|
19710
|
+
return null;
|
|
19711
|
+
return {
|
|
19712
|
+
id,
|
|
19713
|
+
at: optionalString3(note.at, 64) ?? new Date(0).toISOString(),
|
|
19714
|
+
body,
|
|
19715
|
+
source: normalizeSource(note.source)
|
|
19716
|
+
};
|
|
19717
|
+
}
|
|
19718
|
+
function normalizeTaskClaim(raw) {
|
|
19719
|
+
if (!raw || typeof raw !== "object")
|
|
19720
|
+
return;
|
|
19721
|
+
const claim = raw;
|
|
19722
|
+
const by = optionalString3(claim.by, 128);
|
|
19723
|
+
const claimedAt = optionalString3(claim.claimed_at, 64);
|
|
19724
|
+
const leaseExpiresAt = optionalString3(claim.lease_expires_at, 64);
|
|
19725
|
+
if (!by || !claimedAt || !leaseExpiresAt)
|
|
19726
|
+
return;
|
|
19727
|
+
return {
|
|
19728
|
+
by,
|
|
19729
|
+
claimed_at: claimedAt,
|
|
19730
|
+
lease_expires_at: leaseExpiresAt
|
|
19731
|
+
};
|
|
19732
|
+
}
|
|
19733
|
+
function normalizeJournalTask(raw) {
|
|
19734
|
+
if (!raw || typeof raw !== "object")
|
|
19735
|
+
return null;
|
|
19736
|
+
const task = raw;
|
|
19737
|
+
const id = optionalString3(task.id, 128);
|
|
19738
|
+
const title = optionalString3(task.title, JOURNAL_TITLE_MAX_CHARS);
|
|
19739
|
+
if (!id || !title)
|
|
19740
|
+
return null;
|
|
19741
|
+
const status = isJournalTaskStatus(task.status) ? task.status : "todo";
|
|
19742
|
+
const priority = isJournalTaskPriority(task.priority) ? task.priority : "p2";
|
|
19743
|
+
const body = optionalBody(task.body, JOURNAL_TASK_BODY_MAX_BYTES) ?? "";
|
|
19744
|
+
const dueDate = isIsoDate(task.due_date) ? task.due_date : undefined;
|
|
19745
|
+
const sourceDate = isIsoDate(task.source_date) ? task.source_date : undefined;
|
|
19746
|
+
const journalEntryId = optionalString3(task.journal_entry_id, 128);
|
|
19747
|
+
const completedAt = optionalString3(task.completed_at, 64);
|
|
19748
|
+
const notes = Array.isArray(task.notes) ? task.notes.slice(0, MAX_NOTES_PER_TASK).map(normalizeTaskNote).filter((note) => note !== null) : [];
|
|
19749
|
+
const claim = normalizeTaskClaim(task.claim);
|
|
19750
|
+
return {
|
|
19751
|
+
id,
|
|
19752
|
+
title,
|
|
19753
|
+
body,
|
|
19754
|
+
status,
|
|
19755
|
+
priority,
|
|
19756
|
+
labels: normalizeJournalLabels(task.labels),
|
|
19757
|
+
created_at: optionalString3(task.created_at, 64) ?? new Date(0).toISOString(),
|
|
19758
|
+
updated_at: optionalString3(task.updated_at, 64) ?? new Date(0).toISOString(),
|
|
19759
|
+
...dueDate ? { due_date: dueDate } : {},
|
|
19760
|
+
...sourceDate ? { source_date: sourceDate } : {},
|
|
19761
|
+
...journalEntryId ? { journal_entry_id: journalEntryId } : {},
|
|
19762
|
+
...completedAt ? { completed_at: completedAt } : {},
|
|
19763
|
+
...claim ? { claim } : {},
|
|
19764
|
+
...notes.length ? { notes } : {}
|
|
19765
|
+
};
|
|
19766
|
+
}
|
|
19767
|
+
function normalizeJournalTaskState(raw) {
|
|
19768
|
+
if (!raw || typeof raw !== "object")
|
|
19769
|
+
return emptyJournalTaskState();
|
|
19770
|
+
const tasksRaw = raw.tasks;
|
|
19771
|
+
if (!Array.isArray(tasksRaw))
|
|
19772
|
+
return emptyJournalTaskState();
|
|
19773
|
+
const tasks = [];
|
|
19774
|
+
for (const rawTask of tasksRaw) {
|
|
19775
|
+
if (tasks.length >= MAX_TASKS)
|
|
18310
19776
|
break;
|
|
18311
|
-
|
|
19777
|
+
const task = normalizeJournalTask(rawTask);
|
|
19778
|
+
if (task)
|
|
19779
|
+
tasks.push(task);
|
|
18312
19780
|
}
|
|
19781
|
+
return { version: 1, tasks };
|
|
18313
19782
|
}
|
|
18314
|
-
function
|
|
18315
|
-
|
|
18316
|
-
|
|
18317
|
-
|
|
18318
|
-
|
|
18319
|
-
|
|
18320
|
-
|
|
19783
|
+
async function loadDailyJournalState(root) {
|
|
19784
|
+
return dailyJournalStore.load(root);
|
|
19785
|
+
}
|
|
19786
|
+
async function loadJournalTaskState(root) {
|
|
19787
|
+
return journalTaskStore.load(root);
|
|
19788
|
+
}
|
|
19789
|
+
async function updateDailyJournalState(root, updater) {
|
|
19790
|
+
return dailyJournalStore.update(root, updater);
|
|
19791
|
+
}
|
|
19792
|
+
async function updateJournalTaskState(root, updater) {
|
|
19793
|
+
return journalTaskStore.update(root, updater);
|
|
19794
|
+
}
|
|
19795
|
+
function insertOptionCount2(input) {
|
|
19796
|
+
return (input.before_id ? 1 : 0) + (input.after_id ? 1 : 0) + (input.position !== undefined ? 1 : 0);
|
|
19797
|
+
}
|
|
19798
|
+
function taskInsertIndex(tasks, status, input) {
|
|
19799
|
+
if (insertOptionCount2(input) > 1)
|
|
19800
|
+
return { ok: false, error: "use only one of before, after, or position" };
|
|
19801
|
+
if (input.before_id || input.after_id) {
|
|
19802
|
+
const anchorId = input.before_id || input.after_id || "";
|
|
19803
|
+
const anchorIndex = tasks.findIndex((task) => task.id === anchorId);
|
|
19804
|
+
const anchor = tasks[anchorIndex];
|
|
19805
|
+
if (!anchor)
|
|
19806
|
+
return { ok: false, error: "anchor task not found" };
|
|
19807
|
+
if (anchor.status !== status)
|
|
19808
|
+
return { ok: false, error: "anchor task belongs to another column" };
|
|
19809
|
+
return {
|
|
19810
|
+
ok: true,
|
|
19811
|
+
index: input.before_id ? anchorIndex : anchorIndex + 1
|
|
19812
|
+
};
|
|
18321
19813
|
}
|
|
19814
|
+
const statusIndexes = tasks.map((task, index) => ({ task, index })).filter((item) => item.task.status === status).map((item) => item.index);
|
|
19815
|
+
if (input.position !== undefined) {
|
|
19816
|
+
if (!Number.isInteger(input.position) || input.position < 1)
|
|
19817
|
+
return { ok: false, error: "position must be a positive integer" };
|
|
19818
|
+
if (input.position > statusIndexes.length + 1)
|
|
19819
|
+
return { ok: false, error: "position is out of range" };
|
|
19820
|
+
if (input.position <= statusIndexes.length)
|
|
19821
|
+
return { ok: true, index: statusIndexes[input.position - 1] };
|
|
19822
|
+
}
|
|
19823
|
+
const last = statusIndexes[statusIndexes.length - 1];
|
|
19824
|
+
return { ok: true, index: last === undefined ? tasks.length : last + 1 };
|
|
19825
|
+
}
|
|
19826
|
+
function validateEntryInput(input) {
|
|
19827
|
+
if (!isIsoDate(input.date))
|
|
19828
|
+
return { ok: false, error: "date must be YYYY-MM-DD" };
|
|
19829
|
+
if (optionalBody(input.body, JOURNAL_ENTRY_BODY_MAX_BYTES) === undefined)
|
|
19830
|
+
return { ok: false, error: "body is required or too large" };
|
|
19831
|
+
if (!input.body.trim())
|
|
19832
|
+
return { ok: false, error: "body is required" };
|
|
19833
|
+
return { ok: true };
|
|
18322
19834
|
}
|
|
18323
|
-
function
|
|
18324
|
-
const
|
|
18325
|
-
if (
|
|
18326
|
-
|
|
19835
|
+
function addDailyJournalEntry(state, input, now, makeId3 = makeJournalId) {
|
|
19836
|
+
const valid = validateEntryInput(input);
|
|
19837
|
+
if (valid.ok === false)
|
|
19838
|
+
return valid;
|
|
19839
|
+
const title = optionalString3(input.title, JOURNAL_TITLE_MAX_CHARS);
|
|
19840
|
+
const entry = {
|
|
19841
|
+
id: makeId3("j"),
|
|
19842
|
+
date: input.date,
|
|
19843
|
+
...title ? { title } : {},
|
|
19844
|
+
body: input.body,
|
|
19845
|
+
labels: normalizeJournalLabels(input.labels),
|
|
19846
|
+
source: input.source || "user",
|
|
19847
|
+
created_at: now,
|
|
19848
|
+
updated_at: now
|
|
19849
|
+
};
|
|
19850
|
+
return {
|
|
19851
|
+
ok: true,
|
|
19852
|
+
state: { version: 1, entries: [...state.entries, entry] },
|
|
19853
|
+
entry
|
|
19854
|
+
};
|
|
19855
|
+
}
|
|
19856
|
+
function updateDailyJournalEntry(state, id, patch, now) {
|
|
19857
|
+
const entry = state.entries.find((item) => item.id === id);
|
|
19858
|
+
if (!entry)
|
|
19859
|
+
return { ok: false, error: "journal entry not found" };
|
|
19860
|
+
const next = {
|
|
19861
|
+
...entry,
|
|
19862
|
+
updated_at: now
|
|
19863
|
+
};
|
|
19864
|
+
if (patch.date !== undefined) {
|
|
19865
|
+
if (!isIsoDate(patch.date))
|
|
19866
|
+
return { ok: false, error: "date must be YYYY-MM-DD" };
|
|
19867
|
+
next.date = patch.date;
|
|
19868
|
+
}
|
|
19869
|
+
if (patch.title !== undefined) {
|
|
19870
|
+
const title = optionalString3(patch.title, JOURNAL_TITLE_MAX_CHARS);
|
|
19871
|
+
if (title)
|
|
19872
|
+
next.title = title;
|
|
19873
|
+
else
|
|
19874
|
+
delete next.title;
|
|
18327
19875
|
}
|
|
18328
|
-
|
|
18329
|
-
|
|
18330
|
-
|
|
19876
|
+
if (patch.body !== undefined) {
|
|
19877
|
+
const body = optionalBody(patch.body, JOURNAL_ENTRY_BODY_MAX_BYTES);
|
|
19878
|
+
if (body === undefined || !body.trim())
|
|
19879
|
+
return { ok: false, error: "body is required or too large" };
|
|
19880
|
+
next.body = body;
|
|
19881
|
+
}
|
|
19882
|
+
if (patch.labels !== undefined)
|
|
19883
|
+
next.labels = normalizeJournalLabels(patch.labels);
|
|
19884
|
+
if (patch.source !== undefined)
|
|
19885
|
+
next.source = patch.source;
|
|
19886
|
+
return {
|
|
19887
|
+
ok: true,
|
|
19888
|
+
state: {
|
|
19889
|
+
version: 1,
|
|
19890
|
+
entries: state.entries.map((item) => item.id === id ? next : item)
|
|
19891
|
+
},
|
|
19892
|
+
entry: next
|
|
19893
|
+
};
|
|
19894
|
+
}
|
|
19895
|
+
function deleteDailyJournalEntry(state, id) {
|
|
19896
|
+
const entries = state.entries.filter((entry) => entry.id !== id);
|
|
19897
|
+
return {
|
|
19898
|
+
state: { version: 1, entries },
|
|
19899
|
+
removed: entries.length !== state.entries.length
|
|
19900
|
+
};
|
|
19901
|
+
}
|
|
19902
|
+
function validateTaskInput(input) {
|
|
19903
|
+
if (!optionalString3(input.title, JOURNAL_TITLE_MAX_CHARS))
|
|
19904
|
+
return { ok: false, error: "title is required" };
|
|
19905
|
+
if (input.body !== undefined && optionalBody(input.body, JOURNAL_TASK_BODY_MAX_BYTES) === undefined)
|
|
19906
|
+
return { ok: false, error: "body is too large" };
|
|
19907
|
+
if (input.due_date !== undefined && !isIsoDate(input.due_date))
|
|
19908
|
+
return { ok: false, error: "due date must be YYYY-MM-DD" };
|
|
19909
|
+
if (input.source_date !== undefined && !isIsoDate(input.source_date))
|
|
19910
|
+
return { ok: false, error: "source date must be YYYY-MM-DD" };
|
|
19911
|
+
return { ok: true };
|
|
19912
|
+
}
|
|
19913
|
+
function completedAtForStatus(status, previous, now) {
|
|
19914
|
+
return status === "done" ? previous || now : undefined;
|
|
19915
|
+
}
|
|
19916
|
+
function githubIssueTaskBody(issueNumber, issueUrl, memoLabel) {
|
|
19917
|
+
return [
|
|
19918
|
+
`GitHub issue #${issueNumber}`,
|
|
19919
|
+
...issueUrl ? [issueUrl] : [],
|
|
19920
|
+
"",
|
|
19921
|
+
memoLabel || "Memo:"
|
|
19922
|
+
].join(`
|
|
19923
|
+
`);
|
|
19924
|
+
}
|
|
19925
|
+
function githubIssueRequiredLabels(issueNumber, repo, labels) {
|
|
19926
|
+
const repoLabel = journalIssueRepoLabel(repo);
|
|
19927
|
+
return normalizeJournalLabels([
|
|
19928
|
+
"github",
|
|
19929
|
+
journalIssueLabel(issueNumber),
|
|
19930
|
+
...repoLabel ? [repoLabel] : [],
|
|
19931
|
+
...Array.isArray(labels) ? labels : []
|
|
19932
|
+
]);
|
|
19933
|
+
}
|
|
19934
|
+
function taskHasPlacement(input, currentStatus) {
|
|
19935
|
+
return !!input.status && input.status !== currentStatus || !!input.before_id || !!input.after_id || input.position !== undefined;
|
|
19936
|
+
}
|
|
19937
|
+
function addJournalTask(state, input, now, makeId3 = makeJournalId) {
|
|
19938
|
+
const valid = validateTaskInput(input);
|
|
19939
|
+
if (valid.ok === false)
|
|
19940
|
+
return valid;
|
|
19941
|
+
const anchorId = input.before_id || input.after_id;
|
|
19942
|
+
const anchor = anchorId ? state.tasks.find((task2) => task2.id === anchorId) : undefined;
|
|
19943
|
+
if (anchorId && !anchor)
|
|
19944
|
+
return { ok: false, error: "anchor task not found" };
|
|
19945
|
+
const status = input.status || anchor?.status || "todo";
|
|
19946
|
+
const task = {
|
|
19947
|
+
id: makeId3("t"),
|
|
19948
|
+
title: optionalString3(input.title, JOURNAL_TITLE_MAX_CHARS) || "Untitled task",
|
|
19949
|
+
body: input.body || "",
|
|
19950
|
+
status,
|
|
19951
|
+
priority: input.priority || "p2",
|
|
19952
|
+
labels: normalizeJournalLabels(input.labels),
|
|
19953
|
+
created_at: now,
|
|
19954
|
+
updated_at: now,
|
|
19955
|
+
...input.due_date ? { due_date: input.due_date } : {},
|
|
19956
|
+
...input.source_date ? { source_date: input.source_date } : {},
|
|
19957
|
+
...input.journal_entry_id ? { journal_entry_id: input.journal_entry_id } : {},
|
|
19958
|
+
...completedAtForStatus(status, undefined, now) ? { completed_at: now } : {}
|
|
19959
|
+
};
|
|
19960
|
+
const insertAt = taskInsertIndex(state.tasks, status, input);
|
|
19961
|
+
if (insertAt.ok === false)
|
|
19962
|
+
return insertAt;
|
|
19963
|
+
const tasks = [...state.tasks];
|
|
19964
|
+
tasks.splice(insertAt.index, 0, task);
|
|
19965
|
+
return { ok: true, state: { version: 1, tasks }, task };
|
|
19966
|
+
}
|
|
19967
|
+
function updateJournalTask(state, id, patch, now) {
|
|
19968
|
+
const task = state.tasks.find((item) => item.id === id);
|
|
19969
|
+
if (!task)
|
|
19970
|
+
return { ok: false, error: "task not found" };
|
|
19971
|
+
const next = { ...task, updated_at: now };
|
|
19972
|
+
if (patch.title !== undefined) {
|
|
19973
|
+
const title = optionalString3(patch.title, JOURNAL_TITLE_MAX_CHARS);
|
|
19974
|
+
if (!title)
|
|
19975
|
+
return { ok: false, error: "title is required" };
|
|
19976
|
+
next.title = title;
|
|
18331
19977
|
}
|
|
18332
|
-
|
|
19978
|
+
if (patch.body !== undefined) {
|
|
19979
|
+
const body = optionalBody(patch.body, JOURNAL_TASK_BODY_MAX_BYTES);
|
|
19980
|
+
if (body === undefined)
|
|
19981
|
+
return { ok: false, error: "body is too large" };
|
|
19982
|
+
next.body = body;
|
|
19983
|
+
}
|
|
19984
|
+
if (patch.status !== undefined) {
|
|
19985
|
+
next.status = patch.status;
|
|
19986
|
+
const completedAt = completedAtForStatus(patch.status, next.completed_at, now);
|
|
19987
|
+
if (completedAt)
|
|
19988
|
+
next.completed_at = completedAt;
|
|
19989
|
+
else
|
|
19990
|
+
delete next.completed_at;
|
|
19991
|
+
if (patch.status !== "doing")
|
|
19992
|
+
delete next.claim;
|
|
19993
|
+
}
|
|
19994
|
+
if (patch.priority !== undefined)
|
|
19995
|
+
next.priority = patch.priority;
|
|
19996
|
+
if (patch.labels !== undefined)
|
|
19997
|
+
next.labels = normalizeJournalLabels(patch.labels);
|
|
19998
|
+
if (patch.due_date !== undefined) {
|
|
19999
|
+
if (patch.due_date === null || patch.due_date === "")
|
|
20000
|
+
delete next.due_date;
|
|
20001
|
+
else if (isIsoDate(patch.due_date))
|
|
20002
|
+
next.due_date = patch.due_date;
|
|
20003
|
+
else
|
|
20004
|
+
return { ok: false, error: "due date must be YYYY-MM-DD" };
|
|
20005
|
+
}
|
|
20006
|
+
if (patch.source_date !== undefined) {
|
|
20007
|
+
if (patch.source_date === null || patch.source_date === "")
|
|
20008
|
+
delete next.source_date;
|
|
20009
|
+
else if (isIsoDate(patch.source_date))
|
|
20010
|
+
next.source_date = patch.source_date;
|
|
20011
|
+
else
|
|
20012
|
+
return { ok: false, error: "source date must be YYYY-MM-DD" };
|
|
20013
|
+
}
|
|
20014
|
+
if (patch.journal_entry_id !== undefined) {
|
|
20015
|
+
const journalEntryId = optionalString3(patch.journal_entry_id, 128);
|
|
20016
|
+
if (journalEntryId)
|
|
20017
|
+
next.journal_entry_id = journalEntryId;
|
|
20018
|
+
else
|
|
20019
|
+
delete next.journal_entry_id;
|
|
20020
|
+
}
|
|
20021
|
+
return {
|
|
20022
|
+
ok: true,
|
|
20023
|
+
state: {
|
|
20024
|
+
version: 1,
|
|
20025
|
+
tasks: state.tasks.map((item) => item.id === id ? next : item)
|
|
20026
|
+
},
|
|
20027
|
+
task: next
|
|
20028
|
+
};
|
|
18333
20029
|
}
|
|
18334
|
-
|
|
18335
|
-
|
|
18336
|
-
|
|
18337
|
-
|
|
18338
|
-
|
|
18339
|
-
|
|
18340
|
-
|
|
18341
|
-
|
|
18342
|
-
const
|
|
18343
|
-
|
|
18344
|
-
|
|
18345
|
-
const
|
|
18346
|
-
|
|
18347
|
-
|
|
18348
|
-
|
|
18349
|
-
|
|
18350
|
-
|
|
18351
|
-
|
|
18352
|
-
|
|
18353
|
-
|
|
18354
|
-
|
|
18355
|
-
|
|
20030
|
+
function moveJournalTask(state, id, input, now) {
|
|
20031
|
+
const source = state.tasks.find((task) => task.id === id);
|
|
20032
|
+
if (!source)
|
|
20033
|
+
return { ok: false, error: "task not found" };
|
|
20034
|
+
if (input.before_id === id || input.after_id === id)
|
|
20035
|
+
return { ok: false, error: "cannot move task relative to itself" };
|
|
20036
|
+
const tasksWithoutSource = state.tasks.filter((task) => task.id !== id);
|
|
20037
|
+
const anchorId = input.before_id || input.after_id;
|
|
20038
|
+
const anchor = anchorId ? tasksWithoutSource.find((task) => task.id === anchorId) : undefined;
|
|
20039
|
+
if (anchorId && !anchor)
|
|
20040
|
+
return { ok: false, error: "anchor task not found" };
|
|
20041
|
+
const status = input.status || anchor?.status || source.status;
|
|
20042
|
+
const moved = {
|
|
20043
|
+
...source,
|
|
20044
|
+
status,
|
|
20045
|
+
updated_at: now
|
|
20046
|
+
};
|
|
20047
|
+
const completedAt = completedAtForStatus(status, moved.completed_at, now);
|
|
20048
|
+
if (completedAt)
|
|
20049
|
+
moved.completed_at = completedAt;
|
|
20050
|
+
else
|
|
20051
|
+
delete moved.completed_at;
|
|
20052
|
+
if (status !== "doing")
|
|
20053
|
+
delete moved.claim;
|
|
20054
|
+
const insertAt = taskInsertIndex(tasksWithoutSource, status, input);
|
|
20055
|
+
if (insertAt.ok === false)
|
|
20056
|
+
return insertAt;
|
|
20057
|
+
const tasks = [...tasksWithoutSource];
|
|
20058
|
+
tasks.splice(insertAt.index, 0, moved);
|
|
20059
|
+
return { ok: true, state: { version: 1, tasks }, task: moved };
|
|
20060
|
+
}
|
|
20061
|
+
function linkGithubIssueTask(state, input, now, makeId3 = makeJournalId) {
|
|
20062
|
+
if (!Number.isInteger(input.issue_number) || input.issue_number < 1) {
|
|
20063
|
+
return { ok: false, error: "issue number must be a positive integer" };
|
|
20064
|
+
}
|
|
20065
|
+
const title = optionalString3(input.title, JOURNAL_TITLE_MAX_CHARS) || `GitHub issue #${input.issue_number}`;
|
|
20066
|
+
const repo = optionalString3(input.repo, 120);
|
|
20067
|
+
const issueUrl = optionalString3(input.url, 240);
|
|
20068
|
+
const memoLabel = optionalString3(input.memo_label, 80);
|
|
20069
|
+
const requiredLabels = githubIssueRequiredLabels(input.issue_number, repo, input.labels);
|
|
20070
|
+
const linkLabel = journalIssueLabel(input.issue_number);
|
|
20071
|
+
const repoLabel = journalIssueRepoLabel(repo);
|
|
20072
|
+
const linked = state.tasks.find((task2) => {
|
|
20073
|
+
if (!task2.labels.includes("github") || !task2.labels.includes(linkLabel)) {
|
|
20074
|
+
return false;
|
|
20075
|
+
}
|
|
20076
|
+
if (repoLabel)
|
|
20077
|
+
return task2.labels.includes(repoLabel);
|
|
20078
|
+
return !task2.labels.some((label) => label.startsWith("repo-"));
|
|
18356
20079
|
});
|
|
18357
|
-
|
|
20080
|
+
if (!linked) {
|
|
20081
|
+
const hasAnchor = !!input.before_id || !!input.after_id;
|
|
20082
|
+
const result = addJournalTask(state, {
|
|
20083
|
+
title,
|
|
20084
|
+
body: githubIssueTaskBody(input.issue_number, issueUrl, memoLabel),
|
|
20085
|
+
status: input.status || (hasAnchor ? undefined : "draft"),
|
|
20086
|
+
priority: input.priority || "p2",
|
|
20087
|
+
labels: requiredLabels,
|
|
20088
|
+
before_id: input.before_id,
|
|
20089
|
+
after_id: input.after_id,
|
|
20090
|
+
position: input.position
|
|
20091
|
+
}, now, makeId3);
|
|
20092
|
+
if (result.ok === false)
|
|
20093
|
+
return result;
|
|
20094
|
+
return {
|
|
20095
|
+
ok: true,
|
|
20096
|
+
state: result.state,
|
|
20097
|
+
task: result.task,
|
|
20098
|
+
created: true,
|
|
20099
|
+
moved: false
|
|
20100
|
+
};
|
|
20101
|
+
}
|
|
20102
|
+
let nextState = state;
|
|
20103
|
+
let task = linked;
|
|
20104
|
+
let moved = false;
|
|
20105
|
+
const placement = {
|
|
20106
|
+
status: input.status,
|
|
20107
|
+
before_id: input.before_id,
|
|
20108
|
+
after_id: input.after_id,
|
|
20109
|
+
position: input.position
|
|
20110
|
+
};
|
|
20111
|
+
if (taskHasPlacement(placement, task.status)) {
|
|
20112
|
+
const result = moveJournalTask(nextState, task.id, placement, now);
|
|
20113
|
+
if (result.ok === false)
|
|
20114
|
+
return result;
|
|
20115
|
+
nextState = result.state;
|
|
20116
|
+
task = result.task;
|
|
20117
|
+
moved = true;
|
|
20118
|
+
}
|
|
20119
|
+
const labels = normalizeJournalLabels([...task.labels, ...requiredLabels]);
|
|
20120
|
+
const shouldUpdateLabels = labels.length !== task.labels.length || labels.some((label, index) => label !== task.labels[index]);
|
|
20121
|
+
if (input.priority || shouldUpdateLabels) {
|
|
20122
|
+
const result = updateJournalTask(nextState, task.id, {
|
|
20123
|
+
...input.priority ? { priority: input.priority } : {},
|
|
20124
|
+
...shouldUpdateLabels ? { labels } : {}
|
|
20125
|
+
}, now);
|
|
20126
|
+
if (result.ok === false)
|
|
20127
|
+
return result;
|
|
20128
|
+
nextState = result.state;
|
|
20129
|
+
task = result.task;
|
|
20130
|
+
}
|
|
20131
|
+
return { ok: true, state: nextState, task, created: false, moved };
|
|
20132
|
+
}
|
|
20133
|
+
function claimJournalTask(state, id, input, now) {
|
|
20134
|
+
const task = state.tasks.find((item) => item.id === id);
|
|
20135
|
+
if (!task)
|
|
20136
|
+
return { ok: false, error: "task not found" };
|
|
20137
|
+
const nowMs = Date.parse(now);
|
|
20138
|
+
const activeClaim = task.claim && Number.isFinite(Date.parse(task.claim.lease_expires_at)) && Date.parse(task.claim.lease_expires_at) > nowMs;
|
|
20139
|
+
if (activeClaim)
|
|
20140
|
+
return { ok: false, error: "task is already claimed" };
|
|
20141
|
+
if (task.status !== "todo" && task.status !== "doing")
|
|
20142
|
+
return {
|
|
20143
|
+
ok: false,
|
|
20144
|
+
error: "only todo or expired doing tasks can be claimed"
|
|
20145
|
+
};
|
|
20146
|
+
const by = optionalString3(input.by, 128) || "ai";
|
|
20147
|
+
const wipLimit = input.wip_limit;
|
|
20148
|
+
if (wipLimit !== undefined && wipLimit > 0) {
|
|
20149
|
+
const activeDoing = state.tasks.filter((item) => {
|
|
20150
|
+
if (item.status !== "doing" || !item.claim)
|
|
20151
|
+
return false;
|
|
20152
|
+
if (item.claim.by !== by)
|
|
20153
|
+
return false;
|
|
20154
|
+
const expires = Date.parse(item.claim.lease_expires_at);
|
|
20155
|
+
return Number.isFinite(expires) && expires > nowMs;
|
|
20156
|
+
}).length;
|
|
20157
|
+
if (activeDoing >= wipLimit)
|
|
20158
|
+
return { ok: false, error: "WIP limit reached" };
|
|
20159
|
+
}
|
|
20160
|
+
const leaseMinutes = Number.isFinite(input.lease_minutes) && input.lease_minutes ? Math.min(1440, Math.max(1, Math.round(input.lease_minutes))) : 120;
|
|
20161
|
+
const leaseExpiresAt = new Date(nowMs + leaseMinutes * 60000).toISOString();
|
|
20162
|
+
const next = {
|
|
20163
|
+
...task,
|
|
20164
|
+
status: "doing",
|
|
20165
|
+
updated_at: now,
|
|
20166
|
+
claim: {
|
|
20167
|
+
by,
|
|
20168
|
+
claimed_at: now,
|
|
20169
|
+
lease_expires_at: leaseExpiresAt
|
|
20170
|
+
}
|
|
20171
|
+
};
|
|
20172
|
+
return {
|
|
20173
|
+
ok: true,
|
|
20174
|
+
state: {
|
|
20175
|
+
version: 1,
|
|
20176
|
+
tasks: state.tasks.map((item) => item.id === id ? next : item)
|
|
20177
|
+
},
|
|
20178
|
+
task: next
|
|
20179
|
+
};
|
|
18358
20180
|
}
|
|
18359
|
-
|
|
20181
|
+
function completeJournalTask(state, id, input, now, makeId3 = makeJournalId) {
|
|
20182
|
+
const task = state.tasks.find((item) => item.id === id);
|
|
20183
|
+
if (!task)
|
|
20184
|
+
return { ok: false, error: "task not found" };
|
|
20185
|
+
if (task.status !== "doing")
|
|
20186
|
+
return { ok: false, error: "only doing tasks can be completed" };
|
|
20187
|
+
const nowMs = Date.parse(now);
|
|
20188
|
+
const activeClaim = task.claim && Number.isFinite(Date.parse(task.claim.lease_expires_at)) && Date.parse(task.claim.lease_expires_at) > nowMs;
|
|
20189
|
+
if (!activeClaim)
|
|
20190
|
+
return { ok: false, error: "task must be claimed before completion" };
|
|
20191
|
+
const by = optionalString3(input.by, 128);
|
|
20192
|
+
if (!by)
|
|
20193
|
+
return { ok: false, error: "task completion requires claim owner" };
|
|
20194
|
+
if (task.claim?.by !== by)
|
|
20195
|
+
return { ok: false, error: "task claim belongs to another agent" };
|
|
20196
|
+
const notes = [...task.notes || []];
|
|
20197
|
+
if (input.note?.trim()) {
|
|
20198
|
+
const body = optionalBody(input.note, JOURNAL_TASK_NOTE_MAX_BYTES);
|
|
20199
|
+
if (body === undefined)
|
|
20200
|
+
return { ok: false, error: "note is too large" };
|
|
20201
|
+
notes.push({
|
|
20202
|
+
id: makeId3("n"),
|
|
20203
|
+
at: now,
|
|
20204
|
+
body,
|
|
20205
|
+
source: input.source || "ai"
|
|
20206
|
+
});
|
|
20207
|
+
if (notes.length > MAX_NOTES_PER_TASK) {
|
|
20208
|
+
notes.splice(0, notes.length - MAX_NOTES_PER_TASK);
|
|
20209
|
+
}
|
|
20210
|
+
}
|
|
20211
|
+
const next = {
|
|
20212
|
+
...task,
|
|
20213
|
+
status: "done",
|
|
20214
|
+
updated_at: now,
|
|
20215
|
+
completed_at: now,
|
|
20216
|
+
...notes.length ? { notes } : {}
|
|
20217
|
+
};
|
|
20218
|
+
delete next.claim;
|
|
20219
|
+
return {
|
|
20220
|
+
ok: true,
|
|
20221
|
+
state: {
|
|
20222
|
+
version: 1,
|
|
20223
|
+
tasks: state.tasks.map((item) => item.id === id ? next : item)
|
|
20224
|
+
},
|
|
20225
|
+
task: next
|
|
20226
|
+
};
|
|
20227
|
+
}
|
|
20228
|
+
function deleteJournalTask(state, id) {
|
|
20229
|
+
const tasks = state.tasks.filter((task) => task.id !== id);
|
|
20230
|
+
return {
|
|
20231
|
+
state: { version: 1, tasks },
|
|
20232
|
+
removed: tasks.length !== state.tasks.length
|
|
20233
|
+
};
|
|
20234
|
+
}
|
|
20235
|
+
var DAILY_JOURNAL_FILE_NAME = "daily-journal.json", JOURNAL_TASKS_FILE_NAME = "tasks.json", JOURNAL_ENTRY_BODY_MAX_BYTES, JOURNAL_TASK_BODY_MAX_BYTES, JOURNAL_TASK_NOTE_MAX_BYTES, JOURNAL_TITLE_MAX_CHARS = 200, MAX_DAILY_JOURNAL_JSON_BYTES = 8000000, MAX_JOURNAL_TASKS_JSON_BYTES = 8000000, MAX_ENTRIES3 = 2000, MAX_TASKS = 2000, MAX_NOTES_PER_TASK = 100, dailyJournalStore, journalTaskStore;
|
|
20236
|
+
var init_journal2 = __esm(() => {
|
|
20237
|
+
init_journal();
|
|
20238
|
+
init_annotations();
|
|
20239
|
+
init_json_store();
|
|
20240
|
+
JOURNAL_ENTRY_BODY_MAX_BYTES = 128 * 1024;
|
|
20241
|
+
JOURNAL_TASK_BODY_MAX_BYTES = 128 * 1024;
|
|
20242
|
+
JOURNAL_TASK_NOTE_MAX_BYTES = 64 * 1024;
|
|
20243
|
+
dailyJournalStore = createJsonFileStore({
|
|
20244
|
+
filePath: dailyJournalFilePath,
|
|
20245
|
+
empty: emptyDailyJournalState,
|
|
20246
|
+
sanitize: normalizeDailyJournalState,
|
|
20247
|
+
maxBytes: MAX_DAILY_JOURNAL_JSON_BYTES,
|
|
20248
|
+
backupSuffix: "corrupt",
|
|
20249
|
+
sizeErrorMessage: "daily journal state too large"
|
|
20250
|
+
});
|
|
20251
|
+
journalTaskStore = createJsonFileStore({
|
|
20252
|
+
filePath: journalTasksFilePath,
|
|
20253
|
+
empty: emptyJournalTaskState,
|
|
20254
|
+
sanitize: normalizeJournalTaskState,
|
|
20255
|
+
maxBytes: MAX_JOURNAL_TASKS_JSON_BYTES,
|
|
20256
|
+
backupSuffix: "corrupt",
|
|
20257
|
+
sizeErrorMessage: "journal task state too large"
|
|
20258
|
+
});
|
|
20259
|
+
});
|
|
18360
20260
|
|
|
18361
20261
|
// web-src/server/search-service.ts
|
|
18362
|
-
import { existsSync as existsSync7, lstatSync as lstatSync4, readFileSync as
|
|
18363
|
-
import { join as
|
|
20262
|
+
import { existsSync as existsSync7, lstatSync as lstatSync4, readFileSync as readFileSync7, realpathSync as realpathSync5 } from "node:fs";
|
|
20263
|
+
import { join as join17, relative as relative6 } from "node:path";
|
|
18364
20264
|
function rgAvailable(cwd) {
|
|
18365
20265
|
if (rgAvailableCache !== null)
|
|
18366
20266
|
return rgAvailableCache;
|
|
@@ -18381,7 +20281,7 @@ function safeWorktreePath(env, path) {
|
|
|
18381
20281
|
return null;
|
|
18382
20282
|
if (isGitInternalPath(path))
|
|
18383
20283
|
return null;
|
|
18384
|
-
const full =
|
|
20284
|
+
const full = join17(env.cwd, path);
|
|
18385
20285
|
if (!existsSync7(full))
|
|
18386
20286
|
return null;
|
|
18387
20287
|
let realCwd;
|
|
@@ -18427,7 +20327,7 @@ function grepWorktreeFallback(env, query, max, paths) {
|
|
|
18427
20327
|
continue;
|
|
18428
20328
|
let data;
|
|
18429
20329
|
try {
|
|
18430
|
-
data =
|
|
20330
|
+
data = readFileSync7(full);
|
|
18431
20331
|
} catch {
|
|
18432
20332
|
continue;
|
|
18433
20333
|
}
|
|
@@ -18560,8 +20460,8 @@ var init_search_service = __esm(() => {
|
|
|
18560
20460
|
});
|
|
18561
20461
|
|
|
18562
20462
|
// web-src/server/mcp.ts
|
|
18563
|
-
import { readFileSync as
|
|
18564
|
-
import { join as
|
|
20463
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
20464
|
+
import { join as join18 } from "node:path";
|
|
18565
20465
|
function defaultMcpTools(options = {}) {
|
|
18566
20466
|
return [
|
|
18567
20467
|
{
|
|
@@ -20035,7 +21935,7 @@ var init_mcp = __esm(() => {
|
|
|
20035
21935
|
init_search_cli();
|
|
20036
21936
|
init_search_service();
|
|
20037
21937
|
init_status_cli();
|
|
20038
|
-
PACKAGE_VERSION = JSON.parse(
|
|
21938
|
+
PACKAGE_VERSION = JSON.parse(readFileSync8(join18(ROOT, "package.json"), "utf8")).version;
|
|
20039
21939
|
MCP_SERVER_INFO = {
|
|
20040
21940
|
name: "code-viewer",
|
|
20041
21941
|
title: "code-viewer",
|
|
@@ -20122,7 +22022,7 @@ import {
|
|
|
20122
22022
|
lstatSync as lstatSync5,
|
|
20123
22023
|
mkdirSync as mkdirSync4,
|
|
20124
22024
|
openSync as openSync2,
|
|
20125
|
-
readFileSync as
|
|
22025
|
+
readFileSync as readFileSync9,
|
|
20126
22026
|
realpathSync as realpathSync6,
|
|
20127
22027
|
renameSync,
|
|
20128
22028
|
statSync as statSync6,
|
|
@@ -20131,7 +22031,7 @@ import {
|
|
|
20131
22031
|
writeFileSync as writeFileSync2
|
|
20132
22032
|
} from "node:fs";
|
|
20133
22033
|
import { homedir as homedir3 } from "node:os";
|
|
20134
|
-
import { basename as basename3, dirname as dirname5, extname as extname2, join as
|
|
22034
|
+
import { basename as basename3, dirname as dirname5, extname as extname2, join as join19, relative as relative7 } from "node:path";
|
|
20135
22035
|
function parseCli() {
|
|
20136
22036
|
const rest = [];
|
|
20137
22037
|
for (let i = 2;i < process.argv.length; i++) {
|
|
@@ -20143,17 +22043,18 @@ Usage:
|
|
|
20143
22043
|
code-viewer [--cwd <repo>] [--port <port>] [--open] [--bin <name>=<path>] [git-diff-args...]
|
|
20144
22044
|
code-viewer status [--cwd <repo>] [--bin git=<path>] [--ref <ref>] [--limit <N>] [--json]
|
|
20145
22045
|
code-viewer annotate <start|add|add-db|rename|edit|move|list|delete|clear> [options]
|
|
22046
|
+
code-viewer journal <list|add|edit|tasks|task-add|task-update|task-next|github-issues|task-link-issue|task-claim|task-done|task-delete> [options]
|
|
20146
22047
|
code-viewer query <sources|schemas|schema|columns|ddl|exec|list|clear|snapshot|diff|search|redis|elasticsearch|s3> [options] [--bin git=<path>]
|
|
20147
22048
|
code-viewer search code --term <text> [--ref <ref>] [--path <p>...] [--regex] [--max <n>] [--json] [--bin git=<path>]
|
|
20148
22049
|
code-viewer search files --term <pattern> [--ref <ref>] [--max <n>] [--json] [--bin git=<path>]
|
|
20149
22050
|
code-viewer file <blame|history|show|diff> --path <p> [--ref <ref>] [...subcommand options] [--json] [--bin git=<path>]
|
|
20150
22051
|
code-viewer skill install [--agent <list>] [--global]
|
|
20151
|
-
code-viewer doctor [--cwd <path>] [--port <N>] [--json] [--bin <git|docker>=<path>]
|
|
22052
|
+
code-viewer doctor [--cwd <path>] [--port <N>] [--json] [--bin <git|docker|gh>=<path>]
|
|
20152
22053
|
code-viewer agent-help
|
|
20153
22054
|
code-viewer help
|
|
20154
22055
|
|
|
20155
22056
|
AI-agent index (start here): code-viewer agent-help
|
|
20156
|
-
Subcommand guides (AI agents): code-viewer <status|annotate|query|search|file|skill|doctor> agent-help
|
|
22057
|
+
Subcommand guides (AI agents): code-viewer <status|annotate|journal|query|search|file|skill|doctor> agent-help
|
|
20157
22058
|
|
|
20158
22059
|
Examples:
|
|
20159
22060
|
code-viewer --open
|
|
@@ -20251,7 +22152,7 @@ Examples:
|
|
|
20251
22152
|
}
|
|
20252
22153
|
function warnIfLegacyConfigPresent() {
|
|
20253
22154
|
try {
|
|
20254
|
-
if (existsSync8(
|
|
22155
|
+
if (existsSync8(join19(cwd, ".code-viewer.json"))) {
|
|
20255
22156
|
console.warn("[code-viewer] .code-viewer.json is no longer used; configure scope and upload from Viewer Settings instead. The file can be safely removed.");
|
|
20256
22157
|
}
|
|
20257
22158
|
} catch {}
|
|
@@ -20358,10 +22259,10 @@ function staticFile(pathname) {
|
|
|
20358
22259
|
const spec = map[pathname];
|
|
20359
22260
|
if (!spec)
|
|
20360
22261
|
return null;
|
|
20361
|
-
const full =
|
|
22262
|
+
const full = join19(WEB_ROOT, spec[0]);
|
|
20362
22263
|
if (!existsSync8(full))
|
|
20363
22264
|
return text("not found", 404);
|
|
20364
|
-
return new Response(
|
|
22265
|
+
return new Response(readFileSync9(full), {
|
|
20365
22266
|
headers: { "Content-Type": spec[1], "Cache-Control": "no-store" }
|
|
20366
22267
|
});
|
|
20367
22268
|
}
|
|
@@ -20613,7 +22514,7 @@ function safeWorktreePath2(path) {
|
|
|
20613
22514
|
return safeWorktreePath(currentSearchEnv(), path);
|
|
20614
22515
|
}
|
|
20615
22516
|
function worktreePath(path) {
|
|
20616
|
-
return
|
|
22517
|
+
return join19(cwd, path);
|
|
20617
22518
|
}
|
|
20618
22519
|
function safeOpenWorktreePath(path) {
|
|
20619
22520
|
if (path === "") {
|
|
@@ -20698,7 +22599,7 @@ function readReadme(target, dirPath) {
|
|
|
20698
22599
|
if (!full)
|
|
20699
22600
|
continue;
|
|
20700
22601
|
try {
|
|
20701
|
-
return { path, text:
|
|
22602
|
+
return { path, text: readFileSync9(full, "utf8") };
|
|
20702
22603
|
} catch {
|
|
20703
22604
|
continue;
|
|
20704
22605
|
}
|
|
@@ -20884,7 +22785,7 @@ function handleLog(url) {
|
|
|
20884
22785
|
}
|
|
20885
22786
|
function blamePathKey(p) {
|
|
20886
22787
|
try {
|
|
20887
|
-
const st = statSync6(
|
|
22788
|
+
const st = statSync6(join19(cwd, p));
|
|
20888
22789
|
return `${st.mtimeMs}:${st.size}`;
|
|
20889
22790
|
} catch {
|
|
20890
22791
|
return "missing";
|
|
@@ -21378,7 +23279,7 @@ async function handleUploadFiles(req) {
|
|
|
21378
23279
|
total += file.size;
|
|
21379
23280
|
if (total > MAX_UPLOAD_TOTAL_BYTES)
|
|
21380
23281
|
return text("upload too large", 413);
|
|
21381
|
-
const target =
|
|
23282
|
+
const target = join19(realDir, safeName);
|
|
21382
23283
|
if (relative7(realDir, dirname5(target)) !== "")
|
|
21383
23284
|
return text("invalid filename", 400);
|
|
21384
23285
|
if (existsSync8(target))
|
|
@@ -21500,9 +23401,9 @@ function triggerUpdate(changedPaths) {
|
|
|
21500
23401
|
sendSse("update", data);
|
|
21501
23402
|
}
|
|
21502
23403
|
function moveMacPathIntoTrash(path) {
|
|
21503
|
-
const trashDir =
|
|
23404
|
+
const trashDir = join19(homedir3(), ".Trash");
|
|
21504
23405
|
const base = basename3(path) || "code-viewer-trash-item";
|
|
21505
|
-
const target =
|
|
23406
|
+
const target = join19(trashDir, `${base}-${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 8)}`);
|
|
21506
23407
|
try {
|
|
21507
23408
|
mkdirSync4(trashDir, { recursive: true });
|
|
21508
23409
|
renameSync(path, target);
|
|
@@ -21544,7 +23445,7 @@ function restoreTrashPath(originalPath, trashPath) {
|
|
|
21544
23445
|
if (!existsSync8(trashPath))
|
|
21545
23446
|
return { ok: false, error: "trash item not found" };
|
|
21546
23447
|
try {
|
|
21547
|
-
const trashRoot =
|
|
23448
|
+
const trashRoot = join19(homedir3(), ".Trash");
|
|
21548
23449
|
const trashRelative = relative7(trashRoot, trashPath);
|
|
21549
23450
|
if (trashRelative === "" || trashRelative.startsWith("..") || trashRelative.startsWith("/") || trashRelative.startsWith("\\"))
|
|
21550
23451
|
return { ok: false, error: "invalid trash handle" };
|
|
@@ -21694,7 +23595,7 @@ async function handleCreateDirectory(req) {
|
|
|
21694
23595
|
const targetPath = dir ? `${dir}/${name}` : name;
|
|
21695
23596
|
if (!safeRepoPath(targetPath) || isGitInternalPath(targetPath))
|
|
21696
23597
|
return text("invalid target", 400);
|
|
21697
|
-
const target =
|
|
23598
|
+
const target = join19(parent, name);
|
|
21698
23599
|
if (existsSync8(target))
|
|
21699
23600
|
return text("already exists", 409);
|
|
21700
23601
|
try {
|
|
@@ -21779,6 +23680,282 @@ async function handleMcp(req) {
|
|
|
21779
23680
|
}
|
|
21780
23681
|
return json2(dispatched.body);
|
|
21781
23682
|
}
|
|
23683
|
+
function journalSse(kind, id) {
|
|
23684
|
+
sendSse("journal", JSON.stringify({ kind, id }));
|
|
23685
|
+
}
|
|
23686
|
+
function bodyString(body, key) {
|
|
23687
|
+
const value = body[key];
|
|
23688
|
+
return typeof value === "string" ? value : undefined;
|
|
23689
|
+
}
|
|
23690
|
+
function bodyStringList(body, key) {
|
|
23691
|
+
const value = body[key];
|
|
23692
|
+
if (!Array.isArray(value))
|
|
23693
|
+
return [];
|
|
23694
|
+
return value.filter((item) => typeof item === "string");
|
|
23695
|
+
}
|
|
23696
|
+
function bodyTaskStatus(body, key) {
|
|
23697
|
+
const value = body[key];
|
|
23698
|
+
if (value === undefined)
|
|
23699
|
+
return;
|
|
23700
|
+
if (isJournalTaskStatus(value))
|
|
23701
|
+
return value;
|
|
23702
|
+
throw new JournalRequestError(`${key} must be draft, todo, doing, blocked, or done`);
|
|
23703
|
+
}
|
|
23704
|
+
function bodyTaskPriority(body, key) {
|
|
23705
|
+
const value = body[key];
|
|
23706
|
+
if (value === undefined)
|
|
23707
|
+
return;
|
|
23708
|
+
if (isJournalTaskPriority(value))
|
|
23709
|
+
return value;
|
|
23710
|
+
throw new JournalRequestError(`${key} must be p0, p1, p2, or p3`);
|
|
23711
|
+
}
|
|
23712
|
+
function bodyNumber(body, key) {
|
|
23713
|
+
const value = body[key];
|
|
23714
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
23715
|
+
}
|
|
23716
|
+
async function handleJournal(req) {
|
|
23717
|
+
if (req.method === "GET") {
|
|
23718
|
+
const journal = await loadDailyJournalState(cwd);
|
|
23719
|
+
const tasks = await loadJournalTaskState(cwd);
|
|
23720
|
+
return json2({
|
|
23721
|
+
generation,
|
|
23722
|
+
journal,
|
|
23723
|
+
tasks,
|
|
23724
|
+
labels: collectJournalLabels(journal, tasks)
|
|
23725
|
+
});
|
|
23726
|
+
}
|
|
23727
|
+
if (req.method !== "POST")
|
|
23728
|
+
return text("method not allowed", 405);
|
|
23729
|
+
if (!sideEffectRequestAllowed(req))
|
|
23730
|
+
return text("forbidden", 403);
|
|
23731
|
+
const contentType = req.headers.get("content-type") || "";
|
|
23732
|
+
if (!/^application\/json(?:;|$)/i.test(contentType))
|
|
23733
|
+
return text("unsupported media type", 415);
|
|
23734
|
+
const maxBytes = Math.max(JOURNAL_ENTRY_BODY_MAX_BYTES, JOURNAL_TASK_BODY_MAX_BYTES) + 8192;
|
|
23735
|
+
const length = Number(req.headers.get("content-length") || "0");
|
|
23736
|
+
if (length > maxBytes)
|
|
23737
|
+
return text("payload too large", 413);
|
|
23738
|
+
let body = {};
|
|
23739
|
+
try {
|
|
23740
|
+
const raw = await req.text();
|
|
23741
|
+
if (raw.length > maxBytes)
|
|
23742
|
+
return text("payload too large", 413);
|
|
23743
|
+
body = JSON.parse(raw);
|
|
23744
|
+
} catch {
|
|
23745
|
+
return text("invalid json", 400);
|
|
23746
|
+
}
|
|
23747
|
+
try {
|
|
23748
|
+
const action = body.action;
|
|
23749
|
+
const now = new Date().toISOString();
|
|
23750
|
+
if (action === "add-entry") {
|
|
23751
|
+
const entry = await updateDailyJournalState(cwd, (state) => {
|
|
23752
|
+
const result = addDailyJournalEntry(state, {
|
|
23753
|
+
date: bodyString(body, "date") || "",
|
|
23754
|
+
title: bodyString(body, "title"),
|
|
23755
|
+
body: bodyString(body, "body") || "",
|
|
23756
|
+
labels: body.labels,
|
|
23757
|
+
source: body.source === "ai" ? "ai" : "user"
|
|
23758
|
+
}, now);
|
|
23759
|
+
if (result.ok === false)
|
|
23760
|
+
throw new JournalRequestError(result.error);
|
|
23761
|
+
return { state: result.state, result: result.entry };
|
|
23762
|
+
});
|
|
23763
|
+
journalSse("add-entry", entry.id);
|
|
23764
|
+
return json2({ ok: true, entry, generation });
|
|
23765
|
+
}
|
|
23766
|
+
if (action === "list-github-issues") {
|
|
23767
|
+
const label = bodyString(body, "label");
|
|
23768
|
+
const labels = bodyStringList(body, "labels");
|
|
23769
|
+
if (label)
|
|
23770
|
+
labels.push(label);
|
|
23771
|
+
const issues = readGithubIssueList({
|
|
23772
|
+
cwd,
|
|
23773
|
+
repo: bodyString(body, "repo"),
|
|
23774
|
+
labels,
|
|
23775
|
+
search: bodyString(body, "search"),
|
|
23776
|
+
state: normalizeGithubIssueListState(bodyString(body, "state")),
|
|
23777
|
+
limit: normalizeGithubIssueListLimit(bodyNumber(body, "limit"))
|
|
23778
|
+
});
|
|
23779
|
+
return json2({ ok: true, issues, generation });
|
|
23780
|
+
}
|
|
23781
|
+
if (action === "update-entry") {
|
|
23782
|
+
const id = bodyString(body, "id") || "";
|
|
23783
|
+
if (!id)
|
|
23784
|
+
return text("invalid id", 400);
|
|
23785
|
+
const entry = await updateDailyJournalState(cwd, (state) => {
|
|
23786
|
+
const result = updateDailyJournalEntry(state, id, {
|
|
23787
|
+
date: bodyString(body, "date"),
|
|
23788
|
+
title: bodyString(body, "title"),
|
|
23789
|
+
body: bodyString(body, "body"),
|
|
23790
|
+
labels: body.labels,
|
|
23791
|
+
source: body.source === "ai" ? "ai" : undefined
|
|
23792
|
+
}, now);
|
|
23793
|
+
if (result.ok === false)
|
|
23794
|
+
throw new JournalRequestError(result.error);
|
|
23795
|
+
return { state: result.state, result: result.entry };
|
|
23796
|
+
});
|
|
23797
|
+
journalSse("update-entry", entry.id);
|
|
23798
|
+
return json2({ ok: true, entry, generation });
|
|
23799
|
+
}
|
|
23800
|
+
if (action === "delete-entry") {
|
|
23801
|
+
const id = bodyString(body, "id") || "";
|
|
23802
|
+
if (!id)
|
|
23803
|
+
return text("invalid id", 400);
|
|
23804
|
+
const removed = await updateDailyJournalState(cwd, (state) => {
|
|
23805
|
+
const result = deleteDailyJournalEntry(state, id);
|
|
23806
|
+
return { state: result.state, result: result.removed };
|
|
23807
|
+
});
|
|
23808
|
+
if (removed)
|
|
23809
|
+
journalSse("delete-entry", id);
|
|
23810
|
+
return json2({ ok: true, removed, generation });
|
|
23811
|
+
}
|
|
23812
|
+
if (action === "link-github-issue") {
|
|
23813
|
+
const result = await updateJournalTaskState(cwd, (state) => {
|
|
23814
|
+
const linked = linkGithubIssueTask(state, {
|
|
23815
|
+
issue_number: bodyNumber(body, "issue_number") || 0,
|
|
23816
|
+
repo: bodyString(body, "repo"),
|
|
23817
|
+
title: bodyString(body, "title"),
|
|
23818
|
+
url: bodyString(body, "url"),
|
|
23819
|
+
memo_label: bodyString(body, "memo_label"),
|
|
23820
|
+
status: bodyTaskStatus(body, "status"),
|
|
23821
|
+
priority: bodyTaskPriority(body, "priority"),
|
|
23822
|
+
labels: body.labels,
|
|
23823
|
+
before_id: bodyString(body, "before_id"),
|
|
23824
|
+
after_id: bodyString(body, "after_id"),
|
|
23825
|
+
position: bodyNumber(body, "position")
|
|
23826
|
+
}, now);
|
|
23827
|
+
if (linked.ok === false)
|
|
23828
|
+
throw new JournalRequestError(linked.error);
|
|
23829
|
+
return { state: linked.state, result: linked };
|
|
23830
|
+
});
|
|
23831
|
+
journalSse("link-github-issue", result.task.id);
|
|
23832
|
+
return json2({
|
|
23833
|
+
ok: true,
|
|
23834
|
+
task: result.task,
|
|
23835
|
+
created: result.created,
|
|
23836
|
+
moved: result.moved,
|
|
23837
|
+
generation
|
|
23838
|
+
});
|
|
23839
|
+
}
|
|
23840
|
+
if (action === "add-task") {
|
|
23841
|
+
const task = await updateJournalTaskState(cwd, (state) => {
|
|
23842
|
+
const result = addJournalTask(state, {
|
|
23843
|
+
title: bodyString(body, "title") || "",
|
|
23844
|
+
body: bodyString(body, "body"),
|
|
23845
|
+
status: bodyTaskStatus(body, "status"),
|
|
23846
|
+
priority: bodyTaskPriority(body, "priority"),
|
|
23847
|
+
labels: body.labels,
|
|
23848
|
+
due_date: bodyString(body, "due_date"),
|
|
23849
|
+
source_date: bodyString(body, "source_date"),
|
|
23850
|
+
journal_entry_id: bodyString(body, "journal_entry_id"),
|
|
23851
|
+
before_id: bodyString(body, "before_id"),
|
|
23852
|
+
after_id: bodyString(body, "after_id"),
|
|
23853
|
+
position: bodyNumber(body, "position")
|
|
23854
|
+
}, now);
|
|
23855
|
+
if (result.ok === false)
|
|
23856
|
+
throw new JournalRequestError(result.error);
|
|
23857
|
+
return { state: result.state, result: result.task };
|
|
23858
|
+
});
|
|
23859
|
+
journalSse("add-task", task.id);
|
|
23860
|
+
return json2({ ok: true, task, generation });
|
|
23861
|
+
}
|
|
23862
|
+
if (action === "update-task") {
|
|
23863
|
+
const id = bodyString(body, "id") || "";
|
|
23864
|
+
if (!id)
|
|
23865
|
+
return text("invalid id", 400);
|
|
23866
|
+
const task = await updateJournalTaskState(cwd, (state) => {
|
|
23867
|
+
const result = updateJournalTask(state, id, {
|
|
23868
|
+
title: bodyString(body, "title"),
|
|
23869
|
+
body: bodyString(body, "body"),
|
|
23870
|
+
status: bodyTaskStatus(body, "status"),
|
|
23871
|
+
priority: bodyTaskPriority(body, "priority"),
|
|
23872
|
+
labels: body.labels,
|
|
23873
|
+
due_date: body.due_date === null ? null : bodyString(body, "due_date"),
|
|
23874
|
+
source_date: body.source_date === null ? null : bodyString(body, "source_date"),
|
|
23875
|
+
journal_entry_id: body.journal_entry_id === null ? null : bodyString(body, "journal_entry_id")
|
|
23876
|
+
}, now);
|
|
23877
|
+
if (result.ok === false)
|
|
23878
|
+
throw new JournalRequestError(result.error);
|
|
23879
|
+
return { state: result.state, result: result.task };
|
|
23880
|
+
});
|
|
23881
|
+
journalSse("update-task", task.id);
|
|
23882
|
+
return json2({ ok: true, task, generation });
|
|
23883
|
+
}
|
|
23884
|
+
if (action === "move-task") {
|
|
23885
|
+
const id = bodyString(body, "id") || "";
|
|
23886
|
+
if (!id)
|
|
23887
|
+
return text("invalid id", 400);
|
|
23888
|
+
const task = await updateJournalTaskState(cwd, (state) => {
|
|
23889
|
+
const result = moveJournalTask(state, id, {
|
|
23890
|
+
status: bodyTaskStatus(body, "status"),
|
|
23891
|
+
before_id: bodyString(body, "before_id"),
|
|
23892
|
+
after_id: bodyString(body, "after_id"),
|
|
23893
|
+
position: bodyNumber(body, "position")
|
|
23894
|
+
}, now);
|
|
23895
|
+
if (result.ok === false)
|
|
23896
|
+
throw new JournalRequestError(result.error);
|
|
23897
|
+
return { state: result.state, result: result.task };
|
|
23898
|
+
});
|
|
23899
|
+
journalSse("move-task", task.id);
|
|
23900
|
+
return json2({ ok: true, task, generation });
|
|
23901
|
+
}
|
|
23902
|
+
if (action === "claim-task") {
|
|
23903
|
+
const id = bodyString(body, "id") || "";
|
|
23904
|
+
if (!id)
|
|
23905
|
+
return text("invalid id", 400);
|
|
23906
|
+
const task = await updateJournalTaskState(cwd, (state) => {
|
|
23907
|
+
const result = claimJournalTask(state, id, {
|
|
23908
|
+
by: bodyString(body, "by"),
|
|
23909
|
+
lease_minutes: bodyNumber(body, "lease_minutes"),
|
|
23910
|
+
wip_limit: bodyNumber(body, "wip_limit")
|
|
23911
|
+
}, now);
|
|
23912
|
+
if (result.ok === false)
|
|
23913
|
+
throw new JournalRequestError(result.error);
|
|
23914
|
+
return { state: result.state, result: result.task };
|
|
23915
|
+
});
|
|
23916
|
+
journalSse("claim-task", task.id);
|
|
23917
|
+
return json2({ ok: true, task, generation });
|
|
23918
|
+
}
|
|
23919
|
+
if (action === "complete-task") {
|
|
23920
|
+
const id = bodyString(body, "id") || "";
|
|
23921
|
+
if (!id)
|
|
23922
|
+
return text("invalid id", 400);
|
|
23923
|
+
const task = await updateJournalTaskState(cwd, (state) => {
|
|
23924
|
+
const result = completeJournalTask(state, id, {
|
|
23925
|
+
by: bodyString(body, "by"),
|
|
23926
|
+
note: bodyString(body, "note"),
|
|
23927
|
+
source: body.source === "user" ? "user" : "ai"
|
|
23928
|
+
}, now);
|
|
23929
|
+
if (result.ok === false)
|
|
23930
|
+
throw new JournalRequestError(result.error);
|
|
23931
|
+
return { state: result.state, result: result.task };
|
|
23932
|
+
});
|
|
23933
|
+
journalSse("complete-task", task.id);
|
|
23934
|
+
return json2({ ok: true, task, generation });
|
|
23935
|
+
}
|
|
23936
|
+
if (action === "delete-task") {
|
|
23937
|
+
const id = bodyString(body, "id") || "";
|
|
23938
|
+
if (!id)
|
|
23939
|
+
return text("invalid id", 400);
|
|
23940
|
+
const removed = await updateJournalTaskState(cwd, (state) => {
|
|
23941
|
+
const result = deleteJournalTask(state, id);
|
|
23942
|
+
return { state: result.state, result: result.removed };
|
|
23943
|
+
});
|
|
23944
|
+
if (removed)
|
|
23945
|
+
journalSse("delete-task", id);
|
|
23946
|
+
return json2({ ok: true, removed, generation });
|
|
23947
|
+
}
|
|
23948
|
+
} catch (error) {
|
|
23949
|
+
if (error instanceof GithubIssueListError) {
|
|
23950
|
+
return text(error.message, error.status);
|
|
23951
|
+
}
|
|
23952
|
+
if (error instanceof JournalRequestError) {
|
|
23953
|
+
return text(error.message, error.status);
|
|
23954
|
+
}
|
|
23955
|
+
throw error;
|
|
23956
|
+
}
|
|
23957
|
+
return text("invalid action", 400);
|
|
23958
|
+
}
|
|
21782
23959
|
async function handleAnnotations(req) {
|
|
21783
23960
|
if (req.method === "GET")
|
|
21784
23961
|
return json2(await loadAnnotationsState(cwd));
|
|
@@ -21995,8 +24172,9 @@ function restartWorktreeWatch() {
|
|
|
21995
24172
|
}
|
|
21996
24173
|
worktreeWatch = startScopedWorktreeWatch();
|
|
21997
24174
|
}
|
|
21998
|
-
var WEB_ROOT, VERSION, DEFAULT_ARGS, PREVIEW_HUNKS_DEFAULT = 3, PREVIEW_LINES_DEFAULT = 1200, WATCHED_ASSET_FILES, SIZE_SMALL = 2000, SIZE_MEDIUM = 8000, SIZE_LARGE = 20000, LINE_INDEX_MIN_START = 1e4, LINE_INDEX_MAX_FILE_BYTES, BLOB_LINE_CACHE_MAX_BYTES, MAX_UPLOAD_FILE_BYTES, MAX_UPLOAD_TOTAL_BYTES, MAX_UPLOAD_BODY_BYTES, MAX_UPLOAD_FILES = 50, SAFE_UPLOAD_EXTENSIONS, generation = 1, cwd, cliArgs, listenPort = 0, openAfterStart = false, commandOverrides, cwdWasExplicit = false, scopeOmitDirNames, scopeOmitDirCliOverride = null, scopeExcludeNames, scopeWatchLimit, uploadEnabled = true, enc, sseClients, sseKeepalives, fileCache, blameCache, BLAME_CACHE_MAX = 64, metaCache, fileListCache, lineIndexCache, blobLineIndexCache, blobBytesCache, blobLineCacheBytes = 0, safePath, MCP_MAX_BODY_BYTES = 1048576, MCP_INSTRUCTIONS, isCodeViewerInternalPath, watchLimitReached = null, server, worktreeWatch = null, shuttingDown = false;
|
|
24175
|
+
var WEB_ROOT, VERSION, DEFAULT_ARGS, PREVIEW_HUNKS_DEFAULT = 3, PREVIEW_LINES_DEFAULT = 1200, WATCHED_ASSET_FILES, SIZE_SMALL = 2000, SIZE_MEDIUM = 8000, SIZE_LARGE = 20000, LINE_INDEX_MIN_START = 1e4, LINE_INDEX_MAX_FILE_BYTES, BLOB_LINE_CACHE_MAX_BYTES, MAX_UPLOAD_FILE_BYTES, MAX_UPLOAD_TOTAL_BYTES, MAX_UPLOAD_BODY_BYTES, MAX_UPLOAD_FILES = 50, SAFE_UPLOAD_EXTENSIONS, generation = 1, cwd, cliArgs, listenPort = 0, openAfterStart = false, commandOverrides, cwdWasExplicit = false, scopeOmitDirNames, scopeOmitDirCliOverride = null, scopeExcludeNames, scopeWatchLimit, uploadEnabled = true, enc, sseClients, sseKeepalives, fileCache, blameCache, BLAME_CACHE_MAX = 64, metaCache, fileListCache, lineIndexCache, blobLineIndexCache, blobBytesCache, blobLineCacheBytes = 0, safePath, MCP_MAX_BODY_BYTES = 1048576, MCP_INSTRUCTIONS, JournalRequestError, isCodeViewerInternalPath, watchLimitReached = null, server, worktreeWatch = null, shuttingDown = false;
|
|
21999
24176
|
var init_preview = __esm(async () => {
|
|
24177
|
+
init_journal();
|
|
22000
24178
|
init_routes();
|
|
22001
24179
|
init_annotations();
|
|
22002
24180
|
init_cache();
|
|
@@ -22004,6 +24182,8 @@ var init_preview = __esm(async () => {
|
|
|
22004
24182
|
init_dev_assets();
|
|
22005
24183
|
init_doctor();
|
|
22006
24184
|
init_git();
|
|
24185
|
+
init_github_issues();
|
|
24186
|
+
init_journal2();
|
|
22007
24187
|
init_mcp();
|
|
22008
24188
|
init_raw_file_headers();
|
|
22009
24189
|
init_root();
|
|
@@ -22013,8 +24193,8 @@ var init_preview = __esm(async () => {
|
|
|
22013
24193
|
init_server_registry();
|
|
22014
24194
|
init_state_store();
|
|
22015
24195
|
init_worktree_watcher();
|
|
22016
|
-
WEB_ROOT =
|
|
22017
|
-
VERSION = JSON.parse(
|
|
24196
|
+
WEB_ROOT = join19(ROOT, "web");
|
|
24197
|
+
VERSION = JSON.parse(readFileSync9(join19(ROOT, "package.json"), "utf8")).version;
|
|
22018
24198
|
DEFAULT_ARGS = ["HEAD"];
|
|
22019
24199
|
WATCHED_ASSET_FILES = ["index.html", "style.css", "app.js"];
|
|
22020
24200
|
LINE_INDEX_MAX_FILE_BYTES = 256 * 1024 * 1024;
|
|
@@ -22076,6 +24256,13 @@ var init_preview = __esm(async () => {
|
|
|
22076
24256
|
blobBytesCache = new Map;
|
|
22077
24257
|
safePath = isSafePath;
|
|
22078
24258
|
MCP_INSTRUCTIONS = buildMcpInstructions();
|
|
24259
|
+
JournalRequestError = class JournalRequestError extends Error {
|
|
24260
|
+
status;
|
|
24261
|
+
constructor(message, status = 400) {
|
|
24262
|
+
super(message);
|
|
24263
|
+
this.status = status;
|
|
24264
|
+
}
|
|
24265
|
+
};
|
|
22079
24266
|
isCodeViewerInternalPath = isToolInternalPath;
|
|
22080
24267
|
parseCli();
|
|
22081
24268
|
applyPersistedSettings(await loadAppSettingsState(cwd));
|
|
@@ -22141,6 +24328,8 @@ var init_preview = __esm(async () => {
|
|
|
22141
24328
|
}
|
|
22142
24329
|
if (url.pathname === "/_mcp")
|
|
22143
24330
|
return handleMcp(req);
|
|
24331
|
+
if (url.pathname === "/_journal")
|
|
24332
|
+
return handleJournal(req);
|
|
22144
24333
|
if (url.pathname === "/_annotations")
|
|
22145
24334
|
return handleAnnotations(req);
|
|
22146
24335
|
if (url.pathname === "/_refs") {
|
|
@@ -22271,6 +24460,9 @@ if (process.argv[2] === "agent-help") {
|
|
|
22271
24460
|
} else if (process.argv[2] === "annotate") {
|
|
22272
24461
|
const { runAnnotateCli: runAnnotateCli2 } = await Promise.resolve().then(() => (init_annotate_cli(), exports_annotate_cli));
|
|
22273
24462
|
await runAnnotateCli2(process.argv.slice(3));
|
|
24463
|
+
} else if (process.argv[2] === "journal") {
|
|
24464
|
+
const { runJournalCli: runJournalCli2 } = await Promise.resolve().then(() => (init_journal_cli(), exports_journal_cli));
|
|
24465
|
+
await runJournalCli2(process.argv.slice(3));
|
|
22274
24466
|
} else if (process.argv[2] === "query") {
|
|
22275
24467
|
const sub = process.argv[3];
|
|
22276
24468
|
const helpOnly = !sub || sub === "help" || sub === "agent-help" || sub === "--help" || sub === "-h";
|