Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions src/commands/factory.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Factory commands: setup for factory tracker provider configuration.

import { readAgent } from "../utils/init-options.mjs";
import { findInstalledSkills } from "../source.mjs";
import { getAgent } from "../registry.mjs";

const PROVIDERS = ["github", "gitlab", "linear", "jira"];

// ── setup ──────────────────────────────────────────────────────────────
export async function cmdFactorySetup(args, flags) {
const provider = flags.provider;
if (provider && !PROVIDERS.includes(provider)) {
console.error(`Error: --provider must be one of: ${PROVIDERS.join(", ")}`);
return 1;
}

const agentKey = flags.agents[0] || readAgent();
if (!agentKey) {
console.error("Error: Agent required (-a <agent> or set in init-options.json)");
return 1;
}

// No auto-install: the skills source is unknown at this layer (unlike
// `team setup`, which receives it as an argument). Fail loudly instead —
// guessing a source would install untrusted skill content.
const agent = getAgent(agentKey);
if (agent) {
const projectRoot = process.cwd();
const skillsDir = flags.global ? agent.global_skills_dir : agent.skills_dir;
if (skillsDir) {
const skills = await findInstalledSkills(skillsDir, projectRoot);
if (!skills.some((s) => s.name === "factory-setup")) {
console.error(
"Error: factory-setup skill not installed. Run 'adlc-cli skills add <source>' (or 'adlc-cli team setup <source>') first."
);
return 1;
}
}
}

const { cmdAgentRun } = await import("./agent.mjs");
const prompt = provider
? `Run /factory-setup to configure the factory tracker provider for this project (provider preselected: ${provider})`
: "Run /factory-setup to configure the factory tracker provider for this project";
const runArgs = ["-a", agentKey, "--timeout", "300"];
if (flags.format) runArgs.push("--format", flags.format);
if (flags.cwd) runArgs.push("--cwd", flags.cwd);
runArgs.push(prompt);
return cmdAgentRun(runArgs);
}
19 changes: 19 additions & 0 deletions src/dispatch.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import { createRequire } from "node:module";
import { AGENTS } from "./registry.mjs";
import { cmdAdd, cmdUpdate, cmdRemove, cmdStatus } from "./commands/skills.mjs";
import { cmdTeamSetup, cmdTeamUpdate, cmdTeamRepair } from "./commands/team.mjs";
import { cmdFactorySetup } from "./commands/factory.mjs";
import { cmdAgentRun, cmdAgentList } from "./commands/agent.mjs";
import { cmdWorkspaceSetup, cmdWorkspaceInit, cmdWorkspaceStatus } from "./commands/workspace.mjs";
import { cmdWorkflow } from "./commands/workflow.mjs";
import {
printCliHelp,
printSkillsHelp,
printTeamHelp,
printFactoryHelp,
printAgentHelp,
printWorkspaceHelp,
printWorkflowHelp,
Expand Down Expand Up @@ -91,6 +93,21 @@ function runNewTree({ command, args, flags }, argv) {
return 1;
}
}
case "factory": {
const sub = args[0] ?? "help";
const rest = args.slice(1);
switch (sub) {
case "setup":
return cmdFactorySetup(rest, flags);
case "help":
printFactoryHelp();
return 0;
default:
console.error(`Unknown factory command: "${sub}"`);
printFactoryHelp();
return 1;
}
}
case "agent": {
const sub = args[0] ?? "help";
const rest = argv.slice(argv.indexOf(sub) + 1);
Expand Down Expand Up @@ -194,6 +211,8 @@ function parseArgs(argv) {
flags.yes = true;
} else if (arg === "--skip-skills") {
flags.skipSkills = true;
} else if (arg === "--provider") {
flags.provider = rest[++i];
} else if (arg === "--update-confidence") {
flags.updateConfidence = true;
} else if (arg === "--build-to-delete") {
Expand Down
18 changes: 18 additions & 0 deletions src/help.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ COMMANDS:
team setup <source> -a <agent> Install skills + configure team-ai-directives
team update Git pull + skills update + confidence update
team repair Validate and repair team-ai-directives state
factory setup [--provider <p>] -a <agent> Configure factory tracker provider
agent run "<task>" [flags] Run a coding agent headlessly with a task
agent list List supported agents + run profiles
workspace setup [file] Apply workspace file (git modules, skills, commands, goal)
Expand Down Expand Up @@ -91,6 +92,23 @@ Run 'adlc-cli help' for the full list.
`);
}

export function printFactoryHelp() {
console.log(`
USAGE:
adlc-cli factory <command> [flags]

COMMANDS:
setup [--provider <p>] -a <agent> Configure factory tracker provider (.adlc/issues-provider.yml)
help Show this help

SETUP FLAGS:
--provider <p> Preselect provider: github | gitlab | linear | jira (skips the question, headless-safe)
-a <agent> Agent key (default: from init-options.json)

Run 'adlc-cli help' for the full list.
`);
}

export function printAgentHelp() {
console.log(`
USAGE:
Expand Down
52 changes: 52 additions & 0 deletions tests/factory.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { mkdtempSync, rmSync } from "node:fs";
import { join, resolve, dirname } from "node:path";
import { tmpdir } from "node:os";
import { fileURLToPath } from "node:url";

const exec = promisify(execFile);
const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const BIN = join(REPO_ROOT, "bin", "adlc-cli.mjs");

test("factory setup without agent errors", async () => {
const tmp = mkdtempSync(join(tmpdir(), "adlc-test-"));
try {
const r = await exec(process.execPath, [BIN, "factory", "setup"], { cwd: tmp }).catch((e) => e);
assert.equal(r.code ?? r.exitCode, 1);
assert.match(String(r.stderr ?? r.stdout), /agent required/i);
} finally { rmSync(tmp, { recursive: true, force: true }); }
});

test("factory setup with invalid provider errors", async () => {
const r = await exec(process.execPath, [BIN, "factory", "setup", "--provider", "bogus"]).catch((e) => e);
assert.equal(r.code ?? r.exitCode, 1);
assert.match(String(r.stderr ?? r.stdout), /must be one of/i);
});

test("factory setup without installed skill errors", async () => {
const tmp = mkdtempSync(join(tmpdir(), "adlc-test-"));
try {
const r = await exec(process.execPath, [BIN, "factory", "setup", "-a", "opencode"], { cwd: tmp }).catch((e) => e);
assert.equal(r.code ?? r.exitCode, 1);
assert.match(String(r.stderr ?? r.stdout), /not installed/i);
} finally { rmSync(tmp, { recursive: true, force: true }); }
});

test("factory help shows setup", async () => {
const r = await exec(process.execPath, [BIN, "factory", "help"]).catch((e) => e);
assert.match(String(r.stdout ?? ""), /setup/);
});

test("factory unknown subcommand exits 1 with usage", async () => {
const r = await exec(process.execPath, [BIN, "factory", "bogus"]).catch((e) => e);
assert.equal(r.code ?? r.exitCode, 1);
assert.match(String(r.message ?? r.stderr ?? r.stdout), /unknown factory command|usage/i);
});

test("default help includes factory setup", async () => {
const r = await exec(process.execPath, [BIN]).catch((e) => e);
assert.match(String(r.stdout ?? ""), /factory setup/);
});
Loading