@opensassi/opencode 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/AGENTS.md +35 -0
  2. package/README.md +81 -0
  3. package/bin/opencode.js +3 -0
  4. package/lib/cli.js +38 -0
  5. package/lib/commands/init.js +117 -0
  6. package/lib/commands/print-agents.js +6 -0
  7. package/lib/commands/print-skill.js +8 -0
  8. package/lib/commands/run.js +57 -0
  9. package/lib/index.js +4 -0
  10. package/lib/util/paths.js +21 -0
  11. package/package.json +40 -0
  12. package/scripts/asm-optimizer/run-baseline.sh +158 -0
  13. package/scripts/check-artifacts.js +131 -0
  14. package/scripts/extract-artifacts.js +204 -0
  15. package/scripts/install/linux/ubuntu-noble-24.04/install.sh +94 -0
  16. package/scripts/install/osx/macos-sequoia-15.0/install.sh +115 -0
  17. package/scripts/install/windows/wsl2/install.ps1 +98 -0
  18. package/scripts/install.ps1 +32 -0
  19. package/scripts/install.sh +83 -0
  20. package/scripts/puppeteer-config.json +3 -0
  21. package/scripts/test-artifacts.js +346 -0
  22. package/scripts/validate-all.js +18 -0
  23. package/scripts/verify-artifact.js +157 -0
  24. package/skills/asm-optimizer/SKILL.md +295 -0
  25. package/skills/daily-evaluation/SKILL.md +86 -0
  26. package/skills/git/SKILL.md +100 -0
  27. package/skills/issue/SKILL.md +104 -0
  28. package/skills/npm-optimizer/SKILL.md +218 -0
  29. package/skills/opensassi/SKILL.md +77 -0
  30. package/skills/opensassi/scripts/ensure-gitignore.sh +89 -0
  31. package/skills/opensassi/scripts/env-check.ps1 +139 -0
  32. package/skills/opensassi/scripts/env-check.sh +200 -0
  33. package/skills/opensassi/scripts/install-flamegraph.sh +32 -0
  34. package/skills/opensassi/scripts/install-npm-deps.sh +25 -0
  35. package/skills/profiler/SKILL.md +213 -0
  36. package/skills/profiler/scripts/benchmark.sh +63 -0
  37. package/skills/profiler/scripts/common.sh +55 -0
  38. package/skills/profiler/scripts/compare.sh +63 -0
  39. package/skills/profiler/scripts/profile.sh +63 -0
  40. package/skills/profiler/scripts/setup.sh +32 -0
  41. package/skills/session-evaluation/SKILL.md +128 -0
  42. package/skills/skill-manager/SKILL.md +251 -0
  43. package/skills/system-design/SKILL.md +558 -0
  44. package/skills/system-design-review/SKILL.md +396 -0
  45. package/skills/todo/SKILL.md +165 -0
  46. package/skills-index.json +137 -0
package/AGENTS.md ADDED
@@ -0,0 +1,35 @@
1
+ # opencode Agent Instructions — @opensassi/opencode
2
+
3
+ This project uses the **@opensassi/opencode** skill pack.
4
+ All skills, scripts, and tooling are delivered via the npm package.
5
+
6
+ ## Available Skills
7
+
8
+ | `skill` | Use case |
9
+ |---------|----------|
10
+ | `asm-optimizer` | SIMD/assembly optimization framework |
11
+ | `daily-evaluation` | Aggregate session evaluations into dashboards |
12
+ | `git` | Rebase-based single-commit-per-session workflow |
13
+ | `issue` | GitHub issue management |
14
+ | `npm-optimizer` | Port an npm package to a C++ native addon |
15
+ | `opensassi` | Bootstrap a new project environment |
16
+ | `profiler` | Linux perf profiling + flamegraphs |
17
+ | `session-evaluation` | Generate structured session reports |
18
+ | `skill-manager` | Create/revise skills interactively |
19
+ | `system-design` | Interactive C++ spec authoring with diagrams |
20
+ | `system-design-review` | Seven-expert panel audit of technical specs |
21
+ | `todo` | Create issues + debugging skills from session context |
22
+
23
+ ## Workflow
24
+
25
+ 1. `skill opensassi` — Load the bootstrap skill. It exposes the full skills-index as a reference table.
26
+ 2. Run `npx @opensassi/opencode <skill-name>` to load any sub-skill. The agent reads the output as the skill's full instructions.
27
+ 3. Use the skill's commands. Scripts are run via `npx @opensassi/opencode run <path>` or `npx @opensassi/opencode run --skill <name> <path>`.
28
+
29
+ ## Design Constraints
30
+
31
+ - No commits during development — all changes staged at finish session time
32
+ - Single atomic commit per session
33
+ - Full test suite after every rebase
34
+ - Session evaluation is read-only (generate) / write-once (export)
35
+ - All skills, scripts, and AGENTS.md live in the npm package, not in the project
package/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # @opensassi/opencode
2
+
3
+ Agent skill harness for AI-assisted software development. Delivers 12 domain-specific skills (system-design, git workflow, profiling, etc.) and supporting scripts as a standalone npm CLI package.
4
+
5
+ ```
6
+ npx @opensassi/opencode init
7
+ ```
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install --dev @opensassi/opencode
13
+ ```
14
+
15
+ Then bootstrap a project to make its skills available to opencode:
16
+
17
+ ```bash
18
+ npx opencode init
19
+ ```
20
+
21
+ This writes three files to your project:
22
+ - **`AGENTS.md`** — appends full opensassi agent instructions
23
+ - **`.opencode/skills/opensassi/SKILL.md`** — bootstrap skill (the only skill on disk)
24
+ - **`.opencode/opencode.json`** — permission rules + MCP server config
25
+
26
+ All other skills (system-design, git, profiler, etc.) are loaded at runtime via the CLI.
27
+
28
+ ## CLI Commands
29
+
30
+ ```
31
+ npx @opensassi/opencode init # Bootstrap project
32
+ npx @opensassi/opencode <skill-name> # Print skill instructions to stdout
33
+ npx @opensassi/opencode run <path> [args...] # Run a script from the package
34
+ npx @opensassi/opencode run --skill <n> <path> # Run a skill-specific script
35
+ npx @opensassi/opencode help # Show help
36
+ ```
37
+
38
+ ## Skills
39
+
40
+ | Skill | Purpose |
41
+ |-------|---------|
42
+ | `asm-optimizer` | SIMD/assembly optimization framework |
43
+ | `daily-evaluation` | Aggregate session evaluations into dashboards |
44
+ | `git` | Rebase-based single-commit-per-session workflow |
45
+ | `issue` | GitHub issue management |
46
+ | `npm-optimizer` | Port an npm package to a C++ native addon |
47
+ | `opensassi` | Bootstrap a new project environment |
48
+ | `profiler` | Linux perf profiling + flamegraphs |
49
+ | `session-evaluation` | Generate structured session reports |
50
+ | `skill-manager` | Create/revise skills interactively |
51
+ | `system-design` | Interactive C++ spec authoring with diagrams |
52
+ | `system-design-review` | Seven-expert panel audit of technical specs |
53
+ | `todo` | Create issues + debugging skills from session context |
54
+
55
+ ## Package Contents
56
+
57
+ | Directory | Contents |
58
+ |-----------|----------|
59
+ | `bin/` | CLI entry point (`opencode` binary) |
60
+ | `lib/` | Programmatic API + command implementations |
61
+ | `skills/` | 12 skill definitions (SKILL.md) + skill scripts |
62
+ | `scripts/` | Artifact pipeline (extract, test, verify, check) + installers |
63
+ | `AGENTS.md` | Agent harness instructions (appended by init) |
64
+ | `skills-index.json` | Pre-built static skill/command index |
65
+
66
+ ## Development
67
+
68
+ ```bash
69
+ git clone git@github.com:opensassi/opencode.git
70
+ cd opencode
71
+ node bin/opencode.js help
72
+ npm pack --dry-run # Review package contents
73
+ npm run validate-all # Full artifact pipeline test
74
+ ```
75
+
76
+ ## Design
77
+
78
+ - **Minimal filesystem footprint** — `init` writes only 3 files. All sub-skills load from the npm package at runtime.
79
+ - **opencode-agnostic** — The CLI works stand-alone. Skills are consumed by opencode but the package doesn't depend on it.
80
+ - **Runtime skill loading** — Sub-skills are never on disk; the agent loads them by running `npx @opensassi/opencode <name>`.
81
+ - **Programmatic API** — `import { init, run, printSkill } from '@opensassi/opencode'`
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { cli } from '../lib/cli.js'
3
+ process.exit(await cli(process.argv.slice(2)))
package/lib/cli.js ADDED
@@ -0,0 +1,38 @@
1
+ import { initCommand } from './commands/init.js'
2
+ import { printSkill } from './commands/print-skill.js'
3
+ import { runCommand } from './commands/run.js'
4
+
5
+ export async function cli(args) {
6
+ if (args.length === 0 || args[0] === '--help' || args[0] === 'help') {
7
+ console.log(`Usage: opencode <command> [options]
8
+
9
+ Commands:
10
+ init Bootstrap this directory with opensassi skills
11
+ run <path> [args...] Run a script from the package scripts/ directory
12
+ run --skill <name> <path> Run a script from a skill's scripts/ directory
13
+ <skill-name> Print a skill's SKILL.md content to stdout
14
+ <skill-name> --command="..." Execute a skill command
15
+ help Show this help`)
16
+ return 0
17
+ }
18
+
19
+ const cmd = args[0]
20
+
21
+ if (cmd === 'init') {
22
+ await initCommand(args.slice(1))
23
+ return 0
24
+ }
25
+
26
+ if (cmd === 'run') {
27
+ const code = await runCommand(args.slice(1))
28
+ return code
29
+ }
30
+
31
+ const skillContent = await printSkill(cmd)
32
+ if (skillContent === null) {
33
+ console.error(`Unknown command or skill: ${cmd}`)
34
+ return 1
35
+ }
36
+ console.log(skillContent)
37
+ return 0
38
+ }
@@ -0,0 +1,117 @@
1
+ import { readFileSync, existsSync, mkdirSync, writeFileSync, appendFileSync } from 'node:fs'
2
+ import { resolve } from 'node:path'
3
+ import { resolveAgents, resolveSkill, PKG_ROOT } from '../util/paths.js'
4
+
5
+ function readPackageAgents() {
6
+ return readFileSync(resolveAgents(), 'utf-8')
7
+ }
8
+
9
+ function readPackageOpensassiSkill() {
10
+ return readFileSync(resolveSkill('opensassi'), 'utf-8')
11
+ }
12
+
13
+ function defaultOpencodeJson() {
14
+ return JSON.stringify({
15
+ $schema: "https://opencode.ai/config.json",
16
+ permission: {
17
+ skill: "allow",
18
+ read: "allow",
19
+ edit: "allow",
20
+ glob: "allow",
21
+ grep: "allow",
22
+ bash: "ask",
23
+ task: "allow",
24
+ webfetch: "allow"
25
+ },
26
+ instructions: ["AGENTS.md"],
27
+ mcp: {
28
+ playwright: {
29
+ type: "local",
30
+ command: ["npx", "@playwright/mcp@latest", "--headless"],
31
+ enabled: true
32
+ },
33
+ debugger: {
34
+ type: "local",
35
+ command: ["gdb-mcp-server"],
36
+ enabled: true
37
+ }
38
+ }
39
+ }, null, 2) + '\n'
40
+ }
41
+
42
+ export async function initCommand(args) {
43
+ const cwd = process.cwd()
44
+ const dryRun = args.includes('--dry-run')
45
+ const force = args.includes('--force')
46
+
47
+ const hasAgents = existsSync(resolve(cwd, 'AGENTS.md'))
48
+ const hasOpencodeDir = existsSync(resolve(cwd, '.opencode'))
49
+ const hasOpencodeJson = existsSync(resolve(cwd, '.opencode/opencode.json'))
50
+ const hasOpensassiSkill = existsSync(resolve(cwd, '.opencode/skills/opensassi/SKILL.md'))
51
+ const hasGitignore = existsSync(resolve(cwd, '.gitignore'))
52
+
53
+ if (hasOpensassiSkill && !force) {
54
+ console.log('Already initialized. Use --force to refresh.')
55
+ return
56
+ }
57
+
58
+ const pkgAgents = readPackageAgents()
59
+ const pkgSkill = readPackageOpensassiSkill()
60
+
61
+ if (dryRun) {
62
+ console.log('Dry-run: would write the following files')
63
+ if (!hasAgents) console.log(' CREATE AGENTS.md (from package)')
64
+ else console.log(' APPEND AGENTS.md (append opensassi instructions)')
65
+ console.log(' WRITE .opencode/skills/opensassi/SKILL.md')
66
+ console.log(' WRITE .opencode/opencode.json')
67
+ if (!hasGitignore) console.log(' CREATE .gitignore')
68
+ console.log(' APPEND .gitignore (.opencode/skills/)')
69
+ return
70
+ }
71
+
72
+ const separator = '\n---\n\n'
73
+ const agentHeader = 'Instructions from: @opensassi/opencode\n\n'
74
+
75
+ if (!hasAgents) {
76
+ writeFileSync(resolve(cwd, 'AGENTS.md'), pkgAgents, 'utf-8')
77
+ console.log('Created AGENTS.md')
78
+ } else {
79
+ const existing = readFileSync(resolve(cwd, 'AGENTS.md'), 'utf-8')
80
+ if (!existing.includes('@opensassi/opencode')) {
81
+ appendFileSync(resolve(cwd, 'AGENTS.md'), separator + agentHeader + pkgAgents, 'utf-8')
82
+ console.log('Appended opensassi instructions to AGENTS.md')
83
+ } else {
84
+ console.log('AGENTS.md already includes opensassi instructions')
85
+ }
86
+ }
87
+
88
+ {
89
+ const skillDir = resolve(cwd, '.opencode/skills/opensassi')
90
+ if (!existsSync(skillDir)) mkdirSync(skillDir, { recursive: true })
91
+ writeFileSync(resolve(skillDir, 'SKILL.md'), pkgSkill, 'utf-8')
92
+ console.log('Wrote .opencode/skills/opensassi/SKILL.md')
93
+ }
94
+
95
+ if (!hasOpencodeJson) {
96
+ const opencodeDir = resolve(cwd, '.opencode')
97
+ if (!existsSync(opencodeDir)) mkdirSync(opencodeDir, { recursive: true })
98
+ writeFileSync(resolve(opencodeDir, 'opencode.json'), defaultOpencodeJson(), 'utf-8')
99
+ console.log('Wrote .opencode/opencode.json')
100
+ } else {
101
+ console.log('.opencode/opencode.json already exists (skipped)')
102
+ }
103
+
104
+ const gitignorePath = resolve(cwd, '.gitignore')
105
+ if (!hasGitignore) {
106
+ writeFileSync(gitignorePath, '.opencode/skills/\n', 'utf-8')
107
+ console.log('Created .gitignore with .opencode/skills/ rule')
108
+ } else {
109
+ const gitignore = readFileSync(gitignorePath, 'utf-8')
110
+ if (!gitignore.includes('.opencode/skills/')) {
111
+ appendFileSync(gitignorePath, '\n.opencode/skills/\n', 'utf-8')
112
+ console.log('Added .opencode/skills/ to .gitignore')
113
+ } else {
114
+ console.log('.opencode/skills/ already in .gitignore')
115
+ }
116
+ }
117
+ }
@@ -0,0 +1,6 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { resolveAgents } from '../util/paths.js'
3
+
4
+ export async function printAgents() {
5
+ return readFileSync(resolveAgents(), 'utf-8')
6
+ }
@@ -0,0 +1,8 @@
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+ import { resolveSkill } from '../util/paths.js'
3
+
4
+ export async function printSkill(name) {
5
+ const path = resolveSkill(name)
6
+ if (!existsSync(path)) return null
7
+ return readFileSync(path, 'utf-8')
8
+ }
@@ -0,0 +1,57 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { spawn } from 'node:child_process'
3
+ import { resolveSkillScript, resolveScript, PKG_ROOT } from '../util/paths.js'
4
+
5
+ export async function runCommand(args) {
6
+ const skillIdx = args.indexOf('--skill')
7
+ let resolvedPath
8
+ let restArgs
9
+
10
+ if (skillIdx !== -1) {
11
+ const skillName = args[skillIdx + 1]
12
+ if (!skillName) {
13
+ console.error('--skill requires a name argument')
14
+ return 1
15
+ }
16
+ const scriptPath = args[skillIdx + 2]
17
+ if (!scriptPath) {
18
+ console.error('--skill requires a script path after the skill name')
19
+ return 1
20
+ }
21
+ resolvedPath = resolveSkillScript(skillName, scriptPath)
22
+ restArgs = args.slice(skillIdx + 3)
23
+ } else {
24
+ const scriptPath = args[0]
25
+ if (!scriptPath) {
26
+ console.error('Usage: opencode run <script> [args...]')
27
+ return 1
28
+ }
29
+ resolvedPath = resolveScript(scriptPath)
30
+ restArgs = args.slice(1)
31
+ }
32
+
33
+ if (!existsSync(resolvedPath)) {
34
+ console.error(`Script not found: ${resolvedPath}`)
35
+ return 1
36
+ }
37
+
38
+ const ext = resolvedPath.split('.').pop()
39
+
40
+ return new Promise((resolve) => {
41
+ let proc
42
+ if (ext === 'sh' || ext === 'bash') {
43
+ proc = spawn('bash', [resolvedPath, ...restArgs], { stdio: 'inherit' })
44
+ } else if (ext === 'ps1') {
45
+ proc = spawn('powershell', ['-File', resolvedPath, ...restArgs], { stdio: 'inherit' })
46
+ } else if (ext === 'js' || ext === 'mjs') {
47
+ proc = spawn(process.execPath, [resolvedPath, ...restArgs], { stdio: 'inherit' })
48
+ } else {
49
+ proc = spawn(resolvedPath, restArgs, { stdio: 'inherit' })
50
+ }
51
+ proc.on('exit', (code) => resolve(code ?? 1))
52
+ proc.on('error', (err) => {
53
+ console.error(`Failed to run script: ${err.message}`)
54
+ resolve(1)
55
+ })
56
+ })
57
+ }
package/lib/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { init } from './commands/init.js'
2
+ export { run } from './commands/run.js'
3
+ export { printSkill } from './commands/print-skill.js'
4
+ export { printAgents } from './commands/print-agents.js'
@@ -0,0 +1,21 @@
1
+ import { fileURLToPath } from 'node:url'
2
+ import { dirname, resolve } from 'node:path'
3
+
4
+ const __dirname = dirname(fileURLToPath(import.meta.url))
5
+ export const PKG_ROOT = resolve(__dirname, '../..')
6
+
7
+ export function resolveScript(path) {
8
+ return resolve(PKG_ROOT, 'scripts', path)
9
+ }
10
+
11
+ export function resolveSkillScript(skillName, path) {
12
+ return resolve(PKG_ROOT, 'skills', skillName, 'scripts', path)
13
+ }
14
+
15
+ export function resolveSkill(skillName) {
16
+ return resolve(PKG_ROOT, 'skills', skillName, 'SKILL.md')
17
+ }
18
+
19
+ export function resolveAgents() {
20
+ return resolve(PKG_ROOT, 'AGENTS.md')
21
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@opensassi/opencode",
3
+ "version": "0.1.0",
4
+ "description": "Agent skill harness for opencode — bootstrap, system-design, git workflow, profiling, and more",
5
+ "type": "module",
6
+ "bin": {
7
+ "opencode": "bin/opencode.js"
8
+ },
9
+ "main": "lib/index.js",
10
+ "exports": {
11
+ ".": "./lib/index.js"
12
+ },
13
+ "files": [
14
+ "bin/",
15
+ "lib/",
16
+ "skills/",
17
+ "scripts/",
18
+ "AGENTS.md",
19
+ "skills-index.json",
20
+ "!scripts/FlameGraph/",
21
+ "!**/*.spec.md"
22
+ ],
23
+ "scripts": {
24
+ "extract": "node scripts/extract-artifacts.js",
25
+ "extract:file": "node scripts/extract-artifacts.js --file",
26
+ "test-artifacts": "node scripts/test-artifacts.js",
27
+ "test-artifact": "node scripts/test-artifacts.js --file",
28
+ "validate-all": "node scripts/extract-artifacts.js --all && node scripts/test-artifacts.js",
29
+ "verify-animation": "node scripts/verify-artifact.js",
30
+ "check-artifacts": "node scripts/check-artifacts.js",
31
+ "start": "bin/opencode.js"
32
+ },
33
+ "engines": {
34
+ "node": ">=22"
35
+ },
36
+ "devDependencies": {
37
+ "playwright": "^1.60.0",
38
+ "sharp": "^0.34.5"
39
+ }
40
+ }
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # run-baseline.sh
5
+ # Build a tagged release baseline and run the profiling matrix.
6
+ #
7
+ # USAGE: Customize the variables below for your project, then run:
8
+ # ./scripts/asm-optimizer/run-baseline.sh [--rebuild]
9
+ #
10
+ # --rebuild Force rebuild even if baseline binaries exist
11
+
12
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
13
+ REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
14
+ BASELINE_DIR="$REPO_ROOT/perf/baseline"
15
+
16
+ # === CUSTOMIZE THESE FOR YOUR PROJECT ===
17
+ PROJECT_NAME="my-project"
18
+ TAG="v1.0.0"
19
+ BASELINE_SRC="$BASELINE_DIR/$PROJECT_NAME-$TAG"
20
+ BASELINE_BUILD="$BASELINE_DIR/build"
21
+ BASELINE_BIN="$BASELINE_SRC/build"
22
+ PROFILES_DIR="$BASELINE_DIR/profiles/default"
23
+ REPORTS_DIR="$BASELINE_DIR/reports"
24
+ TEST_INPUT="$REPO_ROOT/test/data/input.yuv"
25
+ PROGRAM_ARGS=("--input" "$TEST_INPUT" "--frames" "50")
26
+ # =========================================
27
+
28
+ # Output tree:
29
+ # perf/baseline/
30
+ # ├── <project>-<tag>/ ← git worktree checkout
31
+ # ├── build/ ← Release cmake build
32
+ # ├── profiles/default/
33
+ # │ ├── fast-5fr/
34
+ # │ ├── fast-50fr/
35
+ # │ ├── slow-5fr/
36
+ # │ └── slow-50fr/
37
+ # └── reports/
38
+ # └── profile-summary.json
39
+
40
+ # Perf counter groups
41
+ PERF_COMPREHENSIVE="-d -d -d"
42
+ PERF_SIMD="-e fp_arith_inst_retired.256b_packed_single,fp_arith_inst_retired.128b_packed_single,fp_arith_inst_retired.scalar_single"
43
+ PERF_MEM_UOP="-e mem_load_uops_retired.l1_hit,mem_load_uops_retired.l1_miss,mem_load_uops_retired.l2_hit,mem_load_uops_retired.l2_miss,mem_load_uops_retired.l3_hit,mem_load_uops_retired_misc.l3_miss"
44
+
45
+ run_perf_pass() {
46
+ local label="$1"
47
+ local config="$2"
48
+ local frames="$3"
49
+ local events="$4"
50
+ local suffix="$5"
51
+
52
+ local outdir="$PROFILES_DIR/$label"
53
+ mkdir -p "$outdir"
54
+ local output="$outdir/perf-${suffix}.txt"
55
+
56
+ echo " [perf:$suffix] $config ${frames}fr ..."
57
+ perf stat $events -o "$output" \
58
+ "$BASELINE_BIN/$PROJECT_NAME" \
59
+ "${PROGRAM_ARGS[@]}" \
60
+ --config "$config" \
61
+ --frames "$frames" \
62
+ 2>/dev/null || true
63
+
64
+ if [ -f "$output" ]; then
65
+ sed -i "1i# RUN: config=$config frames=$frames date=$(date -Iseconds)" "$output"
66
+ fi
67
+ }
68
+
69
+ run_timing_pass() {
70
+ local label="$1"
71
+ local config="$2"
72
+ local frames="$3"
73
+
74
+ local outdir="$PROFILES_DIR/$label"
75
+ mkdir -p "$outdir"
76
+ local output="$outdir/timing.txt"
77
+
78
+ echo " [timing] $config ${frames}fr ..."
79
+ for i in 1 2 3; do
80
+ /usr/bin/time -f "real %e user %U sys %S" -a -o "$output" \
81
+ "$BASELINE_BIN/$PROJECT_NAME" \
82
+ "${PROGRAM_ARGS[@]}" \
83
+ --config "$config" \
84
+ --frames "$frames" \
85
+ 2>/dev/null
86
+ done
87
+
88
+ sed -i "1i# RUN: config=$config frames=$frames date=$(date -Iseconds)" "$output"
89
+ }
90
+
91
+ # ----------------------------------------------------------------
92
+
93
+ step() { echo ""; echo "==> $1"; }
94
+
95
+ step "1. Create baseline directory structure"
96
+ mkdir -p "$BASELINE_DIR" "$PROFILES_DIR" "$REPORTS_DIR"
97
+
98
+ step "2. Checkout $TAG via git worktree"
99
+ if [ ! -d "$BASELINE_SRC" ] || [ ! -f "$BASELINE_SRC/CMakeLists.txt" ]; then
100
+ git -C "$REPO_ROOT" worktree add -f "$BASELINE_SRC" "$TAG"
101
+ echo " checked out $TAG at $BASELINE_SRC"
102
+ else
103
+ echo " source already exists"
104
+ fi
105
+
106
+ step "3. Build Release binary"
107
+ if [ ! -f "$BASELINE_BIN/$PROJECT_NAME" ] || [ "${1:-}" == "--rebuild" ]; then
108
+ cmake -S "$BASELINE_SRC" -B "$BASELINE_BUILD" \
109
+ -DCMAKE_BUILD_TYPE=Release
110
+ cmake --build "$BASELINE_BUILD" -j "$(nproc)"
111
+ echo " build complete: $BASELINE_BIN/$PROJECT_NAME"
112
+ else
113
+ echo " binary already exists"
114
+ fi
115
+
116
+ # Verify binary
117
+ "$BASELINE_BIN/$PROJECT_NAME" --version 2>/dev/null || echo " (no --version flag)"
118
+
119
+ step "4. Run profiling matrix"
120
+ CONFIGS=("fast" "slow")
121
+ FRAME_COUNTS=(5 50)
122
+
123
+ for config in "${CONFIGS[@]}"; do
124
+ for frames in "${FRAME_COUNTS[@]}"; do
125
+ label="${config}-${frames}fr"
126
+ echo "--- Profiling $label ---"
127
+
128
+ run_perf_pass "$label" "$config" "$frames" "$PERF_COMPREHENSIVE" "comprehensive"
129
+ run_perf_pass "$label" "$config" "$frames" "$PERF_SIMD" "simd"
130
+ run_perf_pass "$label" "$config" "$frames" "$PERF_MEM_UOP" "mem-uop"
131
+ run_timing_pass "$label" "$config" "$frames"
132
+ done
133
+ done
134
+
135
+ step "5. Generate summary report"
136
+ cat > "$REPORTS_DIR/profile-summary.json" << REPORT_EOF
137
+ {
138
+ "baseline": "$TAG",
139
+ "date": "REPLACE_DATE",
140
+ "configs": ["fast", "slow"],
141
+ "frames": [5, 50],
142
+ "counter_groups": [
143
+ "comprehensive", "simd", "mem-uop"
144
+ ]
145
+ }
146
+ REPORT_EOF
147
+
148
+ sed -i "s/REPLACE_DATE/$(date -Iseconds)/" "$REPORTS_DIR/profile-summary.json"
149
+
150
+ echo ""
151
+ echo "=========================================="
152
+ echo "Baseline setup complete!"
153
+ echo " Source: $BASELINE_SRC"
154
+ echo " Build: $BASELINE_BUILD"
155
+ echo " Binary: $BASELINE_BIN/$PROJECT_NAME"
156
+ echo " Profiles: $PROFILES_DIR/"
157
+ echo " Report: $REPORTS_DIR/profile-summary.json"
158
+ echo "=========================================="