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:
+32
-46
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @file Installs a lane's driving instructions. For a `claude` lane that is
|
||||
* the ship-feature-lane pipeline's agent templates
|
||||
* @file Installs a `claude` lane's driving instructions: the
|
||||
* ship-feature-lane pipeline's agent templates
|
||||
* (qc-local, senior-gate-reviewer) into a lane's own .claude/agents/, so a
|
||||
* driving session can launch them by the BARE subagent_type name. Source is
|
||||
* the repo's own `agents/` — the same files the agenthub plugin ships, where
|
||||
@@ -20,50 +20,36 @@ const TEMPLATE_DIR = path.join(__dirname, "..", "..", "agents");
|
||||
const TEMPLATE_FILES = ["qc-local.md", "senior-gate-reviewer.md"];
|
||||
const EXCLUDE_LINE = ".claude/agents/";
|
||||
|
||||
// Codex reads no `.claude/agents/`, so for a codex lane the useful thing to
|
||||
// install is the one instruction it DOES read: AGENTS.md, the project doc it
|
||||
// loads from the working directory. Marker-delimited so a reinstall replaces
|
||||
// exactly this block and never the user's own prose, and appended rather than
|
||||
// written over an existing file.
|
||||
// The block older builds wrote into a codex lane's AGENTS.md. Codex lanes no
|
||||
// longer carry a pipeline, so `removeStageDoc` takes it back out.
|
||||
const STAGE_BLOCK_START = "<!-- agenthub:stage:start -->";
|
||||
const STAGE_BLOCK_END = "<!-- agenthub:stage:end -->";
|
||||
const STAGE_BLOCK = `${STAGE_BLOCK_START}
|
||||
## Declare your lane stage as you work
|
||||
|
||||
This directory is watched by AgentHub. Its pipeline map advances on its own by
|
||||
*inferring* your stage from your tool calls, but an inference carries no
|
||||
evidence — the node stays amber, never green. Only your own declaration does:
|
||||
|
||||
\`\`\`bash
|
||||
agenthub stage <stage> --tool codex --evidence "<what is actually done>"
|
||||
\`\`\`
|
||||
|
||||
- Call it at every real transition — starting to plan, starting to implement,
|
||||
running tests, opening the PR — not once at the end.
|
||||
- \`--tool codex\` matters: one directory can back both a \`claude\` lane and a
|
||||
\`codex\` lane, and without a tool the command fails rather than guess. Inside
|
||||
a pane the dashboard started you need nothing — it carries \`AGENTHUB_LANE_ID\`.
|
||||
- \`agenthub stage\` warns on stderr and names the valid nodes if a stage matches
|
||||
none of this lane's pipeline template.
|
||||
- If it reports that no lane owns this directory, skip the stage calls and
|
||||
carry on. Stage reporting is reporting, never control flow.
|
||||
- A one-line \`[agenthub]\` reminder may be typed straight into this pane asking for
|
||||
evidence on the current stage. That is the dashboard, not the user — treat it
|
||||
as the same instruction as this section and make the real call.
|
||||
${STAGE_BLOCK_END}`;
|
||||
|
||||
/** Append the stage block to `<cwd>/AGENTS.md`, or replace an existing one. */
|
||||
function installStageDoc(cwd) {
|
||||
/**
|
||||
* Remove AgentHub's stage block from `<cwd>/AGENTS.md`, leaving every other
|
||||
* line as it was. A file left with nothing else in it was AgentHub's own
|
||||
* creation, so it is deleted. Never throws.
|
||||
*
|
||||
* @returns {boolean} whether the file changed
|
||||
*/
|
||||
function removeStageDoc(cwd) {
|
||||
const file = path.join(cwd, "AGENTS.md");
|
||||
const existing = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
|
||||
const start = existing.indexOf(STAGE_BLOCK_START);
|
||||
const end = existing.indexOf(STAGE_BLOCK_END);
|
||||
const next =
|
||||
start !== -1 && end > start
|
||||
? existing.slice(0, start) + STAGE_BLOCK + existing.slice(end + STAGE_BLOCK_END.length)
|
||||
: `${existing.trimEnd()}${existing.trim() ? "\n\n" : ""}${STAGE_BLOCK}\n`;
|
||||
fs.writeFileSync(file, next);
|
||||
return "AGENTS.md";
|
||||
try {
|
||||
const doc = fs.readFileSync(file, "utf8");
|
||||
const start = doc.indexOf(STAGE_BLOCK_START);
|
||||
const end = doc.indexOf(STAGE_BLOCK_END, start);
|
||||
if (start === -1 || end === -1) return false;
|
||||
const rest = (
|
||||
doc.slice(0, start).trimEnd() +
|
||||
"\n\n" +
|
||||
doc.slice(end + STAGE_BLOCK_END.length).trimStart()
|
||||
).trim();
|
||||
if (rest) fs.writeFileSync(file, `${rest}\n`);
|
||||
else fs.rmSync(file);
|
||||
return true;
|
||||
} catch {
|
||||
return false; // no file, or unreadable — nothing of ours to take out
|
||||
}
|
||||
}
|
||||
|
||||
/** The dir shared across every worktree of a repo — where info/exclude
|
||||
@@ -80,8 +66,8 @@ async function commonGitDir(cwd) {
|
||||
* any existing copy — this is a reinstall, not a merge) and idempotently
|
||||
* git-exclude that directory in the shared common git dir.
|
||||
*
|
||||
* A `codex` lane gets the AGENTS.md stage block instead — Codex never reads
|
||||
* `.claude/agents/`, and its own working copy is the only channel it does read.
|
||||
* A `codex` lane gets nothing: Codex never reads `.claude/agents/`, and codex
|
||||
* lanes carry no pipeline to drive.
|
||||
*
|
||||
* @param {{cwd: string, tool?: string}} lane
|
||||
* @returns {Promise<{installed: string[]}>}
|
||||
@@ -93,7 +79,7 @@ async function installAgents(lane) {
|
||||
});
|
||||
}
|
||||
|
||||
if (lane.tool === "codex") return { installed: [installStageDoc(lane.cwd)] };
|
||||
if (lane.tool === "codex") return { installed: [] };
|
||||
|
||||
const dest = path.join(lane.cwd, ".claude", "agents");
|
||||
fs.mkdirSync(dest, { recursive: true });
|
||||
@@ -116,4 +102,4 @@ async function installAgents(lane) {
|
||||
return { installed };
|
||||
}
|
||||
|
||||
module.exports = { installAgents };
|
||||
module.exports = { installAgents, removeStageDoc };
|
||||
|
||||
Reference in New Issue
Block a user