Skip to content

feat(profile): single-source-of-truth sin-profile.md + per-agent renderers (Claude/Codex/Gemini/Cursor/Windsurf/Cline/Copilot) + CI sync #175

Description

@Delqhi

Summary

Generate the per-agent profile files (.claude-plugin/, .codex/, AGENTS.md, GEMINI.md, .cursor/rules/, .windsurf/rules/, .clinerules/, .github/copilot-instructions.md, …) for SIN-Code and for any project that uses SIN-Code, from a single source of truth (docs/agent-profiles/sin-profile.md). Inspired by JuliusBrussee/caveman's src/rules/caveman-activate.md + the per-agent install code in bin/install.js:

We removed the agent-specific dotdir mirrors at the repo root (.cursor/, .windsurf/, .clinerules/, .github/copilot-instructions.md, root caveman/SKILL.md). They were never read by the installer — only used to self-apply caveman to this repo when a maintainer opened it in Cursor/Windsurf/Cline. ... src/rules/caveman-activate.md is the single source of truth. ... Don't re-add per-agent mirrors.

For SIN-Code, we currently have only AGENTS.md at the repo root. CLAUDE.md, GEMINI.md, .claude-plugin/manifest.json, .cursor/, .windsurf/, .clinerules/, .github/copilot-instructions.md are all absent. This issue generates all of them from one template.

Motivation

Caveman's distribution insight is that per-agent profile files are all variations of the same content. The README frames this as a maintenance burden:

A handful of dotdir leftovers (.junie/, .kiro/, .roo/, .agents/) still hold a stale cavecrew/SKILL.md mirror from before the cleanup. They aren't read by anything in the current install path; remove on sight, no migration needed.

The fix is the caveman-activate.md single source of truth + a per-agent render step. From the CLAUDE.md:

Auto-generated / auto-synced — do not edit directly

File Synced from
plugins/caveman/skills/caveman/SKILL.md skills/caveman/SKILL.md
agents/cavecrew-*.md agents/cavecrew-*.md
dist/caveman.skill ZIP of skills/caveman/ directory (gitignored; rebuilt by CI on release)

The CI workflow (.github/workflows/sync-skill.yml) does the actual syncing on every push to main. The "single source of truth" pattern is enforced by tooling, not by convention.

For SIN-Code, the use case is:

  1. Self-hosting: when a maintainer opens the SIN-Code repo in Cursor/Windsurf/Cline, the agent should know the project's rules. Currently, only AGENTS.md is read (by Codex and opencode); the other agents read nothing.
  2. Per-project: a project that uses SIN-Code as an MCP server should have its own AGENTS.md / CLAUDE.md that tells the agent how to use SIN-Code. We currently have no command that generates this.

This issue builds:

  • A single template (docs/agent-profiles/sin-profile.md).
  • A sin-code profile render command that emits the per-agent files.
  • A CI workflow (.github/workflows/agent-profile-sync.yml) that auto-syncs on push to main.

Current State in SIN-Code

Area File Lines What is there
AGENTS.md AGENTS.md 1+ The single source of truth for the SIN-Code repo. Read by Codex and opencode.
CLAUDE.md Does not exist.
GEMINI.md Does not exist.
.claude-plugin/manifest.json Does not exist.
.cursor/ Does not exist.
.windsurf/ Does not exist.
.clinerules/ Does not exist.
.github/copilot-instructions.md Does not exist.
profiles/ profiles/fireworks.toml, profiles/qwen-relay.toml LLM agent profiles (model + base URL), not agent-target profiles.
scripts/ scripts/ci-precheck.sh, scripts/validate_skill.py, scripts/org-cleanup.sh, scripts/promote-to-sin-code.sh Helper scripts; not for profile rendering.
CI workflows .github/workflows/ ceo-audit.yml, sin-code-release.yml, ecosystem-sync.yml. No agent-profile-sync.yml.
AGENTS.md §10 (Naming and stability rules) AGENTS.md:359+ Already documents the bundled-skill naming convention.

What this tells us: SIN-Code has one profile file. It does not have the render step, the template, or the CI sync.

What caveman does (precise)

Caveman's src/rules/caveman-activate.md is the single source of truth for the "always-on" rule content. The per-agent installers (in bin/install.js) read this file and render the agent-specific output:

Agent Output path Render strategy
Claude Code Plugin distribution in plugins/caveman/skills/<name>/SKILL.md Direct copy + frontmatter merge (version, always: true)
Codex .codex/hooks.json + .codex/config.toml Generated by bin/install.js
Gemini GEMINI.md context file Generated by the extension
opencode ~/.config/opencode/plugins/caveman/plugin.js ESM Bun module
OpenClaw ~/.openclaw/workspace/skills/caveman/SKILL.md + marker-fenced block in SOUL.md Generated by bin/lib/openclaw.js
Cursor .cursor/rules/caveman.mdc Generated by src/tools/caveman-init.js
Windsurf .windsurf/rules/caveman.md Same
Cline .clinerules/caveman.md Same
Copilot .github/copilot-instructions.md + AGENTS.md Same

The CI workflow (.github/workflows/sync-skill.yml) does the auto-syncing. The maintainer never edits the per-agent files directly.

Detailed Implementation Plan

Phase 1 — The template

  1. Create docs/agent-profiles/sin-profile.md (new file). This is the single source of truth. It contains the project's rules in a vendor-neutral Markdown that can be rendered to every agent format. The structure is:

    <!-- sin-code:profile v1 -->
    # SIN-Code Project Profile
    
    ## Identity
    <name, version, repo>
    
    ## Hard mandates
    - Single binary (M2)
    - Verification gate (M3)
    - Permission engine (M4)
    - Module path (M5)
    ...
    
    ## Architecture
    <subsystem list, command list>
    
    ## Conventions
    <commit format, branch format, CI gates>
    
    ## Bundled skills
    <table of 34 skills, categories>
    
    ## Lifecycle hooks
    <24 events>
  2. Define a frontmatter schema for the template:

    ---
    sin-code-profile: 1
    name: sin-code
    ---

    The version is bumped when the template changes in a breaking way.

Phase 2 — Per-agent renderers

  1. Create cmd/sin-code/internal/profile/render.go (new package). Public surface:

    type Target struct {
        ID        string // "claude-code" | "codex" | "gemini" | "opencode" | "cursor" | ...
        Path      string // target file or dir
        Format    string // "rule" | "context" | "manifest" | "plugin"
        Frontmatter map[string]string // optional, e.g. {"always": "true", "version": "v3.17.0"}
    }
    var Targets = []Target{ ... }
    func Render(template []byte, t Target) ([]byte, error)
    func RenderAll(template []byte) (map[string][]byte, error)
  2. Implement the 10 renderers:

    Target Renderer Output
    claude-code RenderClaudePlugin .claude-plugin/manifest.json + CLAUDE.md
    codex RenderCodex .codex/config.toml + AGENTS.md (already exists)
    gemini RenderGemini GEMINI.md
    opencode RenderOpencode AGENTS.md (already exists) + ~/.config/opencode/plugin/ entries
    cursor RenderCursor .cursor/rules/sin-code.mdc
    windsurf RenderWindsurf .windsurf/rules/sin-code.md
    cline RenderCline .clinerules/sin-code.md
    copilot RenderCopilot .github/copilot-instructions.md (append, marker-fenced)
    agents (generic) RenderGeneric AGENTS.md (already exists)
    openclaw RenderOpenClaw ~/.openclaw/workspace/skills/sin-code/SKILL.md + marker-fenced block in SOUL.md
  3. For the Cursor .mdc format, the renderer wraps the template in a --- frontmatter with the rule's metadata (description, globs, alwaysApply). This is the format Cursor reads.

  4. For the Copilot context file, the renderer appends a marker-fenced block:

    <!-- sin-code:profile v1 begin -->
    <rendered profile>
    <!-- sin-code:profile v1 end -->
    

    The marker is the unit of idempotency (mirrors caveman's marker-fence pattern).

  5. For the Claude plugin manifest, the renderer writes a manifest.json with the plugin's name, version, and a skills array pointing at the bundled skills.

Phase 3 — CLI

  1. Add sin-code profile subcommand in cmd/sin-code/profile_cmd.go:

    • sin-code profile render --target <id> — render one target.
    • sin-code profile render --all — render every target.
    • sin-code profile render --target cursor --project /path/to/repo — render into a different repo (for projects that use SIN-Code).
    • sin-code profile render --dry-run — print to stdout instead of writing.
    • sin-code profile list — list the supported targets.
    • sin-code profile check — verify that all the per-agent files are in sync with the template (CI gate).
  2. Add sin-code profile init that creates a fresh docs/agent-profiles/sin-profile.md in the current project (for projects that want to use SIN-Code).

  3. Wire the renderer into the sin-code install command (issue feat(install): add sin-code install subcommand + 30-line curl|bash shim + PowerShell equivalent #170). The one-line installer should also render the per-agent profile files.

Phase 4 — CI

  1. Add .github/workflows/agent-profile-sync.yml (new file). On every push to main when docs/agent-profiles/sin-profile.md changes:

    • Run sin-code profile render --all --dry-run.
    • Compare the rendered output to the committed per-agent files.
    • If they differ, push a follow-up commit to main with [skip ci].
  2. Add a profile-sync check to the ci-precheck script (already exists at scripts/ci-precheck.sh). The script runs locally and fails if the profile is out of sync.

Phase 5 — Tests

  1. Unit test for each renderer with a fixture template:

    • Output contains the marker fence.
    • Output preserves code, URLs, paths.
    • Idempotent re-render produces byte-identical output.
  2. Integration test for sin-code profile check:

    • Render once, commit the output.
    • Modify the template, run check, assert it fails.
    • Re-render, run check, assert it passes.
  3. Race test: go test -race -count=1 ./cmd/sin-code/internal/profile/... must pass.

Phase 6 — Docs

  1. docs/agent-profiles.md: the template, the targets, the CI sync, the per-project init.
  2. Update AGENTS.md §6 to list the new internal/profile/ package and the sin-code profile command.
  3. Update README.md to mention the new command.
  4. Update CHANGELOG.md Unreleased section.

Acceptance Criteria

  • docs/agent-profiles/sin-profile.md exists with the v1 schema.
  • cmd/sin-code/internal/profile/render.go exists with 10 renderers.
  • sin-code profile render --all writes the per-agent files into the right paths.
  • sin-code profile check fails when the template is out of sync.
  • .github/workflows/agent-profile-sync.yml is committed and the CI step is green.
  • sin-code profile init creates a fresh template in a new project.
  • docs/agent-profiles.md exists.
  • All unit and integration tests pass with -race.
  • golangci-lint, govulncheck, gosec (SARIF) green.

Risk and Rollback

  • Risk: the per-agent files diverge from the template because a developer edits one directly. Mitigation: the profile check and the CI sync enforce it; the AGENTS.md rule "do not edit the per-agent files" is documented in the template itself.
  • Risk: Cursor's .mdc format is unstable and may change. Mitigation: the renderer reads the frontmatter schema from a separate cmd/sin-code/internal/profile/cursor.go file; if Cursor changes, we update one file.
  • Risk: a project's AGENTS.md is generated automatically and the user wants to edit it. Mitigation: the renderer is opt-in (sin-code profile render); committed files are not auto-overwritten unless the user re-runs.
  • Rollback: revert the PR. The per-agent files are new artifacts; deleting them is harmless.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions