git-tasks
Structured, line-pinned annotations that live inside your Git repository — designed to improve team collaboration and deployment pipelines.
Most context about a codebase lives outside it: in tickets, chats, PR comments that get archived, and // TODO: strings that nobody can query. git-tasks brings that context back into the repo as structured JSON, pinned to the exact lines it describes, so the whole development cycle can read and act on it.
Why it's different by design
- Annotations travel with the code. They're plain JSON files under
.git-tasks/, committed alongside the source. Clone the repo and every task, comment, and issue is already there, pinned to the right line. Branches carry their own annotations. Merges merge the notes.
- Pinned to ranges, not floating tickets. Every entry stores
(file, lineRange, commitSHA, lineContent snapshot). When the surrounding code moves or changes, git-tasks shows a drift warning instead of silently rotting the reference.
- Diffable, reviewable, blameable. Adding or resolving a note is a normal commit — it shows up in code review,
git blame tells you who wrote it, and git log is the audit trail.
- No server, no login, no lock-in. There is nothing to provision. The data is in the repo; the tools just read and write it.
Built for three audiences at once
Humans
A VS Code extension surfaces annotations as gutter icons, hover tooltips, and a sidebar grouped by file — with an accent ring on anything assigned to you. The git-tasks CLI gives the same view in any terminal, with --mine, --type, --status, and --json filters for everyday workflows.
AI coding agents
The schema is stable, fully documented, and machine-readable. An agent can:
git-tasks list --json --assignee <agent-id> --status open to discover work assigned to it,
- read the
lineContent, commitSHA, and text to understand the exact context,
- make the change,
git-tasks update <id> --status resolved to close the loop —
all without an API key, a webhook, or an external service. The repo is the queue, the queue is the repo.
CI/CD pipelines
Because annotations are committed JSON, pipelines can treat them as first-class signals:
- block a release if any
severity: critical issue is open,
- post counts per type/priority to a dashboard on every push,
- enforce "no unresolved tasks touching
src/payments/** on merge to main",
- auto-assign by tag, or notify on Slack when an issue is added in a hot path.
No bot account, no API rate limits — just a jq over a folder of JSON files.
What it's for
git-tasks is for enhancing the development cycle, not for managing projects. It's the layer that captures and acts on the small-grained, code-attached context that today gets lost in chat threads, dropped TODOs, and stale review comments — and exposes it equally to the developer in their editor, the agent doing the work, and the pipeline shipping it.
Install
VS Code extension
git clone <this repo>
cd git-tasks
npm install
npm run compile
Then press F5 in VS Code to launch an Extension Development Host with the extension loaded.
This works because the repo includes .vscode/launch.json configured with an extensionHost debug target. VS Code reads the contributes section of package.json (which declares the commands, sidebar view, and gutter decorations) and loads the compiled output from out/ — no marketplace publish required. The Extension Development Host is a second VS Code window that runs the extension locally, exactly as it would if it were installed.
CLI
npm i @ribarrat/git-tasks
During development (in the repo root):
npm run compile
npm link # symlinks `git-tasks` → out/cli/index.js globally
git-tasks list # works in any terminal from here on
To remove the symlink when done: npm unlink git-tasks.
Testing
The repo has a vitest suite covering the core annotation engine, the git helper, and the CLI surface most exercised by CI integrations.
npm test # compile + run the whole suite
npm run test:watch # iterative dev loop
npm run test:coverage
What's covered:
| Layer |
File |
Approach |
Annotation engine (src/taskManager.ts) |
test/taskManager.test.ts |
Unit tests for the pure helpers (extractLineContent, findSnapshotIn, softMatchSnapshot, three-way merge) and on-disk lifecycle (addEntry → updateEntry → removeEntry, reconcileAll against a real temp repo). |
Git helper (src/gitHelper.ts) |
test/gitHelper.test.ts |
Runs against a real temp git init repo — no mocks — to verify findRepoRoot, getCurrentCommitSHA, isCurrentUser, etc. |
CLI (cli/commands/*) |
test/cli.test.ts |
Shells out to the compiled out/cli/index.js inside a temp repo and asserts exit codes, JSON shape, and on-disk state. Focused on the commands that pipelines depend on: list, show, update, remove, reconcile, check, diff. |
VS Code extension surface (gutterProvider, sidebarProvider, hoverProvider, fileWatcher) is intentionally not covered — it's thin glue over the engine, and reliable VS Code UI testing requires a full @vscode/test-electron host that costs more to maintain than it pays back here.
Tests scaffold isolated temp repos via test/helpers.ts:
- each
makeTempRepo() does a real git init in os.tmpdir(), configures a fixed identity, and seeds an initial commit;
GIT_CONFIG_GLOBAL/GIT_CONFIG_SYSTEM are pinned to /dev/null so the suite never reads or mutates the developer's global git config;
cleanup() removes the temp directory in afterEach.
A pretest npm hook re-runs tsc so the CLI integration tests always exercise the latest compiled output; CI does the same via npm run compile followed by npm test.
Usage
VS Code
- Select one or more lines in any file inside a Git repository.
- Run Git Tasks: Add Annotation from the command palette (or the right-click menu).

- Pick a type, enter your text, set priority/severity/assignee/tags.





- The annotation appears as a gutter icon and in the Git Tasks sidebar panel.


Icons in the gutter:
| Icon |
Meaning |
| ● (blue) |
task |
| 💬 (gray) |
comment |
| ◆ (red) |
issue |
| ⚠ (amber) |
drift — file content has changed since the annotation was written |
| accent ring |
annotation is assigned to you (matched against git config user.name/user.email) |
Issue example:

Task example:

CLI
# Add a single-line task
git-tasks add src/auth.js 42 --type task --text "Missing index" --priority high
# Add a range issue
git-tasks add src/auth.js 42-48 --type issue --text "Refactor this block" --severity major
# List everything
git-tasks list
# Only annotations assigned to me
git-tasks list --mine
# Show one annotation in full
git-tasks show a1b2c3
# Update status
git-tasks update a1b2c3 --status resolved
# Remove
git-tasks remove a1b2c3 --force
Run git-tasks --help for full reference.
Storage / schema
Each annotated source file gets its own JSON file under .git-tasks/, mirroring its path:
.git-tasks/
src/
utils/
auth.js.json
{
"version": "1.2",
"file": "src/utils/auth.js",
"entries": [
{
"id": "<uuid-v4>",
"type": "task", // task | comment | issue
"commitSHA": "<40-char SHA>",
"line": 42, // current position, updated by reconcile
"endLine": 48, // optional: only on multi-line selections
"lineContent": "const user = ...", // snapshot of the lines at `line`
"text": "This query has no index — could be slow under load",
"description": "Confirmed on staging: 1.2s p99 …", // optional, longer context
"author": "Riccardo Barrat",
"assignee": "alice@example.com", // optional
"createdAt": "2026-06-16T10:00:00Z",
"updatedAt": "2026-06-16T10:00:00Z",
"status": "open", // open | in-progress | resolved | closed
"priority": "high", // high | medium | low
"severity": "major", // critical | major | minor | trivial
"tags": ["perf", "security"],
"origin": { // where it was first pinned; never rewritten
"line": 17,
"lineContent": "const user = ...",
"commitSHA": "<40-char SHA>"
},
"drift": { // optional; present only while drift is unresolved
"kind": "stale", // soft-match | stale | orphan
"detectedAt": "2026-06-20T09:00:00Z",
"line": 42,
"lineContent": "const user = ...",
"suggestedLine": 61 // soft-match only; a candidate, never applied
}
}
]
}
Commit the .git-tasks/ folder into your repo so teammates can pull and immediately see all annotations.
CLI reference
| Command |
What it does |
git-tasks add <file> <line>[-<endLine>] --type … --text … [--description …] |
Add a single- or multi-line annotation |
git-tasks list [<file>] [--type] [--status] [--priority] [--assignee] [--mine] [--json] |
List annotations, with filters |
git-tasks show <id> |
Show one annotation in full |
git-tasks update <id> [--text] [--description] [--status] [--priority] [--severity] [--assignee] [--tags] |
Update fields (--description "" clears it) |
git-tasks remove <id> [--force] |
Delete |
git-tasks reconcile [--dry-run] [--quiet] [--json] |
Relocate drifted annotations; report stale / orphan |
git-tasks check [--fail-on …] [--fail-on-open-severity …] [--base <ref>] [--format json] |
CI gate over reconcile + open-severity rules |
git-tasks diff --base <ref> [--json] [--github-annotations] |
List annotations on files changed since <ref>; emit GitHub Checks inline annotations |
git-tasks stats [--sla-days <n>] [--fail-on-aged-critical] [--json] |
Density + SLA report aggregated by file, type, status, severity |
git-tasks purge [--status resolved,closed] [--older-than <days>] [--apply] [--force] [--json] |
Bulk-delete terminal tasks. Dry-run by default; --apply to actually delete |
git-tasks install-hooks / uninstall-hooks |
Manage pre-commit / post-merge / post-checkout hooks |
git-tasks install-merge-driver / uninstall-merge-driver |
Manage the three-way JSON merge driver for .git-tasks/**/*.json |
Output
Rows where assignee matches the current git user are prefixed with → and bolded.
Title and description
text is the one-line title shown in the gutter hover, the sidebar, and list. description is optional and holds the longer context — why the task exists, acceptance criteria, links.
Descriptions are markdown, and may span multiple lines:
p99 is 1.2s on staging under the reporting load.
- [ ] add a covering index on (tenant_id, created_at)
- [ ] re-measure before closing
Dashboard: [grafana](https://example.com/d)
A single newline renders as a line break (not folded away as plain markdown would), and blank-line paragraphs, lists, quotes and fenced code blocks all render as written. Markdown links are clickable in the hover.
Writing one in VS Code. The description prompt is a normal input box for a quick one-liner. Since Enter submits an input box, anything longer goes through the pencil button → "Edit in editor…", which opens a scratch DESCRIPTION.md pre-filled with the current text and a short instruction banner:
- Save the file (⌘S / Ctrl+S) to apply it. The banner is stripped automatically; your own HTML comments are kept.
- Close it without saving to leave the description unchanged — the rest of the task is preserved, not discarded.
The buffer lives in a temp directory and is cleaned up either way; the task itself is written to .git-tasks/, never to the temp file.
Writing one from the CLI. --description accepts embedded newlines, and --description-file reads a file (or - for stdin), which is usually easier from a script:
git-tasks add src/db.js 42 --type issue --text "Missing index" \
--description-file notes.md
git-tasks update 4ba96e --description-file - <<'EOF'
Confirmed on staging.
See [dashboard](https://example.com/d).
EOF
--description "" clears the field.
A note on trust. Task files arrive over git pull, so their text is written by whoever committed it. The hover renders user content with HTML escaped and scopes clickable command: links to git-tasks' own five commands, so a [click me](https://github.com/ribarrat/git-tasks/blob/HEAD/command:…) link injected into a task body does nothing. Sidebar tooltips render task text literally, without markdown.
How drift detection works
When you create an annotation, the lines covered by it are stored verbatim in lineContent. If the file is later edited and the live content of those lines no longer matches, the gutter icon switches to an amber ⚠ and the hover tooltip notes that the lines may have moved.
Original vs. current position
An annotation carries two positions:
line / lineContent — where it lives now. Reconcile updates these as the code moves, so the gutter icon always sits next to the right line.
origin — where it was first pinned (line, endLine, lineContent, commitSHA), captured at creation and never rewritten afterwards.
That split is what stops routine editing from degrading a task. Inserting lines above an annotation shifts it, but the relocation is bookkeeping, not loss: git-tasks show prints both positions, the hover reads "Line 42 · originally 17", and the sidebar appends (from L17). The original commit is still on record, so git show <origin.commitSHA>:<file> recovers the exact context the note was written against.
Entries created before schema 1.1 have no origin. The first time such an entry relocates, its current position is frozen as the origin — the oldest position that can still be proven.
When relocation happens
Drift is only a problem for as long as it goes uncorrected. Reconcile runs automatically at four points:
| Trigger |
Covers |
Needs |
| On save (VS Code) |
the file you are editing right now |
nothing — built into the extension |
pre-commit hook |
everything staged in a commit |
git-tasks install-hooks |
post-merge / post-checkout hooks |
pulls, merges, branch switches |
git-tasks install-hooks |
| Merge driver |
entries whose lines moved in a merge that also edited the task file |
git-tasks install-merge-driver |
The merge driver reconciles positions against the worktree copy of the source right after merging the JSON, and skips the file if git left conflict markers in it.
One caveat worth knowing: git only invokes a merge driver when both sides modified that same .git-tasks/*.json file. Line drift usually comes from source edits alone, which leave the task file untouched on one side — git then resolves it trivially and the driver never runs. So the driver is a useful extra pass, not the safety net; post-merge is what actually covers merges, which is why install-hooks matters more than install-merge-driver for keeping drift down.
If the hooks are missing, the VS Code extension offers to install them once per workspace (or run Git Tasks: Install Git Hooks from the command palette). Hooks run in a bare shell, so the git-tasks CLI must be installed globally for them to work.
Surviving PR merges and conflicts
The whole point of pinning annotations to (file, lineRange, commitSHA, lineContent) is that they can heal themselves across merges. Four CLI subcommands cover the lifecycle:
git-tasks reconcile
For every entry, compare the live file against lineContent:
- Match → nothing to do.
- Snapshot found elsewhere in the file → relocate the annotation to the new lines and bump
updatedAt. Auto-applied.
- Snapshot only ~partially found (≥70% line-LCS) → reported as
soft-match; not auto-applied. Review and resolve manually or via an AI agent.
- Snapshot no longer found → reported as
stale.
- Source file deleted → reported as
orphan.
git-tasks reconcile # default: apply moves, report the rest
git-tasks reconcile --dry-run # report only, write nothing
git-tasks reconcile --json # machine-readable for CI / agents
Exit code is 1 if anything is stale or orphan, so CI can fail on it.
git-tasks install-hooks
Writes a managed block into .git/hooks/post-merge and .git/hooks/post-checkout. Each hook runs git-tasks reconcile --auto --quiet after every git pull or branch checkout, so line shifts heal automatically. The block is idempotent and marked with # >>> git-tasks (managed). Remove with git-tasks uninstall-hooks.
git-tasks install-merge-driver
Registers a custom three-way merge driver for .git-tasks/**/*.json. When both your branch and theirs add or edit entries in the same annotation file, git would otherwise produce a textual conflict in the JSON. With the driver installed:
- New entries on either side are unioned by
id.
- Entries edited on only one side take that side.
- Entries edited on both sides do field-wise merge (last
updatedAt wins for scalar fields; tags are unioned).
- A real structural conflict (deleted on one side, edited on the other) still falls back to git's normal markers.
The installer registers merge.git-tasks-json.driver in .git/config and appends .git-tasks/**/*.json merge=git-tasks-json to .gitattributes. Commit .gitattributes so the driver applies for every collaborator. Remove with git-tasks uninstall-merge-driver.
Drift is recorded, not fatal
Annotations are metadata about code, not a property of it, so drift in them must never stop a build. When reconcile finds an entry it cannot confidently repin, it records the fact on the entry itself and exits 0:
{
"id": "c0ffee01-…",
"line": 351,
"lineContent": " threshold = 0.7,",
"drift": {
"kind": "stale", // soft-match | stale | orphan
"detectedAt": "2026-09-06T12:00:00.000Z",
"line": 351, // where it was pinned when drift was seen
"lineContent": " threshold = 0.7,",
"suggestedLine": 402 // soft-match only — a candidate, never applied
}
}
The mark is derived state: every reconcile recomputes it, and it is cleared automatically the moment the entry pins cleanly again or is successfully relocated. detectedAt is preserved while the same drift persists, so it answers since when. Marking deliberately does not touch updatedAt, so a machine-written mark can never win the merge driver's last-writer-wins tiebreaker against a human edit.
Review what is drifted with git-tasks list --drifted, or in the extension — hovers show the kind and the date.
Nothing in CI fails on this by default. Two explicit opt-ins exist for teams that want the old behavior:
git-tasks reconcile --strict — exit 1 when drift could not be auto-resolved. This is what the local pre-commit hook uses.
git-tasks check --fail-on stale,orphan — exit 1 on the listed conditions.
git-tasks check
Designed for pull_request CI. Reports drift / soft-match / stale / orphan counts plus any recorded drift marks. Exits 0 unless you ask it to fail:
# report only — the default
git-tasks check
# opt in: fail if any annotations are stale or orphaned
git-tasks check --fail-on stale,orphan
# fail if the PR touches a file with an open critical annotation
git-tasks check --fail-on-open-severity critical --base origin/main
# machine-readable (includes a `drifted` array of recorded marks)
git-tasks check --format json
GitHub Actions snippet
# .github/workflows/git-tasks.yml
name: git-tasks
on: [pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm i -g git-tasks
# Report drift; only the critical-severity policy can fail the PR.
- run: git-tasks check --fail-on-open-severity critical --base origin/${{ github.base_ref }}
Suggested one-time setup for a team
git-tasks install-hooks
git-tasks install-merge-driver
git add .gitattributes
git commit -m "git-tasks: enable auto-reconcile on merge"
After that, line shifts heal silently on pull, concurrent annotation edits merge without textual conflicts, drift that cannot heal is marked on the entry, and CI reports rather than blocks.
install-hooks writes three managed blocks:
| Hook |
Behavior |
pre-commit |
Auto-relocates drifted annotations, marks what it cannot relocate, and re-stages them. Runs reconcile --strict, so it blocks the commit if anything is stale or orphan — deliberately local-only friction; bypass with --no-verify. |
post-merge |
Runs reconcile --auto --quiet after every git pull, so line shifts that landed on the remote heal locally on the next pull. |
post-checkout |
Same as post-merge, but on branch checkout. |
How this repo dogfoods git-tasks
This repository uses git-tasks on itself, so cloning it is also a working demo and a copy-pasteable template.
Seeded annotations (.git-tasks/)
Four real entries pinned to current source — every CI integration below operates on this data:
| File:line |
Type |
Tags |
Showcases |
src/taskManager.ts:364 |
task |
enhancement, good-first-issue, onboarding |
AI-agent + onboarding workflow (assigned to claude) |
src/taskManager.ts:734 |
comment |
merge-driver, onboarding |
Annotation pinned to the line it describes — reorganising the merge driver triggers drift on this entry |
cli/commands/check.ts:59 |
task |
enhancement, cli, onboarding |
AI-agent task with a concrete implementation hint |
cli/invocation.ts:14 |
issue (severity: major) |
windows, portability |
CI's --fail-on-open-severity critical --base … gate (triggers when a PR also edits this file) |
Try git-tasks list --mine, git-tasks list --tag onboarding, or git-tasks list --tag merge-driver after cloning.
Workflows (.github/workflows/)
| File |
When |
What it does |
ci.yml → build |
every push / PR |
tsc build |
ci.yml → pre-merge-check |
PRs |
git-tasks check (report only — drift never blocks a PR), blocks PRs that introduce open critical issues on the diff, emits GitHub Checks inline annotations for every entry on a changed file, and posts a sticky PR comment with the full report |
ci.yml → post-merge-audit |
push to main |
runs reconcile on main and commits the settled annotations back in a bot commit; anything unresolvable is left carrying a drift mark. Reports, never fails |
auto-resolve.yml |
push to main |
scans the merge commit message for closes git-tasks: <id> (also gt-closes <id>) and auto-resolves the matching entries in a bot-authored follow-up commit |
weekly-report.yml |
every Monday + manual |
runs git-tasks stats --sla-days 30, publishes the report to the workflow summary, fails on aged criticals, and opens / updates a tracking GitHub Issue when it does |
Local hooks (git-tasks install-hooks)
| Hook |
Behavior |
pre-commit |
Auto-relocates drifted annotations, marks what it cannot relocate, and re-stages them. Runs reconcile --strict, so it blocks the commit if anything is stale or orphan — deliberately local-only friction; bypass with --no-verify. |
post-merge |
Runs reconcile --auto --quiet after every git pull, so line shifts that landed on the remote heal locally on the next pull. |
post-checkout |
Same as post-merge, but on branch checkout. |
Replicate in your repo
git-tasks install-hooks
git-tasks install-merge-driver
cp -R <git-tasks-repo>/.github/workflows/ .github/workflows/
git add .github .gitattributes
git commit -m "ci: wire up git-tasks template"
Adjust --base origin/<your-default-branch> in ci.yml if your default isn't main.
Beyond the editor: SDLC integration ideas
Once annotations live in the repo as structured JSON, the same data file plugs into almost every stage of the development lifecycle. Each of the ideas below is a thin reader on top of .git-tasks/*.json — no new schema, no new service.
:sparkles: Five of these ideas already ship as the template wired up in this repo — see the dogfooding tables above:
Checks-API inline annotations + sticky PR comment (ci.yml), auto-resolve on closes git-tasks: <id> (auto-resolve.yml), scheduled SLA report (weekly-report.yml), the git-tasks diff and git-tasks stats commands, an annotation pinned to the line it describes, and an --tag onboarding walkthrough. Copy the workflow files and you have the same setup in your repo.
PR review (biggest visibility lift)
- Inline diff comments via the GitHub Checks Annotations API. In a CI workflow, walk every entry whose
line/endLine overlaps the PR diff and emit ::warning file=…,line=…::<text>. Each annotation then appears as an inline marker in the GitHub diff view, exactly like a linter finding.
- Sticky PR comment listing annotations touching the diff, grouped by severity, posted by a workflow with
pull-requests: write.
- Auto-resolve on merge. A workflow that scans the merge commit message for
closes git-tasks: <id> (or closes gt:<id>) and updates those entries' status to resolved in a follow-up commit — the same UX as closes [#123](https://github.com/ribarrat/git-tasks/issues/123) for issues.
- Reconcile-drift gate. Run
git-tasks reconcile --dry-run against the PR branch and fail the check if the PR introduces new stale or orphan entries.
Production / on-call (biggest leverage)
- Stack-trace ↔ annotation map. A Sentry / Datadog / Honeycomb plugin (or a plain webhook) takes each error frame
(file, line, commitSHA) and reads .git-tasks/ at the deployed commit. The on-call sees:
"This line was annotated 6 weeks ago by bob@: 'rate-limit me when concurrency > 50'. Status: open, priority: high."
Tribal knowledge becomes operational context at the exact moment it's needed.
- On-call run sheet.
git-tasks list --tag oncall-watchout --status open --json becomes the live watch-list, edited by the people who own the code.
Deploy
Test
- Failure triage. When a test fails in CI, look up annotations on the test file and on the source under test, and surface them in the failure summary. Cuts triage time when the relevant context was already written down.
- Snapshot sentinels. Annotations tagged
snapshot-volatile pinned to known-fragile snapshot lines; a CI step auto-resolves them when the snapshot is updated in the same PR.
Planning, docs, onboarding
- Annotations that can't rot. Pin a comment to the line where a non-obvious decision or assumption lives. If someone replaces that code later, drift detection surfaces the note as
stale and forces a fresh look instead of silently lying.
- Guided onboarding.
git-tasks list --tag onboarding produces an in-codebase tour for new hires, walked through in order.
AI agents (compounds with everything else)
The schema is stable and machine-readable, so beyond the "agent picks a task and resolves it" loop, annotations become context for every agent:
- Code-review agents fetch annotations touching the diff and weigh them in the review prompt ("flag this PR if it doesn't address the open critical race-condition note on
auth.ts:42").
- Doc-writing agents prefer files with low open-annotation density as canonical examples.
- Test-generation agents prioritize files with open
severity: major annotations.
- Refactoring agents read
commitSHA + lineContent to understand the original intent before changing the line.
All of these are git-tasks list --json plus a prompt template.
Continuous improvement
- Density heatmap. Weekly report:
(file, open_issues, oldest_open_age, recent_velocity). Top-quartile files become refactor candidates.
- SLA cron. A scheduled job that pings Slack: "4 critical annotations open > 30 days". The data is already in the repo; just needs the webhook.
- Post-incident annotations. After every postmortem, the owner adds a
type: issue entry at the exact line that caused the incident, tagged with the incident ID. Future readers of that line see the history.
Where to invest first
If you're picking one integration to ship next, the leverage order is roughly:
| # |
Integration |
Why it pays off first |
| 1 |
PR review (Checks API inline comments) |
Highest visibility lift; pure CI workflow change, no infra. |
| 2 |
Stack-trace → annotation lookup in error monitors |
Converts tribal knowledge into incident context at the moment it matters. |
| 3 |
Deploy gate on open criticals |
Prevents the worst class of regression with one CLI call. |
| 4 |
Release-notes auto-generation |
Free writeup quality; reuses data you already maintain. |
| 5 |
Agent context injection |
Tiny effort; compounds with every agent your team adds. |
The thread tying them together: one .git-tasks/*.json file is the input to all of them. Every new integration is a thin reader on the same source of truth, which is exactly the property that makes the in-repo design pay off.