Files
Claude-Code-Monitor/docs/superpowers/plans/2026-08-05-integration-toggle.md
T
nntrivi2001 36d66cda07 docs(lanes): plan F3a — ccam lanes integration toggle reader (F)
3 tasks: isIntegrationEnabled in lane-profile.js (reuses parseEnvFile),
the route + CLI, and wiring SKILL.md's hardcoded-off Setup text to the
real check (still no-op behaviorally — no ticketer/dev-qc agent exists
to act on an enabled toggle yet, but the check itself is now honest).
2026-08-05 16:07:33 +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: ccam lanes integration <name> reads a lane's .ccam/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, ".ccam", "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, ".ccam", "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
 * .ccam/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/ccam.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}. ccam 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 .ccam/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/ccam.js, add near cmdLanesMcpSync:

/** `ccam lanes integration <name> [<id>]` — check whether a named
 *  integration (tracker, dev_qc, ci_wait) is turned on for this lane, per
 *  its .ccam/profile/integrations.env. Exit 0 = on, 1 = off. */
async function cmdLanesIntegration(args) {
  const name = args.find((arg) => !arg.startsWith("--"));
  if (!name) {
    console.error("usage: ccam 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/ccam.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/ccam.js
git commit -m "feat(lanes): add ccam 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 CCAM 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 `ccam 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.** `ccam lanes integration tracker`, `ccam lanes integration dev_qc`, `ccam 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 (`ccam 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 CCAM 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 ccam 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 ccam lanes integration tracker was checked, e.g. append to the existing skip explanation: (checked \ccam 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: ccam lanes integration

A profile can declare `.ccam/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
ccam 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 (ccam 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 (ccam 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)"