diff --git a/.agents/notes/2026-02-21-outfitter-check.md b/.agents/notes/2026-02-21-outfitter-check.md new file mode 100644 index 0000000..82788cd --- /dev/null +++ b/.agents/notes/2026-02-21-outfitter-check.md @@ -0,0 +1,262 @@ +# Compliance Report: packages/ and apps/ + +**Date**: 2026-02-21 +**Scope**: `packages/` and `apps/` directories +**Status**: FAIL + +## Summary + +| Severity | Count | +|----------|-------| +| Critical | 118 | +| High | 7 | +| Medium | 6 | +| Low | 5 | + +--- + +## Critical + +### 1. Thrown Exceptions in Application Code (118 occurrences across 28 files) + +The codebase relies heavily on `throw new Error(...)` and `throw new CliError(...)` for control flow rather than returning `Result` types. This is the single largest compliance gap. + +**Breakdown by area:** + +| Area | Files | Throws | Worst Offenders | +|------|-------|--------|-----------------| +| `packages/cli/src/commands/` | 13 | 72 | `modify.ts` (15), `add.ts` (13), `init.ts` (7), `parsers.ts` (6) | +| `packages/cli/src/utils/` | 6 | 13 | `fs.ts` (4), `prompts.ts` (3), `properties.ts` (2) | +| `packages/cli/src/program.ts` | 1 | 8 | Top-level command handlers throw `CliError` | +| `packages/cli/src/skills/` | 2 | 13 | `parser.ts` (9), `manifest.ts` (4) | +| `packages/core/src/edit.ts` | 1 | 10 | Every validation path throws | +| `apps/mcp/src/` | 6 | 16 | `tools/add.ts` (9), `utils/filesystem.ts` (3) | + +**Key files with throw-heavy patterns:** + +| Location | Issue | +|----------|-------| +| `packages/cli/src/commands/modify.ts:89` | Custom `InteractiveCancelError extends Error` class (should use `CancelledError.create()`) | +| `packages/cli/src/commands/modify.ts:526-588` | 7 instances of `throw new InteractiveCancelError()` for prompt cancellation flow | +| `packages/cli/src/commands/add.ts:131-284` | 12 argument validation throws (should return `Result.err(ValidationError.create(...))`) | +| `packages/core/src/edit.ts:134-647` | 10 throws for file/waymark validation (entire module lacks Result returns) | +| `apps/mcp/src/tools/scan.ts:48` | `throw new Error()` after checking `configResult.isErr()` (has Result, then throws instead of propagating) | +| `packages/cli/src/utils/context.ts:28` | `throw createConfigError(result.error.message)` -- unwraps a Result error only to re-throw it | +| `packages/core/src/config.ts:250,280` | `throw result.error` -- extracts Result error and throws it in a config loader | +| `packages/cli/src/program.ts:281-835` | 8 `throw new CliError(...)` in command handlers | + +**Fix**: Convert `throw` to `Result.err()` returns. Use `createValidator()` from `@outfitter/contracts` for input validation instead of Zod `.parse()` (which throws). For the transition layer in `program.ts`, the existing `runCommand()` bridge pattern is acceptable -- but the throws should move out of the command modules themselves. See [patterns/conversion.md] and [patterns/results.md]. + +### 2. try/catch Control Flow (67 occurrences across 27 files) + +Most `try/catch` blocks wrap operations that should return `Result` instead. + +| Location | Issue | +|----------|-------| +| `packages/cli/src/program.ts` | 8 try/catch blocks catching CliError from sub-commands | +| `packages/cli/src/commands/register.ts` | 18 try/catch blocks in command registration | +| `packages/cli/src/commands/doctor.ts` | 7 try/catch blocks for health checks | +| `packages/core/src/id-index.ts` | 3 try/catch blocks around SQLite operations | +| `packages/core/src/cache/index.ts` | 1 try/catch around database open (already partially migrated) | + +**Fix**: Replace `try/catch` with `Result.try()` or `Result.tryPromise()` wrappers. The `program.ts` top-level catch is partially acceptable as a transport boundary, but command-level catches should propagate Results instead. See [patterns/results.md]. + +### 3. Zod `.parse()` (Throws) Instead of `.safeParse()` or `createValidator()` (3 occurrences) + +| Location | Issue | +|----------|-------| +| `apps/mcp/src/tools/add.ts:92` | `addWaymarkInputSchema.parse(input)` -- throws ZodError on invalid input | +| `apps/mcp/src/tools/scan.ts:21` | `scanInputSchema.parse(input)` -- throws ZodError | +| `apps/mcp/src/tools/graph.ts:21` | `graphInputSchema.parse(input)` -- throws ZodError | + +**Fix**: Use `createValidator()` from `@outfitter/contracts` which wraps Zod validation into `Result`. See [patterns/handler.md]. + +--- + +## High + +### 1. Hardcoded Home Directory Paths (5 occurrences in 4 files) + +The codebase uses `homedir()` with manual `XDG_CONFIG_HOME`/`XDG_CACHE_HOME` fallback logic instead of using `@outfitter/config` functions (`getConfigDir`, `getCacheDir`). + +| Location | Issue | +|----------|-------| +| `packages/core/src/config.ts:270` | `join(homedir(), ".config")` -- manual XDG fallback | +| `packages/core/src/cache/index.ts:240` | `join(homedir(), ".cache")` -- manual XDG fallback | +| `packages/core/src/cache/index.ts:254` | `join(homedir(), ".cache")` -- duplicate manual fallback | +| `packages/cli/src/commands/init.ts:174` | `join(homedir(), ".config")` -- manual XDG fallback | +| `packages/cli/src/commands/doctor.ts:268,318` | `homedir()` for cache and home path resolution | + +**Fix**: Replace with `getConfigDir("waymark")` and `getCacheDir("waymark")` from `@outfitter/config` (already a dependency). These functions handle XDG resolution internally. See [patterns/file-ops.md]. + +### 2. Module-Level Logger Import (No Context Passing) (7 command files) + +All CLI command modules import a singleton `logger` rather than receiving it through handler context. + +| Location | Issue | +|----------|-------| +| `packages/cli/src/commands/update.ts:8` | `import { logger } from "../utils/logger.ts"` | +| `packages/cli/src/commands/modify.ts:18` | `import { logger } from "../utils/logger.ts"` | +| `packages/cli/src/commands/scan.ts:15` | `import { logger } from "../utils/logger"` | +| `packages/cli/src/commands/init.ts:11` | `import { logger } from "../utils/logger.ts"` | +| `packages/cli/src/commands/doctor.ts:10` | `import { logger } from "../utils/logger"` | +| `packages/cli/src/commands/add.ts:17` | `import { logger } from "../utils/logger.ts"` | +| `packages/cli/src/commands/remove.ts:18` | `import { logger } from "../utils/logger.ts"` | + +**Fix**: Add `logger` to `CommandContext` and pass `ctx.logger` through handler calls. The Outfitter handler pattern expects `ctx: HandlerContext` with `logger` as a property. This also enables per-request logger instances with trace correlation. See [patterns/handler.md] and [patterns/logging.md]. + +--- + +## Medium + +### 1. Custom Error Classes Not Using Taxonomy (2 classes) + +| Location | Issue | +|----------|-------| +| `packages/cli/src/errors.ts:5` | `CliError extends Error` -- custom error class with ad-hoc exit codes | +| `packages/cli/src/commands/modify.ts:89` | `InteractiveCancelError extends Error` -- should use `CancelledError` from contracts | + +**Fix**: Replace `CliError` with appropriate taxonomy errors (`ValidationError`, `InternalError`, etc.) and use the existing `mapErrorToExitCode()` in `command-runner.ts`. Replace `InteractiveCancelError` with `CancelledError.create("Interactive prompt cancelled")`. See [patterns/errors.md]. + +### 2. CommandContext Missing Handler Pattern Properties (1 type) + +| Location | Issue | +|----------|-------| +| `packages/cli/src/types.ts:16-20` | `CommandContext` has `config`, `globalOptions`, `workspaceRoot` but no `logger`, `signal`, or `requestId` | + +**Fix**: Align `CommandContext` with the Outfitter `HandlerContext` pattern by adding `logger`, `signal` (for cancellation), and optionally `requestId` for trace correlation. See [patterns/handler.md]. + +### 3. Core Functions Not Returning Result (4 functions) + +Key core operations return raw values and throw on error instead of returning `Result`: + +| Location | Issue | +|----------|-------| +| `packages/core/src/edit.ts:109` | `editWaymark()` returns `Promise` and throws | +| `packages/core/src/insert.ts:97` | `insertWaymarks()` returns `Promise` and throws | +| `packages/core/src/insert.ts:121` | `bulkInsert()` returns `Promise` and throws | +| `packages/core/src/remove.ts:143` | `removeWaymarks()` returns `Promise` with internal try/catch | + +**Fix**: Convert return types to `Result` using `Result.tryPromise()` wrappers. Internal validation should return `Result.err()` instead of throwing. See [patterns/results.md]. + +--- + +## Low + +### 1. `process.exit()` Without `exitWithError()` (4 non-script occurrences) + +| Location | Issue | +|----------|-------| +| `apps/mcp/src/index.ts:71` | `process.exit(1)` in top-level catch | +| `packages/cli/src/program.ts:186,189` | `process.exit()` in signal handlers (acceptable for SIGINT/SIGTERM) | +| `packages/cli/src/program.ts:1128,1256` | `process.exit(exitCode)` in CLI entrypoint (acceptable as transport boundary) | + +**Fix**: The CLI entrypoint usages are acceptable as the transport boundary. The MCP server could use a more structured shutdown. Low priority. + +### 2. Exit Code Duplication (2 systems) + +| Location | Issue | +|----------|-------| +| `packages/cli/src/exit-codes.ts` | `ExitCode` enum (0-4) for CLI | +| `packages/core/src/errors.ts:27-34` | `WAYMARK_EXIT_CODES` with different mapping | + +**Fix**: Consolidate to a single exit code source derived from the error taxonomy's `getExitCode()`. See [patterns/errors.md]. + +--- + +## Migration Guidance + +### Installed Versions + +| Package | Current | Location | +|---------|---------|----------| +| `@outfitter/cli` | 0.3.0 | packages/cli | +| `@outfitter/config` | 0.3.0 | packages/core | +| `@outfitter/contracts` | 0.2.0 | packages/cli, packages/core, apps/mcp | +| `@outfitter/logging` | 0.3.0 | packages/cli, apps/mcp | +| `@outfitter/mcp` | 0.3.0 | apps/mcp | + +### Updates Available + +| Package | Current | Available | Type | +|---------|---------|-----------|------| +| `@outfitter/cli` | 0.3.0 | 0.5.2 | BREAKING | +| `@outfitter/config` | 0.3.0 | 0.3.3 | non-breaking | +| `@outfitter/contracts` | 0.2.0 | 0.4.1 | BREAKING | +| `@outfitter/logging` | 0.3.0 | 0.4.1 | BREAKING | +| `@outfitter/mcp` | 0.3.0 | 0.4.2 | BREAKING | + +All five `@outfitter/*` packages have updates available. Four are breaking changes. Migration guides were not available from `outfitter upgrade --guide` -- check release notes for each package. + +--- + +## What's Going Well + +Before the recommendations, it is worth noting what has already been adopted successfully: + +1. **Result types in config and cache layers** -- `packages/core/src/config.ts` returns `Result` and the cache module (`cache/schema.ts`, `cache/queries.ts`, `cache/writes.ts`) consistently uses `Result` returns (90 `Result.ok/err/try/tryPromise` calls across 30 files). + +2. **@outfitter/contracts error re-exports** -- `packages/core/src/errors.ts` cleanly re-exports `ValidationError`, `NotFoundError`, `InternalError`, etc. from contracts. The `WaymarkError` and `WaymarkResult` type aliases are well-designed. + +3. **@outfitter/logging adoption** -- The CLI logger is properly built on `createLogger` and `createConsoleSink` from `@outfitter/logging` with `resolveLogLevel()` for environment-aware configuration. + +4. **@outfitter/cli theme integration** -- `ANSI` codes and `supportsColor()` from `@outfitter/cli/colors` and `@outfitter/cli/terminal` are in use. + +5. **@outfitter/mcp tool registration** -- The MCP server uses `createMcpServer()`, `defineTool()` with annotations, and properly wires `Result` handling for tool invocations. + +6. **@outfitter/config for config loading** -- `deepMerge()` and `parseConfigFile()` from `@outfitter/config` are used in core config resolution. + +7. **runCommand() bridge pattern** -- `packages/cli/src/utils/command-runner.ts` provides a clean `Result -> CliError` bridge for the transport boundary. + +--- + +## Recommendations + +Prioritized by impact and dependency order: + +### Phase 1: Foundation (blocks everything else) + +1. **Bump @outfitter/contracts 0.2.0 -> 0.4.1** -- This unlocks `createValidator()`, `.create()` factories, and `createContext()`. All downstream work depends on the latest contracts API. + +2. **Bump @outfitter/config 0.3.0 -> 0.3.3** -- Non-breaking update, do first as a quick win. + +### Phase 2: Core Error Model + +3. **Convert `packages/core/src/edit.ts` to Result returns** -- 10 throws in a single file. Convert `editWaymark()` signature to `Promise>` and replace all throws with `Result.err()`. + +4. **Convert `packages/core/src/insert.ts` and `remove.ts`** -- Same pattern as edit.ts. These three files form the core mutation API. + +5. **Replace `homedir()` calls with `@outfitter/config` functions** -- 5 occurrences across 4 files. Use `getConfigDir("waymark")` and `getCacheDir("waymark")`. + +### Phase 3: CLI Layer + +6. **Add `logger` to `CommandContext`** -- Small type change with large impact. Enables per-command logger context instead of module-level singleton. + +7. **Replace `CliError` with taxonomy errors** -- Eliminate the custom error class. Use `ValidationError`, `InternalError`, etc. with the existing `mapErrorToExitCode()`. + +8. **Convert CLI command modules to Result returns** -- Start with `scan.ts` and `lint.ts` (already partially Result-based), then `add.ts` and `modify.ts` (most throws). + +### Phase 4: MCP Layer + +9. **Replace Zod `.parse()` with `createValidator()`** -- 3 occurrences in MCP tools. Quick fix that adds proper `Result` wrapping. + +10. **Convert MCP tool handlers to eliminate throws** -- 16 throws across 6 files. The tool handler should return `Result.err()` instead of throwing. + +### Phase 5: Package Updates + +11. **Bump @outfitter/logging 0.3.0 -> 0.4.1** -- Breaking changes, do after logging context is wired through. + +12. **Bump @outfitter/cli 0.3.0 -> 0.5.2** -- Breaking changes, do after CLI error model is migrated. + +13. **Bump @outfitter/mcp 0.3.0 -> 0.4.2** -- Breaking changes, do after MCP handlers are Result-based. + +--- + +## Pass Criteria + +- [ ] 0 critical issues (currently 118 throws + 67 try/catch + 3 unsafe parse) +- [ ] 0 high issues (currently 5 hardcoded paths + 7 singleton logger imports) +- [ ] All handlers return `Result` +- [ ] No `throw` in application code (test files excluded) +- [ ] No `console.log` in production code (currently clean -- scripts only) +- [ ] No `homedir()` calls (use `@outfitter/config`) diff --git a/.outfitter/reports/upgrade.json b/.outfitter/reports/upgrade.json new file mode 100644 index 0000000..f6057d7 --- /dev/null +++ b/.outfitter/reports/upgrade.json @@ -0,0 +1,69 @@ +{ + "$schema": "https://outfitter.dev/reports/upgrade/v1", + "status": "cancelled", + "checkedAt": "2026-02-22T01:20:08.370Z", + "startedAt": "2026-02-22T01:20:07.798Z", + "finishedAt": "2026-02-22T01:20:08.370Z", + "cwd": "/Users/mg/Developer/outfitter/waymark", + "workspaceRoot": "/Users/mg/Developer/outfitter/waymark", + "flags": { + "dryRun": false, + "yes": false, + "interactive": true, + "all": false, + "noCodemods": false, + "outputMode": "human" + }, + "applied": false, + "summary": { + "total": 5, + "available": 5, + "breaking": 4, + "applied": 0 + }, + "packages": [ + { + "name": "@outfitter/cli", + "current": "0.3.0", + "latest": "0.5.2", + "updateAvailable": true, + "breaking": true + }, + { + "name": "@outfitter/config", + "current": "0.3.0", + "latest": "0.3.3", + "updateAvailable": true, + "breaking": false + }, + { + "name": "@outfitter/contracts", + "current": "0.2.0", + "latest": "0.4.1", + "updateAvailable": true, + "breaking": true + }, + { + "name": "@outfitter/logging", + "current": "0.3.0", + "latest": "0.4.1", + "updateAvailable": true, + "breaking": true + }, + { + "name": "@outfitter/mcp", + "current": "0.3.0", + "latest": "0.4.2", + "updateAvailable": true, + "breaking": true + } + ], + "excluded": { + "breaking": [ + "@outfitter/cli", + "@outfitter/contracts", + "@outfitter/logging", + "@outfitter/mcp" + ] + } +} \ No newline at end of file diff --git a/apps/mcp/package.json b/apps/mcp/package.json index 0af16bf..c5b31e6 100644 --- a/apps/mcp/package.json +++ b/apps/mcp/package.json @@ -20,10 +20,10 @@ "lint": "bunx ultracite check" }, "dependencies": { - "@outfitter/contracts": "0.1.0", - "@outfitter/logging": "0.1.0", + "@outfitter/contracts": "0.4.1", + "@outfitter/logging": "0.4.1", "@modelcontextprotocol/sdk": "^1.12.1", - "@outfitter/mcp": "0.1.0", + "@outfitter/mcp": "0.4.2", "@waymarks/core": "workspace:*", "zod": "^4.3.5" }, diff --git a/bun.lock b/bun.lock index 717c84a..1dbf242 100644 --- a/bun.lock +++ b/bun.lock @@ -28,9 +28,10 @@ "waymark-mcp": "dist/index.js", }, "dependencies": { - "@outfitter/contracts": "0.1.0", - "@outfitter/logging": "0.1.0", - "@outfitter/mcp": "0.1.0", + "@modelcontextprotocol/sdk": "^1.12.1", + "@outfitter/contracts": "0.4.1", + "@outfitter/logging": "0.4.1", + "@outfitter/mcp": "0.4.2", "@waymarks/core": "workspace:*", "zod": "^4.3.5", }, @@ -47,16 +48,18 @@ }, "dependencies": { "@bomb.sh/tab": "0.0.6", - "@outfitter/cli": "0.1.0", - "@outfitter/contracts": "0.1.0", - "@outfitter/logging": "0.1.0", + "@clack/prompts": "^1.0.1", + "@outfitter/cli": "0.5.2", + "@outfitter/contracts": "0.4.1", + "@outfitter/logging": "0.4.1", "@waymarks/core": "workspace:*", + "better-result": "^2.5.1", "commander": "14.0.1", "ignore": "7.0.5", "ora": "9.0.0", "simple-update-notifier": "2.0.0", "yaml": "2.8.1", - "zod": "4.1.12", + "zod": "^4.3.5", }, "devDependencies": { "@types/bun": "1.2.22", @@ -66,13 +69,13 @@ "name": "@waymarks/core", "version": "1.0.0-beta.1", "dependencies": { - "@outfitter/config": "0.1.0", - "@outfitter/contracts": "0.1.0", + "@outfitter/config": "0.3.3", + "@outfitter/contracts": "0.4.1", "@waymarks/grammar": "workspace:*", "safe-regex": "2.1.1", "type-fest": "^5.0.1", "yaml": "2.8.1", - "zod": "4.1.12", + "zod": "^4.3.5", }, "devDependencies": { "@types/bun": "1.2.22", @@ -108,9 +111,9 @@ "@bomb.sh/tab": ["@bomb.sh/tab@0.0.6", "", { "peerDependencies": { "cac": "^6.7.14", "citty": "^0.1.6", "commander": "^13.1.0" }, "optionalPeers": ["cac", "citty", "commander"], "bin": { "tab": "dist/bin/cli.js" } }, "sha512-EK9Ssulo5Ju/N8o9qV35MTA7F5210ehEoHvWzBxtiHrpUEmGk7NYeW0E8BH0scR6BbzdxjDQqHLK4PK/ifjREw=="], - "@clack/core": ["@clack/core@0.5.0", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow=="], + "@clack/core": ["@clack/core@1.0.1", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-WKeyK3NOBwDOzagPR5H08rFk9D/WuN705yEbuZvKqlkmoLM2woKtXb10OO2k1NoSU4SFG947i2/SCYh+2u5e4g=="], - "@clack/prompts": ["@clack/prompts@0.11.0", "", { "dependencies": { "@clack/core": "0.5.0", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw=="], + "@clack/prompts": ["@clack/prompts@1.0.1", "", { "dependencies": { "@clack/core": "1.0.1", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-/42G73JkuYdyWZ6m8d/CJtBrGl1Hegyc7Fy78m5Ob+jF85TOUmLR5XLce/U3LxYAw0kJ8CT5aI99RIvPHcGp/Q=="], "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.10", "", { "os": "aix", "cpu": "ppc64" }, "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw=="], @@ -178,17 +181,19 @@ "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], - "@outfitter/cli": ["@outfitter/cli@0.1.0", "", { "dependencies": { "@clack/prompts": "^0.11.0", "@outfitter/config": "0.1.0", "@outfitter/contracts": "0.1.0", "@outfitter/types": "0.1.0", "better-result": "^2.5.1", "commander": "^14.0.2" }, "peerDependencies": { "zod": "^4.3.5" } }, "sha512-xDEaViaFpadbR5dp/xbFyUPxTI+u56cw6QfP4Mht9PjR4ui2LmaI/0tKQFngIdxFXrK/Zn/ywjndJWtqe4xS7A=="], + "@outfitter/cli": ["@outfitter/cli@0.5.2", "", { "dependencies": { "@clack/prompts": "^1.0.1", "better-result": "^2.5.1", "commander": "^14.0.2" }, "peerDependencies": { "@outfitter/config": ">=0.3.0", "@outfitter/contracts": ">=0.2.0", "@outfitter/schema": ">=0.1.0", "@outfitter/types": ">=0.2.0", "zod": "^4.3.5" } }, "sha512-yw63qnBRcRuvabTIz3Zs+97v4AlulpxYRNEq4/LEWY+RCGLjWaI1qovcuIRr2PLy/AJkYPfF8ugxOTuojScuMg=="], + + "@outfitter/config": ["@outfitter/config@0.3.3", "", { "dependencies": { "@outfitter/contracts": "0.4.1", "@outfitter/types": "0.2.3", "smol-toml": "^1.6.0", "yaml": "^2.8.2", "zod": "^4.3.5" } }, "sha512-PS/dl7rj0ttrda7FjWe0M83rhi6dd6mA7BG0I33E0bD4KevBEO52Q9qMJHH4V3veAIJyXf8i+T/+lSUpQ4jL9w=="], - "@outfitter/config": ["@outfitter/config@0.1.0", "", { "dependencies": { "@outfitter/contracts": "0.1.0", "@outfitter/types": "0.1.0", "zod": "^4.3.5" } }, "sha512-S+sH0YGrKyezMO+qNTHfoWz4m3bpEas4nhr7Uh+VsW6AVWZFSSbSjoXY65YOSPg3UcxVNnzX6za1Ba2nR/BVUQ=="], + "@outfitter/contracts": ["@outfitter/contracts@0.4.1", "", { "dependencies": { "better-result": "^2.5.0", "zod": "^4.3.5" } }, "sha512-GJpPF92HGoePM48g3heBKMbOXbDp++DqycwsgR04oRs2F42DD7bclmPsskS7wjraxemNwOtf5TUlwmMzjaOV+w=="], - "@outfitter/contracts": ["@outfitter/contracts@0.1.0", "", { "dependencies": { "better-result": "^2.5.0", "zod": "^4.3.5" } }, "sha512-JtSOTZhE3f9xfFdO8mtt0EGLEk0j8WuCLyUSDIeBCv9hB58OgCsaamRQUDicHYGNsb7oLSLCPSiFwVELaPRvlA=="], + "@outfitter/logging": ["@outfitter/logging@0.4.1", "", { "dependencies": { "@logtape/logtape": "^2.0.0" }, "peerDependencies": { "@outfitter/config": ">=0.3.0", "@outfitter/contracts": ">=0.2.0" } }, "sha512-zOHlsT1Ec8iiRG+cJkWwzvHkPOTkOOZMdMyA3nqmbsvh5Pta8s23bNSOt5wdBjGdoRRwax6LXcfywmHDrsp9Sw=="], - "@outfitter/logging": ["@outfitter/logging@0.1.0", "", { "dependencies": { "@logtape/logtape": "^2.0.0", "@outfitter/contracts": "0.1.0" } }, "sha512-mTm/SKDwERvE7Y8EGUxd4UwI+eaEPs0jd7TJAYFo1Ph2D+orp9QMXyywINIr87e4gAewLm08jPcwlYv5V7trWw=="], + "@outfitter/mcp": ["@outfitter/mcp@0.4.2", "", { "dependencies": { "@modelcontextprotocol/sdk": "^1.12.1", "zod": "^4.3.5" }, "peerDependencies": { "@outfitter/config": ">=0.3.0", "@outfitter/contracts": ">=0.2.0", "@outfitter/logging": ">=0.3.0" } }, "sha512-4I4FLEUemp0B/XrIbdCtDGYkXOj7926fqt+YwlB0Bbv130N2oVqj8n15voQKTFvCGNrfw9Eh2iHr80L8zsu9nw=="], - "@outfitter/mcp": ["@outfitter/mcp@0.1.0", "", { "dependencies": { "@modelcontextprotocol/sdk": "^1.12.1", "@outfitter/contracts": "0.1.0", "@outfitter/logging": "0.1.0", "zod": "^4.3.5" } }, "sha512-LQKwSwYiUCcz41ontwXj40GvS4JbXaHnK9oFMPMCJs7LsXn1joR6oHByvDSb/s3PjhWuBvCeuTCuRZSxlXVaNA=="], + "@outfitter/schema": ["@outfitter/schema@0.2.2", "", { "dependencies": { "@outfitter/contracts": "0.4.1", "@outfitter/types": "0.2.3" }, "peerDependencies": { "zod": "^4.3.5" } }, "sha512-qzQyP2VvtOiE2gIAsCqq4XH4jqK2orTwKTjKPvMrwY0IPQTlaFbBSQN0dB20k0HDjVtNrPzREFTYEvFUBPzlEQ=="], - "@outfitter/types": ["@outfitter/types@0.1.0", "", { "dependencies": { "@outfitter/contracts": "0.1.0", "better-result": "^2.5.0" } }, "sha512-Dk8YQA9LQDJIumxfJB5+YeT83NT4dYb4iAp8a/EFBp2vOBiEiCHtX4Hd/+fjzAk/e2r5DCWdfPmtsZswj5o+NA=="], + "@outfitter/types": ["@outfitter/types@0.2.3", "", { "dependencies": { "@outfitter/contracts": "0.4.1", "better-result": "^2.5.0" } }, "sha512-PgSX0TH2UbZppQolp0USsgxSVQTOxQl17mOgw6MFi4xOVxSewmCEHRqV60iXR2lyOecnfw4Ffz5CTyiAixgDRA=="], "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.52.2", "", { "os": "android", "cpu": "arm" }, "sha512-o3pcKzJgSGt4d74lSZ+OCnHwkKBeAbFDmbEm5gg70eA8VkyCuC/zV9TwBnmw6VjDlRdF4Pshfb+WE9E6XY1PoQ=="], @@ -760,6 +765,8 @@ "slow-redact": ["slow-redact@0.3.0", "", {}, "sha512-cf723wn9JeRIYP9tdtd86GuqoR5937u64Io+CYjlm2i7jvu7g0H+Cp0l0ShAf/4ZL+ISUTVT+8Qzz7RZmp9FjA=="], + "smol-toml": ["smol-toml@1.6.0", "", {}, "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw=="], + "sonic-boom": ["sonic-boom@4.2.0", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], @@ -856,7 +863,7 @@ "yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="], - "zod": ["zod@4.1.12", "", {}, "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ=="], + "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "zod-to-json-schema": ["zod-to-json-schema@3.24.6", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg=="], @@ -866,15 +873,9 @@ "@outfitter/cli/commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], - "@outfitter/cli/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], - - "@outfitter/config/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], + "@outfitter/config/yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], - "@outfitter/contracts/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], - - "@outfitter/mcp/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], - - "@waymarks/mcp/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], + "better-result/@clack/prompts": ["@clack/prompts@0.11.0", "", { "dependencies": { "@clack/core": "0.5.0", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw=="], "body-parser/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], @@ -884,6 +885,8 @@ "tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + "ultracite/@clack/prompts": ["@clack/prompts@0.11.0", "", { "dependencies": { "@clack/core": "0.5.0", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw=="], + "ultracite/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="], "vite/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], @@ -895,5 +898,9 @@ "zod-to-json-schema/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "@modelcontextprotocol/sdk/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "better-result/@clack/prompts/@clack/core": ["@clack/core@0.5.0", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow=="], + + "ultracite/@clack/prompts/@clack/core": ["@clack/core@0.5.0", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow=="], } } diff --git a/packages/cli/package.json b/packages/cli/package.json index edf2e7c..fb54519 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -21,16 +21,18 @@ }, "dependencies": { "@bomb.sh/tab": "0.0.6", - "@outfitter/cli": "0.1.0", - "@outfitter/contracts": "0.1.0", - "@outfitter/logging": "0.1.0", + "@clack/prompts": "^1.0.1", + "@outfitter/cli": "0.5.2", + "better-result": "^2.5.1", + "@outfitter/contracts": "0.4.1", + "@outfitter/logging": "0.4.1", "@waymarks/core": "workspace:*", "commander": "14.0.1", "ignore": "7.0.5", "ora": "9.0.0", "simple-update-notifier": "2.0.0", "yaml": "2.8.1", - "zod": "4.1.12" + "zod": "^4.3.5" }, "devDependencies": { "@types/bun": "1.2.22" diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 02b3972..cb0c855 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -4,10 +4,10 @@ import { existsSync } from "node:fs"; import { mkdir, readFile, writeFile } from "node:fs/promises"; import { homedir } from "node:os"; import { join, resolve } from "node:path"; -import { promptSelect } from "@outfitter/cli/prompt"; import { InternalError, Result } from "@outfitter/contracts"; import { CliError } from "../errors.ts"; import { ExitCode } from "../exit-codes.ts"; +import { promptSelect } from "../utils/clack-prompts.ts"; import { logger } from "../utils/logger.ts"; import { assertPromptAllowed } from "../utils/prompts.ts"; diff --git a/packages/cli/src/commands/modify.ts b/packages/cli/src/commands/modify.ts index 09023b0..b43cb72 100644 --- a/packages/cli/src/commands/modify.ts +++ b/packages/cli/src/commands/modify.ts @@ -1,7 +1,6 @@ // tldr ::: edit command implementation for wm CLI import { readFile, writeFile } from "node:fs/promises"; -import { promptConfirm, promptSelect, promptText } from "@outfitter/cli/prompt"; import { InternalError, Result } from "@outfitter/contracts"; import { fingerprintContent, @@ -12,8 +11,12 @@ import { type WaymarkConfig, type WaymarkRecord, } from "@waymarks/core"; - import type { CommandContext } from "../types.ts"; +import { + promptConfirm, + promptSelect, + promptText, +} from "../utils/clack-prompts.ts"; import { parseFileLineTarget } from "../utils/file-line.ts"; import { logger } from "../utils/logger.ts"; import { assertPromptAllowed } from "../utils/prompts.ts"; diff --git a/packages/cli/src/utils/clack-prompts.ts b/packages/cli/src/utils/clack-prompts.ts new file mode 100644 index 0000000..031aab0 --- /dev/null +++ b/packages/cli/src/utils/clack-prompts.ts @@ -0,0 +1,109 @@ +// tldr ::: thin Result-returning wrappers around @clack/prompts (replaces @outfitter/cli/prompt) + +import { + confirm as clackConfirm, + select as clackSelect, + text as clackText, + isCancel, + type Option, +} from "@clack/prompts"; +import { Err, Ok } from "better-result"; + +// note ::: @outfitter/cli/prompt was removed in 0.5.2; these wrappers preserve the Result API + +type PromptSelectOptions = { + message: string; + options: Option[]; + initialValue?: T; +}; + +type PromptConfirmOptions = { + message: string; + initialValue?: boolean; +}; + +type PromptTextOptions = { + message: string; + defaultValue?: string; + initialValue?: string; + placeholder?: string; +}; + +type PromptCancelledError = Error & { readonly _cancelled: true }; + +function promptCancelledError(): PromptCancelledError { + const e = new Error("Prompt cancelled") as PromptCancelledError; + (e as { _cancelled: boolean })._cancelled = true; + return e; +} + +/** + * Prompt the user to select a value from a list. + * Returns Err when the user cancels. + * @param opts - Select prompt options. + */ +export async function promptSelect( + opts: PromptSelectOptions +): Promise | Err> { + const result = await clackSelect({ + message: opts.message, + options: opts.options, + ...(opts.initialValue !== undefined + ? { initialValue: opts.initialValue } + : {}), + }); + if (isCancel(result)) { + return new Err(promptCancelledError()); + } + return new Ok(result as T); +} + +/** + * Prompt the user for a boolean confirmation. + * Returns Err when the user cancels. + * @param opts - Confirm prompt options. + */ +export async function promptConfirm( + opts: PromptConfirmOptions +): Promise< + Ok | Err +> { + const result = await clackConfirm({ + message: opts.message, + ...(opts.initialValue !== undefined + ? { initialValue: opts.initialValue } + : {}), + }); + if (isCancel(result)) { + return new Err(promptCancelledError()); + } + return new Ok(result); +} + +/** + * Prompt the user for free-form text input. + * Returns Err when the user cancels. + * @param opts - Text prompt options. + */ +export async function promptText( + opts: PromptTextOptions +): Promise< + Ok | Err +> { + const result = await clackText({ + message: opts.message, + ...(opts.defaultValue !== undefined + ? { defaultValue: opts.defaultValue } + : {}), + ...(opts.initialValue !== undefined + ? { initialValue: opts.initialValue } + : {}), + ...(opts.placeholder !== undefined + ? { placeholder: opts.placeholder } + : {}), + }); + if (isCancel(result)) { + return new Err(promptCancelledError()); + } + return new Ok(result); +} diff --git a/packages/cli/src/utils/prompts.ts b/packages/cli/src/utils/prompts.ts index 65dace9..452b7d5 100644 --- a/packages/cli/src/utils/prompts.ts +++ b/packages/cli/src/utils/prompts.ts @@ -1,9 +1,9 @@ // tldr ::: interactive prompts using @outfitter/cli for CLI confirmations and selection -import { promptConfirm, promptSelect } from "@outfitter/cli/prompt"; import type { WaymarkRecord } from "@waymarks/grammar"; import { CliError } from "../errors.ts"; import { ExitCode } from "../exit-codes.ts"; +import { promptConfirm, promptSelect } from "./clack-prompts.ts"; import { canPrompt } from "./terminal.ts"; type PromptBlockReason = "no-input" | "no-tty" | undefined; diff --git a/packages/core/package.json b/packages/core/package.json index ca45aba..df87e5d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -17,13 +17,13 @@ "lint": "bunx ultracite check" }, "dependencies": { - "@outfitter/config": "0.1.0", - "@outfitter/contracts": "0.1.0", + "@outfitter/config": "0.3.3", + "@outfitter/contracts": "0.4.1", "@waymarks/grammar": "workspace:*", "safe-regex": "2.1.1", "type-fest": "^5.0.1", "yaml": "2.8.1", - "zod": "4.1.12" + "zod": "^4.3.5" }, "devDependencies": { "@types/bun": "1.2.22", diff --git a/upgrade-outfitter.md b/upgrade-outfitter.md new file mode 100644 index 0000000..a1f2806 --- /dev/null +++ b/upgrade-outfitter.md @@ -0,0 +1,54 @@ +# Outfitter Stack Upgrade + +All `@outfitter/*` packages pinned at `0.1.0`. No breaking changes — purely additive upgrades. Waymark is the cleanest consumer: strict Result types throughout, no anti-patterns. + +## Current vs Latest + +| Package | Current | Latest | Used In | +|---------|---------|--------|---------| +| `@outfitter/contracts` | 0.1.0 | 0.2.0 | core, cli, mcp | +| `@outfitter/config` | 0.1.0 | 0.3.0 | core | +| `@outfitter/logging` | 0.1.0 | 0.3.0 | cli, mcp | +| `@outfitter/cli` | 0.1.0 | 0.3.0 | cli | +| `@outfitter/mcp` | 0.1.0 | 0.3.0 | mcp app | + +## High-Value Opportunities + +### 1. MCP resources and tool annotations (mcp 0.1.0 -> 0.3.0) + +The MCP server currently has a single `waymark` tool. With 0.3.0: + +- **Resources** — expose waymark scan results or graph data as MCP resources +- **Tool annotations** — declare `scan`/`graph` actions as read-only, `add` as destructive +- **Progress reporting** — large vault scans can report incremental progress +- **Log forwarding** — structured log visibility for MCP clients + +### 2. CLI output modes and environment profiles (cli 0.1.0 -> 0.3.0) + +- `--json` global flag on all commands via `createCLI()` +- `OUTFITTER_ENV` unified profiles for dev/prod/test defaults +- `resolveVerbose()` utility — replace manual --verbose/--quiet/--debug flag handling + +### 3. Error factory methods and expect() (contracts 0.1.0 -> 0.2.0) + +- `static create()` factories simplify error construction +- `AmbiguousError` from contracts may match waymark's disambiguation needs +- `expect()` for unwrapping Results at boundaries + +### 4. Unified log level resolution (logging 0.1.0 -> 0.3.0) + +- `resolveLogLevel()` with precedence (`OUTFITTER_LOG_LEVEL` > explicit > env profile > default) +- Replace manual level wiring in `packages/cli/src/utils/logger.ts` + +### 5. JSONC and environment profiles (config 0.1.0 -> 0.3.0) + +- JSONC support for config files (JSON with comments) +- `getEnvironment()` and `getEnvironmentDefaults()` for unified profiles + +## Anti-Patterns + +None found. Waymark has strict Result-based error handling, no thrown exceptions in production code, no console.log, and no hardcoded paths. + +## Getting Started + +Run `/outfitter-update` to detect installed versions, surface migration guidance, and apply upgrades.