Instructions
When to activate
Activate when the user invokes /kp-source add, /kp-source status,
or /kp-source check-updates, or asks to add a document or source file
to the project.
Sub-commands
add <path-or-url>
If <path> is a directory:
- Run
<skill-dir>/scripts/ingest.py --list-dir <path> to discover files.
The script walks recursively (skipping hidden files and dirs), hashes each
file, and checks against existing sources for duplicates. All file types
are included - no extension filtering.
- Count files where
duplicate_of is null (new files to ingest).
- If that count exceeds the threshold (default 50, override with
--threshold N):Found N files. This will create N sources. Continue? [y/N]
If the user answers N (or gives no answer), abort with zero sources created.
Pass --yes to skip this prompt for scripted use (--threshold 0 also bypasses it).
- For each non-duplicate file, run the single-file ingestion steps below
(derive slug, create directory, copy, run ingest.py, handle
--sensitive).
- After all files are processed, print a summary:
Added: N
Duplicate: N (already ingested as <source-id>)
Failed: N
If <path> is a single file or a URL:
- Assign a
source-id slug:
- Derive a short kebab-case slug from the filename or URL
(e.g.
cairn-annual-report, privacy-policy-2024).
- For YouTube URLs, derive the slug from the video title if available, or
fall back to
youtube-<video-id> (e.g. youtube-dQw4w9WgXcQ).
- Keep it concise: 2-4 meaningful words, lowercase, hyphens only.
- Check uniqueness against existing directory names in
sources/.
- If the slug already exists, append
-2, -3, etc. until unique.
- Create
sources/<source-id>/.
- Fetch content into
sources/<source-id>/:
- YouTube URL (
youtube.com/watch?v= or youtu.be/): run
uv run <skill-dir>/scripts/fetch_youtube.py <url> sources/<source-id>/
(add --language <code> or --language <code1,code2> if the user
requested a specific subtitle language or priority order, e.g.
--language fr or --language en,fr).
This writes transcript.txt (timestamped plain text, one line per caption)
and attempts to write <title>.info.json via yt-dlp (best-effort).
The primary content file for extraction is transcript.txt.
Without --language, the first available transcript is used
(manually-created tracks are preferred over auto-generated ones), so
non-English videos work with no extra flags. If none of the requested
languages are available, it falls back to any available transcript.
The script's JSON stdout includes language and is_generated for the
transcript it selected - capture these to pass to ingest.py in the
next step.
- Other URL: download with
curl -L -o <filename>.
- Local file: copy into the directory.
- Run
<skill-dir>/scripts/ingest.py --source-id <source-id> --origin <path-or-url>
(for YouTube sources, add --language <code> --is-generated <true|false>
using the values captured from fetch_youtube.py's JSON stdout):
- Computes SHA-256 hash.
- Detects type (
pdf, csv, url, db-dump, markdown, image, other).
- Reads page count for PDFs.
- Writes
sources/<source-id>/.meta.json, including language /
is_generated when provided.
- If
--sensitive: append sources/<source-id>/ to .gitignore.
- Print the assigned
source-id and confirm.
.meta.json schema:
{
"source_id": "generated-source-slug",
"origin": "<path-or-url>",
"type": "pdf",
"ingested_at": "<ISO datetime>",
"hash": "sha256:<hex>",
"page_count": 42,
"sensitive": false,
"extraction": {"status": "pending"},
"stale": false,
"language": "fr",
"is_generated": false
}
language and is_generated are only present for sources where the content
language was explicitly selected (currently YouTube transcripts).
status
For each directory in sources/, read .meta.json and print a table:
| source-id |
type |
origin |
ingested-at |
extraction.status |
stale |
check-updates
For each source in sources/:
- Run
<skill-dir>/scripts/ingest.py --check-update --source-id <source-id>.
- The script recomputes the hash (or re-fetches for URLs) and compares to
the stored hash.
- If changed: set
stale: true in .meta.json and print a warning.
Flags
| Flag |
Effect |
--sensitive |
Adds sources/<source-id>/ to .gitignore. |
--threshold N |
Override the file-count confirmation threshold (default 50). Set to 0 to disable. |
--yes |
Skip the directory file-count confirmation prompt. |
--language <code> |
YouTube URLs only. Requests a subtitle language (or priority list, e.g. en,fr). Falls back to any available transcript if none match. |
Edge cases
- URL download fails: report the error, do not create a partial
sources/<source-id>/.
- YouTube transcript unavailable (private video, no captions at all, requested
--language not found and no other transcript exists): fetch_youtube.py exits non-zero; report the error and do not create a partial source directory.
- File not found: report clearly, suggest checking the path.
- Project not initialized (
.knowledge-project missing): prompt the user to run /kp-init first.
- Empty directory: report zero files found.
- Duplicate file in directory input: log it in the summary (
Duplicate: N) with the existing source-id; do not re-ingest.
1---2name: kp-source3description: Add sources to a knowledge project, check for updates, and track provenance. Use when the user runs /kp-source, wants to add a PDF, URL, CSV, database dump, or any document to sources/, needs to check the status of ingested sources, or wants to detect whether a previously ingested source has changed. "kps" is the short name for this project (Knowledge Project Skills) - also activate when the user says "kps ingestion" or "kps ingest".4---56## Instructions78### When to activate910Activate when the user invokes `/kp-source add`, `/kp-source status`,11or `/kp-source check-updates`, or asks to add a document or source file12to the project.1314---1516### Sub-commands1718#### `add <path-or-url>`1920**If `<path>` is a directory:**21221. Run `<skill-dir>/scripts/ingest.py --list-dir <path>` to discover files.23 The script walks recursively (skipping hidden files and dirs), hashes each24 file, and checks against existing sources for duplicates. All file types25 are included - no extension filtering.262. Count files where `duplicate_of` is `null` (new files to ingest).273. If that count exceeds the threshold (default 50, override with `--threshold N`):28 ```29 Found N files. This will create N sources. Continue? [y/N]30 ```31 If the user answers N (or gives no answer), abort with zero sources created.32 Pass `--yes` to skip this prompt for scripted use (`--threshold 0` also bypasses it).334. For each non-duplicate file, run the single-file ingestion steps below34 (derive slug, create directory, copy, run ingest.py, handle `--sensitive`).355. After all files are processed, print a summary:36 ```37 Added: N38 Duplicate: N (already ingested as <source-id>)39 Failed: N40 ```4142**If `<path>` is a single file or a URL:**43441. Assign a `source-id` slug:45 - Derive a short kebab-case slug from the filename or URL46 (e.g. `cairn-annual-report`, `privacy-policy-2024`).47 - For YouTube URLs, derive the slug from the video title if available, or48 fall back to `youtube-<video-id>` (e.g. `youtube-dQw4w9WgXcQ`).49 - Keep it concise: 2-4 meaningful words, lowercase, hyphens only.50 - Check uniqueness against existing directory names in `sources/`.51 - If the slug already exists, append `-2`, `-3`, etc. until unique.522. Create `sources/<source-id>/`.533. Fetch content into `sources/<source-id>/`:54 - **YouTube URL** (`youtube.com/watch?v=` or `youtu.be/`): run55 `uv run <skill-dir>/scripts/fetch_youtube.py <url> sources/<source-id>/`56 (add `--language <code>` or `--language <code1,code2>` if the user57 requested a specific subtitle language or priority order, e.g.58 `--language fr` or `--language en,fr`).59 This writes `transcript.txt` (timestamped plain text, one line per caption)60 and attempts to write `<title>.info.json` via yt-dlp (best-effort).61 The primary content file for extraction is `transcript.txt`.62 Without `--language`, the first available transcript is used63 (manually-created tracks are preferred over auto-generated ones), so64 non-English videos work with no extra flags. If none of the requested65 languages are available, it falls back to any available transcript.66 The script's JSON stdout includes `language` and `is_generated` for the67 transcript it selected - capture these to pass to `ingest.py` in the68 next step.69 - **Other URL**: download with `curl -L -o <filename>`.70 - **Local file**: copy into the directory.714. Run `<skill-dir>/scripts/ingest.py --source-id <source-id> --origin <path-or-url>`72 (for YouTube sources, add `--language <code> --is-generated <true|false>`73 using the values captured from `fetch_youtube.py`'s JSON stdout):74 - Computes SHA-256 hash.75 - Detects type (`pdf`, `csv`, `url`, `db-dump`, `markdown`, `image`, `other`).76 - Reads page count for PDFs.77 - Writes `sources/<source-id>/.meta.json`, including `language` /78 `is_generated` when provided.795. If `--sensitive`: append `sources/<source-id>/` to `.gitignore`.806. Print the assigned `source-id` and confirm.8182`.meta.json` schema:83```json84{85 "source_id": "generated-source-slug",86 "origin": "<path-or-url>",87 "type": "pdf",88 "ingested_at": "<ISO datetime>",89 "hash": "sha256:<hex>",90 "page_count": 42,91 "sensitive": false,92 "extraction": {"status": "pending"},93 "stale": false,94 "language": "fr",95 "is_generated": false96}97```98`language` and `is_generated` are only present for sources where the content99language was explicitly selected (currently YouTube transcripts).100#### `status`101102For each directory in `sources/`, read `.meta.json` and print a table:103104| source-id | type | origin | ingested-at | extraction.status | stale |105|---|---|---|---|---|---|106107#### `check-updates`108109For each source in `sources/`:1101. Run `<skill-dir>/scripts/ingest.py --check-update --source-id <source-id>`.1112. The script recomputes the hash (or re-fetches for URLs) and compares to112 the stored hash.1133. If changed: set `stale: true` in `.meta.json` and print a warning.114115---116117### Flags118119| Flag | Effect |120|---|---|121| `--sensitive` | Adds `sources/<source-id>/` to `.gitignore`. |122| `--threshold N` | Override the file-count confirmation threshold (default 50). Set to 0 to disable. |123| `--yes` | Skip the directory file-count confirmation prompt. |124| `--language <code>` | YouTube URLs only. Requests a subtitle language (or priority list, e.g. `en,fr`). Falls back to any available transcript if none match. |125126---127128### Edge cases129130- URL download fails: report the error, do not create a partial `sources/<source-id>/`.131- YouTube transcript unavailable (private video, no captions at all, requested `--language` not found and no other transcript exists): `fetch_youtube.py` exits non-zero; report the error and do not create a partial source directory.132- File not found: report clearly, suggest checking the path.133- Project not initialized (`.knowledge-project` missing): prompt the user to run `/kp-init` first.134- Empty directory: report zero files found.135- Duplicate file in directory input: log it in the summary (`Duplicate: N`) with the existing `source-id`; do not re-ingest.