chorenzo
Version:
Open-source CLI engine that automates your engineering workflow with AI-powered workspace analysis and recipe application
1,586 lines (1,550 loc) • 281 kB
JavaScript
#!/usr/bin/env node
// src/main.ts
import { Command } from "commander";
import { render } from "ink";
import React16 from "react";
// src/utils/error.utils.ts
function extractErrorMessage(error) {
return error instanceof Error ? error.message : String(error);
}
function formatErrorMessage(context, error) {
const message = extractErrorMessage(error);
return `${context}: ${message}`;
}
// src/utils/logger.utils.ts
import * as fs3 from "fs";
import * as path3 from "path";
import pino from "pino";
// src/utils/workspace-config.utils.ts
import * as fs2 from "fs";
import * as path2 from "path";
// src/utils/git.utils.ts
import * as fs from "fs";
import * as path from "path";
function findGitRoot(startPath = process.cwd()) {
let currentPath = path.resolve(startPath);
while (currentPath !== path.dirname(currentPath)) {
const gitPath = path.join(currentPath, ".git");
try {
const stat = fs.statSync(gitPath);
if (stat.isDirectory() || stat.isFile()) {
return currentPath;
}
} catch (error) {
if (error && typeof error === "object" && "code" in error && error.code !== "ENOENT") {
throw error;
}
}
currentPath = path.dirname(currentPath);
}
return process.cwd();
}
function normalizeRepoIdentifier(gitUrl) {
let normalized = gitUrl;
if (normalized.startsWith("git@")) {
normalized = normalized.replace(/^git@([^:]+):/, "$1/").replace(/\.git$/, "");
} else {
normalized = normalized.replace(/^https?:\/\//, "").replace(/\.git$/, "");
}
const match = normalized.match(/([^/]+)\/([^/]+\/[^/]+)$/);
if (match?.[2]) {
return match[2];
}
return normalized;
}
// src/utils/workspace-config.utils.ts
var WorkspaceConfig = class _WorkspaceConfig {
static instance = null;
workspaceRoot = null;
constructor() {
}
static getInstance() {
if (!_WorkspaceConfig.instance) {
_WorkspaceConfig.instance = new _WorkspaceConfig();
}
return _WorkspaceConfig.instance;
}
getWorkspaceRoot() {
if (!this.workspaceRoot) {
this.workspaceRoot = findGitRoot();
}
return this.workspaceRoot;
}
getChorenzoDir() {
const root = this.getWorkspaceRoot();
return path2.join(root, ".chorenzo");
}
getAnalysisPath() {
const chorenzoDir = this.getChorenzoDir();
return path2.join(chorenzoDir, "analysis.json");
}
getStatePath() {
const chorenzoDir = this.getChorenzoDir();
return path2.join(chorenzoDir, "state.json");
}
getLogsDir() {
const chorenzoDir = this.getChorenzoDir();
return path2.join(chorenzoDir, "logs");
}
getLogPath() {
const logsDir = this.getLogsDir();
return path2.join(logsDir, "chorenzo.log");
}
getArchivedLogPath() {
const logsDir = this.getLogsDir();
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
return path2.join(logsDir, `chorenzo-${timestamp}.log`);
}
ensureChorenzoDir() {
const chorenzoDir = this.getChorenzoDir();
fs2.mkdirSync(chorenzoDir, { recursive: true });
}
};
var workspaceConfig = WorkspaceConfig.getInstance();
// src/utils/logger.utils.ts
var Logger = class {
static instance = null;
static MAX_LOG_SIZE_MB = 10;
static rotateLogIfNeeded(logPath) {
if (!fs3.existsSync(logPath)) {
return;
}
const stats = fs3.statSync(logPath);
const fileSizeMB = stats.size / (1024 * 1024);
if (fileSizeMB > this.MAX_LOG_SIZE_MB) {
const archivedPath = workspaceConfig.getArchivedLogPath();
fs3.renameSync(logPath, archivedPath);
}
}
static initialize() {
if (this.instance) {
return;
}
const isTest = process.env["NODE_ENV"] === "test" || process.env["JEST_WORKER_ID"] !== void 0;
if (isTest) {
this.instance = pino({ level: "silent" });
return;
}
try {
const logPath = workspaceConfig.getLogPath();
fs3.mkdirSync(path3.dirname(logPath), { recursive: true });
this.rotateLogIfNeeded(logPath);
const logStream = pino.transport({
target: "pino-pretty",
options: {
destination: logPath,
colorize: false,
translateTime: "yyyy-mm-dd HH:MM:ss",
ignore: "pid,hostname",
append: true
}
});
this.instance = pino(
{
level: process.env["DEBUG"] ? "debug" : "info"
},
logStream
);
} catch (error) {
throw new Error(formatErrorMessage("Failed to initialize Logger", error));
}
}
static getInstance() {
if (!this.instance) {
this.initialize();
}
if (!this.instance) {
throw new Error("Failed to initialize logger instance");
}
return this.instance;
}
static debug(obj, msg) {
this.getInstance().debug(obj, msg);
}
static info(obj, msg) {
this.getInstance().info(obj, msg);
}
static warn(obj, msg) {
this.getInstance().warn(obj, msg);
}
static error(obj, msg) {
this.getInstance().error(obj, msg);
}
};
// package.json
var package_default = {
name: "chorenzo",
version: "1.10.0",
description: "Open-source CLI engine that automates your engineering workflow with AI-powered workspace analysis and recipe application",
main: "dist/main.js",
type: "module",
bin: {
chorenzo: "dist/main.js"
},
scripts: {
dev: "tsx watch src/main.ts",
start: "tsx src/main.ts",
build: "tsup",
typecheck: "tsc --noEmit",
format: "prettier --write .",
"format:check": "prettier --check .",
lint: "eslint .",
"lint:fix": "eslint --fix .",
test: "node --experimental-vm-modules --no-warnings node_modules/jest/bin/jest.js",
"test:watch": "node --experimental-vm-modules --no-warnings node_modules/jest/bin/jest.js --watch",
"test:coverage": "node --experimental-vm-modules --no-warnings node_modules/jest/bin/jest.js --coverage",
prepare: "husky"
},
repository: {
type: "git",
url: "git+https://github.com/chorenzo-dev/engine.git"
},
keywords: [
"cli",
"automation",
"devtools",
"ai",
"typescript",
"eslint",
"cicd",
"github-actions",
"code-quality",
"developer-experience",
"workflow",
"recipes",
"engineering-automation",
"claude-ai",
"workspace-analysis",
"monorepo",
"project-setup",
"developer-tools",
"code-formatting",
"linting",
"testing-setup",
"infrastructure-as-code",
"configuration-management",
"npx",
"chorenzo"
],
author: "Chorenzo Development Team",
license: "Apache-2.0",
homepage: "https://github.com/chorenzo-dev/engine#readme",
bugs: {
url: "https://github.com/chorenzo-dev/engine/issues"
},
files: [
"dist",
"LICENSE",
"README.md",
"package.json"
],
publishConfig: {
access: "public",
registry: "https://registry.npmjs.org/"
},
engines: {
node: ">=18.0.0"
},
overrides: {
"test-exclude": "^7.0.1",
glob: "^10.4.1"
},
devDependencies: {
"@commitlint/cli": "^19.8.1",
"@commitlint/config-conventional": "^19.8.1",
"@eslint/js": "^9.31.0",
"@jest/globals": "^30.0.4",
"@semantic-release/exec": "^7.1.0",
"@semantic-release/git": "^10.0.1",
"@semantic-release/github": "^11.0.4",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"@types/jest": "^30.0.0",
"@types/node": "^24.0.10",
"@types/react": "^19.1.8",
"@types/write-file-atomic": "^4.0.3",
"@typescript-eslint/eslint-plugin": "^8.38.0",
"@typescript-eslint/parser": "^8.38.0",
"esbuild-plugin-copy": "^2.1.1",
eslint: "^9.31.0",
globals: "^16.3.0",
husky: "^9.1.7",
jest: "^30.0.4",
"lint-staged": "^16.1.2",
memfs: "^4.17.2",
prettier: "^3.6.2",
"semantic-release": "^24.2.7",
"ts-jest": "^29.4.0",
tsup: "^8.5.0",
tsx: "^4.20.3",
typescript: "^5.8.3"
},
dependencies: {
"@anthropic-ai/claude-code": "^1.0.38",
commander: "^14.0.0",
"fastest-levenshtein": "^1.0.16",
handlebars: "^4.7.8",
ink: "^6.0.1",
"ink-select-input": "^6.2.0",
"ink-spinner": "^5.0.0",
"ink-text-input": "^6.0.0",
pino: "^9.7.0",
"pino-pretty": "^13.0.0",
react: "^19.1.0",
"simple-git": "^3.28.0",
"write-file-atomic": "^6.0.0",
yaml: "^2.8.0",
zod: "^4.0.17"
}
};
// src/Shell.tsx
import { Text as Text20 } from "ink";
import { useState as useState10 } from "react";
// src/containers/AnalysisValidateContainer.tsx
import { useEffect } from "react";
// src/styles/icons.ts
var icons = {
success: "\u2705",
error: "\u274C",
warning: "\u26A0\uFE0F",
celebration: "\u{1F389}",
arrow: "\u2192",
back: "\u2190",
dash: "-",
bullet: "\u2022"
};
// src/utils/analyze.utils.ts
import fs5 from "fs/promises";
import path4 from "path";
// src/schemas/analysis.schema.ts
import { z } from "zod";
// src/types/analysis.ts
var ProjectType = /* @__PURE__ */ ((ProjectType2) => {
ProjectType2["CliTool"] = "cli_tool";
ProjectType2["WebApp"] = "web_app";
ProjectType2["ApiServer"] = "api_server";
ProjectType2["BackendService"] = "backend_service";
ProjectType2["Library"] = "library";
ProjectType2["Script"] = "script";
ProjectType2["Infrastructure"] = "infrastructure";
ProjectType2["DesktopApp"] = "desktop_app";
ProjectType2["MobileApp"] = "mobile_app";
ProjectType2["Unknown"] = "unknown";
return ProjectType2;
})(ProjectType || {});
var CiCdSystem = /* @__PURE__ */ ((CiCdSystem2) => {
CiCdSystem2["GithubActions"] = "github_actions";
CiCdSystem2["GitlabCi"] = "gitlab_ci";
CiCdSystem2["Circleci"] = "circleci";
CiCdSystem2["Jenkins"] = "jenkins";
CiCdSystem2["TravisCi"] = "travis_ci";
CiCdSystem2["AzureDevops"] = "azure_devops";
CiCdSystem2["BitbucketPipelines"] = "bitbucket_pipelines";
CiCdSystem2["Teamcity"] = "teamcity";
CiCdSystem2["Bamboo"] = "bamboo";
CiCdSystem2["Codeship"] = "codeship";
CiCdSystem2["Drone"] = "drone";
CiCdSystem2["Buildkite"] = "buildkite";
CiCdSystem2["Semaphore"] = "semaphore";
CiCdSystem2["Appveyor"] = "appveyor";
CiCdSystem2["None"] = "none";
return CiCdSystem2;
})(CiCdSystem || {});
var Ecosystem = /* @__PURE__ */ ((Ecosystem2) => {
Ecosystem2["Javascript"] = "javascript";
Ecosystem2["Python"] = "python";
Ecosystem2["Java"] = "java";
Ecosystem2["Dotnet"] = "dotnet";
Ecosystem2["Go"] = "go";
Ecosystem2["Rust"] = "rust";
Ecosystem2["Ruby"] = "ruby";
Ecosystem2["Php"] = "php";
Ecosystem2["Swift"] = "swift";
Ecosystem2["Dart"] = "dart";
Ecosystem2["Elixir"] = "elixir";
Ecosystem2["Haskell"] = "haskell";
Ecosystem2["Perl"] = "perl";
Ecosystem2["R"] = "r";
Ecosystem2["Julia"] = "julia";
Ecosystem2["Lua"] = "lua";
Ecosystem2["Unknown"] = "unknown";
return Ecosystem2;
})(Ecosystem || {});
var Language = /* @__PURE__ */ ((Language2) => {
Language2["JavaScript"] = "javascript";
Language2["TypeScript"] = "typescript";
Language2["Python"] = "python";
Language2["Java"] = "java";
Language2["Scala"] = "scala";
Language2["Kotlin"] = "kotlin";
Language2["Groovy"] = "groovy";
Language2["CSharp"] = "csharp";
Language2["FSharp"] = "fsharp";
Language2["VBNet"] = "vbnet";
Language2["Go"] = "go";
Language2["Rust"] = "rust";
Language2["Ruby"] = "ruby";
Language2["Php"] = "php";
Language2["Swift"] = "swift";
Language2["ObjectiveC"] = "objective-c";
Language2["Dart"] = "dart";
Language2["Elixir"] = "elixir";
Language2["Haskell"] = "haskell";
Language2["Perl"] = "perl";
Language2["R"] = "r";
Language2["Julia"] = "julia";
Language2["Lua"] = "lua";
Language2["C"] = "c";
Language2["Cpp"] = "cpp";
Language2["Unknown"] = "unknown";
return Language2;
})(Language || {});
// src/schemas/analysis.schema.ts
var ProjectTypeSchema = z.nativeEnum(ProjectType);
var CiCdSystemSchema = z.nativeEnum(CiCdSystem);
var EcosystemSchema = z.nativeEnum(Ecosystem);
var LanguageSchema = z.nativeEnum(Language);
var ProjectAnalysisSchema = z.object({
path: z.string().min(1, "Project path cannot be empty"),
language: z.string().min(1, "Language cannot be empty"),
type: ProjectTypeSchema,
framework: z.string().nullable().optional(),
dependencies: z.array(z.string()).default([]),
hasPackageManager: z.boolean(),
ecosystem: EcosystemSchema.optional(),
dockerized: z.boolean().optional()
}).strict();
var WorkspaceAnalysisSchema = z.object({
isMonorepo: z.boolean(),
hasWorkspacePackageManager: z.boolean(),
workspaceEcosystem: EcosystemSchema.optional(),
workspaceDependencies: z.array(z.string()).optional().default([]),
projects: z.array(ProjectAnalysisSchema).min(1, "At least one project must be present"),
ciCd: CiCdSystemSchema.optional()
}).strict();
// src/utils/json.utils.ts
import * as fs4 from "fs";
var JsonError = class extends Error {
constructor(message, code) {
super(message);
this.code = code;
this.name = "JsonError";
}
};
function readJson(filePath) {
try {
const fileContent = fs4.readFileSync(filePath, "utf8");
const data = JSON.parse(fileContent);
return data;
} catch (error) {
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
throw new JsonError(`File not found: ${filePath}`, "FILE_NOT_FOUND");
}
if (error instanceof SyntaxError) {
throw new JsonError(
`Invalid JSON in file ${filePath}: ${error.message}`,
"PARSE_ERROR"
);
}
throw new JsonError(
formatErrorMessage("Failed to read JSON file", error),
"READ_ERROR"
);
}
}
function writeJson(filePath, data, pretty = true) {
try {
const jsonContent = pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data);
fs4.writeFileSync(filePath, jsonContent, "utf8");
} catch (error) {
throw new JsonError(
formatErrorMessage("Failed to write JSON file", error),
"WRITE_ERROR"
);
}
}
// src/utils/analyze.utils.ts
async function validateAnalysisFile(filePath) {
try {
const resolvedPath = path4.resolve(filePath);
try {
await fs5.access(resolvedPath);
} catch {
return {
valid: false,
errors: [
{
path: "file",
message: `Analysis file not found: ${filePath}`,
code: "FILE_NOT_FOUND"
}
]
};
}
let data;
try {
data = await readJson(resolvedPath);
} catch (error) {
if (error instanceof JsonError) {
return {
valid: false,
errors: [
{
path: "file",
message: `Invalid JSON: ${error.message}`,
code: "INVALID_JSON"
}
]
};
}
throw error;
}
return validateAnalysisData(data);
} catch (error) {
return {
valid: false,
errors: [
{
path: "file",
message: `Unexpected error: ${error instanceof Error ? error.message : String(error)}`,
code: "UNEXPECTED_ERROR"
}
]
};
}
}
function validateAnalysisData(data) {
try {
const result = WorkspaceAnalysisSchema.safeParse(data);
if (result.success) {
return {
valid: true,
errors: [],
data: result.data
};
}
const errors = result.error.issues.map(
(issue) => {
let message = issue.message;
if (issue.code === "invalid_type" && issue.message.includes("received undefined")) {
message = "is missing";
} else if (issue.code === "unrecognized_keys") {
const keys = issue.keys || [];
message = `unexpected field${keys.length > 1 ? "s" : ""}: ${keys.join(", ")}`;
}
return {
path: issue.path.join(".") || "root",
message,
code: issue.code
};
}
);
return {
valid: false,
errors
};
} catch (error) {
return {
valid: false,
errors: [
{
path: "root",
message: `Validation error: ${error instanceof Error ? error.message : String(error)}`,
code: "VALIDATION_ERROR"
}
]
};
}
}
function formatValidationErrors(errors) {
if (errors.length === 0) {
return "No validation errors";
}
const missingFields = [];
const invalidValues = [];
const unexpectedFields = [];
errors.forEach((error) => {
if (error.message === "is missing") {
missingFields.push(error.path);
} else if (error.message.startsWith("unexpected field")) {
const keys = error.message.match(/unexpected field[s]?: (.+)/)?.[1] || "";
keys.split(", ").forEach((key) => {
const fullPath = error.path === "root" ? key : `${error.path}.${key}`;
unexpectedFields.push(fullPath);
});
} else {
invalidValues.push(`${error.path}: ${error.message}`);
}
});
const lines = [
`Found ${errors.length} validation error${errors.length === 1 ? "" : "s"}:`
];
let errorIndex = 1;
if (missingFields.length > 0) {
lines.push(
` ${errorIndex++}. Missing fields: ${missingFields.join(", ")}`
);
}
if (invalidValues.length > 0) {
invalidValues.forEach((error) => {
lines.push(` ${errorIndex++}. Invalid field value: ${error}`);
});
}
if (unexpectedFields.length > 0) {
lines.push(
` ${errorIndex++}. Unexpected fields: ${unexpectedFields.join(", ")}`
);
}
return lines.join("\n");
}
function getDefaultAnalysisPath() {
return workspaceConfig.getAnalysisPath();
}
// src/commands/analysis.validate.ts
async function analysisValidate(options, onProgress) {
const filePath = options.file || getDefaultAnalysisPath();
Logger.info(`Starting validation process for: ${filePath}`);
onProgress?.(`Checking if analysis file exists`);
if (options.debug) {
onProgress?.(`Looking for analysis file at: ${filePath}`);
}
onProgress?.(`Reading analysis file`);
if (options.debug) {
onProgress?.(`Reading and parsing JSON from: ${filePath}`);
}
onProgress?.(`Validating file structure`);
if (options.debug) {
onProgress?.(`Running Zod schema validation`);
}
const result = await validateAnalysisFile(filePath);
if (result.valid) {
onProgress?.(`${icons.success} Analysis file is valid`);
Logger.info("Schema validation passed successfully");
if (options.debug) {
onProgress?.(
`Validated ${Object.keys(result.data || {}).length} top-level fields`
);
if (result.data && typeof result.data === "object" && "projects" in result.data) {
const projects = result.data.projects;
if (Array.isArray(projects)) {
onProgress?.(
`Found ${projects.length} project${projects.length === 1 ? "" : "s"} in analysis`
);
}
}
}
} else {
onProgress?.(`${icons.error} Validation failed`);
const errorMessage = formatValidationErrors(result.errors);
onProgress?.(errorMessage);
Logger.error(
`Schema validation failed with ${result.errors.length} error${result.errors.length === 1 ? "" : "s"}`
);
if (options.debug) {
onProgress?.(`Detailed error breakdown:`);
result.errors.forEach((error, index) => {
onProgress?.(
`Error ${index + 1}: ${error.path} - ${error.message} (${error.code})`
);
});
onProgress?.(`Validation process completed with errors`);
}
throw new Error(errorMessage);
}
}
// src/components/StepSequence.tsx
import { Box as Box3 } from "ink";
import { useState as useState2 } from "react";
// src/contexts/ProgressContext.tsx
import { createContext, useContext } from "react";
var ProgressContext = createContext(null);
// src/hooks/useDebugMessages.tsx
import { useState } from "react";
var useDebugMessages = (debugMode) => {
const [debugMessages, setDebugMessages] = useState([]);
const addDebugMessage = (stepId, type, message, isThinking) => {
if (debugMode) {
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString();
setDebugMessages((prev) => {
const lastMessage = prev[prev.length - 1];
if (lastMessage && lastMessage.message === message && lastMessage.type === type && lastMessage.stepId === stepId) {
return prev;
}
return [...prev, { timestamp, type, message, stepId, isThinking }];
});
}
};
return {
debugMessages,
addDebugMessage
};
};
// src/components/StepSequence/CompletionRenderer.tsx
import { Box as Box2 } from "ink";
// src/components/StepSequence/DebugMessagesList.tsx
import { Text } from "ink";
// src/styles/colors.ts
var colors = {
success: "green",
error: "red",
warning: "yellow",
info: void 0,
progress: "cyan",
muted: "gray",
secondary: "magenta",
default: void 0
};
// src/components/StepSequence/DebugMessagesList.tsx
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
var DebugMessagesList = ({
messages
}) => {
return /* @__PURE__ */ jsx(Fragment, { children: messages.map((msg, i) => /* @__PURE__ */ jsxs(Text, { children: [
/* @__PURE__ */ jsxs(Text, { color: colors.muted, children: [
"[",
msg.timestamp,
"]"
] }),
" ",
msg.type === "complete" ? /* @__PURE__ */ jsx(Text, { children: msg.message }) : /* @__PURE__ */ jsx(
Text,
{
color: msg.type === "processing" ? colors.info : colors.progress,
children: msg.message
}
)
] }, i)) });
};
// src/components/StepSequence/StepDisplay.tsx
import { Box, Text as Text2 } from "ink";
import Spinner from "ink-spinner";
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
var StepDisplay = ({
title,
status,
activity,
error,
isProcessing,
isActivityProcessing,
isTitleVisible = true,
children
}) => {
const getIcon = () => {
switch (status) {
case "completed":
return icons.success;
case "error":
return icons.error;
default:
return "";
}
};
const getColor = () => {
switch (status) {
case "completed":
return colors.success;
case "error":
return colors.error;
case "in_progress":
return colors.info;
default:
return colors.warning;
}
};
const icon = getIcon();
const color = getColor();
const displayTitle = title;
return /* @__PURE__ */ jsxs2(Box, { flexDirection: "column", children: [
isTitleVisible && /* @__PURE__ */ jsx2(Box, { children: /* @__PURE__ */ jsxs2(Text2, { color, children: [
status === "in_progress" ? isProcessing ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
/* @__PURE__ */ jsx2(Spinner, { type: "dots" }),
" "
] }) : " " : icon ? `${icon} ` : "",
displayTitle
] }) }),
status === "in_progress" && activity && /* @__PURE__ */ jsx2(Box, { children: /* @__PURE__ */ jsxs2(Text2, { color: colors.progress, children: [
isActivityProcessing ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
/* @__PURE__ */ jsx2(Spinner, { type: "dots" }),
" "
] }) : " ",
activity
] }) }),
status === "error" && error && /* @__PURE__ */ jsx2(Box, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { color: colors.error, children: error }) }),
children && /* @__PURE__ */ jsx2(Box, { marginLeft: status === "in_progress" ? 3 : 0, children })
] });
};
// src/components/StepSequence/CompletionRenderer.tsx
import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
var CompletionRenderer = ({
debugMode,
debugMessages,
completionTitle,
completionComponent,
stepContext
}) => {
if (debugMode) {
return /* @__PURE__ */ jsxs3(Box2, { flexDirection: "column", children: [
/* @__PURE__ */ jsx3(DebugMessagesList, { messages: debugMessages }),
completionComponent ? /* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: completionComponent(stepContext) || /* @__PURE__ */ jsx3(StepDisplay, { title: completionTitle, status: "completed" }) }) : /* @__PURE__ */ jsx3(StepDisplay, { title: completionTitle, status: "completed" })
] });
}
if (completionComponent) {
const component = completionComponent(stepContext);
if (component) {
return /* @__PURE__ */ jsx3(Fragment3, { children: component });
}
}
return /* @__PURE__ */ jsx3(StepDisplay, { title: completionTitle, status: "completed" });
};
// src/components/StepSequence/ErrorRenderer.tsx
import { Text as Text3 } from "ink";
import { jsx as jsx4 } from "react/jsx-runtime";
var ErrorRenderer = ({
error,
errorTitle,
errorComponent
}) => {
return errorComponent ? errorComponent(error) : /* @__PURE__ */ jsx4(StepDisplay, { title: errorTitle, status: "error", children: /* @__PURE__ */ jsx4(Text3, { color: "red", children: error.message }) });
};
// src/components/StepSequence.tsx
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
function getStepStatus(stepId, stepIndex, currentStepIndex, completedSteps, errorSteps) {
if (errorSteps.has(stepId)) {
return "error";
}
if (completedSteps.has(stepId)) {
return "completed";
}
if (stepIndex === currentStepIndex) {
return "in_progress";
}
return "pending";
}
var StepRenderer = ({ step, context }) => {
return step.component(context);
};
var StepSequence = ({
steps,
completionTitle = "Process completed!",
completionComponent,
errorTitle = "Process failed!",
errorComponent,
onComplete,
options = {},
debugMode = false
}) => {
const [currentStepIndex, setCurrentStepIndex] = useState2(0);
const [completedSteps, setCompletedSteps] = useState2(/* @__PURE__ */ new Set());
const [errorSteps, setErrorSteps] = useState2(/* @__PURE__ */ new Set());
const [currentActivity, setCurrentActivity] = useState2("");
const [currentError, setCurrentError] = useState2("");
const [isStepProcessing, setIsStepProcessing] = useState2(false);
const [isActivityProcessing, setIsActivityProcessing] = useState2(false);
const [isTitleVisible, setIsTitleVisible] = useState2(true);
const [results, setResults] = useState2({});
const { debugMessages, addDebugMessage } = useDebugMessages(debugMode);
const isFlowComplete = completedSteps.size === steps.length;
const flowError = errorSteps.size > 0 ? new Error(currentError) : null;
const currentStep = steps[currentStepIndex];
const progressControls = {
setActivity: (activity, processing = false) => {
Logger.info(
{ event: "setActivity_debug", activity, processing },
`setActivity called: ${activity} (processing: ${processing})`
);
if (debugMode && currentStep) {
addDebugMessage(currentStep.id, "activity", activity, processing);
} else {
setCurrentActivity(activity);
setIsActivityProcessing(processing);
}
},
setError: (error) => {
if (debugMode && currentStep) {
addDebugMessage(currentStep.id, "error", error);
}
setCurrentError(error);
if (currentStep) {
setErrorSteps((prev) => new Set(prev).add(currentStep.id));
}
},
complete: () => {
if (debugMode && currentStep) {
addDebugMessage(
currentStep.id,
"complete",
`${currentStep.title} - completed`
);
}
if (currentStep) {
setCompletedSteps((prev) => new Set(prev).add(currentStep.id));
}
setCurrentActivity("");
setCurrentError("");
setIsStepProcessing(false);
setIsActivityProcessing(false);
setIsTitleVisible(true);
if (currentStepIndex < steps.length - 1) {
const nextIndex = currentStepIndex + 1;
setCurrentStepIndex(nextIndex);
} else {
onComplete?.();
}
},
setProcessing: (processing) => {
if (debugMode && currentStep && processing) {
addDebugMessage(
currentStep.id,
"processing",
`${currentStep.title} - started`
);
}
setIsStepProcessing(processing);
},
setTitleVisible: (visible) => {
setIsTitleVisible(visible);
}
};
const stepContext = {
...progressControls,
setResult: (result) => {
if (currentStep) {
setResults((prev) => ({ ...prev, [currentStep.id]: result }));
}
},
getResult: (stepId) => results[stepId],
options
};
if (flowError) {
return /* @__PURE__ */ jsx5(
ErrorRenderer,
{
error: flowError,
errorTitle,
errorComponent
}
);
}
if (isFlowComplete) {
return /* @__PURE__ */ jsx5(
CompletionRenderer,
{
debugMode,
debugMessages,
completionTitle,
completionComponent,
stepContext
}
);
}
return /* @__PURE__ */ jsx5(ProgressContext.Provider, { value: progressControls, children: /* @__PURE__ */ jsx5(Box3, { flexDirection: "column", children: steps.map((step, index) => {
const status = getStepStatus(
step.id,
index,
currentStepIndex,
completedSteps,
errorSteps
);
const isCurrentStep = index === currentStepIndex;
const hasError = errorSteps.has(step.id);
if (status === "pending") {
return null;
}
if (debugMode) {
if (isCurrentStep) {
return /* @__PURE__ */ jsxs4(Box3, { flexDirection: "column", children: [
/* @__PURE__ */ jsx5(DebugMessagesList, { messages: debugMessages }),
/* @__PURE__ */ jsx5(StepRenderer, { step, context: stepContext })
] }, step.id);
}
return null;
}
return /* @__PURE__ */ jsx5(
StepDisplay,
{
title: step.title,
status,
activity: isCurrentStep ? currentActivity : void 0,
error: hasError ? currentError : void 0,
isProcessing: isCurrentStep ? isStepProcessing : void 0,
isActivityProcessing: isCurrentStep ? isActivityProcessing : void 0,
isTitleVisible: isCurrentStep ? isTitleVisible : true,
children: isCurrentStep && /* @__PURE__ */ jsx5(StepRenderer, { step, context: stepContext })
},
step.id
);
}) }) });
};
// src/containers/AnalysisValidateContainer.tsx
import { jsx as jsx6 } from "react/jsx-runtime";
var AnalysisValidateContainer = ({ options, onError }) => {
const steps = [
{
id: "validation",
title: "Validating analysis file",
component: (context) => {
useEffect(() => {
const runValidation = async () => {
context.setProcessing(true);
try {
await analysisValidate(options, (message) => {
context.setActivity(message);
});
context.complete();
} catch (error) {
const errorMessage = extractErrorMessage(error);
context.setError(errorMessage);
process.exitCode = 1;
onError(error instanceof Error ? error : new Error(errorMessage));
}
};
runValidation();
}, [context]);
return null;
}
}
];
return /* @__PURE__ */ jsx6(StepSequence, { steps, debugMode: options.debug });
};
// src/containers/AnalyzeContainer.tsx
import { useEffect as useEffect2 } from "react";
// src/commands/analyze.ts
import { query } from "@anthropic-ai/claude-code";
import * as fs9 from "fs";
import * as path8 from "path";
// src/utils/code-changes-events.utils.ts
import * as os from "os";
async function executeCodeChangesOperation(operationPromise, handlers, startTime = /* @__PURE__ */ new Date()) {
let sdkResultMetadata = null;
let result = null;
let errorMessage;
let success = false;
handlers.onThinkingStateChange?.(true);
try {
for await (const message of operationPromise) {
Logger.debug(
{
event: "claude_message_received",
messageType: message.type,
messageSubtype: message["subtype"] || "none",
hasContent: "content" in message,
hasMessage: "message" in message
},
`Claude message: ${message.type}`
);
if (message.type === "result") {
handlers.onThinkingStateChange?.(false);
sdkResultMetadata = message;
if (message.subtype === "success" && "result" in message) {
result = message.result;
success = true;
Logger.info(
{
event: "claude_execution_success",
resultLength: message.result ? String(message.result).length : 0
},
`Claude execution completed successfully. Result preview: ${String(message.result || "").substring(0, 200)}...`
);
} else if (message.subtype?.startsWith("error")) {
const messageObj = message;
if ("error" in messageObj) {
errorMessage = String(messageObj["error"]);
} else if ("message" in messageObj) {
errorMessage = String(messageObj["message"]);
} else if ("reason" in messageObj) {
errorMessage = String(messageObj["reason"]);
} else {
if ("content" in messageObj && messageObj["content"]) {
const content = messageObj["content"];
errorMessage = content.length > 500 ? content.substring(0, 500) + "..." : content;
} else {
errorMessage = `Claude execution failed with ${message.subtype}`;
}
}
Logger.error(
{
event: "claude_execution_error_detailed",
subtype: message.subtype,
errorMessage,
fullMessage: JSON.stringify(messageObj, null, 2).substring(
0,
1e3
)
},
`Claude execution error: ${errorMessage}`
);
success = false;
}
break;
} else {
if (message.type === "assistant" && "message" in message) {
const assistantMessage = message.message;
if (assistantMessage.content) {
for (const content of assistantMessage.content) {
if (content.type === "tool_use") {
Logger.info(
{
event: "claude_tool_use",
toolName: content["name"],
hasInput: !!content["input"]
},
`Claude tool use: ${content["name"]}`
);
if (content["name"] === "Bash" && content["input"] && typeof content["input"] === "object") {
const bashInput = content["input"];
if (bashInput.command) {
Logger.info(
{
event: "claude_bash_command",
command: bashInput.command.substring(0, 200)
},
`Claude bash: ${bashInput.command}`
);
}
}
const toolMessage = formatToolMessage(
String(content["name"]),
content["input"],
handlers.showChorenzoOperations
);
if (toolMessage) {
handlers.onThinkingStateChange?.(false);
handlers.onProgress?.(toolMessage);
}
} else if (content.type === "text") {
Logger.debug(
{
event: "claude_text_response",
textLength: content.text ? content.text.length : 0
},
`Claude text: ${content.text ? content.text.substring(0, 200) : ""}...`
);
}
}
}
} else if (message.type === "user" && "message" in message) {
const userMessage = message.message;
if (userMessage.content) {
for (const content of userMessage.content) {
if (content.type === "tool_result") {
Logger.debug(
{
event: "claude_tool_result",
toolUseId: content.tool_use_id || "unknown",
isError: content.is_error || false,
contentLength: content["content"] ? String(content["content"]).length : 0
},
`Tool result: ${content.is_error ? "error" : "success"}`
);
}
}
}
handlers.onThinkingStateChange?.(true);
}
}
}
const endTime = /* @__PURE__ */ new Date();
const durationSeconds = (endTime.getTime() - startTime.getTime()) / 1e3;
let totalCost = 0;
let totalTurns = 0;
let subtype = "error";
if (sdkResultMetadata?.type === "result") {
if ("total_cost_usd" in sdkResultMetadata) {
totalCost = sdkResultMetadata.total_cost_usd;
}
if ("num_turns" in sdkResultMetadata) {
totalTurns = sdkResultMetadata.num_turns;
}
if ("subtype" in sdkResultMetadata) {
subtype = sdkResultMetadata.subtype;
}
}
const metadata = {
costUsd: totalCost,
turns: totalTurns,
durationSeconds,
subtype: success ? subtype : "error"
};
if (success && result !== null) {
handlers.onComplete?.(result, metadata);
return {
success: true,
result,
metadata
};
} else {
const finalErrorMessage = errorMessage || "Claude operation failed without specific error details";
Logger.error(
{
event: "claude_operation_failed",
hasErrorMessage: !!errorMessage,
errorMessage: finalErrorMessage,
totalCost,
totalTurns,
durationSeconds
},
`Claude operation failed: ${finalErrorMessage}`
);
const error = new Error(finalErrorMessage);
handlers.onError?.(error);
return {
success: false,
error: formatErrorMessage("Claude operation failed", error),
metadata
};
}
} catch (error) {
const endTime = /* @__PURE__ */ new Date();
const durationSeconds = (endTime.getTime() - startTime.getTime()) / 1e3;
const errorMessage2 = extractErrorMessage(error);
Logger.error(
{
event: "code_changes_operation_exception",
errorType: error instanceof Error ? error.constructor.name : typeof error,
errorMessage: errorMessage2,
stack: error instanceof Error ? error.stack : void 0,
durationSeconds
},
`Code changes operation threw exception: ${errorMessage2}`
);
const metadata = {
costUsd: 0,
turns: 0,
durationSeconds,
subtype: "error"
};
const finalError = error instanceof Error ? error : new Error(errorMessage2);
handlers.onError?.(finalError);
return {
success: false,
error: formatErrorMessage("Code changes operation failed", error),
metadata
};
}
}
function formatToolMessage(toolName, input, showChorenzoOperations) {
if (toolName === "TodoWrite" || toolName === "TodoRead") {
return null;
}
if (!input || typeof input !== "object") {
return `Using ${toolName} tool`;
}
switch (toolName) {
case "Read": {
const fileInput = input;
const readPath = getRelativePath(fileInput.file_path) || "file";
if (isChorenzoPath(readPath) && !showChorenzoOperations) {
return "Updating Chorenzo context";
}
return `Reading ${readPath}`;
}
case "Write": {
const fileInput = input;
const writePath = getRelativePath(fileInput.file_path) || "file";
if (isChorenzoPath(writePath) && !showChorenzoOperations) {
return "Updating Chorenzo context";
}
return `Writing ${writePath}`;
}
case "Edit":
case "MultiEdit": {
const fileInput = input;
const editPath = getRelativePath(fileInput.file_path) || "file";
if (isChorenzoPath(editPath) && !showChorenzoOperations) {
return "Updating Chorenzo context";
}
return `Editing ${editPath}`;
}
case "Bash": {
const bashInput = input;
const command = bashInput.command || bashInput.cmd || "";
if (command.includes("mkdir") && command.includes(".chorenzo")) {
return "Initializing the chorenzo engine";
}
if (command.includes("mkdir")) {
const pathMatch = command.match(/mkdir\s+(-p\s+)?["']?([^"'\s]+)["']?/);
if (pathMatch?.[2]) {
const relativePath = getRelativePath(pathMatch[2]);
return `Creating directory: ${relativePath}`;
}
}
if (command.includes("rm ") || command.includes("rmdir")) {
const pathMatch = command.match(
/(?:rm|rmdir)\s+(-[rf]+\s+)?["']?([^"'\s]+)["']?/
);
if (pathMatch?.[2]) {
const relativePath = getRelativePath(pathMatch[2]);
return `Removing: ${relativePath}`;
}
}
return `Running: ${command}`;
}
case "LS": {
const relativePath = getRelativePath(input.path);
let pathDisplay;
if (relativePath === "") {
pathDisplay = "root directory";
} else if (relativePath) {
pathDisplay = relativePath;
} else {
pathDisplay = input.path || "directory";
}
return `Listing ${pathDisplay}`;
}
case "Glob":
return `Finding files: ${input.pattern || "pattern"}`;
case "Grep":
return `Searching for: ${input.pattern || "pattern"}`;
case "Task":
return `Running task: ${input.description || "background task"}`;
default:
return `Using ${toolName} tool`;
}
}
function getRelativePath(filePath) {
if (!filePath) {
return filePath;
}
const workspaceRoot = workspaceConfig.getWorkspaceRoot();
if (filePath.startsWith(workspaceRoot)) {
const relativePath = filePath.substring(workspaceRoot.length);
return relativePath.startsWith("/") ? relativePath.substring(1) : relativePath;
}
const homeDir = os.homedir();
if (filePath.startsWith(homeDir)) {
const relativePath = filePath.substring(homeDir.length);
return `~${relativePath}`;
}
return filePath;
}
function isChorenzoPath(filePath) {
if (!filePath) {
return false;
}
return filePath.includes(".chorenzo");
}
// src/utils/file-tree.utils.ts
import fs7 from "fs/promises";
import path6 from "path";
// src/utils/gitignore.utils.ts
import * as fs6 from "fs";
import * as path5 from "path";
var GitignoreManager = class {
static CHORENZO_SECTION_START = "# Chorenzo";
static CHORENZO_PATTERNS = [
"/.chorenzo/*",
"!/.chorenzo/state.json",
"!/.chorenzo/analysis.json"
];
static ensureChorenzoIgnorePatterns(projectRoot) {
const gitignorePath = path5.join(projectRoot, ".gitignore");
try {
if (!fs6.existsSync(gitignorePath)) {
this.createGitignoreWithChorenzoPatterns(gitignorePath);
return;
}
const content = fs6.readFileSync(gitignorePath, "utf-8");
const updatedContent = this.updateChorenzoPatterns(content);
if (updatedContent !== content) {
fs6.writeFileSync(gitignorePath, updatedContent, "utf-8");
Logger.info(
{
event: "gitignore_updated",
path: gitignorePath
},
"Updated .gitignore with Chorenzo state file tracking"
);
}
} catch (error) {
Logger.warn(
{
event: "gitignore_update_failed",
path: gitignorePath,
error: formatErrorMessage("gitignore update", error)
},
"Failed to update .gitignore for Chorenzo state files"
);
}
}
static createGitignoreWithChorenzoPatterns(gitignorePath) {
const content = this.generateChorenzoSection();
fs6.writeFileSync(gitignorePath, content, "utf-8");
Logger.info(
{
event: "gitignore_created",
path: gitignorePath
},
"Created .gitignore with Chorenzo state file tracking"
);
}
static updateChorenzoPatterns(content) {
const hasChorenzoSection = content.includes(this.CHORENZO_SECTION_START);
if (hasChorenzoSection) {
return this.replaceChorenzoSection(content);
}
return this.appendChorenzoSection(content);
}
static replaceChorenzoSection(content) {
const startIndex = content.indexOf(this.CHORENZO_SECTION_START);
if (startIndex === -1) {
return this.appendChorenzoSection(content);
}
const lines = content.split("\n");
const startLineIndex = lines.findIndex(
(line) => line.includes(this.CHORENZO_SECTION_START)
);
if (startLineIndex === -1) {
return this.appendChorenzoSection(content);
}
let endLineIndex = startLineIndex + 1;
while (endLineIndex < lines.length) {
const line = lines[endLineIndex]?.trim();
if (!line || !line.startsWith("/.chorenzo") && !line.startsWith("!/.chorenzo")) {
break;
}
endLineIndex++;
}
const before = lines.slice(0, startLineIndex).join("\n");
const after = lines.slice(endLineIndex).join("\n");
const newSection = this.generateChorenzoSection();
const beforeTrimmed = before.trim();
const afterTrimmed = after.trim();
const separator = beforeTrimmed && afterTrimmed ? "\n\n" : beforeTrimmed ? "\n" : "";
return beforeTrimmed + (beforeTrimmed ? "\n\n" : "") + newSection + separator + afterTrimmed;
}
static appendChorenzoSection(content) {
const trimmedContent = content.trim();
const separator = trimmedContent ? "\n\n" : "";
return trimmedContent + separator + this.generateChorenzoSection();
}
static generateChorenzoSection() {
const patterns = this.CHORENZO_PATTERNS.join("\n");
return `${this.CHORENZO_SECTION_START}
${patterns}
`;
}
static loadGitIgnorePatternsForDir(directory, parentPatterns = /* @__PURE__ */ new Set()) {
const patterns = new Set(parentPatterns);
const gitignorePath = path5.join(directory, ".gitignore");
if (fs6.existsSync(gitignorePath)) {
try {
const content = fs6.readFileSync(gitignorePath, "utf-8");
const lines = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
for (const line of lines) {
patterns.add(line);
}
} catch (error) {
Logger.warn(
{
event: "gitignore_read_failed",
path: gitignorePath,
error: formatErrorMessage("gitignore read", error)
},
"Failed to read .gitignore file"
);
}
}
return patterns;
}
static isIgnored(filePath, rootDir, ignorePatterns) {
const relPath = path5.relative(rootDir, filePath);
const parts = relPath.split(path5.sep);
for (const pattern of ignorePatterns) {
if (this.matchGitIgnorePattern(relPath, pattern) || relPath.startsWith(pattern) || parts.includes(pattern)) {
return true;
}
}
return false;
}
static matchGitIgnorePattern(filePath, pattern) {
const regexPattern = pattern.replace(/\./g, "\\.").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\*\*/g, ".*");
const regex = new RegExp(`^${regexPattern}$`);
return regex.test(filePath) || regex.test(path5.basename(filePath));
}
};
// src/utils/file-tree.utils.ts
var lockFiles = /* @__PURE__ */ new Set([
"package-lock.json",
"uv.lock",
"poetry.lock",
"Pipfile.lock",
"yarn.lock",
"pnpm-lock.yaml",
"shrinkwrap.yaml"
]);
async function buildFileTree(rootDir, subdir, numLevels) {
const seen = /* @__PURE__ */ new Set();
async function buildTree(currentPath, parentPatterns, level = 0) {
if (numLevels !== void 0 && level >= numLevels) {
return null;
}
const absPath = path6.resolve(currentPath);
if (seen.has(absPath)) {
return null;
}
seen.add(absPath);
const patterns = GitignoreManager.loadGitIgnorePatternsForDir(
currentPath,
parentPatterns
);
if (GitignoreManager.isIgnored(currentPath, rootDir, patterns)) {
return null;
}
const stats = await fs7.stat(absPath);
if (stats.isDirectory()) {
const baseName = path6.basename(absPath);
if (baseName === ".git") {
return null;
}
const children = {};
const files = [];
const entries = await fs7.readdir(absPath);
const sortedEntries = entries.sort(
(a, b) => a.toLowerCase().localeCompare(b.toLowerCase())
);
for (const entry of sortedEntries) {
if (lockFiles.has(entry)) {
continue;
}
const childPath = path6.join(absPath, entry);
const childStats = await fs7.stat(childPath);
if (childStats.isDirectory()) {
const subtree = await buildTree(childPath, patterns, level + 1);
if (subtree !== null) {
children[entry] = subtree;
}
} else if (childStats.isFile()) {
if (!GitignoreManager.isIgnored(childPath, rootDir, patterns)) {
files.push(entry);
}
}
}
if (Object.keys(children).length > 0 && files.length > 0) {
return { ...children, __files__: files };
} else if (Object.keys(children).length > 0) {
return children;
} else if (files.length > 0) {
return files;
} else {
return null;
}
}
return null;
}
const tree = await buildTree(rootDir, /* @__PURE__ */ new Set(), 0);
function toYamlStyle(name, node, indent = 0) {
const pad = " ".repeat(indent);
if (node === null) {
return [`${pad}${name}: (empty)`];
}
if (Array.isArray(node)) {
const lines2 = [`${pad}${name}:`];
for (const file of node) {
lines2.push(`${pad} - ${file}`);
}
return lines2;
}
const lines = [`${pad}${name}:`];
for (const [key, value] of Object.entries(node)) {
if (key === "__files__" && Array.isArray(value)) {
for (const file of value) {
lin