Skip to content

Proposal: auto-tags with seed catalog + local embeddings - #1

Merged
sergdort merged 4 commits into
mainfrom
feat/auto-tags-proposal
Mar 2, 2026
Merged

sergdort merged 4 commits into
mainfrom
feat/auto-tags-proposal

Conversation

@LogenNineFingersIsAlive

Copy link
Copy Markdown
Collaborator

Summary

Adds an implementation proposal for local auto-tagging and a starter seed tag catalog.

Changes

  • add docs/AUTO_TAGS_PROPOSAL.md
  • add config/seed-tags.json

Proposal highlights

  • candidate pool = seed tags + existing user tags
  • optional local embeddings (all-MiniLM-L6-v2) for ranking
  • threshold + top-N guardrails
  • deterministic fallback path
  • phased rollout (--auto-tags opt-in first)

Notes

This PR is docs/config only (no runtime behavior changes yet).

@sergdort
sergdort force-pushed the feat/auto-tags-proposal branch from 6bf4de4 to 77b8fce Compare February 28, 2026 22:07

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces an end-to-end “local auto-tags” feature (rule-based scoring with an optional Python sentence-transformers helper for embedding ranking), including DB provenance tracking, CLI/API/Web wiring, health diagnostics, seed tag catalog, and supporting docs/tests.

Changes:

  • Add auto-tag provenance columns to item_tags plus a new migration and indexes.
  • Implement auto-tagging pipeline (candidate building from seed + manual DB tags, rule scoring, optional Python embedding scoring, pruning stale auto tags) and expose it via save/extract.
  • Add CLI/API/Web UX support (--auto-tags, tags doctor, web save default behavior) and tests/docs.

Reviewed changes

Copilot reviewed 37 out of 37 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
scripts/auto-tags-embed.py Python helper to embed + score candidates.
packages/core/test/auto-tags.test.ts Core auto-tag behavior tests (rule backend).
packages/core/test/auto-tags-doctor.test.ts Doctor/health-check tests with fake python.
packages/core/src/types.ts API contract: auto-tag options + result fields.
packages/core/src/index.ts Export auto-tags feature entrypoints.
packages/core/src/features/tags/service.ts Ensure manual tag writes set provenance fields.
packages/core/src/features/items/service.ts Run auto-tags (and conditional extraction) on save.
packages/core/src/features/extract/service.ts Run auto-tags after extraction.
packages/core/src/features/auto-tags/types.ts Auto-tags internal types/config/report shapes.
packages/core/src/features/auto-tags/service.ts Core auto-tagging pipeline + DB updates.
packages/core/src/features/auto-tags/seed.ts Load seed tag catalog from JSON.
packages/core/src/features/auto-tags/rules.ts Deterministic rule-based scoring + guardrails.
packages/core/src/features/auto-tags/python.ts Spawn python helper and parse scores.
packages/core/src/features/auto-tags/index.ts Barrel exports for auto-tags module.
packages/core/src/features/auto-tags/doctor.ts Runtime dependency checks for python backend.
packages/core/src/features/auto-tags/config.ts Env/config resolution for auto-tags.
packages/core/src/db/schema.ts Add item_tags provenance columns + constraints/indexes.
drizzle/meta/_journal.json Record new migration in drizzle journal.
drizzle/meta/0004_snapshot.json Drizzle snapshot for updated schema.
drizzle/0004_pale_mathemanic.sql Migration to rebuild item_tags with new columns.
docs/CLI_REFERENCE.md Document new CLI flags + doctor output.
docs/AUTO_TAGS_SETUP.md Python backend setup instructions.
docs/AUTO_TAGS_PROPOSAL.md Design proposal for auto-tags approach.
config/seed-tags.json Seed catalog for candidate tags + descriptors.
apps/web/src/features/save/ui/save-form.tsx Web save UX: trigger auto-tags when tags empty.
apps/web/src/features/save/api/save-item.ts Web save payload includes autoTags.
apps/cli/test/integration.test.ts CLI integration coverage for auto-tags + doctor.
apps/cli/src/cli.ts Wire CLI save/extract/tags doctor into core services.
apps/api/src/features/save/routes.ts API save schema supports autoTags.
apps/api/src/features/save/dto.ts API save DTO parses autoTags.
apps/api/src/features/extract/routes.ts API extract endpoint accepts autoTags.
apps/api/src/features/extract/dto.ts API extract DTO parses autoTags.
apps/api/src/app/create-api-app.ts JSON parser override for empty JSON bodies.
README.md Surface auto-tags feature in README.
AGENTS.md Document implemented auto-tags + env controls.
.env.example Add example auto-tags env vars.
.agents/notes.md Add notes about migration pitfalls and env patterns.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread drizzle/0004_pale_mathemanic.sql Outdated
);
--> statement-breakpoint
INSERT INTO `__new_item_tags`("item_id", "tag_id", "created_at", "is_manual", "is_auto", "auto_score", "auto_source", "auto_model", "auto_updated_at")
SELECT it."item_id", it."tag_id", CAST(strftime('%s','now') AS integer), 1, 0, NULL, NULL, NULL, NULL

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The migration populates created_at using strftime('%s','now') (seconds) instead of preserving the existing item_tags.created_at value (which is stored as timestamp_ms elsewhere in the schema). This will both lose historical data and write the wrong unit into created_at for all migrated rows. Consider selecting the old created_at column directly (and only defaulting new provenance columns to constants/nulls).

Suggested change
SELECT it."item_id", it."tag_id", CAST(strftime('%s','now') AS integer), 1, 0, NULL, NULL, NULL, NULL
SELECT it."item_id", it."tag_id", it."created_at", 1, 0, NULL, NULL, NULL, NULL

Copilot uses AI. Check for mistakes.
title: title || undefined,
tags: normalizedTags,
extract,
autoTags: normalizedTags.length === 0 ? true : undefined,

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

autoTags is only set to true when the tag input is empty, but when the user provides manual tags you omit the field (set undefined). If STASH_AUTO_TAGS_ENABLED=true, this omission will still trigger auto-tagging even though the UI text implies manual tags disable it. Consider explicitly sending autoTags: false when manual tags are present to match the UX copy.

Suggested change
autoTags: normalizedTags.length === 0 ? true : undefined,
autoTags: normalizedTags.length === 0,

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +6
# Auto Tags Proposal (Seed Tags + all-MiniLM-L6-v2)

Status: Draft
Owner: Stash
Date: 2026-02-25

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description states this is “docs/config only (no runtime behavior changes yet)”, but this diff includes runtime changes (DB schema/migration, core auto-tags implementation, CLI/API/web behavior, and new tests). Please update the PR description (or split the PR) so reviewers can accurately scope risk and review expectations.

Copilot uses AI. Check for mistakes.
Comment thread docs/AUTO_TAGS_PROPOSAL.md Outdated
- `STASH_AUTO_TAGS_ENABLED=false`
- `STASH_AUTO_TAGS_MAX=3`
- `STASH_AUTO_TAGS_MIN_SCORE=0.62`
- `STASH_AUTO_TAGS_MODEL=all-MiniLM-L6-v2`

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The proposal suggests STASH_AUTO_TAGS_MODEL=all-MiniLM-L6-v2, but the implemented default/model references elsewhere in this PR use the full repo id sentence-transformers/all-MiniLM-L6-v2. To avoid a misconfiguration that will fail model loading, align the proposal text with the actual expected model string (or clarify both accepted forms).

Suggested change
- `STASH_AUTO_TAGS_MODEL=all-MiniLM-L6-v2`
- `STASH_AUTO_TAGS_MODEL=sentence-transformers/all-MiniLM-L6-v2`

Copilot uses AI. Check for mistakes.
Comment on lines +4 to +10
export type AutoTagSource = "embedding" | "rule"

export type AutoTagScore = {
tag: string
score: number
source: AutoTagSource
}

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AutoTagSource / AutoTagScore are now defined both here and again in packages/core/src/features/auto-tags/types.ts. Even though they’re structurally compatible today, duplicating these unions/interfaces increases drift risk (e.g., adding a new source would require updating multiple files). Consider defining these once (e.g., in a shared types module) and re-exporting/importing to keep the API contract and internal types aligned.

Copilot uses AI. Check for mistakes.
Comment on lines +289 to +292
for (const row of selected) {
upsertAutoTag(db, input.itemId, row.tag, row.score, row.source, timestamp, config.model)
}
pruneStaleAutoTags(db, input.itemId, selectedSet)

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

applyAutoTags performs multiple writes (upserts + pruning) without wrapping them in a DB transaction. If an error occurs mid-run (e.g., during an upsert), the item can end up with a partially-updated auto-tag set. Consider running the write portion (upserts + prune) inside a single db.transaction(...) to keep tag provenance updates atomic.

Suggested change
for (const row of selected) {
upsertAutoTag(db, input.itemId, row.tag, row.score, row.source, timestamp, config.model)
}
pruneStaleAutoTags(db, input.itemId, selectedSet)
await db.transaction(async (tx) => {
for (const row of selected) {
await upsertAutoTag(tx, input.itemId, row.tag, row.score, row.source, timestamp, config.model)
}
await pruneStaleAutoTags(tx, input.itemId, selectedSet)
})

Copilot uses AI. Check for mistakes.
@sergdort
sergdort merged commit afcad61 into main Mar 2, 2026
1 check passed
@sergdort
sergdort deleted the feat/auto-tags-proposal branch March 2, 2026 21:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants