PerFilePromptGenerator: relative filePattern resolves against process.cwd() instead of the config file directory

未关闭
#64 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

评估

难度
3/5
预计耗时
1-2 天
新手友好度
72/100
Issue 类型
缺陷
描述清晰度
基本清楚
活跃度
冷清
技术栈
typescript
领域
cli, tooling

调研方向

Start in src/prompt-generators/per-file.ts, especially PerFilePromptGenerator.generate() and resolveFiles(), then review src/prompt-generators/test/per-file.test.ts. Add coverage for a relative pattern with an explicit basePath and run the per-file tests. Done means config-relative files are found regardless of the invocation directory, with resumed IDs remaining consistent across runs.

由索引模型根据 Issue 内容生成。

描述

bug S3

Observed behavior

PerFilePromptGenerator accepts a basePath constructor argument used to resolve {{include:...}} macros, and the CLI loader threads the config-file directory through as that basePath. However, filePattern (and excludePatterns) are passed directly to glob() without a cwd option, so a relative filePattern is resolved against process.cwd() rather than the config file directory.

Source: src/prompt-generators/per-file.ts:62-79

constructor(task: PerFileTask, basePath?: string) {
  this.#task = task;
  this.#basePath = basePath ?? process.cwd();
}

async *generate(loopState: LoopState): AsyncIterable<Prompt> {
  const { filePattern, excludePatterns } = this.#task;
  const allFiles = await resolveFiles(filePattern, excludePatterns);
  // ...
}

And src/prompt-generators/per-file.ts:85-97:

export async function resolveFiles(
  filePattern: string,
  excludePatterns?: Array<string>,
): Promise<Array<string>> {
  const files = await glob(filePattern, {
    ...(excludePatterns ? { ignore: excludePatterns } : {}),
    nodir: true,
  });
  files.sort();
  return files;
}

Note that #basePath is captured but never used inside generate() for the glob call. The glob library defaults its cwd option to process.cwd().

This is inconsistent with the rest of the CLI config loader:

per-file.filePattern is the odd one out.

Expected behavior

A relative filePattern in a CLI JSON config should resolve against the directory containing the config file, the same as outputDir, {{include:...}} macros, and JsonTask.dataFile. A user writing:

{ "promptGenerator": ["per-file", { "filePattern": "src/**/*.ts", "promptTemplate": "Review {{file}}" }] }

in /some/where/config.json naturally expects the loop to enumerate /some/where/src/**/*.ts, not ${cwd}/src/**/*.ts.

Minimal reproduction

mkdir -p /tmp/llt-pf-repro/proj/src
cat > /tmp/llt-pf-repro/proj/src/hello.ts <<'TS'
export const hello = 'world';
TS

cat > /tmp/llt-pf-repro/proj/config.json <<'JSON'
{
  "name": "demo",
  "agent": "claude-sdk",
  "promptGenerator": [
    "per-file",
    { "filePattern": "src/**/*.ts", "promptTemplate": "Review {{file}}" }
  ]
}
JSON

mkdir -p /tmp/llt-pf-repro/elsewhere
cd /tmp/llt-pf-repro/elsewhere
pnpx loop-the-loop --dry-run /tmp/llt-pf-repro/proj/config.json
# Yields zero prompts: glob looks for /tmp/llt-pf-repro/elsewhere/src/**/*.ts.
# Expected: one prompt for src/hello.ts, since the config lives next to src/.

For comparison, running the same command from /tmp/llt-pf-repro/proj does match the file, because process.cwd() happens to coincide with the config directory in that case.

Suggested fix

Thread #basePath through to glob's cwd option:

export async function resolveFiles(
  filePattern: string,
  excludePatterns?: Array<string>,
  cwd?: string,
): Promise<Array<string>> {
  const files = await glob(filePattern, {
    ...(cwd ? { cwd } : {}),
    ...(excludePatterns ? { ignore: excludePatterns } : {}),
    nodir: true,
  });
  files.sort();
  return files;
}

and in generate():

const allFiles = await resolveFiles(filePattern, excludePatterns, this.#basePath);

glob's cwd option is a no-op when the pattern is already absolute, so callers that pass absolute paths (including the existing tests in src/prompt-generators/__test__/per-file.test.ts) keep working. A new test should cover the relative case with an explicit basePath.

One subtlety: with cwd set, glob returns paths relative to cwd. The current id (used by LoopState) is whatever glob returns. If we want IDs to remain stable across runs regardless of the invocation cwd, the fix should also normalize the returned paths (e.g. by resolving to absolute, or by joining with basePath) so that the resumed IDs match a fresh run from a different directory.

主要语言
TypeScript
星标
2
派生
1
PR 合并指标
30 天内没有已合并 PR

环境准备

这个项目没有提供开发容器、Dockerfile 或贡献指南,环境需要你自己搭建:先看它的 README,通用步骤见我们的新手贡献指南。

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

joewalker/loop-the-loop 的其他 Issue

查看 joewalker/loop-the-loop 的全部 Issue

相似的 Issue

更多 TypeScript Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。