Use when saving GitHub repos or issues, or bitbucket.org repos, issues or PRs, as structured luna notes.
Install
npx skillscat add yotamleo-org/himmel/luna-ingest Install via the SkillsCat registry.
luna-ingest — chain-following triage (LUNA-5 Wedge B, MVP; skill conversion LUNA-9)
You are running LUNA-5 Wedge B. Take a github OR bitbucket.org URL, follow its README references one hop (github sources only — see below), classify each, and produce a structured tech-ref note in the luna vault.
Scope: github + bitbucket.org URLs. Twitter (x.com/...) and article URLs raise an explicit "not yet" error and point at LUNA-5 v2.
- github.com: repo URLs run Phases 1–4; issue URLs (
/issues/<n>) take the issue branch (HIMMEL-239); PR (/pull/<n>) and discussion URLs remain out of scope. - bitbucket.org (HIMMEL-329): repo, PR (
/pull-requests/<n>), and issue (/issues/<n>) URLs take the Bitbucket branch below. They dispatch to the himmel bitbucket CLI (node scripts/bitbucket/dist/index.js …) instead ofgh.
Forge routing (HIMMEL-329): the URL host selects the path — parallel to how the dev-loop routes on the git origin. github.com (or bare <owner>/<repo>) → the github Phases below, unchanged. bitbucket.org → the Bitbucket branch. Everything in Phases 1–4 and the issue branch is the github path; the Bitbucket branch reuses the shared Phase 1.5 safety pre-filter and Phase 3/4 heuristics but swaps the fetch transport.
Invocation surfaces (LUNA-9):
- User prompt:
/luna-ingest <github-url> [--vault <path>] [--dest <category>] [--limit <N>] [--deep] [--research] [--dry-run]— handled by the thin wrapper at.claude/commands/luna-ingest.mdwhich delegates here. - Programmatic Skill-tool dispatch (HIMMEL-128 compliant — no headless claude):
Skill { skill: "obsidian-triage:luna-ingest", args: "<github-url> [<flags...>]" }from inside another runbook (e.g./harvest-clipsgithub-URL branch).
Both surfaces feed the same argument shape into the runbook below. Wherever this document references $ARGUMENTS, treat it as the literal arg string supplied via either invocation path.
Inputs
$ARGUMENTS is <url> [--vault <path>] [--dest <category>] [--limit <N>] [--dry-run].
Parse:
<url>— required. Accepted shapes:https://github.com/<owner>/<repo>(+/,/tree/<branch>,/blob/<branch>/<path>)https://github.com/<owner>/<repo>/issues/<n>— routes to the github issue branch (HIMMEL-239)- bare
<owner>/<repo>— treated as github https://bitbucket.org/<ws>/<repo>(+/,/src/<branch>/...) — routes to the Bitbucket branch (HIMMEL-329)https://bitbucket.org/<ws>/<repo>/pull-requests/<n>— Bitbucket PR branchhttps://bitbucket.org/<ws>/<repo>/issues/<n>— Bitbucket issue branch
--vault <path>— luna vault root. Default:$HOME/Documents/luna. Refuse with rc=2 if the dir does not exist OR is not an Obsidian vault (no.obsidian/dir).--dest <category>— vault destination category override. Default:30-Resources/Tech. Validate against the vault's PARA folders (00-Inbox,10-Projects/*,20-Areas/*,30-Resources/Tech,30-Resources/Concepts,30-Resources/Components). Anything else → rc=2.--limit <N>— max 1-hop refs to follow. Default250(LUNA-92: capture all refs by default — the prior[1,25]clamp left high-fanout awesome-lists partial and silently dropped sources). No upper clamp; reject< 1with rc=1.250is a generous safety ceiling for unattendedharvest-clipsdispatch (which passes no--limit), not a quality cap — the largest list we have hit is 188 refs. Pass--limit <N>to bound a noisy meta-list downward. The LUNA-6 rate-limit contract (api_failure verdict + rc=4 resume) is the backstop if a pathological list exceeds the gh-api hourly budget.--dry-run— print what would be written + verdicts, touch no files. rc=0.--allow-unsafe— opt-in flag to ingest a repo flagged by the LUNA-43 safety-keyword pre-filter (Phase 1.5). Without this flag, repos whose name/description/topics match red-flag terms exit rc=6 before fetching the README.--repo <ws/repo>is NOT a luna-ingest flag — it is the bitbucket CLI's own override, set internally by the Bitbucket branch from the parsed URL. Operators pass the bitbucket.org URL, not--repo.--deep— LUNA-57: after classification, run the component scan
(tools/component-scan.mjs) over the source repo (always) and over each
1-hop ref verdictedintegrateortake-parts. Inventories reusable
components (skills/commands/agents/tools/plugin manifests) into a## Reusable componentssection of the Tech note AND a cross-repo-deduped30-Resources/Components/library. Off by default (MVP keeps the light path).--components-dir <rel>— vault-relative Components library root.
Default30-Resources/Components. Only meaningful with--deep.--max-components <N>— per-repo component cap (tail-skip beyond). Default 60.--skill-scan— HIMMEL-5913: run SkillSpector Stage 1 (static,--no-llm, no egress) over each skill directory in the source repo (Phase 3.7) and store the JSON reports beside the note. Advisory only, never gates. Off by default.--research— LUNA-64: after classification, web-research the SOURCE repo
(maturity/adoption, notable alternatives, known limitations, community
sentiment, recency) using Claude's own WebSearch/WebFetch tools and inject a## Research enrichmentsection into the Tech note. Off by default.
Orthogonal to--deep— they enrich different axes (--deep= reusable
components inventoried from the code;--research= external context about
the repo) and may be combined (--deep --researchruns both). This is the
lightweight inline path: NO external Python (research_deep.py), NO
Perplexity/Grok keys, NO vault-wide propagation — those belong to the full/research-deepcommand, deferred to a later LUNA-64 follow-up. Source repo
only in MVP (1-hop refs are not researched). The harvest-clips github
dispatch MUST NOT pass--research(cost + scope safety).
Reject x.com/, twitter.com/, or any host that is neither github.com nor bitbucket.org with:
ERR luna-ingest: only github.com and bitbucket.org URLs supported. Twitter + article inputs ship in LUNA-5 v2.Issue branch — github issue URLs (HIMMEL-239)
Read on demand: When the URL is a github issue URL (/issues/<n>) — before doing anything else for it. Read <plugin>/references/luna-ingest/issue-branch.md (<plugin> is the plugin root, two directories above this skill's directory) in full and follow it exactly as if it were inlined here; do not work from memory of it.
Bitbucket branch — bitbucket.org URLs (HIMMEL-329)
Read on demand: When the URL host is bitbucket.org (repo, PR or issue) — before doing anything else for it. Read <plugin>/references/luna-ingest/bitbucket-branch.md (<plugin> is the plugin root, two directories above this skill's directory) in full and follow it exactly as if it were inlined here; do not work from memory of it.
Phase 1 — Source fetch (github URL)
Read on demand: Whenever the source is a github repo URL (every github run); before fetching. Read <plugin>/references/luna-ingest/phase-1-source-fetch.md (<plugin> is the plugin root, two directories above this skill's directory) in full and follow it exactly as if it were inlined here; do not work from memory of it.
Phase 1.5 — Safety pre-filter (LUNA-43)
Read on demand: After Phase 1, before Phase 2, on every github run; the matching algorithm and rc=6 behaviour live there. Read <plugin>/references/luna-ingest/phase-1-5-safety-prefilter.md (<plugin> is the plugin root, two directories above this skill's directory) in full and follow it exactly as if it were inlined here; do not work from memory of it.
Phase 1.6 — Advisory repo security scan (HIMMEL-5816)
Read on demand: After Phase 1.5, before Phase 2, on every github run; the command, finding kinds and the note fields live there. Read <plugin>/references/luna-ingest/phase-1-6-repo-scan.md (<plugin> is the plugin root, two directories above this skill's directory) in full and follow it exactly as if it were inlined here; do not work from memory of it.
Phase 2 — Reference extraction
Read on demand: When extracting refs from the README (Phase 2) and applying the --limit cap and the README-size warning. Read <plugin>/references/luna-ingest/phase-2-reference-extraction.md (<plugin> is the plugin root, two directories above this skill's directory) in full and follow it exactly as if it were inlined here; do not work from memory of it.
Phase 3 — Per-ref classification
Read on demand: When classifying each ref (Phase 3): the failure classes, the verdict heuristics and the confidence floor. Read <plugin>/references/luna-ingest/phase-3-classification.md (<plugin> is the plugin root, two directories above this skill's directory) in full and follow it exactly as if it were inlined here; do not work from memory of it.
Phase 3.5 — Deep component scan (LUNA-57, --deep only)
Read on demand: Only when --deep was passed; skip otherwise. Read <plugin>/references/luna-ingest/phase-3-5-deep-scan.md (<plugin> is the plugin root, two directories above this skill's directory) in full and follow it exactly as if it were inlined here; do not work from memory of it.
Phase 3.6 — Research enrichment (LUNA-64, --research only)
Read on demand: Only when --research was passed; skip otherwise. Read <plugin>/references/luna-ingest/phase-3-6-research.md (<plugin> is the plugin root, two directories above this skill's directory) in full and follow it exactly as if it were inlined here; do not work from memory of it.
Phase 3.7 — Advisory skill scan (HIMMEL-5913, --skill-scan only)
Read on demand: Only when --skill-scan was passed; skip otherwise. Read <plugin>/references/luna-ingest/phase-3-7-skill-scan.md (<plugin> is the plugin root, two directories above this skill's directory) in full and follow it exactly as if it were inlined here; do not work from memory of it.
Phase 4 — Synthesis
Compute a destination path under <vault>/<dest-category>/<repo-slug>.md where:
<repo-slug>= lowercase, hyphens, max 60 chars. e.g.kepano/obsidian-skills→kepano-obsidian-skills. Slugify: replace any non-[a-z0-9-]with-, collapse repeats, strip leading/trailing-.- If
<dest-category>was passed via--dest, use it verbatim. Else default30-Resources/Tech.
Path safety invariant: before any Write, canonicalise the resolved destination via realpath -m and verify it starts with the canonicalised vault root. Refuse with rc=2 if a .. traversal or symlink escape would land outside the vault. This guards against malformed --dest or weirdly-named refs.
If the target file already exists: refuse with rc=3 unless --force was passed (NOT supported in MVP — rc=3 with a hint to delete + retry).
Trust-tier auto-classification (LUNA-43)
Read on demand: Always, in Phase 4 before the Write step, to compute trust_tier and trust_tier_reason (the Bitbucket branch's B-repo-3 deltas and the issue branch's I-3 also apply these rules). Read <plugin>/references/luna-ingest/trust-tier.md (<plugin> is the plugin root, two directories above this skill's directory) in full and follow it exactly as if it were inlined here; do not work from memory of it.
Tags derivation (HIMMEL-247)
Read on demand: Always, in Phase 4 before building the frontmatter (every emitted note needs tags:). Read <plugin>/references/luna-ingest/tags-derivation.md (<plugin> is the plugin root, two directories above this skill's directory) in full and follow it exactly as if it were inlined here; do not work from memory of it.
---
type: tech-ingest
source: <URL>
source_type: github
ingested_at: <UTC ISO-8601>
ingest_spec_version: 1 # HIMMEL-3055: version of OUR OWN extraction rules, independent of upstream movement. Bump this when the extraction rules change so old notes are known-truncated/known-stale on our side even if upstream hasn't moved (e.g. notes written before the --limit/tail-skip work are truncated and nothing else says so).
ingest_scope: # HIMMEL-3055: machine-readable, not a line in the body
limit: <effective --limit used for this ingest>
tail_skipped: <count, 0 if none>
upstream_commit: <default-branch HEAD SHA at fetch time, or omit on fetch failure> # HIMMEL-3055
upstream_pushed_at: <repo pushed_at, ISO-8601> # HIMMEL-3055: parseable duplicate of the value also folded into trust_tier_reason prose below
readme_sha256: <sha256 of the decoded README bytes, or omit if README missing> # HIMMEL-3055: replaces README-byte-count as the change-detection proxy
# last_revalidated is OMITTED here — never default it to ingested_at (HIMMEL-3055). "Never
# checked" and "checked, unchanged" must not write the same value. It is written ONLY when a
# real revalidation sweep runs later (see "Revalidation invariant" below), paired with a
# revalidation_delta line.
ingest_command: /luna-ingest
verdict_summary:
integrate: <count>
take-parts: <count>
inspire: <count>
skip: <count>
api_failure: <count> # LUNA-6: refs we couldn't classify due to rate-limit / network / auth. NON-ZERO means partial run.
license: <SPDX or unknown>
stars: <N>
language: <primary>
topics:
- <topic>
tags: # HIMMEL-247: REQUIRED — never omit. Derivation rules in "Tags derivation" above.
- github
- <tag>
trust_tier: <tier> # LUNA-43: anthropic-official | known-author | community-active | community-thin | unknown-risk
trust_tier_reason: <text> # LUNA-43: 1-line justification (e.g. "owner=anthropics → anthropic-official"; "owner=kepano in allowlist → known-author"; "stargazers_count=12, pushed_at=2025-04-01 → community-thin"). Field name is `owner=` (the lowercased value extracted in trust-tier rule logic), matching the trust-tier rule wording — NOT `owner_org=`.
safety_flag: <term-or-blank> # LUNA-43: blank if Phase 1.5 found no red-flag terms; matched term otherwise. NON-BLANK forces trust_tier=unknown-risk via rule 0 short-circuit in trust-tier classification.
repo_scan: <clean|flagged|unavailable> # HIMMEL-5816: Phase 1.6 advisory scan result; never gates trust_tier.
skill_scan: <clean|flagged|unavailable> # HIMMEL-5913: set ONLY when --skill-scan ran (Phase 3.7); omit otherwise. Advisory; never gates trust_tier.
research_enriched: <true-or-omit> # LUNA-64: set true ONLY when --research ran (Phase 3.6) AND produced real findings. Omit the field entirely when --research was not passed OR enrichment degraded (dry-run short-circuit / web-failure placeholder) — a degraded note must not advertise itself as enriched to downstream readers (/synthesize-clips, revalidation tooling).
---
# <repo-name>
## Source
[<full_name>](<URL>) — <description from gh api>
- **Language:** <language> | **Stars:** <N> | **License:** <SPDX>
- **Last push:** <pushed_at>
- **Topics:** <topics joined by ", ">
## Summary
<3-5 bullet TL;DR derived from README + repo metadata>
## Referenced repos (1-hop)
<for each ref with confidence ≥0.6, sorted by verdict severity integrate > take-parts > inspire > skip>
### <ref-full-name> — <verdict>
- URL: https://github.com/<ref>
- Language: <lang> | Stars: <N> | License: <SPDX>
- **Rationale:** <1-3 sentences>
<if verdict == take-parts>
- **What to lift:** <file/path/area to inspect first>
</if>
## Security scan (advisory)
<Phase 1.6: the scan `summary` line, then one bullet per finding. Always present on a github run.>
## Skill scan (advisory)
<Only present when --skill-scan was passed (Phase 3.7): one line per scanned skill, as the phase reference specifies. If --skill-scan was NOT passed, omit this section entirely.>
## Research enrichment
<Only present when --research was passed (Phase 3.6). 4–8 bullets synthesizing
the WebSearch findings about the SOURCE repo — maturity/adoption, notable
alternatives, known limitations, community sentiment. Each external claim ends
with a recency marker + source URL inline, e.g.:
`- Actively maintained; v2.3 released as of Mar 2026 ([release notes](https://…)).`
On dry-run, render `_(dry-run — research not executed)_`. On web failure,
render `_(research enrichment requested but web search returned no usable
results)_`. If --research was NOT passed, omit this section entirely.>
## Reusable components
<Only present when --deep was passed. Compose from the source-repo scan
JSON. For each component in `components[]`, one bullet:
`- **<name>** (<type>) — <description>. _Reuse:_ <1-2 line how-to-rephrase
for himmel/luna — YOUR judgment from the description + type>.`
Then, for each integrate/take-parts ref that was scanned, one summary line:
`- _Ref <o>/<r>: N components inventoried → see [[30-Resources/Components/]]._`
If --deep was NOT passed, omit this section entirely.>
## Weak refs (low-confidence, appendix)
<for each ref with confidence <0.6 — same structure, condensed>
## Unclassified (transient — re-run to retry)
<for each ref with `verdict: api_failure` — list URL + verdict_reason. LUNA-6: surfaces rate-limited / network-failed refs distinctly from § Skip so operator knows what to retry. Empty section reads `_(none — all refs classified)_`.>
## Action items
<extracted from README headings like "TODO", "Roadmap", "Known issues" — convert each to `- [ ] <item>`. If none found: `_(none — repo README has no TODO/Roadmap section)_`>
## Open questions
<gaps the chain-follow could not resolve. Always include: "Verify integrate-verdict refs by reading their root README before taking a dep on any of them.">
## Audit log
- Refs scanned: <N>
- Refs above confidence floor (0.6): <K>
- Refs marked api_failure (LUNA-6): <F> (if >0: rc=4; re-run to retry classification)
- Tail-skipped: <T> (set if more refs existed than --limit)
- API calls: <N+2> (HIMMEL-3055: +1 over the prior count for the `upstream_commit` fetch)
- README size: <bytes> (LUNA-7: warn if >512 KB; --limit caps API-call count, not text size)
- Trust tier: <tier> (<reason>) (LUNA-43)
- Safety flag: <term-or-none> (LUNA-43: non-blank value reached rc=6 unless --allow-unsafe; document the term in this row regardless)Revalidation invariant (HIMMEL-3055)
Read on demand: Only when writing or reviewing a ## Re-validation section; a fresh ingest never does. Read <plugin>/references/luna-ingest/revalidation-invariant.md (<plugin> is the plugin root, two directories above this skill's directory) in full and follow it exactly as if it were inlined here; do not work from memory of it.
Semantic layer (HIMMEL-3055 — schema defined, NOT yet populated)
Read on demand: Only when touching the not-yet-populated semantic fields (concepts, solves, maps_to, value_tier, value_reason); this MVP never writes them. Read <plugin>/references/luna-ingest/semantic-layer.md (<plugin> is the plugin root, two directories above this skill's directory) in full and follow it exactly as if it were inlined here; do not work from memory of it.
Phase 5 — Daily-note backref (deferred to v2)
MVP does NOT touch the daily note. v2 picks up the obsidian-triage Phase 5 dedup-by-backreference contract.
Phase 6 — Promotion candidate (deferred to v2)
MVP does NOT write 00-Inbox/promote-<slug>.md. v2 adds this when any verdict ∈ {integrate, take-parts}.
Dry-run mode
When --dry-run is passed:
- Print "DRY luna-ingest: would write "
- Print the synthesized markdown body to stdout, prefixed with 4 spaces
- Print per-ref verdict summary table
- With
--research: skip the Phase 3.6 web queries (printDRY luna-ingest: would run research enrichment for <owner_repo>); the## Research enrichmentsection renders_(dry-run — research not executed)_. - rc=0, touch no files in the vault
Exit codes
Read on demand: Before choosing or interpreting an exit code (0 ok, 1 usage, 2 env, 3 target exists, 4 partial, 5 source fetch failed, 6 safety-blocked). Read <plugin>/references/luna-ingest/exit-codes.md (<plugin> is the plugin root, two directories above this skill's directory) in full and follow it exactly as if it were inlined here; do not work from memory of it.
Out of scope (v2 follow-ups)
Read on demand: Only when asked whether an input form or feature is supported. Read <plugin>/references/luna-ingest/out-of-scope.md (<plugin> is the plugin root, two directories above this skill's directory) in full and follow it exactly as if it were inlined here; do not work from memory of it.