Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down Expand Up @@ -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 |

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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-<category>-<name>`.
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-<category>-<name>`.

```bash
sin-code skills list # list all bundled skills
Expand Down
113 changes: 101 additions & 12 deletions cmd/sin-code/internal/lessons/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,67 @@ 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)
}
if err != nil {
_ = 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 {
Expand All @@ -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)
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion skills/code-skills/skill-code-create/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<category>-skills/`. All bundled skills follow the naming convention `skill-<category>-<descriptive-name>`. 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/<category>-skills/`. All bundled skills follow the naming convention `skill-<category>-<descriptive-name>`. 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

Expand Down
31 changes: 31 additions & 0 deletions skills/code-skills/skill-code-graph/context/triggers.md
Original file line number Diff line number Diff line change
@@ -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.
26 changes: 26 additions & 0 deletions skills/code-skills/skill-code-graph/frameworks/standards.md
Original file line number Diff line number Diff line change
@@ -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
27 changes: 27 additions & 0 deletions skills/code-skills/skill-code-graph/tasks/workflow.md
Original file line number Diff line number Diff line change
@@ -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
25 changes: 25 additions & 0 deletions skills/code-skills/skill-code-graph/templates/output.md
Original file line number Diff line number Diff line change
@@ -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
```
23 changes: 23 additions & 0 deletions skills/code-skills/skill-code-graph/templates/prompt.md
Original file line number Diff line number Diff line change
@@ -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}]}
```
Loading