From 7515458d146a975f8cd24001ed91e6c292b2da3a Mon Sep 17 00:00:00 2001 From: fyodoriv Date: Fri, 29 May 2026 11:03:56 -0400 Subject: [PATCH 1/2] chore: remove archived taskgrind directory and references AIFN-720 --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- AGENTS.md | 15 +- README.md | 23 -- ROADMAP.md | 2 - spec.md | 6 +- taskgrind/README.md | 194 --------------- taskgrind/prompt-template.md | 237 ------------------- taskgrind/scripts/check-admin-merge-rate.mjs | 140 ----------- taskgrind/scripts/check-zero-ship-streak.mjs | 218 ----------------- taskgrind/scripts/lint-pr-shape.mjs | 178 -------------- taskgrind/scripts/safe-admin-merge.sh | 69 ------ 11 files changed, 3 insertions(+), 1081 deletions(-) delete mode 100644 taskgrind/README.md delete mode 100644 taskgrind/prompt-template.md delete mode 100644 taskgrind/scripts/check-admin-merge-rate.mjs delete mode 100644 taskgrind/scripts/check-zero-ship-streak.mjs delete mode 100644 taskgrind/scripts/lint-pr-shape.mjs delete mode 100755 taskgrind/scripts/safe-admin-merge.sh diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index fdcf31a..3c72006 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -7,7 +7,7 @@ section headers and bullet shapes intact. --> ## What changed - + ## Vision trace diff --git a/AGENTS.md b/AGENTS.md index acbffaa..5be1161 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,8 +14,6 @@ supporting tools that make the format useful for humans and agents: - `packages/cli` provides the `tasks` command-line interface. - `commands/` contains the shared `/next-task` and `/lint-tasks` command variants for Claude Code, Codex, Cursor, Devin, Gemini CLI, and Windsurf. -- `taskgrind/` contains the autonomous-grind prompt template and enforcement - scripts adopted by downstream repos. ## Repo Layout @@ -40,7 +38,6 @@ tasks.md/ | +-- lint/ # @tasks-md/lint and tasks-lint binary | +-- mcp/ # tasks-mcp server | +-- cli/ # @tasks-md/cli and tasks binary -+-- taskgrind/ # Long-running autonomous session guardrails ``` ## Development @@ -125,7 +122,7 @@ shifts: After changing a repeated term or command step, run a targeted search such as: ```bash -grep -r "" commands/ examples/ taskgrind/ README.md spec.md +grep -r "" commands/ examples/ README.md spec.md ``` ### Spec Propagation @@ -138,16 +135,6 @@ grep -r "" commands/ examples/ taskgrind/ README.md spec.md - Examples must remain valid and should demonstrate new format features when they would help agents learn the pattern. -### Taskgrind Propagation - -Scripts in `taskgrind/scripts/` are canonical for adopting repos. For behavior -or interface changes, update the script header comment and `taskgrind/README.md` -with adoption notes. Pure bug fixes can say that no downstream action is needed. - -Changes to `taskgrind/prompt-template.md` hard rules are policy changes. Update -the corresponding enforcement script when applicable and document the concrete -failure mode in `taskgrind/README.md`. - ## Agentfile And MCP `Agentfile.yaml` declares repo-local agent integrations: diff --git a/README.md b/README.md index c72fe82..65893e6 100644 --- a/README.md +++ b/README.md @@ -311,29 +311,6 @@ Add more tasks → ...keeps draining the queue You're always adding to the queue. The agent is always draining it. This is the core loop — planning is your job, execution is the agent's. -## Taskgrind — overnight / 24h autonomous sessions - -For unsupervised runs longer than a single coding session, the -`/next-task` loop alone isn't enough. Without guardrails, agents -eventually find micro-doc-drift to "fix" once the real queue is -exhausted, generate single-finding PRs, exceed admin-merge volume on -shared branches, and ignore orchestrator stop signals. - -[`taskgrind/`](taskgrind/) provides a canonical rule set + 4 -enforcement scripts that prevent these failure modes: - -| File | What it does | -|---|---| -| [`prompt-template.md`](taskgrind/prompt-template.md) | 10 hard rules — copy to your repo's `taskgrind.md`, fill in placeholders | -| [`scripts/check-zero-ship-streak.mjs`](taskgrind/scripts/check-zero-ship-streak.mjs) | Pre-flight `STOP`/`CONTINUE` check — already wired into the `next-task` skill | -| [`scripts/check-admin-merge-rate.mjs`](taskgrind/scripts/check-admin-merge-rate.mjs) | Counts admin self-merges in trailing 24h, exits non-zero at ≥5 | -| [`scripts/safe-admin-merge.sh`](taskgrind/scripts/safe-admin-merge.sh) | Wrapper around `gh pr merge --admin` that runs the rate check first | -| [`scripts/lint-pr-shape.mjs`](taskgrind/scripts/lint-pr-shape.mjs) | CI gate — refuses single-finding doc-only PRs without `closes ` | - -See [`taskgrind/README.md`](taskgrind/README.md) for adoption options -(copy / symlink / future npx) and the lessons that motivated each -rule. - ## Tooling ### CLI diff --git a/ROADMAP.md b/ROADMAP.md index 7e87749..9b44a8e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -17,7 +17,6 @@ This file is the root-level milestone summary that the `load-project-context` ru | **Per-agent variants** — Claude Code, Codex, Cursor, Devin, Gemini CLI, Windsurf | ✅ Shipped | [`commands/{claude,codex,cursor,devin,gemini,windsurf}/`](commands/) | | **Plan-first workflow** — agents write `docs/plans/.md` before non-trivial work, validated by a reviewer subagent | ✅ Stable | [`docs/plans/next-task-plan-first-workflow.md`](docs/plans/next-task-plan-first-workflow.md), [`docs/templates/plan-template.md`](docs/templates/plan-template.md) | | **Workspace mode** — `next-task` aggregates TASKS.md files across nested repos in one or more workspaces | 🟡 In progress | [`TASKS.md` § "Workspace mode"](TASKS.md) | -| **Taskgrind** — long-running autonomous-session enforcement scripts adopted by downstream repos | ✅ Shipped | [`taskgrind/`](taskgrind/) | | **Site / docs hub** — public website at tasksmd.github.io | ✅ Live | [tasksmd.github.io/tasks.md](https://tasksmd.github.io/tasks.md/) | ## Active focus @@ -43,7 +42,6 @@ When a new agent vendor ships first-class `/next-task` support, that's a milesto | User stories | [`docs/user-stories/`](docs/user-stories/) (numbered) | New US per shipped capability | | Spec definition | [`spec.md`](spec.md) | Conservative; breaking changes are spec-version bumps | | Architecture | [`ARCHITECTURE.md`](ARCHITECTURE.md) + [`AGENTS.md`](AGENTS.md) | Updated in same commit as behavior changes | -| Long-running session enforcement | [`taskgrind/prompt-template.md`](taskgrind/prompt-template.md) | Hardened from real session learnings | ## Out-of-scope (won't do) diff --git a/spec.md b/spec.md index 140b834..7b2d5a4 100644 --- a/spec.md +++ b/spec.md @@ -518,11 +518,7 @@ When a task is done, the agent removes it from the file — the task line, its m Top-level tasks should never be marked `[x]`. The `[x]` checkbox is only for sub-tasks tracking progress on a parent. When a top-level task is complete, remove the entire block — don't check the box. Linters should flag `[x]` on top-level tasks as a warning. -Taskgrind-powered repos may also require the completion commit to include -`closes ` in the commit message, using lowercase `closes` followed by -the task's exact kebab-case **ID**. This token lets -`taskgrind/scripts/lint-pr-shape.mjs` distinguish a doc-only commit that closes -a queued task from untasked doc drift: +Some repos may also require the completion commit to include `closes ` in the commit message, using lowercase `closes` followed by the task's exact kebab-case **ID**. This convention helps distinguish a doc-only commit that closes a queued task from untasked doc drift: ```text docs: update setup notes diff --git a/taskgrind/README.md b/taskgrind/README.md deleted file mode 100644 index 6099cd0..0000000 --- a/taskgrind/README.md +++ /dev/null @@ -1,194 +0,0 @@ -# taskgrind — universal rules + scripts for autonomous coding sessions - -A canonical rule set + 4 enforcement scripts for any repo running -autonomous overnight / 24h coding sessions ("taskgrinds"). - -The rules are designed to prevent the failure modes of unsupervised -autonomous agents: counter-update busywork, single-finding doc-drift -PRs, audit-cascade loops, and admin-merge volume on shared branches. - -> **Reference deployment**: the rules + scripts here were extracted -> from a real autonomous-grind incident I ran on 2026-04-24. A 22-session run shipped 19 PRs but the queue was -> flat for 7 of 10 hours — the agent kept finding micro-doc-drift -> instead of substantive work. Two PRs directly violated the "no -> counter updates" rule; four others each fixed a single docs-only -> finding; ~15 admin self-merges landed on master with no review. -> The rules below are anchored to those specific failures. - -## What's here - -| File | What it does | -|---|---| -| [`prompt-template.md`](prompt-template.md) | Canonical taskgrind prompt template. Copy to your repo's `taskgrind.md` and fill in the `{{REPO_NAME}}`/`{{REPO_CONTEXT}}` placeholders. The 10 hard rules are universal; the "Repo-specific rules" section is where you add your own | -| [`scripts/check-zero-ship-streak.mjs`](scripts/check-zero-ship-streak.mjs) | Pre-flight stop check — prints `STOP` or `CONTINUE` based on commit history + TASKS.md state. The canonical `next-task` skill calls it at session entry | -| [`scripts/check-admin-merge-rate.mjs`](scripts/check-admin-merge-rate.mjs) | Counts your admin self-merges in the trailing 24h via `gh pr list`. Exits non-zero at ≥5 | -| [`scripts/safe-admin-merge.sh`](scripts/safe-admin-merge.sh) | Wrapper around `gh pr merge --admin` that runs the rate check first. Logs successful merges to `.agent-merge.log` (gitignored) for audit | -| [`scripts/lint-pr-shape.mjs`](scripts/lint-pr-shape.mjs) | CI gate. Refuses single-finding docs-only PRs that don't close a task. Wire into GH Actions and Jenkins | - -## Adoption — three options - -### Option A: copy the scripts (simplest, fully owned) - -```bash -# In your repo: -mkdir -p scripts -cp ~/path/to/tasks.md/taskgrind/scripts/*.mjs scripts/ -cp ~/path/to/tasks.md/taskgrind/scripts/*.sh scripts/ -chmod +x scripts/safe-admin-merge.sh -cp ~/path/to/tasks.md/taskgrind/prompt-template.md taskgrind.md -# Edit taskgrind.md to fill in placeholders -``` - -You own the copies; updates require re-copy. Tradeoff: drift over -time, but no version dependency. - -### Option B: symlink to a checkout (lightest, most up-to-date) - -```bash -# Clone tasks.md once on your machine, then in each adopting repo: -mkdir -p scripts -ln -s ~/path/to/tasks.md/taskgrind/scripts/check-zero-ship-streak.mjs scripts/ -ln -s ~/path/to/tasks.md/taskgrind/scripts/check-admin-merge-rate.mjs scripts/ -ln -s ~/path/to/tasks.md/taskgrind/scripts/safe-admin-merge.sh scripts/ -ln -s ~/path/to/tasks.md/taskgrind/scripts/lint-pr-shape.mjs scripts/ -cp ~/path/to/tasks.md/taskgrind/prompt-template.md taskgrind.md -# Edit taskgrind.md to fill in placeholders -``` - -`git pull` in the canonical checkout updates every adopting repo. -Tradeoff: requires the canonical checkout to exist on every machine -that runs the scripts (CI included). - -### Option C: vendor via npx + the @tasks-md/cli package (planned) - -Future: `npx @tasks-md/cli taskgrind ` will run the -gates without needing local scripts. Not yet implemented. -[Track here](TASKS.md) under any open task tagged `taskgrind-cli`. - -## Wiring into CI - -### GitHub Actions (every adopting repo) - -```yaml -# .github/workflows/ci.yml -jobs: - ci: - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Required for lint-pr-shape merge-base - - - name: Lint PR shape - if: github.event_name == 'pull_request' - run: node scripts/lint-pr-shape.mjs --base origin/${{ github.base_ref }} -``` - -### Jenkins (if your repo uses MSaaS or similar) - -```groovy -// Jenkinsfile -stage('Validate PR shape') { - when { - beforeOptions true - changeRequest() - } - steps { - container('node') { - sh ''' - git fetch origin "${CHANGE_TARGET:-master}" --depth=50 || true - node scripts/lint-pr-shape.mjs --base "origin/${CHANGE_TARGET:-master}" - ''' - } - } -} -``` - -You'll need a `node` pod container (or runtime install). Any image -with Node 18+ works; the runtime requirements are just `node` and -`gh` (for `lint-pr-shape.mjs` and `check-admin-merge-rate.mjs`). - -## Wiring the next-task skill - -The canonical [`next-task` skill](../commands/next-task.md) (and its -6 agent-specific variants) already calls -`scripts/check-zero-ship-streak.mjs` at session entry per -[`commit c974804`](https://github.com/tasksmd/tasks.md/commit/c974804). -If you've adopted the script (Option A or B), the skill will pick -it up automatically. The stop check follows the canonical -`**Blocked**` metadata field from the TASKS.md spec when it detects -fully blocked queues. - -## Wiring the counter-precision rule - -This one's vitest-specific. Add a `counter-precision.test.ts` file -to your repo's tests directory that scans your docs/markdown for -patterns matching the regex `\b\d{2,4}\s+(tests?|suites?|skills?|agents?|...)\b` -and fails when it finds a precise count without an `` -allow-list comment. The directories to scan are repo-specific; the -regex is universal. - -For non-vitest test runners (jest, mocha, etc.) the logic ports -straightforwardly — it's a ~150-line file with no vitest-specific -dependencies beyond `describe`/`it`. - -## Wiring `tasks-lint` into your lint chain - -Add to your `package.json`: - -```json -{ - "scripts": { - "lint": " && yarn lint:tasks", - "lint:tasks": "tasks-lint TASKS.md" - }, - "devDependencies": { - "@tasks-md/lint": "^0.7.0" - } -} -``` - -This makes TASKS.md spec violations fail CI alongside your code lint. - -## The 10 rules at a glance - -(Detailed versions in [`prompt-template.md`](prompt-template.md).) - -1. No outbound communication (Slack/Jira/GitHub posts, MCP "create" actions) -2. No web-UI form submits (admin portals, CI configs, monitoring dashboards) -3. No production touches -4. No destructive git -5. No destructive shell -6. No roaming to other repos -7. ≤5 admin self-merges per 24h -8. No counter updates (use `N+` form) -9. No single-finding doc-drift PRs (`closes ` or batch ≥3) -10. Stop when the audit cascade is exhausted - -## Lessons learned (canonical seed) - -The lessons below are the ones the rules above are anchored to. Add -your repo-specific lessons in your own `taskgrind.md`'s "Lessons -learned" section. - -- **2026-04-24 grind**: 10h27m run, - 19 PRs shipped, queue 28 → 14 in the first 3 hours then flat for - 7. Failure modes that motivated rules 7–10: - - **Rule 7** — ~15 admin self-merges in 12h with no reviewer, - concentrated on master. - - **Rule 8** — two PRs directly violated the global - counter-precision rule (e.g. `800+/30+ → 833/32`, `713+ → 833`). - - **Rule 9** — five PRs each fixed exactly one docs-only finding - in one file. - - **Rule 10** — 17 sessions ran after the queue first reached - 100% `**Blocked**` tasks. The orchestrator's `productive_zero_ship` - and `diminishing_returns` warnings fired repeatedly and were - ignored. - -- **2026-04-26 implementation session** (this directory's source): - the rules above were shipped via PRs in the downstream repo plus - cross-repo work in this one. The rules now bind their own author - — the `safe-admin-merge.sh` wrapper correctly refused to let me - merge a 6th PR, and the structural-change branch in - `lint-pr-shape.mjs` was added because the naive "all-md = drift" - rule wrongly failed PR #118 (which legitimately added the chore - + canonical prompt). Self-consistency check: the system works. diff --git a/taskgrind/prompt-template.md b/taskgrind/prompt-template.md deleted file mode 100644 index ce4becc..0000000 --- a/taskgrind/prompt-template.md +++ /dev/null @@ -1,237 +0,0 @@ -# taskgrind prompt — {{REPO_NAME}} - -> Canonical prompt for `taskgrind` autonomous sessions on this repo. The -> shell launcher concatenates the contents of this file as the live -> prompt. Edit here, never in shell history. Iterations live in git. -> -> Companion to [`TASKS.md`](TASKS.md) (the work queue) and -> [`AGENTS.md`](AGENTS.md) (the onboarding guide). When in doubt, those -> two files override this one. -> -> This file was bootstrapped from the canonical template at -> [`tasksmd/tasks.md`](https://github.com/tasksmd/tasks.md/tree/main/taskgrind). -> Keep universal rules in sync with that source; add repo-specific -> rules below in their own section. - -## Goal - -Ship substantive work that closes [`TASKS.md`](TASKS.md) tasks. Skip -everything else. Exit cleanly when no autonomous-actionable work -remains. - -## Context - -{{REPO_CONTEXT}} - - - -## Hard rules — DO NOT do any of these. Skip the task instead. - -### 1. No outbound communication - -No Slack, Teams, Discord, email, SMS, Jira comments, GitHub issue -comments, Google Doc comments, support tickets, or admin-portal -"request" forms. PR descriptions on PRs you authored are OK; comments -on others' Jira / GitHub / GDocs are not. Outbound-communication -MCP tools (slack, atlassian, google-drive, github comments) are -off-limits for any "create" or "post" action. - -### 2. No web-UI form submits - -Read-only browsing for context is OK; clicking any "Save", "Submit", -"Deploy", or "Create" button in any production-affecting UI is not. -Playwright / agent-browser may navigate but must not submit. - -### 3. No production touches - -No prod cluster `kubectl`, no prod CDN publish, no prod env var -changes. Repo-specific overrides in the "Repo-specific rules" -section below. - -### 4. No destructive git - -No `git push --force`, no `git reset --hard `, no -`git checkout .` on a dirty tree, no remote branch deletion (except -feature branches you opened in this run). Self-merging your own PRs -via `gh pr merge --admin` is OK *with rate limits — see rule 7*. - -### 5. No destructive shell - -No `rm -rf` outside the repo, no `kubectl delete` of any kind, no -`docker rmi` against shared images, no dropping any database. - -### 6. No roaming - -Stay in `{{REPO_NAME}}`. Don't scan `~/apps/*/`, don't `cd` into -sibling repos, don't pick up tasks from other queues. Editing -sibling-repo files as an explicit deliverable of a TASKS.md task -here is the only exception, and only when the task already -documents the cross-repo file paths. - -### 7. No more than 5 admin self-merges per 24h - -The session safety contract permits `gh pr merge --admin` on your own -PRs, but ≤5 in a rolling 24h window. Run -[`bash taskgrind/scripts/safe-admin-merge.sh `](taskgrind/scripts/safe-admin-merge.sh) -(or its installed copy in `scripts/`) instead of `gh pr merge --admin`. -Above the cap, batch follow-ups into one PR or wait for the window -to roll. Never set `ALLOW_ADMIN_BURST=1` autonomously. - -### 8. No counter updates - -NEVER create tasks, PRs, or commits to update approximate counters. -Updating "840+" to "850+" in a doc is waste — the `N+` format is -designed to stay correct without maintenance. Counter-accuracy -"drift" is the #1 source of busywork in autonomous sweeps; skip -it unconditionally. Mechanically enforced by -[`server/counter-precision.test.ts`](server/counter-precision.test.ts) -(or wherever your repo's vitest tests live); allowlist genuine -exceptions with ``. - -### 9. No single-finding doc-drift PRs - -A PR is acceptable only if it (a) closes a TASKS.md task via -`closes ` in the HEAD commit message, using lowercase -`closes` followed by the task's exact kebab-case `**ID**`, OR -(b) batches ≥3 distinct markdown findings into one commit, OR -(c) is structural — adds / deletes / renames a markdown file. -Single-finding doc-drift PRs (one stale port, one stale link, -one stale path) are not acceptable — accumulate findings or skip -them. Mechanically enforced by -[`taskgrind/scripts/lint-pr-shape.mjs`](taskgrind/scripts/lint-pr-shape.mjs) -(or its installed copy); CI runs it on every PR. - -### 10. Stop when the audit cascade is exhausted - -Hard stop conditions, checked at session entry: - -- If `git log --oneline -3 origin/master` shows 3 consecutive - doc-only commits with no `closes ` token, exit - immediately. The audit cascade is exhausted. -- If 100% of P0–P3 tasks in `TASKS.md` carry a non-empty - `**Blocked**` metadata line, exit immediately. Don't - run the audit cascade in this state. -- If a previous session reported `productive_zero_ship` or - `diminishing_returns`, treat it as a hard stop, not a hint. - -Conditions 1 and 2 are mechanically enforced by -[`taskgrind/scripts/check-zero-ship-streak.mjs`](taskgrind/scripts/check-zero-ship-streak.mjs); -the canonical `next-task` skill (in -[`tasks.md/commands/`](https://github.com/tasksmd/tasks.md/tree/main/commands)) -calls it at session entry. Condition 3 is on the agent — read -the session prompt before doing anything else. - -## What to do when blocked - -If a task fundamentally requires a forbidden action (Slack post, -admin-portal submit, prod deploy, infra request): - -1. Add this exact metadata line under the task in `TASKS.md`: - `**Blocked**: ` -2. Commit only that `TASKS.md` edit (one commit, one task). -3. Move to the next task. - -Don't "skip" by silently moving on — the `**Blocked**` marker is the deliverable. -The marker tells future sessions and humans exactly what's blocking -the task. - -## Prefer - -In ascending priority: - -1. **P0 tasks tagged `agent-guardrail` or `prevents-waste`.** These - unblock all future autonomous sessions and have the highest - leverage. Pick these first. -2. **P0–P3 tasks tagged `code`, `test`, or `refactor`** whose `Files` - list contains only files in this repo and whose `Details` don't - reference forbidden actions. -3. **P0–P3 tasks tagged `docs`** only when they batch ≥3 findings - (rule 9). - -## Queue pressure: deliver vs add - -Before picking a task, count unclaimed P0–P2 vs P3 in `TASKS.md` and pick -a session mode: - -```bash -P012=$(awk '/^## P0$/{f=1; next} /^## P3$/{f=0} f && /^- \[ \]/ && !/\(@/{c++} END{print c+0}' TASKS.md) -P3=$(awk '/^## P3$/{f=1; next} /^## /{f=0} f && /^- \[ \]/ && !/\(@/{c++} END{print c+0}' TASKS.md) -echo "P012=$P012 P3=$P3" -``` - -| Pressure | Trigger | Mode | Behavior | -|---|---|---|---| -| **HIGH** | `P012 > 10` | **Deliver** | Pick the highest-priority unblocked task and ship it. Don't run audit cascades, don't sweep, don't generate net-new tasks unless they fall out of the work in front of you (see exception below). The queue is loud enough already. | -| **LOW** | `P012 ≤ 10` AND P3 > 0 | **Add** | After clearing any unclaimed P0–P2, spend the rest of the session on audit cascades and `sweep`-style finds — file new tasks where you find leverage. The queue is quiet, time to refill it. | -| **EMPTY** | `P012 == 0` AND `P3 == 0` | **Stop** | Audit cascade exhausted. Apply rule 10 and exit. | - -**Exception — opportunistic-add ALWAYS overrides pressure mode.** If -during *any* implementation you stumble on something fixable (a flaky -test, a stale doc, a missing log line, a dead variable, a TODO older -than 30 days, a misleading error message), file a one-task TASKS.md -entry inline with your work and keep going. Do this regardless of the -mode above — opportunistic additions cost ~30 seconds and capture -context that's hard to recover later. The pressure rule throttles -*proactive* sweeping, not *reactive* note-taking. - -The threshold (`> 10` for P0–P2 unclaimed) is the line between "ship -what we have" and "look for more work." Bumping it weakens delivery -focus; lowering it weakens audit cadence. Don't tune per session — -only edit this template if a pattern across multiple repos demands it. - -## Skip - -- Any task with a non-empty `**Blocked**` metadata line. -- Any task `**Blocked by**` a blocked task (transitive blockers - count). -- Counter-update tasks (rule 8). -- Single-finding doc-drift fixes (rule 9). -- Audit-cascade tiers beyond Tier 1 (verify) when no real findings - exist after Tier 1. - -## Default: do less - -When in doubt, exit early with a one-line summary commit on the -current branch rather than ship busywork. A skipped task with a -`**Blocked**` metadata line is a successful session. A -zero-shipped session that correctly identifies the queue as blocked -is a successful session. Better to land 8 hours of substantive code -in a 24h budget and exit cleanly than to fill 24h with doc drift. - -## Repo-specific rules - - - -(none yet — add as you discover them) - -## Lessons learned - -Every rule above is anchored to a real failure. When you add a new -rule, add the failure that motivated it here. Format: - -- **`` grind** (``): - - **Rule N** — one-sentence description of the failure mode and - the specific commits / sessions / log lines that surfaced it. - -(Initial seed lessons are in the canonical template's commit history -in `tasksmd/tasks.md` — see `taskgrind/README.md` there.) diff --git a/taskgrind/scripts/check-admin-merge-rate.mjs b/taskgrind/scripts/check-admin-merge-rate.mjs deleted file mode 100644 index 4dfb7e3..0000000 --- a/taskgrind/scripts/check-admin-merge-rate.mjs +++ /dev/null @@ -1,140 +0,0 @@ -#!/usr/bin/env node -// scripts/check-admin-merge-rate.mjs -// -// Rate-limit check for autonomous `gh pr merge --admin` self-merges. -// Implements taskgrind.md rule 7: an autonomous agent may admin-merge -// at most N (default 5) of its own PRs in any rolling 24-hour window. -// -// The 2026-04-24 grind shipped ~15 self-authored admin merges in 12 -// hours with zero reviewers — the volume on a shared master branch is -// the real risk surface, not any individual merge. This script makes -// the rate observable and enforceable. -// -// Usage: -// node scripts/check-admin-merge-rate.mjs # query gh, check rate -// node scripts/check-admin-merge-rate.mjs --json # also dump JSON -// node scripts/check-admin-merge-rate.mjs --from-file -// # test mode: load -// # JSON instead of gh -// -// Configuration (env vars): -// ADMIN_MERGE_LIMIT — max self-merges per window (default: 5) -// ADMIN_MERGE_WINDOW — window in hours (default: 24) -// -// Exit codes: -// 0 count is below the limit (OK to merge) -// 1 count >= limit (rate-limited; refuse to merge) -// 2 error fetching/parsing data (defensive — also refuses) -// -// JSON shape expected (output of `gh pr list --json number,mergedAt,title`): -// [ -// { "number": 117, "mergedAt": "2026-04-25T05:23:11Z", "title": "..." }, -// ... -// ] - -import { execFileSync } from "node:child_process"; -import { readFileSync } from "node:fs"; - -const LIMIT = Number.parseInt(process.env.ADMIN_MERGE_LIMIT ?? "5", 10); -const WINDOW_HOURS = Number.parseInt( - process.env.ADMIN_MERGE_WINDOW ?? "24", - 10, -); - -const args = process.argv.slice(2); -const fromFileIdx = args.indexOf("--from-file"); -const showJson = args.includes("--json"); - -/** @returns {Array<{number: number, mergedAt: string, title: string}>} */ -function loadEntries() { - if (fromFileIdx !== -1) { - const path = args[fromFileIdx + 1]; - if (!path) { - console.error("[check-admin-merge-rate] --from-file requires a path."); - process.exit(2); - } - return JSON.parse(readFileSync(path, "utf8")); - } - - // Production path: query gh. - const sinceMs = Date.now() - WINDOW_HOURS * 60 * 60 * 1000; - const sinceIso = new Date(sinceMs).toISOString().replace(/\.\d+Z$/, "Z"); - let raw; - try { - raw = execFileSync( - "gh", - [ - "pr", - "list", - "--state", - "merged", - "--search", - `author:@me merged:>=${sinceIso}`, - "--json", - "number,mergedAt,title", - "--limit", - "50", - ], - { encoding: "utf8" }, - ); - } catch (err) { - console.error( - `[check-admin-merge-rate] gh query failed: ${ - /** @type {Error} */ (err).message - }`, - ); - console.error(" Falling back to fail-closed: refusing the merge."); - process.exit(2); - } - return JSON.parse(raw); -} - -let entries; -try { - entries = loadEntries(); -} catch (err) { - console.error( - `[check-admin-merge-rate] Failed to load merge data: ${ - /** @type {Error} */ (err).message - }`, - ); - process.exit(2); -} - -if (showJson) { - console.log(JSON.stringify(entries, null, 2)); -} - -const count = entries.length; - -if (count >= LIMIT) { - console.error( - `[check-admin-merge-rate] FAIL: ${count}/${LIMIT} admin self-merges in last ${WINDOW_HOURS}h.`, - ); - console.error(""); - console.error("Recent merges:"); - for (const entry of entries.slice(0, 10)) { - console.error( - ` #${entry.number} ${entry.mergedAt} ${entry.title}`, - ); - } - console.error(""); - console.error("Above the rate limit. Either:"); - console.error(" - Batch follow-up work into one PR"); - console.error( - ` - Wait for the ${WINDOW_HOURS}h window to roll (oldest merge will roll off)`, - ); - console.error( - " - Human-only override: ALLOW_ADMIN_BURST=1 bash scripts/safe-admin-merge.sh ", - ); - console.error(""); - console.error( - "Per taskgrind.md rule 7 + AGENTS.md \"Task queue conventions\".", - ); - process.exit(1); -} - -console.log( - `[check-admin-merge-rate] ${count}/${LIMIT} admin self-merges in last ${WINDOW_HOURS}h. OK.`, -); -process.exit(0); diff --git a/taskgrind/scripts/check-zero-ship-streak.mjs b/taskgrind/scripts/check-zero-ship-streak.mjs deleted file mode 100644 index b354dc8..0000000 --- a/taskgrind/scripts/check-zero-ship-streak.mjs +++ /dev/null @@ -1,218 +0,0 @@ -#!/usr/bin/env node -// scripts/check-zero-ship-streak.mjs -// -// Pre-flight stop-condition check for autonomous taskgrind sessions. -// Implements taskgrind rule 10: detect when the audit cascade is -// exhausted or when 100% of TASKS.md is marked with `**Blocked**`, so -// the next-task skill can exit early instead of running another -// pointless audit pass. -// -// Reference deployment: a 22-session autonomous grind on a private -// downstream repo (2026-04-24) ran 17 sessions after the queue first -// hit 100% blocked, generating ~10 single-finding doc-drift PRs. -// This script would have printed STOP from session 6 onward, saving -// ~7 hours of model time. -// -// Usage: -// node taskgrind/scripts/check-zero-ship-streak.mjs -// (or copy to scripts/ in your repo and run from there) -// -// Configuration (env vars): -// BASE — base ref for `git log` (default: auto-detect -// between origin/master and origin/main, in that -// order; first one that exists wins). -// TICKET_PREFIX — substring required in commit subjects to count as -// a doc-drift commit (default: empty — disabled). -// Set to your repo's ticket convention (e.g. -// "FOO-123") to scope the rule to a specific -// ticket family. -// TASKS_PATH — path to TASKS.md (default: TASKS.md) -// -// Output: -// First line is `STOP` or `CONTINUE`. Following lines describe why. -// Exit code is always 0 — this script is informational, not a gate. -// The calling skill grep's the first line and decides what to do. - -import { execFileSync } from "node:child_process"; -import { readFileSync } from "node:fs"; -import { resolve } from "node:path"; - -const TICKET_PREFIX = process.env.TICKET_PREFIX ?? ""; -const TASKS_PATH = resolve(process.cwd(), process.env.TASKS_PATH ?? "TASKS.md"); - -const CLOSES_PATTERN = /\bcloses\s+[a-z][a-z0-9-]+\b/i; - -/** @param {string[]} args */ -function git(args) { - try { - return execFileSync("git", args, { encoding: "utf8" }).trim(); - } catch { - return ""; - } -} - -/** Auto-detect the base ref: prefer explicit BASE, then origin/master, then origin/main. */ -function resolveBase() { - const explicit = process.env.BASE; - if (explicit) return explicit; - for (const candidate of ["origin/master", "origin/main"]) { - try { - execFileSync("git", ["rev-parse", "--verify", candidate], { - stdio: "ignore", - }); - return candidate; - } catch { - // not present, try next - } - } - // Neither exists; return the conventional default and let later git - // calls fail gracefully (the script's defensive try/catch returns ""). - return "origin/master"; -} - -const BASE = resolveBase(); - -/** - * Check 1 — last 3 commits on the base ref are all docs-only with no - * `closes ` token (and optionally with the ticket prefix in - * the subject). When this fires, the audit cascade has been hit - * recently; more drift fixes won't unblock anything. - * - * @returns {{ commits: string[] } | null} - */ -function checkConsecutiveDocDrift() { - const log = git(["log", "--format=%H", "-3", BASE]); - const shas = log.split("\n").filter(Boolean); - if (shas.length < 3) return null; - - const summaries = []; - for (const sha of shas) { - const message = git(["log", "-1", "--format=%B", sha]); - const filesRaw = git([ - "diff-tree", - "--no-commit-id", - "--name-only", - "-r", - sha, - ]); - const files = filesRaw.split("\n").filter(Boolean); - - if (files.length === 0) return null; - const allMd = files.every((file) => file.toLowerCase().endsWith(".md")); - if (!allMd) return null; - - if (CLOSES_PATTERN.test(message)) return null; - - if (TICKET_PREFIX && !message.includes(TICKET_PREFIX)) return null; - - const subject = message.split("\n")[0] ?? ""; - summaries.push(`${sha.slice(0, 7)} ${subject}`); - } - - return { commits: summaries }; -} - -/** - * Split TASKS.md content into top-level task blocks. A block starts at - * a `^- [ ]` line and ends at the next `^- [ ]`, the next `^# `–`^###### ` - * heading, or end-of-file. - * - * @param {string} content - * @returns {string[][]} - */ -function parseTaskBlocks(content) { - const lines = content.split("\n"); - /** @type {string[][]} */ - const blocks = []; - /** @type {string[] | null} */ - let current = null; - const flush = () => { - if (current) { - blocks.push(current); - current = null; - } - }; - for (const line of lines) { - if (/^- \[ \]/.test(line)) { - flush(); - current = [line]; - } else if (/^#{1,6} /.test(line)) { - flush(); - } else if (current) { - current.push(line); - } - } - flush(); - return blocks; -} - -/** - * Check 2 — every P0–P3 task in TASKS.md carries a non-empty - * `**Blocked**` metadata line. When this fires, the - * autonomous queue is empty by safety contract; running the audit - * cascade just produces busywork. - * - * @returns {{ totalTasks: number } | null} - */ -function checkAllBlockedMarked() { - let content; - try { - content = readFileSync(TASKS_PATH, "utf8"); - } catch { - return null; - } - - const blocks = parseTaskBlocks(content); - if (blocks.length === 0) return null; - - const blockedBlocks = blocks.filter((block) => - block.some((line) => /^\s+-\s+\*\*Blocked\*\*:\s*\S/i.test(line)), - ); - if (blockedBlocks.length === blocks.length) { - return { totalTasks: blocks.length }; - } - return null; -} - -const docDrift = checkConsecutiveDocDrift(); -const allBlocked = checkAllBlockedMarked(); - -if (docDrift || allBlocked) { - console.log("STOP"); - console.log(""); - if (docDrift) { - console.log( - ` Reason 1: last 3 commits on ${BASE} are docs-only with no \`closes \` token`, - ); - if (TICKET_PREFIX) { - console.log(` (filtered by TICKET_PREFIX="${TICKET_PREFIX}")`); - } - console.log(" — audit cascade is exhausted."); - console.log(""); - console.log(" Recent commits:"); - for (const summary of docDrift.commits) { - console.log(` ${summary}`); - } - console.log(""); - } - if (allBlocked) { - console.log( - ` Reason 2: 100% of TASKS.md tasks (${allBlocked.totalTasks}/${allBlocked.totalTasks}) carry`, - ); - console.log(" a non-empty **Blocked** metadata line — no"); - console.log(" autonomous work available."); - console.log(""); - } - console.log( - " See taskgrind.md rule 10 + AGENTS.md \"Autonomous session stop-conditions\".", - ); - console.log( - " Exit the session cleanly. The next session will re-run this check.", - ); - process.exit(0); -} - -console.log("CONTINUE"); -console.log(""); -console.log(" No stop-condition active. Proceed with the next-task workflow."); -process.exit(0); diff --git a/taskgrind/scripts/lint-pr-shape.mjs b/taskgrind/scripts/lint-pr-shape.mjs deleted file mode 100644 index 2761c96..0000000 --- a/taskgrind/scripts/lint-pr-shape.mjs +++ /dev/null @@ -1,178 +0,0 @@ -#!/usr/bin/env node -// scripts/lint-pr-shape.mjs -// -// Enforces taskgrind.md rule 9 / AGENTS.md "Task queue conventions": -// every PR is either -// (a) substantive — the diff contains at least one non-`.md` file, OR -// (b) structural — at least one `.md` file is added/deleted/renamed -// (creating a new doc page or removing one is a deliberate -// structural choice, not drift), OR -// (c) closes a task — the HEAD commit message contains -// `closes `, OR -// (d) a doc-drift batch — the diff modifies >=3 distinct markdown -// files. -// -// Single-finding docs-only PRs (one stale port, one stale link, one -// stale path) are not acceptable — they fragment the git log and burn -// review/build cycles. The 2026-04-24 taskgrind shipped ~10 of these. -// -// Usage: -// node scripts/lint-pr-shape.mjs [--base ] -// -// Default base: origin/master. -// -// Emergency override (human operator only — autonomous agents MUST NOT -// set this): -// ALLOW_SINGLE_DOC_PR=1 node scripts/lint-pr-shape.mjs -// -// Exit codes: -// 0 OK (substantive, closes-token, batched, or skipped) -// 1 FAIL — docs-only PR with no task closure and <3 findings -// -// CI integration: this script needs git history reaching the merge-base -// with the target branch. In GitHub Actions, set -// - uses: actions/checkout@v4 -// with: { fetch-depth: 0 } -// so the merge-base lookup succeeds. If the merge-base can't be found -// (shallow clone, fresh worktree, etc.), the script skips the check and -// exits 0 — fail-open is correct here because false positives would be -// worse than missed catches given the manual-override path exists. - -import { execFileSync } from "node:child_process"; - -/** Auto-detect the base ref: prefer --base flag, then origin/master, then origin/main. */ -function resolveBase(args) { - for (let i = 0; i < args.length; i++) { - if (args[i] === "--base" && args[i + 1]) { - return args[i + 1]; - } - } - for (const candidate of ["origin/master", "origin/main"]) { - try { - execFileSync("git", ["rev-parse", "--verify", candidate], { - stdio: "ignore", - }); - return candidate; - } catch { - // not present, try next - } - } - return "origin/master"; -} - -const args = process.argv.slice(2); -const base = resolveBase(args); - -if (process.env.ALLOW_SINGLE_DOC_PR === "1") { - console.log( - "[lint-pr-shape] ALLOW_SINGLE_DOC_PR=1 set — skipping check (human override).", - ); - process.exit(0); -} - -/** @param {string[]} cmdArgs */ -function git(cmdArgs) { - return execFileSync("git", cmdArgs, { encoding: "utf8" }).trim(); -} - -// Step 1 — Find merge-base with the target branch. Skip the check -// gracefully if we can't (shallow clone, branch points off-tree, etc.). -let mergeBase; -try { - mergeBase = git(["merge-base", base, "HEAD"]); -} catch { - console.log( - `[lint-pr-shape] Cannot find merge-base with ${base} (shallow clone or unrelated history). Skipping check.`, - ); - process.exit(0); -} - -// Step 2 — List changed files with status (A/M/D/R/C) in the PR diff. -const statusOutput = git([ - "diff", - "--name-status", - "--find-renames", - `${mergeBase}..HEAD`, -]); -const fileChanges = statusOutput - .split("\n") - .map((line) => line.trim()) - .filter((line) => line.length > 0) - .map((line) => { - // Format: "\t" or "R\t\t" for renames. - const parts = line.split("\t"); - const status = parts[0]?.[0] ?? "M"; - // For renames/copies, use the new path (last token). - const path = parts[parts.length - 1] ?? ""; - return { status, path }; - }); - -if (fileChanges.length === 0) { - console.log("[lint-pr-shape] No changes vs base. Nothing to check."); - process.exit(0); -} - -// Step 3 — Substantive PR (any non-`.md` file)? Pass. -const nonMdFiles = fileChanges.filter( - (change) => !change.path.toLowerCase().endsWith(".md"), -); -if (nonMdFiles.length > 0) { - console.log( - `[lint-pr-shape] Substantive PR — ${nonMdFiles.length} non-markdown file(s) changed. OK.`, - ); - process.exit(0); -} - -// Step 4 — Structural markdown change (added / deleted / renamed)? Pass. -// Drift fixes are always Modifications. New doc pages, removed ones, or -// renames are structural decisions worth their own PR even if single-file. -const structuralChange = fileChanges.find( - (change) => change.status !== "M", -); -if (structuralChange) { - console.log( - `[lint-pr-shape] Structural markdown change (${structuralChange.status} ${structuralChange.path}). OK.`, - ); - process.exit(0); -} - -// Step 5 — All changes are `.md` modifications. Does the HEAD commit -// message close a task? -const headMessage = git(["log", "-1", "--format=%B"]); -const closesPattern = /\bcloses\s+[a-z][a-z0-9-]+\b/i; -if (closesPattern.test(headMessage)) { - console.log("[lint-pr-shape] Docs-only PR closes a task. OK."); - process.exit(0); -} - -// Step 6 — No task closure. Are >=3 distinct markdown files modified? -if (fileChanges.length >= 3) { - console.log( - `[lint-pr-shape] Docs-only PR batches ${fileChanges.length} markdown findings. OK.`, - ); - process.exit(0); -} - -// Step 7 — Fail. Docs-only modifications, no closure, fewer than 3 findings. -const message = [ - "", - `[lint-pr-shape] FAIL: docs-only PR with ${fileChanges.length} finding(s) and no task closure.`, - "", - "Files changed:", - ...fileChanges.map((change) => ` ${change.status} ${change.path}`), - "", - "To pass, do one of:", - " (a) Close a TASKS.md task — include `closes ` in the", - " HEAD commit message.", - " (b) Batch >=3 distinct doc-drift findings into one commit.", - " (c) If a structural change makes sense (new doc page, doc rename),", - " do that instead of a one-line edit.", - "", - "Per `taskgrind.md` rule 9 + AGENTS.md Task queue conventions.", - "", - "Emergency override (human operator only — autonomous agents must NOT set):", - " ALLOW_SINGLE_DOC_PR=1 yarn lint:pr-shape", - "", -]; -for (const line of message) console.error(line); -process.exit(1); diff --git a/taskgrind/scripts/safe-admin-merge.sh b/taskgrind/scripts/safe-admin-merge.sh deleted file mode 100755 index 109ba2b..0000000 --- a/taskgrind/scripts/safe-admin-merge.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env bash -# scripts/safe-admin-merge.sh -# -# Rate-limited wrapper around `gh pr merge --admin`. Autonomous agents -# must use this instead of the raw command per taskgrind.md rule 7 + -# AGENTS.md "Task queue conventions". Enforces a 5-per-24h ceiling on -# self-authored admin merges to a shared master branch. -# -# Usage: -# bash scripts/safe-admin-merge.sh [extra gh args...] -# -# Examples: -# bash scripts/safe-admin-merge.sh 119 --squash -# bash scripts/safe-admin-merge.sh --dry-run # check rate only -# ALLOW_ADMIN_BURST=1 bash scripts/safe-admin-merge.sh 119 --squash -# # human override -# -# After a successful merge, appends a one-line entry to -# `.agent-merge.log` (gitignored) for telemetry. The check script -# reads from `gh` directly, not the log — the log is informational -# only. -# -# Exit codes follow check-admin-merge-rate.mjs: -# 0 merge succeeded (or --dry-run passed) -# 1 rate limit exceeded; merge refused -# 2 data-fetch error; merge refused defensively -# * whatever `gh pr merge` returns on its own failures - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" -LOG_FILE="${REPO_ROOT}/.agent-merge.log" -CHECK_SCRIPT="${SCRIPT_DIR}/check-admin-merge-rate.mjs" - -# Dry-run: just check the rate, don't merge anything. -if [ "${1:-}" = "--dry-run" ]; then - exec node "${CHECK_SCRIPT}" -fi - -# Args sanity. -if [ -z "${1:-}" ]; then - echo "[safe-admin-merge] Missing PR number." >&2 - echo "Usage: bash scripts/safe-admin-merge.sh [extra gh args...]" >&2 - exit 64 # EX_USAGE -fi - -PR_NUMBER="$1" -shift - -# Rate check (skip if human explicitly overrides). -if [ "${ALLOW_ADMIN_BURST:-}" = "1" ]; then - echo "[safe-admin-merge] ALLOW_ADMIN_BURST=1 — skipping rate check (human override)." >&2 -else - if ! node "${CHECK_SCRIPT}"; then - echo "" >&2 - echo "[safe-admin-merge] Rate check failed. Refusing to merge PR #${PR_NUMBER}." >&2 - exit 1 - fi -fi - -# Run the merge with whatever extra args the caller passed. -gh pr merge "${PR_NUMBER}" --admin "$@" - -# Log the merge. ISO 8601 UTC, PR number, author for audit. -TS="$(date -u +%Y-%m-%dT%H:%M:%SZ)" -ME="$(gh api user --jq .login 2>/dev/null || echo unknown)" -echo "${TS} ${ME} pr=${PR_NUMBER}" >> "${LOG_FILE}" -echo "[safe-admin-merge] Merged PR #${PR_NUMBER}; logged to .agent-merge.log." From 2d3179e9aa2a28b14f28f744eff28c09df60d638 Mon Sep 17 00:00:00 2001 From: fyodoriv Date: Fri, 29 May 2026 11:12:59 -0400 Subject: [PATCH 2/2] chore: remove archived taskgrind task entries --- TASKS.md | 171 ++++--------------------------------------------------- 1 file changed, 11 insertions(+), 160 deletions(-) diff --git a/TASKS.md b/TASKS.md index a254628..397bb78 100644 --- a/TASKS.md +++ b/TASKS.md @@ -25,7 +25,7 @@ Today `/next-task` reads `./TASKS.md` only — there's no first-class way to (a) pick the highest-priority unblocked task across one workspace, OR (b) pick across all workspaces on the host. Friction scales with workspace count × repo count. - The right answer is a workspace mode in the canonical tasks.md tooling, since the spec + parser + MCP + CLI are the load-bearing dependencies every other tool (agentbrew, dotfiles, minsky-observer, taskgrind) consumes. The design MUST support **multiple workspaces on one host** as a first-class concept — single-workspace mode is just N=1. + The right answer is a workspace mode in the canonical tasks.md tooling, since the spec + parser + MCP + CLI are the load-bearing dependencies every other tool (agentbrew, dotfiles, minsky-observer) consumes. The design MUST support **multiple workspaces on one host** as a first-class concept — single-workspace mode is just N=1. **Outcome-shaped** (what a workspace-aware operator sees): @@ -81,120 +81,6 @@ ## P2 -- [ ] Add mid-session main sync (or per-session sync) option to taskgrind - - **ID**: taskgrind-per-session-sync - - **Tags**: taskgrind, sync, queue, duplicate-work, cross-repo - - **Blocked**: needs-user-approval — the deliverable is a code - change in `/Users/fivanishche/apps/taskgrind` (`bin/taskgrind`, - `tests/git-sync.bats`, `README.md`, `man/taskgrind.1`), and - landing it requires pushing a feature branch to that remote and - opening an upstream PR — a cross-repo public write outside the - current `tasks.md` workspace that requires explicit - current-session operator approval. Additionally, the taskgrind - repo is being actively modified right now: 8 files dirty on - `main` including `bin/taskgrind`, `tests/features.bats`, - `tests/signals.bats`, `README.md`, `man/taskgrind.1`, - `docs/architecture.md`, `docs/user-stories.md`, and `TASKS.md`, - with a `bats tests/signals.bats` run in flight (PID 38645+) and - `.taskgrind-state` reporting `status=running session=4`. Two of - the three target files (`bin/taskgrind`, `README.md`) overlap - with the concurrent agent's WIP — picking this up from a - `tasks.md` session would race their edits. The previous - `**Blocked**` line on this task was unintentionally removed by - the squash-merge of #45 (which re-included session 4's pre-block - snapshot of TASKS.md); restoring it here. - - **Details**: With `TG_SYNC_INTERVAL=5` (default), sessions 1-4 - work off the same stale `main`. During the 2026-05-02 tech-lead - run, the operator merged session 1's PRs (which removed 5 task - blocks) but session 2 still saw the old TASKS.md and re-claimed - one of the just-completed tasks (`reconcile-session-28-30-followups`), - redoing the cherry-picks and conflicts on a fresh branch. Add an - option `TG_SYNC_INTERVAL=1` (or change the default) and document - the trade-off — frequent syncs add fetch overhead but prevent - duplicate-work episodes when an external operator is merging - PRs in parallel. Better: detect when local main has diverged - from origin/main between sessions and force a sync regardless of - interval. - - **Files** (in taskgrind repo): `bin/taskgrind`, - `lib/constants.sh`, `tests/git-sync.bats`, `README.md`, - `man/taskgrind.1` - - **Acceptance**: Default sync interval is 1 OR a "concurrent - operator" mode is available that forces `git fetch && rebase` - between every session. Documented behavior change. Test added. - - **Research**: 2026-05-02 — implementation sketch - Sync behavior in taskgrind currently lives in three call sites: - 1. `lib/constants.sh:35` defines `DVB_DEFAULT_SYNC_INTERVAL="5"`. - 2. `bin/taskgrind:673` reads - `sync_interval="${DVB_SYNC_INTERVAL:-$DVB_DEFAULT_SYNC_INTERVAL}"` - (the `TG_*` → `DVB_*` translation table around line 231 maps - `TG_SYNC_INTERVAL` to `DVB_SYNC_INTERVAL`). - 3. The sync gate at `bin/taskgrind:5413` triggers when - `sync_interval == 0` OR `session % sync_interval == 0`, with - a `_dvb_slot >= 1` early-return so only slot 0 syncs and a - `git_sync skipped (interval=…, session=…)` log line at - `bin/taskgrind:5586` for the non-trigger path. - Help/doc surfaces that mention the default: `bin/taskgrind:94` - (header help block), `README.md:248` (env-var table row), and - `man/taskgrind.1` (the same env-var entry — concurrent agent is - rewriting this file as part of the `TG_STALL_EXIT` consolidation). - Smallest-change option (acceptance "default sync interval is 1"): - flip `DVB_DEFAULT_SYNC_INTERVAL` from `"5"` to `"1"`, then update - the three doc surfaces above. Existing tests in - `tests/git-sync.bats` already parameterize - `DVB_SYNC_INTERVAL=0|2|3` (lines 42-131), so a new bats case can - drop the `DVB_SYNC_INTERVAL` export entirely and assert that - every loop iteration logs `git_sync` (not `git_sync skipped`). - Divergence-detection option (acceptance "concurrent-operator - mode that forces sync"): keep the default at 5 but add a cheap - probe before the interval gate at line 5413 — `git fetch - --quiet origin "$_default_branch"` followed by `git rev-list - HEAD..origin/$_default_branch --count`. If the count is non-zero, - log `git_sync forced reason=diverged ahead=N` and run the - existing stash/checkout/fetch/rebase block; otherwise fall - through to the interval logic. Bats coverage stages a remote one - commit ahead and asserts `git_sync forced` appears even with - `DVB_SYNC_INTERVAL=99`. Either option must keep the - `_dvb_slot >= 1` early-return so only slot 0 syncs. - Concurrent-agent note: the live README diff in taskgrind is the - `TG_STALL_EXIT` consolidation (collapsing `TG_NO_STALL_EXIT`, - `TG_EXIT_ON_STALL`, and `TG_EARLY_EXIT_ON_STALL` into a single - `TG_STALL_EXIT={never|first|second}` knob) — it rewrites the - same env-var table neighborhood as the `TG_SYNC_INTERVAL` row, - so this work should land after that branch merges (or be - rebased onto it) to avoid a textual conflict. The taskgrind - state file `.taskgrind-state` showed `status=running session=4` - during this enrichment, so the operator should also wait for - that grind to finish (or pause it) before unblocking. - - **Last-enriched**: 2026-05-02 - -- [ ] File Bosun follow-up: deliver orphan `main` commit `924f8f14` - - **ID**: bosun-orphan-main-commit-924f8f14 - - **Tags**: bosun, delivery, cross-repo - - **Blocked**: needs-user-approval — this task lives in a different - repo (`/Users/fivanishche/apps/bosun`) and would require pushing a - feature branch to that remote and opening a Bosun PR. Cross-repo - publication outside the current `tasks.md` workspace requires - explicit current-session operator approval. Another agent is also - actively working in Bosun, so the operator should coordinate the - handoff before unblocking. - - **Details**: `/Users/fivanishche/apps/bosun` local `main` is one - commit ahead of `origin/main` with `924f8f14 refactor(skills): apply - 3 improvement themes to user persona AIFN-720` (authored - 2026-05-02 12:33). Direct commits on `main` violate the Bosun policy - `` from `bosun/TASKS.md`. The proper delivery path is to - move the commit to a feature branch and open a PR. Another agent is - actively working in Bosun (saw branch flips and staged `bin/bosun` - changes during this taskgrind run), so coordinate before rewriting - local `main`. - - **Files** (in bosun repo): - `skill-plugins/orchestrator/orchestrator-user/SKILL.md` - - **Acceptance**: Either (a) commit is delivered via a Bosun PR with - `AIFN-720` suffix and bosun's `cd orchestrator && npm run verify` - passes, or (b) the commit is reverted on local main if it duplicates - work already on origin/main. - - **Last-enriched**: 2026-05-02 - ## P3 - [ ] Set up custom domain for GitHub Pages @@ -224,41 +110,9 @@ Generated by `companion-task-groom` on 2026-05-21. Lint status: pass — `npx -y @tasks-md/lint TASKS.md` exits 0 with 0 errors. Total tasks before this append: 3 (P2: 2, - P3: 1). Findings (3): - - 1. [probable-dead] `taskgrind-per-session-sync` (P2, line - 13). All 5 paths in `**Files**` live in - `/Users/fivanishche/apps/taskgrind/` (`bin/taskgrind`, - `lib/constants.sh`, `tests/git-sync.bats`, `README.md`, - `man/taskgrind.1`), but that directory does not exist - on this host. The in-repo `taskgrind/` subdir at - `/Users/fivanishche/apps/tooling/tasks.md/taskgrind/` - only contains `prompt-template.md`, `README.md`, and - `scripts/` — none of the referenced paths match. The - task's `**Research**` field also pins line numbers in - the same dead paths (`bin/taskgrind:673`, - `bin/taskgrind:5413`, `lib/constants.sh:35`, etc.). - Last-enriched 2026-05-02. Action: confirm with operator - whether the taskgrind tool still exists at a different - path, then either refresh `**Files**` / `**Research**` - against the new location or retire the task with a - commit message noting the repo is gone. - - 2. [probable-dead] `bosun-orphan-main-commit-924f8f14` (P2, - line 99). `**Files**` references - `/Users/fivanishche/apps/bosun/skill-plugins/orchestrator/orchestrator-user/SKILL.md`, - but `/Users/fivanishche/apps/bosun/` does not exist - anywhere under `/Users/fivanishche/apps/` on this host - (verified with `find /Users/fivanishche/apps -maxdepth 4 - -name bosun` returning empty). The orphan commit - `924f8f14` was on that repo's local `main`, so if the - repo is gone the deliverable is presumably also gone. - Last-enriched 2026-05-02. Action: confirm whether bosun - was renamed, archived, or retired; if the orphan commit - no longer needs delivery, remove the task with a commit - message noting the bosun repo is gone. + P3: 1). Findings (1): - 3. [worker-fixable] `set-up-github-pages-custom-domain` + 1. [worker-fixable] `set-up-github-pages-custom-domain` (P3, line 129). Carries `**ID**`, `**Tags**`, `**Blocked**`, `**Details**`, `**Research**`, `**Files**`, and `**Last-enriched**`, but no `**Acceptance**` line. @@ -272,23 +126,20 @@ what "done" looks like. Suggested resolution path: worker (or operator) reviews - each finding and either updates the underlying task, + the finding and either updates the underlying task, removes it with reasoning in the commit message, or - explicitly defers. Once all three findings are addressed, + explicitly defers. Once the finding is addressed, remove THIS grooming task in the same commit and confirm `npx -y @tasks-md/lint TASKS.md` still passes. - Bucket counts: worker-fixable=1, probable-dead=2, + Bucket counts: worker-fixable=1, probable-dead=0, stale-claim=0, duplicate=0, spec-violation=0. Companion-sweep notes: - - Both probable-dead tasks already carry - `**Blocked**: needs-user-approval` for cross-repo writes, - so they were already unpickable. The dead-files finding - just promotes them from "blocked-but-someday" to - "blocked AND likely obsolete". - - All three tasks were last-enriched 2026-05-02 (19 days - before this sweep), so they have cleared the 7-day + - Archived project task entries have been removed in a prior + commit. + - The remaining task was last-enriched 2026-05-02 (19 days + before this sweep), so it has cleared the 7-day enrichment cooldown from spec.md § "Enriching blocked tasks". No fresh-cooldown skips this round. - No `(@agent-id)` claims appear on any task, so no @@ -298,7 +149,7 @@ so all findings are batched inline here instead of staged to a separate audit file. - **Files**: `TASKS.md` - - **Acceptance**: Each of the three findings above is + - **Acceptance**: The finding above is addressed — task updated in-place, removed with reasoning in the commit message, or explicitly deferred with a documented rationale appended to the task. After