AdithyaSK HF Staff commited on
Commit
3949fb1
Β·
1 Parent(s): e265a14

v0.4: surface ALL task files, not just an allowlist

Browse files

The previous version hardcoded six file IDs in HarborTask / _read_task_file /
_build_file_tree (task.toml, instruction.md, solution/patch.diff,
solution/solve.sh, tests/test.sh, environment/Dockerfile). Anything else in a
task dir (e.g. tests/grader.py, environment/pull_bucket.py, helper modules)
was downloaded by snapshot_download but never shown in the UI.

Generalized:

- HarborTask gains a 'files: dict[str, str]' field β€” every readable text
file under the task dir, keyed by POSIX-style relative path.
- parse.py: new _discover_task_files() walks task_dir.rglob('*'), skips
binaries (UTF-8 decode failures), hidden noise (.DS_Store, .git,
__pycache__, .cache, node_modules, .venv, *_cache), and oversized files
(>512 KiB). Convenience fields (instruction_md, oracle_patch, …) kept
populated from the same dict for backwards compat with anything that
reaches in by name.
- app.py: _read_task_file becomes a dict lookup with the two special cases
preserved (task.toml uses task_toml_raw; instruction.md falls back to
the inline 'task.instruction' field).
- app.py: _build_file_tree walks task.files, groups by top-level folder,
orders Overview β†’ top-level files β†’ folders alphabetically β†’ children
alphabetically. Tasks with extra files (e.g. data-agent's grader.py +
pull_bucket.py) now show every artifact faithfully.
- app.py: _EXTENSION_LANGUAGE extended for .py, .md, .json, .yaml/.yml,
.ini/.cfg, .conf, .html/.css/.js/.ts, .txt/.csv/.tsv so non-shell files
render with the right Prism token.

Backwards compatible: existing datasets (e.g. AdithyaSK/click-r2e-v082post1)
keep working β€” same six files surface, just now via the new path.

Files changed (2) hide show
  1. app.py +60 -39
  2. viewer/parse.py +56 -5
app.py CHANGED
@@ -51,6 +51,22 @@ _EXTENSION_LANGUAGE: dict[str, str] = {
51
  ".patch": "python",
52
  ".sh": "shell",
53
  ".bash": "shell",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  }
55
 
56
 
@@ -63,69 +79,74 @@ def _file_language(filename: str) -> str:
63
 
64
 
65
  def _read_task_file(task: HarborTask, file_id: str) -> str | None:
66
- """Fetch the content for a file_id; None when the file isn't present."""
 
 
 
 
 
 
67
  if file_id == "task.toml":
68
  return task.task_toml_raw or None
69
  if file_id == "instruction.md":
70
- return task.instruction_md or task.instruction_inline
71
- if file_id == "solution/patch.diff":
72
- return task.oracle_patch
73
- if file_id == "solution/solve.sh":
74
- return task.solve_sh
75
- if file_id == "tests/test.sh":
76
- return task.test_sh
77
- if file_id == "environment/Dockerfile":
78
- return task.dockerfile
79
- return None
80
 
81
 
82
  def _build_file_tree(task: HarborTask) -> tuple[list[tuple[str, str]], dict[str, str]]:
83
  """Render a file-explorer-style choice list for a task.
84
 
 
 
 
 
 
 
85
  Returns:
86
  choices: list of (label, value) tuples for `gr.Radio`. Labels use unicode
87
  tree glyphs (πŸ“‚, β”œβ”€, └─) so the radio reads like a file tree.
88
  folder_redirects: maps each folder pseudo-id to its first present child
89
  file_id so clicking a folder header opens its first file.
90
-
91
- Order is fixed: Overview first, then top-level files, then folders with
92
- their children. Folders only appear when they have β‰₯1 present child.
93
  """
94
  choices: list[tuple[str, str]] = [("β“˜ Overview", _OVERVIEW)]
95
  redirects: dict[str, str] = {}
96
 
97
- # Top-level files (always under root)
 
 
 
 
 
 
 
 
 
 
 
98
  if (task.task_toml_raw or "").strip():
99
  choices.append(("πŸ“„ task.toml", "task.toml"))
100
- if (task.instruction_md or task.instruction_inline):
101
  choices.append(("πŸ“„ instruction.md", "instruction.md"))
102
-
103
- def _maybe_folder(folder: str, children: list[tuple[str, str]]) -> None:
104
- """Append `πŸ“‚ folder/` + indented children, only if children non-empty.
105
-
106
- `children` is a list of (basename, full_file_id) tuples.
107
- """
108
- present = [(b, fid) for (b, fid) in children if _read_task_file(task, fid) is not None]
109
- if not present:
110
- return
 
 
111
  folder_id = f"{_FOLDER_PREFIX}{folder}"
112
  choices.append((f"πŸ“‚ {folder}/", folder_id))
113
- redirects[folder_id] = present[0][1] # folder header β†’ first child
114
- for i, (basename, full_id) in enumerate(present):
115
- glyph = "└─" if i == len(present) - 1 else "β”œβ”€"
 
 
116
  choices.append((f" {glyph} {basename}", full_id))
117
 
118
- _maybe_folder("solution", [
119
- ("patch.diff", "solution/patch.diff"),
120
- ("solve.sh", "solution/solve.sh"),
121
- ])
122
- _maybe_folder("tests", [
123
- ("test.sh", "tests/test.sh"),
124
- ])
125
- _maybe_folder("environment", [
126
- ("Dockerfile", "environment/Dockerfile"),
127
- ])
128
-
129
  return choices, redirects
130
 
131
 
 
51
  ".patch": "python",
52
  ".sh": "shell",
53
  ".bash": "shell",
54
+ ".py": "python",
55
+ ".json": "json",
56
+ ".yaml": "yaml",
57
+ ".yml": "yaml",
58
+ ".md": "markdown",
59
+ ".markdown": "markdown",
60
+ ".txt": "shell",
61
+ ".csv": "shell",
62
+ ".tsv": "shell",
63
+ ".ini": "yaml",
64
+ ".cfg": "yaml",
65
+ ".conf": "shell",
66
+ ".html": "html",
67
+ ".css": "css",
68
+ ".js": "javascript",
69
+ ".ts": "typescript",
70
  }
71
 
72
 
 
79
 
80
 
81
  def _read_task_file(task: HarborTask, file_id: str) -> str | None:
82
+ """Fetch the content for a file_id; None when the file isn't present.
83
+
84
+ task.toml and instruction.md have special handling (task.toml uses the
85
+ pre-captured raw text; instruction.md falls back to the inline `task.instruction`
86
+ field from task.toml when no instruction.md file is on disk). Everything else
87
+ is a direct lookup against the dict populated by walking the task dir.
88
+ """
89
  if file_id == "task.toml":
90
  return task.task_toml_raw or None
91
  if file_id == "instruction.md":
92
+ return task.files.get("instruction.md") or task.instruction_inline
93
+ return task.files.get(file_id)
 
 
 
 
 
 
 
 
94
 
95
 
96
  def _build_file_tree(task: HarborTask) -> tuple[list[tuple[str, str]], dict[str, str]]:
97
  """Render a file-explorer-style choice list for a task.
98
 
99
+ Walks every file discovered under the task dir (via `task.files`) and groups
100
+ them by their first path segment. Order: Overview β†’ top-level files
101
+ (task.toml, instruction.md, anything else) β†’ folders alphabetically with
102
+ their children alphabetically. No hardcoded allowlist β€” what's on disk is
103
+ what gets shown.
104
+
105
  Returns:
106
  choices: list of (label, value) tuples for `gr.Radio`. Labels use unicode
107
  tree glyphs (πŸ“‚, β”œβ”€, └─) so the radio reads like a file tree.
108
  folder_redirects: maps each folder pseudo-id to its first present child
109
  file_id so clicking a folder header opens its first file.
 
 
 
110
  """
111
  choices: list[tuple[str, str]] = [("β“˜ Overview", _OVERVIEW)]
112
  redirects: dict[str, str] = {}
113
 
114
+ # Bucket every discovered file by top-level dir ("" = at task root)
115
+ top_level: list[str] = []
116
+ by_folder: dict[str, list[str]] = {}
117
+ for path in sorted(task.files):
118
+ if "/" in path:
119
+ folder = path.split("/", 1)[0]
120
+ by_folder.setdefault(folder, []).append(path)
121
+ else:
122
+ top_level.append(path)
123
+
124
+ # Top-level files: task.toml first, then instruction.md (with inline
125
+ # fallback), then anything else alphabetically. Order is presentational.
126
  if (task.task_toml_raw or "").strip():
127
  choices.append(("πŸ“„ task.toml", "task.toml"))
128
+ if (task.files.get("instruction.md") or task.instruction_inline):
129
  choices.append(("πŸ“„ instruction.md", "instruction.md"))
130
+ for path in sorted(top_level):
131
+ if path in ("task.toml", "instruction.md"):
132
+ continue # already added
133
+ choices.append((f"πŸ“„ {path}", path))
134
+
135
+ # Folders alphabetically (environment / solution / tests / ...) with
136
+ # children alphabetical within each.
137
+ for folder in sorted(by_folder):
138
+ children = sorted(by_folder[folder])
139
+ if not children:
140
+ continue
141
  folder_id = f"{_FOLDER_PREFIX}{folder}"
142
  choices.append((f"πŸ“‚ {folder}/", folder_id))
143
+ redirects[folder_id] = children[0] # folder header β†’ first child
144
+ for i, full_id in enumerate(children):
145
+ # Show the path *inside* the folder (handles nested subdirs too)
146
+ basename = full_id[len(folder) + 1:] # strip "<folder>/"
147
+ glyph = "└─" if i == len(children) - 1 else "β”œβ”€"
148
  choices.append((f" {glyph} {basename}", full_id))
149
 
 
 
 
 
 
 
 
 
 
 
 
150
  return choices, redirects
151
 
152
 
viewer/parse.py CHANGED
@@ -61,6 +61,14 @@ class HarborTask:
61
  dockerfile: str | None = None
62
  task_toml_raw: str = ""
63
 
 
 
 
 
 
 
 
 
64
 
65
  def _read_text(path: Path) -> str | None:
66
  """Read a text file, return None if it doesn't exist."""
@@ -73,6 +81,42 @@ def _read_text(path: Path) -> str | None:
73
  return None
74
 
75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  def _discover_task_roots(dataset_root: Path) -> list[Path]:
77
  """Find every directory under `dataset_root` that contains a `task.toml`.
78
 
@@ -136,6 +180,10 @@ def load_task(dataset_root: Path, task_id: str) -> HarborTask:
136
  if repo2env is not None and not isinstance(repo2env, dict):
137
  repo2env = None
138
 
 
 
 
 
139
  return HarborTask(
140
  id=task_id,
141
  root=task_dir,
@@ -150,10 +198,13 @@ def load_task(dataset_root: Path, task_id: str) -> HarborTask:
150
  agent_timeout_sec=agent_block.get("timeout_sec"),
151
  verifier_timeout_sec=verifier_block.get("timeout_sec"),
152
  repo2env=repo2env,
153
- instruction_md=_read_text(task_dir / "instruction.md"),
154
- oracle_patch=_read_text(task_dir / "solution" / "patch.diff"),
155
- solve_sh=_read_text(task_dir / "solution" / "solve.sh"),
156
- test_sh=_read_text(task_dir / "tests" / "test.sh"),
157
- dockerfile=_read_text(task_dir / "environment" / "Dockerfile"),
 
 
158
  task_toml_raw=raw,
 
159
  )
 
61
  dockerfile: str | None = None
62
  task_toml_raw: str = ""
63
 
64
+ # Generic discovery: every readable text file under the task dir,
65
+ # keyed by path relative to the task root. e.g. "tests/grader.py",
66
+ # "environment/pull_bucket.py", "solution/patch.diff". Populated by
67
+ # load_task() walking the directory tree β€” the file viewer surfaces
68
+ # everything in here so the dataset is shown faithfully, not via
69
+ # a hardcoded allowlist.
70
+ files: dict[str, str] = field(default_factory=dict)
71
+
72
 
73
  def _read_text(path: Path) -> str | None:
74
  """Read a text file, return None if it doesn't exist."""
 
81
  return None
82
 
83
 
84
+ # Files we never surface in the file viewer (binaries, caches, secrets).
85
+ _SKIP_DIRS: set[str] = {".git", "__pycache__", ".cache", "node_modules", ".venv", ".pytest_cache", ".mypy_cache"}
86
+ _SKIP_NAME_PREFIXES: tuple[str, ...] = (".DS_Store",)
87
+ # Hard cap on file size β€” anything bigger we treat as non-displayable.
88
+ _MAX_FILE_BYTES = 512 * 1024 # 512 KiB; viewer's code panel chokes well below this
89
+
90
+
91
+ def _discover_task_files(task_dir: Path) -> dict[str, str]:
92
+ """Walk `task_dir` recursively and return every readable text file as
93
+ {relative_path: content}. Skips binaries, hidden noise, and oversized files.
94
+ Paths use forward slashes (POSIX-style) for stable file_ids across OSes.
95
+ """
96
+ out: dict[str, str] = {}
97
+ for path in sorted(task_dir.rglob("*")):
98
+ if not path.is_file():
99
+ continue
100
+ # Skip files inside excluded directories (any level)
101
+ if any(part in _SKIP_DIRS for part in path.relative_to(task_dir).parts):
102
+ continue
103
+ if path.name.startswith(_SKIP_NAME_PREFIXES):
104
+ continue
105
+ try:
106
+ size = path.stat().st_size
107
+ except OSError:
108
+ continue
109
+ if size > _MAX_FILE_BYTES:
110
+ continue
111
+ try:
112
+ content = path.read_text(encoding="utf-8")
113
+ except (UnicodeDecodeError, OSError):
114
+ continue
115
+ rel = path.relative_to(task_dir).as_posix()
116
+ out[rel] = content
117
+ return out
118
+
119
+
120
  def _discover_task_roots(dataset_root: Path) -> list[Path]:
121
  """Find every directory under `dataset_root` that contains a `task.toml`.
122
 
 
180
  if repo2env is not None and not isinstance(repo2env, dict):
181
  repo2env = None
182
 
183
+ # Walk the whole task dir so any present file (grader.py, pull_bucket.py,
184
+ # helper modules, multiple test scripts, ...) ends up in the viewer.
185
+ files = _discover_task_files(task_dir)
186
+
187
  return HarborTask(
188
  id=task_id,
189
  root=task_dir,
 
198
  agent_timeout_sec=agent_block.get("timeout_sec"),
199
  verifier_timeout_sec=verifier_block.get("timeout_sec"),
200
  repo2env=repo2env,
201
+ # Convenience fields β€” kept populated for backwards compat with callers
202
+ # that reach in by name. Source of truth for the viewer is `files`.
203
+ instruction_md=files.get("instruction.md"),
204
+ oracle_patch=files.get("solution/patch.diff"),
205
+ solve_sh=files.get("solution/solve.sh"),
206
+ test_sh=files.get("tests/test.sh"),
207
+ dockerfile=files.get("environment/Dockerfile"),
208
  task_toml_raw=raw,
209
+ files=files,
210
  )