diff --git a/AGENTS.md b/AGENTS.md index 99635dc0..2c632b42 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -413,7 +413,7 @@ SIN-Code/ │ SIN-Code-SCA-Tool-Go, SIN-Code-Secrets-Scanner │ ├── src/sin_code_bundle/ ← Python companion: `sin` CLI + `sin-serve` -├── skills/ ← 35 bundled skills in category directories +├── skills/ ← 37 bundled skills in category directories │ ├── browser-skills/ │ ├── code-skills/ │ ├── debug-skills/ @@ -621,7 +621,7 @@ Headless JSON contract (stable API — never break without major bump): | v3.18.0 | ✅ SHIPPED | `sin-code install` single-binary installer (issue #170), curl/bash + PowerShell shims, SHA256-verified release downloads | | v3.19.0 | ✅ SHIPPED | `sin-code review --complexity` (issue #179): `internal/complexity/` static analyzer with ponytail 5-tag format (`delete`, `stdlib`, `native`, `yagni`, `shrink`), `// sin-debt:` marker support (issue #177), text/json/markdown output, race-clean tests | | v3.20.0 | ✅ SHIPPED | Tool coverage, M6 enforcement, catalog, telemetry (issues #249, #253, #248, #250, #252, #251): agent profiles expose full `sin_*` + MCP prefix surface; system prompt injects SIN-tool preference fragment; runtime `ToolCoverageEnforcer` rejects missing/forbidden tool usage; `ledger tools` heatmap/coverage/unused; orchestrator planner emits mandatory `ToolChain` per intent; `sin-code catalog` unifies 46+ MCP tools, 17+ chat tools, and 14+ external MCP prefixes. | -| v3.20.0 | ✅ SHIPPED | `sin-code image-graph` — SOTA ECharts chart generation (bar/line/pie/area); `skill-github-readme` bundled as 35th skill. 42 subcommands, 35 bundled skills. | +| v3.20.0 | ✅ SHIPPED | `sin-code image-graph` — SOTA ECharts chart generation (bar/line/pie/area); `skill-github-readme` bundled. 42 subcommands, 37 bundled skills. | | v3.21.0 | ✅ SHIPPED | Test-First Verify-Loop (RFC-test-automation.md): `sin_test` + `sin_test_generate` + `sin_quality_gate` + `sin_mutation` + `sin_fuzz` + `sin_property`; `tool.post` hook payload path; `test.*` config keys; `evals/test-generation.json` golden dataset. | | v3.22.0 | ACTIVE | SIN Fusion v1: Verify-Tournament (issue #290) — multi-model fan-out on verify.fail, Fireworks pool (6 models), thinking mode, cost-governor, difficulty gate, PoC-only | diff --git a/README.md b/README.md index 92ff0329..5dd99269 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ swarm mode, skill bootstrapping, and methodology skills. ## MCP Integration - **MCP Server**: Go — `sin-code serve` (main binary, 44+ tools); Python legacy — `src/sin_code_bundle/mcp_server.py` -- **Tools**: 40 subcommands, 34 bundled skills, 12 ecosystem skill servers, and external MCP servers (websearch, browser, symfony-lens, etc.) +- **Tools**: 42 subcommands, 37 bundled skills, 12 ecosystem skill servers, and external MCP servers (websearch, browser, symfony-lens, etc.) - **Register**: Add `sin-code serve` to your MCP client config (see `docs/mcp.json.example`), or register the legacy Python server via `sin mcp register sin-serve src/sin_code_bundle/mcp_server.py` ## Development @@ -87,7 +87,7 @@ swarm mode, skill bootstrapping, and methodology skills. ## Bundled Skills (v3.17.0) -SIN-Code ships **34 bundled skills** embedded in the binary, installable via `sin-code skills list|install`. Skills are organized into category directories and follow a unified naming convention `skill--`. +SIN-Code ships **37 bundled skills** embedded in the binary, installable via `sin-code skills list|install`. Skills are organized into category directories and follow a unified naming convention `skill--`. ```bash sin-code skills list # list all bundled skills diff --git a/cmd/sin-code/internal/lessons/store.go b/cmd/sin-code/internal/lessons/store.go index a8fa49e7..b8ee37ed 100644 --- a/cmd/sin-code/internal/lessons/store.go +++ b/cmd/sin-code/internal/lessons/store.go @@ -107,6 +107,8 @@ CREATE TABLE IF NOT EXISTS lessons ( CREATE INDEX IF NOT EXISTS idx_lessons_workspace ON lessons(workspace); CREATE INDEX IF NOT EXISTS idx_lessons_type ON lessons(type); CREATE INDEX IF NOT EXISTS idx_lessons_occurrences ON lessons(occurrences DESC); + +PRAGMA user_version = 2; ` _, err = db.Exec(schema) } @@ -114,7 +116,58 @@ CREATE INDEX IF NOT EXISTS idx_lessons_occurrences ON lessons(occurrences DESC); _ = db.Close() return nil, err } - return &Store{db: db}, nil + s := &Store{db: db} + if err := s.migrateFingerprints(); err != nil { + _ = db.Close() + return nil, err + } + return s, nil +} + +// migrateFingerprints rewrites any 16-hex (64-bit) fingerprints to full 64-hex +// SHA-256 fingerprints. Idempotent: only rows with exactly 16 hex chars are touched. +func (s *Store) migrateFingerprints() error { + rows, err := s.db.Query(`SELECT id, type, workspace, context, lesson, occurrences, first_seen, last_seen FROM lessons WHERE LENGTH(id) = 16`) + if err != nil { + return err + } + defer rows.Close() + type oldRow struct { + id, typ, ws, ctx, lesson, first, last string + occurrences int + } + var toMigrate []oldRow + for rows.Next() { + var r oldRow + if err := rows.Scan(&r.id, &r.typ, &r.ws, &r.ctx, &r.lesson, &r.occurrences, &r.first, &r.last); err != nil { + return err + } + toMigrate = append(toMigrate, r) + } + if err := rows.Err(); err != nil { + return err + } + if len(toMigrate) == 0 { + return nil + } + tx, err := s.db.Begin() + if err != nil { + return err + } + defer tx.Rollback() + for _, r := range toMigrate { + var ctxMap map[string]any + if err := json.Unmarshal([]byte(r.ctx), &ctxMap); err != nil { + ctxMap = nil + } + newID := Fingerprint(EntryType(r.typ), r.ws, ctxMap) + if _, err := tx.Exec(` + UPDATE lessons SET id = ? WHERE id = ? + `, newID, r.id); err != nil { + return err + } + } + return tx.Commit() } func (s *Store) Close() error { @@ -125,6 +178,8 @@ func (s *Store) Close() error { } // Record upserts a lesson — same fingerprint increments the count. +// If a collision is detected (same fingerprint but different content), +// a deterministic variant ID is used so the distinct lesson is preserved. func (s *Store) Record(ctx context.Context, e Entry) error { if e.ID == "" { e.ID = Fingerprint(e.Type, e.Workspace, e.Context) @@ -134,15 +189,49 @@ func (s *Store) Record(ctx context.Context, e Entry) error { if err != nil { return err } - _, err = s.db.ExecContext(ctx, ` -INSERT INTO lessons (id, type, workspace, context, lesson, first_seen, last_seen) -VALUES (?, ?, ?, ?, ?, ?, ?) -ON CONFLICT(id) DO UPDATE SET - occurrences = occurrences + 1, - last_seen = excluded.last_seen, - lesson = excluded.lesson -`, e.ID, e.Type, e.Workspace, ctxJSON, e.Lesson, now, now) - return err + + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer tx.Rollback() + + id := e.ID + for attempt := 0; attempt < 10; attempt++ { + var existing struct { + typ, ws, ctx, lesson string + } + err := tx.QueryRowContext(ctx, ` + SELECT type, workspace, context, lesson FROM lessons WHERE id = ? + `, id).Scan(&existing.typ, &existing.ws, &existing.ctx, &existing.lesson) + if err == sql.ErrNoRows { + // Insert new lesson. + _, err = tx.ExecContext(ctx, ` + INSERT INTO lessons (id, type, workspace, context, lesson, first_seen, last_seen) + VALUES (?, ?, ?, ?, ?, ?, ?) + `, id, e.Type, e.Workspace, ctxJSON, e.Lesson, now, now) + if err != nil { + return err + } + return tx.Commit() + } + if err != nil { + return err + } + // Collision check: compare canonical content. + if existing.typ == string(e.Type) && existing.ws == e.Workspace && existing.ctx == string(ctxJSON) && existing.lesson == e.Lesson { + _, err = tx.ExecContext(ctx, ` + UPDATE lessons SET occurrences = occurrences + 1, last_seen = ? WHERE id = ? + `, now, id) + if err != nil { + return err + } + return tx.Commit() + } + // True collision: try a variant ID. + id = fmt.Sprintf("%s:%d", e.ID, attempt+1) + } + return errors.New("too many fingerprint collisions") } // Query returns relevant lessons for a workspace, ordered by frequency. @@ -200,12 +289,12 @@ WHERE occurrences = 1 AND last_seen < ? return int(n), nil } -// sin-debt: 16-hex (64-bit) fingerprint — collision risk at ~4B entries, upgrade: switch to full 64-hex sha256 + collision check when lesson count > 100k // Fingerprint is the stable identity of a lesson (type+workspace+context). +// Uses full 64-hex SHA-256 to avoid 64-bit birthday collisions. func Fingerprint(t EntryType, ws string, ctx map[string]any) string { data, _ := json.Marshal(map[string]any{"type": t, "ws": ws, "ctx": ctx}) h := sha256.Sum256(data) - return hex.EncodeToString(h[:])[:16] + return hex.EncodeToString(h[:]) } // sin-debt: linear scan over all lessons per briefing call, upgrade: switch to top-K precomputed index when entry count > 10k diff --git a/skills/code-skills/skill-code-create/SKILL.md b/skills/code-skills/skill-code-create/SKILL.md index ef75725f..a6a67d8e 100644 --- a/skills/code-skills/skill-code-create/SKILL.md +++ b/skills/code-skills/skill-code-create/SKILL.md @@ -19,7 +19,7 @@ lifecycle: native Create a new SIN-Code / OpenCode compatible skill from a template. Produces a valid skill directory with `SKILL.md`, `context/`, `frameworks/`, `tasks/`, `templates/`, and optional `scripts/` / `tests/` / `lib/`. -As of **v3.20.0**, SIN-Code ships **36 bundled skills** embedded in the `sin-code` binary under `skills/-skills/`. All bundled skills follow the naming convention `skill--`. New skills must be created in the correct category directory and added to the registry/docs before they are discoverable by agents. +As of **v3.20.0**, SIN-Code ships **37 bundled skills** embedded in the `sin-code` binary under `skills/-skills/`. All bundled skills follow the naming convention `skill--`. New skills must be created in the correct category directory and added to the registry/docs before they are discoverable by agents. ## When to Use diff --git a/skills/code-skills/skill-code-graph/context/triggers.md b/skills/code-skills/skill-code-graph/context/triggers.md new file mode 100644 index 00000000..266b6663 --- /dev/null +++ b/skills/code-skills/skill-code-graph/context/triggers.md @@ -0,0 +1,31 @@ +# Context: Triggers & Boundaries + +Docs: ../SKILL.md + +## Trigger Phrases + +- "chart" +- "graph" +- "benchmark chart" +- "comparison chart" +- "plot data" +- "visualize data" +- "bar chart" +- "pie chart" +- "line chart" +- "area chart" +- "generate graph" +- "skill-code-graph" + +## Boundaries + +- **In scope:** Generating SOTA charts (bar, line, pie, area) from JSON data using `sin-code image-graph`. Deterministic, free, no AI. +- **Out of scope:** Creative/conceptual visuals (use sin-image-generation skill). Architecture diagrams. Text-based tables. + +## Required Input + +JSON data file with chart spec (title, categories/series/items), chart type (bar/line/pie/area), and output path. + +## Tone + +Technical, data-driven, precise. diff --git a/skills/code-skills/skill-code-graph/frameworks/standards.md b/skills/code-skills/skill-code-graph/frameworks/standards.md new file mode 100644 index 00000000..db79cb08 --- /dev/null +++ b/skills/code-skills/skill-code-graph/frameworks/standards.md @@ -0,0 +1,26 @@ +# Frameworks: Standards & Constraints + +Docs: ../SKILL.md + +## Technology Stack + +- `sin-code image-graph` Go binary (stdlib only, no external Go deps) +- Apache ECharts 5.5.0 via CDN (no Go wrapper library) +- Headless Chrome for PNG screenshots + +## Standards + +- Dark theme: #0B1120 background, Inter font +- SOTA color palette: Indigo, Pink, Emerald, Amber, Blue, Red, Purple, Cyan +- Gradient fills on bars and lines +- Glow shadows (shadowBlur 12-24) +- Rounded bar corners (borderRadius [8,8,0,0]) +- Staggered animations (elasticOut/cubicOut, per-item delay) +- Interactive HTML output (hover tooltips, save-as-image, toolbox) + +## Constraints + +- Same JSON input = same output (deterministic, byte-stable) +- No AI, no credits, no external API calls +- JSON input must match the chart type format (bar/line/area vs pie) +- PNG requires headless Chrome on PATH diff --git a/skills/code-skills/skill-code-graph/tasks/workflow.md b/skills/code-skills/skill-code-graph/tasks/workflow.md new file mode 100644 index 00000000..43f3e757 --- /dev/null +++ b/skills/code-skills/skill-code-graph/tasks/workflow.md @@ -0,0 +1,27 @@ +# Tasks: Workflow + +Docs: ../SKILL.md + +## Pre-flight + +- [ ] Identify the data source (JSON file, API endpoint, inline JSON) +- [ ] Determine chart type: bar (comparison), line (trend), pie (proportion), area (volume trend) +- [ ] Prepare the JSON spec matching the chart type format + +## Execution + +- [ ] Task 1: Prepare JSON spec + - Acceptance: Valid JSON with title, categories/series/items, type field + - Verify: JSON parses without error +- [ ] Task 2: Run `sin-code image-graph` + - Acceptance: HTML file generated at output path + - Verify: File exists and opens in browser +- [ ] Task 3: Verify PNG output (if headless Chrome available) + - Acceptance: PNG file generated alongside HTML + - Verify: File exists and is a valid image + +## Post-flight + +- [ ] Open HTML in browser to verify interactivity (tooltips, hover, save-as-image) +- [ ] Verify chart renders with correct data, labels, and colors +- [ ] Clean up temporary JSON files if any diff --git a/skills/code-skills/skill-code-graph/templates/output.md b/skills/code-skills/skill-code-graph/templates/output.md new file mode 100644 index 00000000..3223c0cf --- /dev/null +++ b/skills/code-skills/skill-code-graph/templates/output.md @@ -0,0 +1,25 @@ +# Template: Output Format + +Docs: ../SKILL.md + +## Chart Generation Report + +```markdown +# Generated Chart: {title} + +## Details +- Type: {bar|line|pie|area} +- Source: {data file or inline JSON} +- Output: {output.html} + {output.png} + +## Files +- {output}.html — interactive chart (opens in browser) +- {output}.png — static screenshot (if headless Chrome available) + +## Verification +- [x] HTML opens in browser +- [x] Data renders correctly +- [x] Colors and gradients applied +- [x] Hover tooltips functional +- [x] Save-as-image toolbox available +``` diff --git a/skills/code-skills/skill-code-graph/templates/prompt.md b/skills/code-skills/skill-code-graph/templates/prompt.md new file mode 100644 index 00000000..3f026d1c --- /dev/null +++ b/skills/code-skills/skill-code-graph/templates/prompt.md @@ -0,0 +1,23 @@ +# Template: Prompt Snippet + +Docs: ../SKILL.md + +## User wants to generate a chart + +```markdown +You are generating a SOTA chart using sin-code image-graph. + +Chart type: {bar|line|pie|area} +Data source: {JSON file path or inline JSON} +Output path: {output.html} + +Follow the workflow from tasks/workflow.md: +1. Prepare JSON spec matching the chart type format +2. Run: sin-code image-graph --type {type} --data {file} --output {output} +3. Verify HTML opens in browser with correct data +4. Check PNG output if headless Chrome is available + +JSON format reference: +- bar/line/area: {title, y_label, type, categories, series: [{name, values}]} +- pie: {title, type, items: [{label, value}]} +```