feat: per-change-block hunk actions (#50), codex lanes without a pipeline, macOS LaunchAgent
- git (#50): stage-hunk/discard-hunk take an optional `block` — one run of consecutive +/- lines inside an @@ hunk. `onlyBlock` (server/lib/git-diff.js) cuts git's fresh hunk down to it, folding the other blocks into the side being checked; LaneDiffView hangs one review bar per block (unified + split) and its arrows walk blocks. - git exclude: entries AgentHub writes for a lane go to the COMMON git dir with the lane's path prefix (server/lib/git-exclude.js, used by router-apply and lane-mcp) — a linked worktree's own info/exclude is never read by git, and a subdir lane has no .git. MCP sync no longer fails with ENOTDIR on a worktree lane or creates a stray .git in a non-repo lane. - codex lanes carry no pipeline: codex-ingest mirrors only idle/running, codex-nudge is removed, agents install writes nothing for codex, the Workspace hides stage/pipeline/feature pickers/PipelineMap for them, and every server start clears leftover codex stage data and the old AGENTS.md stage block. - Changes panel: a whole file opens for reading and enters the editor on a click on its text (caret at the clicked line), not automatically. - macOS: LaunchAgent start-at-login (scripts/launchd-service.js, picked by scripts/service-manager.js). - Adds the AgentHub introduction slides and their image-embedding script.
This commit is contained in:
@@ -34,6 +34,7 @@ const path = require("node:path");
|
||||
|
||||
const routers = require("./ai-routers");
|
||||
const { patchSettings } = require("./cc-mutate");
|
||||
const { excludeFromGit } = require("./git-exclude");
|
||||
const { getCodexHome } = require("./codex-home");
|
||||
const { lanesByRouter, setRouterProfile } = require("./lanes");
|
||||
|
||||
@@ -72,26 +73,6 @@ function readOrNull(name) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The lane's own git directory. For a normal clone that is `<cwd>/.git`; for a
|
||||
* LINKED WORKTREE — which is what every managed lane is — `.git` is a pointer
|
||||
* FILE, and joining `info/exclude` onto it names a path that cannot exist.
|
||||
*
|
||||
* @returns {string|null} absolute git dir, or null when this is not a repo
|
||||
*/
|
||||
function gitDirOf(laneDir) {
|
||||
const dotGit = path.join(laneDir, ".git");
|
||||
let stat;
|
||||
try {
|
||||
stat = fs.statSync(dotGit);
|
||||
} catch {
|
||||
return null; // not a git repo — a perfectly normal adopted directory
|
||||
}
|
||||
if (stat.isDirectory()) return dotGit;
|
||||
const match = fs.readFileSync(dotGit, "utf8").match(/^gitdir:\s*(.+)$/m);
|
||||
return match ? path.resolve(laneDir, match[1].trim()) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Idempotently keep `.claude/settings.local.json` out of git, for two reasons
|
||||
* that both matter:
|
||||
@@ -101,28 +82,12 @@ function gitDirOf(laneDir) {
|
||||
* is deleted and the lane silently loses its router.
|
||||
* - The file holds an API key. Untracked means `git add -A` can commit it.
|
||||
*
|
||||
* Best effort: a lane that is not a repo, or a git dir that cannot be written,
|
||||
* must not fail an otherwise-successful attach.
|
||||
* The backup directory beside it, for the same reason plus one more: those
|
||||
* copies hold the API key too, and they are what a user sees as a dozen
|
||||
* untracked files appearing in their project while they switch routers.
|
||||
*/
|
||||
function excludeSettingsFromGit(laneDir) {
|
||||
// The backup directory beside it, for the same reason plus one more: those
|
||||
// copies hold the API key too, and they are what a user sees as a dozen
|
||||
// untracked files appearing in their project while they switch routers.
|
||||
const lines = [".claude/settings.local.json", ".claude/.cc-config-backups/"];
|
||||
try {
|
||||
const gitDir = gitDirOf(laneDir);
|
||||
if (!gitDir) return;
|
||||
const excludePath = path.join(gitDir, "info", "exclude");
|
||||
fs.mkdirSync(path.dirname(excludePath), { recursive: true });
|
||||
let existing = fs.existsSync(excludePath) ? fs.readFileSync(excludePath, "utf8") : "";
|
||||
const present = new Set(existing.split("\n"));
|
||||
const missing = lines.filter((line) => !present.has(line));
|
||||
if (missing.length === 0) return;
|
||||
if (existing && !existing.endsWith("\n")) existing += "\n";
|
||||
fs.writeFileSync(excludePath, `${existing}${missing.join("\n")}\n`);
|
||||
} catch {
|
||||
/* best effort — see above */
|
||||
}
|
||||
excludeFromGit(laneDir, [".claude/settings.local.json", ".claude/.cc-config-backups/"]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user