Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

### Fixed

- Prevent explicit `./` and `../` prose references from passing through an unrelated package's suffix match while retaining existing repository-root/AGENTS-scope interpretations and accepting explicit document-relative paths in other formats. Preserve significant hidden-directory dots and skip references with no in-repository interpretation before index lookup.
- Stop scans on directory traversal errors rather than silently using a partial path index. Scan/compare/feedback report operational exit `2` without success output; `doctor` exposes a structured `repository-paths` error. Raw traversal messages are not echoed, and excluded dependency/admin trees remain pruned.
- Avoid `dead-path` false positives for narrowly typed repository/remote slugs and immediate closed `(if exists)` prose references, without exempting other local paths, actual Markdown links or dangerous commands.
- Keep real package-relative source paths beneath `build`/`dist`/`target`/`.next` in the existence index while preserving instruction-discovery exclusions. Prune dependency/admin trees before indexing and do not follow directory symlinks or index external/broken link entries. English, Simplified and Traditional limitation notes stay aligned.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ The tool and development script have recorded source fingerprints. Package
version `0.23.0` alone does not identify this **unreleased** rule state; this is
not a scan with the immutable published v0.23.0 wheel.

This retained run was completed on **2026-10-11** and identifies that day's
development source, not every subsequent `main` revision. Later path/operational
fixes do not retroactively update this report. Reproduction with newer code must
use a new output path and remain explicitly development-only.

| Scope | Files | Frozen findings | Development findings |
|---|---:|---:|---:|
| Native discovery | 27 | 8 `dead-path`, 1 `context-large` | 1 `context-large` |
Expand Down
8 changes: 8 additions & 0 deletions docs/doctor.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The command is read-only and offline. It checks:
- whether `.agentconfigscore.json` exists and parses successfully;
- whether the config uses the canonical AgentConfigScore JSON Schema;
- whether supported coding-agent instruction files are discoverable;
- whether the repository path index can be traversed without a filesystem error;
- whether the directory is inside a Git repository for `diff` workflows;
- whether `agent-config-score diff` can automatically resolve a safe local baseline;
- whether the standard GitHub Actions workflow exists and invokes AgentConfigScore correctly;
Expand All @@ -20,6 +21,13 @@ When automatic baseline detection succeeds, doctor reports the selected ref, for

Warnings are advisory and keep exit status `0`. Concrete configuration or standard-workflow errors return a non-zero exit status.

The `repository-paths` check reports an error if index traversal fails, for
example because a source directory cannot be read. Fix the access problem before
scanning; an incomplete index must not be treated as a clean result or evidence
that a file is missing. Excluded dependency/admin trees are not traversed by this
index check. It checks path metadata only, not instruction quality, tracked-file
membership, or an atomic snapshot; access can change after the check completes.

For scripts and CI:

```bash
Expand Down
20 changes: 20 additions & 0 deletions docs/limitations.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,19 @@ Markdown validator.

## Plain prose path references

Backtick file references starting with `./` or `../` are checked against the
repository root and applicable instruction-directory bases. Nested AGENTS-family
files retain their in-repository ancestor interpretations; other formats also
accept an explicit document-relative reference. These paths cannot pass merely
because an unrelated package has a matching suffix. Significant leading dots,
such as `.github`, are preserved. If no interpretation stays inside the repository,
the reference is skipped rather than probed outside or substituted with an
indexed path. Actual Markdown links remain strictly document-relative.

Unprefixed prose still uses conservative package-relative suffix matching.
Neither mode infers the working directory of a described command, follows `cd`
instructions, or proves which interpretation the author intended.

File-like prose references are heuristic, not a complete natural-language path
resolver. An immediate, closed `(if exists)` or `(if it exists)` condition makes
only that prose reference optional; Markdown link destinations remain checked.
Expand All @@ -108,6 +121,13 @@ or intended by an ambiguous reference. Generated/untracked files not present in
the checkout can still be reported missing; filesystem case behavior remains
host-dependent. These exemptions do not disable command or credential checks.

Directory traversal errors while building the index stop the scan with an
operational error, not a partial clean report or inferred missing path. CLI scan,
compare and feedback return exit `2` without a success report; `doctor` exposes
an error check named `repository-paths` and returns `1`. Dependency/admin trees
excluded from the index remain pruned. This is a local filesystem check, not a
Git-tracked-only index or an atomic filesystem snapshot.

## Red-team audit

The project added the following adversarial cases after testing the v0.18.0
Expand Down
6 changes: 6 additions & 0 deletions docs/simplified/limitations.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,18 @@ AgentConfigScore 的分数衡量已经检测到的、由规则定义的风险,

## 普通散文中的路径

反引号中的 `./`、`../` 文件路径按仓库根目录及适用的指令目录核对;嵌套 AGENTS 文件保留仓库内祖先目录的解释,其他格式也接受明确的文档相对路径。这些路径不会因无关包有相同后缀而通过。`.github` 等名称中的开头圆点会保留;所有解释都逃出仓库时,不访问外部路径,也不拿索引中的其他文件代替。真正的 Markdown 链接仍严格相对于文档目录。

无前缀的散文路径仍使用保守的包相对后缀匹配。两种模式都不推断命令的工作目录、不执行或跟踪 `cd` 指令,也不证明哪一种解释符合作者意图。

只在紧邻路径且闭合的 `(if exists)`、`(if it exists)` 条件下,把对应散文路径视为可选;括号内 `-` 或 `:` 后可带简短解释。真正的 Markdown 链接仍检查目标。同一行的其他路径、远处的“可选”字样、不闭合的条件不会因此豁免。

`owner/tool.js` 这类两段名称,只在明确的同一行仓库角色语法中跳过:后接 `repository`/`remote` 及标点,或前接 `merge target is`、`fork of`、`re-pushed to a branch in`、`CI trusts branches inside`;也识别相同仓库名的 `fork of ... and not ... itself` 形式。仅提到 GitHub 或仓库,不会让本地路径免检。其他改写和语言仍可能误判。[完整英文边界](../limitations.md#plain-prose-path-references)。

包相对路径的后缀索引包含 `build`、`dist`、`target`、`.next` 中已经存在的路径,但不会因此扩大指令文件的发现范围。依赖、管理目录仍排除在后缀索引外;不遍历目录符号链接,也不索引外部或失效链接。存在不代表内容正确或路径意图明确;未生成的文件仍可能被报告缺失,大小写判断依赖宿主文件系统。这些豁免不会关闭危险命令或凭据检查。

构建索引时的目录遍历错误会停止扫描,不把部分索引当作干净结果或文件缺失的证据。扫描、`compare`、`feedback` 返回 `2`,不输出成功报告;`doctor` 给出 `repository-paths` 错误项并返回 `1`。已排除的依赖、管理目录仍不遍历。这是本地文件系统检查,不是仅限 Git 跟踪文件的索引,也不是原子文件系统快照。

## 回归保护的具体范围

- 没有支持的指令文件会产生 `no-config`;空文件会产生 `empty-instructions`,不会显示 A 等级。
Expand Down
1 change: 1 addition & 0 deletions docs/simplified/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ agent-config-score feedback . --output .agent-config-score/case.md
|---|---|
| 找不到命令 | 确认装入当前 Python 环境,并检查 Scripts/bin 的 PATH。v0.23.0+ 可用 `python -m agent_config_score`。 |
| 配置或工作流编码错误 | 保存为 UTF-8,不要用 UTF-16 文件替代。 |
| `Unable to read repository path index` | 运行 `doctor`,核对 `repository-paths` 错误并修复目录访问或 I/O 问题。部分索引不会生成成功报告。 |
| 报告不可写 | 选择可写的文件路径,不是已有目录;检查父目录权限。 |
| 无法检测基线 | 运行 `doctor`,然后指定本地已有的默认分支引用。 |
| 禁止示例被报为危险指令 | 对照[支持的禁止语法(英文)](../instruction-context.md)检查具体上下文;工具不理解任意语言和改写。 |
Expand Down
6 changes: 6 additions & 0 deletions docs/traditional/limitations.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,18 @@ AgentConfigScore 的分數衡量已偵測到、由規則定義的風險,不衡

## 一般敘述中的路徑

反引號中的 `./`、`../` 檔案路徑依儲存庫根目錄及適用的指令目錄檢查;巢狀 AGENTS 檔保留儲存庫內祖先目錄的解釋,其他格式也接受明確的文件相對路徑。這些路徑不會因無關套件有相同後綴而通過。`.github` 等名稱的開頭圓點會保留;所有解釋都離開儲存庫時,不存取外部路徑,也不以索引中的其他檔案替代。實際的 Markdown 連結仍嚴格依文件目錄解析。

無前綴的敘述路徑仍使用保守的套件相對後綴匹配。兩種模式都不推論命令的工作目錄、不執行或追蹤 `cd` 指令,也不證明哪一種解釋符合作者意圖。

只在緊鄰路徑且閉合的 `(if exists)`、`(if it exists)` 條件下,將對應敘述路徑視為選用;括號內 `-` 或 `:` 後可附簡短說明。實際的 Markdown 連結仍檢查目標。同一行的其他路徑、遠處的「選用」字樣、未閉合條件不會因此豁免。

`owner/tool.js` 這類兩段名稱,只在明確的同一行儲存庫角色語法中略過:後接 `repository`/`remote` 及標點,或前接 `merge target is`、`fork of`、`re-pushed to a branch in`、`CI trusts branches inside`;也識別相同儲存庫名稱的 `fork of ... and not ... itself` 形式。僅提到 GitHub 或儲存庫,不會讓本機路徑免檢。其他改寫及語言仍可能誤判。[完整英文界線](../limitations.md#plain-prose-path-references)。

套件相對路徑的後綴索引包含 `build`、`dist`、`target`、`.next` 中已存在的路徑,但不會因此擴大指令檔的探索範圍。相依套件、管理目錄仍排除於後綴索引之外;不遍歷目錄符號連結,也不索引外部或失效連結。存在不代表內容正確或路徑意圖明確;未產生的檔案仍可能被回報缺失,大小寫判斷依賴宿主檔案系統。這些豁免不會關閉危險命令或認證資訊檢查。

建立索引時的目錄遍歷錯誤會停止掃描,不將部分索引當成乾淨結果或檔案缺失的證據。掃描、`compare`、`feedback` 返回 `2`,不輸出成功報告;`doctor` 提供 `repository-paths` 錯誤項並返回 `1`。已排除的相依套件、管理目錄仍不遍歷。這是本機檔案系統檢查,不是僅限 Git 追蹤檔案的索引,也不是原子檔案系統快照。

## 回歸保護的具體範圍

- 沒有支援的指令檔會產生 `no-config`;空白檔案會產生 `empty-instructions`,不會顯示 A 等級。
Expand Down
1 change: 1 addition & 0 deletions docs/traditional/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ agent-config-score feedback . --output .agent-config-score/case.md
|---|---|
| 找不到命令 | 確認安裝在目前 Python 環境,並檢查 Scripts/bin 的 PATH。v0.23.0+ 可用 `python -m agent_config_score`。 |
| 設定或工作流程編碼錯誤 | 儲存為 UTF-8,不要以 UTF-16 檔案取代。 |
| `Unable to read repository path index` | 執行 `doctor`,檢查 `repository-paths` 錯誤並修正目錄存取或 I/O 問題。部分索引不會產生成功報告。 |
| 報告無法寫入 | 選擇可寫入的檔案路徑,不是既有目錄;檢查父目錄權限。 |
| 無法偵測基準 | 執行 `doctor`,再指定本機已存在的預設分支參照。 |
| 禁止範例被判為危險指令 | 對照[支援的禁止語法(英文)](../instruction-context.md)檢查上下文;工具不理解任意語言和改寫。 |
Expand Down
7 changes: 7 additions & 0 deletions docs/translations.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ path-reference changes built on source revision
roles, path-index/discovery separation, symlink and host-case limits. This is an
unreleased synchronized source update, not a change to the original v0.23.0 tag.

On 2026-10-14, both guides and limitation summaries were reviewed together with
the English explicit-relative-path and index-I/O changes built on source revision
`ef733582a47233c96cdc76b0017b9880f415e5da`. The existing root/AGENTS scope
interpretations remain, arbitrary suffix substitution is disabled for explicit
relative references, and index read failures stay operational/doctor errors.
Commands and the published v0.23.0 artifacts remain unchanged.

When an English source page changes, review both variants together and record
the new source revision here. Preserve executable examples, rule IDs and JSON
keys verbatim. Keep the score limitations and failure/exit semantics aligned;
Expand Down
1 change: 1 addition & 0 deletions docs/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ Nothing is uploaded. Review and sanitize the draft yourself before sharing.
| Command is not found | Install into the active Python environment; confirm its Scripts/bin directory is on PATH. In v0.23.0+, use `python -m agent_config_score` as a PATH-independent alternative. |
| Configuration / workflow encoding error | Save the file as UTF-8; do not copy a UTF-16 file into the integration. |
| Cannot write HTML, SARIF or Markdown | Choose a writable file path, not an existing directory; inspect parent-directory permissions. |
| `Unable to read repository path index` | Run `doctor` and check its `repository-paths` error. Fix directory access or I/O failures; no success report is emitted from a partial index. |
| `diff` cannot detect a baseline | Check `doctor`, then pass an available default-branch ref explicitly. |
| A prohibited command was flagged | Compare its exact context with the [supported prohibition grammar](instruction-context.md); arbitrary languages and paraphrases are not understood. |
| Historic GitHub run is red | Open the run for the current commit. Fixing a later commit does not erase an earlier failed run. |
Expand Down
14 changes: 13 additions & 1 deletion src/agent_config_score/doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from .config import CONFIG_NAME, SCHEMA_URL, ConfigError, load_policy
from .gitdiff import GitError, detect_base_ref, repository_root
from .scanner import discover
from .scanner import _repository_path_index, discover


@dataclass(frozen=True)
Expand Down Expand Up @@ -167,6 +167,18 @@ def diagnose(root: Path) -> DoctorReport:
"No supported coding-agent instruction files were discovered.",
))

try:
_repository_path_index(root)
except OSError:
checks.append(DoctorCheck(
"repository-paths", "error",
"Could not build the repository path index; check filesystem access before scanning.",
))
else:
checks.append(DoctorCheck(
"repository-paths", "pass", "Repository path index is readable.",
))

try:
git_root = repository_root(root)
except GitError as exc:
Expand Down
27 changes: 22 additions & 5 deletions src/agent_config_score/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -793,7 +793,10 @@ def _candidate_exists(
candidate: str,
repository_paths: set[str] | None = None,
) -> bool | None:
# Repository-root relative paths remain the primary interpretation. For a
candidate = candidate.replace("\\", "/")
explicit_relative = candidate.startswith(("./", "../"))
# Prose can describe commands run from the repository root rather than
# from the instruction directory. Preserve that root interpretation. For a
# nested AGENTS-family file, also accept a path relative to that file's
# directory, which is the root of its instruction scope.
bases = [root]
Expand All @@ -805,6 +808,10 @@ def _candidate_exists(
while current != root:
bases.append(current)
current = current.parent
elif explicit_relative and source.parent != root:
# Explicit relative notation may also be document-relative in other
# formats. Only these concrete bases count, not an arbitrary suffix.
bases.append(source.parent)

checked = False
for base in bases:
Expand All @@ -814,19 +821,29 @@ def _candidate_exists(
checked = True
if target.exists():
return True
if repository_paths is not None:
normalized = candidate.lstrip("./")
if not checked:
return None
if not explicit_relative and repository_paths is not None:
# lstrip('./') removes characters, not a path prefix: it previously
# erased the significant dot in .github and dots in parent traversals.
normalized = PurePosixPath(candidate).as_posix()
if any(path == normalized or path.endswith("/" + normalized) for path in repository_paths):
return True
return False if checked else None
return False


def _path_index_error(exc: OSError) -> None:
# A partial index is not evidence that a referenced path is missing. Keep
# source-controlled path names and raw filesystem messages out of the CLI.
raise OSError(exc.errno, "Unable to read repository path index") from exc


def _repository_path_index(root: Path) -> set[str]:
root = root.resolve()
paths: set[str] = set()
# Prune dependency/admin trees before descent, and never follow directory
# symlinks. This indexes names only; it does not read generated source files.
for directory, dirs, files in os.walk(root, followlinks=False):
for directory, dirs, files in os.walk(root, followlinks=False, onerror=_path_index_error):
descend = []
for name in dirs:
if name in PATH_INDEX_SKIP_DIRS:
Expand Down
Loading
Loading