monomind 2.0.0 → 2.0.2
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/README.md +11 -12
- package/package.json +2 -2
- package/packages/@monomind/cli/.claude/helpers/token-tracker.cjs +10 -0
- package/packages/@monomind/cli/README.md +11 -12
- package/packages/@monomind/cli/dist/src/browser/dashboard/server.js +71 -12
- package/packages/@monomind/cli/dist/src/browser/dashboard/ui.html +4 -4
- package/packages/@monomind/cli/dist/src/commands/browse-workflow.js +2 -2
- package/packages/@monomind/cli/dist/src/index.js +8 -7
- package/packages/@monomind/cli/dist/src/mcp-tools/coherence/types.d.ts +18 -18
- package/packages/@monomind/cli/dist/src/mcp-tools/quality/coverage-analysis/prioritize-gaps.d.ts +12 -12
- package/packages/@monomind/cli/dist/src/mcp-tools/quality/security-compliance/detect-secrets.d.ts +4 -4
- package/packages/@monomind/cli/dist/src/orgrt/bus.d.ts +23 -0
- package/packages/@monomind/cli/dist/src/orgrt/bus.js +64 -0
- package/packages/@monomind/cli/dist/src/orgrt/daemon.d.ts +41 -0
- package/packages/@monomind/cli/dist/src/orgrt/daemon.js +101 -0
- package/packages/@monomind/cli/dist/src/orgrt/forwarder.d.ts +11 -0
- package/packages/@monomind/cli/dist/src/orgrt/forwarder.js +37 -0
- package/packages/@monomind/cli/dist/src/orgrt/mailbox.d.ts +25 -0
- package/packages/@monomind/cli/dist/src/orgrt/mailbox.js +39 -0
- package/packages/@monomind/cli/dist/src/orgrt/policy.d.ts +24 -0
- package/packages/@monomind/cli/dist/src/orgrt/policy.js +86 -0
- package/packages/@monomind/cli/dist/src/orgrt/provider.d.ts +9 -0
- package/packages/@monomind/cli/dist/src/orgrt/provider.js +54 -0
- package/packages/@monomind/cli/dist/src/orgrt/session.d.ts +23 -0
- package/packages/@monomind/cli/dist/src/orgrt/session.js +78 -0
- package/packages/@monomind/cli/dist/src/orgrt/types.d.ts +847 -0
- package/packages/@monomind/cli/dist/src/orgrt/types.js +51 -0
- package/packages/@monomind/cli/dist/src/parser.js +31 -5
- package/packages/@monomind/cli/dist/src/ui/collector.mjs +69 -44
- package/packages/@monomind/cli/dist/src/ui/dashboard.html +112 -25
- package/packages/@monomind/cli/dist/src/ui/orgs.html +54 -0
- package/packages/@monomind/cli/dist/src/ui/server.mjs +87 -12
- package/packages/@monomind/cli/package.json +3 -2
- package/packages/@monomind/cli/dist/src/consensus/vote-signer.d.ts +0 -36
- package/packages/@monomind/cli/dist/src/consensus/vote-signer.js +0 -88
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// packages/@monomind/cli/src/orgrt/types.ts
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
/** Per-role provider config. Default (absent) = subscription login of local Claude Code. */
|
|
4
|
+
export const ProviderSchema = z.object({
|
|
5
|
+
kind: z.enum(['subscription', 'api-key', 'base-url', 'bedrock', 'vertex']).default('subscription'),
|
|
6
|
+
/** env var NAME holding the API key (never the key itself) */
|
|
7
|
+
apiKeyEnv: z.string().optional(),
|
|
8
|
+
baseUrl: z.string().optional(),
|
|
9
|
+
/** env var NAME holding the auth token for base-url providers */
|
|
10
|
+
authTokenEnv: z.string().optional(),
|
|
11
|
+
}).strict();
|
|
12
|
+
export const RolePolicySchema = z.object({
|
|
13
|
+
allowTools: z.array(z.string()).optional(),
|
|
14
|
+
denyTools: z.array(z.string()).default([]),
|
|
15
|
+
/** glob patterns relative to org cwd */
|
|
16
|
+
fileWrite: z.array(z.string()).default(['**']),
|
|
17
|
+
fileRead: z.array(z.string()).default(['**']),
|
|
18
|
+
/** allowed domains for WebFetch/WebSearch; empty array = no web */
|
|
19
|
+
webAllow: z.array(z.string()).optional(),
|
|
20
|
+
maxTokens: z.number().int().positive().optional(),
|
|
21
|
+
}).partial().passthrough();
|
|
22
|
+
export const RoleSchema = z.object({
|
|
23
|
+
id: z.string().min(1),
|
|
24
|
+
title: z.string().default(''),
|
|
25
|
+
type: z.string().default('specialist'),
|
|
26
|
+
reports_to: z.string().nullable().default(null),
|
|
27
|
+
responsibilities: z.array(z.string()).default([]),
|
|
28
|
+
instructions_file: z.string().optional(),
|
|
29
|
+
adapter_config: z.object({
|
|
30
|
+
model: z.string().default('claude-sonnet-4-5'),
|
|
31
|
+
max_tokens: z.number().optional(),
|
|
32
|
+
}).partial().optional(),
|
|
33
|
+
provider: ProviderSchema.optional(),
|
|
34
|
+
policy: RolePolicySchema.optional(),
|
|
35
|
+
}).passthrough();
|
|
36
|
+
export const OrgDefSchema = z.object({
|
|
37
|
+
name: z.string().min(1),
|
|
38
|
+
goal: z.string().default(''),
|
|
39
|
+
status: z.string().default('stopped'),
|
|
40
|
+
schedule: z.union([z.string(), z.number(), z.null()]).default(null),
|
|
41
|
+
run_config: z.object({
|
|
42
|
+
max_concurrent_agents: z.number().int().positive().default(4),
|
|
43
|
+
budget_tokens: z.number().int().positive().default(1_000_000),
|
|
44
|
+
memory_namespace: z.string().optional(),
|
|
45
|
+
max_turns_per_message: z.number().int().positive().default(30),
|
|
46
|
+
}).partial().passthrough().default({})
|
|
47
|
+
.transform(rc => ({ max_concurrent_agents: 4, budget_tokens: 1_000_000, max_turns_per_message: 30, ...rc })),
|
|
48
|
+
roles: z.array(RoleSchema).min(1),
|
|
49
|
+
}).passthrough();
|
|
50
|
+
export const ORG_DIR = '.monomind/orgs';
|
|
51
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -159,11 +159,13 @@ export class CommandParser {
|
|
|
159
159
|
result.command.push(arg);
|
|
160
160
|
// Check for subcommand (level 1)
|
|
161
161
|
const cmd = this.commands.get(arg);
|
|
162
|
+
resolvedCmd = cmd;
|
|
162
163
|
if (cmd?.subcommands && i + 1 < args.length) {
|
|
163
164
|
const nextArg = args[i + 1];
|
|
164
165
|
const subCmd = cmd.subcommands.find(sc => sc.name === nextArg || sc.aliases?.includes(nextArg));
|
|
165
166
|
if (subCmd) {
|
|
166
167
|
result.command.push(nextArg);
|
|
168
|
+
resolvedCmd = subCmd;
|
|
167
169
|
i++;
|
|
168
170
|
// Check for nested subcommand (level 2)
|
|
169
171
|
if (subCmd.subcommands && i + 1 < args.length) {
|
|
@@ -171,6 +173,7 @@ export class CommandParser {
|
|
|
171
173
|
const nestedCmd = subCmd.subcommands.find(sc => sc.name === nestedArg || sc.aliases?.includes(nestedArg));
|
|
172
174
|
if (nestedCmd) {
|
|
173
175
|
result.command.push(nestedArg);
|
|
176
|
+
resolvedCmd = nestedCmd;
|
|
174
177
|
i++;
|
|
175
178
|
// Check for deeply nested subcommand (level 3)
|
|
176
179
|
if (nestedCmd.subcommands && i + 1 < args.length) {
|
|
@@ -178,6 +181,7 @@ export class CommandParser {
|
|
|
178
181
|
const deepCmd = nestedCmd.subcommands.find(sc => sc.name === deepArg || sc.aliases?.includes(deepArg));
|
|
179
182
|
if (deepCmd) {
|
|
180
183
|
result.command.push(deepArg);
|
|
184
|
+
resolvedCmd = deepCmd;
|
|
181
185
|
i++;
|
|
182
186
|
}
|
|
183
187
|
}
|
|
@@ -193,8 +197,10 @@ export class CommandParser {
|
|
|
193
197
|
}
|
|
194
198
|
i++;
|
|
195
199
|
}
|
|
196
|
-
// Apply defaults
|
|
197
|
-
|
|
200
|
+
// Apply defaults — the resolved (sub)command's own option definitions
|
|
201
|
+
// shadow same-name global options (e.g. `browse screenshot --format` is an
|
|
202
|
+
// image format, not the global text|json|table output format).
|
|
203
|
+
this.applyDefaults(result.flags, resolvedCmd);
|
|
198
204
|
return result;
|
|
199
205
|
}
|
|
200
206
|
parseFlag(args, index, aliases, booleanFlags) {
|
|
@@ -398,10 +404,24 @@ export class CommandParser {
|
|
|
398
404
|
}
|
|
399
405
|
return flags;
|
|
400
406
|
}
|
|
401
|
-
applyDefaults(flags) {
|
|
407
|
+
applyDefaults(flags, resolvedCmd) {
|
|
408
|
+
// The resolved command's own options shadow same-name globals: apply the
|
|
409
|
+
// command's defaults and suppress the global default for those names.
|
|
410
|
+
const shadowed = new Set();
|
|
411
|
+
if (resolvedCmd?.options) {
|
|
412
|
+
for (const opt of resolvedCmd.options) {
|
|
413
|
+
const key = this.normalizeKey(opt.name);
|
|
414
|
+
shadowed.add(key);
|
|
415
|
+
if (flags[key] === undefined && opt.default !== undefined) {
|
|
416
|
+
flags[key] = opt.default;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
402
420
|
// Apply global option defaults
|
|
403
421
|
for (const opt of this.globalOptions) {
|
|
404
422
|
const key = this.normalizeKey(opt.name);
|
|
423
|
+
if (shadowed.has(key))
|
|
424
|
+
continue;
|
|
405
425
|
if (flags[key] === undefined && opt.default !== undefined) {
|
|
406
426
|
flags[key] = opt.default;
|
|
407
427
|
}
|
|
@@ -418,10 +438,16 @@ export class CommandParser {
|
|
|
418
438
|
}
|
|
419
439
|
validateFlags(flags, command) {
|
|
420
440
|
const errors = [];
|
|
421
|
-
|
|
441
|
+
// Command options shadow same-name globals — validate against the
|
|
442
|
+
// command's definition (its choices/validators), not the global's.
|
|
443
|
+
const byName = new Map();
|
|
444
|
+
for (const opt of this.globalOptions)
|
|
445
|
+
byName.set(opt.name, opt);
|
|
422
446
|
if (command?.options) {
|
|
423
|
-
|
|
447
|
+
for (const opt of command.options)
|
|
448
|
+
byName.set(opt.name, opt);
|
|
424
449
|
}
|
|
450
|
+
const allOptions = [...byName.values()];
|
|
425
451
|
// Check required flags
|
|
426
452
|
for (const opt of allOptions) {
|
|
427
453
|
const key = this.normalizeKey(opt.name);
|
|
@@ -277,12 +277,17 @@ function collectAgents(projectDir) {
|
|
|
277
277
|
// Single source-of-truth for all model pricing (canonical list from src/pricing/model-pricing.ts).
|
|
278
278
|
// server.mjs imports _tokPrice and _tokCost from here instead of duplicating this table.
|
|
279
279
|
const _TOK_PRICES = {
|
|
280
|
+
// Frontier (Fable/Mythos)
|
|
281
|
+
'claude-fable-5': { in: 10e-6, out: 50e-6, cw: 12.5e-6, cr: 1e-6 },
|
|
282
|
+
'claude-mythos-5': { in: 10e-6, out: 50e-6, cw: 12.5e-6, cr: 1e-6 },
|
|
280
283
|
// Opus
|
|
281
284
|
'claude-opus-4-8': { in: 5e-6, out: 25e-6, cw: 6.25e-6, cr: 0.5e-6 },
|
|
285
|
+
'claude-opus-4-7': { in: 5e-6, out: 25e-6, cw: 6.25e-6, cr: 0.5e-6 },
|
|
282
286
|
'claude-opus-4-6': { in: 5e-6, out: 25e-6, cw: 6.25e-6, cr: 0.5e-6 },
|
|
283
287
|
'claude-opus-4-5': { in: 5e-6, out: 25e-6, cw: 6.25e-6, cr: 0.5e-6 },
|
|
284
288
|
'claude-opus-4': { in: 15e-6, out: 75e-6, cw: 18.75e-6, cr: 1.5e-6 },
|
|
285
289
|
// Sonnet
|
|
290
|
+
'claude-sonnet-5': { in: 3e-6, out: 15e-6, cw: 3.75e-6, cr: 0.3e-6 },
|
|
286
291
|
'claude-sonnet-4-6': { in: 3e-6, out: 15e-6, cw: 3.75e-6, cr: 0.3e-6 },
|
|
287
292
|
'claude-sonnet-4-5': { in: 3e-6, out: 15e-6, cw: 3.75e-6, cr: 0.3e-6 },
|
|
288
293
|
'claude-sonnet-4': { in: 3e-6, out: 15e-6, cw: 3.75e-6, cr: 0.3e-6 },
|
|
@@ -489,6 +494,15 @@ function collectMetrics(projectDir) {
|
|
|
489
494
|
const avgConf = routingConfCount > 0 ? Math.round((routingConfSum / routingConfCount) * 100) : null;
|
|
490
495
|
const topAgent = Object.entries(agentCounts).sort((a, b) => b[1] - a[1])[0];
|
|
491
496
|
|
|
497
|
+
// Worker freshness: stat the live worker output files so the frontend can
|
|
498
|
+
// render freshness pills. Shape: workers: [{name, exists, ageMs}]
|
|
499
|
+
const workerFiles = ['codebase-map', 'security-audit', 'performance', 'consolidation', 'ddd-progress'];
|
|
500
|
+
const now = Date.now();
|
|
501
|
+
const workers = workerFiles.map(name => {
|
|
502
|
+
const s = fileStat(path.join(base, 'metrics', name + '.json'));
|
|
503
|
+
return { name, exists: !!s, ageMs: s ? Math.max(0, now - s.mtimeMs) : null };
|
|
504
|
+
});
|
|
505
|
+
|
|
492
506
|
return {
|
|
493
507
|
routing: {
|
|
494
508
|
total: routingTotal,
|
|
@@ -507,7 +521,9 @@ function collectMetrics(projectDir) {
|
|
|
507
521
|
monthCost: tokenSummary.monthCost,
|
|
508
522
|
monthCalls: tokenSummary.monthCalls,
|
|
509
523
|
},
|
|
510
|
-
|
|
524
|
+
// Live worker output (audit worker) — fields include riskLevel, recommendations
|
|
525
|
+
security: readJSON(path.join(base, 'metrics', 'security-audit.json')) || {},
|
|
526
|
+
workers,
|
|
511
527
|
};
|
|
512
528
|
}
|
|
513
529
|
|
|
@@ -554,43 +570,56 @@ function collectMemory(projectDir) {
|
|
|
554
570
|
const d = path.resolve(projectDir);
|
|
555
571
|
const monomindDir = path.join(d, '.monomind');
|
|
556
572
|
|
|
557
|
-
//
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
] : [
|
|
573
|
-
path.join(d, 'data', 'memory.graph'),
|
|
574
|
-
path.join(d, '.swarm', 'hnsw.index'),
|
|
575
|
-
];
|
|
576
|
-
const hnswHit = probeFile(...hnswCandidates);
|
|
577
|
-
const hnsw = !!hnswHit;
|
|
573
|
+
// Auto-memory pattern store — array of pattern entries written by hooks
|
|
574
|
+
let patternCount = 0;
|
|
575
|
+
let patternsUpdated = null;
|
|
576
|
+
const storePath = path.join(monomindDir, 'data', 'auto-memory-store.json');
|
|
577
|
+
const store = readJSON(storePath);
|
|
578
|
+
if (Array.isArray(store)) {
|
|
579
|
+
patternCount = store.length;
|
|
580
|
+
for (const e of store) {
|
|
581
|
+
if (e && typeof e.ts === 'number' && (!patternsUpdated || e.ts > patternsUpdated)) patternsUpdated = e.ts;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
if (!patternsUpdated) {
|
|
585
|
+
const s = fileStat(storePath);
|
|
586
|
+
if (s) patternsUpdated = s.mtimeMs;
|
|
587
|
+
}
|
|
578
588
|
|
|
579
|
-
//
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
589
|
+
// Episodic memory — one JSON line per episode
|
|
590
|
+
let episodeCount = 0;
|
|
591
|
+
let lastEpisode = null;
|
|
592
|
+
try {
|
|
593
|
+
const raw = fs.readFileSync(path.join(monomindDir, 'episodic', 'episodes.jsonl'), 'utf8');
|
|
594
|
+
const lines = raw.split('\n').filter(Boolean);
|
|
595
|
+
episodeCount = lines.length;
|
|
596
|
+
if (lines.length) {
|
|
597
|
+
try {
|
|
598
|
+
const last = JSON.parse(lines[lines.length - 1]);
|
|
599
|
+
lastEpisode = last.ts || last.timestamp || null;
|
|
600
|
+
} catch {}
|
|
601
|
+
}
|
|
602
|
+
} catch {}
|
|
586
603
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
604
|
+
// Last consolidation run (consolidate worker output)
|
|
605
|
+
let lastConsolidated = null;
|
|
606
|
+
const consolidation = readJSON(path.join(monomindDir, 'metrics', 'consolidation.json'));
|
|
607
|
+
if (consolidation && consolidation.timestamp) lastConsolidated = consolidation.timestamp;
|
|
590
608
|
|
|
591
609
|
const files = collectMemoryFiles(projectDir);
|
|
592
610
|
|
|
593
|
-
return {
|
|
611
|
+
return {
|
|
612
|
+
patternCount,
|
|
613
|
+
patternsUpdated,
|
|
614
|
+
episodeCount,
|
|
615
|
+
lastEpisode,
|
|
616
|
+
lastConsolidated,
|
|
617
|
+
files,
|
|
618
|
+
count: files.length,
|
|
619
|
+
// Legacy keys kept for frontend backward-safety (v1 backends no longer written in v2)
|
|
620
|
+
dbSize: 0, dbPath: null, hnsw: false,
|
|
621
|
+
monovectorSize: 0, monovectorExists: false, monovectorPatterns: 0,
|
|
622
|
+
};
|
|
594
623
|
}
|
|
595
624
|
|
|
596
625
|
function collectSystem() {
|
|
@@ -771,7 +800,10 @@ export function getWatchPaths(projectDir) {
|
|
|
771
800
|
path.join(m, 'metrics', 'token-summary.json'),
|
|
772
801
|
path.join(m, 'metrics', 'token-sessions.json'),
|
|
773
802
|
path.join(m, 'metrics', 'ddd-progress.json'),
|
|
774
|
-
path.join(m, 'metrics', '
|
|
803
|
+
path.join(m, 'metrics', 'codebase-map.json'),
|
|
804
|
+
path.join(m, 'metrics', 'security-audit.json'),
|
|
805
|
+
path.join(m, 'metrics', 'performance.json'),
|
|
806
|
+
path.join(m, 'metrics', 'consolidation.json'),
|
|
775
807
|
// Agents
|
|
776
808
|
path.join(m, 'registry.json'),
|
|
777
809
|
path.join(m, 'agents', 'registrations'),
|
|
@@ -782,17 +814,10 @@ export function getWatchPaths(projectDir) {
|
|
|
782
814
|
// Knowledge
|
|
783
815
|
path.join(m, 'knowledge', 'chunks.jsonl'),
|
|
784
816
|
path.join(m, 'skills.jsonl'),
|
|
785
|
-
//
|
|
786
|
-
path.join(m, 'security', 'audit-status.json'),
|
|
787
|
-
// Triggers & memory — watch all candidate locations
|
|
817
|
+
// Triggers & memory — live v2 sources
|
|
788
818
|
path.join(m, 'trigger-index.json'),
|
|
789
|
-
path.join(
|
|
790
|
-
path.join(
|
|
791
|
-
path.join(resolvedDir, '.swarm', 'memory.db'),
|
|
792
|
-
path.join(resolvedDir, '.swarm', 'hnsw.index'),
|
|
793
|
-
path.join(m, 'memory.db'),
|
|
794
|
-
path.join(m, 'data', 'monovector.db'),
|
|
795
|
-
path.join(m, 'data', 'ranked-context.json'),
|
|
819
|
+
path.join(m, 'data', 'auto-memory-store.json'),
|
|
820
|
+
path.join(m, 'episodic', 'episodes.jsonl'),
|
|
796
821
|
// Sessions
|
|
797
822
|
path.join(c, 'sessions')
|
|
798
823
|
];
|
|
@@ -2663,6 +2663,7 @@ function switchProject(path) {
|
|
|
2663
2663
|
// Reset Monograph cache so it reloads for the new project
|
|
2664
2664
|
_mgLoaded = false;
|
|
2665
2665
|
_mgGraph = null;
|
|
2666
|
+
window._mgRealTotals = null;
|
|
2666
2667
|
document.getElementById('sb-proj').textContent = path.split('/').filter(Boolean).pop() || '—';
|
|
2667
2668
|
document.getElementById('sb-path').textContent = path;
|
|
2668
2669
|
_showNavProjectCtx(path);
|
|
@@ -3174,9 +3175,10 @@ async function loadStatusStrip() {
|
|
|
3174
3175
|
const strip = document.getElementById('status-strip');
|
|
3175
3176
|
if (!strip || !DIR) return;
|
|
3176
3177
|
try {
|
|
3177
|
-
const [statusRes, memRes] = await Promise.allSettled([
|
|
3178
|
+
const [statusRes, memRes, metricsRes] = await Promise.allSettled([
|
|
3178
3179
|
apiFetch('/api/status?dir=' + enc(DIR)),
|
|
3179
3180
|
apiFetch('/api/memory/stats?dir=' + enc(DIR)),
|
|
3181
|
+
apiFetch('/api/section?name=metrics&dir=' + enc(DIR)),
|
|
3180
3182
|
]);
|
|
3181
3183
|
const checks = statusRes.status === 'fulfilled'
|
|
3182
3184
|
? (Array.isArray(statusRes.value) ? statusRes.value : (statusRes.value?.checks || []))
|
|
@@ -3193,15 +3195,27 @@ async function loadStatusStrip() {
|
|
|
3193
3195
|
pills.push(`<span class="ss-pill ${cls}">${esc(c.name || c.label || c.key || '?')}</span>`);
|
|
3194
3196
|
});
|
|
3195
3197
|
|
|
3196
|
-
// HNSW status
|
|
3197
|
-
const hnswOn = mem.hnsw === true || mem.hnswEnabled === true || mem.hnsw_enabled === true;
|
|
3198
|
-
pills.push(`<span class="ss-pill ${hnswOn ? 'on' : ''}">HNSW ${hnswOn ? 'ON' : 'OFF'}</span>`);
|
|
3199
|
-
|
|
3200
3198
|
// Patterns count
|
|
3201
3199
|
if (mem.patterns != null) {
|
|
3202
3200
|
pills.push(`<span class="ss-pill">PATTERNS ${Number(mem.patterns).toLocaleString()}</span>`);
|
|
3203
3201
|
}
|
|
3204
3202
|
|
|
3203
|
+
// Worker freshness (metrics.workers = [{name, exists, ageMs}])
|
|
3204
|
+
const metricsSec = metricsRes.status === 'fulfilled'
|
|
3205
|
+
? (metricsRes.value?.metrics || metricsRes.value || {})
|
|
3206
|
+
: {};
|
|
3207
|
+
const workers = Array.isArray(metricsSec.workers) ? metricsSec.workers : [];
|
|
3208
|
+
const fmtAge = ms => ms < 3600e3 ? Math.round(ms / 60e3) + 'm'
|
|
3209
|
+
: ms < 86400e3 ? Math.round(ms / 3600e3) + 'h'
|
|
3210
|
+
: Math.round(ms / 86400e3) + 'd';
|
|
3211
|
+
workers.forEach(w => {
|
|
3212
|
+
if (!w || !w.name) return;
|
|
3213
|
+
const fresh = w.exists && typeof w.ageMs === 'number' && w.ageMs < 12 * 3600e3;
|
|
3214
|
+
const cls = !w.exists ? '' : (fresh ? 'on' : 'warn');
|
|
3215
|
+
const ageTxt = !w.exists ? 'missing' : (typeof w.ageMs === 'number' ? fmtAge(w.ageMs) : '?');
|
|
3216
|
+
pills.push(`<span class="ss-pill ${cls}" title="worker ${esc(w.name)}: ${esc(ageTxt)}">⚙ ${esc(w.name)}</span>`);
|
|
3217
|
+
});
|
|
3218
|
+
|
|
3205
3219
|
// Chunks count
|
|
3206
3220
|
if (mem.chunks != null) {
|
|
3207
3221
|
pills.push(`<span class="ss-pill">CHUNKS ${Number(mem.chunks).toLocaleString()}</span>`);
|
|
@@ -3506,8 +3520,20 @@ async function loadMemUsagePeriod(btn, period) {
|
|
|
3506
3520
|
}
|
|
3507
3521
|
|
|
3508
3522
|
const totalCost = typeof s.todayCost === 'number' ? s.todayCost : (typeof s.cost === 'number' ? s.cost : null);
|
|
3509
|
-
|
|
3510
|
-
|
|
3523
|
+
// Cache efficiency = cacheRead / (cacheRead + input), capped at 100%.
|
|
3524
|
+
// (The old cacheTokens/totalTokensIn math exceeded 100% because cache
|
|
3525
|
+
// reads are not included in the input-token total.)
|
|
3526
|
+
const cacheEff = s.cacheTokens
|
|
3527
|
+
? Math.min(100, Math.round(s.cacheTokens / (s.cacheTokens + (Number(s.totalTokensIn) || 0)) * 100))
|
|
3528
|
+
: null;
|
|
3529
|
+
|
|
3530
|
+
// Projects rows sometimes arrive without a usable name — fall back to
|
|
3531
|
+
// the directory basename, then 'unknown'.
|
|
3532
|
+
const projRows = projects.map(p => {
|
|
3533
|
+
const raw = (p.project && p.project !== '?') ? p.project : (p.name && p.name !== '?' ? p.name : '');
|
|
3534
|
+
const base = String(p.dir || p.path || '').split('/').filter(Boolean).pop() || '';
|
|
3535
|
+
return Object.assign({}, p, { project: raw || base || 'unknown' });
|
|
3536
|
+
});
|
|
3511
3537
|
|
|
3512
3538
|
content.innerHTML = `
|
|
3513
3539
|
<!-- Overview stats -->
|
|
@@ -3536,8 +3562,8 @@ async function loadMemUsagePeriod(btn, period) {
|
|
|
3536
3562
|
<div style="margin-bottom:14px">${barChart(mcps, 'count', 'server', 'oklch(70% 0.18 60)', 8)}</div>` : ''}
|
|
3537
3563
|
|
|
3538
3564
|
<!-- Projects -->
|
|
3539
|
-
${
|
|
3540
|
-
<div style="margin-bottom:14px">${barChart(
|
|
3565
|
+
${projRows.length ? `<div class="m-group-title" style="margin-bottom:6px">Projects</div>
|
|
3566
|
+
<div style="margin-bottom:14px">${barChart(projRows, 'cost', 'project', 'oklch(65% 0.15 150)', 5)}</div>` : ''}
|
|
3541
3567
|
|
|
3542
3568
|
<!-- Fallback: session breakdown table if no breakdown data -->
|
|
3543
3569
|
${!models.length && !tools.length && rows.length ? `
|
|
@@ -6274,7 +6300,6 @@ const _v2AvatarKnown = new Set([
|
|
|
6274
6300
|
'quorum-manager','consensus-coordinator','perf-analyzer','benchmarker',
|
|
6275
6301
|
'task-orchestrator','memory-coordinator','load-balancer','resource-allocator',
|
|
6276
6302
|
'pr-manager','code-review-swarm','issue-tracker','release-manager','repo-architect',
|
|
6277
|
-
'workflow-automation','sparc-coord','sparc-coder','specification','pseudocode',
|
|
6278
6303
|
'architecture','refinement','backend-dev','frontend-developer','mobile-dev',
|
|
6279
6304
|
'ml-developer','cicd-engineer','system-architect','ai-engineer','model-qa',
|
|
6280
6305
|
'data-engineer','analytics-reporter','experiment-tracker','data-consolidator',
|
|
@@ -8227,7 +8252,7 @@ function v2RenderOrgConfig() {
|
|
|
8227
8252
|
const rc = d.run_config || {};
|
|
8228
8253
|
const gov = (d.governance && typeof d.governance === 'object') ? d.governance : { policy: d.governance || 'auto' };
|
|
8229
8254
|
const topos = ['hierarchical','hierarchical-mesh','mesh','star','ring','adaptive','hybrid'];
|
|
8230
|
-
const modes = ['daemon','once','scheduled'];
|
|
8255
|
+
const modes = [{ v: 'daemon', l: 'persistent (daemon)' }, { v: 'once', l: 'once' }, { v: 'scheduled', l: 'scheduled' }];
|
|
8231
8256
|
const statuses = ['active','paused','archived'];
|
|
8232
8257
|
const govPolicies = ['auto','board','strict'];
|
|
8233
8258
|
const inp = (id,val,type,extra) => '<input class="filter-input" id="'+id+'" type="'+(type||'text')+'" value="'+esc(String(val??''))+'" '+(extra||'')+' style="width:100%;box-sizing:border-box">';
|
|
@@ -8239,7 +8264,7 @@ function v2RenderOrgConfig() {
|
|
|
8239
8264
|
fld('Name', inp('oc-name', d.name||'','text','readonly style="opacity:0.5"'))
|
|
8240
8265
|
+fld('Status', sel('oc-status', statuses, d.status||'active'))
|
|
8241
8266
|
+'<div style="grid-column:1/-1">'+fld('Goal', '<textarea id="oc-goal" class="filter-input" rows="3" style="width:100%;box-sizing:border-box;resize:vertical;line-height:1.5">'+esc(d.goal||'')+'</textarea>')+'</div>'
|
|
8242
|
-
+fld('Mode',
|
|
8267
|
+
+fld('Mode', '<select class="filter-input" id="oc-mode" style="width:100%;box-sizing:border-box;cursor:pointer">'+modes.map(o=>'<option value="'+esc(o.v)+'"'+((d.mode||'daemon')===o.v?' selected':'')+'>'+esc(o.l)+'</option>').join('')+'</select>')
|
|
8243
8268
|
+fld('Topology', sel('oc-topology', topos, d.topology||'hierarchical'))
|
|
8244
8269
|
+'<div style="grid-column:1/-1">'+fld('Schedule', inp('oc-schedule', d.schedule||''))+'</div>'
|
|
8245
8270
|
)
|
|
@@ -9144,7 +9169,6 @@ const _MASTERMIND_SKILLS = [
|
|
|
9144
9169
|
'/monomind:repeat','monomind:review','monomind:understand','monomind:adr',
|
|
9145
9170
|
'/monomind:budget','monomind:graph-status','monomind:loops','monomind:swarm',
|
|
9146
9171
|
'/swarm:development','swarm:analysis','swarm:testing','swarm:optimization',
|
|
9147
|
-
'/sparc:architect','sparc:code','sparc:tdd','sparc:reviewer','sparc:security-review',
|
|
9148
9172
|
'/github:pr-manager','github:issue-tracker','github:release-manager',
|
|
9149
9173
|
];
|
|
9150
9174
|
window._allSkills = _MASTERMIND_SKILLS;
|
|
@@ -11871,8 +11895,16 @@ function renderMgOverview() {
|
|
|
11871
11895
|
const E = g.edges.length;
|
|
11872
11896
|
const avgDeg = N > 0 ? ((2 * E) / N).toFixed(1) : '0';
|
|
11873
11897
|
const typeSet = new Set(g.nodes.map(n => n.type || n.kind || 'unknown'));
|
|
11874
|
-
|
|
11875
|
-
|
|
11898
|
+
// The graph fetch is capped at 2000 nodes — prefer real totals from the
|
|
11899
|
+
// monograph report (set by mgLoadReport) and relabel honestly when capped.
|
|
11900
|
+
const _capped = N >= 2000 || E >= 2000;
|
|
11901
|
+
const _rt = window._mgRealTotals || null;
|
|
11902
|
+
const _nEl = document.getElementById('mg-stat-nodes');
|
|
11903
|
+
const _eEl = document.getElementById('mg-stat-edges');
|
|
11904
|
+
_nEl.textContent = (_rt && _rt.nodes) ? Number(_rt.nodes).toLocaleString() : N;
|
|
11905
|
+
_eEl.textContent = (_rt && _rt.edges) ? Number(_rt.edges).toLocaleString() : E;
|
|
11906
|
+
if (_nEl.previousElementSibling) _nEl.previousElementSibling.textContent = ((_rt && _rt.nodes) || !_capped) ? 'Nodes' : 'Nodes (top 2000)';
|
|
11907
|
+
if (_eEl.previousElementSibling) _eEl.previousElementSibling.textContent = ((_rt && _rt.edges) || !_capped) ? 'Edges' : 'Edges (top 2000)';
|
|
11876
11908
|
document.getElementById('mg-stat-avgdeg').textContent = avgDeg;
|
|
11877
11909
|
document.getElementById('mg-stat-types').textContent = typeSet.size;
|
|
11878
11910
|
|
|
@@ -11918,7 +11950,14 @@ function renderMgOverview() {
|
|
|
11918
11950
|
else {
|
|
11919
11951
|
const show = dead.slice(0, 12);
|
|
11920
11952
|
deadEl.innerHTML = `<table class="mg-table"><thead><tr><th>Name</th><th>Type</th></tr></thead><tbody>` +
|
|
11921
|
-
show.map(n =>
|
|
11953
|
+
show.map(n => {
|
|
11954
|
+
// Prefer a real name; if the label is just a generic type word
|
|
11955
|
+
// (e.g. "function"), fall back to the qualified id/path instead.
|
|
11956
|
+
const raw = n.label || n.name || '';
|
|
11957
|
+
const generic = !raw || /^(function|method|class|node|unknown|anonymous)$/i.test(String(raw).trim());
|
|
11958
|
+
const display = generic ? (n.id || n.name || raw || '') : raw;
|
|
11959
|
+
return `<tr><td title="${esc(n.id||n.name||n.label)}">${esc(mgShortLabel(display))}</td><td>${esc(n.type||n.kind||'')}</td></tr>`;
|
|
11960
|
+
}).join('') +
|
|
11922
11961
|
(dead.length > 12 ? `<tr><td colspan="2" style="color:var(--text-xs);font-size:11px">…and ${dead.length-12} more</td></tr>` : '') +
|
|
11923
11962
|
`</tbody></table>`;
|
|
11924
11963
|
}
|
|
@@ -12820,6 +12859,18 @@ async function mgLoadReport() {
|
|
|
12820
12859
|
// Extract markdown from JSON envelope {exists, report, stats}
|
|
12821
12860
|
const markdown = (data && data.report) ? data.report : (typeof data === 'string' ? data : JSON.stringify(data, null, 2));
|
|
12822
12861
|
const stats = (data && data.stats) || {};
|
|
12862
|
+
// Real graph totals — the overview cards otherwise show the capped fetch size
|
|
12863
|
+
const _mdN = (String(markdown).match(/\*\*Nodes\*\*:\s*([\d,]+)/i) || [])[1];
|
|
12864
|
+
const _mdE = (String(markdown).match(/\*\*Edges\*\*:\s*([\d,]+)/i) || [])[1];
|
|
12865
|
+
const _totN = stats.nodes || (_mdN ? Number(_mdN.replace(/,/g, '')) : null);
|
|
12866
|
+
const _totE = stats.edges || (_mdE ? Number(_mdE.replace(/,/g, '')) : null);
|
|
12867
|
+
if (_totN || _totE) {
|
|
12868
|
+
window._mgRealTotals = { nodes: _totN, edges: _totE };
|
|
12869
|
+
const nEl = document.getElementById('mg-stat-nodes');
|
|
12870
|
+
const eEl = document.getElementById('mg-stat-edges');
|
|
12871
|
+
if (_totN && nEl) { nEl.textContent = Number(_totN).toLocaleString(); if (nEl.previousElementSibling) nEl.previousElementSibling.textContent = 'Nodes'; }
|
|
12872
|
+
if (_totE && eEl) { eEl.textContent = Number(_totE).toLocaleString(); if (eEl.previousElementSibling) eEl.previousElementSibling.textContent = 'Edges'; }
|
|
12873
|
+
}
|
|
12823
12874
|
mgRenderReport(markdown, el, stats);
|
|
12824
12875
|
} catch (err) {
|
|
12825
12876
|
el.innerHTML = `<div style="color:var(--red);font-size:12px">Error: ${esc(String(err))}</div>`;
|
|
@@ -13156,7 +13207,7 @@ function mgWikiFindRelated(nodeId) {
|
|
|
13156
13207
|
}
|
|
13157
13208
|
|
|
13158
13209
|
function mgWikiRefresh() {
|
|
13159
|
-
_mgLoaded = false; _mgGraph = null;
|
|
13210
|
+
_mgLoaded = false; _mgGraph = null; window._mgRealTotals = null;
|
|
13160
13211
|
document.getElementById('mg-wiki-list').innerHTML = '<div class="loading-txt">Loading…</div>';
|
|
13161
13212
|
loadMonograph();
|
|
13162
13213
|
}
|
|
@@ -13383,7 +13434,7 @@ async function selectSwarmRun(idx) {
|
|
|
13383
13434
|
detail.innerHTML =
|
|
13384
13435
|
'<div style="margin-bottom:10px">' +
|
|
13385
13436
|
'<div style="font-size:13px;font-weight:600;color:var(--text-hi)">' + esc((run.swarmId || run.id || '—').toString().slice(0, 14)) + '</div>' +
|
|
13386
|
-
'<div style="font-size:11px;color:var(--text-lo);margin-top:3px">' + esc(run.topology || '—') + ' · ' + esc(run.consensus
|
|
13437
|
+
'<div style="font-size:11px;color:var(--text-lo);margin-top:3px">' + esc(run.topology || '—') + ' · ' + (run.consensus ? esc(run.consensus) + ' vote-threshold' : '—') + ' · ' + (run.agentCount || 0) + ' agents</div>' +
|
|
13387
13438
|
'</div>' +
|
|
13388
13439
|
'<canvas id="swarm-topo-canvas" style="width:100%;max-width:380px;height:190px;display:block;border:1px solid var(--border);border-radius:6px;margin-bottom:12px"></canvas>' +
|
|
13389
13440
|
'<div id="swarm-agent-list" style="margin-bottom:10px"></div>' +
|
|
@@ -13545,25 +13596,40 @@ function _renderChunks(list) {
|
|
|
13545
13596
|
grid.innerHTML = '<div class="empty">No chunks indexed.<br><span style="font-size:11px;color:var(--text-xs)">Run /monomind:understand to build the index.</span></div>';
|
|
13546
13597
|
return;
|
|
13547
13598
|
}
|
|
13548
|
-
|
|
13599
|
+
// Keep the rendered slice around so button handlers can look chunks up by
|
|
13600
|
+
// index — interpolating chunk content into onclick attributes broke (and
|
|
13601
|
+
// leaked markup) whenever the text contained quotes.
|
|
13602
|
+
const shown = list.slice(0, 200);
|
|
13603
|
+
window._chunksShown = shown;
|
|
13604
|
+
grid.innerHTML = shown.map((c, i) => {
|
|
13549
13605
|
const src = (c.source || c.file || '').split('/').slice(-2).join('/');
|
|
13550
13606
|
const excerpt = (c.content || c.text || c.body || '').slice(0, 220);
|
|
13551
13607
|
const ns = c.namespace || c.type || '';
|
|
13552
|
-
const chunkId = JSON.stringify(c.id || c.path || c.source || '');
|
|
13553
|
-
const chunkContent = JSON.stringify(c.content || c.text || c.body || '');
|
|
13554
|
-
const chunkSrc = JSON.stringify(src);
|
|
13555
13608
|
return '<div class="chunk-card" data-search="' + esc((src + ' ' + excerpt + ' ' + ns).toLowerCase()) + '">' +
|
|
13556
13609
|
'<div class="chunk-src">' + esc(src || '—') + '</div>' +
|
|
13557
13610
|
'<div class="chunk-excerpt">' + esc(excerpt) + '</div>' +
|
|
13558
13611
|
'<div class="chunk-footer">' +
|
|
13559
13612
|
(ns ? '<span class="chunk-ns">' + esc(ns) + '</span>' : '') +
|
|
13560
|
-
'<button class="btn" style="margin-left:auto;font-size:10px;padding:1px 7px" onclick="
|
|
13561
|
-
'<button class="btn" style="font-size:10px;padding:1px 7px;color:var(--red);border-color:var(--red)" onclick="
|
|
13613
|
+
'<button class="btn" style="margin-left:auto;font-size:10px;padding:1px 7px" onclick="openChunkEditAt(' + i + ')">✎ Edit</button>' +
|
|
13614
|
+
'<button class="btn" style="font-size:10px;padding:1px 7px;color:var(--red);border-color:var(--red)" onclick="deleteChunkAt(' + i + ')">✕</button>' +
|
|
13562
13615
|
'</div>' +
|
|
13563
13616
|
'</div>';
|
|
13564
13617
|
}).join('');
|
|
13565
13618
|
}
|
|
13566
13619
|
|
|
13620
|
+
function openChunkEditAt(i) {
|
|
13621
|
+
const c = (window._chunksShown || [])[i];
|
|
13622
|
+
if (!c) return;
|
|
13623
|
+
const src = (c.source || c.file || '').split('/').slice(-2).join('/');
|
|
13624
|
+
openChunkEdit(c.id || c.path || c.source || '', c.content || c.text || c.body || '', src);
|
|
13625
|
+
}
|
|
13626
|
+
|
|
13627
|
+
function deleteChunkAt(i) {
|
|
13628
|
+
const c = (window._chunksShown || [])[i];
|
|
13629
|
+
if (!c) return;
|
|
13630
|
+
deleteChunk(c.id || c.path || c.source || '');
|
|
13631
|
+
}
|
|
13632
|
+
|
|
13567
13633
|
function openChunkEdit(id, content, srcLabel) {
|
|
13568
13634
|
_editChunkId = id;
|
|
13569
13635
|
document.getElementById('chunk-modal-title').textContent = 'Edit Chunk';
|
|
@@ -13665,7 +13731,28 @@ async function loadAgentGraphTab() {
|
|
|
13665
13731
|
if (bar) bar.innerHTML = '<div class="loading-txt">Loading…</div>';
|
|
13666
13732
|
try {
|
|
13667
13733
|
const data = await apiFetch('/api/graph?dir=' + enc(DIR));
|
|
13668
|
-
|
|
13734
|
+
// /api/graph returns {nodes, edges} — session nodes + agenttype nodes.
|
|
13735
|
+
// Transform into the summary/sessions shape this tab renders.
|
|
13736
|
+
const nodes = Array.isArray(data && data.nodes) ? data.nodes : [];
|
|
13737
|
+
const sessNodes = nodes.filter(n => n.type === 'session').sort((a, b) => (b.mtime || 0) - (a.mtime || 0));
|
|
13738
|
+
const agNodes = nodes.filter(n => n.type === 'agenttype');
|
|
13739
|
+
const sessions = sessNodes.map(n => ({
|
|
13740
|
+
id: n.id,
|
|
13741
|
+
turns: n.turns || 0,
|
|
13742
|
+
spawnCount: Object.values(n.agentSpawns || {}).reduce((a, b) => a + b, 0),
|
|
13743
|
+
toolCount: n.totalTools || 0,
|
|
13744
|
+
tools: n.toolCounts || {},
|
|
13745
|
+
agentTypes: n.agentSpawns || {},
|
|
13746
|
+
cost: n.cost,
|
|
13747
|
+
}));
|
|
13748
|
+
_agData = {
|
|
13749
|
+
sessions,
|
|
13750
|
+
sessionCount: sessions.length,
|
|
13751
|
+
agentTypes: agNodes.length,
|
|
13752
|
+
totalSpawns: agNodes.reduce((a, n) => a + (n.totalSpawns || 0), 0),
|
|
13753
|
+
totalToolCalls: sessions.reduce((a, s) => a + (s.toolCount || 0), 0),
|
|
13754
|
+
totalCost: sessions.reduce((a, s) => a + (Number(s.cost) || 0), 0),
|
|
13755
|
+
};
|
|
13669
13756
|
_renderAgSummary();
|
|
13670
13757
|
_renderAgSessList();
|
|
13671
13758
|
} catch (e) {
|
|
@@ -841,6 +841,15 @@ html, body {
|
|
|
841
841
|
<div class="meta-cell-value" id="meta-created" style="font-size:10px;color:var(--muted)">—</div>
|
|
842
842
|
</div>
|
|
843
843
|
</div>
|
|
844
|
+
|
|
845
|
+
<!-- Audit Coverage card (populated from /api/org-coverage; hidden when no coverage.json) -->
|
|
846
|
+
<div id="audit-coverage-card" style="display:none; background:var(--bg-panel); border:1px solid var(--border); border-radius:3px; padding:10px 14px;">
|
|
847
|
+
<div class="meta-cell-label" style="display:flex; align-items:center; gap:8px;">
|
|
848
|
+
AUDIT COVERAGE
|
|
849
|
+
<span id="acov-summary" style="letter-spacing:1px; color:var(--muted)"></span>
|
|
850
|
+
</div>
|
|
851
|
+
<div id="acov-areas" style="display:flex; flex-wrap:wrap; gap:6px; margin-top:2px;"></div>
|
|
852
|
+
</div>
|
|
844
853
|
<div id="chart-svg-wrap">
|
|
845
854
|
<svg id="org-chart-svg" viewBox="0 0 720 320">
|
|
846
855
|
<defs>
|
|
@@ -1203,6 +1212,51 @@ async function selectOrg(name) {
|
|
|
1203
1212
|
}
|
|
1204
1213
|
|
|
1205
1214
|
renderTab(currentTab);
|
|
1215
|
+
loadAuditCoverage(listOrg);
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// ── Audit Coverage card (reads .monomind/audit/coverage.json via /api/org-coverage) ──
|
|
1219
|
+
async function loadAuditCoverage(listOrg) {
|
|
1220
|
+
const card = document.getElementById('audit-coverage-card');
|
|
1221
|
+
card.style.display = 'none';
|
|
1222
|
+
const esc = s => String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
|
1223
|
+
try {
|
|
1224
|
+
const dir = listOrg.projectDir || window._orgDir;
|
|
1225
|
+
const url = '/api/org-coverage' + (dir ? `?dir=${encodeURIComponent(dir)}` : '');
|
|
1226
|
+
const r = await fetch(url);
|
|
1227
|
+
if (!r.ok) return; // 404 = no coverage.json — keep card hidden
|
|
1228
|
+
const cov = await r.json();
|
|
1229
|
+
const areas = cov.areas && typeof cov.areas === 'object' ? cov.areas : {};
|
|
1230
|
+
const names = Object.keys(areas);
|
|
1231
|
+
if (!names.length) return;
|
|
1232
|
+
|
|
1233
|
+
const summ = [];
|
|
1234
|
+
if (cov.areas_converged != null && cov.areas_total != null) summ.push(`${cov.areas_converged}/${cov.areas_total} CONVERGED`);
|
|
1235
|
+
if (cov.current_area) summ.push(`NOW: ${String(cov.current_area).toUpperCase()}`);
|
|
1236
|
+
document.getElementById('acov-summary').textContent = summ.join(' · ');
|
|
1237
|
+
|
|
1238
|
+
const verdictColor = v => {
|
|
1239
|
+
const s = String(v || '').toUpperCase();
|
|
1240
|
+
if (s.includes('CONVERGE') || s.includes('KEEP') || s.includes('PASS') || s.includes('DONE')) return 'var(--green, oklch(68% 0.2 150))';
|
|
1241
|
+
if (s.includes('ITERATE') || s.includes('WARN')) return 'oklch(78% 0.18 80)';
|
|
1242
|
+
if (s.includes('FAIL') || s.includes('REJECT')) return 'var(--red, oklch(62% 0.22 25))';
|
|
1243
|
+
return 'var(--muted, oklch(55% 0.02 186))';
|
|
1244
|
+
};
|
|
1245
|
+
document.getElementById('acov-areas').innerHTML = names.map(name => {
|
|
1246
|
+
const a = areas[name] || {};
|
|
1247
|
+
const verdict = a.verdict || a.status || '—';
|
|
1248
|
+
const value = a.value_delivered || a.valueDelivered || null;
|
|
1249
|
+
const c = verdictColor(verdict);
|
|
1250
|
+
return `<span style="display:inline-flex; align-items:center; gap:5px; font-size:9px; font-family:var(--mono);
|
|
1251
|
+
border:1px solid var(--border); border-radius:3px; padding:2px 7px; color:var(--text);"
|
|
1252
|
+
title="${esc(value || verdict)}">
|
|
1253
|
+
${esc(name)}
|
|
1254
|
+
<span style="color:${c}; letter-spacing:1px;">${esc(String(verdict).toUpperCase())}</span>
|
|
1255
|
+
${value ? `<span style="color:var(--dim); max-width:180px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">${esc(value)}</span>` : ''}
|
|
1256
|
+
</span>`;
|
|
1257
|
+
}).join('');
|
|
1258
|
+
card.style.display = '';
|
|
1259
|
+
} catch (_) { /* keep hidden */ }
|
|
1206
1260
|
}
|
|
1207
1261
|
|
|
1208
1262
|
function updateHeader(listOrg, data) {
|