$npx -y skills add Varnan-Tech/opendirectory --skill product-update-loggerTell the skill what your product shipped. It writes a polished dated entry to a living docs/changelog.md and produces a ready-to-use content package: tweet thread, LinkedIn post, email snippet, and one-liner.
| 1 | # product-update-logger |
| 2 | |
| 3 | Tell this skill what your product shipped. It writes a polished changelog entry to `docs/changelog.md` (a living log, newest entry first) and simultaneously produces a content package: tweet thread, LinkedIn post, email snippet, and one-liner. |
| 4 | |
| 5 | Input sources: free text from your message, git commits auto-read from the local repo, or GitHub PRs if you provide a repo. Any combination works. |
| 6 | |
| 7 | ## Reference Files |
| 8 | |
| 9 | Read these files before each run: |
| 10 | |
| 11 | ```bash |
| 12 | cat references/changelog-format.md |
| 13 | cat references/content-rules.md |
| 14 | cat references/noise-filter.md |
| 15 | ``` |
| 16 | |
| 17 | --- |
| 18 | |
| 19 | ## Step 1: Setup Check |
| 20 | |
| 21 | ```bash |
| 22 | echo "GITHUB_TOKEN: ${GITHUB_TOKEN:-not set -- GitHub PR fetching disabled}" |
| 23 | echo "Git: $(git rev-parse --is-inside-work-tree 2>/dev/null && echo 'repo detected' || echo 'not a git repo')" |
| 24 | echo "Changelog: $(ls docs/changelog.md 2>/dev/null && echo 'exists' || echo 'will be created')" |
| 25 | ``` |
| 26 | |
| 27 | Note whether git is available and whether a changelog already exists. This determines the version label format. |
| 28 | |
| 29 | --- |
| 30 | |
| 31 | ## Step 2: Parse Input |
| 32 | |
| 33 | Collect from the conversation: |
| 34 | |
| 35 | - `items` -- free text description of what shipped (pipe-separated if multiple). Optional if git is available. |
| 36 | - `since` -- how many days back to look. Default: 7. User may say "last 2 weeks" (14) or "since last release." |
| 37 | - `repo` -- GitHub "owner/repo" for PR fetching. Optional. |
| 38 | - `version_label` -- custom label like "v2.1.0" or "The Speed Update." Optional; default is date-based. |
| 39 | |
| 40 | **If the user said nothing about items AND there is no git repo:** Ask "What did you ship? List the features, fixes, or improvements -- one per line." |
| 41 | |
| 42 | **If git is available and user said nothing specific:** Proceed with git auto-read in Step 3. Show the user what was found and confirm before transforming. |
| 43 | |
| 44 | Write parsed input: |
| 45 | |
| 46 | ```bash |
| 47 | python3 << 'PYEOF' |
| 48 | import json, os, re |
| 49 | |
| 50 | inp = { |
| 51 | "items": "", # FILL: pipe-separated free text, or "" if none |
| 52 | "since": 7, # FILL: integer days |
| 53 | "repo": "", # FILL: "owner/repo" or "" |
| 54 | "version_label": "" # FILL: "" means auto (date-based), or custom string |
| 55 | } |
| 56 | |
| 57 | with open("/tmp/pul-input.json", "w") as f: |
| 58 | json.dump(inp, f, indent=2) |
| 59 | print(f"Since: {inp['since']} days") |
| 60 | print(f"Free text items: {inp['items'] or 'none (will use git/GitHub)'}") |
| 61 | print(f"GitHub repo: {inp['repo'] or 'none'}") |
| 62 | print(f"Version label: {inp['version_label'] or 'auto (date-based)'}") |
| 63 | PYEOF |
| 64 | ``` |
| 65 | |
| 66 | --- |
| 67 | |
| 68 | ## Step 3: Run the Gather Script |
| 69 | |
| 70 | ```bash |
| 71 | ls scripts/gather.py 2>/dev/null && echo "script found" || echo "ERROR: scripts/gather.py not found" |
| 72 | ``` |
| 73 | |
| 74 | ```bash |
| 75 | GITHUB_TOKEN="${GITHUB_TOKEN:-}" python3 scripts/gather.py \ |
| 76 | --since "$(python3 -c "import json; print(json.load(open('/tmp/pul-input.json'))['since'])")" \ |
| 77 | --repo "$(python3 -c "import json; print(json.load(open('/tmp/pul-input.json'))['repo'])")" \ |
| 78 | --items "$(python3 -c "import json; print(json.load(open('/tmp/pul-input.json'))['items'])")" \ |
| 79 | --output /tmp/pul-raw.json |
| 80 | ``` |
| 81 | |
| 82 | Verify output: |
| 83 | |
| 84 | ```bash |
| 85 | python3 -c " |
| 86 | import json |
| 87 | with open('/tmp/pul-raw.json') as f: |
| 88 | d = json.load(f) |
| 89 | print(f'Items found: {d[\"total_items\"]}') |
| 90 | print(f'Noise filtered: {d[\"noise_filtered\"]}') |
| 91 | print(f'Git available: {d[\"git_available\"]}') |
| 92 | print(f'GitHub available: {d[\"github_available\"]}') |
| 93 | print(f'Sources: git={sum(1 for i in d[\"items\"] if i[\"source\"]==\"git_commit\")}, ' |
| 94 | f'prs={sum(1 for i in d[\"items\"] if i[\"source\"]==\"github_pr\")}, ' |
| 95 | f'text={sum(1 for i in d[\"items\"] if i[\"source\"]==\"free_text\")}') |
| 96 | print() |
| 97 | print('Items:') |
| 98 | for item in d['items']: |
| 99 | print(f' [{item[\"source\"]}] {item[\"subject\"]}') |
| 100 | " |
| 101 | ``` |
| 102 | |
| 103 | **If total_items == 0:** Stop. Tell the user: "No shipped items found. Either describe what you shipped, point me to a git repo with recent commits, or add a GitHub repo with `repo: owner/repo` and a GITHUB_TOKEN." |
| 104 | |
| 105 | **Show the item list to the user and ask: "These are the items I found. Anything to add or remove before I write the changelog?"** |
| 106 | |
| 107 | Wait for confirmation or edits. If the user says "looks good", "proceed", or makes no changes, continue. If the user adds or removes items, update `/tmp/pul-raw.json` accordingly before Step 4. |
| 108 | |
| 109 | --- |
| 110 | |
| 111 | ## Step 4: Generate Changelog Entry |
| 112 | |
| 113 | Print items for context: |
| 114 | |
| 115 | ```bash |
| 116 | python3 -c " |
| 117 | import json |
| 118 | with open('/tmp/pul-raw.json') as f: |
| 119 | d = json.load(f) |
| 120 | print(json.dumps(d['items'], indent=2)) |
| 121 | print() |
| 122 | print(f'Existing changelog format: {d[\"existing_changelog\"][\"format\"]}') |
| 123 | print(f'Last label: {d[\"existing_changelog\"][\"last_label\"]}') |
| 124 | print(f'Today: {d[\"date\"]}') |
| 125 | " |
| 126 | ``` |
| 127 | |
| 128 | **AI instructions:** Transform each raw item from technical language to user-facing benefit language. Follow `references/changelog-format.md` |