skills/tusk-update/SKILL.md
Update domains, agents, task types, and other config settings post-install without losing data
npx skillsauth add gioe/tusk tusk-updateInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Updates the tusk configuration after initial setup. Modifies tusk/config.json and regenerates validation triggers without destroying the database or existing tasks.
Run all diagnostics without prompting the user. Collect everything needed to form recommendations in one pass.
1a. Current config and domain task counts:
tusk config
tusk -header -column "
SELECT domain, COUNT(*) as task_count
FROM tasks
WHERE status <> 'Done'
GROUP BY domain
ORDER BY task_count DESC
"
1b. CLAUDE.md convention scan:
ls CLAUDE.md 2>/dev/null && echo "exists" || echo "not found"
If CLAUDE.md exists, scan for any section whose heading contains the word "convention" (case-insensitive). Extract bullet points (lines beginning with - or *). If no matching section is found, or if the section contains only a pointer line (e.g., "Run tusk conventions list..."), there is nothing to migrate.
1c. Existing conventions:
tusk conventions list
Compare against CLAUDE.md bullets to identify which bullets are not already in the DB. These are migration candidates.
1d. Pillars:
tusk pillars list
1e. Test command detection:
tusk test-detect
This inspects the repo root for lockfiles and returns JSON {"command": "<cmd>", "confidence": "high|medium|low|none"}.
1f. Unassigned tasks:
tusk -header -column "
SELECT id, summary, task_type, priority
FROM tasks
WHERE status <> 'Done'
AND (domain IS NULL OR domain = '')
ORDER BY priority_score DESC, id
"
Hold all findings in memory. Do not output anything to the user yet.
Analyze the findings from Step 1 and form a prioritized, numbered recommendation list. Each entry must include what was found, what action would be taken, and why.
Findings that trigger a recommendation:
| Finding | Recommendation | Justification |
|---------|---------------|---------------|
| CLAUDE.md convention bullets not in DB | Migrate all bullets and replace section with pointer line | DB-stored conventions are queryable by topic; CLAUDE.md bullets are not |
| test_command is empty and test-detect returned a high/medium-confidence command | Set test_command to the detected command | Enables pre-commit test gate |
| test_command is set but differs from auto-detected command (high confidence) | Update test_command to the detected command | Detected command better matches current project tooling |
| Pillars list is empty | Note: no pillars configured; suggest running /tusk-init to seed them | Pillars provide design vocabulary for task evaluation |
| N open tasks have no domain assigned and the user has requested adding a new domain | Reassign N unassigned tasks to <new_domain> | Keeps the backlog organized without manual follow-up |
| worktree.symlink_files is empty and project_type has a canonical default (python_service → [".venv", ".env"]; web_app → ["node_modules", ".env", ".env.local"]) | Suggest adopting the canonical default | /tusk-init seeds these for fresh installs; pre-TASK-409 installs need an explicit opt-in to adopt them |
| User invoked /tusk-update <description of changes> | Include the user-requested changes as explicit recommendation items | User intent overrides auto-detection |
Note on low/none confidence: If
test-detectreturnslowornoneconfidence, notest_commandrecommendation is generated — auto-detection could not identify the project's test tooling with sufficient certainty. To set or cleartest_commandin this case, describe the change explicitly when invoking the skill (e.g.,/tusk-update set test_command to pytest tests/).
If the user specified changes when invoking /tusk-update (e.g., /tusk-update add a testing domain), include those as top-priority items in the recommendation list alongside any auto-detected findings.
Present the full recommendation list in one message. Format each item as:
[N] <Action>
Finding: <what was found>
Why: <justification>
After the list, include the configurable fields reference table so the user knows what is changeable:
| Field | Requires Trigger Regen | Notes |
|-------|----------------------|-------|
| domains | Yes | Empty array disables validation |
| task_types | Yes | Empty array disables validation |
| statuses | Yes | Always validated; changing can break workflow queries |
| priorities | Yes | Always validated |
| closed_reasons | Yes | Always validated |
| agents | No | { "<name>": "description" } — see note below |
| test_command | No | Shell command run before each commit; empty string disables the gate |
| dupes.strip_prefixes | No | Python-side only |
| dupes.check_threshold | No | Python-side only (0.0–1.0) |
| dupes.similar_threshold | No | Python-side only (0.0–1.0) |
| review.mode | No | "disabled" or "ai_only"; config-side only |
| review.max_passes | No | Integer; max fix-and-re-review cycles; config-side only |
| review.reviewer | No | Single {name, description} object (or absent for inline-only review); config-side only |
| review_categories | Yes | Valid comment categories; empty array disables validation |
| review_severities | Yes | Valid severity levels; empty array disables validation |
| project_type | No | String key identifying the project type (e.g. python_service, ios_app); null if unset |
| project_libs.*.ref | No | Pin a project lib's bootstrap ref to a tag or commit SHA; defaults to "main" |
| worktree.symlink_files | No | JSON array of basenames symlinked from the primary checkout into new task worktrees; empty array disables auto-symlink. See Step 5c for set/clear/append semantics. |
Agents object shape: { "<agent_name>": "<description string>" } — e.g. { "backend": "API, business logic, data layer", "frontend": "UI components, styling, client-side" }.
Then ask a single approval question:
Which of these would you like to apply? Enter numbers (e.g.
1,3),all,none, or describe additional changes:
If there are no recommendations and the user requested no changes: Report "Config looks healthy — no recommendations." and exit. Do not proceed further.
Before removing any value from a trigger-validated field (domains, task_types, statuses, priorities, closed_reasons), check if existing tasks use that value:
# Example: check if domain "old_domain" is in use
tusk -header -column "
SELECT id, summary, status FROM tasks
WHERE domain = 'old_domain' AND status <> 'Done'
"
If tasks use a value being removed:
old to domain new)tusk "UPDATE tasks SET domain = 'new_value', updated_at = datetime('now') WHERE domain = 'old_value'"
For Done tasks referencing removed values: these won't cause trigger issues (triggers only fire on INSERT/UPDATE), but warn the user that historical data will reference the old value.
Show a clear diff of what will change:
Current config:
domains: [cli, skills, schema, install, docs, dashboard]
Proposed config:
domains: [cli, skills, schema, install, docs, dashboard, testing]
^^^^^^^
Added: testing
Wait for explicit user confirmation before writing.
Use the Read tool to load tusk/config.json, then Edit to update only the fields the user requested — preserve everything else.
Convention migration (if approved in Step 2): For each approved bullet, strip markdown markers (**, *, backtick fences) and insert as plain text:
tusk conventions add "<bullet text>"
Then Edit CLAUDE.md to replace the "Key Conventions" section body with Run \tusk conventions list` to see project conventions.` (leave the heading in place).
Only if a domain reassignment was approved in Step 2. Execute without additional prompting:
DOMAIN=$(tusk sql-quote "<new_domain>")
# All unassigned tasks:
tusk "UPDATE tasks SET domain = $DOMAIN, updated_at = datetime('now') WHERE status <> 'Done' AND (domain IS NULL OR domain = '')"
# Or specific IDs (e.g., 12, 15, 18):
tusk "UPDATE tasks SET domain = $DOMAIN, updated_at = datetime('now') WHERE id IN (12, 15, 18) AND status <> 'Done'"
tusk "SELECT changes() AS rows_updated"
Report the rows_updated count. If multiple domains were added, repeat for each.
worktree.symlink_files (if approved)worktree.symlink_files is a JSON array nested under worktree in tusk/config.json. tusk task-worktree create reads it and symlinks each basename from the primary checkout into new task worktrees (the documented mechanism for letting gitignored runtime files like .venv, .env, node_modules reach feature-branch worktrees). All edits route through tusk init-write-config --worktree-symlink-files '<json_array>'. The helper replaces the entire array on every call — there is no append flag, so append flows must read the current value, merge, and pass the full merged list.
Show the current value before editing:
tusk config worktree
This returns the worktree JSON subtree (e.g. {"symlink_files":[".venv",".env"]}). Pull symlink_files from it. If the worktree key is absent or symlink_files is missing, treat it as [].
Set: pass the full desired list.
tusk init-write-config --worktree-symlink-files '[".venv", ".env"]'
Clear (revert to [], disables auto-symlink):
tusk init-write-config --worktree-symlink-files '[]'
Append: read the current value (e.g. [".venv", ".env"]), concatenate the new basenames, deduplicate, then pass the full merged array. Do not pass only the new basename(s) — that overwrites the existing list.
# Current: [".venv", ".env"]; appending "node_modules"
tusk init-write-config --worktree-symlink-files '[".venv", ".env", "node_modules"]'
Validation: the value must be a JSON array of strings. Non-string entries (e.g. [".venv", 42]) are rejected with --worktree-symlink-files must be a JSON array of strings, and the existing config is left untouched.
Effect: immediate for any new tusk task-worktree create calls. Existing worktrees are not retroactively re-symlinked — they retain whatever symlinks (or lack thereof) they had at creation time. To apply a new list to an existing worktree, remove and recreate it.
This field is not trigger-validated, so Step 6 (regen-triggers) is not required after editing it.
If any trigger-validated field was changed (domains, task_types, statuses, priorities, closed_reasons), regenerate triggers:
tusk regen-triggers
This drops all existing validate_* triggers and recreates them from the updated config. No data is lost.
If only non-trigger fields changed (agents, dupes, test_command, review), skip this step.
project_type changed)If project_type was changed in Step 5 (set, cleared, or replaced), re-run the applies_to_project_types filter so .claude/skills/ matches the new value:
tusk reconcile-skills
This installs any skills now matching the new project_type and removes any skills that no longer match. Universal skills (no applies_to_project_types frontmatter) are left untouched. Print the one-line summary the command emits — typically either Skills already in sync (project_type=<pt>). or Reconciled skills (project_type=<pt>): Installed N: <names> / Removed N: <names>.
If project_type was not changed in this run, skip this step.
Confirm the changes took effect:
tusk config
If trigger-validated fields (domains, task_types, statuses, priorities, closed_reasons) were changed, follow the smoke-test procedure in SMOKE-TEST.md to verify the regenerated triggers accept valid values and reject invalid ones. Otherwise skip.
Never call tusk init --force — this destroys the database. Use tusk regen-triggers instead.
If the user wants to add a project lib that is not yet in project_libs, or if Step 1 reveals unconfigured built-in libs, run this step.
8a. Identify unconfigured built-in libs:
Built-in libs are defined in config.default.json under project_libs. Check which ones are not yet configured in the project's tusk/config.json:
tusk config project_libs
Compare against the built-in keys (ios_app, python_service). Any key absent from the project config is an unconfigured built-in.
8b. Present and confirm:
List unconfigured libs to the user. Example:
The following built-in libs are available but not yet configured:
ios_app(gioe/ios-libs) — SharedKit UI design tokens, APIClient HTTP clientpython_service(gioe/python-libs) — structured logging, OpenTelemetry/Sentry observabilityWould you like to add any of these? You can also provide a custom
--repo owner/repo.
Wait for the user to select one or more libs (or decline).
8c. Add the lib and seed bootstrap tasks:
For each selected lib, call:
tusk add-lib --lib <name>
# or for a custom lib:
tusk add-lib --lib <name> --repo <owner/repo> [--ref <branch|tag|sha>]
This writes the lib to project_libs in tusk/config.json (no DB reinit) and fetches the bootstrap task list.
Parse the JSON output. If error is non-null, report it and skip seeding. Otherwise, present the fetched tasks to the user using the same pattern as /tusk-init Step 8.5:
Available bootstrap tasks for
<lib>:
<summary>(complexity: XS, priority: High)<summary>(complexity: S, priority: Medium) ...Seed all tasks? Enter numbers (e.g.
1,3),all, ornone:
For each confirmed task, insert it:
tusk task-insert "<summary>" "<description>" \
--priority <priority> \
--task-type <task_type> \
--complexity <complexity> \
--domain <appropriate_domain> \
$(for criterion in <criteria_array>; do echo "--criteria \"$criterion\""; done)
Report the created task IDs to the user.
Run tusk validate as the canonical final check after all writes and trigger regens:
tusk validate
tusk validate fails: show the full output to the user and warn that the configuration or database may have issues.tusk validate passes: report "✓ Configuration updated and validated successfully."data-ai
Autonomously work through the backlog — dispatches /chain for chain heads, /tusk for standalone tasks, repeating until empty
tools
Investigate the scope of a problem and form an honest assessment — task creation is optional
data-ai
Groom the backlog by closing completed tickets, removing redundant/stale tickets, reprioritizing, and assigning agents
tools
File a GitHub issue against the tusk repo itself — tusk bugs, CLI limitations, skill improvements, or missing features. Use anytime the user identifies a gap in tusk (not in their own project's code).