Fetch Source
Turns whatever the user is starting from into a project: creates .planning/[project-name]/ and
writes seed.md. This is Step 1 of the GSDL pipeline:
Pick up source → Brainstorm → SPEC.md → Slices → Review → Branch → Execute → Verify → PR + bot → Close loop
GSDL accepts three kinds of starting point, and treats them identically from Step 3 onwards:
| Starting point | Example | Tracker item created later? |
|---|---|---|
| Tracker work item | ENG-441, PROJ-45, owner/repo#12, a Linear/Jira/GitHub URL |
Yes — slices become sub-items of it |
| Specification document | A Notion, Slite, or Confluence URL | Only if a tracker is configured and the user wants a parent item |
| A plain idea | "a CLI that syncs local files to S3" | No — tasks.md is the work-item list |
There is no wrong entry point. A project with no tracker runs the same nine steps and produces the same artifacts.
Tier
SMALL. This step is mechanical: resolve an ID, call one API, fill a template. All tracker I/O in
GSDL runs on SMALL (see gsdl-tracker-sync for the write path) — when the gsdl orchestrator runs
this step it spawns a SMALL subagent for it. Invoked standalone on a larger model that's fine, but
there is no judgment here worth paying for.
Running as a subagent means non-interactive: don't ask the user anything. If you hit the manual fallback, an existing project folder, or unparseable input, return a BLOCKER to the caller describing exactly what's needed instead of guessing or overwriting. The orchestrator resolves those with the user on the LARGE tier.
Inputs
SOURCE— a work-item URL or ID, a document URL, or the user's plain description of the ideaSOURCE_TYPE(optional) — explicit hint when auto-detection would be wrong
Output
- Project name — kebab-case, prefixed with the item ID when there is one
.planning/[project-name]/folder, created if absent.planning/[project-name]/seed.md, populated from the source.planning/gsdl.config.md, created on the first run in this workspace- Returned to the caller: project name, seed path, item ID (or
none), the tracker's suggested branch name (ornone), and a one-line source summary
Step 1 — Resolve the Provider Config
Read the config contract in the gsdl skill's config.md (resolve the path the same way sub-skills
are resolved), then read .planning/gsdl.config.md.
If it exists, use it. Do not re-probe, and do not override a field the user set by hand.
If it does not exist, probe once and create it:
- List connected MCP servers and match them against the provider table in
config.md. - Check the credential environment variables for anything not covered by MCP.
- Check the forge:
gh auth statusfor GitHub. - Infer the model provider from the session's own model unless told otherwise.
- Write
.planning/gsdl.config.mdwith what you resolved and defaults for the rest.
Anything genuinely ambiguous — two trackers available, a docs target that needs a parent ID — is left blank and returned to the caller as a question, not guessed. A blank field is resolved later, on first use, and written back then.
Step 2 — Classify the Source
| Pattern | Source type |
|---|---|
linear.app/{team}/issue/{ID}/{slug} or a bare ABC-123 matching a Linear team key |
Linear item |
{site}.atlassian.net/browse/{KEY-123} or a bare KEY-123 matching a Jira project key |
Jira item |
github.com/{owner}/{repo}/issues/{n}, {owner}/{repo}#{n}, or #{n} in a GitHub repo |
GitHub issue |
notion.so/… / notion.site/… |
Notion page — an item if it is a row in the configured tracker database, otherwise a document |
slite.com/… or {workspace}.slite.com/p/… |
Slite document |
{site}.atlassian.net/wiki/… |
Confluence document |
| Anything else that is a URL | Unknown — try a generic web fetch, and say so |
| Not a URL or ID | A plain idea — go to Method D |
A bare ID is ambiguous between trackers. Disambiguate with the configured tracker provider
first; only if that fails, try the others. SOURCE_TYPE overrides all of this.
Step 3 — Fetch the Content
Try each method in order. Stop at the first success. Use the access method recorded in the config if there is one — the ladder below is for resolving it the first time.
Method 0 — MCP (preferred, auth handled automatically)
| Source | Typical tools |
|---|---|
| Linear | get_issue / linear_get_issue with { "id": "ENG-441" } |
| Jira | getJiraIssue / jira_get_issue with the issue key |
| GitHub | get_issue / github_get_issue with { owner, repo, issue_number } |
| Notion | retrieve-page + retrieve-block-children with the page ID |
| Slite | get-note / slite_get_note with the note ID |
| Confluence | getConfluencePage / confluence_get_page with the page ID |
Walk Notion/Confluence block trees for paragraph, heading_1/2/3, bulleted_list_item,
numbered_list_item, to_do, and callout content.
Record which tool worked so gsdl-tracker-sync can reuse the same access path.
Method A — API + credentials
Linear ($LINEAR_API_KEY):
curl -s -X POST -H "Content-Type: application/json" -H "Authorization: $LINEAR_API_KEY" \
-d '{"query":"{ issue(id: \"ENG-441\") { title description priority priorityLabel branchName state { name } team { id key } labels { nodes { name } } assignee { name } parent { identifier title } } }"}' \
https://api.linear.app/graphql
Jira ($JIRA_BASE_URL, $JIRA_EMAIL, $JIRA_API_TOKEN):
curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \
"$JIRA_BASE_URL/rest/api/3/issue/PROJ-45?fields=summary,description,status,priority,labels,assignee,parent,project"
Jira descriptions come back as Atlassian Document Format — flatten the content tree to markdown
rather than dumping the JSON.
GitHub Issues (gh, already authenticated):
gh issue view 12 --repo owner/repo --json title,body,state,labels,assignees,url,number
Notion ($NOTION_TOKEN):
curl -s -H "Authorization: Bearer $NOTION_TOKEN" -H "Notion-Version: 2022-06-28" \
https://api.notion.com/v1/pages/{PAGE_ID}
curl -s -H "Authorization: Bearer $NOTION_TOKEN" -H "Notion-Version: 2022-06-28" \
"https://api.notion.com/v1/blocks/{PAGE_ID}/children?page_size=100"
Slite ($SLITE_API_KEY):
curl -s -H "x-slite-api-key: $SLITE_API_KEY" https://api.slite.com/v1/notes/{NOTE_ID}
Confluence ($CONFLUENCE_BASE_URL, $CONFLUENCE_API_TOKEN, $JIRA_EMAIL):
curl -s -u "$JIRA_EMAIL:$CONFLUENCE_API_TOKEN" \
"$CONFLUENCE_BASE_URL/wiki/api/v2/pages/{PAGE_ID}?body-format=storage"
Method B — Web fetch fallback
If neither MCP nor credentials are available, fetch the URL directly. Works only for publicly shared content. Extract the meaningful text, ignoring navigation and boilerplate.
Method C — Manual fallback
If everything automated fails:
- Say which methods were attempted and why each failed.
- Offer: configure the relevant MCP server, set the credential env var, or paste the content directly into the chat.
- Wait for the user's choice.
As a subagent, do not do this yourself — return a BLOCKER naming the source, the methods tried, and the three options, and let the orchestrator run it with the user.
Method D — No source (a plain idea)
No fetch at all. Use the user's description as the seed content, and mark the project as having no tracker item. This is a supported path, not a fallback from a failure — do not apologise for it or push the user towards creating a ticket.
If the description is thin, that is fine: gsdl-create-spec (Step 3) asks the clarifying questions.
Capture what the user actually said rather than inventing detail.
Step 4 — Derive the Project Name
- Take the item/document title, or the first clause of the user's description
- Strip special characters (keep alphanumerics and spaces), replace spaces with hyphens, lowercase
- Truncate to ≤ 40 characters at a word boundary
- Prefix with the item ID when there is one:
eng-441-rule-calculator-class,proj-45-billing-webhook,gh-12-flaky-retry. With no item, the title alone.
Confirm the name with the user: "I picked up ENG-441 — Rule Calculator Class. I'll use
eng-441-rule-calculator-class as the project name — does that work?"
As a subagent (non-interactive), skip the prompt — return the derived name and let the caller confirm.
Step 5 — Create the Project Structure
mkdir -p .planning/[project-name]
Step 6 — Write seed.md
# {title}
## Source
{one of the Source blocks below}
## Initial Idea
{description — use as-is if concise; summarize lightly if very long}
## Problem/Opportunity
{Extract from the description if present; otherwise derive from the title and context}
## Key Features / Acceptance Criteria
{Extract bullet points, checklists, or sub-items from the description if present}
## Questions/Uncertainties
{Extract open questions from the description, or leave as a placeholder}
## Next Steps
- [ ] Brainstorm / clarify intent
- [ ] Produce SPEC.md
Source blocks
Tracker item:
- **Tracker**: {linear | jira | github | notion}
- **Item**: {ITEM-ID} — {SOURCE_URL}
- **Status**: {state name}
- **Priority**: {priority label, or "none"}
- **Assignee**: {name, or "Unassigned"}
- **Labels**: {comma-separated, or "None"}
- **Project/Team**: {team key or project key} ({ID})
- **Suggested branch**: {branchName if the tracker exposes one, else "none — derive from the project name"}
{if parent: "- **Parent item**: {identifier} — {title}"}
Only Linear exposes a suggested branch name today. For Jira, GitHub, and Notion, write
none — derive from the project name; Step 5.5 handles it.
Document:
- **Document**: {notion | slite | confluence} — {SOURCE_URL}
- **Tracker item**: none (created later if a tracker is configured)
Plain idea:
- **Source**: local idea, captured {YYYY-MM-DD}
- **Tracker item**: none
seed.md doubles as Step 2 (Brainstorm / Clarify Intent)'s starting point. The user may edit it
before gsdl-create-spec reads it.
Step 7 — Confirm and Hand Off
Show the user:
- The created
.planning/[project-name]/folder andseed.mdpath - A one-line summary of what was picked up
- Whether a provider config was created, and what it resolved to
- That the next step is producing
SPEC.mdviagsdl-create-spec
Called from the gsdl orchestrator, return the project name, the seed content, the item ID, the
project/team ID, and the suggested branch name, so the orchestrator can write progress.md and set
up the working branch at Step 5.5 without re-prompting.
Edge Cases
Project already exists
If .planning/[project-name]/ already has a seed.md, ask whether to re-fetch and overwrite (the
item may have changed) or keep the existing seed. As a subagent, return a BLOCKER — never overwrite.
The item has no description
Still create the project; note in seed.md that the source had no description and that the
clarifying questions in gsdl-create-spec will establish scope from scratch.
A document that is really a spec
Long documents often already contain goals, requirements, and non-goals. Map them into the matching
seed.md sections rather than dumping the whole document into ## Initial Idea. SPEC.md is still
produced at Step 3 — the document is input, not a substitute.
A bare ID that matches nothing
Report which trackers were tried and ask for a full URL.
Error Handling
| Situation | Action |
|---|---|
| Input matches no known pattern and is not prose | Ask for a URL, an ID, or a description |
| MCP tool call fails | Log the error; fall through to Method A |
| MCP tool not found | Fall through to Method A silently |
| Credentials missing | Skip Method A; try Method B |
| API returns 404 / 403 | Report the error; try Method B |
| Web fetch returns a login page or empty content | Fall back to Method C |
| Fetched content is very short (< 50 chars) | Warn; ask the user to confirm the source has real content |
| No tracker configured but the input is an item ID | Fetch it anyway if credentials allow, and record Tracker → provider in the config |