@bobmatnyc/ai-trackdown-tools
Version:
Professional CLI tool for ai-trackdown functionality with comprehensive PR management system
1,478 lines (1,432 loc) • 1.04 MB
JavaScript
#!/usr/bin/env node
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
}) : x)(function(x) {
if (typeof require !== "undefined") return require.apply(this, arguments);
throw Error('Dynamic require of "' + x + '" is not supported');
});
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
// node_modules/tsup/assets/esm_shims.js
import path from "path";
import { fileURLToPath } from "url";
var init_esm_shims = __esm({
"node_modules/tsup/assets/esm_shims.js"() {
"use strict";
}
});
// src/utils/unified-path-resolver.ts
import { existsSync } from "fs";
import { join } from "path";
var _UnifiedPathResolver, UnifiedPathResolver;
var init_unified_path_resolver = __esm({
"src/utils/unified-path-resolver.ts"() {
"use strict";
init_esm_shims();
_UnifiedPathResolver = class _UnifiedPathResolver {
config;
projectRoot;
cliTasksDir;
// CLI override via --tasks-dir or --root-dir
constructor(config, projectRoot, cliTasksDir) {
this.config = config;
this.projectRoot = projectRoot;
this.cliTasksDir = cliTasksDir;
}
/**
* Get the tasks root directory with proper priority resolution:
* 1. CLI option override (--root-dir, --tasks-dir)
* 2. Environment variable (AITRACKDOWN_TASKS_DIR)
* 3. Config file setting (tasks_directory)
* 4. Default to "tasks"
*/
getTasksRootDirectory() {
if (this.cliTasksDir) {
return this.cliTasksDir;
}
const envTasksDir = process.env.AITRACKDOWN_TASKS_DIR || process.env.AITRACKDOWN_ROOT_DIR;
if (envTasksDir) {
return envTasksDir;
}
if (this.config.tasks_directory) {
return this.config.tasks_directory;
}
return "tasks";
}
/**
* Get all unified paths following the required structure
*/
getUnifiedPaths() {
const tasksRoot = this.getTasksRootDirectory();
return {
projectRoot: this.projectRoot,
configDir: join(this.projectRoot, ".ai-trackdown"),
tasksRoot: join(this.projectRoot, tasksRoot),
epicsDir: join(this.projectRoot, tasksRoot, this.config.structure.epics_dir),
issuesDir: join(this.projectRoot, tasksRoot, this.config.structure.issues_dir),
tasksDir: join(this.projectRoot, tasksRoot, this.config.structure.tasks_dir),
prsDir: join(this.projectRoot, tasksRoot, this.config.structure.prs_dir || "prs"),
templatesDir: join(this.projectRoot, tasksRoot, this.config.structure.templates_dir)
};
}
/**
* Get path for specific item type
*/
getItemTypeDirectory(type) {
const paths = this.getUnifiedPaths();
switch (type) {
case "project":
return join(paths.tasksRoot, "projects");
case "epic":
return paths.epicsDir;
case "issue":
return paths.issuesDir;
case "task":
return paths.tasksDir;
case "pr":
return paths.prsDir;
default:
throw new Error(`Unknown item type: ${type}`);
}
}
/**
* Get all directories that should be created for the unified structure
*/
getRequiredDirectories() {
const paths = this.getUnifiedPaths();
return [
paths.configDir,
paths.tasksRoot,
paths.epicsDir,
paths.issuesDir,
paths.tasksDir,
paths.prsDir,
paths.templatesDir
];
}
/**
* Check if legacy directory structure exists (separate root directories)
*/
detectLegacyStructure() {
const legacyDirs = [];
const suggestions = [];
const potentialLegacyDirs = [
join(this.projectRoot, "epics"),
join(this.projectRoot, "issues"),
join(this.projectRoot, "tasks"),
join(this.projectRoot, "prs"),
join(this.projectRoot, "trackdown")
// Old trackdown structure
];
for (const dir of potentialLegacyDirs) {
if (existsSync(dir)) {
legacyDirs.push(dir);
}
}
if (legacyDirs.length > 0) {
const tasksRoot = this.getTasksRootDirectory();
suggestions.push(
`# Detected legacy directory structure. Migration options:`,
``,
`# Option 1: Use CLI override to maintain current structure`,
`export AITRACKDOWN_TASKS_DIR="" # Use project root`,
``,
`# Option 2: Migrate to unified structure`,
`mkdir -p ${tasksRoot}`,
...legacyDirs.map((dir) => {
const dirName = dir.split("/").pop();
return `mv ${dirName} ${tasksRoot}/${dirName} 2>/dev/null || true`;
}),
``,
`# Option 3: Update configuration`,
`# Edit .ai-trackdown/config.yaml and set:`,
`# tasks_directory: "" # Use project root`
);
}
return {
hasLegacy: legacyDirs.length > 0,
legacyDirs,
suggestions
};
}
/**
* Get migration commands for moving to unified structure
*/
getMigrationCommands() {
const legacy = this.detectLegacyStructure();
if (!legacy.hasLegacy) {
return [];
}
return legacy.suggestions;
}
/**
* Validate current directory structure
*/
validateStructure() {
const _paths = this.getUnifiedPaths();
const issues = [];
const missingDirs = [];
const requiredDirs = this.getRequiredDirectories();
for (const dir of requiredDirs) {
if (!existsSync(dir)) {
missingDirs.push(dir);
}
}
const legacy = this.detectLegacyStructure();
if (legacy.hasLegacy) {
issues.push(`Legacy directory structure detected: ${legacy.legacyDirs.join(", ")}`);
}
return {
valid: issues.length === 0 && missingDirs.length === 0,
issues,
missingDirs
};
}
/**
* Update CLI tasks directory override
*/
setCliTasksDir(tasksDir) {
this.cliTasksDir = tasksDir;
}
/**
* Clear CLI tasks directory override
*/
clearCliTasksDir() {
this.cliTasksDir = void 0;
}
/**
* Show structure information for debugging
*/
showStructureInfo() {
const paths = this.getUnifiedPaths();
const validation = this.validateStructure();
console.log(`
\u{1F3D7}\uFE0F AI-Trackdown Directory Structure`);
console.log(`\u{1F4C1} Tasks Root: ${paths.tasksRoot}`);
console.log(` \u251C\u2500\u2500 \u{1F4C2} epics/ \u2192 ${paths.epicsDir}`);
console.log(` \u251C\u2500\u2500 \u{1F4C2} issues/ \u2192 ${paths.issuesDir}`);
console.log(` \u251C\u2500\u2500 \u{1F4C2} tasks/ \u2192 ${paths.tasksDir}`);
console.log(` \u251C\u2500\u2500 \u{1F4C2} prs/ \u2192 ${paths.prsDir}`);
console.log(` \u2514\u2500\u2500 \u{1F4C2} templates/ \u2192 ${paths.templatesDir}`);
if (validation.missingDirs.length > 0) {
console.log(`
\u26A0\uFE0F Missing directories:`);
validation.missingDirs.forEach((dir) => console.log(` \u2022 ${dir}`));
}
if (validation.issues.length > 0) {
console.log(`
\u{1F6A8} Issues detected:`);
validation.issues.forEach((issue) => console.log(` \u2022 ${issue}`));
}
const legacy = this.detectLegacyStructure();
if (legacy.hasLegacy) {
console.log(`
\u{1F4CB} Migration suggestions:`);
legacy.suggestions.forEach((suggestion) => console.log(` ${suggestion}`));
}
}
};
__name(_UnifiedPathResolver, "UnifiedPathResolver");
UnifiedPathResolver = _UnifiedPathResolver;
}
});
// src/utils/template-manager.ts
var template_manager_exports = {};
__export(template_manager_exports, {
TemplateManager: () => TemplateManager
});
import * as fs from "fs";
import * as path2 from "path";
import { fileURLToPath as fileURLToPath2 } from "url";
import * as YAML from "yaml";
var __filename2, __dirname2, _TemplateManager, TemplateManager;
var init_template_manager = __esm({
"src/utils/template-manager.ts"() {
"use strict";
init_esm_shims();
__filename2 = fileURLToPath2(import.meta.url);
__dirname2 = path2.dirname(__filename2);
_TemplateManager = class _TemplateManager {
bundledTemplatesDir;
constructor() {
const possiblePaths = [
path2.join(__dirname2, "../../templates"),
// Development: src/utils -> templates
path2.join(__dirname2, "../templates"),
// Compiled: dist/utils -> dist/templates
path2.join(__dirname2, "templates"),
// Compiled: dist -> dist/templates
path2.resolve(__dirname2, "..", "templates")
// Alternative dist structure
];
this.bundledTemplatesDir = possiblePaths.find((dir) => {
try {
return fs.existsSync(dir);
} catch {
return false;
}
}) || path2.join(__dirname2, "../../templates");
}
/**
* Get the path to bundled templates
*/
getBundledTemplatesDir() {
return this.bundledTemplatesDir;
}
/**
* Check if bundled templates exist
*/
hasBundledTemplates() {
return fs.existsSync(this.bundledTemplatesDir);
}
/**
* List all bundled template files
*/
listBundledTemplates() {
if (!this.hasBundledTemplates()) {
return [];
}
try {
return fs.readdirSync(this.bundledTemplatesDir).filter((file) => file.endsWith(".yaml")).sort();
} catch (error) {
console.warn(
`Failed to list bundled templates: ${error instanceof Error ? error.message : "Unknown error"}`
);
return [];
}
}
/**
* Deploy bundled templates to project's templates directory
*/
deployTemplates(projectTemplatesDir, force = false) {
if (!this.hasBundledTemplates()) {
console.warn("No bundled templates found. Creating default templates programmatically.");
this.createDefaultTemplates(projectTemplatesDir, force);
return;
}
if (!fs.existsSync(projectTemplatesDir)) {
fs.mkdirSync(projectTemplatesDir, { recursive: true });
}
const bundledFiles = this.listBundledTemplates();
let deployedCount = 0;
for (const templateFile of bundledFiles) {
const sourcePath = path2.join(this.bundledTemplatesDir, templateFile);
const destPath = path2.join(projectTemplatesDir, templateFile);
try {
if (fs.existsSync(destPath) && !force) {
console.log(`\u23ED\uFE0F Skipping ${templateFile} (already exists)`);
continue;
}
fs.copyFileSync(sourcePath, destPath);
console.log(`\u2705 Deployed ${templateFile}`);
deployedCount++;
} catch (error) {
console.error(
`\u274C Failed to deploy ${templateFile}: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
console.log(`\u{1F4E6} Deployed ${deployedCount} template(s) to ${projectTemplatesDir}`);
}
/**
* Create default templates programmatically if bundled templates are not available
*/
createDefaultTemplates(projectTemplatesDir, force = false) {
if (!fs.existsSync(projectTemplatesDir)) {
fs.mkdirSync(projectTemplatesDir, { recursive: true });
}
const defaultTemplates = [
{
type: "epic",
name: "default",
description: "Default epic template",
frontmatter_template: {
title: "Epic Title",
description: "Epic description",
status: "planning",
priority: "medium",
assignee: "unassigned",
created_date: "",
updated_date: "",
estimated_tokens: 0,
actual_tokens: 0,
ai_context: [
"context/requirements",
"context/constraints",
"context/assumptions",
"context/dependencies"
],
sync_status: "local"
},
content_template: `# Epic: {{title}}
## Overview
{{description}}
## Objectives
- [ ] Objective 1
- [ ] Objective 2
- [ ] Objective 3
## Acceptance Criteria
- [ ] Criteria 1
- [ ] Criteria 2
## Related Issues
{{#related_issues}}
- {{.}}
{{/related_issues}}
## Notes
Add any additional notes here.`
},
{
type: "issue",
name: "default",
description: "Default issue template",
frontmatter_template: {
title: "Issue Title",
description: "Issue description",
status: "planning",
priority: "medium",
assignee: "unassigned",
created_date: "",
updated_date: "",
estimated_tokens: 0,
actual_tokens: 0,
ai_context: [
"context/requirements",
"context/constraints",
"context/assumptions",
"context/dependencies"
],
sync_status: "local"
},
content_template: `# Issue: {{title}}
## Description
{{description}}
## Tasks
{{#related_tasks}}
- [ ] {{.}}
{{/related_tasks}}
## Acceptance Criteria
- [ ] Criteria 1
- [ ] Criteria 2
## Notes
Add any additional notes here.`
},
{
type: "task",
name: "default",
description: "Default task template",
frontmatter_template: {
title: "Task Title",
description: "Task description",
status: "planning",
priority: "medium",
assignee: "unassigned",
created_date: "",
updated_date: "",
estimated_tokens: 0,
actual_tokens: 0,
ai_context: [
"context/requirements",
"context/constraints",
"context/assumptions",
"context/dependencies"
],
sync_status: "local"
},
content_template: `# Task: {{title}}
## Description
{{description}}
## Steps
1. Step 1
2. Step 2
3. Step 3
## Acceptance Criteria
- [ ] Criteria 1
- [ ] Criteria 2
## Notes
Add any additional notes here.`
},
{
type: "pr",
name: "default",
description: "Default PR template",
frontmatter_template: {
title: "PR Title",
description: "PR description",
status: "planning",
priority: "medium",
assignee: "unassigned",
created_date: "",
updated_date: "",
estimated_tokens: 0,
actual_tokens: 0,
ai_context: [
"context/requirements",
"context/constraints",
"context/assumptions",
"context/dependencies"
],
sync_status: "local"
},
content_template: `# PR: {{title}}
## Description
{{description}}
## Changes
- Change 1
- Change 2
- Change 3
## Testing
- [ ] Unit tests pass
- [ ] Integration tests pass
- [ ] Manual testing completed
## Checklist
- [ ] Code follows style guidelines
- [ ] Self-review completed
- [ ] Documentation updated
- [ ] Tests added/updated
## Related
- Issue: {{issue_id}}
- Branch: {{branch_name}}
- Target: {{target_branch}}
## Notes
Add any additional notes here.`
}
];
let deployedCount = 0;
for (const template of defaultTemplates) {
const templatePath = path2.join(projectTemplatesDir, `${template.type}-${template.name}.yaml`);
try {
if (fs.existsSync(templatePath) && !force) {
console.log(`\u23ED\uFE0F Skipping ${template.type}-${template.name}.yaml (already exists)`);
continue;
}
const templateContent = YAML.stringify(template, {
indent: 2,
lineWidth: 120
});
fs.writeFileSync(templatePath, templateContent, "utf8");
console.log(`\u2705 Created ${template.type}-${template.name}.yaml`);
deployedCount++;
} catch (error) {
console.error(
`\u274C Failed to create ${template.type}-${template.name}.yaml: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
console.log(`\u{1F4E6} Created ${deployedCount} default template(s) in ${projectTemplatesDir}`);
}
/**
* Get template by type and name, with fallback to bundled templates
*/
getTemplate(projectTemplatesDir, type, name = "default") {
const templateFileName = `${type}-${name}.yaml`;
const projectTemplatePath = path2.join(projectTemplatesDir, templateFileName);
if (fs.existsSync(projectTemplatePath)) {
try {
const templateContent = fs.readFileSync(projectTemplatePath, "utf8");
return YAML.parse(templateContent);
} catch (error) {
console.warn(
`Failed to load project template ${projectTemplatePath}: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
if (this.hasBundledTemplates()) {
const bundledTemplatePath = path2.join(this.bundledTemplatesDir, templateFileName);
if (fs.existsSync(bundledTemplatePath)) {
try {
const templateContent = fs.readFileSync(bundledTemplatePath, "utf8");
return YAML.parse(templateContent);
} catch (error) {
console.warn(
`Failed to load bundled template ${bundledTemplatePath}: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
}
return null;
}
/**
* Validate template file structure
*/
validateTemplate(templatePath) {
try {
const content = fs.readFileSync(templatePath, "utf8");
const template = YAML.parse(content);
const requiredFields = [
"type",
"name",
"description",
"frontmatter_template",
"content_template"
];
for (const field of requiredFields) {
if (!template[field]) {
console.error(`Template ${templatePath} missing required field: ${field}`);
return false;
}
}
const validTypes = ["epic", "issue", "task", "pr"];
if (!validTypes.includes(template.type)) {
console.error(`Template ${templatePath} has invalid type: ${template.type}`);
return false;
}
return true;
} catch (error) {
console.error(
`Failed to validate template ${templatePath}: ${error instanceof Error ? error.message : "Unknown error"}`
);
return false;
}
}
};
__name(_TemplateManager, "TemplateManager");
TemplateManager = _TemplateManager;
}
});
// src/utils/config-manager.ts
var config_manager_exports = {};
__export(config_manager_exports, {
ConfigManager: () => ConfigManager
});
import * as fs2 from "fs";
import * as path3 from "path";
import * as YAML2 from "yaml";
var DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILE, _ConfigManager, ConfigManager;
var init_config_manager = __esm({
"src/utils/config-manager.ts"() {
"use strict";
init_esm_shims();
init_unified_path_resolver();
DEFAULT_CONFIG_DIR = ".ai-trackdown";
DEFAULT_CONFIG_FILE = "config.yaml";
_ConfigManager = class _ConfigManager {
configPath;
config = null;
constructor(projectRoot) {
const root = projectRoot || this.findProjectRoot();
this.configPath = path3.join(root, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILE);
}
/**
* Load configuration from file
*/
loadConfig() {
var _a;
if (this.config) {
return this.config;
}
if (!fs2.existsSync(this.configPath)) {
throw new Error(
`AI-Trackdown configuration not found at ${this.configPath}. Run 'aitrackdown init' to create a new project.`
);
}
try {
const configContent = fs2.readFileSync(this.configPath, "utf8");
const rawConfig = YAML2.parse(configContent);
if (((_a = rawConfig.project) == null ? void 0 : _a.name) && !rawConfig.name) {
this.config = {
name: rawConfig.project.name,
description: rawConfig.project.description,
version: rawConfig.version || "1.0.0",
tasks_directory: rawConfig.tasks_directory || "tasks",
structure: rawConfig.structure || {
epics_dir: "epics",
issues_dir: "issues",
tasks_dir: "tasks",
templates_dir: "templates",
prs_dir: "prs"
},
naming_conventions: rawConfig.naming_conventions || {
epic_prefix: "EP",
issue_prefix: "ISS",
task_prefix: "TSK",
pr_prefix: "PR",
file_extension: ".md"
},
default_assignee: rawConfig.default_assignee || "unassigned",
ai_context_templates: rawConfig.ai_context_templates || [],
automation: rawConfig.automation || {
auto_update_timestamps: true,
auto_calculate_tokens: false,
auto_sync_status: true
}
};
} else {
this.config = rawConfig;
}
this.validateConfig(this.config);
this.normalizeConfig(this.config);
return this.config;
} catch (error) {
throw new Error(
`Failed to load AI-Trackdown configuration: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
/**
* Save configuration to file
*/
saveConfig(config) {
this.validateConfig(config);
const configDir = path3.dirname(this.configPath);
if (!fs2.existsSync(configDir)) {
fs2.mkdirSync(configDir, { recursive: true });
}
const yamlContent = YAML2.stringify(config, {
indent: 2,
lineWidth: 120,
minContentWidth: 20
});
fs2.writeFileSync(this.configPath, yamlContent, "utf8");
this.config = config;
}
/**
* Create default configuration
*/
createDefaultConfig(projectName, options = {}) {
const defaultConfig = {
name: projectName,
description: options.description || `AI-Trackdown project: ${projectName}`,
version: "1.0.0",
// NEW: Default tasks directory for unified structure
tasks_directory: options.tasks_directory || "tasks",
structure: {
epics_dir: "epics",
issues_dir: "issues",
tasks_dir: "tasks",
templates_dir: "templates",
prs_dir: "prs"
// NEW: PR directory
},
naming_conventions: {
epic_prefix: "EP",
issue_prefix: "ISS",
task_prefix: "TSK",
pr_prefix: "PR",
// NEW: PR prefix
file_extension: ".md"
},
default_assignee: options.default_assignee || "unassigned",
ai_context_templates: [
"context/requirements",
"context/constraints",
"context/assumptions",
"context/dependencies"
],
automation: {
auto_update_timestamps: true,
auto_calculate_tokens: false,
auto_sync_status: true
},
...options
};
return defaultConfig;
}
/**
* Initialize new project with default structure
*/
initializeProject(projectName, options = {}) {
const config = this.createDefaultConfig(projectName, options);
this.createProjectStructure(config);
this.createDefaultTemplates(config);
this.saveConfig(config);
return config;
}
/**
* Initialize new project with structure only (no template creation)
*/
initializeProjectStructure(projectName, options = {}) {
const config = this.createDefaultConfig(projectName, options);
this.createProjectStructure(config);
this.saveConfig(config);
return config;
}
/**
* Update specific configuration values
*/
updateConfig(updates) {
const currentConfig = this.loadConfig();
const updatedConfig = this.deepMerge(currentConfig, updates);
this.saveConfig(updatedConfig);
return updatedConfig;
}
/**
* Get configuration with environment overrides
*/
getConfig() {
const config = this.loadConfig();
if (process.env.ATD_DEFAULT_ASSIGNEE) {
config.default_assignee = process.env.ATD_DEFAULT_ASSIGNEE;
}
if (process.env.ATD_AUTO_TIMESTAMPS === "false") {
config.automation.auto_update_timestamps = false;
}
if (process.env.ATD_AUTO_CALCULATE_TOKENS === "true") {
config.automation.auto_calculate_tokens = true;
}
return config;
}
/**
* Get absolute paths for project structure using unified directory layout
*/
getAbsolutePaths(cliTasksDir) {
const config = this.getConfig();
const projectRoot = path3.dirname(path3.dirname(this.configPath));
const pathResolver = new UnifiedPathResolver(config, projectRoot, cliTasksDir);
const unifiedPaths = pathResolver.getUnifiedPaths();
return {
projectRoot: unifiedPaths.projectRoot,
configDir: unifiedPaths.configDir,
tasksRoot: unifiedPaths.tasksRoot,
epicsDir: unifiedPaths.epicsDir,
issuesDir: unifiedPaths.issuesDir,
tasksDir: unifiedPaths.tasksDir,
prsDir: unifiedPaths.prsDir,
templatesDir: unifiedPaths.templatesDir
};
}
/**
* Check if current directory is an AI-Trackdown project
*/
isProjectDirectory(dir) {
const checkDir = dir || process.cwd();
const configPath = path3.join(checkDir, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILE);
return fs2.existsSync(configPath);
}
/**
* Find project root by walking up directory tree
*/
findProjectRoot(startDir) {
let currentDir = startDir || process.cwd();
while (currentDir !== path3.dirname(currentDir)) {
const configPath = path3.join(currentDir, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILE);
if (fs2.existsSync(configPath)) {
return currentDir;
}
currentDir = path3.dirname(currentDir);
}
return startDir || process.cwd();
}
/**
* Validate configuration structure
*/
validateConfig(config) {
const required = ["name", "version", "structure", "naming_conventions"];
for (const field of required) {
if (!config[field]) {
throw new Error(`Configuration missing required field: ${field}`);
}
}
const structureFields = ["epics_dir", "issues_dir", "tasks_dir", "templates_dir"];
for (const field of structureFields) {
if (!config.structure[field]) {
throw new Error(`Configuration structure missing required field: ${field}`);
}
}
const namingFields = ["epic_prefix", "issue_prefix", "task_prefix", "file_extension"];
for (const field of namingFields) {
if (!config.naming_conventions[field]) {
throw new Error(`Configuration naming_conventions missing required field: ${field}`);
}
}
}
/**
* Normalize configuration (ensure defaults and proper types)
*/
normalizeConfig(config) {
if (!config.automation) {
config.automation = {
auto_update_timestamps: true,
auto_calculate_tokens: false,
auto_sync_status: true
};
}
if (!config.ai_context_templates) {
config.ai_context_templates = [];
}
config.structure.epics_dir = config.structure.epics_dir.replace(/^\/|\/$/g, "");
config.structure.issues_dir = config.structure.issues_dir.replace(/^\/|\/$/g, "");
config.structure.tasks_dir = config.structure.tasks_dir.replace(/^\/|\/$/g, "");
config.structure.templates_dir = config.structure.templates_dir.replace(/^\/|\/$/g, "");
if (!config.naming_conventions.file_extension.startsWith(".")) {
config.naming_conventions.file_extension = `.${config.naming_conventions.file_extension}`;
}
}
/**
* Create project directory structure using unified layout
*/
createProjectStructure(config) {
const projectRoot = path3.dirname(path3.dirname(this.configPath));
const pathResolver = new UnifiedPathResolver(config, projectRoot);
const requiredDirs = pathResolver.getRequiredDirectories();
for (const dir of requiredDirs) {
if (!fs2.existsSync(dir)) {
fs2.mkdirSync(dir, { recursive: true });
}
}
}
/**
* Create default templates
*/
createDefaultTemplates(config) {
const projectRoot = path3.dirname(path3.dirname(this.configPath));
const pathResolver = new UnifiedPathResolver(config, projectRoot);
const paths = pathResolver.getUnifiedPaths();
const templatesDir = paths.templatesDir;
const templates = [
{
type: "epic",
name: "default",
description: "Default epic template",
frontmatter_template: {
title: "Epic Title",
description: "Epic description",
status: "planning",
priority: "medium",
assignee: config.default_assignee || "unassigned",
created_date: "",
updated_date: "",
estimated_tokens: 0,
actual_tokens: 0,
ai_context: config.ai_context_templates || [],
sync_status: "local"
},
content_template: `# Epic: {{title}}
## Overview
{{description}}
## Objectives
- [ ] Objective 1
- [ ] Objective 2
- [ ] Objective 3
## Acceptance Criteria
- [ ] Criteria 1
- [ ] Criteria 2
## Related Issues
{{#related_issues}}
- {{.}}
{{/related_issues}}
## Notes
Add any additional notes here.`
},
{
type: "issue",
name: "default",
description: "Default issue template",
frontmatter_template: {
title: "Issue Title",
description: "Issue description",
status: "planning",
priority: "medium",
assignee: config.default_assignee || "unassigned",
created_date: "",
updated_date: "",
estimated_tokens: 0,
actual_tokens: 0,
ai_context: config.ai_context_templates || [],
sync_status: "local"
},
content_template: `# Issue: {{title}}
## Description
{{description}}
## Tasks
{{#related_tasks}}
- [ ] {{.}}
{{/related_tasks}}
## Acceptance Criteria
- [ ] Criteria 1
- [ ] Criteria 2
## Notes
Add any additional notes here.`
},
{
type: "task",
name: "default",
description: "Default task template",
frontmatter_template: {
title: "Task Title",
description: "Task description",
status: "planning",
priority: "medium",
assignee: config.default_assignee || "unassigned",
created_date: "",
updated_date: "",
estimated_tokens: 0,
actual_tokens: 0,
ai_context: config.ai_context_templates || [],
sync_status: "local"
},
content_template: `# Task: {{title}}
## Description
{{description}}
## Steps
1. Step 1
2. Step 2
3. Step 3
## Acceptance Criteria
- [ ] Criteria 1
- [ ] Criteria 2
## Notes
Add any additional notes here.`
},
{
type: "pr",
name: "default",
description: "Default PR template",
frontmatter_template: {
title: "PR Title",
description: "PR description",
status: "planning",
priority: "medium",
assignee: config.default_assignee || "unassigned",
created_date: "",
updated_date: "",
estimated_tokens: 0,
actual_tokens: 0,
ai_context: config.ai_context_templates || [],
sync_status: "local"
},
content_template: `# PR: {{title}}
## Description
{{description}}
## Changes
- Change 1
- Change 2
- Change 3
## Testing
- [ ] Unit tests pass
- [ ] Integration tests pass
- [ ] Manual testing completed
## Checklist
- [ ] Code follows style guidelines
- [ ] Self-review completed
- [ ] Documentation updated
- [ ] Tests added/updated
## Related
- Issue: {{issue_id}}
- Branch: {{branch_name}}
- Target: {{target_branch}}
## Notes
Add any additional notes here.`
}
];
for (const template of templates) {
const templatePath = path3.join(templatesDir, `${template.type}-${template.name}.yaml`);
if (!fs2.existsSync(templatePath)) {
const templateContent = YAML2.stringify(template, {
indent: 2,
lineWidth: 120
});
fs2.writeFileSync(templatePath, templateContent, "utf8");
}
}
}
/**
* Deep merge two objects
*/
deepMerge(target, source) {
const result = { ...target };
for (const key in source) {
if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) {
result[key] = this.deepMerge(result[key] || {}, source[key]);
} else {
result[key] = source[key];
}
}
return result;
}
/**
* Get template by type and name
*/
getTemplate(type, name = "default") {
const config = this.getConfig();
const projectRoot = path3.dirname(path3.dirname(this.configPath));
const pathResolver = new UnifiedPathResolver(config, projectRoot);
const paths = pathResolver.getUnifiedPaths();
const templatesDir = paths.templatesDir;
const templatePath = path3.join(templatesDir, `${type}-${name}.yaml`);
if (!fs2.existsSync(templatePath)) {
return null;
}
try {
const templateContent = fs2.readFileSync(templatePath, "utf8");
return YAML2.parse(templateContent);
} catch (error) {
console.warn(
`Failed to load template ${templatePath}: ${error instanceof Error ? error.message : "Unknown error"}`
);
return null;
}
}
/**
* Get template by type and name with fallback to bundled templates
*/
getTemplateWithFallback(type, name = "default") {
const config = this.getConfig();
const projectRoot = path3.dirname(path3.dirname(this.configPath));
const pathResolver = new UnifiedPathResolver(config, projectRoot);
const paths = pathResolver.getUnifiedPaths();
const templatesDir = paths.templatesDir;
const projectTemplate = this.getTemplate(type, name);
if (projectTemplate) {
return projectTemplate;
}
const TemplateManager2 = (init_template_manager(), __toCommonJS(template_manager_exports)).TemplateManager;
const templateManager = new TemplateManager2();
return templateManager.getTemplate(templatesDir, type, name);
}
/**
* List available templates
*/
listTemplates() {
const config = this.getConfig();
const projectRoot = path3.dirname(path3.dirname(this.configPath));
const pathResolver = new UnifiedPathResolver(config, projectRoot);
const paths = pathResolver.getUnifiedPaths();
const templatesDir = paths.templatesDir;
if (!fs2.existsSync(templatesDir)) {
return [];
}
const templates = [];
const files = fs2.readdirSync(templatesDir).filter((file) => file.endsWith(".yaml"));
for (const file of files) {
try {
const templateContent = fs2.readFileSync(path3.join(templatesDir, file), "utf8");
const template = YAML2.parse(templateContent);
templates.push({
type: template.type,
name: template.name,
description: template.description
});
} catch (error) {
console.warn(
`Failed to parse template ${file}: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
return templates;
}
};
__name(_ConfigManager, "ConfigManager");
ConfigManager = _ConfigManager;
}
});
// src/utils/colors.ts
import chalk from "chalk";
var colors, _ColorTheme, ColorTheme;
var init_colors = __esm({
"src/utils/colors.ts"() {
"use strict";
init_esm_shims();
colors = {
// Main brand color - cyan for professional tech feel
primary: chalk.cyan,
// Success operations and positive feedback
success: chalk.green,
// Warnings and cautionary messages
warning: chalk.yellow,
// Errors and critical issues
error: chalk.red,
// Informational messages and tips
info: chalk.blue,
// Secondary text and less important information
muted: chalk.gray,
// Important highlights and emphasis
highlight: chalk.bold.white
};
_ColorTheme = class _ColorTheme {
// Priority-based colors
static priority(level) {
switch (level.toLowerCase()) {
case "low":
return chalk.gray;
case "medium":
return chalk.yellow;
case "high":
return chalk.magenta;
case "critical":
return chalk.red.bold;
default:
return chalk.whiteBright;
}
}
// Status-based colors
static status(status) {
switch (status.toLowerCase()) {
case "todo":
return chalk.gray;
case "in-progress":
return chalk.blue;
case "done":
return chalk.green;
case "blocked":
return chalk.red;
default:
return chalk.whiteBright;
}
}
// Command-specific colors
static command(command) {
return chalk.cyan.bold(command);
}
// Option colors
static option(option) {
return chalk.yellow(option);
}
// Argument colors
static argument(arg) {
return chalk.green(arg);
}
// Header styling
static header(text) {
return chalk.bold.cyan(`
${text}
${"=".repeat(text.length)}`);
}
// Subheader styling
static subheader(text) {
return chalk.bold.white(`
${text}
${"-".repeat(text.length)}`);
}
// Badge styling for tags, labels, etc.
static badge(text, variant = "info") {
const colorFn = colors[variant];
return colorFn(` ${text} `);
}
// Create a bordered box for important messages
static box(text, variant = "info") {
const lines = text.split("\n");
const maxLength = Math.max(...lines.map((line) => line.length));
const colorFn = colors[variant];
const border = "\u2500".repeat(maxLength + 2);
const top = `\u250C${border}\u2510`;
const bottom = `\u2514${border}\u2518`;
const content = lines.map((line) => `\u2502 ${line.padEnd(maxLength)} \u2502`).join("\n");
return colorFn(`${top}
${content}
${bottom}`);
}
// Progress indicators
static progress(current, total) {
const percentage = Math.round(current / total * 100);
const filled = Math.round(current / total * 20);
const empty = 20 - filled;
const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
if (percentage < 30) {
return chalk.red(`[${bar}] ${percentage}%`);
} else if (percentage < 70) {
return chalk.yellow(`[${bar}] ${percentage}%`);
} else {
return chalk.green(`[${bar}] ${percentage}%`);
}
}
// Create a separator line
static separator(char = "\u2500", length = 50) {
return chalk.gray(char.repeat(length));
}
// Timestamp formatting
static timestamp(date) {
return chalk.dim(date.toISOString().replace("T", " ").substring(0, 19));
}
// File path formatting
static path(path40) {
return chalk.cyan.underline(path40);
}
// Code formatting
static code(code) {
return chalk.gray.inverse(` ${code} `);
}
// URL formatting
static url(url) {
return chalk.blue.underline(url);
}
// Keyboard shortcut formatting
static key(key) {
return chalk.inverse(` ${key} `);
}
};
__name(_ColorTheme, "ColorTheme");
ColorTheme = _ColorTheme;
}
});
// src/utils/formatter.ts
import boxen from "boxen";
import figlet from "figlet";
var _Formatter, Formatter;
var init_formatter = __esm({
"src/utils/formatter.ts"() {
"use strict";
init_esm_shims();
init_colors();
_Formatter = class _Formatter {
static success(message) {
return colors.success(`\u2705 ${message}`);
}
static error(message) {
return colors.error(`\u274C ${message}`);
}
static warning(message) {
return colors.warning(`\u26A0\uFE0F ${message}`);
}
static info(message) {
return colors.info(`\u2139\uFE0F ${message}`);
}
static debug(message) {
return colors.muted(`\u{1F50D} ${message}`);
}
static header(text) {
return ColorTheme.header(text);
}
static subheader(text) {
return ColorTheme.subheader(text);
}
static highlight(text) {
return colors.highlight(text);
}
static dim(text) {
return colors.muted(text);
}
// Enhanced banner for CLI startup
static banner(text) {
try {
const ascii = figlet.textSync(text, {
font: "ANSI Shadow",
horizontalLayout: "default",
verticalLayout: "default",
width: 80,
whitespaceBreak: true
});
return colors.primary(ascii);
} catch {
return ColorTheme.header(text);
}
}
// Create beautiful notification boxes
static box(message, variant = "info") {
const borderColors = {
info: "cyan",
success: "green",
warning: "yellow",
error: "red"
};
return boxen(message, {
padding: 1,
margin: 1,
borderStyle: "round",
borderColor: borderColors[variant],
backgroundColor: void 0
});
}
// Professional item formatting with enhanced styling
static formatItem(item, format = "detailed") {
var _a, _b;
const statusBadge = ColorTheme.badge(
item.status.toUpperCase(),
_Formatter.getStatusVariant(item.status)
);
const priorityBadge = ColorTheme.badge(
item.priority.toUpperCase(),
_Formatter.getPriorityVariant(item.priority)
);
if (format === "compact") {
return [
`${statusBadge} ${priorityBadge} ${colors.highlight(item.title)}`,
colors.muted(`ID: ${item.id}`),
item.assignee ? colors.info(`@${item.assignee}`) : "",
((_a = item.tags) == null ? void 0 : _a.length) ? colors.muted(`[${item.tags.join(", ")}]`) : ""
].filter(Boolean).join(" ");
}
const sections = [
`${statusBadge} ${priorityBadge}`,
colors.highlight(item.title),
colors.muted(`ID: ${item.id}`),
item.assignee ? colors.info(`\u{1F464} Assignee: ${item.assignee}`) : "",
item.description ? colors.muted(`\u{1F4DD} ${item.description}`) : "",
item.estimate ? colors.warning(`\u{1F4CA} ${item.estimate} story points`) : "",
((_b = item.tags) == null ? void 0 : _b.length) ? colors.primary(`\u{1F3F7}\uFE0F Tags: ${item.tags.join(", ")}`) : "",
ColorTheme.timestamp(item.createdAt)
];
return sections.filter(Boolean).join("\n");
}
// Enhanced list formatting with statistics
static formatList(items, showStats = true) {
if (items.length === 0) {
return _Formatter.box("No items found", "info");
}
const formatted = items.map((item, index) => {
const prefix = colors.muted(`${(index + 1).toString().padStart(2, " ")}. `);
return `${prefix}${_Formatter.formatItem(item, "compact")}`;
}).join("\n");
if (!showStats) {
return formatted;
}
const stats = _Formatter.generateStats(items);
return `${formatted}
${stats}`;
}
// Generate item statistics
static generateStats(items) {
const total = items.length;
const byStatus = _Formatter.groupBy(items, "status");
const byPriority = _Formatter.groupBy(items, "priority");
const statusSection = Object.entries(byStatus).map(([status, count]) => {
const color = ColorTheme.status(status);
return color(`${status}: ${count}`);
}).join(" | ");
const prioritySection = Object.entries(byPriority).map(([priority, count]) => {
const color = ColorTheme.priority(priority);
return color(`${priority}: ${count}`);
}).join(" | ");
return [
ColorTheme.separator(),
colors.highlight(`\u{1F4CA} Statistics (${total} total)`),
`Status: ${statusSection}`,
`Priority: ${prioritySection}`,
ColorTheme.separator()
].join("\n");
}
// Enhanced table formatting
static formatTable(items) {
if (items.length === 0) {
return _Formatter.box("No items found", "info");
}
const headers = ["ID", "Title", "Status", "Priority", "Assignee", "Tags"];
const maxWidths = _Formatter.calculateColumnWidths(items, headers);
const headerRow = headers.map((header, i) => colors.highlight(header.padEnd(maxWidths[i]))).join(" | ");
const separator = ColorTheme.separator("\u2500", headerRow.length);
const rows = items.map((item) => {
var _a;
const cells = [
item.id,
item.title.length > 30 ? `${item.title.substring(0, 27)}...` : item.title,
item.status,
item.priority,
item.assignee || "unassigned",
((_a = item.tags) == null ? void 0 : _a.join(", ")) || ""
];
return cells.map((cell, i) => {
const colored = i === 2 ? ColorTheme.status(cell)(cell) : i === 3 ? ColorTheme.priority(cell)(cell) : cell;
return colored.padEnd(maxWidths[i]);
}).join(" | ");
});
return [headerRow, separator, ...rows].join("\n");