Files
AgentHub/docs/superpowers/plans/2026-08-05-integration-toggle.md
T
nntrivi2001 17d56d0e01 refactor!: rebrand CCAM to AgentHub
Clean break, no compatibility alias. Renames the product name, the CLI
binary (`ccam` -> `agenthub`), the env var prefix (`CCAM_` ->
`AGENTHUB_`), the Prometheus metric namespace (`ccam_*` ->
`agenthub_*`), the tmux session prefix (`ccam-lane-<id>` ->
`agenthub-lane-<id>`), the per-repo runtime profile directory
(`.ccam/profile` -> `.agenthub/profile`), the lanes state root
(`~/.claude/ccam-lanes` -> `~/.claude/agenthub-lanes`), the bundled
plugins (`plugins/ccam*` -> `plugins/agenthub*`) and their slash
commands (`/ccam-doctor` -> `/agenthub-doctor`, `/ccam:*` ->
`/agenthub:*`).

Because there is no alias, an existing install has to be updated by
hand: rename its `.ccam/profile` directory and any `CCAM_*` variables,
and reinstall the plugin under its new name.

Two other name families are deliberately left alone here, since they are
not just strings: the `Claude-Code-Monitor` repository/marketplace name,
and the `agent-dashboard` npm package plus its `~/.claude/agent-dashboard`
runtime directory, which holds the live database and needs a migration
rather than a rename.

Verified: server 1406/1406, client 459/459, `grep -i ccam` clean across
tracked files, `openapi.yaml` regenerated (95 paths, unchanged count).
2026-09-07 12:09:15 +07:00

13 KiB

F3a — Integration Toggle Reader Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: agenthub lanes integration <name> reads a lane's .agenthub/profile/integrations.env and reports whether <NAME>_ENABLED=1 is set, exit 0/1 — so a driving skill can gate a stage on a real check instead of a hardcoded assumption.

Architecture: One function in the existing lane-profile.js (reusing its already-exported parseEnvFile), one lightweight route, one CLI subcommand. No new files beyond a test.

Tech Stack: Plain node:fs/node:path, the existing parseEnvFile helper. No git calls, no new dependency.

Global Constraints

  • Every applicable source file (.js) MUST start with the project's authorship header — verify with bash .claude/skills/file-headers/scripts/check-headers.sh.
  • Missing integrations.env or missing <NAME>_ENABLED key → false (off), never an error — same off-by-default shape every other optional declaration in this codebase already follows.
  • Do not touch GET /api/lanes/:id/runtime — this is a separate, lightweight endpoint (GET /:id/integrations/:name), not an addition to the polled/broadcast runtime payload.
  • Never use git add -A. Stage exactly the files each task names.
  • Run npm run test:server (full suite) plus bash .claude/skills/file-headers/scripts/check-headers.sh before every commit.

Task 1: isIntegrationEnabled in lane-profile.js

Files:

  • Modify: server/lib/lane-profile.js
  • Test: server/__tests__/lane-profile.test.js

Interfaces:

  • Produces: isIntegrationEnabled(lane, name) => boolean. name is the lowercase form ("tracker", "dev_qc", "ci_wait"); the function uppercases it and appends _ENABLED to build the env key it looks for (TRACKER_ENABLED, DEV_QC_ENABLED, CI_WAIT_ENABLED).

  • Step 1: Write the failing tests

Add to server/__tests__/lane-profile.test.js (it already has makeLane/writeProfile helpers — reuse them):

describe("isIntegrationEnabled", () => {
  it("returns false when integrations.env doesn't exist at all", () => {
    const lane = makeLane();
    writeProfile(lane.cwd, "PORTS=api\n");
    assert.equal(profileLib.isIntegrationEnabled(lanesLib.getLane(lane.id), "tracker"), false);
  });

  it("returns true when <NAME>_ENABLED=1 is set", () => {
    const lane = makeLane();
    writeProfile(lane.cwd, "PORTS=api\n");
    fs.writeFileSync(
      path.join(lane.cwd, ".agenthub", "profile", "integrations.env"),
      "TRACKER_ENABLED=1\nTRACKER_PROJECT=demo\n"
    );
    assert.equal(profileLib.isIntegrationEnabled(lanesLib.getLane(lane.id), "tracker"), true);
  });

  it("returns false when the flag is 0 or absent from an existing file", () => {
    const lane = makeLane();
    writeProfile(lane.cwd, "PORTS=api\n");
    fs.writeFileSync(
      path.join(lane.cwd, ".agenthub", "profile", "integrations.env"),
      "DEV_QC_ENABLED=0\n"
    );
    assert.equal(profileLib.isIntegrationEnabled(lanesLib.getLane(lane.id), "dev_qc"), false);
    assert.equal(profileLib.isIntegrationEnabled(lanesLib.getLane(lane.id), "ci_wait"), false);
  });
});
  • Step 2: Run test to verify it fails

Run: node --test server/__tests__/lane-profile.test.js Expected: FAIL — profileLib.isIntegrationEnabled is not a function.

  • Step 3: Implement

In server/lib/lane-profile.js, add near resolveProfile (after it, since it reuses the same two-location search pattern):

/**
 * Whether a named integration is turned on for this lane — reads
 * .agenthub/profile/integrations.env (same two-location search as profile.env:
 * the lane's own working copy first, the source repo second) and checks
 * <NAME>_ENABLED=1. A missing file or missing key is off, never an error —
 * same off-by-default shape every other optional declaration follows.
 *
 * @param {object} lane - Lane row (`cwd`, `source_repo`).
 * @param {string} name - Lowercase integration name, e.g. "tracker", "dev_qc", "ci_wait".
 * @returns {boolean}
 */
function isIntegrationEnabled(lane, name) {
  const candidates = [lane.cwd, lane.source_repo].filter(Boolean);
  const key = `${name.toUpperCase()}_ENABLED`;
  for (const root of candidates) {
    const filePath = path.join(root, PROFILE_SUBDIR, "integrations.env");
    if (!fs.existsSync(filePath)) continue;
    let declared;
    try {
      declared = parseEnvFile(fs.readFileSync(filePath, "utf8"));
    } catch {
      continue;
    }
    return declared[key] === "1";
  }
  return false;
}

Update module.exports (server/lib/lane-profile.js:422-433):

module.exports = {
  HOOKS,
  DEFAULTS,
  PROFILE_SUBDIR,
  parseEnvFile,
  splitList,
  parseQcBootEnv,
  resolveProfile,
  profileSearchPaths,
  isIntegrationEnabled,
  hookEnv,
  runHook,
};
  • Step 4: Run tests to verify they pass

Run: node --test server/__tests__/lane-profile.test.js Expected: PASS — all 3 new tests, plus every existing test in the file still green.

  • Step 5: Header check + full suite
bash .claude/skills/file-headers/scripts/check-headers.sh
npm run test:server
  • Step 6: Commit
git add server/lib/lane-profile.js server/__tests__/lane-profile.test.js
git commit -m "feat(lanes): add isIntegrationEnabled toggle check (F3a)"

Task 2: Route + CLI

Files:

  • Modify: server/routes/lanes.js
  • Modify: bin/agenthub.js

Interfaces:

  • Consumes: isIntegrationEnabled(lane, name) => boolean from Task 1 (require("../lib/lane-profile") — already imported in server/routes/lanes.js for HOOKS/runHook/resolveProfile, just add isIntegrationEnabled to that existing destructure).

  • Produces: GET /api/lanes/:id/integrations/:name → {enabled: boolean}. agenthub lanes integration <name> [<id>] → prints on/off, process.exitCode 0 when enabled, 1 when disabled.

  • Step 1: Add the route

In server/routes/lanes.js, extend the existing import (server/routes/lanes.js:35):

const { HOOKS, runHook, resolveProfile, isIntegrationEnabled } = require("../lib/lane-profile");

Add the route directly after the /:id/mcp/sync route (added in F1, search router.post("/:id/mcp/sync" to find it):

/**
 * Whether a named integration (tracker, dev_qc, ci_wait, ...) is turned on
 * for this lane — reads .agenthub/profile/integrations.env. Read-only, no
 * sameOriginGuard needed (same reasoning GET /:id/git already documents:
 * that guard exists for destructive actions).
 */
router.get("/:id/integrations/:name", (req, res) => {
  const lane = lanesLib.getLane(req.params.id);
  if (!lane) return res.status(404).json({ error: { code: "ENOLANE", message: "lane not found" } });
  res.json({ enabled: isIntegrationEnabled(lane, req.params.name) });
});
  • Step 2: Add the CLI subcommand

In bin/agenthub.js, add near cmdLanesMcpSync:

/** `agenthub lanes integration <name> [<id>]` — check whether a named
 *  integration (tracker, dev_qc, ci_wait) is turned on for this lane, per
 *  its .agenthub/profile/integrations.env. Exit 0 = on, 1 = off. */
async function cmdLanesIntegration(args) {
  const name = args.find((arg) => !arg.startsWith("--"));
  if (!name) {
    console.error("usage: agenthub lanes integration <name> [<id>]");
    process.exitCode = 1;
    return;
  }
  const resolved = await resolveLaneArg(args.filter((a) => a !== name));
  if (!resolved) return;
  const { enabled } = await get(`/api/lanes/${resolved.laneId}/integrations/${encodeURIComponent(name)}`);
  console.log(enabled ? "on" : "off");
  process.exitCode = enabled ? 0 : 1;
}

Wire the dispatcher — in the lanes case, right after the mcp sync check:

      if (rest[0] === "mcp" && rest[1] === "sync") {
        return cmdLanesMcpSync(rest.slice(2));
      }

Add directly below it:

      if (rest[0] === "integration") {
        return cmdLanesIntegration(rest.slice(1));
      }

Add the help-catalog entry right after lanes mcp sync:

      [
        "lanes integration",
        "<name> [<id>]",
        "Check whether a named integration (tracker, dev_qc, ci_wait) is on for this lane — exit 0/1",
      ],
  • Step 3: Manual smoke check
npm run dev &
sleep 3
curl -s http://localhost:4820/api/lanes/1/integrations/tracker
echo
node bin/agenthub.js lanes integration tracker 1
echo "exit: $?"

Expected: {"enabled":false} (or true if lane 1's profile happens to declare it) and matching CLI output/exit code. Stop the dev server afterward.

  • Step 4: Run the full suite + header check
bash .claude/skills/file-headers/scripts/check-headers.sh
npm run test:server
  • Step 5: Commit
git add server/routes/lanes.js bin/agenthub.js
git commit -m "feat(lanes): add agenthub lanes integration CLI + route (F3a)"

Task 3: Wire SKILL.md's hardcoded-off checks to the real primitive

Files:

  • Modify: .claude/skills/ship-feature-lane/SKILL.md
  • Modify: docs/LANES.md
  • Modify: docs/superpowers/plans/2026-08-03-shipyard-parity-lanes.md

Interfaces: none — documentation, but now backed by a real command.

  • Step 1: SKILL.md Setup — replace the hardcoded-off line

Find:

- **Integration toggles are currently OFF.** Tracker (ticket-filing), dev-site QC, and CI deploy-wait integrations are not built in AgentHub yet — treat all three as permanently off for this run: Stage 9 (ticket) is skipped entirely, Stage 13's dev-QC half is skipped, Stage 13's dev-CI-wait half is skipped, and Stage 10's CI watch always uses the plain `gh pr checks` path (never `agenthub ci`, which doesn't exist). When these land, this skill gets a follow-up edit to make the checks real — do not invent a check now.

Replace with:

- **Integration toggles: check, don't assume.** `agenthub lanes integration tracker`, `agenthub lanes integration dev_qc`, `agenthub lanes integration ci_wait` each exit 0 (on) or 1 (off), reading the profile's `integrations.env`. No agent exists yet to actually FILE a ticket or run dev-QC even when a toggle reads on (`ticketer`/`dev-qc` are a later task) — so regardless of the check's result, Stage 9 (ticket) stays skipped, Stage 13's dev-QC and dev-CI-wait halves stay skipped, and Stage 10's CI watch keeps using the plain `gh pr checks` path (`agenthub ci` doesn't exist yet). Check the toggle where noted below anyway, so the evidence you record is honest about whether the PROFILE wants the integration on, distinct from whether AgentHub can act on it yet.
  • Step 2: Stage 9 (ticket) — record the toggle in evidence

Find the Stage 9 section (search grep -n "^### 9" .claude/skills/ship-feature-lane/SKILL.md) and its skip line (likely something like agenthub stage reported --status ... or similar noting the skip — read the current text first with sed -n '/^### 9/,/^### 10/p' .claude/skills/ship-feature-lane/SKILL.md to get its exact current wording before editing, since this section's precise text was written in E1 and may have shifted slightly across later edits). Add one line noting agenthub lanes integration tracker was checked, e.g. append to the existing skip explanation: (checked \agenthub lanes integration tracker` — even when on, no ticketer agent exists yet to act on it)`.

  • Step 3: docs/LANES.md — add a short subsection

Under ## The ship-feature-lane skill (E1), after the ### Syncing MCP servers: mcp sync subsection (added in F1), insert:

### Checking integration toggles: agenthub lanes integration

A profile can declare `.agenthub/profile/integrations.env` with `TRACKER_ENABLED`, `DEV_QC_ENABLED`, `CI_WAIT_ENABLED` flags (all off by default — see `profiles/_template/integrations.env`-style declarations in a profile's own docs). Check one:

```bash
agenthub lanes integration tracker    # exit 0 = on, 1 = off

This only reports what the PROFILE wants — it doesn't file a ticket, run dev-QC, or wait on CI. The agents that would act on an enabled toggle (ticketer, dev-qc) aren't built yet; ci_wait's consumer (agenthub ci) isn't either. ship-feature-lane's Stage 9/13/10 stay skipped/fallback regardless of what the toggle reads, until those land.


- [ ] **Step 4: Roadmap progress line**

In `docs/superpowers/plans/2026-08-03-shipyard-parity-lanes.md`, find the `## F` section's `**Progress:**` line (added in F1) and append:

integration toggle reader (F3a) done 2026-08-05 — see docs/superpowers/specs/2026-08-05-integration-toggle-design.md. ticketer/dev-qc agents, CI-wait (agenthub ci), and dev-QC remain.


- [ ] **Step 5: Verify and commit**

```bash
bash .claude/skills/file-headers/scripts/check-headers.sh
npm run test:server
git add .claude/skills/ship-feature-lane/SKILL.md docs/LANES.md docs/superpowers/plans/2026-08-03-shipyard-parity-lanes.md
git commit -m "docs(lanes): wire ship-feature-lane to the real integration toggle check (F3a)"