karajan-code 2.7.2 β 2.7.4
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 +4 -4
- package/bin/kj-tail +34 -7
- package/package.json +1 -1
- package/src/checks/node.js +10 -4
- package/src/checks/ports.js +18 -2
- package/src/checks/runner.js +26 -10
- package/src/checks/tokens.js +77 -24
- package/src/cli.js +48 -18
- package/src/commands/architect.js +26 -21
- package/src/commands/audit.js +32 -24
- package/src/commands/code.js +31 -25
- package/src/commands/discover.js +26 -21
- package/src/commands/plan.js +12 -1
- package/src/commands/researcher.js +19 -14
- package/src/commands/review.js +10 -4
- package/src/commands/run.js +31 -21
- package/src/commands/triage.js +27 -21
- package/src/config.js +24 -1
- package/src/mcp/direct-role-runner.js +12 -0
- package/src/mcp/handlers/direct-handlers.js +6 -0
- package/src/mcp/handlers/run-handler.js +7 -1
- package/src/mcp/shared-helpers.js +23 -0
- package/src/mcp/tools.js +18 -16
- package/src/orchestrator/flow-runner.js +62 -4
- package/src/orchestrator/preflight-checks.js +15 -1
- package/src/session-store.js +6 -0
- package/src/utils/cli-run-log.js +83 -0
- package/src/utils/task-file.js +109 -0
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
26
|
-
> **v2.7.
|
|
26
|
+
> **v2.7.4 released** β Three contract fixes: (1) **Sonar is now intrinsic** to Karajan for code tasks (`sw`/`refactor`/`add-tests`) β `sonarqube.enabled` config and `--no-sonar` flag are ignored with a deprecation warning; a code task without a quality gate is not a job Karajan calls complete, like TDD; Solomon still may skip an iteration at runtime via rules. (2) **Preflight no longer demands API keys Karajan doesn't use** β the bogus `ANTHROPIC_API_KEY not set` check was blocking every Claude Code MCP run; replaced with a real CLI availability check (`claude`, `codex`, `gemini` on PATH). (3) **Orchestrator no longer crashes** with `Cannot read properties of undefined (reading 'push')` on the Solomon init-error path. Adds two architectural invariants (`no-provider-apis`, `sonar-intrinsic`) to CI so the contracts can't silently drift back. New `docs/TESTS.md` test-suite guide with diagrams. See [CHANGELOG.md](./CHANGELOG.md) for full history and [MIGRATION-v2.md](./MIGRATION-v2.md) for v1βv2 breaking changes.
|
|
27
27
|
|
|
28
28
|
You describe what you want to build. Karajan orchestrates multiple AI agents to plan it, implement it, test it, review it with SonarQube, and iterate. No babysitting required.
|
|
29
29
|
|
|
@@ -297,9 +297,9 @@ Not nostalgia, not stubbornness. I've been using JavaScript since 1997, when Bre
|
|
|
297
297
|
| Tool | Why |
|
|
298
298
|
|------|-----|
|
|
299
299
|
| [**RTK**](https://github.com/rtk-ai/rtk) | Reduces token consumption by 60-90% on Bash command outputs |
|
|
300
|
-
| [**Planning Game MCP**](https://github.com/
|
|
301
|
-
| [**GitHub MCP**](https://github.com/
|
|
302
|
-
| [**Chrome DevTools MCP**](https://github.com/
|
|
300
|
+
| [**Planning Game MCP**](https://github.com/manufosela/planning-game-xp-mcp) | Agile project management (tasks, sprints, estimation), XP-native |
|
|
301
|
+
| [**GitHub MCP**](https://github.com/github/github-mcp-server) | Create PRs, manage issues directly from the agent |
|
|
302
|
+
| [**Chrome DevTools MCP**](https://github.com/ChromeDevTools/chrome-devtools-mcp) | Verify UI changes visually after frontend modifications |
|
|
303
303
|
|
|
304
304
|
## Contributing
|
|
305
305
|
|
package/bin/kj-tail
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
set -euo pipefail
|
|
6
6
|
|
|
7
|
-
VERSION="1.
|
|
7
|
+
VERSION="1.38.0"
|
|
8
8
|
|
|
9
9
|
# ββ Colors ββββββββββββββββββββββββββββββββββββββββββββββ
|
|
10
10
|
RED='\033[0;31m'
|
|
@@ -94,15 +94,42 @@ PROJECT_DIR="${PROJECT_DIR:-$(pwd)}"
|
|
|
94
94
|
LOG_FILE="${PROJECT_DIR}/.kj/run.log"
|
|
95
95
|
|
|
96
96
|
# ββ Locate log ββββββββββββββββββββββββββββββββββββββββββ
|
|
97
|
+
# When the log does not exist yet, kj-tail stays open and polls until any
|
|
98
|
+
# `kj` / `kj_*` invocation creates it. Exit with Ctrl-C. This used to be a
|
|
99
|
+
# hard exit, which forced the user to time `kj-tail` start vs. the command;
|
|
100
|
+
# too easy to miss early lines. Follow-mode and snapshot mode both get this.
|
|
97
101
|
if [[ ! -f "$LOG_FILE" ]]; then
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
+
if [[ "$FOLLOW" == "false" ]]; then
|
|
103
|
+
# Snapshot mode (-s): nothing to print, say so and exit cleanly.
|
|
104
|
+
echo -e "${YELLOW}No run log at ${LOG_FILE} yet.${RESET}"
|
|
105
|
+
echo ""
|
|
106
|
+
echo "The log is created when Karajan starts a pipeline β run any of:"
|
|
107
|
+
echo " kj run \"...\" kj audit kj code kj review kj plan"
|
|
108
|
+
echo " mcp__karajan-mcp__kj_run / kj_audit / kj_code / kj_review / kj_plan ..."
|
|
109
|
+
echo ""
|
|
110
|
+
exit 0
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
echo -e "${YELLOW}Waiting for run log to appear at ${LOG_FILE}${RESET}"
|
|
114
|
+
echo -e "${DIM}(It is created the moment you run any kj command that invokes the orchestrator or a role.${RESET}"
|
|
115
|
+
echo -e "${DIM} Ctrl-C to stop waiting. Triggers: kj run/audit/code/review/plan/discover/triage/researcher/architect, or the same via MCP.)${RESET}"
|
|
102
116
|
echo ""
|
|
103
|
-
|
|
117
|
+
# Ensure parent dir exists so we can poll even on a fresh project
|
|
118
|
+
mkdir -p "$(dirname "$LOG_FILE")" 2>/dev/null || true
|
|
119
|
+
# Poll every 500ms until the file shows up (low CPU, visible responsiveness).
|
|
120
|
+
# Cap at 4h (28800 ticks) to avoid infinite stuck monitors; after the cap we
|
|
121
|
+
# exit 0 so tmux/screen panes don't linger as errors.
|
|
122
|
+
tick=0
|
|
123
|
+
while [[ ! -f "$LOG_FILE" ]]; do
|
|
124
|
+
sleep 0.5
|
|
125
|
+
tick=$((tick + 1))
|
|
126
|
+
if (( tick >= 28800 )); then
|
|
127
|
+
echo -e "${YELLOW}Still no run log after 4h β exiting. Re-run kj-tail when ready.${RESET}"
|
|
128
|
+
exit 0
|
|
129
|
+
fi
|
|
130
|
+
done
|
|
131
|
+
echo -e "${GREEN}Log appeared β streaming...${RESET}"
|
|
104
132
|
echo ""
|
|
105
|
-
exit 1
|
|
106
133
|
fi
|
|
107
134
|
|
|
108
135
|
# ββ Filter & colorize ββββββββββββββββββββββββββββββββββ
|
package/package.json
CHANGED
package/src/checks/node.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Node.js runtime version check. Requires Node >=
|
|
3
|
-
* ships
|
|
4
|
-
*
|
|
2
|
+
* Node.js runtime version check. Requires Node >= 18 β the LTS baseline that
|
|
3
|
+
* ships every runtime feature Karajan needs: ESM support, `fetch` (built-in),
|
|
4
|
+
* `structuredClone` (17+), `AbortSignal.timeout` (17.3+), and
|
|
5
|
+
* `Array.prototype.findLast` / `findLastIndex` (18+). Nothing in the runtime
|
|
6
|
+
* code path uses Node 20+ APIs (no `Promise.withResolvers`, no
|
|
7
|
+
* `Object.groupBy`, no `Set.prototype.union/intersection`, no
|
|
8
|
+
* `Array.prototype.toSorted`). Strategy: manual β we cannot auto-upgrade the
|
|
9
|
+
* runtime. Users on older Node get a clear upgrade instruction with nvm + brew
|
|
10
|
+
* hints instead of a silent runtime crash.
|
|
5
11
|
*/
|
|
6
12
|
|
|
7
13
|
import { STRATEGY } from "./types.js";
|
|
8
14
|
|
|
9
|
-
export const MIN_NODE_MAJOR =
|
|
15
|
+
export const MIN_NODE_MAJOR = 18;
|
|
10
16
|
|
|
11
17
|
/**
|
|
12
18
|
* Accepts a version string in the form "v20.12.1" or "20.12.1" and returns
|
package/src/checks/ports.js
CHANGED
|
@@ -38,7 +38,18 @@ export function createSonarPortCheck() {
|
|
|
38
38
|
name: "port:sonar",
|
|
39
39
|
label: "SonarQube port",
|
|
40
40
|
strategy: STRATEGY.MANUAL,
|
|
41
|
-
|
|
41
|
+
// Since v2.7.4 Sonar is intrinsic to Karajan for code tasks, so the
|
|
42
|
+
// port check almost always applies. The only legitimate skip is when
|
|
43
|
+
// the user runs their own external Sonar instance (`external: true`),
|
|
44
|
+
// in which case the port is managed outside Karajan. The legacy
|
|
45
|
+
// `sonarqube.enabled` field is ignored (deprecation warning emitted
|
|
46
|
+
// at config load time β see emitConfigDeprecations in flow-runner.js).
|
|
47
|
+
applies: (config) => {
|
|
48
|
+
if (config?.sonarqube?.external === true) {
|
|
49
|
+
return { applies: false, reason: "sonarqube.external is true β port managed outside Karajan" };
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
},
|
|
42
53
|
async detect({ config }) {
|
|
43
54
|
const port = extractPortFromHost(config?.sonarqube?.host, DEFAULT_SONAR_PORT);
|
|
44
55
|
const free = await isPortAvailable(port);
|
|
@@ -73,7 +84,12 @@ export function createHuBoardPortCheck() {
|
|
|
73
84
|
name: "port:hu-board",
|
|
74
85
|
label: "HU Board port",
|
|
75
86
|
strategy: STRATEGY.AUTO,
|
|
76
|
-
applies: (config) =>
|
|
87
|
+
applies: (config) => {
|
|
88
|
+
if (config?.hu_board?.enabled === false) {
|
|
89
|
+
return { applies: false, reason: "hu_board.enabled is false in kj.config.yml β HU Board dashboard skipped" };
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
},
|
|
77
93
|
describe: "Rebind HU Board to the next free port",
|
|
78
94
|
async detect({ config }) {
|
|
79
95
|
const port = config?.hu_board?.port || DEFAULT_HU_BOARD_PORT;
|
package/src/checks/runner.js
CHANGED
|
@@ -55,19 +55,35 @@ export async function runChecks(checks, ctx, options = {}) {
|
|
|
55
55
|
const overrides = {};
|
|
56
56
|
|
|
57
57
|
// Build applicable entries (skipped checks noted up front).
|
|
58
|
+
// A check.applies(config) may return:
|
|
59
|
+
// - true / false β boolean form (back-compat).
|
|
60
|
+
// - an object { applies: boolean, reason?: string } β when false, `reason`
|
|
61
|
+
// is shown verbatim so users see WHY (e.g. "sonarqube.enabled is false
|
|
62
|
+
// in kj.config.yml") instead of the opaque "Not applicable" message
|
|
63
|
+
// that confused users debugging their config.
|
|
58
64
|
for (const check of checks) {
|
|
59
|
-
if (typeof check.applies
|
|
60
|
-
entries.push({
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
});
|
|
68
|
-
} else {
|
|
65
|
+
if (typeof check.applies !== "function") {
|
|
66
|
+
entries.push({ check, status: null, detail: null, runMs: 0, detectResult: null, remediated: false });
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const verdict = check.applies(config);
|
|
70
|
+
const isObj = verdict && typeof verdict === "object" && "applies" in verdict;
|
|
71
|
+
const applies = isObj ? Boolean(verdict.applies) : Boolean(verdict);
|
|
72
|
+
if (applies) {
|
|
69
73
|
entries.push({ check, status: null, detail: null, runMs: 0, detectResult: null, remediated: false });
|
|
74
|
+
continue;
|
|
70
75
|
}
|
|
76
|
+
const reason = (isObj && typeof verdict.reason === "string" && verdict.reason.trim())
|
|
77
|
+
? verdict.reason.trim()
|
|
78
|
+
: "Not applicable for current configuration";
|
|
79
|
+
entries.push({
|
|
80
|
+
check,
|
|
81
|
+
status: STATUS.SKIPPED,
|
|
82
|
+
detail: reason,
|
|
83
|
+
runMs: 0,
|
|
84
|
+
detectResult: null,
|
|
85
|
+
remediated: false,
|
|
86
|
+
});
|
|
71
87
|
}
|
|
72
88
|
|
|
73
89
|
// Phase 1: detect in parallel with timeout.
|
package/src/checks/tokens.js
CHANGED
|
@@ -1,41 +1,83 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Provider CLI availability checks (preflight).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* active role
|
|
4
|
+
* IMPORTANT β Karajan does NOT call provider APIs directly. There is no
|
|
5
|
+
* `@anthropic-ai/sdk`, no `openai`, no `@google/generative-ai` in
|
|
6
|
+
* package.json. Every active role spawns its agent CLI as a subprocess
|
|
7
|
+
* (`claude`, `codex`, `gemini`, `opencode`, `aider`). The CLI handles its
|
|
8
|
+
* own auth, typically via OAuth + on-disk token (Claude Code, Codex CLI,
|
|
9
|
+
* Gemini CLI). The user does NOT need to set ANTHROPIC_API_KEY etc. for
|
|
10
|
+
* Karajan to work.
|
|
7
11
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* Pre-v2.7.4 this module checked for ANTHROPIC_API_KEY / OPENAI_API_KEY /
|
|
13
|
+
* GEMINI_API_KEY env vars and FAILed preflight when missing β a false
|
|
14
|
+
* positive that blocked every user running Karajan as an MCP child of
|
|
15
|
+
* Claude Code (where `apiKeySource: "none"` is the norm). Replaced with a
|
|
16
|
+
* CLI-availability check that mirrors what the orchestrator actually does.
|
|
17
|
+
*
|
|
18
|
+
* GH_TOKEN is still checked when `git.auto_pr=true`, because that flow uses
|
|
19
|
+
* `git push` directly with the env var.
|
|
14
20
|
*/
|
|
15
21
|
|
|
16
22
|
import { checkBinary } from "../utils/agent-detect.js";
|
|
17
|
-
import { getRequiredProviderEnvs,
|
|
23
|
+
import { getRequiredProviderEnvs, normalizeProvider } from "../utils/provider-env.js";
|
|
18
24
|
import { STRATEGY } from "./types.js";
|
|
19
25
|
|
|
20
26
|
/**
|
|
21
|
-
*
|
|
27
|
+
* Map canonical provider name β the CLI binary Karajan actually spawns.
|
|
28
|
+
* Keep in sync with src/agents/index.js.
|
|
22
29
|
*/
|
|
23
|
-
|
|
30
|
+
const PROVIDER_TO_CLI = Object.freeze({
|
|
31
|
+
anthropic: "claude",
|
|
32
|
+
openai: "codex",
|
|
33
|
+
google: "gemini",
|
|
34
|
+
opencode: "opencode",
|
|
35
|
+
aider: "aider",
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Build a CLI-availability check for one provider.
|
|
40
|
+
*
|
|
41
|
+
* The check runs `<cli> --version`. If the binary is on PATH and exits 0,
|
|
42
|
+
* preflight passes β Karajan can spawn it. If the CLI is missing, preflight
|
|
43
|
+
* fails with the install URL we already track in PROVIDER_ENV.
|
|
44
|
+
*
|
|
45
|
+
* Note: we do NOT verify that the CLI is *authenticated*. Doing so would
|
|
46
|
+
* require provider-specific introspection (`claude doctor`, `codex login
|
|
47
|
+
* status`, ...), each with its own quirks and exit codes. If the CLI is
|
|
48
|
+
* present but not logged in, the agent will fail at the first request with
|
|
49
|
+
* a clear error β and the orchestrator surfaces that error already.
|
|
50
|
+
*/
|
|
51
|
+
function createProviderCliCheck(entry) {
|
|
52
|
+
const cli = PROVIDER_TO_CLI[entry.provider];
|
|
24
53
|
return {
|
|
25
|
-
name: `
|
|
26
|
-
label:
|
|
54
|
+
name: `cli:${entry.provider}`,
|
|
55
|
+
label: `${entry.provider} CLI (${cli})`,
|
|
27
56
|
strategy: STRATEGY.MANUAL,
|
|
28
57
|
async detect() {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
58
|
+
if (!cli) {
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
severity: "warn",
|
|
62
|
+
detail: `Unknown CLI for provider ${entry.provider}`,
|
|
63
|
+
extra: { provider: entry.provider },
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const result = await checkBinary(cli);
|
|
67
|
+
if (result.ok) {
|
|
68
|
+
return {
|
|
69
|
+
ok: true,
|
|
70
|
+
severity: "info",
|
|
71
|
+
detail: `${cli} on PATH (${result.version || "version unknown"})`,
|
|
72
|
+
extra: { provider: entry.provider, cli, version: result.version, path: result.path },
|
|
73
|
+
};
|
|
32
74
|
}
|
|
33
75
|
return {
|
|
34
76
|
ok: false,
|
|
35
77
|
severity: entry.optional ? "warn" : "fail",
|
|
36
|
-
detail: `${
|
|
37
|
-
fix: `
|
|
38
|
-
extra: { provider: entry.provider,
|
|
78
|
+
detail: `${cli} not found on PATH`,
|
|
79
|
+
fix: `Install the ${cli} CLI: see ${entry.howToGetUrl}`,
|
|
80
|
+
extra: { provider: entry.provider, cli, howToGetUrl: entry.howToGetUrl },
|
|
39
81
|
};
|
|
40
82
|
},
|
|
41
83
|
};
|
|
@@ -43,6 +85,8 @@ function createProviderTokenCheck(entry) {
|
|
|
43
85
|
|
|
44
86
|
/**
|
|
45
87
|
* GH token check + auto-remediation via `gh auth login` when it's installed.
|
|
88
|
+
* Kept from the previous version: this one IS legitimate because Karajan
|
|
89
|
+
* uses `gh` (or raw `git push`) directly when auto_pr is on.
|
|
46
90
|
*/
|
|
47
91
|
function createGhTokenCheck() {
|
|
48
92
|
return {
|
|
@@ -82,15 +126,24 @@ function createGhTokenCheck() {
|
|
|
82
126
|
}
|
|
83
127
|
|
|
84
128
|
/**
|
|
85
|
-
* Build the list of token checks for the active config.
|
|
86
|
-
*
|
|
129
|
+
* Build the list of CLI/token checks for the active config.
|
|
130
|
+
* - One CLI availability check per active provider (claude / codex / gemini / ...).
|
|
131
|
+
* - One GH token check, conditional on git.auto_pr.
|
|
132
|
+
*
|
|
133
|
+
* Function name kept as `getTokenChecks` for backward compat with
|
|
134
|
+
* preflight-checks.js, even though the bulk of the checks no longer touch
|
|
135
|
+
* tokens at all. Tracked: rename to `getProviderChecks` in a follow-up
|
|
136
|
+
* once consumers are updated.
|
|
87
137
|
*
|
|
88
138
|
* @param {Object} config
|
|
89
139
|
* @returns {import("./types.js").Check[]}
|
|
90
140
|
*/
|
|
91
141
|
export function getTokenChecks(config) {
|
|
92
142
|
const required = getRequiredProviderEnvs(config);
|
|
93
|
-
const checks = required.map(
|
|
143
|
+
const checks = required.map(createProviderCliCheck);
|
|
94
144
|
checks.push(createGhTokenCheck());
|
|
95
145
|
return checks;
|
|
96
146
|
}
|
|
147
|
+
|
|
148
|
+
// Re-export for tests that need to introspect the mapping.
|
|
149
|
+
export { PROVIDER_TO_CLI, normalizeProvider };
|
package/src/cli.js
CHANGED
|
@@ -76,7 +76,8 @@ program
|
|
|
76
76
|
program
|
|
77
77
|
.command("run")
|
|
78
78
|
.description("Run coder+sonar+reviewer loop")
|
|
79
|
-
.argument("
|
|
79
|
+
.argument("[task]", "Task description (or use --task-file to read from a .md file)")
|
|
80
|
+
.option("--task-file <path>", "Read the task from a file (e.g. .md). Alternative to the positional task argument.")
|
|
80
81
|
.option("--planner <name>")
|
|
81
82
|
.option("--coder <name>")
|
|
82
83
|
.option("--reviewer <name>")
|
|
@@ -138,32 +139,40 @@ program
|
|
|
138
139
|
.option("-v, --verbose", "Show full agent output (stream-json, raw lines)")
|
|
139
140
|
.action(async (task, flags) => {
|
|
140
141
|
await withConfig("run", flags, async ({ config, logger }) => {
|
|
141
|
-
|
|
142
|
+
const { resolveTaskInput } = await import("./utils/task-file.js");
|
|
143
|
+
const resolvedTask = await resolveTaskInput({ task, taskFile: flags.taskFile, projectDir: config.projectDir, logger });
|
|
144
|
+
await runCommandHandler({ task: resolvedTask, config, logger, flags });
|
|
142
145
|
});
|
|
143
146
|
});
|
|
144
147
|
|
|
145
148
|
program
|
|
146
149
|
.command("code")
|
|
147
150
|
.description("Run only coder")
|
|
148
|
-
.argument("
|
|
151
|
+
.argument("[task]", "Task description (or use --task-file)")
|
|
152
|
+
.option("--task-file <path>", "Read the task from a file (e.g. .md)")
|
|
149
153
|
.option("--coder <name>")
|
|
150
154
|
.option("--coder-model <name>")
|
|
151
155
|
.action(async (task, flags) => {
|
|
152
156
|
await withConfig("code", flags, async ({ config, logger }) => {
|
|
153
|
-
|
|
157
|
+
const { resolveTaskInput } = await import("./utils/task-file.js");
|
|
158
|
+
const resolvedTask = await resolveTaskInput({ task, taskFile: flags.taskFile, projectDir: config.projectDir, logger });
|
|
159
|
+
await codeCommand({ task: resolvedTask, config, logger });
|
|
154
160
|
});
|
|
155
161
|
});
|
|
156
162
|
|
|
157
163
|
program
|
|
158
164
|
.command("review")
|
|
159
165
|
.description("Run only reviewer")
|
|
160
|
-
.argument("
|
|
166
|
+
.argument("[task]", "Task description (or use --task-file)")
|
|
167
|
+
.option("--task-file <path>", "Read the task from a file (e.g. .md)")
|
|
161
168
|
.option("--reviewer <name>")
|
|
162
169
|
.option("--reviewer-model <name>")
|
|
163
170
|
.option("--base-ref <ref>")
|
|
164
171
|
.action(async (task, flags) => {
|
|
165
172
|
await withConfig("review", flags, async ({ config, logger }) => {
|
|
166
|
-
|
|
173
|
+
const { resolveTaskInput } = await import("./utils/task-file.js");
|
|
174
|
+
const resolvedTask = await resolveTaskInput({ task, taskFile: flags.taskFile, projectDir: config.projectDir, logger });
|
|
175
|
+
await reviewCommand({ task: resolvedTask, config, logger, baseRef: flags.baseRef });
|
|
167
176
|
});
|
|
168
177
|
});
|
|
169
178
|
|
|
@@ -248,15 +257,18 @@ const plan = program.command("plan").description("Plan management (generate, rev
|
|
|
248
257
|
plan
|
|
249
258
|
.command("generate", { isDefault: true })
|
|
250
259
|
.description("Generate implementation plan with HUs")
|
|
251
|
-
.argument("
|
|
260
|
+
.argument("[task]", "Task description (or use --task-file)")
|
|
261
|
+
.option("--task-file <path>", "Read the task from a file (e.g. .md)")
|
|
252
262
|
.option("--planner <name>")
|
|
253
263
|
.option("--planner-model <name>")
|
|
254
264
|
.option("--context <text>", "Additional context for the planner")
|
|
255
265
|
.option("--json", "Output raw JSON plan")
|
|
256
266
|
.action(async (task, flags) => {
|
|
257
267
|
await withConfig("plan", flags, async ({ config, logger }) => {
|
|
268
|
+
const { resolveTaskInput } = await import("./utils/task-file.js");
|
|
269
|
+
const resolvedTask = await resolveTaskInput({ task, taskFile: flags.taskFile, projectDir: config.projectDir, logger });
|
|
258
270
|
const { planGenerateCommand } = await import("./commands/plan.js");
|
|
259
|
-
await planGenerateCommand({ task, config, logger, json: flags.json, context: flags.context });
|
|
271
|
+
await planGenerateCommand({ task: resolvedTask, config, logger, json: flags.json, context: flags.context });
|
|
260
272
|
});
|
|
261
273
|
});
|
|
262
274
|
|
|
@@ -317,65 +329,83 @@ plan.command("remove-hu").description("Remove HU from plan").argument("<planId>"
|
|
|
317
329
|
program
|
|
318
330
|
.command("discover")
|
|
319
331
|
.description("Analyze task for gaps, ambiguities and missing info")
|
|
320
|
-
.argument("
|
|
332
|
+
.argument("[task]", "Task description (or use --task-file)")
|
|
333
|
+
.option("--task-file <path>", "Read the task from a file (e.g. .md)")
|
|
321
334
|
.option("--mode <name>", "Discovery mode: gaps|momtest|wendel|classify|jtbd", "gaps")
|
|
322
335
|
.option("--discover <name>", "Override discover agent")
|
|
323
336
|
.option("--discover-model <name>", "Override discover model")
|
|
324
337
|
.option("--json", "Output raw JSON")
|
|
325
338
|
.action(async (task, flags) => {
|
|
326
339
|
await withConfig("discover", flags, async ({ config, logger }) => {
|
|
327
|
-
|
|
340
|
+
const { resolveTaskInput } = await import("./utils/task-file.js");
|
|
341
|
+
const resolvedTask = await resolveTaskInput({ task, taskFile: flags.taskFile, projectDir: config.projectDir, logger });
|
|
342
|
+
await discoverCommand({ task: resolvedTask, config, logger, mode: flags.mode, json: flags.json });
|
|
328
343
|
});
|
|
329
344
|
});
|
|
330
345
|
|
|
331
346
|
program
|
|
332
347
|
.command("triage")
|
|
333
348
|
.description("Classify task complexity and recommend pipeline roles")
|
|
334
|
-
.argument("
|
|
349
|
+
.argument("[task]", "Task description (or use --task-file)")
|
|
350
|
+
.option("--task-file <path>", "Read the task from a file (e.g. .md)")
|
|
335
351
|
.option("--triage <name>", "Override triage agent")
|
|
336
352
|
.option("--triage-model <name>", "Override triage model")
|
|
337
353
|
.option("--json", "Output raw JSON")
|
|
338
354
|
.action(async (task, flags) => {
|
|
339
355
|
await withConfig("triage", flags, async ({ config, logger }) => {
|
|
340
|
-
|
|
356
|
+
const { resolveTaskInput } = await import("./utils/task-file.js");
|
|
357
|
+
const resolvedTask = await resolveTaskInput({ task, taskFile: flags.taskFile, projectDir: config.projectDir, logger });
|
|
358
|
+
await triageCommand({ task: resolvedTask, config, logger, json: flags.json });
|
|
341
359
|
});
|
|
342
360
|
});
|
|
343
361
|
|
|
344
362
|
program
|
|
345
363
|
.command("researcher")
|
|
346
364
|
.description("Research codebase for a task (files, patterns, constraints)")
|
|
347
|
-
.argument("
|
|
365
|
+
.argument("[task]", "Task description (or use --task-file)")
|
|
366
|
+
.option("--task-file <path>", "Read the task from a file (e.g. .md)")
|
|
348
367
|
.option("--researcher <name>", "Override researcher agent")
|
|
349
368
|
.option("--researcher-model <name>", "Override researcher model")
|
|
350
369
|
.action(async (task, flags) => {
|
|
351
370
|
await withConfig("researcher", flags, async ({ config, logger }) => {
|
|
352
|
-
|
|
371
|
+
const { resolveTaskInput } = await import("./utils/task-file.js");
|
|
372
|
+
const resolvedTask = await resolveTaskInput({ task, taskFile: flags.taskFile, projectDir: config.projectDir, logger });
|
|
373
|
+
await researcherCommand({ task: resolvedTask, config, logger });
|
|
353
374
|
});
|
|
354
375
|
});
|
|
355
376
|
|
|
356
377
|
program
|
|
357
378
|
.command("architect")
|
|
358
379
|
.description("Design solution architecture (layers, patterns, contracts)")
|
|
359
|
-
.argument("
|
|
380
|
+
.argument("[task]", "Task description (or use --task-file)")
|
|
381
|
+
.option("--task-file <path>", "Read the task from a file (e.g. .md)")
|
|
360
382
|
.option("--architect <name>", "Override architect agent")
|
|
361
383
|
.option("--architect-model <name>", "Override architect model")
|
|
362
384
|
.option("--context <text>", "Additional context (e.g. researcher output)")
|
|
363
385
|
.option("--json", "Output raw JSON")
|
|
364
386
|
.action(async (task, flags) => {
|
|
365
387
|
await withConfig("architect", flags, async ({ config, logger }) => {
|
|
366
|
-
|
|
388
|
+
const { resolveTaskInput } = await import("./utils/task-file.js");
|
|
389
|
+
const resolvedTask = await resolveTaskInput({ task, taskFile: flags.taskFile, projectDir: config.projectDir, logger });
|
|
390
|
+
await architectCommand({ task: resolvedTask, config, logger, context: flags.context, json: flags.json });
|
|
367
391
|
});
|
|
368
392
|
});
|
|
369
393
|
|
|
370
394
|
program
|
|
371
395
|
.command("audit")
|
|
372
396
|
.description("Analyze codebase health (read-only)")
|
|
373
|
-
.argument("[task]")
|
|
397
|
+
.argument("[task]", "Task description. If absent, defaults to a full-codebase analysis. Use --task-file to point at a .md.")
|
|
398
|
+
.option("--task-file <path>", "Read the task from a file (e.g. .md)")
|
|
374
399
|
.option("--dimensions <list>", "Comma-separated: security,quality,performance,architecture,testing", "all")
|
|
375
400
|
.option("--json", "Output raw JSON")
|
|
376
401
|
.action(async (task, flags) => {
|
|
377
402
|
await withConfig("audit", flags, async ({ config, logger }) => {
|
|
378
|
-
|
|
403
|
+
let resolvedTask = task;
|
|
404
|
+
if (flags.taskFile) {
|
|
405
|
+
const { readTaskFile } = await import("./utils/task-file.js");
|
|
406
|
+
resolvedTask = await readTaskFile(flags.taskFile, { projectDir: config.projectDir });
|
|
407
|
+
}
|
|
408
|
+
await auditCommand({ task: resolvedTask || "Analyze the full codebase", config, logger, dimensions: flags.dimensions, json: flags.json });
|
|
379
409
|
});
|
|
380
410
|
});
|
|
381
411
|
|
|
@@ -58,30 +58,35 @@ function formatArchitect(result) {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
export async function architectCommand({ task, config, logger, context, json }) {
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
const { withCliRunLog } = await import("../utils/cli-run-log.js");
|
|
62
|
+
return withCliRunLog("architect", { projectDir: config?.projectDir, logger }, async ({ runLog }) => {
|
|
63
|
+
const architectRole = resolveRole(config, "architect");
|
|
64
|
+
await assertAgentsAvailable([architectRole.provider]);
|
|
65
|
+
logger.info(`Architect (${architectRole.provider}) starting...`);
|
|
66
|
+
runLog.logText(`[architect] provider=${architectRole.provider}`);
|
|
64
67
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
const agent = createAgent(architectRole.provider, config, logger);
|
|
69
|
+
const prompt = await buildArchitectPrompt({ task, researchContext: context });
|
|
70
|
+
const onOutput = ({ line }) => process.stdout.write(`${line}\n`);
|
|
71
|
+
const result = await agent.runTask({ prompt, onOutput, role: "architect" });
|
|
69
72
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
if (!result.ok) {
|
|
74
|
+
throw new Error(result.error || result.output || "Architect failed");
|
|
75
|
+
}
|
|
73
76
|
|
|
74
|
-
|
|
77
|
+
const parsed = parseArchitectOutput(result.output);
|
|
75
78
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
if (json) {
|
|
80
|
+
console.log(JSON.stringify(parsed || result.output, null, 2));
|
|
81
|
+
return { ok: true };
|
|
82
|
+
}
|
|
80
83
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
if (parsed?.verdict) {
|
|
85
|
+
console.log(formatArchitect(parsed));
|
|
86
|
+
} else {
|
|
87
|
+
console.log(result.output);
|
|
88
|
+
}
|
|
89
|
+
logger.info("Architect completed.");
|
|
90
|
+
return { ok: true };
|
|
91
|
+
});
|
|
87
92
|
}
|