Skip to content

Configurable Workspace Paths (Cycle 0025) - #12

Merged
flyingrobots merged 3 commits into
mainfrom
cycles/0025-configurable-workspace-paths
Apr 7, 2026
Merged

flyingrobots merged 3 commits into
mainfrom
cycles/0025-configurable-workspace-paths

Conversation

@flyingrobots

Copy link
Copy Markdown
Owner

Summary

Makes METHOD's workspace directory layout configurable via .method.json:

{
  "paths": {
    "backlog": ".method/backlog",
    "design": ".method/design",
    "retro": ".method/retro",
    "tests": "spec",
    "graveyard": ".method/graveyard",
    "method_dir": ".method"
  }
}

Defaults match the current layout — zero behavioral change for existing workspaces.

Changes

  • src/config.ts — PathsSchema with defaults, DEFAULT_PATHS export
  • src/domain.ts — removed BACKLOG_DIR/DESIGN_DIR/RETRO_DIR constants
  • src/index.ts — Workspace.paths resolved from config, initWorkspace accepts optional paths
  • src/drift.ts — detectWorkspaceDrift accepts testsDir parameter
  • src/cli.ts — passes config paths to initWorkspace
  • tests/cli.test.ts — new integration test with custom .method.json paths

Test plan

  • 119 tests pass (118 prior + 1 new custom paths integration test)
  • Build clean
  • Cycle closed with retro and witness

Make workspace directory layout configurable via .method.json paths
section. Currently all paths are hardcoded constants.
The paths section in .method.json lets projects override where METHOD
puts backlog, design, retro, tests, graveyard, and the method dir:

  { "paths": { "backlog": ".method/backlog", "design": ".method/design", ... } }

Defaults match the current layout — zero behavioral change when no
config exists.

- Added PathsSchema with defaults to config.ts
- Removed BACKLOG_DIR/DESIGN_DIR/RETRO_DIR constants from domain.ts
- Workspace resolves all paths from config in constructor
- initWorkspace accepts optional PathsConfig
- detectWorkspaceDrift accepts testsDir parameter
- CLI passes config paths to initWorkspace
- New integration test proves custom paths work end-to-end

119 tests pass (118 prior + 1 new custom paths test).
Hill met. Workspace layout is now configurable via .method.json paths
section. Defaults preserve existing behavior. 119 tests pass.
@coderabbitai

coderabbitai Bot commented Apr 7, 2026 •

Copy link
Copy Markdown

Summary by CodeRabbit

Release Notes

  • New Features
    • Workspace directory paths are now configurable via the paths section in .method.json. Customize the locations of backlog, design, retro, tests, and graveyard directories. All workspace commands respect these configurations while preserving the default directory structure for existing projects.

Walkthrough

This pull request implements configurable workspace directory paths via a .method.json configuration file. The feature replaces hardcoded directory constants with a schema-driven approach, allowing customization of backlog, design, retro, tests, graveyard, and method directory locations while preserving existing defaults for backward compatibility.

Changes

Cohort / File(s) Summary
Configuration & Schema
.mcp.json, src/config.ts
Added MCP server configuration. Extended config schema with PathsSchema, PathsConfig, and DEFAULT_PATHS, integrating configurable paths into the core Config type.
Documentation
docs/design/0025-configurable-workspace-paths/..., docs/method/retro/0025-configurable-workspace-paths/...
Added design specification and retrospective documentation with verification witness confirming all tests pass and feature compliance.
Core Path Management
src/domain.ts, src/cli.ts, src/drift.ts
Removed hardcoded BACKLOG_DIR, DESIGN_DIR, RETRO_DIR constants. Updated CLI init command to load configuration and pass paths to workspace initialization. Modified detectWorkspaceDrift signature to accept optional testsDir parameter for configurable test discovery.
Workspace Refactoring
src/index.ts
Added ResolvedPaths interface and resolvePaths() helper for absolute path computation. Refactored initWorkspace() to accept optional PathsConfig and use resolved paths for all directory/file operations. Extended Workspace class with paths property and updated all operations (cycle discovery, drift detection, file access) to use configured paths instead of constants.
Integration Testing
tests/cli.test.ts
Added end-to-end test verifying method init, method inbox, method pull, and method status correctly respect custom workspace paths from .method.json configuration.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI
    participant ConfigLoader
    participant Workspace
    participant FileSystem

    User->>CLI: method init <target>
    CLI->>ConfigLoader: loadConfig(target)
    ConfigLoader->>FileSystem: read .method.json
    ConfigLoader-->>CLI: Config {paths: PathsConfig}
    CLI->>Workspace: initWorkspace(target, config.paths)
    Workspace->>Workspace: resolvePaths(root, paths)
    Workspace-->>Workspace: ResolvedPaths {backlog, design, retro, tests, graveyard, methodDir}
    Workspace->>FileSystem: create directories at resolved paths
    Workspace->>FileSystem: scaffold files (.method.json, cycles, etc.)
    FileSystem-->>Workspace: success
    Workspace-->>CLI: {created: [paths]}
    CLI-->>User: initialization complete
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Rationale: This diff introduces interconnected structural changes across multiple core files with new type definitions, path resolution logic, and refactored initialization/workspace operations. Critical areas demanding verification: (1) path resolution correctness and absolute path computation; (2) exhaustive replacement of hardcoded constants throughout workspace operations; (3) proper threading of PathsConfig through CLI → initialization → Workspace; (4) drift detection integration with configurable test directory; (5) backward compatibility via default path preservation. The heterogeneous nature of edits (schema additions, constant removal, method signature changes, class property additions) and multi-file dependencies require careful line-by-line analysis despite reasonable code structure.

Possibly Related PRs

  • Split CLI into behavior-owned modules and close out cycle 0007 #4: Modifies detectWorkspaceDrift integration—this PR updates the function signature to accept configurable testsDir and wires it through Workspace.detectDrift(), directly building on drift detection surface area.
  • Add drift detector command and close out cycle 0005 #2: Related through drift-detector implementation and test-discovery surface—this PR integrates the configurable test directory parameter into the same drift detection pipeline.
  • Two-way GitHub Sync (Cycle 0021) #7: Both PRs modify core src/index.ts workspace implementation—the main PR adds path resolution and refactors workspace initialization; the retrieved PR extends workspace methods (moveBacklogItem, updateBody), requiring coordination on workspace structure.

Poem

🗺️ Gone are the days of hardcoded stone,

Each path now claims a throne of its own,

CONFIG_DRIVEN: the workspace takes flight,

Backlog, design, retro—where YOU choose right! ✨🧭

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change: making workspace directory layout configurable with cycle number reference.
Description check ✅ Passed The description is directly related to the changeset, clearly explaining the feature, providing configuration examples, enumerating all modified files, and documenting test coverage.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cycles/0025-configurable-workspace-paths

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@flyingrobots
flyingrobots merged commit 9a7e2d4 into main Apr 7, 2026
2 of 3 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 06a4b4a470

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .mcp.json
"mcpServers": {
"method": {
"command": "node",
"args": ["dist/cli.js", "mcp"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use a runnable MCP entrypoint by default

The new MCP config points to dist/cli.js, but this repository does not include a built dist/ directory by default, so MCP clients that load .mcp.json in a fresh clone fail immediately with Cannot find module '/workspace/method/dist/cli.js'. That makes the shipped MCP integration unusable unless users discover and run a separate build step first; the config should target a runnable source entrypoint or explicitly enforce/build before launch.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/config.ts`:
- Around line 22-29: The outer defaults duplicate the per-field defaults inside
PathsSchema; replace the explicit object passed to PathsSchema.default(...) with
an empty object so the inner .default() values on each PathsSchema field are
used when the paths key is missing — update the paths assignment that currently
uses PathsSchema.default({backlog:..., design:..., ...}) to use
PathsSchema.default({}) (or remove the outer defaults entirely) so only
PathsSchema's internal defaults govern values.

In `@tests/cli.test.ts`:
- Around line 434-447: Capture and assert the exit codes returned by runCli for
the inbox and pull invocations and add a runCli invocation to exercise the
custom tests directory with the method drift flow: change the two awaited
runCli([...], { cwd, stdout, stderr }) calls to store their return values (e.g.,
inboxResult and pullResult) and add assertions that inboxResult.code === 0 and
pullResult.code === 0 (or equivalent property returned by runCli); then invoke
runCli for the 'method drift' or appropriate test runner command pointing at the
custom tests folder (the tests:'spec' config) and assert its exit code is 0
and/or its output contains expected results to ensure the custom tests directory
is actually exercised.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4d8fb8c0-9ce1-4b3d-a220-4d71f984b4a5

📥 Commits

Reviewing files that changed from the base of the PR and between f88b764 and 06a4b4a.

📒 Files selected for processing (10)
  • .mcp.json
  • docs/design/0025-configurable-workspace-paths/configurable-workspace-paths.md
  • docs/method/retro/0025-configurable-workspace-paths/configurable-workspace-paths.md
  • docs/method/retro/0025-configurable-workspace-paths/witness/verification.md
  • src/cli.ts
  • src/config.ts
  • src/domain.ts
  • src/drift.ts
  • src/index.ts
  • tests/cli.test.ts
💤 Files with no reviewable changes (1)
  • src/domain.ts

Comment thread src/config.ts
Comment on lines +22 to +29
paths: PathsSchema.default({
backlog: 'docs/method/backlog',
design: 'docs/design',
retro: 'docs/method/retro',
tests: 'tests',
graveyard: 'docs/method/graveyard',
method_dir: 'docs/method',
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

DRY violation — defaults are duplicated and will drift.

You've defined the same default values twice:

  1. Inner defaults (lines 6–11): via .default() on each field of PathsSchema
  2. Outer defaults (lines 22–29): via PathsSchema.default({...}) with explicit values

When the paths key is missing entirely, Zod uses the outer default. But that object then gets parsed through PathsSchema, where the inner defaults would apply anyway. The explicit outer defaults are redundant — pass an empty object and let the inner defaults do their job.

This duplication is a maintenance hazard. Someone will change one set and forget the other. Murphy's law.

♻️ Eliminate duplicate defaults
 export const ConfigSchema = z.object({
   forge: z.enum(['github']).default('github'),
   github_token: z.string().optional(),
   github_repo: z.string().optional(), // owner/repo
-  paths: PathsSchema.default({
-    backlog: 'docs/method/backlog',
-    design: 'docs/design',
-    retro: 'docs/method/retro',
-    tests: 'tests',
-    graveyard: 'docs/method/graveyard',
-    method_dir: 'docs/method',
-  }),
+  paths: PathsSchema.default({}),
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
paths: PathsSchema.default({
backlog: 'docs/method/backlog',
design: 'docs/design',
retro: 'docs/method/retro',
tests: 'tests',
graveyard: 'docs/method/graveyard',
method_dir: 'docs/method',
}),
paths: PathsSchema.default({}),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/config.ts` around lines 22 - 29, The outer defaults duplicate the
per-field defaults inside PathsSchema; replace the explicit object passed to
PathsSchema.default(...) with an empty object so the inner .default() values on
each PathsSchema field are used when the paths key is missing — update the paths
assignment that currently uses PathsSchema.default({backlog:..., design:...,
...}) to use PathsSchema.default({}) (or remove the outer defaults entirely) so
only PathsSchema's internal defaults govern values.

Comment thread tests/cli.test.ts
Comment on lines +434 to +447
// Inbox should work against custom paths
const inboxOut = new MemoryWriter();
await runCli(['inbox', 'custom path test', '--legend', 'PROC'], { cwd: root, stdout: inboxOut, stderr: new MemoryWriter() });
expect(existsSync(join(root, '.method/backlog/inbox/PROC_custom-path-test.md'))).toBe(true);

// Pull should work against custom paths
const pullOut = new MemoryWriter();
await runCli(['pull', 'PROC_custom-path-test'], { cwd: root, stdout: pullOut, stderr: new MemoryWriter() });
expect(existsSync(join(root, '.method/design/0001-custom-path-test/custom-path-test.md'))).toBe(true);

// Status should reflect it
const statusOut = new MemoryWriter();
await runCli(['status'], { cwd: root, stdout: statusOut, stderr: new MemoryWriter() });
expect(statusOut.output).toContain('0001-custom-path-test');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing exit code assertions — test could silently pass on command failures.

You're asserting file existence and status output, but you're discarding the exit codes from inbox and pull. If either command fails with exit code 1 (e.g., due to a path resolution bug), this test would still pass as long as the filesystem state happens to match. That's a latent defect waiting to humiliate you in production.

Also: you configured tests: 'spec' but never exercised method drift against it. The custom tests directory path remains untested in this integration scenario.

🔧 Proposed fix to capture and assert exit codes
     // Inbox should work against custom paths
     const inboxOut = new MemoryWriter();
-    await runCli(['inbox', 'custom path test', '--legend', 'PROC'], { cwd: root, stdout: inboxOut, stderr: new MemoryWriter() });
+    const inboxExitCode = await runCli(['inbox', 'custom path test', '--legend', 'PROC'], { cwd: root, stdout: inboxOut, stderr: new MemoryWriter() });
+    expect(inboxExitCode).toBe(0);
     expect(existsSync(join(root, '.method/backlog/inbox/PROC_custom-path-test.md'))).toBe(true);

     // Pull should work against custom paths
     const pullOut = new MemoryWriter();
-    await runCli(['pull', 'PROC_custom-path-test'], { cwd: root, stdout: pullOut, stderr: new MemoryWriter() });
+    const pullExitCode = await runCli(['pull', 'PROC_custom-path-test'], { cwd: root, stdout: pullOut, stderr: new MemoryWriter() });
+    expect(pullExitCode).toBe(0);
     expect(existsSync(join(root, '.method/design/0001-custom-path-test/custom-path-test.md'))).toBe(true);

     // Status should reflect it
     const statusOut = new MemoryWriter();
-    await runCli(['status'], { cwd: root, stdout: statusOut, stderr: new MemoryWriter() });
+    const statusExitCode = await runCli(['status'], { cwd: root, stdout: statusOut, stderr: new MemoryWriter() });
+    expect(statusExitCode).toBe(0);
     expect(statusOut.output).toContain('0001-custom-path-test');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Inbox should work against custom paths
const inboxOut = new MemoryWriter();
await runCli(['inbox', 'custom path test', '--legend', 'PROC'], { cwd: root, stdout: inboxOut, stderr: new MemoryWriter() });
expect(existsSync(join(root, '.method/backlog/inbox/PROC_custom-path-test.md'))).toBe(true);
// Pull should work against custom paths
const pullOut = new MemoryWriter();
await runCli(['pull', 'PROC_custom-path-test'], { cwd: root, stdout: pullOut, stderr: new MemoryWriter() });
expect(existsSync(join(root, '.method/design/0001-custom-path-test/custom-path-test.md'))).toBe(true);
// Status should reflect it
const statusOut = new MemoryWriter();
await runCli(['status'], { cwd: root, stdout: statusOut, stderr: new MemoryWriter() });
expect(statusOut.output).toContain('0001-custom-path-test');
// Inbox should work against custom paths
const inboxOut = new MemoryWriter();
const inboxExitCode = await runCli(['inbox', 'custom path test', '--legend', 'PROC'], { cwd: root, stdout: inboxOut, stderr: new MemoryWriter() });
expect(inboxExitCode).toBe(0);
expect(existsSync(join(root, '.method/backlog/inbox/PROC_custom-path-test.md'))).toBe(true);
// Pull should work against custom paths
const pullOut = new MemoryWriter();
const pullExitCode = await runCli(['pull', 'PROC_custom-path-test'], { cwd: root, stdout: pullOut, stderr: new MemoryWriter() });
expect(pullExitCode).toBe(0);
expect(existsSync(join(root, '.method/design/0001-custom-path-test/custom-path-test.md'))).toBe(true);
// Status should reflect it
const statusOut = new MemoryWriter();
const statusExitCode = await runCli(['status'], { cwd: root, stdout: statusOut, stderr: new MemoryWriter() });
expect(statusExitCode).toBe(0);
expect(statusOut.output).toContain('0001-custom-path-test');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli.test.ts` around lines 434 - 447, Capture and assert the exit codes
returned by runCli for the inbox and pull invocations and add a runCli
invocation to exercise the custom tests directory with the method drift flow:
change the two awaited runCli([...], { cwd, stdout, stderr }) calls to store
their return values (e.g., inboxResult and pullResult) and add assertions that
inboxResult.code === 0 and pullResult.code === 0 (or equivalent property
returned by runCli); then invoke runCli for the 'method drift' or appropriate
test runner command pointing at the custom tests folder (the tests:'spec'
config) and assert its exit code is 0 and/or its output contains expected
results to ensure the custom tests directory is actually exercised.

@flyingrobots
flyingrobots deleted the cycles/0025-configurable-workspace-paths branch April 7, 2026 07:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant