@opensassi/opencode 0.1.3 → 0.1.5
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.
- package/AGENTS.md +3 -2
- package/dashboard/dashboard.e2e.test.ts +247 -0
- package/dashboard/dist/index.d.ts +9 -0
- package/dashboard/dist/index.js +36 -0
- package/dashboard/dist/routes/api.d.ts +2 -0
- package/dashboard/dist/routes/api.js +215 -0
- package/dashboard/dist/services/cache.d.ts +13 -0
- package/dashboard/dist/services/cache.js +29 -0
- package/dashboard/dist/services/experiments.d.ts +11 -0
- package/dashboard/dist/services/experiments.js +108 -0
- package/dashboard/dist/services/git.d.ts +12 -0
- package/dashboard/dist/services/git.js +149 -0
- package/dashboard/dist/services/sessions.d.ts +25 -0
- package/dashboard/dist/services/sessions.js +208 -0
- package/dashboard/dist/services/specs.d.ts +9 -0
- package/dashboard/dist/services/specs.js +102 -0
- package/dashboard/dist/types.d.ts +173 -0
- package/dashboard/dist/types.js +1 -0
- package/dashboard/opencode.e2e.test.ts +100 -0
- package/dashboard/playwright.config.ts +11 -0
- package/dashboard/public/app.js +961 -0
- package/dashboard/public/index.html +29 -0
- package/dashboard/public/style.css +231 -0
- package/dashboard/src/index.ts +53 -0
- package/dashboard/src/routes/api.ts +235 -0
- package/dashboard/src/services/cache.ts +38 -0
- package/dashboard/src/services/experiments.ts +117 -0
- package/dashboard/src/services/git.ts +139 -0
- package/dashboard/src/services/sessions.ts +216 -0
- package/dashboard/src/services/specs.ts +95 -0
- package/dashboard/src/types.ts +168 -0
- package/dashboard/technical-specification.md +414 -0
- package/dashboard/test-api.sh +127 -0
- package/dashboard/tsconfig.json +16 -0
- package/lib/util/paths.js +9 -1
- package/package.json +10 -1
- package/scripts/dashboard.js +17 -0
- package/scripts/generate-daily-summaries.js +190 -0
- package/skills/opensassi/SKILL.md +8 -5
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, readdirSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { join, resolve, basename } from 'node:path';
|
|
4
|
+
import { execFileSync } from 'node:child_process';
|
|
5
|
+
|
|
6
|
+
const SESSIONS_DIR = resolve(process.cwd(), 'sessions');
|
|
7
|
+
|
|
8
|
+
function parseMdField(content, label) {
|
|
9
|
+
const re = new RegExp(`\\*\\*${label}:\\*\\*\\s*(.+?)(?:\\n\\n|\\n(?=\\*\\*)|$)`, 's');
|
|
10
|
+
const m = content.match(re);
|
|
11
|
+
return m ? m[1].trim() : null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function parseDurationMinutes(content) {
|
|
15
|
+
// Try **Duration:** HH<something>
|
|
16
|
+
const durMatch = content.match(/\*\*Duration:\*\*\s*(\d+)\s*min/);
|
|
17
|
+
if (durMatch) return parseInt(durMatch[1], 10);
|
|
18
|
+
|
|
19
|
+
// Fallback: from .json.bz2 (handled below)
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function parsePrompterMinutes(content) {
|
|
24
|
+
const totalMatch = content.match(/\*\*Total:\*\*\s*([\d.]+)\s*hours?/);
|
|
25
|
+
if (totalMatch) return Math.round(parseFloat(totalMatch[1]) * 60);
|
|
26
|
+
// also try: prompter active ≈ X hours
|
|
27
|
+
const activeMatch = content.match(/prompter active\s*[≈~]?\s*([\d.]+)\s*hours?/);
|
|
28
|
+
if (activeMatch) return Math.round(parseFloat(activeMatch[1]) * 60);
|
|
29
|
+
return 30;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseSmeHours(content) {
|
|
33
|
+
const smeMatch = content.match(/\*\*Model-Equivalent SME Time Estimate:\*\*\s*(?:~|≈)?\s*([\d.]+)\s*hours?/);
|
|
34
|
+
if (smeMatch) return Math.round(parseFloat(smeMatch[1]) * 60);
|
|
35
|
+
return 60;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parseTags(content) {
|
|
39
|
+
const tagMatch = content.match(/\*\*Aggregation Tags:\*\*\s*(.+?)(?:\\n\\n|$)/s);
|
|
40
|
+
if (tagMatch) {
|
|
41
|
+
return tagMatch[1].split(',').map(t => t.trim()).filter(Boolean);
|
|
42
|
+
}
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function parseSessionId(content) {
|
|
47
|
+
return parseMdField(content, 'Session ID') || '';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function parseDescription(content) {
|
|
51
|
+
return parseMdField(content, 'Top-Level Component') || '';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function parseConfidence(/*content*/) {
|
|
55
|
+
return 'medium';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function extractDate(sessionId, filename) {
|
|
59
|
+
if (sessionId) {
|
|
60
|
+
const m = sessionId.match(/^(\d{4}-\d{2}-\d{2})/);
|
|
61
|
+
if (m) return m[1];
|
|
62
|
+
}
|
|
63
|
+
const m = filename.match(/^(\d{4}-\d{2}-\d{2})/);
|
|
64
|
+
return m ? m[1] : null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getBz2Duration(sessionDir, sessionId) {
|
|
68
|
+
if (!sessionId) return null;
|
|
69
|
+
const files = readdirSync(sessionDir);
|
|
70
|
+
const match = files.find(f => f.startsWith(sessionId) && f.endsWith('.json.bz2'));
|
|
71
|
+
if (!match) return null;
|
|
72
|
+
try {
|
|
73
|
+
const buf = execFileSync('bzcat', [join(sessionDir, match)], {
|
|
74
|
+
encoding: 'utf-8', maxBuffer: 100 * 1024 * 1024, stdio: ['pipe', 'pipe', 'ignore'],
|
|
75
|
+
});
|
|
76
|
+
const d = JSON.parse(buf);
|
|
77
|
+
const info = d.info || {};
|
|
78
|
+
const t = info.time || {};
|
|
79
|
+
const created = t.created || 0;
|
|
80
|
+
const updated = t.updated || 0;
|
|
81
|
+
if (updated && created) return Math.round((updated - created) / 60000);
|
|
82
|
+
return null;
|
|
83
|
+
} catch { return null; }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function main() {
|
|
87
|
+
if (!existsSync(SESSIONS_DIR)) {
|
|
88
|
+
console.error(`Sessions directory not found: ${SESSIONS_DIR}`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const files = readdirSync(SESSIONS_DIR);
|
|
93
|
+
const mdFiles = files.filter(f => f.endsWith('.md') && !f.endsWith('.spec.md') && f !== 'README.md' && f !== 'export-session.sh');
|
|
94
|
+
|
|
95
|
+
// Group sessions by date
|
|
96
|
+
const byDate = {};
|
|
97
|
+
|
|
98
|
+
for (const mdFile of mdFiles) {
|
|
99
|
+
const content = readFileSync(join(SESSIONS_DIR, mdFile), 'utf-8');
|
|
100
|
+
const sessionId = parseSessionId(content);
|
|
101
|
+
const date = extractDate(sessionId, mdFile);
|
|
102
|
+
if (!date) { console.error(`Could not extract date from ${mdFile}`); continue; }
|
|
103
|
+
|
|
104
|
+
const prompterMin = parsePrompterMinutes(content);
|
|
105
|
+
const smeMin = parseSmeHours(content);
|
|
106
|
+
const bz2Duration = getBz2Duration(SESSIONS_DIR, sessionId);
|
|
107
|
+
const durationMin = bz2Duration || prompterMin || parseDurationMinutes(content);
|
|
108
|
+
const tags = parseTags(content);
|
|
109
|
+
const description = parseDescription(content);
|
|
110
|
+
const confidence = parseConfidence(content);
|
|
111
|
+
|
|
112
|
+
if (!byDate[date]) byDate[date] = [];
|
|
113
|
+
byDate[date].push({
|
|
114
|
+
session_id: sessionId || mdFile.replace(/\.md$/, ''),
|
|
115
|
+
duration_minutes: durationMin,
|
|
116
|
+
prompter_time_minutes: Math.min(prompterMin, durationMin || prompterMin),
|
|
117
|
+
sme_time_minutes: smeMin,
|
|
118
|
+
top_component_summary: description,
|
|
119
|
+
tags,
|
|
120
|
+
human_confidence: confidence,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Generate daily files
|
|
125
|
+
const dailyDir = join(SESSIONS_DIR, 'daily');
|
|
126
|
+
if (!existsSync(dailyDir)) {
|
|
127
|
+
mkdirSync(dailyDir, { recursive: true });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for (const [date, sessions] of Object.entries(byDate).sort()) {
|
|
131
|
+
let totalPrompter = 0, totalSme = 0;
|
|
132
|
+
for (const s of sessions) {
|
|
133
|
+
totalPrompter += s.prompter_time_minutes;
|
|
134
|
+
totalSme += s.sme_time_minutes;
|
|
135
|
+
}
|
|
136
|
+
const totalPrompterHrs = Math.round((totalPrompter / 60) * 10) / 10;
|
|
137
|
+
const totalSmeHrs = Math.round((totalSme / 60) * 10) / 10;
|
|
138
|
+
|
|
139
|
+
// Compute per-tag aggregates
|
|
140
|
+
const tagMap = {};
|
|
141
|
+
for (const s of sessions) {
|
|
142
|
+
const perTagSme = s.sme_time_minutes / (s.tags.length || 1);
|
|
143
|
+
const perTagPrompter = s.prompter_time_minutes / (s.tags.length || 1);
|
|
144
|
+
for (const tag of s.tags) {
|
|
145
|
+
if (!tagMap[tag]) tagMap[tag] = { prompter: 0, sme: 0 };
|
|
146
|
+
tagMap[tag].prompter += perTagPrompter;
|
|
147
|
+
tagMap[tag].sme += perTagSme;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const topSubjectAreas = Object.entries(tagMap)
|
|
151
|
+
.map(([name, v]) => ({
|
|
152
|
+
name,
|
|
153
|
+
prompter_time_hours: Math.round((v.prompter / 60) * 100) / 100,
|
|
154
|
+
sme_time_hours: Math.round((v.sme / 60) * 100) / 100,
|
|
155
|
+
ai_multiplier: v.prompter > 0 ? Math.round((v.sme / v.prompter) * 10) / 10 : 0,
|
|
156
|
+
}))
|
|
157
|
+
.sort((a, b) => b.sme_time_hours - a.sme_time_hours);
|
|
158
|
+
|
|
159
|
+
const aiMultiplier = totalPrompterHrs > 0
|
|
160
|
+
? Math.round((totalSmeHrs / totalPrompterHrs) * 10) / 10
|
|
161
|
+
: 0;
|
|
162
|
+
|
|
163
|
+
const daily = {
|
|
164
|
+
dashboard: {
|
|
165
|
+
metadata: {
|
|
166
|
+
generated_at: new Date().toISOString(),
|
|
167
|
+
audited: false,
|
|
168
|
+
audit_note: 'Auto-generated from session evaluation files',
|
|
169
|
+
},
|
|
170
|
+
daily_summary: {
|
|
171
|
+
date,
|
|
172
|
+
total_prompter_time_hours: totalPrompterHrs,
|
|
173
|
+
total_sme_time_hours: totalSmeHrs,
|
|
174
|
+
ai_multiplier: aiMultiplier,
|
|
175
|
+
total_sessions: sessions.length,
|
|
176
|
+
top_subject_areas: topSubjectAreas,
|
|
177
|
+
},
|
|
178
|
+
session_breakdown: sessions,
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const outPath = join(dailyDir, `${date}.json`);
|
|
183
|
+
writeFileSync(outPath, JSON.stringify(daily, null, 2) + '\n');
|
|
184
|
+
console.log(`Wrote ${outPath} (${sessions.length} sessions, ${totalPrompterHrs}h prompter, ${totalSmeHrs}h SME)`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
console.log(`\nDone. Generated ${Object.keys(byDate).length} daily summaries.`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
main().catch(e => { console.error(e); process.exit(1); });
|
|
@@ -5,13 +5,16 @@ description: Root skill ecosystem — loads system-design + spec tree, routes su
|
|
|
5
5
|
|
|
6
6
|
# Skill: opensassi — Root Skill Ecosystem
|
|
7
7
|
|
|
8
|
+
> **Invocation note:** Within this project, use `npm run opencode -- <cmd>`.
|
|
9
|
+
> Consumers of the published `@opensassi/opencode` package use `npx @opensassi/opencode <cmd>` instead.
|
|
10
|
+
|
|
8
11
|
## Entry Point
|
|
9
12
|
|
|
10
13
|
| Input | Action |
|
|
11
14
|
|-------|--------|
|
|
12
15
|
| `/opensassi` | Load `skill system-design`, read `technical-specification.md` + spec tree depth 2 (root + facade specs). Report ready. |
|
|
13
16
|
| `/opensassi init` | Run `env-check.sh`. Parse JSON result: if node+git+FlameGraph+deps all present → "Already bootstrapped". Otherwise run full bootstrap sequence (env-check → install → flamegraph → npm-deps → gitignore). |
|
|
14
|
-
| `/opensassi <skill> <command> [args]` | Load `<skill>` from npm via `
|
|
17
|
+
| `/opensassi <skill> <command> [args]` | Load `<skill>` from npm via `npm run opencode -- <skill>`, then run `<command>` with `[args]`. Return result. |
|
|
15
18
|
|
|
16
19
|
### Spec tree depth
|
|
17
20
|
|
|
@@ -23,7 +26,7 @@ Depth is controlled by `--depth` flag on `load spec`:
|
|
|
23
26
|
|
|
24
27
|
## Init
|
|
25
28
|
|
|
26
|
-
Single shell command: `
|
|
29
|
+
Single shell command: `npm run opencode -- run --skill opensassi env-check.sh`
|
|
27
30
|
|
|
28
31
|
Returns JSON: `{"os": ..., "distro": ..., "node_version": ..., "git_version": ..., ...}`
|
|
29
32
|
|
|
@@ -37,11 +40,11 @@ bootstrapped = (node_version != "" && git_version != ""
|
|
|
37
40
|
If bootstrapped → report "Environment ready." + show node/git versions.
|
|
38
41
|
If not → run full bootstrap:
|
|
39
42
|
|
|
40
|
-
1. `
|
|
43
|
+
1. `npm run opencode -- run --skill opensassi env-check.sh` — install git + Node.js LTS if missing, write `.nvmrc`
|
|
41
44
|
2. `init install` — run platform-specific installer (cmake, nasm, gdb, ripgrep, perf, htop, etc.) or report none found
|
|
42
45
|
3. `init flamegraph` — clone FlameGraph v1.0 to `scripts/FlameGraph/`
|
|
43
|
-
4. `
|
|
44
|
-
5. `
|
|
46
|
+
4. `npm run opencode -- run --skill opensassi install-npm-deps.sh` — `npm install`
|
|
47
|
+
5. `npm run opencode -- run --skill opensassi ensure-gitignore.sh` — append common patterns
|
|
45
48
|
|
|
46
49
|
## Lexicon
|
|
47
50
|
|