Conversation
Introduces weekly changelog versioned by Monday date (YYYY.MM.DD) with a rolling current entry that accumulates bullets across the week. Backfills nine historical weeks covering Feb-Apr 2026. - .claude/skills/changelog: skill definition with backfill/append/rollup modes - scripts/changelog-append.mjs: parses feat/fix/perf subjects into current - scripts/changelog-rollup.mjs: promotes current to a tagged weekly release - .github/workflows/changelog-append.yml: runs append on every push to main - package.json: version bumped to latest released week (2026.04.06)
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
📝 WalkthroughWalkthroughAdds a weekly CalVer-based changelog system: JSON types/data, append and rollup CLI scripts, a GitHub Actions append workflow, VS Code launch config, package script entries, and a "What's New" UI in the editor with seen-state persistence. Changes
Sequence Diagram(s)sequenceDiagram
participant GH as GitHub Actions
participant Repo as Repository
participant Node as Node script (append)
participant Git as Git CLI
GH->>Repo: push to main (trigger)
GH->>Repo: checkout full history
GH->>Git: compute commit range
GH->>Node: run scripts/changelog-append.mjs with range
Node->>Repo: read/parse commits, update src/data/changelog.json
Node->>Repo: write changelog.json (if changed)
GH->>Git: commit file and push (retry up to 5x with fetch/rebase on failure)
sequenceDiagram
participant Client as Editor UI
participant Data as `src/data/changelog.json`
participant Storage as localStorage
Client->>Data: import/read changelog
Client->>Client: render WhatsNewDialog (list + details)
Client->>Storage: get `freecut:whatsNewLastSeen`
alt user opens dialog
Client->>Storage: markChangelogSeen() -> set latest id
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| git config user.name "github-actions[bot]" | ||
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | ||
| git add src/data/changelog.json | ||
| git commit -m "chore(changelog): append bullets from $(git rev-parse --short HEAD^) [skip ci]" |
There was a problem hiding this comment.
Bug: Commit message references wrong SHA
The command $(git rev-parse --short HEAD^) resolves to the parent of the commit that triggered the workflow, not the actual commit(s) being processed.
When this workflow runs:
- A commit is pushed to
main(this becomes HEAD) - The script processes commits in the computed range
- The commit message incorrectly references
HEAD^(the grandparent commit)
This means every automated changelog commit will cite the wrong source commit in its message.
Fix:
git commit -m "chore(changelog): append bullets from $(git rev-parse --short HEAD) [skip ci]"Or if referencing the range start:
git commit -m "chore(changelog): append bullets from ${{ github.event.before }}..$(git rev-parse --short HEAD) [skip ci]"| git commit -m "chore(changelog): append bullets from $(git rev-parse --short HEAD^) [skip ci]" | |
| git commit -m "chore(changelog): append bullets from $(git rev-parse --short HEAD) [skip ci]" |
Spotted by Graphite
Is this helpful? React 👍 or 👎 to let us know.
Greptile SummaryThis PR introduces a weekly CalVer changelog pipeline ( Confidence Score: 5/5Safe to merge — all findings are P2 style/best-practice suggestions with no correctness impact. The changelog pipeline logic is sound: dedup is correct, rollup math is correct, the lastMondayIso formula produces the right date, and the unseen-dot identifier handles the empty-string edge case. No P0/P1 bugs were found. .github/workflows/changelog-append.yml line 47 (unquoted expression) and scripts/changelog-rollup.mjs line 173 (execSync shell injection) are the only spots worth a second look. Important Files Changed
Sequence DiagramsequenceDiagram
participant Dev as Developer
participant GH as GitHub (push to main)
participant CI as changelog-append.yml
participant Script as changelog-append.mjs
participant JSON as changelog.json
participant UI as WhatsNewDialog
Dev->>GH: git push main
GH->>CI: trigger (if not chore(changelog))
CI->>CI: compute range (github.event.before..HEAD)
CI->>Script: node changelog-append.mjs range
Script->>Script: git log --no-merges (feat/fix/perf only)
Script->>JSON: dedup + append bullets to current
CI->>GH: git push chore(changelog) commit [skip ci]
Note over Dev,JSON: Weekly rollup (manual)
Dev->>Script: npm run changelog:rollup -- --date YYYY-MM-DD
Script->>JSON: promote current to releases[], current = null
Script->>Dev: CHANGELOG.md + package.json updated, git tag created
Note over UI,JSON: Runtime (bundled at build time)
UI->>JSON: import changelog.json (static)
UI->>UI: getLatestIdentifier() returns current date or version
UI->>UI: hasUnseenChangelog() via localStorage
UI-->>UI: show unseen dot if identifier changed
Prompt To Fix All With AIThis is a comment left during a code review.
Path: .github/workflows/changelog-append.yml
Line: 47
Comment:
**Quote the range expression**
`${{ steps.range.outputs.range }}` is interpolated by the Actions runner before the shell sees it. While the value is always `<hex>..HEAD` today, leaving it unquoted means any future whitespace or glob char in the output would cause word-splitting or unexpected expansion. Double-quoting is the safe default.
```suggestion
run: node scripts/changelog-append.mjs "${{ steps.range.outputs.range }}"
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: scripts/changelog-rollup.mjs
Line: 173
Comment:
**Backticks and `$()` in tagMessage not escaped**
`run()` calls `execSync` with the string passed directly to a shell. Only `"` is escaped; if `highlights[0]` contains a backtick or `$(...)`, the shell will interpret it as a subshell. Since the message comes from `changelog.json` (curated by an operator), the practical risk is low, but it's safer to use `execFileSync` with an args array to bypass the shell entirely.
```js
// Safer: use execFileSync to bypass shell entirely
import { execFileSync } from 'node:child_process';
execFileSync('git', ['tag', '-a', tagName, sha, '-m', tagMessage], { stdio: 'inherit' });
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/features/editor/components/whats-new-dialog.tsx
Line: 51
Comment:
**Hardcoded version string for "Initial release" label**
`entry.version.startsWith('2026.02.02')` couples the UI to a specific version string. If the initial date is ever corrected or a new "first" entry is introduced, this silently stops working. Consider adding a `subtitle?: string` field to `ChangelogEntry` so the data drives the label rather than a code-level string match.
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "fix(changelog): use github.event.before ..." | Re-trigger Greptile |
| node-version: 22 | ||
|
|
||
| - name: Run append script | ||
| run: node scripts/changelog-append.mjs ${{ steps.range.outputs.range }} |
There was a problem hiding this comment.
${{ steps.range.outputs.range }} is interpolated by the Actions runner before the shell sees it. While the value is always <hex>..HEAD today, leaving it unquoted means any future whitespace or glob char in the output would cause word-splitting or unexpected expansion. Double-quoting is the safe default.
| run: node scripts/changelog-append.mjs ${{ steps.range.outputs.range }} | |
| run: node scripts/changelog-append.mjs "${{ steps.range.outputs.range }}" |
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/changelog-append.yml
Line: 47
Comment:
**Quote the range expression**
`${{ steps.range.outputs.range }}` is interpolated by the Actions runner before the shell sees it. While the value is always `<hex>..HEAD` today, leaving it unquoted means any future whitespace or glob char in the output would cause word-splitting or unexpected expansion. Double-quoting is the safe default.
```suggestion
run: node scripts/changelog-append.mjs "${{ steps.range.outputs.range }}"
```
How can I resolve this? If you propose a fix, please make it concise.| const tagMessage = `${tagName} — ${highlightLine}`; | ||
|
|
||
| console.log(`\nTagging ${tagName} at ${sha.slice(0, 8)}`); | ||
| run(`git tag -a ${tagName} ${sha} -m "${tagMessage.replaceAll('"', '\\"')}"`); |
There was a problem hiding this comment.
Backticks and
$() in tagMessage not escaped
run() calls execSync with the string passed directly to a shell. Only " is escaped; if highlights[0] contains a backtick or $(...), the shell will interpret it as a subshell. Since the message comes from changelog.json (curated by an operator), the practical risk is low, but it's safer to use execFileSync with an args array to bypass the shell entirely.
// Safer: use execFileSync to bypass shell entirely
import { execFileSync } from 'node:child_process';
execFileSync('git', ['tag', '-a', tagName, sha, '-m', tagMessage], { stdio: 'inherit' });Prompt To Fix With AI
This is a comment left during a code review.
Path: scripts/changelog-rollup.mjs
Line: 173
Comment:
**Backticks and `$()` in tagMessage not escaped**
`run()` calls `execSync` with the string passed directly to a shell. Only `"` is escaped; if `highlights[0]` contains a backtick or `$(...)`, the shell will interpret it as a subshell. Since the message comes from `changelog.json` (curated by an operator), the practical risk is low, but it's safer to use `execFileSync` with an args array to bypass the shell entirely.
```js
// Safer: use execFileSync to bypass shell entirely
import { execFileSync } from 'node:child_process';
execFileSync('git', ['tag', '-a', tagName, sha, '-m', tagMessage], { stdio: 'inherit' });
```
How can I resolve this? If you propose a fix, please make it concise.| function formatEntrySubtitle(entry: ChangelogEntry): string { | ||
| if (entry.version === 'current') return `As of ${formatSingleDate(entry.date)}`; | ||
| if (entry.version.startsWith('2026.02.02')) return 'Initial release'; | ||
| return `Week of ${formatWeekRange(entry.date)}`; |
There was a problem hiding this comment.
Hardcoded version string for "Initial release" label
entry.version.startsWith('2026.02.02') couples the UI to a specific version string. If the initial date is ever corrected or a new "first" entry is introduced, this silently stops working. Consider adding a subtitle?: string field to ChangelogEntry so the data drives the label rather than a code-level string match.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/features/editor/components/whats-new-dialog.tsx
Line: 51
Comment:
**Hardcoded version string for "Initial release" label**
`entry.version.startsWith('2026.02.02')` couples the UI to a specific version string. If the initial date is ever corrected or a new "first" entry is introduced, this silently stops working. Consider adding a `subtitle?: string` field to `ChangelogEntry` so the data drives the label rather than a code-level string match.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (3)
.github/workflows/changelog-append.yml (1)
14-16: Guard also tolerates substring matches in unrelated commit subjects.
contains(..., 'chore(changelog)')will match any commit whose subject happens to include that substring (e.g. a revert:Revert "chore(changelog): ..."). That's almost certainly fine in practice, but the stricter check is a prefix test. Non-blocking.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/changelog-append.yml around lines 14 - 16, Replace the lenient substring check contains(github.event.head_commit.message, 'chore(changelog)') with a prefix test using startsWith(github.event.head_commit.message, 'chore(changelog)') in the workflow if condition so only commits whose subject begins with "chore(changelog)" are matched; update the condition line that currently reads contains(..., 'chore(changelog)') to use startsWith(...) instead.src/data/changelog.json (1)
91-866: Consider normalizingscopevalues across releases.Scopes are free-form strings and use both hyphen-compounds (
media-library) and singulars (compositions,effects,analysis,player,keyframes,transitions,settings,export) — plus the earliest2026.02.02entry has no scope at all. Sincechangelog-append.mjsemits raw git trailers, an expected set / normalization map would keep the "What's New" UI'sscopechips consistent over time. Non-blocking.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/data/changelog.json` around lines 91 - 866, The changelog uses inconsistent free-form "scope" values inside the "releases" array (many entries in src/data/changelog.json) and some entries lack a scope; update all "scope" keys to a canonical set (choose consistent hyphenation/singular/plural forms) and fill missing scopes (e.g., the 2026.02.02 items) so the UI chips remain stable, and add a normalization/validation step in changelog-append.mjs that maps incoming git-trailer scopes to the canonical values (e.g., a small normalization map and fallback logging) before appending to the "releases" array.src/features/editor/components/toolbar.tsx (1)
66-73: Simplify: collapse the two effects into a single mount-time read.The second effect clears
hasUnseenWhatsNewwhenever the dialog opens, butWhatsNewDialogalready callsmarkChangelogSeen()on open. Since the indicator is only recomputed on mount anyway, you could instead sethasUnseenWhatsNewtofalsein the open-click handler and drop the second effect. Minor readability nit.Proposed diff
- useEffect(() => { - setHasUnseenWhatsNew(hasUnseenChangelog()); - }, []); - - useEffect(() => { - if (!showWhatsNewDialog) return; - setHasUnseenWhatsNew(false); - }, [showWhatsNewDialog]); + useEffect(() => { + setHasUnseenWhatsNew(hasUnseenChangelog()); + }, []);- onClick={() => setShowWhatsNewDialog(true)} + onClick={() => { + setShowWhatsNewDialog(true); + setHasUnseenWhatsNew(false); + }}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/editor/components/toolbar.tsx` around lines 66 - 73, Collapse the two useEffect hooks by removing the effect that watches showWhatsNewDialog and instead clear the local state when opening the dialog: keep the mount-time effect that calls setHasUnseenWhatsNew(hasUnseenChangelog()), delete the second useEffect tied to [showWhatsNewDialog], and modify the handler that sets showWhatsNewDialog (the open-click handler) to also call setHasUnseenWhatsNew(false) (and continue to rely on WhatsNewDialog.markChangelogSeen() on actual open); update references to useEffect, setHasUnseenWhatsNew, hasUnseenChangelog, showWhatsNewDialog, WhatsNewDialog, and markChangelogSeen accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/changelog-append.yml:
- Around line 49-59: The commit-and-push step may fail if another push landed
after checkout; update the block that runs the push (the commands around git
add, git commit -m and git push) to perform a small rebase-and-retry loop:
before git push run git pull --rebase origin main (or the target branch) and if
that fails, retry the sequence a few times (re-attempt git add/commit if needed)
or abort after N attempts with a non-zero exit; ensure the commit message (used
in git commit -m) is preserved across retries and that the retry logic only runs
when there are changes to push.
- Around line 33-39: The current workflow writes range=$BEFORE..HEAD without
verifying $BEFORE exists, which breaks on force-push; update the step that sets
BEFORE to run git cat-file -e "$BEFORE" (or equivalent) and if that check fails
set the output to range=HEAD~1..HEAD, otherwise keep range=$BEFORE..HEAD so
changelog-append.mjs never receives a ref that git log cannot resolve; reference
the BEFORE variable and the range output written to GITHUB_OUTPUT when making
this change.
- Around line 1-10: Add a top-level concurrency key to the workflow to serialize
concurrent runs and prevent racing git pushes: insert a concurrency block (e.g.
concurrency: group: "changelog-append-${{ github.ref }}" cancel-in-progress:
false) at the top-level of the YAML (alongside name/on/permissions) so runs that
target the same ref are executed serially.
In `@scripts/changelog-rollup.mjs`:
- Around line 50-60: The comment in lastMondayIso misstates the Sunday behavior:
the current formula for daysBack = ((dayOfWeek + 6) % 7) + 7 returns 13 for
Sunday (two Mondays back) not 6; update the comment to accurately describe that
on Sunday the function returns the Monday 13 days ago (because we want the
Monday of the previous calendar week that already closed), or if you prefer the
alternative behavior change the formula that computes daysBack
accordingly—locate lastMondayIso and the daysBack calculation and either correct
the explanatory comment to match the implemented logic or replace the formula to
yield 6 for Sunday.
- Around line 168-173: The tag message is still passed through the shell via
string interpolation (the code using run(`git tag -a ${tagName} ${sha} -m
"...")`) and the replaceAll escape is incomplete; change the call to invoke git
with an argv array (e.g., use child_process.execFileSync or update run to accept
an args-array) and pass tagName, sha and tagMessage as separate arguments so the
shell is bypassed; keep the same tagMessage computation (shaArg,
latestMainMergeSha(), highlightLine from releaseEntry.highlights or
formatWeekRange) but remove the replaceAll escaping and call git with a safe
argv array instead.
---
Nitpick comments:
In @.github/workflows/changelog-append.yml:
- Around line 14-16: Replace the lenient substring check
contains(github.event.head_commit.message, 'chore(changelog)') with a prefix
test using startsWith(github.event.head_commit.message, 'chore(changelog)') in
the workflow if condition so only commits whose subject begins with
"chore(changelog)" are matched; update the condition line that currently reads
contains(..., 'chore(changelog)') to use startsWith(...) instead.
In `@src/data/changelog.json`:
- Around line 91-866: The changelog uses inconsistent free-form "scope" values
inside the "releases" array (many entries in src/data/changelog.json) and some
entries lack a scope; update all "scope" keys to a canonical set (choose
consistent hyphenation/singular/plural forms) and fill missing scopes (e.g., the
2026.02.02 items) so the UI chips remain stable, and add a
normalization/validation step in changelog-append.mjs that maps incoming
git-trailer scopes to the canonical values (e.g., a small normalization map and
fallback logging) before appending to the "releases" array.
In `@src/features/editor/components/toolbar.tsx`:
- Around line 66-73: Collapse the two useEffect hooks by removing the effect
that watches showWhatsNewDialog and instead clear the local state when opening
the dialog: keep the mount-time effect that calls
setHasUnseenWhatsNew(hasUnseenChangelog()), delete the second useEffect tied to
[showWhatsNewDialog], and modify the handler that sets showWhatsNewDialog (the
open-click handler) to also call setHasUnseenWhatsNew(false) (and continue to
rely on WhatsNewDialog.markChangelogSeen() on actual open); update references to
useEffect, setHasUnseenWhatsNew, hasUnseenChangelog, showWhatsNewDialog,
WhatsNewDialog, and markChangelogSeen accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 2bcb1941-0083-4c4a-a317-18e4b566f641
📒 Files selected for processing (13)
.claude/launch.json.claude/skills/changelog/SKILL.md.github/workflows/changelog-append.yml.gitignoreCHANGELOG.mdpackage.jsonscripts/changelog-append.mjsscripts/changelog-rollup.mjssrc/data/changelog-types.tssrc/data/changelog.jsonsrc/features/editor/components/toolbar.tsxsrc/features/editor/components/whats-new-dialog.tsxsrc/features/editor/components/whats-new-seen.ts
Workflow: - Add concurrency block to serialize runs on the same ref - Verify github.event.before exists before using (git cat-file -e), fall back to HEAD~1..HEAD when missing or not in history (force-push) - Quote the range expression in the node invocation - Use startsWith instead of contains for the chore(changelog) guard - Replace single git push with a rebase-and-retry loop (5 attempts) Rollup script: - Clarify comment in lastMondayIso to accurately describe Sunday behavior (13 days back = previous completed week's Monday) - Use execFileSync with argv array for git tag to bypass shell escaping - Remove now-unused run() helper Dialog: - Add optional subtitle field to ChangelogEntry so data drives the label - Replace hardcoded 2026.02.02 match with subtitle lookup - Backfill "Initial release" subtitle on the 2026.02.02 entry Toolbar: - Collapse two useEffects into one mount-time effect; clear the unseen-dot state inline in the open handler instead of via effect
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
scripts/changelog-rollup.mjs (1)
123-129:latestMainMergeShahardcodesmain— fails on detached HEAD or forked default branches.
git log --first-parent main -1will error out if the local clone doesn't have amainref (e.g., CI checkout with a different default branch, or a worktree tracking only the current branch). Consider falling back toHEADor the upstream of the current branch, and wrapping in a clearer error than the raw git exit.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/changelog-rollup.mjs` around lines 123 - 129, latestMainMergeSha currently hardcodes the branch name "main" which will fail in detached HEADs or repos whose default branch is different; modify latestMainMergeSha to call git in a guarded way: detect a safe ref first (try to resolve the current branch upstream via "git rev-parse --abbrev-ref --symbolic-full-name @{u}" or fallback to the local branch name via "git rev-parse --abbrev-ref HEAD"), then use that resolved ref instead of the literal "main" when running the existing execSync('git log --first-parent ... --pretty=format:%H'), and wrap all execSync calls in try/catch to throw a clearer, contextual error if resolution or the git log command fails; reference the latestMainMergeSha function and its use of execSync in your changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/changelog-rollup.mjs`:
- Around line 38-47: The CLI parsing loop currently lets `--date` fall through
when the next token is missing or is another flag and silently ignores unknown
`--flag`s; update the parsing in the for-loop that inspects `args`,
`dateOverride`, and `sha` so that when encountering `--date` you check that
`args[i + 1]` exists and does NOT start with '--' (and optionally is a valid
date string) before assigning `dateOverride`, otherwise emit an explicit
error/usage message and exit instead of falling back to lastMondayIso(); also
treat any unknown token that starts with '--' as an error (print message and
exit) rather than silently ignoring it to avoid surprising behavior.
- Around line 162-164: The value assigned to pkg.version should have leading
zeros removed from numeric identifiers so it is valid SemVer (keep the original
zero-padded version for tags/changelog); change the assignment that sets
pkg.version = version to instead derive a package-safe version by splitting the
version on '.', for each identifier that is purely numeric convert to a Number
and back to string to strip any leading zeros (leave non-numeric identifiers
unchanged), then join with '.' and set pkg.version to that cleaned string before
calling writeJson; refer to the variables/version string named "version", the
package object "pkg", and the writeJson call to locate where to apply this
transformation.
---
Nitpick comments:
In `@scripts/changelog-rollup.mjs`:
- Around line 123-129: latestMainMergeSha currently hardcodes the branch name
"main" which will fail in detached HEADs or repos whose default branch is
different; modify latestMainMergeSha to call git in a guarded way: detect a safe
ref first (try to resolve the current branch upstream via "git rev-parse
--abbrev-ref --symbolic-full-name @{u}" or fallback to the local branch name via
"git rev-parse --abbrev-ref HEAD"), then use that resolved ref instead of the
literal "main" when running the existing execSync('git log --first-parent ...
--pretty=format:%H'), and wrap all execSync calls in try/catch to throw a
clearer, contextual error if resolution or the git log command fails; reference
the latestMainMergeSha function and its use of execSync in your changes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: db04de4b-ead0-41e5-8157-c02e78ae8abf
📒 Files selected for processing (6)
.github/workflows/changelog-append.ymlscripts/changelog-rollup.mjssrc/data/changelog-types.tssrc/data/changelog.jsonsrc/features/editor/components/toolbar.tsxsrc/features/editor/components/whats-new-dialog.tsx
✅ Files skipped from review due to trivial changes (3)
- src/data/changelog.json
- .github/workflows/changelog-append.yml
- src/data/changelog-types.ts
| for (let i = 0; i < args.length; i += 1) { | ||
| const a = args[i]; | ||
| if (a === '--date') { | ||
| dateOverride = args[i + 1]; | ||
| i += 1; | ||
| } else if (!a.startsWith('--')) { | ||
| sha = a; | ||
| } | ||
| } | ||
| return { sha, dateOverride }; |
There was a problem hiding this comment.
--date with missing value silently falls through to default.
If the user runs node scripts/changelog-rollup.mjs --date (no value) or --date --other, args[i + 1] is undefined / another flag and lastMondayIso() is used instead, producing a surprising version. Also, an unknown --flag is silently ignored.
🔧 Proposed fix
if (a === '--date') {
- dateOverride = args[i + 1];
- i += 1;
+ const val = args[i + 1];
+ if (!val || val.startsWith('--') || !/^\d{4}-\d{2}-\d{2}$/.test(val)) {
+ console.error('--date requires a YYYY-MM-DD argument');
+ process.exit(1);
+ }
+ dateOverride = val;
+ i += 1;
} else if (!a.startsWith('--')) {
sha = a;
+ } else {
+ console.error(`Unknown flag: ${a}`);
+ process.exit(1);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for (let i = 0; i < args.length; i += 1) { | |
| const a = args[i]; | |
| if (a === '--date') { | |
| dateOverride = args[i + 1]; | |
| i += 1; | |
| } else if (!a.startsWith('--')) { | |
| sha = a; | |
| } | |
| } | |
| return { sha, dateOverride }; | |
| for (let i = 0; i < args.length; i += 1) { | |
| const a = args[i]; | |
| if (a === '--date') { | |
| const val = args[i + 1]; | |
| if (!val || val.startsWith('--') || !/^\d{4}-\d{2}-\d{2}$/.test(val)) { | |
| console.error('--date requires a YYYY-MM-DD argument'); | |
| process.exit(1); | |
| } | |
| dateOverride = val; | |
| i += 1; | |
| } else if (!a.startsWith('--')) { | |
| sha = a; | |
| } else { | |
| console.error(`Unknown flag: ${a}`); | |
| process.exit(1); | |
| } | |
| } | |
| return { sha, dateOverride }; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/changelog-rollup.mjs` around lines 38 - 47, The CLI parsing loop
currently lets `--date` fall through when the next token is missing or is
another flag and silently ignores unknown `--flag`s; update the parsing in the
for-loop that inspects `args`, `dateOverride`, and `sha` so that when
encountering `--date` you check that `args[i + 1]` exists and does NOT start
with '--' (and optionally is a valid date string) before assigning
`dateOverride`, otherwise emit an explicit error/usage message and exit instead
of falling back to lastMondayIso(); also treat any unknown token that starts
with '--' as an error (print message and exit) rather than silently ignoring it
to avoid surprising behavior.
| const pkg = loadJson(PACKAGE_JSON); | ||
| pkg.version = version; | ||
| writeJson(PACKAGE_JSON, pkg); |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Does npm semver allow leading zeros in numeric identifiers like 2026.04.06?
💡 Result:
No, npm semver does not allow leading zeros in numeric identifiers like 2026.04.06. The SemVer specification (v2.0.0), which npm's semver implementation follows strictly by default, states that numeric identifiers in the major, minor, and patch versions MUST NOT contain leading zeroes. Section 2 of the spec explicitly says: "A normal version number MUST take the form X.Y.Z where X, Y, and Z are non-negative integers, and MUST NOT contain leading zeroes." In 2026.04.06, the minor (04) and patch (06) identifiers have leading zeros, making it invalid. npm/node-semver's valid function returns null for such versions in strict mode (default), as confirmed in multiple GitHub issues where examples like '1.04', '0.67.09', and '2018.06.05' fail validation or coercion without loose: true option. While semver.coerce with {loose: true} might strip leading zeros (e.g., '0000.004.02' -> '0.4.2'), this is not standard validation and the output would be '2026.4.6', not preserving the original. For strict SemVer compliance used by npm for package versions, leading zeros are prohibited to ensure proper numeric sorting and comparison.
Citations:
- 1: https://github.com/semver/semver/blob/master/semver.md
- 2: https://semver.org//
- 3: Numeric-only part with leading zero(s) in prerelease section causes "Invalid Version" npm/node-semver#237
- 4: Coerce returns null if have a 0 appended to major, minor or patch npm/node-semver#232
- 5: Add leading zeros remove npm/node-semver#233
🏁 Script executed:
cat -n scripts/changelog-rollup.mjs | head -180 | tail -40Repository: walterlow/freecut
Length of output: 1568
🏁 Script executed:
cat -n scripts/changelog-rollup.mjs | head -150Repository: walterlow/freecut
Length of output: 6123
Strip leading zeros from the version written to package.json.
mondayIso.replaceAll('-', '.') produces 2026.04.06, which violates the SemVer 2.0.0 specification: numeric identifiers MUST NOT include leading zeroes. Writing this to package.json#version will break npm version, npm publish, lockfile resolution, and fail semver.valid().
Keep the zero-padded form for the git tag and changelog display, but strip leading zeros for the package version:
Proposed fix
const version = mondayIso.replaceAll('-', '.');
const tagName = `v${version}`;
+// SemVer forbids leading zeros in numeric identifiers; strip them for package.json.
+const pkgVersion = version
+ .split('.')
+ .map((p) => String(Number(p)))
+ .join('.');
const pkg = loadJson(PACKAGE_JSON);
-pkg.version = version;
+pkg.version = pkgVersion;
writeJson(PACKAGE_JSON, pkg);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/changelog-rollup.mjs` around lines 162 - 164, The value assigned to
pkg.version should have leading zeros removed from numeric identifiers so it is
valid SemVer (keep the original zero-padded version for tags/changelog); change
the assignment that sets pkg.version = version to instead derive a package-safe
version by splitting the version on '.', for each identifier that is purely
numeric convert to a Number and back to string to strip any leading zeros (leave
non-numeric identifiers unchanged), then join with '.' and set pkg.version to
that cleaned string before calling writeJson; refer to the variables/version
string named "version", the package object "pkg", and the writeJson call to
locate where to apply this transformation.
Summary
YYYY.MM.DD= Monday of the week) with a rollingcurrententry that accumulates bullets across the weekmainvia GitHub Action; weekly rollup vianpm run changelog:rollupArchitecture
src/data/changelog.json— typed source of truth ({ current, releases[] }); imported by UICHANGELOG.md— human-facing mirror.claude/skills/changelog/SKILL.md— skill for curation/rollup work (backfill/append/rollup modes)scripts/changelog-append.mjs— parsesfeat/fix/perfsubjects, appends deduped bullets tocurrentscripts/changelog-rollup.mjs— promotescurrent→ tagged weekly release, bumpspackage.json, creates annotated tag.github/workflows/changelog-append.yml— runs append on push-to-main withgithub.event.before..HEADrange and full-depth checkout so merge-commit side branches are walkableTags (local only, 9 total)
v2026.02.02v2026.02.09v2026.02.16v2026.02.23v2026.03.09v2026.03.16v2026.03.23v2026.03.30v2026.04.06Push with
git push origin --tagsafter this lands on main.Test plan
This Weekcard at topfreecut:whatsNewLastSeen)current(will include 2 meta-bullets for the changelog feature itself unless the merge message carries[skip changelog])npm run changelog:rollup -- --date 2026-04-13after the current week closes to verify rollup outputCaveats
GITHUB_TOKEN; self-pushes don't trigger further runs (belt-and-suspendersif:guard also filters on commit message)changelogskill before rollup.claude/is now partially tracked:settings.local.jsonandworktrees/stay ignoredSummary by CodeRabbit
New Features
Documentation