# CLI Package Implementation Plan

> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Build `@harness-engineering/cli` — a TypeScript CLI that wraps core library APIs for project validation, scaffolding, and agent orchestration.

**Architecture:** Commander.js CLI with modular command structure. Each command delegates to `@harness-engineering/core` APIs. Output supports JSON (for agents) and human-readable formats. Config loaded from `harness.config.json`.

**Tech Stack:** TypeScript, Commander.js, chalk, @harness-engineering/core

**Spec Reference:** [Phase 2 Design Spec](../specs/2026-03-12-phase2-tooling-automation-design.md)

---

## File Structure

```
packages/cli/
├── src/
│   ├── index.ts              # Commander program setup, global options
│   ├── commands/
│   │   ├── validate.ts       # harness validate
│   │   ├── check-deps.ts     # harness check-deps
│   │   ├── check-docs.ts     # harness check-docs
│   │   ├── init.ts           # harness init
│   │   ├── add.ts            # harness add <component>
│   │   ├── cleanup.ts        # harness cleanup
│   │   ├── fix-drift.ts      # harness fix-drift
│   │   └── agent/
│   │       ├── index.ts      # Agent subcommand group
│   │       ├── run.ts        # harness agent run <task>
│   │       └── review.ts     # harness agent review
│   ├── config/
│   │   ├── schema.ts         # HarnessConfig Zod schema
│   │   └── loader.ts         # findConfig, loadConfig
│   ├── output/
│   │   ├── formatter.ts      # OutputFormatter class
│   │   └── logger.ts         # Colored console output
│   └── utils/
│       └── errors.ts         # CLIError class, exit codes
├── bin/
│   └── harness.ts            # Entry point with shebang
├── tests/
│   ├── commands/
│   │   ├── validate.test.ts
│   │   ├── check-deps.test.ts
│   │   ├── check-docs.test.ts
│   │   ├── init.test.ts
│   │   ├── cleanup.test.ts
│   │   └── fix-drift.test.ts
│   ├── config/
│   │   └── loader.test.ts
│   ├── output/
│   │   └── formatter.test.ts
│   ├── integration/
│   │   └── cli.test.ts
│   └── fixtures/
│       ├── valid-project/
│       │   ├── harness.config.json
│       │   ├── AGENTS.md
│       │   └── docs/
│       └── invalid-project/
│           └── harness.config.json
├── package.json
├── tsconfig.json
├── vitest.config.mts
└── README.md
```

---

## Chunk 1: Package Setup & Infrastructure

### Task 1: Initialize CLI Package

**Files:**

- Create: `packages/cli/package.json`
- Create: `packages/cli/tsconfig.json`
- Create: `packages/cli/vitest.config.mts`
- Modify: `pnpm-workspace.yaml` (if needed)

- [ ] **Step 1: Create package.json**

```json
{
  "name": "@harness-engineering/cli",
  "version": "0.1.0",
  "description": "CLI for Harness Engineering toolkit",
  "type": "module",
  "bin": {
    "harness": "./dist/bin/harness.js"
  },
  "main": "./dist/index.js",
  "module": "./dist/index.mjs",
  "types": "./dist/index.d.ts",
  "files": ["dist", "bin"],
  "scripts": {
    "build": "tsup src/index.ts src/bin/harness.ts --format esm --dts --outDir dist",
    "dev": "tsup src/index.ts src/bin/harness.ts --format esm --dts --watch",
    "lint": "eslint src",
    "typecheck": "tsc --noEmit",
    "test": "vitest",
    "test:coverage": "vitest run --coverage"
  },
  "dependencies": {
    "@harness-engineering/core": "workspace:*",
    "commander": "^12.0.0",
    "chalk": "^5.3.0"
  },
  "devDependencies": {
    "@vitest/coverage-v8": "^4.0.18",
    "tsup": "^8.0.0",
    "vitest": "^4.0.18"
  },
  "license": "MIT"
}
```

- [ ] **Step 2: Create tsconfig.json**

```json
{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src",
    "declaration": true,
    "declarationMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "tests"]
}
```

- [ ] **Step 3: Create vitest.config.mts**

```typescript
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    globals: true,
    environment: 'node',
    include: ['tests/**/*.test.ts'],
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html'],
      exclude: ['tests/**', 'dist/**'],
    },
  },
});
```

- [ ] **Step 4: Install dependencies**

Run: `cd packages/cli && pnpm install`
Expected: Dependencies installed successfully

- [ ] **Step 5: Commit**

```bash
git add packages/cli/package.json packages/cli/tsconfig.json packages/cli/vitest.config.mts
git commit -m "chore(cli): initialize CLI package with dependencies"
```

---

### Task 2: Create Error Handling Utilities

**Files:**

- Create: `packages/cli/src/utils/errors.ts`
- Create: `packages/cli/tests/utils/errors.test.ts`

- [ ] **Step 1: Write the failing test**

```typescript
// packages/cli/tests/utils/errors.test.ts
import { describe, it, expect } from 'vitest';
import { CLIError, ExitCode, formatError } from '../../src/utils/errors';

describe('CLIError', () => {
  it('creates error with message and exit code', () => {
    const error = new CLIError('Validation failed', ExitCode.VALIDATION_FAILED);
    expect(error.message).toBe('Validation failed');
    expect(error.exitCode).toBe(1);
    expect(error.name).toBe('CLIError');
  });

  it('defaults to ERROR exit code', () => {
    const error = new CLIError('Something went wrong');
    expect(error.exitCode).toBe(2);
  });
});

describe('ExitCode', () => {
  it('has correct values', () => {
    expect(ExitCode.SUCCESS).toBe(0);
    expect(ExitCode.VALIDATION_FAILED).toBe(1);
    expect(ExitCode.ERROR).toBe(2);
  });
});

describe('formatError', () => {
  it('formats CLIError with exit code', () => {
    const error = new CLIError('Config not found', ExitCode.ERROR);
    const result = formatError(error);
    expect(result).toContain('Config not found');
  });

  it('formats generic Error', () => {
    const error = new Error('Unknown error');
    const result = formatError(error);
    expect(result).toContain('Unknown error');
  });
});
```

- [ ] **Step 2: Run test to verify it fails**

Run: `cd packages/cli && pnpm test -- tests/utils/errors.test.ts`
Expected: FAIL - cannot find module

- [ ] **Step 3: Write implementation**

```typescript
// packages/cli/src/utils/errors.ts

/**
 * CLI Exit Codes
 */
export const ExitCode = {
  SUCCESS: 0,
  VALIDATION_FAILED: 1,
  ERROR: 2,
} as const;

export type ExitCodeType = (typeof ExitCode)[keyof typeof ExitCode];

/**
 * CLI-specific error with exit code
 */
export class CLIError extends Error {
  readonly exitCode: ExitCodeType;

  constructor(message: string, exitCode: ExitCodeType = ExitCode.ERROR) {
    super(message);
    this.name = 'CLIError';
    this.exitCode = exitCode;
  }
}

/**
 * Format error for display
 */
export function formatError(error: unknown): string {
  if (error instanceof CLIError) {
    return `Error: ${error.message}`;
  }
  if (error instanceof Error) {
    return `Error: ${error.message}`;
  }
  return `Error: ${String(error)}`;
}

/**
 * Handle error and exit process
 */
export function handleError(error: unknown): never {
  const message = formatError(error);
  console.error(message);

  const exitCode = error instanceof CLIError ? error.exitCode : ExitCode.ERROR;
  process.exit(exitCode);
}
```

- [ ] **Step 4: Run test to verify it passes**

Run: `cd packages/cli && pnpm test -- tests/utils/errors.test.ts`
Expected: PASS

- [ ] **Step 5: Commit**

```bash
git add packages/cli/src/utils/errors.ts packages/cli/tests/utils/errors.test.ts
git commit -m "feat(cli): add error handling utilities with exit codes"
```

---

### Task 3: Create Output Formatter

**Files:**

- Create: `packages/cli/src/output/logger.ts`
- Create: `packages/cli/src/output/formatter.ts`
- Create: `packages/cli/tests/output/formatter.test.ts`

- [ ] **Step 1: Create logger (no test needed - thin wrapper)**

```typescript
// packages/cli/src/output/logger.ts
import chalk from 'chalk';

export const logger = {
  info: (message: string) => console.log(chalk.blue('ℹ'), message),
  success: (message: string) => console.log(chalk.green('✓'), message),
  warn: (message: string) => console.log(chalk.yellow('⚠'), message),
  error: (message: string) => console.error(chalk.red('✗'), message),
  dim: (message: string) => console.log(chalk.dim(message)),

  // For JSON output mode
  raw: (data: unknown) => console.log(JSON.stringify(data, null, 2)),
};
```

- [ ] **Step 2: Write formatter test**

```typescript
// packages/cli/tests/output/formatter.test.ts
import { describe, it, expect } from 'vitest';
import { OutputFormatter, OutputMode } from '../../src/output/formatter';

describe('OutputFormatter', () => {
  describe('json mode', () => {
    it('outputs valid JSON', () => {
      const formatter = new OutputFormatter(OutputMode.JSON);
      const data = { valid: true, issues: [] };
      const result = formatter.format(data);
      expect(JSON.parse(result)).toEqual(data);
    });
  });

  describe('text mode', () => {
    it('formats validation success', () => {
      const formatter = new OutputFormatter(OutputMode.TEXT);
      const data = { valid: true, issues: [] };
      const result = formatter.formatValidation(data);
      expect(result).toContain('valid');
    });

    it('formats validation failures with issues', () => {
      const formatter = new OutputFormatter(OutputMode.TEXT);
      const data = {
        valid: false,
        issues: [{ file: 'src/index.ts', message: 'Missing export' }],
      };
      const result = formatter.formatValidation(data);
      expect(result).toContain('src/index.ts');
      expect(result).toContain('Missing export');
    });
  });

  describe('quiet mode', () => {
    it('outputs nothing on success', () => {
      const formatter = new OutputFormatter(OutputMode.QUIET);
      const data = { valid: true, issues: [] };
      const result = formatter.formatValidation(data);
      expect(result).toBe('');
    });

    it('outputs only errors on failure', () => {
      const formatter = new OutputFormatter(OutputMode.QUIET);
      const data = {
        valid: false,
        issues: [{ file: 'test.ts', message: 'Error' }],
      };
      const result = formatter.formatValidation(data);
      expect(result).toContain('Error');
    });
  });
});
```

- [ ] **Step 3: Run test to verify it fails**

Run: `cd packages/cli && pnpm test -- tests/output/formatter.test.ts`
Expected: FAIL - cannot find module

- [ ] **Step 4: Write formatter implementation**

```typescript
// packages/cli/src/output/formatter.ts
import chalk from 'chalk';

export const OutputMode = {
  JSON: 'json',
  TEXT: 'text',
  QUIET: 'quiet',
  VERBOSE: 'verbose',
} as const;

export type OutputModeType = (typeof OutputMode)[keyof typeof OutputMode];

interface ValidationIssue {
  file?: string;
  message: string;
  line?: number;
  suggestion?: string;
}

interface ValidationResult {
  valid: boolean;
  issues: ValidationIssue[];
}

export class OutputFormatter {
  constructor(private mode: OutputModeType = OutputMode.TEXT) {}

  /**
   * Format raw data (for JSON mode)
   */
  format(data: unknown): string {
    if (this.mode === OutputMode.JSON) {
      return JSON.stringify(data, null, 2);
    }
    return String(data);
  }

  /**
   * Format validation result
   */
  formatValidation(result: ValidationResult): string {
    if (this.mode === OutputMode.JSON) {
      return JSON.stringify(result, null, 2);
    }

    if (this.mode === OutputMode.QUIET) {
      if (result.valid) return '';
      return result.issues.map((i) => `${i.file ?? ''}: ${i.message}`).join('\n');
    }

    const lines: string[] = [];

    if (result.valid) {
      lines.push(chalk.green('✓ Validation passed'));
    } else {
      lines.push(chalk.red(`✗ Validation failed (${result.issues.length} issues)`));
      lines.push('');

      for (const issue of result.issues) {
        const location = issue.file
          ? issue.line
            ? `${issue.file}:${issue.line}`
            : issue.file
          : 'unknown';
        lines.push(`  ${chalk.yellow('•')} ${chalk.dim(location)}`);
        lines.push(`    ${issue.message}`);
        if (issue.suggestion && this.mode === OutputMode.VERBOSE) {
          lines.push(`    ${chalk.dim('→')} ${issue.suggestion}`);
        }
      }
    }

    return lines.join('\n');
  }

  /**
   * Format a summary line
   */
  formatSummary(label: string, value: string | number, success: boolean): string {
    if (this.mode === OutputMode.JSON || this.mode === OutputMode.QUIET) {
      return '';
    }
    const icon = success ? chalk.green('✓') : chalk.red('✗');
    return `${icon} ${label}: ${value}`;
  }
}
```

- [ ] **Step 5: Run test to verify it passes**

Run: `cd packages/cli && pnpm test -- tests/output/formatter.test.ts`
Expected: PASS

- [ ] **Step 6: Commit**

```bash
git add packages/cli/src/output/logger.ts packages/cli/src/output/formatter.ts packages/cli/tests/output/formatter.test.ts
git commit -m "feat(cli): add output formatter with JSON, text, and quiet modes"
```

---

### Task 4: Create Config Loader

**Files:**

- Create: `packages/cli/src/config/schema.ts`
- Create: `packages/cli/src/config/loader.ts`
- Create: `packages/cli/tests/config/loader.test.ts`
- Create: `packages/cli/tests/fixtures/valid-project/harness.config.json`
- Create: `packages/cli/tests/fixtures/invalid-project/harness.config.json`

- [ ] **Step 1: Create config schema**

```typescript
// packages/cli/src/config/schema.ts
import { z } from 'zod';

export const LayerSchema = z.object({
  name: z.string(),
  pattern: z.string(),
  allowedDependencies: z.array(z.string()),
});

export const ForbiddenImportSchema = z.object({
  from: z.string(),
  disallow: z.array(z.string()),
  message: z.string().optional(),
});

export const BoundaryConfigSchema = z.object({
  requireSchema: z.array(z.string()),
});

export const AgentConfigSchema = z.object({
  executor: z.enum(['subprocess', 'cloud', 'noop']).default('subprocess'),
  timeout: z.number().default(300000),
  skills: z.array(z.string()).optional(),
});

export const EntropyConfigSchema = z.object({
  excludePatterns: z.array(z.string()).default(['**/node_modules/**', '**/*.test.ts']),
  autoFix: z.boolean().default(false),
});

export const HarnessConfigSchema = z.object({
  version: z.literal(1),
  name: z.string().optional(),
  rootDir: z.string().default('.'),
  layers: z.array(LayerSchema).optional(),
  forbiddenImports: z.array(ForbiddenImportSchema).optional(),
  boundaries: BoundaryConfigSchema.optional(),
  agentsMapPath: z.string().default('./AGENTS.md'),
  docsDir: z.string().default('./docs'),
  agent: AgentConfigSchema.optional(),
  entropy: EntropyConfigSchema.optional(),
});

export type HarnessConfig = z.infer<typeof HarnessConfigSchema>;
export type Layer = z.infer<typeof LayerSchema>;
```

- [ ] **Step 2: Write loader test**

```typescript
// packages/cli/tests/config/loader.test.ts
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { findConfigFile, loadConfig } from '../../src/config/loader';
import * as path from 'path';
import * as fs from 'fs';
import * as os from 'os';

describe('findConfigFile', () => {
  it('finds harness.config.json in current directory', () => {
    const fixtureDir = path.join(__dirname, '../fixtures/valid-project');
    const result = findConfigFile(fixtureDir);
    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value).toContain('harness.config.json');
    }
  });

  it('returns error when no config found', () => {
    const result = findConfigFile(os.tmpdir());
    expect(result.ok).toBe(false);
  });
});

describe('loadConfig', () => {
  it('loads and validates config file', () => {
    const configPath = path.join(__dirname, '../fixtures/valid-project/harness.config.json');
    const result = loadConfig(configPath);
    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.version).toBe(1);
      expect(result.value.name).toBe('test-project');
    }
  });

  it('returns error for invalid config', () => {
    const configPath = path.join(__dirname, '../fixtures/invalid-project/harness.config.json');
    const result = loadConfig(configPath);
    expect(result.ok).toBe(false);
  });

  it('returns error for missing file', () => {
    const result = loadConfig('/nonexistent/harness.config.json');
    expect(result.ok).toBe(false);
  });
});
```

- [ ] **Step 3: Create test fixtures**

```json
// packages/cli/tests/fixtures/valid-project/harness.config.json
{
  "version": 1,
  "name": "test-project",
  "layers": [
    { "name": "types", "pattern": "src/types/**", "allowedDependencies": [] },
    { "name": "domain", "pattern": "src/domain/**", "allowedDependencies": ["types"] }
  ],
  "agentsMapPath": "./AGENTS.md",
  "docsDir": "./docs"
}
```

```json
// packages/cli/tests/fixtures/invalid-project/harness.config.json
{
  "version": 2,
  "invalid": true
}
```

- [ ] **Step 4: Run test to verify it fails**

Run: `cd packages/cli && pnpm test -- tests/config/loader.test.ts`
Expected: FAIL - cannot find module

- [ ] **Step 5: Write loader implementation**

```typescript
// packages/cli/src/config/loader.ts
import * as fs from 'fs';
import * as path from 'path';
import type { Result } from '@harness-engineering/types';
import { Ok, Err } from '@harness-engineering/core';
import { HarnessConfigSchema, type HarnessConfig } from './schema';
import { CLIError, ExitCode } from '../utils/errors';

const CONFIG_FILENAMES = ['harness.config.json'];

/**
 * Find config file starting from given directory
 */
export function findConfigFile(startDir: string = process.cwd()): Result<string, CLIError> {
  let currentDir = path.resolve(startDir);
  const root = path.parse(currentDir).root;

  while (currentDir !== root) {
    for (const filename of CONFIG_FILENAMES) {
      const configPath = path.join(currentDir, filename);
      if (fs.existsSync(configPath)) {
        return Ok(configPath);
      }
    }
    currentDir = path.dirname(currentDir);
  }

  return Err(
    new CLIError('No harness.config.json found. Run "harness init" to create one.', ExitCode.ERROR)
  );
}

/**
 * Load and validate config from file
 */
export function loadConfig(configPath: string): Result<HarnessConfig, CLIError> {
  // Check file exists
  if (!fs.existsSync(configPath)) {
    return Err(new CLIError(`Config file not found: ${configPath}`, ExitCode.ERROR));
  }

  // Read and parse JSON
  let rawConfig: unknown;
  try {
    const content = fs.readFileSync(configPath, 'utf-8');
    rawConfig = JSON.parse(content);
  } catch (error) {
    return Err(
      new CLIError(
        `Failed to parse config: ${error instanceof Error ? error.message : 'Unknown error'}`,
        ExitCode.ERROR
      )
    );
  }

  // Validate against schema
  const parsed = HarnessConfigSchema.safeParse(rawConfig);
  if (!parsed.success) {
    const issues = parsed.error.issues
      .map((i) => `  - ${i.path.join('.')}: ${i.message}`)
      .join('\n');
    return Err(new CLIError(`Invalid config:\n${issues}`, ExitCode.ERROR));
  }

  return Ok(parsed.data);
}

/**
 * Load config from default location or specified path
 */
export function resolveConfig(configPath?: string): Result<HarnessConfig, CLIError> {
  if (configPath) {
    return loadConfig(configPath);
  }

  const findResult = findConfigFile();
  if (!findResult.ok) {
    return findResult;
  }

  return loadConfig(findResult.value);
}
```

- [ ] **Step 6: Run test to verify it passes**

Run: `cd packages/cli && pnpm test -- tests/config/loader.test.ts`
Expected: PASS

- [ ] **Step 7: Commit**

```bash
git add packages/cli/src/config/ packages/cli/tests/config/ packages/cli/tests/fixtures/
git commit -m "feat(cli): add config schema and loader with validation"
```

---

### Task 5: Create CLI Entry Point

**Files:**

- Create: `packages/cli/src/index.ts`
- Create: `packages/cli/src/bin/harness.ts`

- [ ] **Step 1: Create main program**

```typescript
// packages/cli/src/index.ts
import { Command } from 'commander';
import { VERSION } from '@harness-engineering/core';

export function createProgram(): Command {
  const program = new Command();

  program
    .name('harness')
    .description('CLI for Harness Engineering toolkit')
    .version(VERSION)
    .option('-c, --config <path>', 'Path to config file')
    .option('--json', 'Output as JSON')
    .option('--verbose', 'Verbose output')
    .option('--quiet', 'Minimal output');

  return program;
}

// Re-export utilities
export { CLIError, ExitCode, handleError } from './utils/errors';
export { OutputFormatter, OutputMode } from './output/formatter';
export { logger } from './output/logger';
export { loadConfig, findConfigFile, resolveConfig } from './config/loader';
export type { HarnessConfig } from './config/schema';
```

- [ ] **Step 2: Create bin entry point**

```typescript
#!/usr/bin/env node
// packages/cli/src/bin/harness.ts
import { createProgram, handleError } from '../index';

async function main() {
  const program = createProgram();

  // Commands will be registered here as we implement them
  // For now, just parse and show help if no command

  try {
    await program.parseAsync(process.argv);
  } catch (error) {
    handleError(error);
  }
}

main();
```

- [ ] **Step 3: Build and test CLI runs**

Run: `cd packages/cli && pnpm build && node dist/bin/harness.js --version`
Expected: Outputs version number (0.5.0)

- [ ] **Step 4: Commit**

```bash
git add packages/cli/src/index.ts packages/cli/src/bin/harness.ts
git commit -m "feat(cli): add CLI entry point with global options"
```

---

## Chunk 2: Core Validation Commands

### Task 6: Implement `harness validate` Command

**Files:**

- Create: `packages/cli/src/commands/validate.ts`
- Create: `packages/cli/tests/commands/validate.test.ts`
- Create: `packages/cli/tests/fixtures/valid-project/AGENTS.md`
- Create: `packages/cli/tests/fixtures/valid-project/docs/guide.md`

- [ ] **Step 1: Create test fixtures**

```markdown
<!-- packages/cli/tests/fixtures/valid-project/AGENTS.md -->

# Project Knowledge Map

## About This Project

Test project for CLI validation.

## Documentation

- Guide: `docs/guide.md`

## Source Code

- Main entry: `src/index.ts`
```

```markdown
<!-- packages/cli/tests/fixtures/valid-project/docs/guide.md -->

# Guide

This is a test guide.
```

- [ ] **Step 2: Write validate command test**

```typescript
// packages/cli/tests/commands/validate.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { createValidateCommand, runValidate } from '../../src/commands/validate';
import * as path from 'path';

describe('validate command', () => {
  const validProjectPath = path.join(__dirname, '../fixtures/valid-project');

  describe('runValidate', () => {
    it('returns success for valid project', async () => {
      const result = await runValidate({
        cwd: validProjectPath,
        configPath: path.join(validProjectPath, 'harness.config.json'),
      });
      expect(result.ok).toBe(true);
      if (result.ok) {
        expect(result.value.valid).toBe(true);
      }
    });

    it('checks AGENTS.md exists', async () => {
      const result = await runValidate({
        cwd: validProjectPath,
        configPath: path.join(validProjectPath, 'harness.config.json'),
      });
      expect(result.ok).toBe(true);
      if (result.ok) {
        expect(result.value.checks.agentsMap).toBe(true);
      }
    });
  });

  describe('createValidateCommand', () => {
    it('creates command with correct name', () => {
      const cmd = createValidateCommand();
      expect(cmd.name()).toBe('validate');
    });
  });
});
```

- [ ] **Step 3: Run test to verify it fails**

Run: `cd packages/cli && pnpm test -- tests/commands/validate.test.ts`
Expected: FAIL - cannot find module

- [ ] **Step 4: Write validate command implementation**

```typescript
// packages/cli/src/commands/validate.ts
import { Command } from 'commander';
import * as path from 'path';
import type { Result } from '@harness-engineering/types';
import { Ok, Err } from '@harness-engineering/core';
import {
  validateAgentsMap,
  validateFileStructure,
  validateKnowledgeMap,
} from '@harness-engineering/core';
import { resolveConfig, type HarnessConfig } from '../config/loader';
import { OutputFormatter, OutputMode, type OutputModeType } from '../output/formatter';
import { logger } from '../output/logger';
import { CLIError, ExitCode } from '../utils/errors';

interface ValidateOptions {
  cwd?: string;
  configPath?: string;
  json?: boolean;
  verbose?: boolean;
  quiet?: boolean;
}

interface ValidateResult {
  valid: boolean;
  checks: {
    agentsMap: boolean;
    fileStructure: boolean;
    knowledgeMap: boolean;
  };
  issues: Array<{
    check: string;
    file?: string;
    message: string;
    suggestion?: string;
  }>;
}

export async function runValidate(
  options: ValidateOptions
): Promise<Result<ValidateResult, CLIError>> {
  const cwd = options.cwd ?? process.cwd();

  // Load config
  const configResult = resolveConfig(options.configPath);
  if (!configResult.ok) {
    return configResult;
  }
  const config = configResult.value;

  const result: ValidateResult = {
    valid: true,
    checks: {
      agentsMap: false,
      fileStructure: false,
      knowledgeMap: false,
    },
    issues: [],
  };

  // Check AGENTS.md
  const agentsMapPath = path.resolve(cwd, config.agentsMapPath);
  const agentsResult = validateAgentsMap(agentsMapPath);
  if (agentsResult.ok) {
    result.checks.agentsMap = true;
  } else {
    result.valid = false;
    result.issues.push({
      check: 'agentsMap',
      file: config.agentsMapPath,
      message: agentsResult.error.message,
      suggestion: agentsResult.error.suggestions?.[0],
    });
  }

  // Check knowledge map integrity (no broken links)
  const knowledgeResult = validateKnowledgeMap(agentsMapPath, cwd);
  if (knowledgeResult.ok && knowledgeResult.value.brokenLinks.length === 0) {
    result.checks.knowledgeMap = true;
  } else if (knowledgeResult.ok) {
    result.valid = false;
    for (const broken of knowledgeResult.value.brokenLinks) {
      result.issues.push({
        check: 'knowledgeMap',
        file: broken.path,
        message: `Broken link: ${broken.path}`,
        suggestion: 'Remove or fix the broken link',
      });
    }
  } else {
    result.valid = false;
    result.issues.push({
      check: 'knowledgeMap',
      message: knowledgeResult.error.message,
    });
  }

  // Check file structure if conventions defined
  // For now, mark as passed if no conventions
  result.checks.fileStructure = true;

  return Ok(result);
}

export function createValidateCommand(): Command {
  const command = new Command('validate')
    .description('Run all validation checks')
    .action(async (opts, cmd) => {
      const globalOpts = cmd.optsWithGlobals();
      const mode: OutputModeType = globalOpts.json
        ? OutputMode.JSON
        : globalOpts.quiet
          ? OutputMode.QUIET
          : globalOpts.verbose
            ? OutputMode.VERBOSE
            : OutputMode.TEXT;

      const formatter = new OutputFormatter(mode);

      const result = await runValidate({
        configPath: globalOpts.config,
        json: globalOpts.json,
        verbose: globalOpts.verbose,
        quiet: globalOpts.quiet,
      });

      if (!result.ok) {
        if (mode === OutputMode.JSON) {
          console.log(JSON.stringify({ error: result.error.message }));
        } else {
          logger.error(result.error.message);
        }
        process.exit(result.error.exitCode);
      }

      const output = formatter.formatValidation({
        valid: result.value.valid,
        issues: result.value.issues,
      });

      if (output) {
        console.log(output);
      }

      process.exit(result.value.valid ? ExitCode.SUCCESS : ExitCode.VALIDATION_FAILED);
    });

  return command;
}
```

- [ ] **Step 5: Register command in main program**

Update `packages/cli/src/index.ts`:

```typescript
import { Command } from 'commander';
import { VERSION } from '@harness-engineering/core';
import { createValidateCommand } from './commands/validate';

export function createProgram(): Command {
  const program = new Command();

  program
    .name('harness')
    .description('CLI for Harness Engineering toolkit')
    .version(VERSION)
    .option('-c, --config <path>', 'Path to config file')
    .option('--json', 'Output as JSON')
    .option('--verbose', 'Verbose output')
    .option('--quiet', 'Minimal output');

  // Register commands
  program.addCommand(createValidateCommand());

  return program;
}

// Re-export utilities
export { CLIError, ExitCode, handleError } from './utils/errors';
export { OutputFormatter, OutputMode } from './output/formatter';
export { logger } from './output/logger';
export { loadConfig, findConfigFile, resolveConfig } from './config/loader';
export type { HarnessConfig } from './config/schema';
```

- [ ] **Step 6: Run test to verify it passes**

Run: `cd packages/cli && pnpm test -- tests/commands/validate.test.ts`
Expected: PASS

- [ ] **Step 7: Integration test - run CLI**

Run: `cd packages/cli && pnpm build && node dist/bin/harness.js validate --config tests/fixtures/valid-project/harness.config.json`
Expected: Validation passed output

- [ ] **Step 8: Commit**

```bash
git add packages/cli/src/commands/validate.ts packages/cli/tests/commands/validate.test.ts packages/cli/tests/fixtures/valid-project/
git commit -m "feat(cli): implement validate command with AGENTS.md and knowledge map checks"
```

---

### Task 7: Implement `harness check-deps` Command

**Files:**

- Create: `packages/cli/src/commands/check-deps.ts`
- Create: `packages/cli/tests/commands/check-deps.test.ts`

- [ ] **Step 1: Write check-deps test**

```typescript
// packages/cli/tests/commands/check-deps.test.ts
import { describe, it, expect } from 'vitest';
import { createCheckDepsCommand, runCheckDeps } from '../../src/commands/check-deps';
import * as path from 'path';

describe('check-deps command', () => {
  const validProjectPath = path.join(__dirname, '../fixtures/valid-project');

  describe('runCheckDeps', () => {
    it('returns success when no layers configured', async () => {
      const result = await runCheckDeps({
        cwd: validProjectPath,
        configPath: path.join(validProjectPath, 'harness.config.json'),
      });
      expect(result.ok).toBe(true);
      if (result.ok) {
        expect(result.value.valid).toBe(true);
      }
    });
  });

  describe('createCheckDepsCommand', () => {
    it('creates command with correct name', () => {
      const cmd = createCheckDepsCommand();
      expect(cmd.name()).toBe('check-deps');
    });
  });
});
```

- [ ] **Step 2: Run test to verify it fails**

Run: `cd packages/cli && pnpm test -- tests/commands/check-deps.test.ts`
Expected: FAIL - cannot find module

- [ ] **Step 3: Write check-deps implementation**

```typescript
// packages/cli/src/commands/check-deps.ts
import { Command } from 'commander';
import * as path from 'path';
import type { Result } from '@harness-engineering/types';
import { Ok, Err } from '@harness-engineering/core';
import {
  validateDependencies,
  detectCircularDeps,
  defineLayer,
  TypeScriptParser,
} from '@harness-engineering/core';
import { resolveConfig } from '../config/loader';
import { OutputFormatter, OutputMode, type OutputModeType } from '../output/formatter';
import { logger } from '../output/logger';
import { CLIError, ExitCode } from '../utils/errors';

interface CheckDepsOptions {
  cwd?: string;
  configPath?: string;
  json?: boolean;
  verbose?: boolean;
  quiet?: boolean;
}

interface CheckDepsResult {
  valid: boolean;
  layerViolations: Array<{
    file: string;
    imports: string;
    fromLayer: string;
    toLayer: string;
    message: string;
  }>;
  circularDeps: Array<{
    cycle: string[];
  }>;
}

export async function runCheckDeps(
  options: CheckDepsOptions
): Promise<Result<CheckDepsResult, CLIError>> {
  const cwd = options.cwd ?? process.cwd();

  // Load config
  const configResult = resolveConfig(options.configPath);
  if (!configResult.ok) {
    return configResult;
  }
  const config = configResult.value;

  const result: CheckDepsResult = {
    valid: true,
    layerViolations: [],
    circularDeps: [],
  };

  // If no layers configured, skip layer validation
  if (!config.layers || config.layers.length === 0) {
    return Ok(result);
  }

  // Define layers
  const layers = config.layers.map((l) => defineLayer(l.name, l.allowedDependencies));

  // Build layer config
  const layerConfig = {
    layers: layers.filter((l): l is NonNullable<typeof l> => l.ok).map((l) => l.value),
    rootDir: path.resolve(cwd, config.rootDir),
    parser: new TypeScriptParser(),
    layerPatterns: Object.fromEntries(config.layers.map((l) => [l.name, l.pattern])),
  };

  // Validate dependencies
  const depsResult = await validateDependencies(layerConfig);
  if (depsResult.ok) {
    for (const violation of depsResult.value.violations) {
      result.valid = false;
      result.layerViolations.push({
        file: violation.file,
        imports: violation.imports,
        fromLayer: violation.fromLayer ?? 'unknown',
        toLayer: violation.toLayer ?? 'unknown',
        message: violation.reason,
      });
    }
  }

  // Detect circular dependencies
  const circularResult = await detectCircularDeps(layerConfig.rootDir, layerConfig.parser);
  if (circularResult.ok && circularResult.value.found) {
    result.valid = false;
    for (const cycle of circularResult.value.cycles) {
      result.circularDeps.push({ cycle: cycle.cycle });
    }
  }

  return Ok(result);
}

export function createCheckDepsCommand(): Command {
  const command = new Command('check-deps')
    .description('Validate dependency layers and detect circular dependencies')
    .action(async (opts, cmd) => {
      const globalOpts = cmd.optsWithGlobals();
      const mode: OutputModeType = globalOpts.json
        ? OutputMode.JSON
        : globalOpts.quiet
          ? OutputMode.QUIET
          : globalOpts.verbose
            ? OutputMode.VERBOSE
            : OutputMode.TEXT;

      const formatter = new OutputFormatter(mode);

      const result = await runCheckDeps({
        configPath: globalOpts.config,
        json: globalOpts.json,
        verbose: globalOpts.verbose,
        quiet: globalOpts.quiet,
      });

      if (!result.ok) {
        if (mode === OutputMode.JSON) {
          console.log(JSON.stringify({ error: result.error.message }));
        } else {
          logger.error(result.error.message);
        }
        process.exit(result.error.exitCode);
      }

      const issues = [
        ...result.value.layerViolations.map((v) => ({
          file: v.file,
          message: `Layer violation: ${v.fromLayer} → ${v.toLayer} (${v.message})`,
        })),
        ...result.value.circularDeps.map((c) => ({
          message: `Circular dependency: ${c.cycle.join(' → ')}`,
        })),
      ];

      const output = formatter.formatValidation({
        valid: result.value.valid,
        issues,
      });

      if (output) {
        console.log(output);
      }

      process.exit(result.value.valid ? ExitCode.SUCCESS : ExitCode.VALIDATION_FAILED);
    });

  return command;
}
```

- [ ] **Step 4: Register command in main program**

Update `packages/cli/src/index.ts` to add:

```typescript
import { createCheckDepsCommand } from './commands/check-deps';
// ... in createProgram():
program.addCommand(createCheckDepsCommand());
```

- [ ] **Step 5: Run test to verify it passes**

Run: `cd packages/cli && pnpm test -- tests/commands/check-deps.test.ts`
Expected: PASS

- [ ] **Step 6: Commit**

```bash
git add packages/cli/src/commands/check-deps.ts packages/cli/tests/commands/check-deps.test.ts packages/cli/src/index.ts
git commit -m "feat(cli): implement check-deps command for layer and circular dependency validation"
```

---

### Task 8: Implement `harness check-docs` Command

**Files:**

- Create: `packages/cli/src/commands/check-docs.ts`
- Create: `packages/cli/tests/commands/check-docs.test.ts`

- [ ] **Step 1: Write check-docs test**

```typescript
// packages/cli/tests/commands/check-docs.test.ts
import { describe, it, expect } from 'vitest';
import { createCheckDocsCommand, runCheckDocs } from '../../src/commands/check-docs';
import * as path from 'path';

describe('check-docs command', () => {
  const validProjectPath = path.join(__dirname, '../fixtures/valid-project');

  describe('runCheckDocs', () => {
    it('returns documentation coverage report', async () => {
      const result = await runCheckDocs({
        cwd: validProjectPath,
        configPath: path.join(validProjectPath, 'harness.config.json'),
      });
      expect(result.ok).toBe(true);
      if (result.ok) {
        expect(typeof result.value.coveragePercent).toBe('number');
      }
    });
  });

  describe('createCheckDocsCommand', () => {
    it('creates command with correct name', () => {
      const cmd = createCheckDocsCommand();
      expect(cmd.name()).toBe('check-docs');
    });
  });
});
```

- [ ] **Step 2: Run test to verify it fails**

Run: `cd packages/cli && pnpm test -- tests/commands/check-docs.test.ts`
Expected: FAIL - cannot find module

- [ ] **Step 3: Write check-docs implementation**

```typescript
// packages/cli/src/commands/check-docs.ts
import { Command } from 'commander';
import * as path from 'path';
import type { Result } from '@harness-engineering/types';
import { Ok, Err } from '@harness-engineering/core';
import { checkDocCoverage, validateKnowledgeMap } from '@harness-engineering/core';
import { resolveConfig } from '../config/loader';
import { OutputFormatter, OutputMode, type OutputModeType } from '../output/formatter';
import { logger } from '../output/logger';
import { CLIError, ExitCode } from '../utils/errors';

interface CheckDocsOptions {
  cwd?: string;
  configPath?: string;
  minCoverage?: number;
  json?: boolean;
  verbose?: boolean;
  quiet?: boolean;
}

interface CheckDocsResult {
  valid: boolean;
  coveragePercent: number;
  documented: string[];
  undocumented: string[];
  brokenLinks: string[];
}

export async function runCheckDocs(
  options: CheckDocsOptions
): Promise<Result<CheckDocsResult, CLIError>> {
  const cwd = options.cwd ?? process.cwd();
  const minCoverage = options.minCoverage ?? 80;

  // Load config
  const configResult = resolveConfig(options.configPath);
  if (!configResult.ok) {
    return configResult;
  }
  const config = configResult.value;

  const docsDir = path.resolve(cwd, config.docsDir);
  const agentsMapPath = path.resolve(cwd, config.agentsMapPath);

  // Check documentation coverage
  const coverageResult = checkDocCoverage(docsDir, {
    rootDir: cwd,
    includePatterns: ['**/*.ts'],
    excludePatterns: ['**/*.test.ts', '**/node_modules/**'],
  });

  // Check knowledge map for broken links
  const knowledgeResult = validateKnowledgeMap(agentsMapPath, cwd);
  const brokenLinks = knowledgeResult.ok
    ? knowledgeResult.value.brokenLinks.map((b) => b.path)
    : [];

  const coveragePercent = coverageResult.ok ? coverageResult.value.coveragePercentage : 0;

  const result: CheckDocsResult = {
    valid: coveragePercent >= minCoverage && brokenLinks.length === 0,
    coveragePercent,
    documented: coverageResult.ok ? coverageResult.value.documented : [],
    undocumented: coverageResult.ok ? coverageResult.value.undocumented : [],
    brokenLinks,
  };

  return Ok(result);
}

export function createCheckDocsCommand(): Command {
  const command = new Command('check-docs')
    .description('Check documentation coverage')
    .option('--min-coverage <percent>', 'Minimum coverage percentage', '80')
    .action(async (opts, cmd) => {
      const globalOpts = cmd.optsWithGlobals();
      const mode: OutputModeType = globalOpts.json
        ? OutputMode.JSON
        : globalOpts.quiet
          ? OutputMode.QUIET
          : globalOpts.verbose
            ? OutputMode.VERBOSE
            : OutputMode.TEXT;

      const formatter = new OutputFormatter(mode);

      const result = await runCheckDocs({
        configPath: globalOpts.config,
        minCoverage: parseInt(opts.minCoverage, 10),
        json: globalOpts.json,
        verbose: globalOpts.verbose,
        quiet: globalOpts.quiet,
      });

      if (!result.ok) {
        if (mode === OutputMode.JSON) {
          console.log(JSON.stringify({ error: result.error.message }));
        } else {
          logger.error(result.error.message);
        }
        process.exit(result.error.exitCode);
      }

      if (mode === OutputMode.JSON) {
        console.log(JSON.stringify(result.value, null, 2));
      } else if (mode !== OutputMode.QUIET) {
        const { value } = result;
        console.log(
          formatter.formatSummary(
            'Documentation coverage',
            `${value.coveragePercent.toFixed(1)}%`,
            value.valid
          )
        );

        if (value.undocumented.length > 0 && (mode === OutputMode.VERBOSE || !value.valid)) {
          console.log('\nUndocumented files:');
          for (const file of value.undocumented.slice(0, 10)) {
            console.log(`  - ${file}`);
          }
          if (value.undocumented.length > 10) {
            console.log(`  ... and ${value.undocumented.length - 10} more`);
          }
        }

        if (value.brokenLinks.length > 0) {
          console.log('\nBroken links:');
          for (const link of value.brokenLinks) {
            console.log(`  - ${link}`);
          }
        }
      }

      process.exit(result.value.valid ? ExitCode.SUCCESS : ExitCode.VALIDATION_FAILED);
    });

  return command;
}
```

- [ ] **Step 4: Register command in main program**

Update `packages/cli/src/index.ts` to add:

```typescript
import { createCheckDocsCommand } from './commands/check-docs';
// ... in createProgram():
program.addCommand(createCheckDocsCommand());
```

- [ ] **Step 5: Run test to verify it passes**

Run: `cd packages/cli && pnpm test -- tests/commands/check-docs.test.ts`
Expected: PASS

- [ ] **Step 6: Commit**

```bash
git add packages/cli/src/commands/check-docs.ts packages/cli/tests/commands/check-docs.test.ts packages/cli/src/index.ts
git commit -m "feat(cli): implement check-docs command for documentation coverage"
```

---

## Chunk 3: Scaffolding & Entropy Commands

### Task 9: Implement `harness init` Command

**Files:**

- Create: `packages/cli/src/commands/init.ts`
- Create: `packages/cli/src/templates/basic.ts`
- Create: `packages/cli/tests/commands/init.test.ts`

- [ ] **Step 1: Write init test**

```typescript
// packages/cli/tests/commands/init.test.ts
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { createInitCommand, runInit } from '../../src/commands/init';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';

describe('init command', () => {
  let tempDir: string;

  beforeEach(() => {
    tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-test-'));
  });

  afterEach(() => {
    fs.rmSync(tempDir, { recursive: true, force: true });
  });

  describe('runInit', () => {
    it('creates harness.config.json', async () => {
      const result = await runInit({
        cwd: tempDir,
        name: 'test-project',
      });
      expect(result.ok).toBe(true);
      expect(fs.existsSync(path.join(tempDir, 'harness.config.json'))).toBe(true);
    });

    it('creates AGENTS.md', async () => {
      const result = await runInit({
        cwd: tempDir,
        name: 'test-project',
      });
      expect(result.ok).toBe(true);
      expect(fs.existsSync(path.join(tempDir, 'AGENTS.md'))).toBe(true);
    });

    it('does not overwrite existing config', async () => {
      fs.writeFileSync(path.join(tempDir, 'harness.config.json'), '{}');
      const result = await runInit({
        cwd: tempDir,
        name: 'test-project',
      });
      expect(result.ok).toBe(false);
    });
  });

  describe('createInitCommand', () => {
    it('creates command with correct name', () => {
      const cmd = createInitCommand();
      expect(cmd.name()).toBe('init');
    });
  });
});
```

- [ ] **Step 2: Run test to verify it fails**

Run: `cd packages/cli && pnpm test -- tests/commands/init.test.ts`
Expected: FAIL - cannot find module

- [ ] **Step 3: Create templates**

```typescript
// packages/cli/src/templates/basic.ts

export const CONFIG_TEMPLATE = (name: string) => ({
  version: 1 as const,
  name,
  layers: [
    { name: 'types', pattern: 'src/types/**', allowedDependencies: [] },
    { name: 'domain', pattern: 'src/domain/**', allowedDependencies: ['types'] },
    { name: 'services', pattern: 'src/services/**', allowedDependencies: ['types', 'domain'] },
    { name: 'api', pattern: 'src/api/**', allowedDependencies: ['types', 'domain', 'services'] },
  ],
  agentsMapPath: './AGENTS.md',
  docsDir: './docs',
});

export const AGENTS_MD_TEMPLATE = (name: string) => `# ${name} Knowledge Map

## About This Project

${name} - A project using Harness Engineering practices.

## Documentation

- Main docs: \`docs/\`

## Source Code

- Entry point: \`src/index.ts\`

## Architecture

See \`docs/architecture.md\` for architectural decisions.
`;

export const DOCS_INDEX_TEMPLATE = (name: string) => `# ${name} Documentation

Welcome to the ${name} documentation.

## Getting Started

TODO: Add getting started guide.

## Architecture

TODO: Add architecture documentation.
`;
```

- [ ] **Step 4: Write init implementation**

```typescript
// packages/cli/src/commands/init.ts
import { Command } from 'commander';
import * as fs from 'fs';
import * as path from 'path';
import type { Result } from '@harness-engineering/types';
import { Ok, Err } from '@harness-engineering/core';
import { CONFIG_TEMPLATE, AGENTS_MD_TEMPLATE, DOCS_INDEX_TEMPLATE } from '../templates/basic';
import { logger } from '../output/logger';
import { CLIError, ExitCode } from '../utils/errors';

interface InitOptions {
  cwd?: string;
  name?: string;
  force?: boolean;
}

interface InitResult {
  filesCreated: string[];
}

export async function runInit(options: InitOptions): Promise<Result<InitResult, CLIError>> {
  const cwd = options.cwd ?? process.cwd();
  const name = options.name ?? path.basename(cwd);
  const force = options.force ?? false;

  const configPath = path.join(cwd, 'harness.config.json');
  const agentsPath = path.join(cwd, 'AGENTS.md');
  const docsDir = path.join(cwd, 'docs');

  // Check if already initialized
  if (!force && fs.existsSync(configPath)) {
    return Err(
      new CLIError('Project already initialized. Use --force to overwrite.', ExitCode.ERROR)
    );
  }

  const filesCreated: string[] = [];

  try {
    // Create config
    const config = CONFIG_TEMPLATE(name);
    fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
    filesCreated.push('harness.config.json');

    // Create AGENTS.md
    if (!fs.existsSync(agentsPath) || force) {
      fs.writeFileSync(agentsPath, AGENTS_MD_TEMPLATE(name));
      filesCreated.push('AGENTS.md');
    }

    // Create docs directory
    if (!fs.existsSync(docsDir)) {
      fs.mkdirSync(docsDir, { recursive: true });
      fs.writeFileSync(path.join(docsDir, 'index.md'), DOCS_INDEX_TEMPLATE(name));
      filesCreated.push('docs/index.md');
    }

    return Ok({ filesCreated });
  } catch (error) {
    return Err(
      new CLIError(
        `Failed to initialize: ${error instanceof Error ? error.message : 'Unknown error'}`,
        ExitCode.ERROR
      )
    );
  }
}

export function createInitCommand(): Command {
  const command = new Command('init')
    .description('Initialize a new harness-engineering project')
    .option('-n, --name <name>', 'Project name')
    .option('-f, --force', 'Overwrite existing files')
    .action(async (opts, cmd) => {
      const globalOpts = cmd.optsWithGlobals();

      const result = await runInit({
        name: opts.name,
        force: opts.force,
      });

      if (!result.ok) {
        logger.error(result.error.message);
        process.exit(result.error.exitCode);
      }

      if (!globalOpts.quiet) {
        logger.success('Project initialized!');
        logger.info('Created files:');
        for (const file of result.value.filesCreated) {
          console.log(`  - ${file}`);
        }
        console.log('\nNext steps:');
        console.log('  1. Review harness.config.json');
        console.log('  2. Update AGENTS.md with your project structure');
        console.log('  3. Run "harness validate" to check your setup');
      }

      process.exit(ExitCode.SUCCESS);
    });

  return command;
}
```

- [ ] **Step 5: Register command**

Update `packages/cli/src/index.ts`:

```typescript
import { createInitCommand } from './commands/init';
// ... in createProgram():
program.addCommand(createInitCommand());
```

- [ ] **Step 6: Run test to verify it passes**

Run: `cd packages/cli && pnpm test -- tests/commands/init.test.ts`
Expected: PASS

- [ ] **Step 7: Commit**

```bash
git add packages/cli/src/commands/init.ts packages/cli/src/templates/basic.ts packages/cli/tests/commands/init.test.ts packages/cli/src/index.ts
git commit -m "feat(cli): implement init command for project scaffolding"
```

---

### Task 10: Implement `harness cleanup` Command

**Files:**

- Create: `packages/cli/src/commands/cleanup.ts`
- Create: `packages/cli/tests/commands/cleanup.test.ts`

- [ ] **Step 1: Write cleanup test**

```typescript
// packages/cli/tests/commands/cleanup.test.ts
import { describe, it, expect } from 'vitest';
import { createCleanupCommand, runCleanup } from '../../src/commands/cleanup';
import * as path from 'path';

describe('cleanup command', () => {
  const validProjectPath = path.join(__dirname, '../fixtures/valid-project');

  describe('runCleanup', () => {
    it('returns entropy report', async () => {
      const result = await runCleanup({
        cwd: validProjectPath,
        configPath: path.join(validProjectPath, 'harness.config.json'),
      });
      expect(result.ok).toBe(true);
      if (result.ok) {
        expect(result.value).toHaveProperty('driftIssues');
        expect(result.value).toHaveProperty('deadCode');
      }
    });

    it('can filter by type', async () => {
      const result = await runCleanup({
        cwd: validProjectPath,
        configPath: path.join(validProjectPath, 'harness.config.json'),
        type: 'drift',
      });
      expect(result.ok).toBe(true);
    });
  });

  describe('createCleanupCommand', () => {
    it('creates command with correct name', () => {
      const cmd = createCleanupCommand();
      expect(cmd.name()).toBe('cleanup');
    });
  });
});
```

- [ ] **Step 2: Run test to verify it fails**

Run: `cd packages/cli && pnpm test -- tests/commands/cleanup.test.ts`
Expected: FAIL - cannot find module

- [ ] **Step 3: Write cleanup implementation**

```typescript
// packages/cli/src/commands/cleanup.ts
import { Command } from 'commander';
import * as path from 'path';
import type { Result } from '@harness-engineering/types';
import { Ok, Err } from '@harness-engineering/core';
import {
  EntropyAnalyzer,
  detectDocDrift,
  detectDeadCode,
  TypeScriptParser,
} from '@harness-engineering/core';
import { resolveConfig } from '../config/loader';
import { OutputFormatter, OutputMode, type OutputModeType } from '../output/formatter';
import { logger } from '../output/logger';
import { CLIError, ExitCode } from '../utils/errors';

type CleanupType = 'drift' | 'dead-code' | 'patterns' | 'all';

interface CleanupOptions {
  cwd?: string;
  configPath?: string;
  type?: CleanupType;
  json?: boolean;
  verbose?: boolean;
  quiet?: boolean;
}

interface CleanupResult {
  driftIssues: Array<{ file: string; issue: string }>;
  deadCode: Array<{ file: string; symbol?: string }>;
  patternViolations: Array<{ file: string; pattern: string; message: string }>;
  totalIssues: number;
}

export async function runCleanup(
  options: CleanupOptions
): Promise<Result<CleanupResult, CLIError>> {
  const cwd = options.cwd ?? process.cwd();
  const type = options.type ?? 'all';

  // Load config
  const configResult = resolveConfig(options.configPath);
  if (!configResult.ok) {
    return configResult;
  }
  const config = configResult.value;

  const result: CleanupResult = {
    driftIssues: [],
    deadCode: [],
    patternViolations: [],
    totalIssues: 0,
  };

  const parser = new TypeScriptParser();
  const docsDir = path.resolve(cwd, config.docsDir);
  const rootDir = path.resolve(cwd, config.rootDir);

  // Check doc drift
  if (type === 'all' || type === 'drift') {
    const driftResult = await detectDocDrift({
      docsDir,
      codeDir: rootDir,
      parser,
    });
    if (driftResult.ok) {
      result.driftIssues = driftResult.value.drifts.map((d) => ({
        file: d.file,
        issue: `${d.issue}: ${d.details}`,
      }));
    }
  }

  // Check dead code
  if (type === 'all' || type === 'dead-code') {
    const deadResult = await detectDeadCode({
      entryPoints: [path.join(rootDir, 'src/index.ts')],
      rootDir,
      parser,
    });
    if (deadResult.ok) {
      result.deadCode = [
        ...deadResult.value.unusedFiles.map((f) => ({ file: f })),
        ...deadResult.value.unusedExports.map((e) => ({ file: e.file, symbol: e.name })),
      ];
    }
  }

  result.totalIssues =
    result.driftIssues.length + result.deadCode.length + result.patternViolations.length;

  return Ok(result);
}

export function createCleanupCommand(): Command {
  const command = new Command('cleanup')
    .description('Detect entropy issues (doc drift, dead code, patterns)')
    .option('-t, --type <type>', 'Issue type: drift, dead-code, patterns, all', 'all')
    .action(async (opts, cmd) => {
      const globalOpts = cmd.optsWithGlobals();
      const mode: OutputModeType = globalOpts.json
        ? OutputMode.JSON
        : globalOpts.quiet
          ? OutputMode.QUIET
          : globalOpts.verbose
            ? OutputMode.VERBOSE
            : OutputMode.TEXT;

      const formatter = new OutputFormatter(mode);

      const result = await runCleanup({
        configPath: globalOpts.config,
        type: opts.type as CleanupType,
        json: globalOpts.json,
        verbose: globalOpts.verbose,
        quiet: globalOpts.quiet,
      });

      if (!result.ok) {
        if (mode === OutputMode.JSON) {
          console.log(JSON.stringify({ error: result.error.message }));
        } else {
          logger.error(result.error.message);
        }
        process.exit(result.error.exitCode);
      }

      if (mode === OutputMode.JSON) {
        console.log(JSON.stringify(result.value, null, 2));
      } else if (mode !== OutputMode.QUIET || result.value.totalIssues > 0) {
        console.log(
          formatter.formatSummary(
            'Entropy issues',
            result.value.totalIssues.toString(),
            result.value.totalIssues === 0
          )
        );

        if (result.value.driftIssues.length > 0) {
          console.log('\nDocumentation drift:');
          for (const issue of result.value.driftIssues) {
            console.log(`  - ${issue.file}: ${issue.issue}`);
          }
        }

        if (result.value.deadCode.length > 0) {
          console.log('\nDead code:');
          for (const item of result.value.deadCode.slice(0, 10)) {
            console.log(`  - ${item.file}${item.symbol ? `: ${item.symbol}` : ''}`);
          }
          if (result.value.deadCode.length > 10) {
            console.log(`  ... and ${result.value.deadCode.length - 10} more`);
          }
        }
      }

      process.exit(result.value.totalIssues === 0 ? ExitCode.SUCCESS : ExitCode.VALIDATION_FAILED);
    });

  return command;
}
```

- [ ] **Step 4: Register command**

Update `packages/cli/src/index.ts`:

```typescript
import { createCleanupCommand } from './commands/cleanup';
// ... in createProgram():
program.addCommand(createCleanupCommand());
```

- [ ] **Step 5: Run test to verify it passes**

Run: `cd packages/cli && pnpm test -- tests/commands/cleanup.test.ts`
Expected: PASS

- [ ] **Step 6: Commit**

```bash
git add packages/cli/src/commands/cleanup.ts packages/cli/tests/commands/cleanup.test.ts packages/cli/src/index.ts
git commit -m "feat(cli): implement cleanup command for entropy detection"
```

---

### Task 11: Implement `harness fix-drift` Command

**Files:**

- Create: `packages/cli/src/commands/fix-drift.ts`
- Create: `packages/cli/tests/commands/fix-drift.test.ts`

- [ ] **Step 1: Write fix-drift test**

```typescript
// packages/cli/tests/commands/fix-drift.test.ts
import { describe, it, expect } from 'vitest';
import { createFixDriftCommand, runFixDrift } from '../../src/commands/fix-drift';
import * as path from 'path';

describe('fix-drift command', () => {
  const validProjectPath = path.join(__dirname, '../fixtures/valid-project');

  describe('runFixDrift', () => {
    it('runs in dry-run mode by default', async () => {
      const result = await runFixDrift({
        cwd: validProjectPath,
        configPath: path.join(validProjectPath, 'harness.config.json'),
      });
      expect(result.ok).toBe(true);
      if (result.ok) {
        expect(result.value.dryRun).toBe(true);
      }
    });
  });

  describe('createFixDriftCommand', () => {
    it('creates command with correct name', () => {
      const cmd = createFixDriftCommand();
      expect(cmd.name()).toBe('fix-drift');
    });
  });
});
```

- [ ] **Step 2: Run test to verify it fails**

Run: `cd packages/cli && pnpm test -- tests/commands/fix-drift.test.ts`
Expected: FAIL - cannot find module

- [ ] **Step 3: Write fix-drift implementation**

```typescript
// packages/cli/src/commands/fix-drift.ts
import { Command } from 'commander';
import * as path from 'path';
import type { Result } from '@harness-engineering/types';
import { Ok, Err } from '@harness-engineering/core';
import {
  detectDocDrift,
  createFixes,
  applyFixes,
  generateSuggestions,
  TypeScriptParser,
} from '@harness-engineering/core';
import { resolveConfig } from '../config/loader';
import { OutputFormatter, OutputMode, type OutputModeType } from '../output/formatter';
import { logger } from '../output/logger';
import { CLIError, ExitCode } from '../utils/errors';

interface FixDriftOptions {
  cwd?: string;
  configPath?: string;
  dryRun?: boolean;
  json?: boolean;
  verbose?: boolean;
  quiet?: boolean;
}

interface FixDriftResult {
  dryRun: boolean;
  fixes: Array<{
    file: string;
    action: string;
    applied: boolean;
  }>;
  suggestions: Array<{
    file: string;
    suggestion: string;
  }>;
}

export async function runFixDrift(
  options: FixDriftOptions
): Promise<Result<FixDriftResult, CLIError>> {
  const cwd = options.cwd ?? process.cwd();
  const dryRun = options.dryRun ?? true;

  // Load config
  const configResult = resolveConfig(options.configPath);
  if (!configResult.ok) {
    return configResult;
  }
  const config = configResult.value;

  const parser = new TypeScriptParser();
  const docsDir = path.resolve(cwd, config.docsDir);
  const rootDir = path.resolve(cwd, config.rootDir);

  const result: FixDriftResult = {
    dryRun,
    fixes: [],
    suggestions: [],
  };

  // Detect drift
  const driftResult = await detectDocDrift({
    docsDir,
    codeDir: rootDir,
    parser,
  });

  if (!driftResult.ok) {
    return Ok(result); // No drift to fix
  }

  // Generate fixes
  const fixes = createFixes(driftResult.value, {
    dryRun,
    fixTypes: ['unused-imports', 'format-drift'],
  });

  if (fixes.ok) {
    for (const fix of fixes.value) {
      result.fixes.push({
        file: fix.file,
        action: fix.description,
        applied: !dryRun,
      });
    }

    // Apply fixes if not dry run
    if (!dryRun && fixes.value.length > 0) {
      await applyFixes(fixes.value);
    }
  }

  // Generate suggestions for issues that can't be auto-fixed
  const suggestions = generateSuggestions(driftResult.value);
  if (suggestions.ok) {
    result.suggestions = suggestions.value.suggestions.map((s) => ({
      file: s.file ?? 'general',
      suggestion: s.message,
    }));
  }

  return Ok(result);
}

export function createFixDriftCommand(): Command {
  const command = new Command('fix-drift')
    .description('Auto-fix entropy issues where possible')
    .option('--no-dry-run', 'Actually apply fixes (default is dry-run)')
    .action(async (opts, cmd) => {
      const globalOpts = cmd.optsWithGlobals();
      const mode: OutputModeType = globalOpts.json
        ? OutputMode.JSON
        : globalOpts.quiet
          ? OutputMode.QUIET
          : globalOpts.verbose
            ? OutputMode.VERBOSE
            : OutputMode.TEXT;

      const dryRun = opts.dryRun !== false;

      const result = await runFixDrift({
        configPath: globalOpts.config,
        dryRun,
        json: globalOpts.json,
        verbose: globalOpts.verbose,
        quiet: globalOpts.quiet,
      });

      if (!result.ok) {
        if (mode === OutputMode.JSON) {
          console.log(JSON.stringify({ error: result.error.message }));
        } else {
          logger.error(result.error.message);
        }
        process.exit(result.error.exitCode);
      }

      if (mode === OutputMode.JSON) {
        console.log(JSON.stringify(result.value, null, 2));
      } else if (mode !== OutputMode.QUIET) {
        if (result.value.dryRun) {
          logger.info('Dry run - no changes applied');
        }

        if (result.value.fixes.length > 0) {
          console.log('\nFixes:');
          for (const fix of result.value.fixes) {
            const status = fix.applied ? '✓' : '○';
            console.log(`  ${status} ${fix.file}: ${fix.action}`);
          }
        }

        if (result.value.suggestions.length > 0) {
          console.log('\nSuggestions (manual action required):');
          for (const suggestion of result.value.suggestions) {
            console.log(`  → ${suggestion.file}: ${suggestion.suggestion}`);
          }
        }

        if (result.value.fixes.length === 0 && result.value.suggestions.length === 0) {
          logger.success('No drift detected');
        }
      }

      process.exit(ExitCode.SUCCESS);
    });

  return command;
}
```

- [ ] **Step 4: Register command**

Update `packages/cli/src/index.ts`:

```typescript
import { createFixDriftCommand } from './commands/fix-drift';
// ... in createProgram():
program.addCommand(createFixDriftCommand());
```

- [ ] **Step 5: Run test to verify it passes**

Run: `cd packages/cli && pnpm test -- tests/commands/fix-drift.test.ts`
Expected: PASS

- [ ] **Step 6: Commit**

```bash
git add packages/cli/src/commands/fix-drift.ts packages/cli/tests/commands/fix-drift.test.ts packages/cli/src/index.ts
git commit -m "feat(cli): implement fix-drift command for auto-fixing entropy"
```

---

---

## Chunk 4: Agent Commands & Integration

### Task 12: Implement `harness agent` Subcommands

**Files:**

- Create: `packages/cli/src/commands/agent/index.ts`
- Create: `packages/cli/src/commands/agent/run.ts`
- Create: `packages/cli/src/commands/agent/review.ts`
- Create: `packages/cli/tests/commands/agent.test.ts`

- [ ] **Step 1: Write agent tests**

```typescript
// packages/cli/tests/commands/agent.test.ts
import { describe, it, expect } from 'vitest';
import { createAgentCommand } from '../../src/commands/agent';

describe('agent command', () => {
  describe('createAgentCommand', () => {
    it('creates command with subcommands', () => {
      const cmd = createAgentCommand();
      expect(cmd.name()).toBe('agent');
      expect(cmd.commands.map((c) => c.name())).toContain('run');
      expect(cmd.commands.map((c) => c.name())).toContain('review');
    });
  });
});
```

- [ ] **Step 2: Run test to verify it fails**

Run: `cd packages/cli && pnpm test -- tests/commands/agent.test.ts`
Expected: FAIL - cannot find module

- [ ] **Step 3: Create agent run command**

```typescript
// packages/cli/src/commands/agent/run.ts
import { Command } from 'commander';
import type { Result } from '@harness-engineering/types';
import { Ok, Err } from '@harness-engineering/core';
import { requestPeerReview, type AgentType } from '@harness-engineering/core';
import { resolveConfig } from '../../config/loader';
import { logger } from '../../output/logger';
import { CLIError, ExitCode } from '../../utils/errors';

interface RunOptions {
  configPath?: string;
  timeout?: number;
}

export async function runAgentTask(
  task: string,
  options: RunOptions
): Promise<Result<{ success: boolean; output: string }, CLIError>> {
  const configResult = resolveConfig(options.configPath);
  if (!configResult.ok) {
    return configResult;
  }

  // Map task to agent type
  const agentTypeMap: Record<string, AgentType> = {
    review: 'architecture-enforcer',
    'doc-review': 'documentation-maintainer',
    'test-review': 'test-reviewer',
  };

  const agentType = agentTypeMap[task];
  if (!agentType) {
    return Err(
      new CLIError(
        `Unknown task: ${task}. Available: ${Object.keys(agentTypeMap).join(', ')}`,
        ExitCode.ERROR
      )
    );
  }

  const config = configResult.value;
  const timeout = options.timeout ?? config.agent?.timeout ?? 300000;

  // Request peer review using core library's executor
  const reviewResult = await requestPeerReview(agentType, {
    files: [],
    diff: '',
    commitMessage: task,
    metadata: { task, timeout },
  });

  if (!reviewResult.ok) {
    return Err(new CLIError(`Agent task failed: ${reviewResult.error.message}`, ExitCode.ERROR));
  }

  const review = reviewResult.value;
  return Ok({
    success: review.approved,
    output: review.approved
      ? `Agent task '${task}' completed successfully`
      : `Agent task '${task}' found issues:\n${review.comments.map((c) => `  - ${c.message}`).join('\n')}`,
  });
}

export function createRunCommand(): Command {
  return new Command('run')
    .description('Run an agent task')
    .argument('<task>', 'Task to run (review, doc-review, test-review)')
    .option('--timeout <ms>', 'Timeout in milliseconds', '300000')
    .action(async (task, opts, cmd) => {
      const globalOpts = cmd.optsWithGlobals();

      const result = await runAgentTask(task, {
        configPath: globalOpts.config,
        timeout: parseInt(opts.timeout, 10),
      });

      if (!result.ok) {
        logger.error(result.error.message);
        process.exit(result.error.exitCode);
      }

      logger.success(result.value.output);
      process.exit(ExitCode.SUCCESS);
    });
}
```

- [ ] **Step 4: Create agent review command**

```typescript
// packages/cli/src/commands/agent/review.ts
import { Command } from 'commander';
import * as path from 'path';
import { execSync } from 'child_process';
import type { Result } from '@harness-engineering/types';
import { Ok, Err } from '@harness-engineering/core';
import { createSelfReview, parseDiff } from '@harness-engineering/core';
import { resolveConfig } from '../../config/loader';
import { OutputMode, type OutputModeType } from '../../output/formatter';
import { logger } from '../../output/logger';
import { CLIError, ExitCode } from '../../utils/errors';

interface ReviewOptions {
  configPath?: string;
  json?: boolean;
  verbose?: boolean;
  quiet?: boolean;
}

export async function runAgentReview(options: ReviewOptions): Promise<
  Result<
    {
      passed: boolean;
      checklist: Array<{ check: string; passed: boolean; details?: string }>;
    },
    CLIError
  >
> {
  const configResult = resolveConfig(options.configPath);
  if (!configResult.ok) {
    return configResult;
  }

  // Get git diff
  let diff: string;
  try {
    diff = execSync('git diff --cached', { encoding: 'utf-8' });
    if (!diff) {
      diff = execSync('git diff', { encoding: 'utf-8' });
    }
  } catch {
    return Err(new CLIError('Failed to get git diff', ExitCode.ERROR));
  }

  if (!diff) {
    return Ok({
      passed: true,
      checklist: [{ check: 'No changes to review', passed: true }],
    });
  }

  // Parse diff and create self-review
  const parsedDiff = parseDiff(diff);
  const review = createSelfReview({
    files: parsedDiff.files.map((f) => ({
      path: f.path,
      additions: f.additions,
      deletions: f.deletions,
      hunks: f.hunks,
    })),
    diff,
    commitMessage: '',
  });

  if (!review.ok) {
    return Err(new CLIError(review.error.message, ExitCode.ERROR));
  }

  return Ok({
    passed: review.value.failCount === 0,
    checklist: review.value.items.map((item) => ({
      check: item.check,
      passed: item.passed,
      details: item.details,
    })),
  });
}

export function createReviewCommand(): Command {
  return new Command('review')
    .description('Run self-review on current changes')
    .action(async (opts, cmd) => {
      const globalOpts = cmd.optsWithGlobals();
      const mode: OutputModeType = globalOpts.json
        ? OutputMode.JSON
        : globalOpts.quiet
          ? OutputMode.QUIET
          : OutputMode.TEXT;

      const result = await runAgentReview({
        configPath: globalOpts.config,
        json: globalOpts.json,
        verbose: globalOpts.verbose,
        quiet: globalOpts.quiet,
      });

      if (!result.ok) {
        if (mode === OutputMode.JSON) {
          console.log(JSON.stringify({ error: result.error.message }));
        } else {
          logger.error(result.error.message);
        }
        process.exit(result.error.exitCode);
      }

      if (mode === OutputMode.JSON) {
        console.log(JSON.stringify(result.value, null, 2));
      } else if (mode !== OutputMode.QUIET) {
        console.log(result.value.passed ? '✓ Self-review passed' : '✗ Self-review found issues');
        console.log('');

        for (const item of result.value.checklist) {
          const icon = item.passed ? '✓' : '✗';
          console.log(`  ${icon} ${item.check}`);
          if (item.details && !item.passed) {
            console.log(`    ${item.details}`);
          }
        }
      }

      process.exit(result.value.passed ? ExitCode.SUCCESS : ExitCode.VALIDATION_FAILED);
    });
}
```

- [ ] **Step 5: Create agent index**

```typescript
// packages/cli/src/commands/agent/index.ts
import { Command } from 'commander';
import { createRunCommand } from './run';
import { createReviewCommand } from './review';

export function createAgentCommand(): Command {
  const command = new Command('agent').description('Agent orchestration commands');

  command.addCommand(createRunCommand());
  command.addCommand(createReviewCommand());

  return command;
}
```

- [ ] **Step 6: Register command**

Update `packages/cli/src/index.ts`:

```typescript
import { createAgentCommand } from './commands/agent';
// ... in createProgram():
program.addCommand(createAgentCommand());
```

- [ ] **Step 7: Run test to verify it passes**

Run: `cd packages/cli && pnpm test -- tests/commands/agent.test.ts`
Expected: PASS

- [ ] **Step 8: Commit**

```bash
git add packages/cli/src/commands/agent/ packages/cli/tests/commands/agent.test.ts packages/cli/src/index.ts
git commit -m "feat(cli): implement agent subcommands for task execution and self-review"
```

---

### Task 13: Implement `harness add` Command

**Files:**

- Create: `packages/cli/src/commands/add.ts`
- Create: `packages/cli/tests/commands/add.test.ts`

- [ ] **Step 1: Write add test**

```typescript
// packages/cli/tests/commands/add.test.ts
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { createAddCommand, runAdd } from '../../src/commands/add';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';

describe('add command', () => {
  let tempDir: string;

  beforeEach(() => {
    tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-test-'));
    // Create basic structure
    fs.writeFileSync(
      path.join(tempDir, 'harness.config.json'),
      JSON.stringify({ version: 1, name: 'test' })
    );
    fs.mkdirSync(path.join(tempDir, 'src'), { recursive: true });
  });

  afterEach(() => {
    fs.rmSync(tempDir, { recursive: true, force: true });
  });

  describe('runAdd', () => {
    it('creates layer directory structure', async () => {
      const result = await runAdd('layer', 'services', { cwd: tempDir });
      expect(result.ok).toBe(true);
      expect(fs.existsSync(path.join(tempDir, 'src/services'))).toBe(true);
    });
  });

  describe('createAddCommand', () => {
    it('creates command with correct name', () => {
      const cmd = createAddCommand();
      expect(cmd.name()).toBe('add');
    });
  });
});
```

- [ ] **Step 2: Run test to verify it fails**

Run: `cd packages/cli && pnpm test -- tests/commands/add.test.ts`
Expected: FAIL - cannot find module

- [ ] **Step 3: Write add implementation**

```typescript
// packages/cli/src/commands/add.ts
import { Command } from 'commander';
import * as fs from 'fs';
import * as path from 'path';
import type { Result } from '@harness-engineering/types';
import { Ok, Err } from '@harness-engineering/core';
import { resolveConfig } from '../config/loader';
import { logger } from '../output/logger';
import { CLIError, ExitCode } from '../utils/errors';

type ComponentType = 'layer' | 'module' | 'doc';

interface AddOptions {
  cwd?: string;
  configPath?: string;
}

interface AddResult {
  created: string[];
}

const LAYER_INDEX_TEMPLATE = (name: string) => `// ${name} layer
// Add your ${name} exports here

export {};
`;

const MODULE_TEMPLATE = (name: string) => `/**
 * ${name} module
 */

export function ${name}() {
  // TODO: Implement
}
`;

const DOC_TEMPLATE = (name: string) => `# ${name}

## Overview

TODO: Add overview

## Usage

TODO: Add usage examples
`;

export async function runAdd(
  componentType: ComponentType,
  name: string,
  options: AddOptions
): Promise<Result<AddResult, CLIError>> {
  const cwd = options.cwd ?? process.cwd();

  const configResult = resolveConfig(options.configPath);
  if (!configResult.ok) {
    // Allow adding without config for basic scaffolding
  }

  const created: string[] = [];

  try {
    switch (componentType) {
      case 'layer': {
        const layerDir = path.join(cwd, 'src', name);
        if (!fs.existsSync(layerDir)) {
          fs.mkdirSync(layerDir, { recursive: true });
          created.push(`src/${name}/`);
        }
        const indexPath = path.join(layerDir, 'index.ts');
        if (!fs.existsSync(indexPath)) {
          fs.writeFileSync(indexPath, LAYER_INDEX_TEMPLATE(name));
          created.push(`src/${name}/index.ts`);
        }
        break;
      }

      case 'module': {
        const modulePath = path.join(cwd, 'src', `${name}.ts`);
        if (fs.existsSync(modulePath)) {
          return Err(new CLIError(`Module ${name} already exists`, ExitCode.ERROR));
        }
        fs.writeFileSync(modulePath, MODULE_TEMPLATE(name));
        created.push(`src/${name}.ts`);
        break;
      }

      case 'doc': {
        const docsDir = path.join(cwd, 'docs');
        if (!fs.existsSync(docsDir)) {
          fs.mkdirSync(docsDir, { recursive: true });
        }
        const docPath = path.join(docsDir, `${name}.md`);
        if (fs.existsSync(docPath)) {
          return Err(new CLIError(`Doc ${name} already exists`, ExitCode.ERROR));
        }
        fs.writeFileSync(docPath, DOC_TEMPLATE(name));
        created.push(`docs/${name}.md`);
        break;
      }

      default:
        return Err(
          new CLIError(
            `Unknown component type: ${componentType}. Use: layer, module, doc`,
            ExitCode.ERROR
          )
        );
    }

    return Ok({ created });
  } catch (error) {
    return Err(
      new CLIError(
        `Failed to add ${componentType}: ${error instanceof Error ? error.message : 'Unknown error'}`,
        ExitCode.ERROR
      )
    );
  }
}

export function createAddCommand(): Command {
  const command = new Command('add')
    .description('Add a component to the project')
    .argument('<type>', 'Component type (layer, module, doc)')
    .argument('<name>', 'Component name')
    .action(async (type, name, opts, cmd) => {
      const globalOpts = cmd.optsWithGlobals();

      const result = await runAdd(type as ComponentType, name, {
        configPath: globalOpts.config,
      });

      if (!result.ok) {
        logger.error(result.error.message);
        process.exit(result.error.exitCode);
      }

      if (!globalOpts.quiet) {
        logger.success(`Added ${type}: ${name}`);
        for (const file of result.value.created) {
          console.log(`  + ${file}`);
        }
      }

      process.exit(ExitCode.SUCCESS);
    });

  return command;
}
```

- [ ] **Step 4: Register command**

Update `packages/cli/src/index.ts`:

```typescript
import { createAddCommand } from './commands/add';
// ... in createProgram():
program.addCommand(createAddCommand());
```

- [ ] **Step 5: Run test to verify it passes**

Run: `cd packages/cli && pnpm test -- tests/commands/add.test.ts`
Expected: PASS

- [ ] **Step 6: Commit**

```bash
git add packages/cli/src/commands/add.ts packages/cli/tests/commands/add.test.ts packages/cli/src/index.ts
git commit -m "feat(cli): implement add command for component scaffolding"
```

---

### Task 14: Integration Tests & Final Polish

**Files:**

- Create: `packages/cli/tests/integration/cli.test.ts`
- Update: `packages/cli/README.md`

- [ ] **Step 1: Write integration tests**

```typescript
// packages/cli/tests/integration/cli.test.ts
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { spawnSync } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';

const CLI_PATH = path.join(__dirname, '../../dist/bin/harness.js');

function runCLI(args: string[], cwd?: string) {
  return spawnSync('node', [CLI_PATH, ...args], {
    cwd,
    encoding: 'utf-8',
  });
}

describe('CLI Integration', () => {
  let tempDir: string;

  beforeEach(() => {
    tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-cli-test-'));
  });

  afterEach(() => {
    fs.rmSync(tempDir, { recursive: true, force: true });
  });

  describe('harness --version', () => {
    it('outputs version', () => {
      const result = runCLI(['--version']);
      expect(result.status).toBe(0);
      expect(result.stdout).toMatch(/\d+\.\d+\.\d+/);
    });
  });

  describe('harness --help', () => {
    it('outputs help', () => {
      const result = runCLI(['--help']);
      expect(result.status).toBe(0);
      expect(result.stdout).toContain('harness');
      expect(result.stdout).toContain('validate');
    });
  });

  describe('harness init', () => {
    it('creates config files', () => {
      const result = runCLI(['init', '--name', 'test-project'], tempDir);
      expect(result.status).toBe(0);
      expect(fs.existsSync(path.join(tempDir, 'harness.config.json'))).toBe(true);
      expect(fs.existsSync(path.join(tempDir, 'AGENTS.md'))).toBe(true);
    });
  });

  describe('harness validate', () => {
    it('validates initialized project', () => {
      // Initialize first
      runCLI(['init', '--name', 'test'], tempDir);

      // Then validate
      const result = runCLI(['validate'], tempDir);
      expect(result.status).toBe(0);
    });

    it('outputs JSON when --json flag used', () => {
      runCLI(['init', '--name', 'test'], tempDir);
      const result = runCLI(['validate', '--json'], tempDir);
      expect(result.status).toBe(0);
      const output = JSON.parse(result.stdout);
      expect(output).toHaveProperty('valid');
    });
  });

  describe('harness add', () => {
    it('adds layer to project', () => {
      runCLI(['init', '--name', 'test'], tempDir);
      fs.mkdirSync(path.join(tempDir, 'src'), { recursive: true });

      const result = runCLI(['add', 'layer', 'services'], tempDir);
      expect(result.status).toBe(0);
      expect(fs.existsSync(path.join(tempDir, 'src/services/index.ts'))).toBe(true);
    });
  });
});
```

- [ ] **Step 2: Build CLI before running integration tests**

Run: `cd packages/cli && pnpm build`
Expected: Build succeeds

- [ ] **Step 3: Run integration tests**

Run: `cd packages/cli && pnpm test -- tests/integration/cli.test.ts`
Expected: PASS

- [ ] **Step 4: Create README**

````markdown
<!-- packages/cli/README.md -->

# @harness-engineering/cli

CLI for Harness Engineering toolkit.

## Installation

```bash
npm install -g @harness-engineering/cli
```
````

## Commands

### `harness init`

Initialize a new harness-engineering project.

```bash
harness init
harness init --name my-project
```

### `harness validate`

Run all validation checks.

```bash
harness validate
harness validate --json
harness validate --verbose
```

### `harness check-deps`

Validate dependency layers and detect circular dependencies.

```bash
harness check-deps
```

### `harness check-docs`

Check documentation coverage.

```bash
harness check-docs
harness check-docs --min-coverage 90
```

### `harness cleanup`

Detect entropy issues.

```bash
harness cleanup
harness cleanup --type drift
harness cleanup --type dead-code
```

### `harness fix-drift`

Auto-fix entropy issues.

```bash
harness fix-drift           # Dry run
harness fix-drift --no-dry-run  # Apply fixes
```

### `harness add`

Add components to the project.

```bash
harness add layer services
harness add module user
harness add doc architecture
```

### `harness agent`

Agent orchestration commands.

```bash
harness agent run review
harness agent review
```

## Global Options

- `--config <path>` - Path to config file
- `--json` - Output as JSON
- `--verbose` - Verbose output
- `--quiet` - Minimal output

## Configuration

Create `harness.config.json` in your project root:

```json
{
  "version": 1,
  "name": "my-project",
  "layers": [
    { "name": "types", "pattern": "src/types/**", "allowedDependencies": [] },
    { "name": "domain", "pattern": "src/domain/**", "allowedDependencies": ["types"] }
  ],
  "agentsMapPath": "./AGENTS.md",
  "docsDir": "./docs"
}
```

## Exit Codes

- `0` - Success
- `1` - Validation failed
- `2` - Error

````

- [ ] **Step 5: Run all tests**

Run: `cd packages/cli && pnpm test`
Expected: All tests pass

- [ ] **Step 6: Check coverage**

Run: `cd packages/cli && pnpm test:coverage`
Expected: >80% coverage

- [ ] **Step 7: Commit**

```bash
git add packages/cli/tests/integration/cli.test.ts packages/cli/README.md
git commit -m "test(cli): add integration tests and documentation"
````

- [ ] **Step 8: Final commit - update package version**

```bash
cd packages/cli && npm version 1.0.0 --no-git-tag-version
git add packages/cli/package.json
git commit -m "chore(cli): release v1.0.0"
```

---

## Summary

This plan implements the complete `@harness-engineering/cli` package:

**Commands implemented:**

- `harness validate` - AGENTS.md and knowledge map validation
- `harness check-deps` - Layer and circular dependency validation
- `harness check-docs` - Documentation coverage checking
- `harness init` - Project scaffolding
- `harness add` - Component scaffolding
- `harness cleanup` - Entropy detection
- `harness fix-drift` - Auto-fixing entropy
- `harness agent run` - Agent task execution
- `harness agent review` - Self-review

**Features:**

- Config loading from `harness.config.json`
- Multiple output modes (JSON, text, quiet, verbose)
- Proper exit codes
- Integration with all `@harness-engineering/core` APIs

**Next:** After CLI is complete, proceed to ESLint plugin plan.
