Commit Graph

16 Commits

Author SHA1 Message Date
naudachu 091dceec1d refactor: split issue domain from Gitea transport
An issue was a Gitea row that happened to be cached locally: its identity
was the tracker's number (42.md), its dependencies were tracker numbers
(depends: [#12]), and a local issue existed only as a draft that push
deleted on success. Nothing could be planned or tracked without a tracker.

Split into layers, with knowledge flowing one way:

  skills/issue  DOMAIN  what an issue is: format, validation, dep graph
        ^               offline; stdlib imports only, no subprocess
        | imports
  skills/sync   BRIDGE  map.py    md <-> Gitea JSON, pure, no I/O
                        _gitea.py login pin, api, pagination, filters
  skills/use    REFERENCE  tea CLI docs for non-issue entities

skills/issue never imports skills/sync. Delete the sync layer and the
domain keeps working.

Identity is now a slug derived from the title (wire-sqlc-appclick.md) and
is stable across retitles and pushes. Tracker numbers live in a `gitea:`
field, never in a file name and never in `depends:`; the pair is indexed
in .remote.json, which is a cache over the files, not a second source of
truth.

Behavior changes:

- Pushing is additive. The file is never deleted; it gains gitea:/url:/
  synced: and origin: flips from local to gitea. `origin: local` is a
  durable state, not a pending one.
- Pushes go in topological order so dependencies get numbers first.
- The dependency graph is computed offline from `depends:` metadata; body
  prose is passed through unchanged in both directions rather than being
  rewritten between slugs and #N.
- `origin` is domain-owned (whether work exists elsewhere is a fact about
  the work); the handle and how to reach it stay with sync.

Script moves:

  issue_get.py   -> sync/pull.py
  issue_push.py  -> sync/push.py
  issue_list.py  -> sync/remote.py
  issue_index.py -> issue/issue_index.py
  _tea.py        -> split into issue/issue.py, sync/map.py, sync/_gitea.py

New: issue/issue_new.py, issue/issue_check.py, issue/issue_tree.py, and
sync/comment.py — comment posting was the last issue operation still
hand-rolled through raw `tea api`.

references/issue-format.md moves to skills/issue/references/format.md;
label hex colors move out of it into map.py, since a color is how a
tracker paints a chip, not what an issue is.

Verified: offline path end to end (new, check, tree, index, push
--dry-run) and read-only against Gitea (remote listing, pull with
mapping, comment guard). Write paths of push.py and comment.py are not
exercised here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 23:37:32 +05:00
naudachu 335b0bbd54 feat: local issue cache and draft-then-push workflow
Replace fetch_issue.py with four scripts around a flat, greppable cache in
tmp/issues/. Planning stays offline and issues reach Gitea in one push:

- issue_get.py: fetch by key or by filter (--milestone/--label/-q). The list
  endpoint carries issue bodies, so a whole milestone costs one request per 50
  issues. Gitea silently ignores an unresolvable milestones= filter and returns
  the entire backlog, so the milestone is resolved up front and every returned
  issue is re-checked locally. --deps walks the dependency graph downwards via
  the structured sections plus native dependencies and writes tree-<slug>.md.
- issue_push.py: validate a local draft against the canonical format, create
  missing labels with the right colors and exclusivity, POST, delete the draft.
- issue_list.py: discovery to stdout, writes nothing.
- issue_index.py: rebuild INDEX.md from what is on disk.

Files use one metadata field per line with inline lists so plain grep works
without a parser. This is a cache and a drafting area, not a mirror: no drift
tracking, no sync back.

Projects are not fetchable — the projects API is 404 on Gitea 1.26; documented
alongside the milestone caveat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 19:35:38 +05:00
naudachu 3549f37ebf chore: gitignore CLAUDE.md symlinks
agents-sync recreates CLAUDE.md -> AGENTS.md in every directory on each
Bash call, so tracking them adds churn and duplicates content that
already lives in AGENTS.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 18:08:11 +05:00
naudachu 492c27df98 feat: align issue format with wiki workflow spec
Match the Issues-Workflow wiki (claude-skills/tea#1): types expand to
bug|task|refactor|test|feature|draft (feature becomes a container,
task takes over new functionality), add severity/tech/comp label
namespaces, an optional "Depends on" section, and templates for
test and feature. Milestone/Project containers documented.

Refs claude-skills/tea#1

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 18:06:16 +05:00
naudachu 6c6e0149ac feat: fetch_issue script for trimmed issue reads
Raw `tea issues -o json` / `tea api` dumps the full payload (avatars,
nested users, every comment) into the model context. The script writes
trimmed markdown to tmp/issue/<n>/ and prints only a compact index;
login comes from the operator pin, never an argument.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 18:06:09 +05:00
naudachu e9ddc999f7 feat: add agents-sync hook and AGENTS.md
Keep AGENTS.md as the single source of agent docs: agents-sync runs
before every Bash command and makes each directory canonical —
AGENTS.md real file, CLAUDE.md a symlink to it. Renames a lone
CLAUDE.md, creates or re-points symlinks, replaces identical
duplicates; differing files are only reported, never merged. Fails
open so it can never block a command.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 17:54:47 +05:00
naudachu 061196c41b feat: issue skill 2026-08-07 14:24:21 +05:00
naudachu 96661d2783 refactor: rename login skill to auth
`/tea:login` could not be invoked as `/login` because Claude Code
reserves that name as a built-in command. Renaming the skill to `auth`
frees the short form: `/tea:auth` and `/auth` both resolve.

Updates all references in the guard hook, README, plugin manifests, and
the use skill.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 15:22:10 +05:00
naudachu 47a95ba6cb readme: fix install via plugin marketplace
The install steps used a non-existent "plugins" settings key, so a fresh
clone loaded nothing. Add .claude-plugin/marketplace.json and document the
supported /plugin marketplace add + /plugin install tea@tea flow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 22:20:54 +05:00
naudachu a97c1a6638 readme: python req added; 2026-06-03 06:35:31 +05:00
naudachu b8b4cb3eae readme 2026-06-03 06:34:28 +05:00
naudachu dc8706e809 Merge branch 'login-rewrite-guard' 2026-05-30 16:27:39 +05:00
naudachu bac45028bf tea-guard: resolve and rewrite --login instead of env-checking
The previous guard required $GITEA_LOGIN to be set in the Bash environment,
which (a) only happens after a session restart and (b) let Claude name any
login it liked as long as one was set. Two failures: pinning needed a restart
to take effect, and Claude could pick the wrong identity from memory.

Rewrite the guard (now python3 for JSON in/out) to RESOLVE the login itself:

- Claude must write the literal placeholder --login "$GITEA_LOGIN".
- The hook reads the operator's pin from .claude/settings.local.json
  (env.GITEA_LOGIN) at call time — from the FILE via CLAUDE_PROJECT_DIR/cwd
  walk-up — and rewrites the command to that literal via updatedInput.
- A literal login, another variable, an empty value, or a missing --login are
  all blocked: Claude may not choose the identity, only the operator may.
- No pin -> block with a pointer to /tea:login.

Effect: pinning works in the same session (no restart), and Claude can no
longer act under a login it picked. /tea:login now mandates an explicit
operator choice (AskUserQuestion), never inferring from memory. /tea:use
documents the placeholder-only contract.

Guard unit-tested across 13 rewrite/block/passthrough cases incl. -l,
--login=, ${...}, compound+walkup+pipe. claude plugin validate passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 16:26:54 +05:00
naudachu d4aa0a9038 Merge branch 'plugin-restructure' 2026-05-30 15:54:49 +05:00
naudachu b3db734cd8 Restructure tea skill into a plugin with a mandatory-login guard
Convert the standalone `tea` skill into a skills-dir plugin so commands are
namespaced and an enforcement hook can ship with it:

- /tea:login  — pin the project Gitea login into .claude/settings.local.json
- /tea:use    — tea CLI reference (was the old root SKILL.md), with the
                login rule slimmed since the hook now enforces it
- hooks/tea-guard.sh — PreToolUse(Bash) guard: blocks any `tea` command that
  touches Gitea unless it carries --login and $GITEA_LOGIN is set. Exempts
  `tea logins list` and `tea --version/--help` so /tea:login can bootstrap.

References moved under skills/use/references/. `claude plugin validate` passes;
guard unit-tested across allow/block cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 15:54:48 +05:00
naudachu bb4ad963ee initial 2026-05-18 15:50:02 +05:00