From 1e557859a17210f5dad5cbe3e68532942f30ce28 Mon Sep 17 00:00:00 2001 From: Ashutosh Tripathi Date: Sat, 28 Feb 2026 21:05:34 +0530 Subject: [PATCH 1/3] release: v0.4.1 (#42) * chore: new branch (#33) * fix(ci): bench scorecard ci windows fixes (#34) * ci: auto-template and title for dev to main PRs * release: v0.3.2 (dev -> main) (#35) * New branch (#33) * chore: add pending project files * refactor(ingest): centralize ingestion via parser/resolver/store layers * docs: document layered ingest architecture * test(perf): add qmd benchmark harness and non-blocking CI regression check * perf(bench): add ingest hotpath benchmark and record qmd optimization * perf(ingest): batch session writes and add stable benchmark tooling * Add benchmark scorecard to CI summary and sticky PR comment * Fix bench import path and temporarily disable design-contract workflow * CI: checkout qmd submodule in perf bench workflow * Fix Windows path handling in ingest session discovery * Feature/bench scorecard ci windows fixes (#34) * chore: add pending project files * refactor(ingest): centralize ingestion via parser/resolver/store layers * docs: document layered ingest architecture * test(perf): add qmd benchmark harness and non-blocking CI regression check * perf(bench): add ingest hotpath benchmark and record qmd optimization * perf(ingest): batch session writes and add stable benchmark tooling * Add benchmark scorecard to CI summary and sticky PR comment * Fix bench import path and temporarily disable design-contract workflow * CI: checkout qmd submodule in perf bench workflow * Fix Windows path handling in ingest session discovery * CI: run full test matrix only on merge branches * CI: auto-create draft prerelease on successful dev CI * CI: auto-template and title for dev to main PRs * ci: create dev draft release after successful dev test matrix * chore: add e2e dev release flow test marker (#36) * release: v0.3.2 (dev -> main) (#37) * New branch (#33) * chore: add pending project files * refactor(ingest): centralize ingestion via parser/resolver/store layers * docs: document layered ingest architecture * test(perf): add qmd benchmark harness and non-blocking CI regression check * perf(bench): add ingest hotpath benchmark and record qmd optimization * perf(ingest): batch session writes and add stable benchmark tooling * Add benchmark scorecard to CI summary and sticky PR comment * Fix bench import path and temporarily disable design-contract workflow * CI: checkout qmd submodule in perf bench workflow * Fix Windows path handling in ingest session discovery * Feature/bench scorecard ci windows fixes (#34) * chore: add pending project files * refactor(ingest): centralize ingestion via parser/resolver/store layers * docs: document layered ingest architecture * test(perf): add qmd benchmark harness and non-blocking CI regression check * perf(bench): add ingest hotpath benchmark and record qmd optimization * perf(ingest): batch session writes and add stable benchmark tooling * Add benchmark scorecard to CI summary and sticky PR comment * Fix bench import path and temporarily disable design-contract workflow * CI: checkout qmd submodule in perf bench workflow * Fix Windows path handling in ingest session discovery * CI: run full test matrix only on merge branches * CI: auto-create draft prerelease on successful dev CI * CI: auto-template and title for dev to main PRs * CI: create dev draft release after successful dev test matrix * chore: add e2e dev release flow test marker (#36) * docs: update CHANGELOG.md for v0.4.0 [skip ci] * docs: add CI/release workflow architecture and north-star plan * ci: add commit lint, semver metadata, and deterministic release notes * docs: finalize workflow policy docs without backlog sections * ci: scope commit lint to pull request commit ranges only * fix(ci): setup bun before dev draft release metadata step * fix(ci): allow legacy non-conventional history for dev draft metadata * fix(release): align dev-main PR version with latest stable tag * ci: improve workflow and check naming for PR readability * ci: skip PR test job for dev to main release PRs * fix(ci): use import.meta.dir for cross-platform path resolution new URL(import.meta.url).pathname produces /D:/a/... on Windows, causing ENOENT errors. import.meta.dir is Bun's cross-platform alternative. Co-Authored-By: Claude Opus 4.6 * ci: add auto-release job for main branch merges After tests pass on main, automatically compute the next semver version and create a GitHub release. Handles squash merges (which lose individual commit types) by defaulting to patch when commits exist but bump is "none". Skips if HEAD is already tagged. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: github-actions[bot] Co-authored-by: Baseline User Co-authored-by: Claude Opus 4.6 --- .github/workflows/ci.yml | 74 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 766c0fc..9125d3b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -143,3 +143,77 @@ jobs: generate_release_notes: false draft: true prerelease: true + + auto-release: + name: Push(main) / Auto Release + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + needs: test-merge + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: recursive + + - name: Check if already tagged + id: check + run: | + # Skip if this commit already has a stable version tag + for tag in $(git tag --points-at HEAD 2>/dev/null); do + if echo "$tag" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "skip=true" >> "$GITHUB_OUTPUT" + echo "Commit already tagged as $tag, skipping." + exit 0 + fi + done + echo "skip=false" >> "$GITHUB_OUTPUT" + + - name: Setup Bun + if: steps.check.outputs.skip != 'true' + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Install dependencies + if: steps.check.outputs.skip != 'true' + run: bun install + + - name: Compute release metadata + if: steps.check.outputs.skip != 'true' + id: meta + run: | + bun run scripts/release-meta.ts --allow-invalid --github-output "$GITHUB_OUTPUT" + + # Squash merges lose individual commit types, so if bump is + # "none" but there are unreleased commits, default to patch. + BUMP=$(grep '^bump=' "$GITHUB_OUTPUT" | cut -d= -f2) + COUNT=$(grep '^commit_count=' "$GITHUB_OUTPUT" | cut -d= -f2) + if [ "$BUMP" = "none" ] && [ "$COUNT" -gt 0 ]; then + echo "Bump was 'none' with $COUNT commits — overriding to 'patch'" + LATEST=$(git tag --list 'v*.*.*' --sort=-version:refname \ + | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1) + if [ -n "$LATEST" ]; then + IFS='.' read -r MAJ MIN PAT <<< "${LATEST#v}" + NEXT="v${MAJ}.${MIN}.$((PAT + 1))" + else + NEXT="v0.1.0" + fi + echo "next_version=${NEXT}" >> "$GITHUB_OUTPUT" + echo "bump=patch" >> "$GITHUB_OUTPUT" + fi + + - name: Create release + if: steps.check.outputs.skip != 'true' && steps.meta.outputs.bump != 'none' + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.meta.outputs.next_version }} + target_commitish: ${{ github.sha }} + name: ${{ steps.meta.outputs.next_version }} + body: ${{ steps.meta.outputs.release_notes }} + generate_release_notes: false + draft: false + prerelease: false From 0a90454cf0015f9a3e8fa68af01fe1119005ccd2 Mon Sep 17 00:00:00 2001 From: Baseline User Date: Sat, 28 Feb 2026 21:27:50 +0530 Subject: [PATCH 2/3] ci: trigger auto-release workflow on main Previous squash merge body contained [skip ci] from an old commit message, which prevented GitHub Actions from running. Co-Authored-By: Claude Opus 4.6 From 0781b1ebe24ad6bd85d1664b037bfceddb49813d Mon Sep 17 00:00:00 2001 From: Baseline User Date: Tue, 3 Mar 2026 15:15:07 +0530 Subject: [PATCH 3/3] feat(db): add model-aware cost estimation and sidecar cleanup Add MODEL_PRICING map for Claude model families, estimateCost() for per-turn USD estimation, wire estimated_cost_usd into upsertSessionCosts, and add deleteSidecarRows() for force re-ingest cleanup. Co-Authored-By: Claude Opus 4.6 --- src/db.ts | 43 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/src/db.ts b/src/db.ts index d468696..8533d56 100644 --- a/src/db.ts +++ b/src/db.ts @@ -719,6 +719,31 @@ export function insertError( ).run(messageId, sessionId, errorType, message, createdAt); } +// Per-million-token pricing by model family +const MODEL_PRICING: Record = { + "claude-opus-4": { input: 15.0, output: 75.0, cacheRead: 1.5 }, + "claude-sonnet-4": { input: 3.0, output: 15.0, cacheRead: 0.3 }, + "claude-haiku-4": { input: 0.8, output: 4.0, cacheRead: 0.08 }, +}; +const DEFAULT_PRICING = { input: 3.0, output: 15.0, cacheRead: 0.3 }; + +export function estimateCost( + model: string, + inputTokens: number, + outputTokens: number, + cacheTokens: number +): number { + // Match model family: "claude-sonnet-4-20250514" → "claude-sonnet-4" + const family = Object.keys(MODEL_PRICING).find((k) => model.startsWith(k)); + const pricing = family ? MODEL_PRICING[family] : DEFAULT_PRICING; + return ( + (inputTokens * pricing.input + + outputTokens * pricing.output + + cacheTokens * pricing.cacheRead) / + 1_000_000 + ); +} + export function upsertSessionCosts( db: Database, sessionId: string, @@ -728,16 +753,28 @@ export function upsertSessionCosts( cacheTokens: number, durationMs: number ): void { + const modelName = model || "unknown"; + const cost = estimateCost(modelName, inputTokens, outputTokens, cacheTokens); db.prepare( - `INSERT INTO smriti_session_costs(session_id, model, total_input_tokens, total_output_tokens, total_cache_tokens, turn_count, total_duration_ms) - VALUES(?, ?, ?, ?, ?, 1, ?) + `INSERT INTO smriti_session_costs(session_id, model, total_input_tokens, total_output_tokens, total_cache_tokens, estimated_cost_usd, turn_count, total_duration_ms) + VALUES(?, ?, ?, ?, ?, ?, 1, ?) ON CONFLICT(session_id, model) DO UPDATE SET total_input_tokens = total_input_tokens + excluded.total_input_tokens, total_output_tokens = total_output_tokens + excluded.total_output_tokens, total_cache_tokens = total_cache_tokens + excluded.total_cache_tokens, + estimated_cost_usd = estimated_cost_usd + excluded.estimated_cost_usd, turn_count = turn_count + 1, total_duration_ms = total_duration_ms + excluded.total_duration_ms` - ).run(sessionId, model || "unknown", inputTokens, outputTokens, cacheTokens, durationMs); + ).run(sessionId, modelName, inputTokens, outputTokens, cacheTokens, cost, durationMs); +} + +export function deleteSidecarRows(db: Database, sessionId: string): void { + db.prepare(`DELETE FROM smriti_tool_usage WHERE session_id = ?`).run(sessionId); + db.prepare(`DELETE FROM smriti_file_operations WHERE session_id = ?`).run(sessionId); + db.prepare(`DELETE FROM smriti_commands WHERE session_id = ?`).run(sessionId); + db.prepare(`DELETE FROM smriti_errors WHERE session_id = ?`).run(sessionId); + db.prepare(`DELETE FROM smriti_git_operations WHERE session_id = ?`).run(sessionId); + db.prepare(`DELETE FROM smriti_session_costs WHERE session_id = ?`).run(sessionId); } export function insertGitOperation(