karajan-code 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -4
- package/package.json +5 -2
- package/src/agents/aider-agent.js +1 -2
- package/src/agents/base-agent.js +63 -1
- package/src/agents/claude-agent.js +3 -4
- package/src/agents/codex-agent.js +1 -2
- package/src/agents/gemini-agent.js +1 -2
- package/src/agents/index.js +11 -2
- package/src/agents/opencode-agent.js +1 -2
- package/src/brain/decision-tracker.js +123 -0
- package/src/brain/decisor.js +243 -0
- package/src/brain/solomon-consult.js +163 -0
- package/src/budget/comparison.js +96 -0
- package/src/checks/binaries.js +123 -0
- package/src/checks/ci.js +105 -0
- package/src/checks/config-files.js +217 -0
- package/src/checks/dir-setup.js +63 -0
- package/src/checks/mcp-health.js +157 -0
- package/src/checks/node.js +61 -0
- package/src/checks/ports.js +110 -0
- package/src/checks/rtk.js +36 -0
- package/src/checks/runner.js +267 -0
- package/src/checks/skills.js +49 -0
- package/src/checks/sonar.js +67 -0
- package/src/checks/system.js +53 -0
- package/src/checks/tokens.js +96 -0
- package/src/checks/types.js +105 -0
- package/src/cli.js +29 -3
- package/src/commands/board.js +23 -0
- package/src/commands/doctor.js +130 -362
- package/src/commands/resume.js +1 -1
- package/src/commands/run.js +2 -1
- package/src/commands/skills.js +49 -0
- package/src/config/schema.js +222 -0
- package/src/config.js +32 -3
- package/src/guards/policy-resolver.js +8 -0
- package/src/hu/store.js +9 -0
- package/src/infrastructure/command-runner.js +44 -0
- package/src/infrastructure/environment.js +38 -0
- package/src/infrastructure/file-system-service.js +63 -0
- package/src/infrastructure/mocks.js +182 -0
- package/src/mcp/handlers/management-handlers.js +7 -2
- package/src/mcp/tools.js +10 -3
- package/src/orchestrator/config-init.js +16 -1
- package/src/orchestrator/flow-runner.js +2085 -0
- package/src/orchestrator/pipeline-context.js +17 -0
- package/src/orchestrator/preflight-checks.js +78 -4
- package/src/orchestrator/solomon-escalation.js +21 -0
- package/src/orchestrator/stages/stage-executor.js +155 -0
- package/src/orchestrator.js +19 -1923
- package/src/prompts/architect.js +2 -2
- package/src/prompts/coder.js +2 -2
- package/src/prompts/prompt-resolver.js +127 -0
- package/src/prompts/reviewer.js +2 -2
- package/src/roles/architect-role.js +3 -1
- package/src/roles/base-role.js +23 -2
- package/src/roles/coder-role.js +3 -1
- package/src/roles/security-role.js +12 -0
- package/src/roles/tester-role.js +11 -0
- package/src/session/journal/decisions-writer.js +200 -0
- package/src/session/journal/iteration-logger.js +194 -0
- package/src/session/journal/summary-writer.js +176 -0
- package/src/session/journal/tree-writer.js +164 -0
- package/src/skills/semantic-detector.js +147 -0
- package/src/skills/skill-detector.js +186 -8
- package/src/skills/skill-loader.js +131 -1
- package/src/skills/skills-cache.js +128 -0
- package/src/types/agent.js +55 -0
- package/src/types/config.js +114 -0
- package/src/types/hu.js +47 -0
- package/src/types/index.js +34 -0
- package/src/types/policy.js +43 -0
- package/src/types/session.js +74 -0
- package/src/types/stage.js +68 -0
- package/src/utils/display/event-handlers.js +389 -0
- package/src/utils/display/formatters.js +132 -0
- package/src/utils/display/header.js +48 -0
- package/src/utils/display/session-summary.js +108 -0
- package/src/utils/display/solomon.js +42 -0
- package/src/utils/port-check.js +45 -0
- package/src/utils/port-occupant.js +95 -0
- package/src/utils/provider-env.js +116 -0
- package/src/utils/with-timeout.js +42 -0
- package/templates/roles/architect/anthropic.md +65 -0
- package/templates/roles/architect/default.md +63 -0
- package/templates/roles/architect/google.md +52 -0
- package/templates/roles/architect/openai.md +51 -0
- package/templates/roles/architect/opencode.md +50 -0
- package/templates/roles/coder/anthropic.md +72 -0
- package/templates/roles/coder/default.md +69 -0
- package/templates/roles/coder/google.md +78 -0
- package/templates/roles/coder/openai.md +59 -0
- package/templates/roles/coder/opencode.md +49 -0
- package/templates/roles/planner/anthropic.md +50 -0
- package/templates/roles/planner/default.md +48 -0
- package/templates/roles/planner/google.md +66 -0
- package/templates/roles/planner/openai.md +46 -0
- package/templates/roles/planner/opencode.md +53 -0
- package/templates/roles/reviewer/anthropic.md +61 -0
- package/templates/roles/reviewer/default.md +62 -0
- package/templates/roles/reviewer/google.md +63 -0
- package/templates/roles/reviewer/openai.md +57 -0
- package/templates/roles/reviewer/opencode.md +42 -0
- package/templates/roles/solomon/anthropic.md +71 -0
- package/templates/roles/solomon/default.md +83 -0
- package/templates/roles/solomon/google.md +61 -0
- package/templates/roles/solomon/openai.md +53 -0
- package/templates/roles/solomon/opencode.md +55 -0
- package/src/utils/display.js +0 -669
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenSkills CLI availability check. Only runs when skills are enabled.
|
|
3
|
+
* Strategy: prompt — we can offer to run `npm install -g openskills`,
|
|
4
|
+
* but global installs are invasive enough that they need user consent.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { isOpenSkillsAvailable } from "../skills/openskills-client.js";
|
|
8
|
+
import { runCommand } from "../utils/process.js";
|
|
9
|
+
import { STRATEGY } from "./types.js";
|
|
10
|
+
|
|
11
|
+
export function skillsEnabled(config) {
|
|
12
|
+
return config?.skills?.enabled !== false;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function createOpenSkillsCheck() {
|
|
16
|
+
return {
|
|
17
|
+
name: "openskills",
|
|
18
|
+
label: "OpenSkills CLI",
|
|
19
|
+
strategy: STRATEGY.PROMPT,
|
|
20
|
+
applies: skillsEnabled,
|
|
21
|
+
describe: "Install OpenSkills globally: npm install -g openskills",
|
|
22
|
+
async detect() {
|
|
23
|
+
const available = await isOpenSkillsAvailable();
|
|
24
|
+
if (available) {
|
|
25
|
+
return { ok: true, severity: "info", detail: "Available via `npx openskills`" };
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
ok: false,
|
|
29
|
+
severity: "warn", // skills enabled but missing → degrade gracefully
|
|
30
|
+
detail: "`npx openskills` not available — skill injection will be skipped",
|
|
31
|
+
fix: "Install OpenSkills: npm install -g openskills",
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
async remediate() {
|
|
35
|
+
const res = await runCommand("npm", ["install", "-g", "openskills"], { timeout: 60000 });
|
|
36
|
+
if (res.exitCode === 0) {
|
|
37
|
+
return { fixed: true, detail: "Installed openskills globally via npm" };
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
fixed: false,
|
|
41
|
+
detail: `npm install -g openskills failed (exit ${res.exitCode}): ${res.stderr.split("\n")[0]}`,
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function getSkillsChecks() {
|
|
48
|
+
return [createOpenSkillsCheck()];
|
|
49
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SonarQube reachability + auth checks.
|
|
3
|
+
*
|
|
4
|
+
* Auto-remediation strategy (preserving existing behavior from
|
|
5
|
+
* preflight-checks.js): if Sonar is not reachable, try `sonarUp()` which
|
|
6
|
+
* starts the Docker container, then poll for readiness. If auth fails but
|
|
7
|
+
* admin credentials exist, generate a fresh token and persist it.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { isSonarReachable } from "../sonar/manager.js";
|
|
11
|
+
import { STRATEGY } from "./types.js";
|
|
12
|
+
import { withDocLink } from "../utils/doc-links.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Whether Sonar is enabled in the active config.
|
|
16
|
+
*/
|
|
17
|
+
function sonarEnabled(config) {
|
|
18
|
+
return config.sonarqube?.enabled !== false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function sonarHost(config) {
|
|
22
|
+
return config.sonarqube?.host || "http://localhost:9000";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Informative check: Sonar enabled/disabled status.
|
|
27
|
+
* Produces an OK result with disabled description when Sonar is off.
|
|
28
|
+
*/
|
|
29
|
+
export function createSonarStatusCheck() {
|
|
30
|
+
return {
|
|
31
|
+
name: "sonarqube",
|
|
32
|
+
label: "SonarQube",
|
|
33
|
+
strategy: STRATEGY.NONE,
|
|
34
|
+
async detect({ config }) {
|
|
35
|
+
const host = sonarHost(config);
|
|
36
|
+
if (!sonarEnabled(config)) {
|
|
37
|
+
return { ok: true, severity: "info", detail: "Disabled in config" };
|
|
38
|
+
}
|
|
39
|
+
let reachable = false;
|
|
40
|
+
try {
|
|
41
|
+
reachable = await isSonarReachable(host);
|
|
42
|
+
} catch {
|
|
43
|
+
reachable = false;
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
ok: reachable,
|
|
47
|
+
severity: "fail",
|
|
48
|
+
detail: reachable ? `Reachable at ${host}` : `Not reachable at ${host}`,
|
|
49
|
+
fix: reachable
|
|
50
|
+
? undefined
|
|
51
|
+
: withDocLink(
|
|
52
|
+
"Run 'kj sonar start' or 'docker start karajan-sonarqube'. Use --no-sonar to skip.",
|
|
53
|
+
"sonar_docker"
|
|
54
|
+
),
|
|
55
|
+
extra: { host, reachable },
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* All Sonar-related checks. Keeps just the status check for commit 1;
|
|
63
|
+
* auth + auto-remediation are layered in commit 3.
|
|
64
|
+
*/
|
|
65
|
+
export function getSonarChecks() {
|
|
66
|
+
return [createSonarStatusCheck()];
|
|
67
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Miscellaneous system-level checks: Karajan version, git repo presence.
|
|
3
|
+
* Kept in a dedicated module so they're easy to find and extend.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { ensureGitRepo } from "../utils/git.js";
|
|
10
|
+
import { STRATEGY } from "./types.js";
|
|
11
|
+
|
|
12
|
+
function getPackageVersion() {
|
|
13
|
+
const pkgPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../package.json");
|
|
14
|
+
return JSON.parse(readFileSync(pkgPath, "utf8")).version;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createKarajanVersionCheck() {
|
|
18
|
+
return {
|
|
19
|
+
name: "karajan",
|
|
20
|
+
label: "Karajan Code",
|
|
21
|
+
strategy: STRATEGY.NONE,
|
|
22
|
+
async detect() {
|
|
23
|
+
const version = getPackageVersion();
|
|
24
|
+
return { ok: true, severity: "info", detail: `v${version}` };
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function createGitRepoCheck() {
|
|
30
|
+
return {
|
|
31
|
+
name: "git",
|
|
32
|
+
label: "Git repository",
|
|
33
|
+
strategy: STRATEGY.MANUAL,
|
|
34
|
+
async detect() {
|
|
35
|
+
let gitOk = false;
|
|
36
|
+
try {
|
|
37
|
+
gitOk = await ensureGitRepo();
|
|
38
|
+
} catch {
|
|
39
|
+
gitOk = false;
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
ok: gitOk,
|
|
43
|
+
severity: "fail",
|
|
44
|
+
detail: gitOk ? "Inside a git repository" : "Not a git repository",
|
|
45
|
+
fix: gitOk ? undefined : "Run 'git init' or navigate to a git-managed project.",
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function getSystemChecks() {
|
|
52
|
+
return [createKarajanVersionCheck(), createGitRepoCheck()];
|
|
53
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API token availability checks.
|
|
3
|
+
*
|
|
4
|
+
* Provider tokens (ANTHROPIC_API_KEY / OPENAI_API_KEY / ...) are LAZY:
|
|
5
|
+
* we only check them for providers that are actually used by at least one
|
|
6
|
+
* active role in the current config.
|
|
7
|
+
*
|
|
8
|
+
* Strategy:
|
|
9
|
+
* - Required providers with no key → FAIL, strategy manual (we can't
|
|
10
|
+
* fetch an API key for the user). Fix hint includes the how-to URL.
|
|
11
|
+
* - Optional providers (opencode, aider) without key → WARN.
|
|
12
|
+
* - GH_TOKEN is checked when git.auto_pr=true, strategy prompt (we can
|
|
13
|
+
* launch `gh auth login` with user approval).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { checkBinary } from "../utils/agent-detect.js";
|
|
17
|
+
import { getRequiredProviderEnvs, hasEnvVar } from "../utils/provider-env.js";
|
|
18
|
+
import { STRATEGY } from "./types.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A single provider env check. One is created per active provider.
|
|
22
|
+
*/
|
|
23
|
+
function createProviderTokenCheck(entry) {
|
|
24
|
+
return {
|
|
25
|
+
name: `token:${entry.provider}`,
|
|
26
|
+
label: `API token: ${entry.provider}`,
|
|
27
|
+
strategy: STRATEGY.MANUAL,
|
|
28
|
+
async detect() {
|
|
29
|
+
const ok = hasEnvVar(entry);
|
|
30
|
+
if (ok) {
|
|
31
|
+
return { ok: true, severity: "info", detail: `${entry.envVars[0]} is set` };
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
ok: false,
|
|
35
|
+
severity: entry.optional ? "warn" : "fail",
|
|
36
|
+
detail: `${entry.envVars.join(" / ")} not set`,
|
|
37
|
+
fix: `Get an API key at ${entry.howToGetUrl} and export ${entry.envVars[0]}=...`,
|
|
38
|
+
extra: { provider: entry.provider, envVars: entry.envVars, howToGetUrl: entry.howToGetUrl },
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* GH token check + auto-remediation via `gh auth login` when it's installed.
|
|
46
|
+
*/
|
|
47
|
+
function createGhTokenCheck() {
|
|
48
|
+
return {
|
|
49
|
+
name: "token:gh",
|
|
50
|
+
label: "GitHub token (for auto_pr)",
|
|
51
|
+
strategy: STRATEGY.PROMPT,
|
|
52
|
+
applies: (config) => config?.git?.auto_pr === true,
|
|
53
|
+
describe: "Run 'gh auth login' to authenticate with GitHub",
|
|
54
|
+
async detect() {
|
|
55
|
+
const envOk = !!process.env.GH_TOKEN || !!process.env.GITHUB_TOKEN;
|
|
56
|
+
if (envOk) {
|
|
57
|
+
return { ok: true, severity: "info", detail: "GH_TOKEN or GITHUB_TOKEN is set" };
|
|
58
|
+
}
|
|
59
|
+
// If `gh` is installed and authenticated, it manages its own token.
|
|
60
|
+
const gh = await checkBinary("gh");
|
|
61
|
+
if (!gh.ok) {
|
|
62
|
+
return {
|
|
63
|
+
ok: false,
|
|
64
|
+
severity: "fail",
|
|
65
|
+
detail: "No GH_TOKEN/GITHUB_TOKEN and `gh` CLI not installed",
|
|
66
|
+
fix: "Install GitHub CLI (https://cli.github.com) or export GH_TOKEN=...",
|
|
67
|
+
extra: { hasGh: false },
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
ok: false,
|
|
72
|
+
severity: "fail",
|
|
73
|
+
detail: "No GH_TOKEN/GITHUB_TOKEN; `gh` is installed but auth status unknown",
|
|
74
|
+
fix: "Run 'gh auth login' or export GH_TOKEN=...",
|
|
75
|
+
extra: { hasGh: true },
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
// remediate() intentionally omitted: launching `gh auth login` requires an
|
|
79
|
+
// interactive terminal session. The runner's prompt strategy will point
|
|
80
|
+
// the user to the command and exit with FAIL in non-TTY contexts.
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Build the list of token checks for the active config. Each active provider
|
|
86
|
+
* gets one, plus conditional GH token when auto_pr is on.
|
|
87
|
+
*
|
|
88
|
+
* @param {Object} config
|
|
89
|
+
* @returns {import("./types.js").Check[]}
|
|
90
|
+
*/
|
|
91
|
+
export function getTokenChecks(config) {
|
|
92
|
+
const required = getRequiredProviderEnvs(config);
|
|
93
|
+
const checks = required.map(createProviderTokenCheck);
|
|
94
|
+
checks.push(createGhTokenCheck());
|
|
95
|
+
return checks;
|
|
96
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared check schema for doctor/preflight subsystem.
|
|
3
|
+
*
|
|
4
|
+
* A Check describes an environment requirement (binary present, port free,
|
|
5
|
+
* token configured, MCP reachable...). Each check can optionally remediate
|
|
6
|
+
* itself when it fails, using one of 4 strategies:
|
|
7
|
+
*
|
|
8
|
+
* - "auto" → fix without asking. Use for non-invasive, reversible changes
|
|
9
|
+
* (in-memory runtime overrides, starting existing services,
|
|
10
|
+
* creating files under ~/.karajan/, etc.)
|
|
11
|
+
* - "prompt" → ask user before fixing. Use for invasive changes that touch
|
|
12
|
+
* the user's system outside Karajan's scope (global npm install,
|
|
13
|
+
* writing kj.config.yml, `gh auth login`, etc.)
|
|
14
|
+
* - "manual" → cannot auto-fix, only report a hint (Node runtime upgrade,
|
|
15
|
+
* missing Docker, missing API key with no admin creds)
|
|
16
|
+
* - "none" → informational check, always OK (Karajan version, feature
|
|
17
|
+
* disabled by config)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {Object} CheckResult
|
|
22
|
+
* @property {boolean} ok - true if check passes
|
|
23
|
+
* @property {"fail"|"warn"|"info"} [severity] - relevant when !ok; default "fail"
|
|
24
|
+
* @property {string} detail - human-readable status
|
|
25
|
+
* @property {string} [fix] - remediation hint shown when manual or user declines
|
|
26
|
+
* @property {Object} [extra] - structured data consumed by remediate (e.g. occupant PID, host, roles)
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {Object} RemediationContext
|
|
31
|
+
* @property {Object} config - resolved Karajan config
|
|
32
|
+
* @property {AbortSignal} [signal] - cancellation
|
|
33
|
+
* @property {Object} extra - check's CheckResult.extra
|
|
34
|
+
* @property {Object} logger - logger instance
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @typedef {Object} RemediationResult
|
|
39
|
+
* @property {boolean} fixed - true if remediation succeeded
|
|
40
|
+
* @property {string} detail - what was done, human-readable
|
|
41
|
+
* @property {Object} [changes] - runtime config overrides to merge into the active session
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @typedef {Object} Check
|
|
46
|
+
* @property {string} name - slug, unique across all checks (e.g. "node-version")
|
|
47
|
+
* @property {string} label - human label (e.g. "Node.js runtime")
|
|
48
|
+
* @property {"auto"|"prompt"|"manual"|"none"} strategy
|
|
49
|
+
* @property {(ctx: { config: Object }) => Promise<CheckResult>} detect
|
|
50
|
+
* @property {(ctx: RemediationContext) => Promise<RemediationResult>} [remediate]
|
|
51
|
+
* @property {string} [describe] - shown to user when strategy is "prompt"
|
|
52
|
+
* @property {(config: Object) => boolean} [applies] - if false, check is SKIPPED (lazy evaluation). Default: always applies.
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @typedef {Object} CheckReport
|
|
57
|
+
* @property {string} name
|
|
58
|
+
* @property {string} label
|
|
59
|
+
* @property {"OK"|"FIXED"|"WARN"|"FAIL"|"SKIPPED"|"TIMEOUT"} status
|
|
60
|
+
* @property {"auto"|"prompt"|"manual"|"none"} strategy
|
|
61
|
+
* @property {string} detail - detect detail or remediate detail when FIXED
|
|
62
|
+
* @property {string} [fix] - remediation hint for FAIL/WARN
|
|
63
|
+
* @property {number} runMs - total time (detect + remediate + re-verify)
|
|
64
|
+
* @property {boolean} [cached] - future: served from cache
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @typedef {Object} RunReport
|
|
69
|
+
* @property {CheckReport[]} checks
|
|
70
|
+
* @property {Object} overrides - merged runtime overrides from auto-remediations
|
|
71
|
+
* @property {number} totalMs
|
|
72
|
+
* @property {{ ok: number, fixed: number, warn: number, fail: number, skipped: number, timeout: number }} summary
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Status constants.
|
|
77
|
+
*/
|
|
78
|
+
export const STATUS = Object.freeze({
|
|
79
|
+
OK: "OK",
|
|
80
|
+
FIXED: "FIXED",
|
|
81
|
+
WARN: "WARN",
|
|
82
|
+
FAIL: "FAIL",
|
|
83
|
+
SKIPPED: "SKIPPED",
|
|
84
|
+
TIMEOUT: "TIMEOUT",
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
export const STRATEGY = Object.freeze({
|
|
88
|
+
AUTO: "auto",
|
|
89
|
+
PROMPT: "prompt",
|
|
90
|
+
MANUAL: "manual",
|
|
91
|
+
NONE: "none",
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
export const SEVERITY = Object.freeze({
|
|
95
|
+
FAIL: "fail",
|
|
96
|
+
WARN: "warn",
|
|
97
|
+
INFO: "info",
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Whether the final status blocks the pipeline (for preflight).
|
|
102
|
+
*/
|
|
103
|
+
export function isBlocking(status) {
|
|
104
|
+
return status === STATUS.FAIL || status === STATUS.TIMEOUT;
|
|
105
|
+
}
|
package/src/cli.js
CHANGED
|
@@ -128,6 +128,10 @@ program
|
|
|
128
128
|
.option("--no-smart-models", "Disable smart model selection")
|
|
129
129
|
.option("--design", "Activate design refactoring mode (impeccable role applies design changes)")
|
|
130
130
|
.option("--domain <text-or-path>", "Domain knowledge: inline text or path to .md file")
|
|
131
|
+
.option("--skills-mode <mode>", "Skill detection mode: auto|regex|semantic|none (default: auto)")
|
|
132
|
+
.option("--brain <mode>", "Brain decisor: on|off (default: on). When off, only legacy flags drive routing.")
|
|
133
|
+
.option("--skip-role <role...>", "Force one or more roles OFF regardless of triage (e.g. --skip-role tester security)")
|
|
134
|
+
.option("--force-role <role...>", "Force one or more roles ON regardless of triage")
|
|
131
135
|
.option("--dry-run", "Show what would be executed without running anything")
|
|
132
136
|
.option("--json", "Output JSON only (no styled display)")
|
|
133
137
|
.option("-q, --quiet", "Show only stage status lines, suppress raw agent output (default)")
|
|
@@ -172,9 +176,22 @@ program
|
|
|
172
176
|
|
|
173
177
|
program
|
|
174
178
|
.command("doctor")
|
|
175
|
-
.description("Check environment requirements")
|
|
176
|
-
.
|
|
177
|
-
|
|
179
|
+
.description("Check environment requirements and auto-remediate when possible")
|
|
180
|
+
.option("--check-only", "Detect issues without applying fixes")
|
|
181
|
+
.option("-y, --yes", "Auto-confirm prompts for invasive remediations (CI mode)")
|
|
182
|
+
.option("--json", "Emit machine-readable JSON instead of human output")
|
|
183
|
+
.option("--verbose", "Include fix hints and timing for every check")
|
|
184
|
+
.action(async (flags) => {
|
|
185
|
+
const exitCode = await withConfig("doctor", {}, (ctx) =>
|
|
186
|
+
doctorCommand({
|
|
187
|
+
...ctx,
|
|
188
|
+
checkOnly: !!flags.checkOnly,
|
|
189
|
+
yes: !!flags.yes,
|
|
190
|
+
json: !!flags.json,
|
|
191
|
+
verbose: !!flags.verbose,
|
|
192
|
+
})
|
|
193
|
+
);
|
|
194
|
+
if (Number.isInteger(exitCode)) process.exit(exitCode);
|
|
178
195
|
});
|
|
179
196
|
|
|
180
197
|
program
|
|
@@ -198,6 +215,15 @@ program
|
|
|
198
215
|
await reportCommand(flags);
|
|
199
216
|
});
|
|
200
217
|
|
|
218
|
+
program
|
|
219
|
+
.command("skills [action]")
|
|
220
|
+
.description("Manage the local OpenSkills cache (actions: list | clear-cache)")
|
|
221
|
+
.action(async (action) => {
|
|
222
|
+
const { skillsCommand } = await import("./commands/skills.js");
|
|
223
|
+
const exitCode = await skillsCommand({ action: action || "list" });
|
|
224
|
+
if (Number.isInteger(exitCode)) process.exit(exitCode);
|
|
225
|
+
});
|
|
226
|
+
|
|
201
227
|
program
|
|
202
228
|
.command("roles [subcommand] [role]")
|
|
203
229
|
.description("List pipeline roles or show role template instructions")
|
package/src/commands/board.js
CHANGED
|
@@ -115,6 +115,29 @@ export async function boardStatus(port = 4000) {
|
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Render a highlighted cyan box with the HU Board URL + optional project name.
|
|
120
|
+
* Centralizes the banner used by both the init-time auto-start (flow-runner.js)
|
|
121
|
+
* and the post-planner auto-HU generation safety net so they stay in sync.
|
|
122
|
+
*
|
|
123
|
+
* @param {Object} params
|
|
124
|
+
* @param {string} params.url - "http://localhost:4000"
|
|
125
|
+
* @param {string} params.status - "started" | "already running"
|
|
126
|
+
* @param {string} [params.projectName] - optional project label
|
|
127
|
+
* @returns {string} ANSI-escaped box ready for console.log
|
|
128
|
+
*/
|
|
129
|
+
export function renderBoardBanner({ url, status, projectName }) {
|
|
130
|
+
const title = projectName ? ` Project: \u001b[1m${projectName}\u001b[0m` : null;
|
|
131
|
+
const lines = [
|
|
132
|
+
"",
|
|
133
|
+
"\u001b[36m\u256d\u2500 HU Board \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e",
|
|
134
|
+
`\u001b[36m\u2502\u001b[0m \u001b[1m${status}\u001b[0m \u001b[4m${url}\u001b[0m`,
|
|
135
|
+
];
|
|
136
|
+
if (title) lines.push(`\u001b[36m\u2502\u001b[0m${title}`);
|
|
137
|
+
lines.push("\u001b[36m\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f\u001b[0m", "");
|
|
138
|
+
return lines.join("\n");
|
|
139
|
+
}
|
|
140
|
+
|
|
118
141
|
export async function boardCommand({ action = "start", port = 4000, logger }) {
|
|
119
142
|
switch (action) {
|
|
120
143
|
case "start": {
|