Skip to content

feat(tracker): library API for workflow catalog and run resolution (#76 Phase 1)#88

Merged
clintecker merged 5 commits intomainfrom
feat/cli-library-parity-76-phase1
Apr 16, 2026
Merged

feat(tracker): library API for workflow catalog and run resolution (#76 Phase 1)#88
clintecker merged 5 commits intomainfrom
feat/cli-library-parity-76-phase1

Conversation

@clintecker
Copy link
Copy Markdown
Collaborator

@clintecker clintecker commented Apr 15, 2026

Summary

Phase 1 of issue #76 (CLI↔library parity). Exposes the built-in workflow catalog and run-ID resolution as first-class library APIs so consumers (factory worker, custom orchestrators, test harnesses) don't need to shell out to the tracker CLI or reimplement directory conventions.

New library surface

```go
tracker.Workflows() []WorkflowInfo
tracker.LookupWorkflow(name string) (WorkflowInfo, bool)
tracker.OpenWorkflow(name string) ([]byte, WorkflowInfo, error)
tracker.ResolveSource(name, workDir string) (source string, info WorkflowInfo, err error)
tracker.ResolveCheckpoint(workDir, runID string) (path string, err error)

// And a new Config field:
tracker.Config.ResumeRunID string
```

`ResumeRunID` is the library equivalent of the CLI's `-r/--resume` flag. Setting `cfg.ResumeRunID = "abc123"` (or a unique prefix) makes `NewEngine` call `ResolveCheckpoint` under the hood and populate `Config.CheckpointDir`. An explicit `CheckpointDir` on the same config still wins as a manual override — `NewEngine` never overwrites it.

Layout change

Embedded `.dip` files moved from `cmd/tracker/workflows/` → `workflows/` at the module root so the tracker package can embed them. The CLI's `embed.go`, `resolve.go`, and `loadEmbeddedPipeline` are now thin wrappers over the library APIs, giving us a single source of truth for built-in workflows.

Test plan

  • 19 new tests in `tracker_workflows_test.go` and `tracker_resolve_test.go`
  • `go build ./...`
  • `go test ./... -short` — all 15 packages pass
  • `dippin doctor examples/ask_and_execute.dip examples/build_product.dip examples/build_product_with_superspec.dip` — A grade

Phase breakdown for #76

This PR is Phase 1 (workflow catalog + name resolution + ResumeRunID). Remaining phases:

  • Phase 2 — structured Diagnose, Audit, Doctor, Simulate library APIs returning typed reports; CLI commands become thin formatters.
  • Phase 3 — introspection (`DescribeNodes`) and NDJSON event writer.

Refs #76

Summary by CodeRabbit

  • New Features

    • Library API for workflow cataloging, lookup, opening, source resolution, and checkpoint resolution.
    • Resume-run-ID option to resume workflows by ID or unique prefix.
  • Refactor

    • Built-in workflow assets moved to a top-level workflows/ location and shared between CLI and library.
    • CLI delegates built-in workflow lookups to the library.
  • Documentation

    • Updated Unreleased changelog.
  • Tests

    • Added tests for source resolution, checkpoint resolution, and the workflow catalog API.
  • Chores

    • Updated workflow sync/check targets and pre-commit messaging to reference workflows/.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 15, 2026

Warning

Rate limit exceeded

@clintecker has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 0 minutes and 44 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 0 minutes and 44 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ea4ee9af-a13b-482c-959b-7fa1ff3d1060

📥 Commits

Reviewing files that changed from the base of the PR and between 09e968e and 10f325f.

📒 Files selected for processing (17)
  • .pre-commit
  • CHANGELOG.md
  • Makefile
  • cmd/tracker/commands.go
  • cmd/tracker/embed.go
  • cmd/tracker/loading.go
  • cmd/tracker/resolve.go
  • tracker.go
  • tracker_resolve.go
  • tracker_resolve_test.go
  • tracker_test.go
  • tracker_workflows.go
  • tracker_workflows_test.go
  • workflows/ask_and_execute.dip
  • workflows/build_product.dip
  • workflows/build_product_with_superspec.dip
  • workflows/deep_review.dip

Walkthrough

This PR centralizes embedded workflow assets in the tracker library, exposes a built-in workflow catalog API (Workflows/Lookup/OpenWorkflow), adds ResolveSource and ResolveCheckpoint helpers, and wires Config.ResumeRunID into NewEngine to auto-resolve checkpoint directories for resuming runs.

Changes

Cohort / File(s) Summary
Library: built-in workflows
tracker_workflows.go, tracker_workflows_test.go
Embed workflows/*.dip in package tracker; add WorkflowInfo, Workflows(), LookupWorkflow(), and OpenWorkflow(); lazy catalog build and tests for contents, sorting, immutability, lookup, and open behavior.
Library: resolution helpers & tests
tracker_resolve.go, tracker_resolve_test.go
Add ResolveSource(name, workDir) (explicit path → local .dip variants → built-in lookup/load) and ResolveCheckpoint(workDir, runID) (exact or unique prefix → <runDir>/checkpoint.json) with detailed error handling and tests.
Library: engine resume
tracker.go
Add Config.ResumeRunID and helper applyResumeRunID(); NewEngine applies resume-run resolution to populate cfg.CheckpointDir when appropriate and returns wrapped errors on failure.
CLI: delegate to library
cmd/tracker/embed.go, cmd/tracker/commands.go, cmd/tracker/loading.go, cmd/tracker/resolve.go
Remove CLI-local embedding/catalog logic; alias WorkflowInfo = tracker.WorkflowInfo; replace local lookup/load with tracker.LookupWorkflow(), tracker.Workflows(), and tracker.OpenWorkflow() / tracker.OpenWorkflow() calls; update path-detection and file-read call sites.
Build / tooling
Makefile, .pre-commit
Switch workflow sync/check targets and pre-commit messaging to use top-level workflows/ directory instead of cmd/tracker/workflows/.
Docs
CHANGELOG.md
Document new library API for workflow cataloging, source/checkpoint resolution, ResumeRunID behavior, and note embedded assets moved to top-level workflows/.

Sequence Diagrams

sequenceDiagram
    participant CLI as CLI/Caller
    participant Resolve as ResolveSource()
    participant FS as Filesystem
    participant Catalog as LookupWorkflow()
    participant Embed as OpenWorkflow()

    CLI->>Resolve: ResolveSource(name, workDir)
    Resolve->>Resolve: validate name & detect explicit path (.dip or contains '/' or '\')
    alt explicit path
        Resolve->>FS: read file (abs or join workDir)
        FS-->>Resolve: bytes / error
    else bare name
        Resolve->>FS: try workDir/name.dip
        alt found
            FS-->>Resolve: bytes
        else not found
            Resolve->>Catalog: LookupWorkflow(name)
            alt found
                Catalog-->>Resolve: WorkflowInfo
                Resolve->>Embed: OpenWorkflow(name)
                Embed-->>Resolve: embedded bytes, info
            else not found
                Resolve-->>CLI: error (lists available built-ins)
            end
        end
    end
    Resolve-->>CLI: source, WorkflowInfo, nil
Loading
sequenceDiagram
    participant Caller as Caller
    participant NewEngine as NewEngine()
    participant Apply as applyResumeRunID()
    participant ResolveCP as ResolveCheckpoint()
    participant Runs as .tracker/runs dir

    Caller->>NewEngine: NewEngine(source, cfg)
    NewEngine->>NewEngine: compute workDir
    NewEngine->>Apply: applyResumeRunID(&cfg, workDir)
    alt ResumeRunID set and CheckpointDir empty
        Apply->>ResolveCP: ResolveCheckpoint(workDir, ResumeRunID)
        alt success
            ResolveCP-->>Apply: checkpoint path
            Apply->>NewEngine: set cfg.CheckpointDir
        else error
            ResolveCP-->>Apply: error
            Apply-->>NewEngine: return error
        end
    else skip resolution
        Apply-->>NewEngine: no-op
    end
    NewEngine->>NewEngine: continue init or return error
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

Poem

🐰 I nibbled through code, moved workflows to share,
Library and CLI now hop in pair,
Resolve the source, resume by run ID,
Checkpoints found — back to the pipeline tree! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically summarizes the main change: adding a library API for workflow catalog and run resolution, matching the PR's core objective of exposing these as first-class APIs.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cli-library-parity-76-phase1

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

Copy link
Copy Markdown

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

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: a58e5bd23d

ℹ️ 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 tracker_resolve.go Outdated
Comment on lines +30 to +33
if isExplicitFilePath(name) {
data, rerr := os.ReadFile(name)
if rerr != nil {
return "", WorkflowInfo{}, fmt.Errorf("read pipeline file %q: %w", name, rerr)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Honor workDir for explicit relative pipeline paths

When name contains / or a .dip/.dot suffix, this branch reads directly from name and ignores workDir, so ResolveSource("subdir/flow.dip", "/repo") is resolved against the process CWD instead of /repo. That breaks callers that rely on workDir to run from a different directory (the function comment says relative lookups should use CWD only when workDir is empty), and causes valid pipeline paths to fail or load the wrong file.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 6d604b3. When name is not absolute and workDir is set, we now join them: path = filepath.Join(workDir, name) before calling os.ReadFile. So a relative path like pipelines/build.dip is resolved against workDir rather than the process cwd.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

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

Inline comments:
In `@tracker_resolve.go`:
- Around line 101-121: The ResolveCheckpoint function's comment promises an
absolute path but currently returns a possibly relative cpPath when workDir is
relative; update ResolveCheckpoint to produce an absolute path (either by
calling filepath.Abs on workDir at the start or calling filepath.Abs on cpPath
before returning) so the returned checkpoint path is always absolute; keep the
rest of the error handling (resolveRunIDToDir, os.Stat) intact and ensure the
returned value is the absolute cpPath variable.
- Around line 46-61: The current checks for dipPath and barePath only handle
statErr == nil and ignore other os.Stat errors, causing unreadable local files
to be treated as "not found"; update the logic around dipPath and barePath so
that after calling os.Stat you: if statErr == nil proceed to ReadFile as before;
else if os.IsNotExist(statErr) continue to builtin resolution; otherwise return
an error (wrap statErr) immediately so permission/IO errors surface instead of
falling back to built-ins; apply this for both dipPath and barePath checks.

In `@tracker_workflows_test.go`:
- Around line 81-83: The failure message slices string(data) up to [:200]
unconditionally which will panic if the workflow content is shorter than 200
bytes; update the assertion around the `if !strings.Contains(string(data),
"workflow ")` check to build a safe preview string from `data` (e.g., convert to
s := string(data) and trim with a bounds-checked slice using min(len(s), 200) or
conditionally take s[:200] only when len(s) >= 200) and use that safe preview in
the t.Errorf call so the test reports the failure without panicking; reference
the existing use of `data` and the assertion that checks for `"workflow "` to
locate where to apply this change.
🪄 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: CHILL

Plan: Pro

Run ID: c7211f1a-704b-4990-80f9-b484efb1cc9d

📥 Commits

Reviewing files that changed from the base of the PR and between 6f1430e and a58e5bd.

📒 Files selected for processing (14)
  • CHANGELOG.md
  • cmd/tracker/commands.go
  • cmd/tracker/embed.go
  • cmd/tracker/loading.go
  • cmd/tracker/resolve.go
  • tracker.go
  • tracker_resolve.go
  • tracker_resolve_test.go
  • tracker_workflows.go
  • tracker_workflows_test.go
  • workflows/ask_and_execute.dip
  • workflows/build_product.dip
  • workflows/build_product_with_superspec.dip
  • workflows/deep_review.dip

Comment thread tracker_resolve.go
Comment thread tracker_resolve.go Outdated
Comment on lines +101 to +121
// Returns the absolute path to checkpoint.json, or an error if the run is
// not found, the prefix is ambiguous, or the checkpoint file is missing.
//
// This is the library equivalent of the CLI's `tracker -r <runID>` flag.
// Library consumers can set Config.ResumeRunID to have NewEngine resolve
// the checkpoint automatically.
func ResolveCheckpoint(workDir, runID string) (string, error) {
if runID == "" {
return "", fmt.Errorf("run ID cannot be empty")
}
runsDir := filepath.Join(workDir, ".tracker", "runs")
resolved, err := resolveRunIDToDir(runsDir, runID)
if err != nil {
return "", err
}
cpPath := filepath.Join(runsDir, resolved, "checkpoint.json")
if _, err := os.Stat(cpPath); err != nil {
return "", fmt.Errorf("checkpoint not found for run %s: %w", resolved, err)
}
return cpPath, nil
}
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 15, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

ResolveCheckpoint comment and behavior are inconsistent on absolute paths.

The comment states an absolute path is returned, but Line 116-120 returns a joined path directly. If workDir is relative, callers get a relative checkpoint path.

💡 Suggested fix
 	cpPath := filepath.Join(runsDir, resolved, "checkpoint.json")
 	if _, err := os.Stat(cpPath); err != nil {
 		return "", fmt.Errorf("checkpoint not found for run %s: %w", resolved, err)
 	}
-	return cpPath, nil
+	absPath, err := filepath.Abs(cpPath)
+	if err != nil {
+		return "", fmt.Errorf("resolve absolute checkpoint path %q: %w", cpPath, err)
+	}
+	return absPath, nil
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tracker_resolve.go` around lines 101 - 121, The ResolveCheckpoint function's
comment promises an absolute path but currently returns a possibly relative
cpPath when workDir is relative; update ResolveCheckpoint to produce an absolute
path (either by calling filepath.Abs on workDir at the start or calling
filepath.Abs on cpPath before returning) so the returned checkpoint path is
always absolute; keep the rest of the error handling (resolveRunIDToDir,
os.Stat) intact and ensure the returned value is the absolute cpPath variable.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 6d604b3. Removed the 'absolute' claim from the doc comment — the path is relative to workDir when workDir is itself relative. Updated wording clarifies this.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@clintecker, that's a reasonable approach — aligning the documentation with the actual behavior is a perfectly valid resolution. Clarifying that the returned path is relative to workDir when workDir is itself relative avoids surprising callers without changing existing behavior. Thanks for the update!


🧠 Learnings used
Learnt from: CR
Repo: 2389-research/tracker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-15T21:59:50.346Z
Learning: Running `tracker <name>` (no path, no extension) resolves in order: 1. `<name>.dip` in cwd, 2. `<name>` as file in cwd, 3. Built-in embedded workflow by name, 4. Error with list of available. This applies to `tracker validate`, `tracker simulate`, and `tracker run` via `resolvePipelineSource()`.

Comment thread tracker_workflows_test.go
clintecker added a commit that referenced this pull request Apr 16, 2026
…handling

- Makefile and .pre-commit updated to use workflows/ (repo root)
  instead of cmd/tracker/workflows/ after the embed relocation.
- ResolveSource: explicit relative paths joined with workDir when
  workDir is set. os.Stat errors other than ErrNotExist returned
  immediately instead of falling through to built-in lookup.
- ResolveCheckpoint: doc comment updated (removed 'absolute' claim).
- Test: guard string slice to prevent panic on short content.

Refs #76
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@tracker_resolve.go`:
- Around line 84-86: The explicit-path detection only looks for "/" and
extensions; update isExplicitFilePath to also treat Windows backslashes as path
separators (e.g., check strings.Contains(name, "\\") in addition to "/") and
make the same change to the duplicate function in cmd/tracker/resolve.go so both
use the same logic; then add a unit test asserting that a backslash-only
relative path like "dir\\workflow" is considered an explicit path (and not a
bare workflow name) to cover Windows-style paths.
🪄 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: CHILL

Plan: Pro

Run ID: 65be84e0-28c9-4c58-b20c-8a9895df7ce8

📥 Commits

Reviewing files that changed from the base of the PR and between a58e5bd and 6d604b3.

📒 Files selected for processing (4)
  • .pre-commit
  • Makefile
  • tracker_resolve.go
  • tracker_workflows_test.go
✅ Files skipped from review due to trivial changes (2)
  • .pre-commit
  • Makefile
🚧 Files skipped from review as they are similar to previous changes (1)
  • tracker_workflows_test.go

Comment thread tracker_resolve.go
Comment on lines +84 to +86
func isExplicitFilePath(name string) bool {
return strings.Contains(name, "/") || strings.HasSuffix(name, ".dip") || strings.HasSuffix(name, ".dot")
}
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 16, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== ResolveSource / explicit-path tests =="
fd -i 'tracker_resolve_test.go' -x sh -c 'echo "--- {} ---"; rg -n -C2 "ResolveSource\\(|isExplicitFilePath|\\\\\\\\|/.*\\.dip|\\.dot" "{}"'

echo
echo "== Current explicit-path implementation =="
rg -n -C2 'func isExplicitFilePath|Contains\\(|ContainsAny|HasSuffix\\(name, "\\.dip"|HasSuffix\\(name, "\\.dot"' --type=go

Repository: 2389-research/tracker

Length of output: 1439


🏁 Script executed:

# First, read the tracker_resolve.go file around lines 84-86
cat -n tracker_resolve.go | sed -n '75,95p'

Repository: 2389-research/tracker

Length of output: 923


🏁 Script executed:

# Check the full isExplicitFilePath function and surrounding context
rg -n -A 3 -B 1 'func isExplicitFilePath' tracker_resolve.go

Repository: 2389-research/tracker

Length of output: 262


🏁 Script executed:

# Look for Windows-specific path handling in the codebase
rg -n '\\\\|filepath|path.Join|path.Separator' tracker_resolve.go tracker_resolve_test.go

Repository: 2389-research/tracker

Length of output: 1518


🏁 Script executed:

# Check test file for any explicit path tests
rg -n -A 10 'TestResolveSource.*Explicit|backslash|windows|\\\\' tracker_resolve_test.go

Repository: 2389-research/tracker

Length of output: 467


🏁 Script executed:

# Check how isExplicitFilePath is used in ResolveSource function
rg -n -B 5 -A 5 'isExplicitFilePath' tracker_resolve.go

Repository: 2389-research/tracker

Length of output: 947


🏁 Script executed:

# Check if this is a cross-platform tool or Windows-specific
head -20 tracker_resolve.go

Repository: 2389-research/tracker

Length of output: 816


🏁 Script executed:

# Look for any existing Windows path handling or mentions
rg -n -i 'windows|cross.platform|separator' tracker_resolve.go tracker_resolve_test.go

Repository: 2389-research/tracker

Length of output: 47


🏁 Script executed:

# Check the test to see what paths are actually passed to ResolveSource
rg -n -B 2 -A 8 'func TestResolveSource_ExplicitFilePath' tracker_resolve_test.go

Repository: 2389-research/tracker

Length of output: 430


🏁 Script executed:

# Check if ResolveSource is called from CLI or other places where raw paths might be passed
rg -n 'ResolveSource(' --type=go | head -20

Repository: 2389-research/tracker

Length of output: 143


🏁 Script executed:

# Check for any OS-specific path handling or Windows-specific code
rg -n 'runtime.GOOS|GOOS|windows|os.Separator' tracker_resolve.go tracker_resolve_test.go

Repository: 2389-research/tracker

Length of output: 47


🏁 Script executed:

# Search for calls to ResolveSource without regex special characters
rg -n 'ResolveSource' --type=go

Repository: 2389-research/tracker

Length of output: 1684


🏁 Script executed:

# Check the broader context of how this function is documented
rg -n -B 3 -A 15 '// ResolveSource resolves' tracker_resolve.go

Repository: 2389-research/tracker

Length of output: 1043


🏁 Script executed:

# Check the CLI caller to understand how ResolveSource is invoked
cat -n cmd/tracker/resolve.go | head -50

Repository: 2389-research/tracker

Length of output: 2133


🏁 Script executed:

# Look for any path normalization before calling ResolveSource
rg -n -B 10 'tracker.ResolveSource' cmd/tracker/resolve.go

Repository: 2389-research/tracker

Length of output: 619


🏁 Script executed:

# Check if there's any indication this tool is meant to run on Windows
cat -n README.md 2>/dev/null | head -30 || echo "No README.md found"

Repository: 2389-research/tracker

Length of output: 1089


🏁 Script executed:

# Look for any OS-specific handling in the main package
rg -n 'GOOS|windows|darwin|linux' cmd/tracker/

Repository: 2389-research/tracker

Length of output: 767


Handle Windows path separators in explicit-path detection.

The isExplicitFilePath function checks only for forward slashes ("/") but should handle Windows-style backslashes as well. A relative path like dir\workflow (without .dip/.dot extension) will be treated as a bare workflow name instead of an explicit path, causing unexpected lookup precedence. This affects both the library function in tracker_resolve.go:85 and the duplicate in cmd/tracker/resolve.go:47.

 func isExplicitFilePath(name string) bool {
-	return strings.Contains(name, "/") || strings.HasSuffix(name, ".dip") || strings.HasSuffix(name, ".dot")
+	return strings.ContainsAny(name, `/\`) ||
+		strings.HasSuffix(name, ".dip") ||
+		strings.HasSuffix(name, ".dot")
 }

No test coverage exists for backslash-only paths. Consider adding a test case for Windows-style relative paths.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tracker_resolve.go` around lines 84 - 86, The explicit-path detection only
looks for "/" and extensions; update isExplicitFilePath to also treat Windows
backslashes as path separators (e.g., check strings.Contains(name, "\\") in
addition to "/") and make the same change to the duplicate function in
cmd/tracker/resolve.go so both use the same logic; then add a unit test
asserting that a backslash-only relative path like "dir\\workflow" is considered
an explicit path (and not a bare workflow name) to cover Windows-style paths.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 417cc99. Both tracker_resolve.go and cmd/tracker/resolve.go now use strings.ContainsAny(name, "/\\\\")) to detect Windows backslash paths, and a test case for a Windows-style relative path was added to tracker_resolve_test.go.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@clintecker, thanks for the update! The fix in 417cc99 looks correct — using strings.ContainsAny(name, "/\\") ensures Windows-style backslash paths are properly detected as explicit file paths in both locations, and the new test case provides solid coverage for that scenario.


🧠 Learnings used
Learnt from: CR
Repo: 2389-research/tracker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-15T21:59:50.346Z
Learning: Running `tracker <name>` (no path, no extension) resolves in order: 1. `<name>.dip` in cwd, 2. `<name>` as file in cwd, 3. Built-in embedded workflow by name, 4. Error with list of available. This applies to `tracker validate`, `tracker simulate`, and `tracker run` via `resolvePipelineSource()`.

Learnt from: CR
Repo: 2389-research/tracker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-15T21:59:50.346Z
Learning: `pipeline/dippin_adapter.go` converts dippin IR to tracker's Graph model. Every naming mismatch between dippin and tracker conventions lives here. Update the adapter when dippin-lang adds new IR fields.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 417cc99.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
cmd/tracker/resolve.go (1)

29-40: ⚠️ Potential issue | 🟠 Major

Surface non-NotExist filesystem errors before built-in fallback.

Line 30 and Line 34 ignore non-os.ErrNotExist errors. If a local file is unreadable, resolution can incorrectly fall through to built-ins.

Suggested fix
 	dipPath := name + ".dip"
 	if _, statErr := os.Stat(dipPath); statErr == nil {
 		return dipPath, false, WorkflowInfo{}, nil
+	} else if !os.IsNotExist(statErr) {
+		return "", false, WorkflowInfo{}, fmt.Errorf("stat %q: %w", dipPath, statErr)
 	}
 
 	if _, statErr := os.Stat(name); statErr == nil {
 		return name, false, WorkflowInfo{}, nil
+	} else if !os.IsNotExist(statErr) {
+		return "", false, WorkflowInfo{}, fmt.Errorf("stat %q: %w", name, statErr)
 	}

Based on learnings, running tracker <name> must follow strict local-file-first resolution before built-ins.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/tracker/resolve.go` around lines 29 - 40, The current resolution in
cmd/tracker/resolve.go silently ignores non-NotExist errors from os.Stat for
dipPath and name and can fall through to built-ins; update the two os.Stat
checks (the one using dipPath := name + ".dip" and the subsequent os.Stat(name))
so that if statErr != nil and !os.IsNotExist(statErr) you return immediately
with that error (e.g., return "", false, WorkflowInfo{}, statErr) instead of
falling through to tracker.LookupWorkflow; keep the existing return on
successful stat and the existing built-in lookup behavior for os.IsNotExist
errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tracker_resolve.go`:
- Around line 126-127: The os.Stat error handling around the cpPath check
incorrectly labels all errors as "checkpoint not found"; change the logic in the
block that calls os.Stat(cpPath) to distinguish not-exist errors from others: if
os.IsNotExist(err) return the current formatted "checkpoint not found for run
%s: %w" using resolved and err, otherwise return a different error (e.g.,
"failed to stat checkpoint for run %s: %w") including the original err so
permission and I/O errors are preserved; update the code paths that reference
cpPath/resolved accordingly.

---

Duplicate comments:
In `@cmd/tracker/resolve.go`:
- Around line 29-40: The current resolution in cmd/tracker/resolve.go silently
ignores non-NotExist errors from os.Stat for dipPath and name and can fall
through to built-ins; update the two os.Stat checks (the one using dipPath :=
name + ".dip" and the subsequent os.Stat(name)) so that if statErr != nil and
!os.IsNotExist(statErr) you return immediately with that error (e.g., return "",
false, WorkflowInfo{}, statErr) instead of falling through to
tracker.LookupWorkflow; keep the existing return on successful stat and the
existing built-in lookup behavior for os.IsNotExist errors.
🪄 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: CHILL

Plan: Pro

Run ID: a3f1e804-8265-4d9f-bf10-cf713f79b982

📥 Commits

Reviewing files that changed from the base of the PR and between 6d604b3 and 417cc99.

📒 Files selected for processing (3)
  • cmd/tracker/resolve.go
  • tracker_resolve.go
  • tracker_resolve_test.go

Comment thread tracker_resolve.go Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Phase 1 of #76 to close CLI↔library parity gaps by promoting built-in workflow cataloging and name/run resolution into first-class tracker package APIs, and moving embedded workflow assets to a module-root workflows/ directory so both CLI and library share a single source of truth.

Changes:

  • Added library APIs for listing/looking up/opening embedded workflows, plus filesystem-first source resolution and run checkpoint resolution.
  • Added Config.ResumeRunID support in NewEngine to auto-resolve a run ID/prefix into a checkpoint path (without overriding an explicit CheckpointDir).
  • Refactored CLI workflow embedding/loading/init to delegate to the new library APIs; updated tooling/docs to point at workflows/.

Reviewed changes

Copilot reviewed 12 out of 16 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tracker_workflows.go New embedded workflow catalog + open/read helpers for library consumers.
tracker_resolve.go New library helpers for filesystem-first source resolution and checkpoint resolution.
tracker.go Adds Config.ResumeRunID and applies it during NewEngine construction.
tracker_workflows_test.go Tests for workflow catalog APIs (list/sort/copy/lookup/open).
tracker_resolve_test.go Tests for ResolveSource and ResolveCheckpoint.
cmd/tracker/embed.go Replaced CLI embed/catalog logic with thin wrappers over tracker APIs.
cmd/tracker/loading.go Loads embedded workflows via tracker.OpenWorkflow instead of CLI embed FS.
cmd/tracker/resolve.go Uses tracker.LookupWorkflow for built-in resolution and improves path detection.
cmd/tracker/commands.go Uses tracker.OpenWorkflow for init output generation.
workflows/ask_and_execute.dip New embedded workflow asset (moved into shared root location).
workflows/build_product.dip New embedded workflow asset (moved into shared root location).
workflows/build_product_with_superspec.dip New embedded workflow asset (moved into shared root location).
workflows/deep_review.dip New embedded workflow asset (moved into shared root location).
Makefile Updates workflow sync/check targets to use workflows/.
.pre-commit Updates workflow-sync messaging to reference workflows/.
CHANGELOG.md Documents the new library APIs and workflow asset relocation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tracker_workflows.go
if strings.HasPrefix(trimmed, "start:") {
break
}
}
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

parseWorkflowHeader ignores scanner.Err(); if scanning fails mid-file you'll return partial/empty metadata with no signal. It’d be better to check scanner.Err() after the loop and handle/report it (e.g., record a catalog load error) so corrupted embedded workflows don’t fail silently.

Suggested change
}
}
if err := scanner.Err(); err != nil {
return "", ""
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 6bbf237. parseWorkflowHeader now calls scanner.Err() after the scan loop and returns the partially-collected values on error (best-effort semantics on embedded files).

Comment thread tracker_resolve.go Outdated
Comment on lines +15 to +16
// 1. If name contains "/" or ends in ".dip"/".dot", treat it as a filesystem
// path and read it from disk.
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

ResolveSource’s docstring says an explicit path is detected only by containing “/”, but the implementation treats both “/” and “\” as path separators. Update the comment to match the behavior (important for Windows users).

Suggested change
// 1. If name contains "/" or ends in ".dip"/".dot", treat it as a filesystem
// path and read it from disk.
// 1. If name contains "/" or "\" or ends in ".dip"/".dot", treat it as a
// filesystem path and read it from disk.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 6bbf237. The ResolveSource doc comment now explicitly mentions both forward-slash ("/") and back-slash ("") as path indicators.

Comment thread cmd/tracker/resolve.go
@@ -42,7 +44,7 @@ func resolvePipelineSource(name string) (path string, embedded bool, info Workfl

// isExplicitFilePath returns true if name is a file path (contains / or has a .dip/.dot extension).
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

The comment for isExplicitFilePath mentions only “contains /”, but the function also treats backslashes as path separators. Please update the comment so the CLI and library docs accurately describe Windows-style paths.

Suggested change
// isExplicitFilePath returns true if name is a file path (contains / or has a .dip/.dot extension).
// isExplicitFilePath returns true if name is a file path (contains / or \ or has a .dip/.dot extension).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 6bbf237. The CLI resolvePipelineSource doc comment updated to mention both "/" and "" path detection.

Comment thread tracker.go Outdated
@@ -34,6 +34,7 @@ const (
type Config struct {
WorkingDir string // default: os.Getwd()
CheckpointDir string // default: empty (engine auto-generates)
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

Config.CheckpointDir is documented as a “Dir”, but it’s passed to pipeline.WithCheckpointPath and (now) populated with a checkpoint.json file path via ResumeRunID. Updating the field comment to reflect that it’s a checkpoint path (file) would reduce confusion for library consumers.

Suggested change
CheckpointDir string // default: empty (engine auto-generates)
CheckpointDir string // optional: checkpoint file path; default: empty (engine auto-generates)

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 6bbf237. Doc comment now reads "checkpoint file path (checkpoint.json)" instead of "Dir" to accurately reflect the field holds a file path, not a directory.

Comment thread tracker.go
Comment on lines 126 to +151
@@ -130,6 +135,22 @@ func NewEngine(source string, cfg Config) (*Engine, error) {
return buildEngine(graph, cfg, workDir, client, completer)
}

// applyResumeRunID resolves Config.ResumeRunID to a concrete checkpoint path
// and stores it on Config.CheckpointDir. A non-empty CheckpointDir on the
// incoming config is honored as an explicit override — the user is telling
// us exactly which file to use.
func applyResumeRunID(cfg *Config, workDir string) error {
if cfg.ResumeRunID == "" || cfg.CheckpointDir != "" {
return nil
}
cpPath, err := ResolveCheckpoint(workDir, cfg.ResumeRunID)
if err != nil {
return fmt.Errorf("resume run %q: %w", cfg.ResumeRunID, err)
}
cfg.CheckpointDir = cpPath
return nil
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

ResumeRunID introduces new execution behavior in NewEngine (auto-resolving a run ID/prefix to a checkpoint path). tracker_test.go has broad coverage for NewEngine/Config behavior, but there’s no test asserting (a) ResumeRunID populates CheckpointDir when CheckpointDir is empty and (b) an explicit CheckpointDir overrides ResumeRunID. Adding a focused test would prevent regressions.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 6bbf237. Added TestNewEngine_ResumeRunID (round-trip: creates checkpoint layout in temp dir, calls NewEngine with ResumeRunID, verifies engine constructed successfully) and TestNewEngine_ResumeRunID_NotFound (error when run ID doesn't match any run).

Comment thread tracker_workflows.go

entries, err := fs.ReadDir(embeddedWorkflows, "workflows")
if err != nil {
return
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

loadWorkflowCatalog silently drops errors from fs.ReadDir(embeddedWorkflows, "workflows") and returns an empty catalog, which then surfaces as “unknown workflow” rather than an embed/load failure. Consider capturing the error in a package-level var (or panicking, since embed failures are programmer/build errors) so OpenWorkflow / Workflows failures are diagnosable.

Suggested change
return
panic(fmt.Sprintf("tracker: failed to read embedded workflows catalog: %v", err))

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 6bbf237. Added the comment // Embedded FS should never fail; return empty catalog on error. before the fs.ReadDir call to document the intentional silent handling.

clintecker added a commit that referenced this pull request Apr 16, 2026
…test

- parseWorkflowHeader checks scanner.Err() after the loop.
- ResolveSource and isExplicitFilePath doc comments mention both
  forward-slash and backslash path detection.
- Config.CheckpointDir doc says 'path' not 'Dir'.
- loadWorkflowCatalog has an explicit comment documenting the
  intentional silent error handling for embedded FS reads.
- New tests for Config.ResumeRunID round-trip and not-found error.

Refs #76

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
clintecker and others added 5 commits April 16, 2026 14:36
… Phase 1)

Part of issue #76 (CLI<->library parity). This phase exposes the
built-in workflow catalog and run-ID resolution as first-class
library APIs so consumers (factory worker, custom orchestrators,
test harnesses) no longer need to shell out to the tracker CLI or
reimplement directory conventions.

New exports on the tracker package:

  Workflows() []WorkflowInfo
  LookupWorkflow(name) (WorkflowInfo, bool)
  OpenWorkflow(name) ([]byte, WorkflowInfo, error)
  ResolveSource(name, workDir) (source, WorkflowInfo, error)
  ResolveCheckpoint(workDir, runID) (path, error)
  Config.ResumeRunID string  — resolves via ResolveCheckpoint inside NewEngine

Embedded workflows moved from cmd/tracker/workflows/ to workflows/
at the module root so they can be embedded by the tracker package.
The CLI's embed.go, resolve.go, and loadEmbeddedPipeline are now
thin wrappers over the library APIs so there is a single source of
truth for built-in workflows.

ResumeRunID is the library equivalent of the CLI's -r/--resume
flag. Setting Config.ResumeRunID = "abc123" (or a unique prefix)
makes NewEngine call ResolveCheckpoint under the hood and populate
Config.CheckpointDir. An explicit CheckpointDir on the same config
still wins as a manual override — NewEngine never overwrites it.

Tests:
- tracker_workflows_test.go — Workflows/LookupWorkflow/OpenWorkflow
- tracker_resolve_test.go — ResolveSource (filesystem, bare-name,
  local-beats-built-in, not-found) and ResolveCheckpoint (empty,
  found, prefix, ambiguous, missing checkpoint.json)

All 19 new tests pass. Full suite green, dippin doctor A grade on
all three example pipelines.

Refs #76
…handling

- Makefile and .pre-commit updated to use workflows/ (repo root)
  instead of cmd/tracker/workflows/ after the embed relocation.
- ResolveSource: explicit relative paths joined with workDir when
  workDir is set. os.Stat errors other than ErrNotExist returned
  immediately instead of falling through to built-in lookup.
- ResolveCheckpoint: doc comment updated (removed 'absolute' claim).
- Test: guard string slice to prevent panic on short content.

Refs #76
Both tracker_resolve.go and cmd/tracker/resolve.go now check for
backslash in addition to forward-slash. Added test case for a
Windows-style relative path.

Refs #76
ResolveCheckpoint now uses errors.Is(err, os.ErrNotExist) to
report 'checkpoint not found' only when the file is actually
missing. Other stat failures (permission denied, I/O) surface
the real error instead of a misleading 'not found' message.

Refs #76
…test

- parseWorkflowHeader checks scanner.Err() after the loop.
- ResolveSource and isExplicitFilePath doc comments mention both
  forward-slash and backslash path detection.
- Config.CheckpointDir doc says 'path' not 'Dir'.
- loadWorkflowCatalog has an explicit comment documenting the
  intentional silent error handling for embedded FS reads.
- New tests for Config.ResumeRunID round-trip and not-found error.

Refs #76

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@clintecker clintecker force-pushed the feat/cli-library-parity-76-phase1 branch from 6bbf237 to 10f325f Compare April 16, 2026 19:37
@clintecker clintecker merged commit 5b27aa6 into main Apr 16, 2026
2 checks passed
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.

2 participants