From 94713596521751fde9a876ed319a9a0407e8f918 Mon Sep 17 00:00:00 2001 From: Marius1311 Date: Thu, 15 Oct 2026 09:39:10 +0200 Subject: [PATCH 01/10] Ask for the deliverables; the README is the task's one record; release 0.4.0 No report by default: the agent asks what the task should deliver and records it. Decisions, PRs and issues go in the README; deliverables and PR bodies link to it. Earlier task directories are never edited. Figure naming is left to sci-figures. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011aQpF4MA9v7dTVK1FqMMVJ --- .claude-plugin/plugin.json | 2 +- README.md | 3 +- hooks/session_context.md | 4 +-- skills/analysis-workflow/SKILL.md | 27 ++++++++++----- .../assets/task_template/README.md | 33 ++++++++++++------- 5 files changed, 46 insertions(+), 23 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index a3bccc6..d5780a5 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "analysis-workflow", - "version": "0.3.0", + "version": "0.4.0", "description": "Conventions for analysis repos where humans (notebooks) and coding agents (scripts) work side by side: one task per session, worktree and directory; worktree-safe output paths; additive write-back to AnnData zarr working objects. Ships a skill, guard hooks and the analysis_workflow Python package.", "author": { "name": "quadbio" diff --git a/README.md b/README.md index 8ea035a..87042ea 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,8 @@ claude --worktree 1. The agent works on that worktree's branch, in a new task directory `analysis//_vN/`; a later answer to the same question gets the next version, in a new session. -2. It asks you which working object is current, then works in scripts; outputs go to the task directory. +2. It asks you which working object is current and what the task should deliver (a report, a figure, a write-back, …), + then works in scripts; outputs go to the task directory. 3. Results reach a shared working object only after you sign off the dry run. 4. It opens a PR. Once it is merged and its batch jobs have finished, leave with `/exit` and remove the worktree. diff --git a/hooks/session_context.md b/hooks/session_context.md index ff5a43e..a200e0b 100644 --- a/hooks/session_context.md +++ b/hooks/session_context.md @@ -4,8 +4,8 @@ analysis code, outputs or data. Always: 1. One task = one session = one git worktree = one directory `analysis//.../_vN/`. A new task directory needs a new session, which the human starts with `claude --worktree ` in the main checkout. 2. Every output path comes from `task_paths(__file__)` (package `analysis_workflow`); never a bare relative path. -3. Ask the human which working object (and which label keys) is current; never infer it from a script or the - newest file name. +3. Ask the human which working object (and which label keys) is current, never inferring it from a script or the + newest file name; and what the task should deliver, recorded in its README. 4. Shared objects grow by addition only (`commit_adata`), and only after sign-off on the dry run. 5. Agents never write to `data//results/` or the central `figures/`; those are the human lane. 6. A worktree, and every batch job submitted from it, runs on the main checkout's environment; only the human diff --git a/skills/analysis-workflow/SKILL.md b/skills/analysis-workflow/SKILL.md index 5b6e9c2..5ba2899 100644 --- a/skills/analysis-workflow/SKILL.md +++ b/skills/analysis-workflow/SKILL.md @@ -45,10 +45,13 @@ into the human lane. 1. One task = one session = one git worktree = one branch = one directory `analysis//.../_vN/`, merged through one or more PRs. A new task directory needs a new session, which the human starts with `claude --worktree ` in the main checkout; a later answer to the same question is `_v2`. -2. Copy `assets/task_template/`. +2. Copy `assets/task_template/`. A later version names the one it follows in its README; earlier task directories + are never edited. 3. Ask which working object and which label keys are current, and record them under Inputs. Never infer them from a script or the newest file name. -4. Rename freely until the first write-back or the merge; then the name is frozen, because keys carry it. +4. Ask what the task should deliver: a report, a figure, an HTML page, just results in `outputs/`, a write-back, or + several of these. Record the answer under Deliverables, and any later change to it. +5. Rename freely until the first write-back or the merge; then the name is frozen, because keys carry it. **Run** - Every output path comes from `PATHS = task_paths(__file__)` in `_common.py`; `PATHS.ensure()` in the writer. @@ -57,8 +60,8 @@ into the human lane. | dir | tracked | lives in | holds | | --- | --- | --- | --- | | `results/` | yes | this checkout | small evidence tables; past ~1 MB they belong in `outputs/` | - | `reports/` | yes | this checkout | the findings report | - | `figures/` | no | main checkout | `fig1_.pdf` … in reading order | + | `reports/` | yes | this checkout | a report, if one is a deliverable | + | `figures/` | no | main checkout | figures | | `outputs/` | no | main checkout | data artifacts | | `logs/` | no | main checkout | batch-job logs, passed as an absolute path | @@ -69,16 +72,24 @@ into the human lane. to submit them is the cluster's skill. - Large single-cell computations (neighbors, UMAP, clustering on ≳100k cells) use rapids-singlecell over scanpy, in a GPU job. -- A figure that prints a number may carry `scripts/verify.py` re-deriving it. A code package's invariants belong in - that package's tests. +- A deliverable that states a number may carry `scripts/verify.py` re-deriving it. A code package's invariants + belong in that package's tests. + +**Record.** Each fact has one home. The README is the task's record: its deliverables, inputs, decisions (who made +each, and why), write-back, how to run it, and every PR and issue the session opened, in any repo. Findings live in +the deliverables; they and the PR bodies link to the README instead of restating it. **Finish** -1. Report, and the README: Inputs, Outputs, Write-back, Running, Notes. +1. The agreed deliverables, and the README brought up to date. 2. Write back: dry run, sign-off on that diff, then `yes=True`. 3. An object other tasks will load is promoted to `data//processed/` at sign-off. Ask which subdirectory; record the path. 4. `analysis-workflow check layout`, then push. -5. The worktree is removed only once no batch job submitted from it is still queued or running. +5. Open the PR and add it to the README. Its body links the task README and says what this PR adds or changes, by + deliverable; what needs the human: a write-back or promotion to sign off, decisions the agent took alone, open + questions; and the PRs and issues it depends on. Method, parameters and numbers stay in the README and the + deliverables. +6. The worktree is removed only once no batch job submitted from it is still queued or running. ## Data diff --git a/skills/analysis-workflow/assets/task_template/README.md b/skills/analysis-workflow/assets/task_template/README.md index 9ef6947..43b6687 100644 --- a/skills/analysis-workflow/assets/task_template/README.md +++ b/skills/analysis-workflow/assets/task_template/README.md @@ -2,7 +2,17 @@ **Date:** -One or two sentences on what this task set out to answer. +One or two sentences on what this task set out to answer. A later version names the one it follows and what it +changes. + +## Deliverables + +Agreed with the human at the start; a later change is agreed and recorded here too. + +| deliverable | where | +| --- | --- | +| | `reports/` | +| | Write-back, below | ## Inputs @@ -11,14 +21,14 @@ Name the actual files, not concepts: this is what makes the task re-runnable. - Working object: `data//processed/.../.zarr`, and the label keys used - Other artifacts consumed: `analysis//outputs/`, `data//processed/` -## Outputs +## Decisions -| what | where | -| --- | --- | -| evidence tables | `results/` (tracked) | -| report | `reports/` (tracked) | -| figures | `figures/` (gitignored, in the main checkout) | -| data artifacts | `outputs/` (gitignored, in the main checkout) | +Load-bearing parameters, what was tried and rejected, and the diagnostics that changed the method. A decision not +yet taken has the value `open`. + +| decision | value | by | why | +| --- | --- | --- | --- | +| | | | | ## Write-back @@ -41,7 +51,8 @@ $PY scripts/.py LOGS=$($PY -c "from _common import PATHS; print(PATHS.ensure().logs)") ``` -## Notes +## PRs and issues + +Every PR and issue this session opened, in any repo, added when opened. -Decisions a reader would otherwise have to reverse-engineer: what was tried and rejected, which parameters are -load-bearing, what is still provisional. +- `#`: one line on what it is From 3c06a0101232edd0e9a095d6c8025addc0f8c6bd Mon Sep 17 00:00:00 2001 From: Marius1311 Date: Thu, 15 Oct 2026 09:42:31 +0200 Subject: [PATCH 02/10] Push the README again after adding the PR to it Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011aQpF4MA9v7dTVK1FqMMVJ --- skills/analysis-workflow/SKILL.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/skills/analysis-workflow/SKILL.md b/skills/analysis-workflow/SKILL.md index 5ba2899..ea8c3a9 100644 --- a/skills/analysis-workflow/SKILL.md +++ b/skills/analysis-workflow/SKILL.md @@ -85,10 +85,10 @@ the deliverables; they and the PR bodies link to the README instead of restating 3. An object other tasks will load is promoted to `data//processed/` at sign-off. Ask which subdirectory; record the path. 4. `analysis-workflow check layout`, then push. -5. Open the PR and add it to the README. Its body links the task README and says what this PR adds or changes, by - deliverable; what needs the human: a write-back or promotion to sign off, decisions the agent took alone, open - questions; and the PRs and issues it depends on. Method, parameters and numbers stay in the README and the - deliverables. +5. Open the PR, then add it to the README and push again. Its body links the task README and says what this PR + adds or changes, by deliverable; what needs the human: a write-back or promotion to sign off, decisions the agent + took alone, open questions; and the PRs and issues it depends on. Method, parameters and numbers stay in the + README and the deliverables. 6. The worktree is removed only once no batch job submitted from it is still queued or running. ## Data From e5da9ec7e4e36cec49e27ae71603a588b1d38bfa Mon Sep 17 00:00:00 2001 From: Marius1311 Date: Thu, 15 Oct 2026 10:44:50 +0200 Subject: [PATCH 03/10] Fixed task layout, PR bodies hold the details, a deliverables setting A hook holds task directories and READMEs to the task template, read from the template itself. The PR body, laid out by the analysis template's PR template, holds a PR's details; the README summarizes each PR and lists the session's jobs. Analysis moves forward: earlier task directories are never edited. `deliverables` sets the default a task proposes. Figure and compute-environment rules leave the plugin, and what the analysis template owns is linked, not restated. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011aQpF4MA9v7dTVK1FqMMVJ --- .claude-plugin/plugin.json | 10 +- README.md | 37 +++---- hooks/guard_task_layout.py | 103 ++++++++++++++++++ hooks/hooks.json | 7 +- hooks/session_context.md | 3 +- hooks/session_start.py | 2 + skills/analysis-workflow/SKILL.md | 68 +++++------- .../assets/task_template/README.md | 50 +++++---- .../task_template/{ => scripts}/_common.py | 0 skills/analysis-workflow/references/repos.md | 28 +++-- .../analysis-workflow/references/storage.md | 9 +- tests/test_hooks.py | 61 +++++++++++ 12 files changed, 270 insertions(+), 108 deletions(-) create mode 100644 hooks/guard_task_layout.py rename skills/analysis-workflow/assets/task_template/{ => scripts}/_common.py (100%) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index d5780a5..117afbd 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -7,5 +7,13 @@ }, "homepage": "https://github.com/quadbio/analysis-workflow", "license": "MIT", - "keywords": ["single-cell", "spatial-transcriptomics", "anndata", "zarr", "git-worktree", "pixi", "scverse"] + "keywords": ["single-cell", "spatial-transcriptomics", "anndata", "zarr", "git-worktree", "pixi", "scverse"], + "userConfig": { + "deliverables": { + "type": "string", + "title": "Default deliverables", + "description": "What a task delivers unless you choose otherwise at its start, e.g. \"a figure\" or \"a figure and a report\". Blank: the agent asks.", + "default": "a figure" + } + } } diff --git a/README.md b/README.md index 87042ea..2a98c5a 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ shared AnnData zarr *working object* per dataset that grows by additive write-ba | Part | What it does | | --- | --- | | `skills/analysis-workflow/` | The conventions: repo model, human and agent lanes, task lifecycle, `data/` layout, working objects, environments. Loaded on demand. | -| `hooks/` | Always-on core rules and the main checkout's environment at session start; blocks worktree-unsafe output paths and `pixi install` in a worktree. | +| `hooks/` | Always-on core rules and the main checkout's environment at session start; blocks worktree-unsafe output paths, `pixi install` in a worktree, and task directories off the template. | | `src/analysis_workflow/` | `task_paths`, `main_checkout`, `commit_adata`, `keys_on_disk`, `analyze_anndata_size`, and `analysis-workflow check layout`. | It assumes analysis repos made from [analysis_template](https://github.com/quadbio/analysis_template) @@ -25,11 +25,12 @@ claude --worktree ``` 1. The agent works on that worktree's branch, in a new task directory `analysis//_vN/`; a later - answer to the same question gets the next version, in a new session. -2. It asks you which working object is current and what the task should deliver (a report, a figure, a write-back, …), - then works in scripts; outputs go to the task directory. + answer to the same question gets the next version, in a new session. Earlier task directories are never edited. +2. It asks you which working object is current and agrees what the task delivers (by default your `deliverables` + setting), then works in scripts; outputs go to the task directory. 3. Results reach a shared working object only after you sign off the dry run. -4. It opens a PR. Once it is merged and its batch jobs have finished, leave with `/exit` and remove the worktree. +4. It opens a PR whose body holds the details; the task README summarizes each PR. Once it is merged and its batch + jobs have finished, leave with `/exit` and remove the worktree. Notebooks stay with you, in the main checkout. A task that changes the environment locks, installs and tests it in a temporary checkout beside the main one; after its merge you pull and run `pixi install` there. With no @@ -39,26 +40,16 @@ analysis repo yet, ask Claude to start an analysis project: the skill sets one u **The plugin**, once per machine, from the [quadbio marketplace](https://github.com/quadbio/claude-plugins). The skill is then available everywhere; the hooks and session-start rules act only in repos that enable the plugin in -their committed `.claude/settings.json`, as [analysis_template](https://github.com/quadbio/analysis_template) does: - -```json -{ - "extraKnownMarketplaces": { - "quadbio": { "source": { "source": "github", "repo": "quadbio/claude-plugins" } } - }, - "enabledPlugins": { "analysis-workflow@quadbio": true }, - "permissions": { "additionalDirectories": ["../-lock"] } -} -``` - -`` is the main checkout's directory name: a task changing the environment locks it in `../-lock`. +their committed `.claude/settings.json`. Every repo made from +[analysis_template](https://github.com/quadbio/analysis_template) does, and depends on the package in its +`pixi.toml` (its lock pins the commit; `pixi update` moves it). To adopt an existing repo, ask Claude to adopt the +conventions. -**The package**, in the analysis repo's `pixi.toml` (its lock pins the commit; `pixi update` moves it): +**Settings**, saved with `/plugin configure analysis-workflow@quadbio`: -```toml -[pypi-dependencies] -analysis-workflow = { git = "https://github.com/quadbio/analysis-workflow" } -``` +| Setting | Default | Meaning | +| --- | --- | --- | +| `deliverables` | `a figure` | What a task delivers unless you choose otherwise at its start; recorded in the task README. Blank: the agent asks. | ## Develop diff --git a/hooks/guard_task_layout.py b/hooks/guard_task_layout.py new file mode 100644 index 0000000..34b8998 --- /dev/null +++ b/hooks/guard_task_layout.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +"""Keep every task directory in the template's layout. + +A task directory (`analysis//.../_vN/`) holds at its top only what the task template holds +(`skills/analysis-workflow/assets/task_template/`) and the output directories of `task_paths`. Its README's title +is the directory's name, and its other headings are the template README's, all of them and in order. Both are read +from their sources, the template and `paths.py`, so a change there is a change here. Only the text this call would +leave in the file is checked. + +PreToolUse hook (Edit|Write|NotebookEdit): exit 2 blocks the call and shows stderr to the agent. +Standard library only: it runs on whatever `python3` is on PATH. +""" + +import importlib.util +import json +import re +import sys +from pathlib import Path + +from _repo import adopted, checkouts + +PLUGIN = Path(__file__).parents[1] +TEMPLATE = PLUGIN / "skills" / "analysis-workflow" / "assets" / "task_template" +TASK_DIR = re.compile(r".+_v\d+$") +HEADING = re.compile(r"^(#{1,6})\s+(.*?)\s*#*\s*$") +FENCE = re.compile(r"^\s*(```|~~~)") + + +def output_dirs() -> set[str]: + """The task output directories, from the package's `paths.py` (loaded alone: the package may not be installed).""" + spec = importlib.util.spec_from_file_location("_aw_paths", PLUGIN / "src" / "analysis_workflow" / "paths.py") + paths = importlib.util.module_from_spec(spec) + spec.loader.exec_module(paths) + return {*paths.TRACKED_TASK_DIRS, *paths.UNTRACKED_TASK_DIRS} + + +def headings(text: str) -> list[tuple[int, str]]: + """(level, text) of each Markdown heading outside fenced code blocks.""" + found, fenced = [], False + for line in text.splitlines(): + if FENCE.match(line): + fenced = not fenced + elif not fenced and (m := HEADING.match(line)): + found.append((len(m.group(1)), m.group(2))) + return found + + +def after(tool_input: dict, path: Path) -> str | None: + """The file's text once this Write or Edit is applied; None if it cannot be known.""" + if "content" in tool_input: + return str(tool_input["content"]) + try: + text = path.read_text() + except OSError: + return None + old, new = str(tool_input.get("old_string", "")), str(tool_input.get("new_string", "")) + return text.replace(old, new) if tool_input.get("replace_all") else text.replace(old, new, 1) + + +def task_dir(path: Path) -> Path | None: + """The task directory ``path`` is in: its nearest ancestor under `analysis/` named `_vN`.""" + for parent in path.parents: + if parent.name == "analysis": + return None + if TASK_DIR.match(parent.name): + return parent + return None + + +def problem(path: Path, task: Path, tool_input: dict) -> str | None: + """What is off-layout about this call, or None.""" + top = path.relative_to(task).parts[0] + allowed = {p.name for p in TEMPLATE.iterdir()} | output_dirs() + if top not in allowed: + return f"`{top}` is not part of a task directory, whose top holds only {', '.join(sorted(allowed))}." + if path != task / "README.md" or (text := after(tool_input, path)) is None: + return None + expected = [(1, task.name), *((n, h) for n, h in headings((TEMPLATE / "README.md").read_text()) if n > 1)] + actual = headings(text) + if actual == expected: + return None + show = "\n".join(f" {'#' * n} {h}" for n, h in expected) + return f"the README must have exactly the template's headings, titled with the directory's name:\n{show}" + + +def main() -> int: + """Read the hook payload from stdin; return the exit code.""" + tool_input = json.load(sys.stdin).get("tool_input", {}) + path = Path(tool_input.get("file_path") or tool_input.get("notebook_path") or "") + if "analysis" not in path.parts or (task := task_dir(path)) is None: + return 0 + found = checkouts(task) + if not found or not adopted(found[0]): + return 0 + if (why := problem(path, task, tool_input)) is None: + return 0 + print(f"Refused: in {task.name}/, {why}", file=sys.stderr) + print("See the analysis-workflow skill (Tasks) and its task template.", file=sys.stderr) + return 2 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/hooks/hooks.json b/hooks/hooks.json index 9fb85c7..a969c36 100644 --- a/hooks/hooks.json +++ b/hooks/hooks.json @@ -1,5 +1,5 @@ { - "description": "analysis-workflow, in repos that enable it: always-on core rules, plus guards against worktree-unsafe output paths and pixi installs.", + "description": "analysis-workflow, in repos that enable it: always-on core rules, plus guards against worktree-unsafe output paths, pixi installs and task READMEs off the template.", "hooks": { "SessionStart": [ { @@ -30,6 +30,11 @@ "type": "command", "command": "python3 \"${CLAUDE_PLUGIN_ROOT}/hooks/guard_task_paths.py\"", "timeout": 10 + }, + { + "type": "command", + "command": "python3 \"${CLAUDE_PLUGIN_ROOT}/hooks/guard_task_layout.py\"", + "timeout": 10 } ] } diff --git a/hooks/session_context.md b/hooks/session_context.md index a200e0b..99db7d2 100644 --- a/hooks/session_context.md +++ b/hooks/session_context.md @@ -3,9 +3,10 @@ analysis code, outputs or data. Always: 1. One task = one session = one git worktree = one directory `analysis//.../_vN/`. A new task directory needs a new session, which the human starts with `claude --worktree ` in the main checkout. + Analysis only moves forward: earlier task directories are never edited, fixed or updated. 2. Every output path comes from `task_paths(__file__)` (package `analysis_workflow`); never a bare relative path. 3. Ask the human which working object (and which label keys) is current, never inferring it from a script or the - newest file name; and what the task should deliver, recorded in its README. + newest file name; and agree what the task delivers (a default, if set, is listed below), recorded in its README. 4. Shared objects grow by addition only (`commit_adata`), and only after sign-off on the dry run. 5. Agents never write to `data//results/` or the central `figures/`; those are the human lane. 6. A worktree, and every batch job submitted from it, runs on the main checkout's environment; only the human diff --git a/hooks/session_start.py b/hooks/session_start.py index d382e2c..ab1d6ea 100644 --- a/hooks/session_start.py +++ b/hooks/session_start.py @@ -69,6 +69,8 @@ def main() -> int: print(f"\nEvery Bash command runs on `{variables['CONDA_PREFIX']}`: run `python …` directly.") else: print(f"\nNo environment is active: run `pixi run --manifest-path {main_checkout}/pixi.toml --as-is …`.") + if deliverables := os.environ.get("CLAUDE_PLUGIN_OPTION_DELIVERABLES", "").strip(): + print(f"Default deliverables, from the plugin settings: {deliverables}.") return 0 diff --git a/skills/analysis-workflow/SKILL.md b/skills/analysis-workflow/SKILL.md index ea8c3a9..da0be2d 100644 --- a/skills/analysis-workflow/SKILL.md +++ b/skills/analysis-workflow/SKILL.md @@ -18,14 +18,14 @@ Steps: `references/repos.md`. Elsewhere: - the repo's `AGENTS.md`: its datasets, where its working objects live, its environments. It wins over examples here; -- compute and job submission: the environment's own docs or skill; -- **every scientific plot: the `sci-figures` skill**; cell-type annotation: `cell-type-annotation`; +- figures, analysis methods, and the compute environment with its job submission: their own docs or skills. + Nothing here covers them, and these conventions hold on any machine; - why the storage rules are what they are: `references/storage.md`. ## Where code goes -- Science code that several tasks need goes to a code repo, plotting code included. Figure code is copied only - between versions of one task. The analysis repo's own package holds `FilePaths`, dataset versioning and a CLI. +- Science code that several tasks need goes to a code repo; a new version of a task copies what it reuses from the + one it follows. The analysis repo's own package holds `FilePaths`, dataset versioning and a CLI. - A task that changes a code repo does so in a worktree of that repo, added from the same session: `git -C worktree add .claude/worktrees/ -b `. - Look before writing: existing packages (scverse, then its ecosystem), sibling code repos, earlier task directories. @@ -36,26 +36,21 @@ Elsewhere: ## Two lanes Humans work in notebooks, flat under `analysis//`, writing to `data//results/` and -`figures//`; notebook outputs are committed. Agents work in task directories, with scripts, and never write -into the human lane. +`figures//`; notebook outputs are committed. Agents work in task directories, with scripts. ## Tasks **Start** -1. One task = one session = one git worktree = one branch = one directory `analysis//.../_vN/`, merged - through one or more PRs. A new task directory needs a new session, which the human starts with - `claude --worktree ` in the main checkout; a later answer to the same question is `_v2`. -2. Copy `assets/task_template/`. A later version names the one it follows in its README; earlier task directories - are never edited. -3. Ask which working object and which label keys are current, and record them under Inputs. Never infer them from a - script or the newest file name. -4. Ask what the task should deliver: a report, a figure, an HTML page, just results in `outputs/`, a write-back, or - several of these. Record the answer under Deliverables, and any later change to it. -5. Rename freely until the first write-back or the merge; then the name is frozen, because keys carry it. +1. A task is one branch, merged through one or more PRs; a later answer to the same question is the next version, + in a new session. +2. Copy `assets/task_template/` into the task directory. +3. Settle what session rule 3 asks, the working object with its label keys and the deliverables, and record both in + the README. Deliverables are any of a report, a figure, an HTML page, just results in `outputs/` and a + write-back; start from the default the session start lists, if any, which the human may change for this task. +4. Rename freely until the first write-back or the merge; then the name is frozen, because keys carry it. **Run** -- Every output path comes from `PATHS = task_paths(__file__)` in `_common.py`; `PATHS.ensure()` in the writer. - A bare relative path written from a worktree dies with the worktree. +- `PATHS = task_paths(__file__)` lives in `scripts/_common.py`; call `PATHS.ensure()` in the writer. | dir | tracked | lives in | holds | | --- | --- | --- | --- | @@ -67,28 +62,26 @@ into the human lane. - Another task's untracked outputs: `task_paths().outputs`. Read the main checkout by absolute path; never `cd` into it. -- Batch-job scripts sit in `scripts/` beside the code they run. They get the main checkout as `$MAIN` from the - submit command, never hardcoded, run `pixi run --manifest-path "$MAIN/pixi.toml" --as-is`, and log to `PATHS.logs`. How - to submit them is the cluster's skill. -- Large single-cell computations (neighbors, UMAP, clustering on ≳100k cells) use rapids-singlecell over scanpy, in - a GPU job. +- Scripts go in `scripts/`: the steps as `NN_.py`, numbered in run order, and batch-job scripts beside the + code they run. A batch job gets the main checkout as `$MAIN` from the submit command, never hardcoded, and logs to + `PATHS.logs`; how to submit it is the compute environment's concern. - A deliverable that states a number may carry `scripts/verify.py` re-deriving it. A code package's invariants belong in that package's tests. -**Record.** Each fact has one home. The README is the task's record: its deliverables, inputs, decisions (who made -each, and why), write-back, how to run it, and every PR and issue the session opened, in any repo. Findings live in -the deliverables; they and the PR bodies link to the README instead of restating it. +**Record.** The task README, in the task template's layout, is the overview, kept current. The deliverables hold the +findings; the README links to them rather than restating them. The details of each PR go in its body, laid out by +the repo's `.github/pull_request_template.md`; the README gives each PR, and each issue the session opened in any +repo, a short summary. **Finish** -1. The agreed deliverables, and the README brought up to date. +1. The agreed deliverables, and the README brought up to date, its jobs counted with the compute environment's + tools. 2. Write back: dry run, sign-off on that diff, then `yes=True`. 3. An object other tasks will load is promoted to `data//processed/` at sign-off. Ask which subdirectory; record the path. 4. `analysis-workflow check layout`, then push. -5. Open the PR, then add it to the README and push again. Its body links the task README and says what this PR - adds or changes, by deliverable; what needs the human: a write-back or promotion to sign off, decisions the agent - took alone, open questions; and the PRs and issues it depends on. Method, parameters and numbers stay in the - README and the deliverables. +5. Open the PR, its body from the PR template; then add it with a short summary to the README and push again. The + body is edited until the merge, never after. 6. The worktree is removed only once no batch job submitted from it is still queued or running. ## Data @@ -113,11 +106,10 @@ the deliverables; they and the PR bodies link to the README instead of restating ## Environments - The root `pixi.toml` is shared; reusability decides what goes in. A benchmark's candidates go task-local. -- A worktree runs on the main checkout's environment; only the human installs it, in the main checkout, one - session at a time. The session-start hook activates it for every Bash command, subagents' included: run - `python …` directly. Batch jobs, and anything run outside a session, use - `pixi run --manifest-path
/pixi.toml --as-is …`; without `--as-is`, pixi relocks or installs into main - whenever its manifest or lock is ahead of the environment. +- The human installs the main checkout's environment one session at a time. The session-start hook activates it + for every Bash command, subagents' included: run `python …` directly. Anything run outside a session uses + `pixi run --manifest-path
/pixi.toml --as-is …`, as batch jobs do; without `--as-is`, pixi relocks or + installs into main whenever its manifest or lock is ahead of the environment. - A task that changes `pixi.toml` locks, installs and tests it in a temporary checkout beside the main one, where the relative path dependencies resolve; main's environment stays untouched. Commit `pixi.toml`, then `git worktree add --detach
-lock HEAD` (`
` is the main checkout's path), and pass @@ -136,5 +128,5 @@ the deliverables; they and the PR bodies link to the README instead of restating ## Review -Each repo keeps a `REVIEW_GUIDE.md` for reviewers that cannot load this plugin; its generic half is -[the template's](https://github.com/quadbio/analysis_template/blob/main/%7B%7Bcookiecutter.project_name%7D%7D/REVIEW_GUIDE.md). +A repo's PR template and the generic half of its `REVIEW_GUIDE.md`, for reviewers that cannot load this plugin, +come from [the analysis template](https://github.com/quadbio/analysis_template/tree/main/%7B%7Bcookiecutter.project_name%7D%7D). diff --git a/skills/analysis-workflow/assets/task_template/README.md b/skills/analysis-workflow/assets/task_template/README.md index 43b6687..a4d2dce 100644 --- a/skills/analysis-workflow/assets/task_template/README.md +++ b/skills/analysis-workflow/assets/task_template/README.md @@ -1,49 +1,42 @@ -# +# **Date:** -One or two sentences on what this task set out to answer. A later version names the one it follows and what it -changes. + + + ## Deliverables -Agreed with the human at the start; a later change is agreed and recorded here too. + | deliverable | where | | --- | --- | -| | `reports/` | +| | `figures/` (gitignored, in the main checkout) | | | Write-back, below | ## Inputs -Name the actual files, not concepts: this is what makes the task re-runnable. + - Working object: `data//processed/.../.zarr`, and the label keys used - Other artifacts consumed: `analysis//outputs/`, `data//processed/` -## Decisions - -Load-bearing parameters, what was tried and rejected, and the diagnostics that changed the method. A decision not -yet taken has the value `open`. - -| decision | value | by | why | -| --- | --- | --- | --- | -| | | | | - ## Write-back -What went into the working object, and under which keys. The version in this directory's name appears in every -key, so a key can be traced back here by grepping the task READMEs. + - `obs["_"]`: one line on what it holds -- `obsm["X__"]`: likewise - -Anything promoted to `data//processed/` at sign-off is recorded here with its final path. ## Running -From the task directory, on the main checkout's environment. Batch jobs get `MAIN=
` at submit and -log to `PATHS.logs`: + ```bash PY="pixi run --manifest-path
/pixi.toml --as-is python" @@ -51,8 +44,17 @@ $PY scripts/.py LOGS=$($PY -c "from _common import PATHS; print(PATHS.ensure().logs)") ``` +## Jobs + + + +| queue | launched | succeeded | failed | cancelled | +| --- | --- | --- | --- | --- | +| | | | | | + ## PRs and issues -Every PR and issue this session opened, in any repo, added when opened. + -- `#`: one line on what it is +- `#`: one or two lines on what it did diff --git a/skills/analysis-workflow/assets/task_template/_common.py b/skills/analysis-workflow/assets/task_template/scripts/_common.py similarity index 100% rename from skills/analysis-workflow/assets/task_template/_common.py rename to skills/analysis-workflow/assets/task_template/scripts/_common.py diff --git a/skills/analysis-workflow/references/repos.md b/skills/analysis-workflow/references/repos.md index 48d47a9..515249b 100644 --- a/skills/analysis-workflow/references/repos.md +++ b/skills/analysis-workflow/references/repos.md @@ -5,35 +5,33 @@ | template | `https://github.com/quadbio/analysis_template` | `https://github.com/scverse/cookiecutter-scverse` | | managed with | pixi | uv | -`uvx cruft update` pulls template changes later, including the generic half of `REVIEW_GUIDE.md`. +The analysis template's README says how to create a project from it and update it later. ## Creating a project -Ask the human for the project name, a one-line description, the author, and where the repos go (default: beside -their other repos). Then: +Ask the human for the answers to the template's prompts (its `cookiecutter.json`) and where the repos go (default: +beside their other repos). Then: -1. Analysis repo: `uvx cruft create