Merge pull request #80 from nrslib/takt/issue-70-1769924108
CI用にログ出力最小モードを導入する
This commit is contained in:
commit
29841f0c51
20
src/cli.ts
20
src/cli.ts
@ -66,6 +66,9 @@ let resolvedCwd = '';
|
|||||||
/** Whether pipeline mode is active (--task specified, set in preAction) */
|
/** Whether pipeline mode is active (--task specified, set in preAction) */
|
||||||
let pipelineMode = false;
|
let pipelineMode = false;
|
||||||
|
|
||||||
|
/** Whether quiet mode is active (--quiet flag or config, set in preAction) */
|
||||||
|
let quietMode = false;
|
||||||
|
|
||||||
export interface WorktreeConfirmationResult {
|
export interface WorktreeConfirmationResult {
|
||||||
execCwd: string;
|
execCwd: string;
|
||||||
isWorktree: boolean;
|
isWorktree: boolean;
|
||||||
@ -263,7 +266,8 @@ program
|
|||||||
.option('-t, --task <string>', 'Task content (as alternative to GitHub issue)')
|
.option('-t, --task <string>', 'Task content (as alternative to GitHub issue)')
|
||||||
.option('--pipeline', 'Pipeline mode: non-interactive, no worktree, direct branch creation')
|
.option('--pipeline', 'Pipeline mode: non-interactive, no worktree, direct branch creation')
|
||||||
.option('--skip-git', 'Skip branch creation, commit, and push (pipeline mode)')
|
.option('--skip-git', 'Skip branch creation, commit, and push (pipeline mode)')
|
||||||
.option('--create-worktree <yes|no>', 'Skip the worktree prompt by explicitly specifying yes or no');
|
.option('--create-worktree <yes|no>', 'Skip the worktree prompt by explicitly specifying yes or no')
|
||||||
|
.option('-q, --quiet', 'Minimal output mode: suppress AI output (for CI)');
|
||||||
|
|
||||||
// Common initialization for all commands
|
// Common initialization for all commands
|
||||||
program.hook('preAction', async () => {
|
program.hook('preAction', async () => {
|
||||||
@ -285,17 +289,27 @@ program.hook('preAction', async () => {
|
|||||||
|
|
||||||
initDebugLogger(debugConfig, resolvedCwd);
|
initDebugLogger(debugConfig, resolvedCwd);
|
||||||
|
|
||||||
|
// Load config once for both log level and quiet mode
|
||||||
|
const config = loadGlobalConfig();
|
||||||
|
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
setVerboseConsole(true);
|
setVerboseConsole(true);
|
||||||
setLogLevel('debug');
|
setLogLevel('debug');
|
||||||
} else {
|
} else {
|
||||||
const config = loadGlobalConfig();
|
|
||||||
setLogLevel(config.logLevel);
|
setLogLevel(config.logLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info('TAKT CLI starting', { version: cliVersion, cwd: resolvedCwd, verbose, pipelineMode });
|
// Quiet mode: CLI flag takes precedence over config
|
||||||
|
quietMode = rootOpts.quiet === true || config.minimalOutput === true;
|
||||||
|
|
||||||
|
log.info('TAKT CLI starting', { version: cliVersion, cwd: resolvedCwd, verbose, pipelineMode, quietMode });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** Get whether quiet mode is active (CLI flag or config, resolved in preAction) */
|
||||||
|
export function isQuietMode(): boolean {
|
||||||
|
return quietMode;
|
||||||
|
}
|
||||||
|
|
||||||
// --- Subcommands ---
|
// --- Subcommands ---
|
||||||
|
|
||||||
program
|
program
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
import * as readline from 'node:readline';
|
import * as readline from 'node:readline';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { loadGlobalConfig } from '../config/globalConfig.js';
|
import { loadGlobalConfig } from '../config/globalConfig.js';
|
||||||
|
import { isQuietMode } from '../cli.js';
|
||||||
import { loadAgentSessions, updateAgentSession } from '../config/paths.js';
|
import { loadAgentSessions, updateAgentSession } from '../config/paths.js';
|
||||||
import { getProvider, type ProviderType } from '../providers/index.js';
|
import { getProvider, type ProviderType } from '../providers/index.js';
|
||||||
import { createLogger } from '../utils/debug.js';
|
import { createLogger } from '../utils/debug.js';
|
||||||
@ -151,14 +152,14 @@ export async function interactiveMode(cwd: string, initialInput?: string): Promi
|
|||||||
|
|
||||||
/** Call AI with automatic retry on session error (stale/invalid session ID). */
|
/** Call AI with automatic retry on session error (stale/invalid session ID). */
|
||||||
async function callAIWithRetry(prompt: string): Promise<CallAIResult | null> {
|
async function callAIWithRetry(prompt: string): Promise<CallAIResult | null> {
|
||||||
const display = new StreamDisplay('assistant');
|
const display = new StreamDisplay('assistant', isQuietMode());
|
||||||
try {
|
try {
|
||||||
const result = await callAI(provider, prompt, cwd, model, sessionId, display);
|
const result = await callAI(provider, prompt, cwd, model, sessionId, display);
|
||||||
// If session failed, clear it and retry without session
|
// If session failed, clear it and retry without session
|
||||||
if (!result.success && sessionId) {
|
if (!result.success && sessionId) {
|
||||||
log.info('Session invalid, retrying without session');
|
log.info('Session invalid, retrying without session');
|
||||||
sessionId = undefined;
|
sessionId = undefined;
|
||||||
const retryDisplay = new StreamDisplay('assistant');
|
const retryDisplay = new StreamDisplay('assistant', isQuietMode());
|
||||||
const retry = await callAI(provider, prompt, cwd, model, undefined, retryDisplay);
|
const retry = await callAI(provider, prompt, cwd, model, undefined, retryDisplay);
|
||||||
if (retry.sessionId) {
|
if (retry.sessionId) {
|
||||||
sessionId = retry.sessionId;
|
sessionId = retry.sessionId;
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import {
|
|||||||
updateWorktreeSession,
|
updateWorktreeSession,
|
||||||
} from '../config/paths.js';
|
} from '../config/paths.js';
|
||||||
import { loadGlobalConfig } from '../config/globalConfig.js';
|
import { loadGlobalConfig } from '../config/globalConfig.js';
|
||||||
|
import { isQuietMode } from '../cli.js';
|
||||||
import {
|
import {
|
||||||
header,
|
header,
|
||||||
info,
|
info,
|
||||||
@ -200,7 +201,8 @@ export async function executeWorkflow(
|
|||||||
log.debug('Step instruction', instruction);
|
log.debug('Step instruction', instruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
displayRef.current = new StreamDisplay(step.agentDisplayName);
|
// Use quiet mode from CLI (already resolved CLI flag + config in preAction)
|
||||||
|
displayRef.current = new StreamDisplay(step.agentDisplayName, isQuietMode());
|
||||||
|
|
||||||
// Write step_start record to NDJSON log
|
// Write step_start record to NDJSON log
|
||||||
const record: NdjsonStepStart = {
|
const record: NdjsonStepStart = {
|
||||||
|
|||||||
@ -52,6 +52,7 @@ export function loadGlobalConfig(): GlobalConfig {
|
|||||||
commitMessageTemplate: parsed.pipeline.commit_message_template,
|
commitMessageTemplate: parsed.pipeline.commit_message_template,
|
||||||
prBodyTemplate: parsed.pipeline.pr_body_template,
|
prBodyTemplate: parsed.pipeline.pr_body_template,
|
||||||
} : undefined,
|
} : undefined,
|
||||||
|
minimalOutput: parsed.minimal_output,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,6 +96,9 @@ export function saveGlobalConfig(config: GlobalConfig): void {
|
|||||||
raw.pipeline = pipelineRaw;
|
raw.pipeline = pipelineRaw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (config.minimalOutput !== undefined) {
|
||||||
|
raw.minimal_output = config.minimalOutput;
|
||||||
|
}
|
||||||
writeFileSync(configPath, stringifyYaml(raw), 'utf-8');
|
writeFileSync(configPath, stringifyYaml(raw), 'utf-8');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -185,6 +185,8 @@ export const GlobalConfigSchema = z.object({
|
|||||||
openai_api_key: z.string().optional(),
|
openai_api_key: z.string().optional(),
|
||||||
/** Pipeline execution settings */
|
/** Pipeline execution settings */
|
||||||
pipeline: PipelineConfigSchema.optional(),
|
pipeline: PipelineConfigSchema.optional(),
|
||||||
|
/** Minimal output mode for CI - suppress AI output to prevent sensitive information leaks */
|
||||||
|
minimal_output: z.boolean().optional().default(false),
|
||||||
});
|
});
|
||||||
|
|
||||||
/** Project config schema */
|
/** Project config schema */
|
||||||
|
|||||||
@ -207,6 +207,8 @@ export interface GlobalConfig {
|
|||||||
openaiApiKey?: string;
|
openaiApiKey?: string;
|
||||||
/** Pipeline execution settings */
|
/** Pipeline execution settings */
|
||||||
pipeline?: PipelineConfig;
|
pipeline?: PipelineConfig;
|
||||||
|
/** Minimal output mode for CI - suppress AI output to prevent sensitive information leaks */
|
||||||
|
minimalOutput?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Project-level configuration */
|
/** Project-level configuration */
|
||||||
|
|||||||
@ -166,10 +166,14 @@ export class StreamDisplay {
|
|||||||
private spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
private spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
||||||
private spinnerFrame = 0;
|
private spinnerFrame = 0;
|
||||||
|
|
||||||
constructor(private agentName = 'Claude') {}
|
constructor(
|
||||||
|
private agentName = 'Claude',
|
||||||
|
private quiet = false,
|
||||||
|
) {}
|
||||||
|
|
||||||
/** Display initialization event */
|
/** Display initialization event */
|
||||||
showInit(model: string): void {
|
showInit(model: string): void {
|
||||||
|
if (this.quiet) return;
|
||||||
console.log(chalk.gray(`[${this.agentName}] Model: ${model}`));
|
console.log(chalk.gray(`[${this.agentName}] Model: ${model}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,6 +206,8 @@ export class StreamDisplay {
|
|||||||
|
|
||||||
/** Display tool use event */
|
/** Display tool use event */
|
||||||
showToolUse(tool: string, input: Record<string, unknown>): void {
|
showToolUse(tool: string, input: Record<string, unknown>): void {
|
||||||
|
if (this.quiet) return;
|
||||||
|
|
||||||
// Clear any buffered text first
|
// Clear any buffered text first
|
||||||
this.flushText();
|
this.flushText();
|
||||||
|
|
||||||
@ -216,6 +222,7 @@ export class StreamDisplay {
|
|||||||
|
|
||||||
/** Display tool output streaming */
|
/** Display tool output streaming */
|
||||||
showToolOutput(output: string, tool?: string): void {
|
showToolOutput(output: string, tool?: string): void {
|
||||||
|
if (this.quiet) return;
|
||||||
if (!output) return;
|
if (!output) return;
|
||||||
this.stopToolSpinner();
|
this.stopToolSpinner();
|
||||||
this.flushThinking();
|
this.flushThinking();
|
||||||
@ -238,9 +245,22 @@ export class StreamDisplay {
|
|||||||
|
|
||||||
/** Display tool result event */
|
/** Display tool result event */
|
||||||
showToolResult(content: string, isError: boolean): void {
|
showToolResult(content: string, isError: boolean): void {
|
||||||
// Stop the spinner first
|
// Stop the spinner first (always, even in quiet mode to prevent spinner artifacts)
|
||||||
this.stopToolSpinner();
|
this.stopToolSpinner();
|
||||||
|
|
||||||
|
if (this.quiet) {
|
||||||
|
// In quiet mode: show errors but suppress success messages
|
||||||
|
if (isError) {
|
||||||
|
const toolName = this.lastToolUse || 'Tool';
|
||||||
|
const errorContent = content || 'Unknown error';
|
||||||
|
console.log(chalk.red(` ✗ ${toolName}:`), chalk.red(truncate(errorContent, 70)));
|
||||||
|
}
|
||||||
|
this.lastToolUse = null;
|
||||||
|
this.currentToolInputPreview = null;
|
||||||
|
this.toolOutputPrinted = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.toolOutputBuffer) {
|
if (this.toolOutputBuffer) {
|
||||||
this.printToolOutputLines([this.toolOutputBuffer], this.lastToolUse ?? undefined);
|
this.printToolOutputLines([this.toolOutputBuffer], this.lastToolUse ?? undefined);
|
||||||
this.toolOutputBuffer = '';
|
this.toolOutputBuffer = '';
|
||||||
@ -264,6 +284,8 @@ export class StreamDisplay {
|
|||||||
|
|
||||||
/** Display streaming thinking (Claude's internal reasoning) */
|
/** Display streaming thinking (Claude's internal reasoning) */
|
||||||
showThinking(thinking: string): void {
|
showThinking(thinking: string): void {
|
||||||
|
if (this.quiet) return;
|
||||||
|
|
||||||
// Stop spinner if running
|
// Stop spinner if running
|
||||||
this.stopToolSpinner();
|
this.stopToolSpinner();
|
||||||
// Flush any regular text first
|
// Flush any regular text first
|
||||||
@ -292,6 +314,8 @@ export class StreamDisplay {
|
|||||||
|
|
||||||
/** Display streaming text (accumulated) */
|
/** Display streaming text (accumulated) */
|
||||||
showText(text: string): void {
|
showText(text: string): void {
|
||||||
|
if (this.quiet) return;
|
||||||
|
|
||||||
// Stop spinner if running
|
// Stop spinner if running
|
||||||
this.stopToolSpinner();
|
this.stopToolSpinner();
|
||||||
// Flush any thinking first
|
// Flush any thinking first
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user