cyborg-hunter 0.8.0 → 0.9.1
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/CHANGELOG.md +48 -0
- package/CITATION.cff +2 -2
- package/README.md +6 -4
- package/dist/cyborg-hunter-replay.js +2 -2
- package/dist/cyborg-hunter.esm.js +3 -1
- package/dist/cyborg-hunter.min.js +2 -2
- package/dist/extension-cyborg-hunter.js +1 -1
- package/package.json +1 -1
- package/src/cli/analyzers/score-weights.js +145 -0
- package/src/cli/analyzers/triage.js +55 -39
- package/src/cli/config.js +15 -0
- package/src/cli/preview-entry.js +6 -0
- package/src/cli/renderers/font-face-css.js +16 -0
- package/src/cli/renderers/fonts/FONTS_MANIFEST.json +69 -0
- package/src/cli/renderers/fonts/majormonodisplay/OFL.txt +93 -0
- package/src/cli/renderers/fonts/majormonodisplay/majormonodisplay-400.woff2 +0 -0
- package/src/cli/renderers/fonts/recursive/OFL.txt +93 -0
- package/src/cli/renderers/fonts/recursive/recursive-300-1000.woff2 +0 -0
- package/src/cli/renderers/fonts/sofiasans/OFL.txt +93 -0
- package/src/cli/renderers/fonts/sofiasans/sofiasans-1-1000.woff2 +0 -0
- package/src/cli/renderers/fonts/sora/OFL.txt +93 -0
- package/src/cli/renderers/fonts/sora/sora-100-800.woff2 +0 -0
- package/src/cli/renderers/fonts/spacegrotesk/OFL.txt +93 -0
- package/src/cli/renderers/fonts/spacegrotesk/spacegrotesk-300-700.woff2 +0 -0
- package/src/cli/renderers/fonts/tomorrow/OFL.txt +93 -0
- package/src/cli/renderers/fonts/tomorrow/tomorrow-400.woff2 +0 -0
- package/src/cli/renderers/html-index-core.js +120 -106
- package/src/cli/renderers/html-index.js +7 -2
- package/src/cli/renderers/replay-styles.js +61 -0
- package/src/cli/renderers/replay-viewer.client.js +8 -6
- package/src/cli/renderers/report-fonts.js +24 -0
- package/src/cli/renderers/score-weights.js +16 -0
- package/src/cli/renderers/summary-csv.js +2 -0
- package/src/cli/renderers/trajectories-core.js +2 -1
- package/src/cli/renderers/triage-md.js +9 -2
- package/src/cli/report.js +10 -3
- package/src/jspsych/extension-cyborg-hunter-replay.js +1 -1
- package/src/jspsych/extension-cyborg-hunter.js +1 -1
- package/src/shared/constants.js +1 -1
- package/src/shared/schema.js +1 -0
- package/src/shared/validation.js +1 -1
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
// - Title with trial ID, RT, mouse count, tab-away count + total duration
|
|
27
27
|
|
|
28
28
|
import { ruleChronologicalCompare } from '../extract-core.js';
|
|
29
|
+
import { formatScore } from '../analyzers/score-weights.js';
|
|
29
30
|
|
|
30
31
|
// Panel dimensions (pixels).
|
|
31
32
|
const PANEL_W = 400;
|
|
@@ -121,7 +122,7 @@ export function drawTrajectoryGrid(p, triageEntry, config, createCanvas) {
|
|
|
121
122
|
ctx.fillRect(0, 0, canvasW, canvasH);
|
|
122
123
|
|
|
123
124
|
// Header
|
|
124
|
-
const headerText = `${p.participantId} — Score: ${triageEntry?.score ?? '?'} — ${triageEntry?.reason ?? ''}`;
|
|
125
|
+
const headerText = `${p.participantId} — Score: ${formatScore(triageEntry?.score ?? '?')} — ${triageEntry?.reason ?? ''}`;
|
|
125
126
|
ctx.fillStyle = COLORS.headerText;
|
|
126
127
|
ctx.font = 'bold 16px sans-serif';
|
|
127
128
|
ctx.fillText(headerText, PANEL_PAD, 30);
|
|
@@ -4,8 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
import { writeFileSync } from 'fs';
|
|
6
6
|
import { join } from 'path';
|
|
7
|
+
import { resolveScoreWeights, formulaText, formatScore } from '../analyzers/score-weights.js';
|
|
7
8
|
|
|
8
9
|
export async function renderTriage(triage, config) {
|
|
10
|
+
// Default weights keep the 0.8.0 sentence verbatim; custom ones state the
|
|
11
|
+
// formula that was actually applied (config.scoreWeights).
|
|
12
|
+
const { weights, isDefault } = resolveScoreWeights(config?.scoreWeights);
|
|
13
|
+
const formula = isDefault
|
|
14
|
+
? 'heuristic (5×paste + 5×copy + 3×sidebar + 1×tab-away) — it orders rows'
|
|
15
|
+
: `heuristic (${formulaText(weights)}, set by scoreWeights) — it orders rows`;
|
|
9
16
|
const lines = [
|
|
10
17
|
'# Participant Triage — Ranked by Suspiciousness',
|
|
11
18
|
'',
|
|
@@ -14,7 +21,7 @@ export async function renderTriage(triage, config) {
|
|
|
14
21
|
'**Tier** is the library\'s two-tier screening verdict: `HARD` = a hard signal',
|
|
15
22
|
'(paste/drop/copy) crossed its count threshold; `soft` = library soft score ≥',
|
|
16
23
|
'its threshold; `clean` = neither. **Score** is the CLI\'s separate ranking',
|
|
17
|
-
|
|
24
|
+
formula,
|
|
18
25
|
'*within* a tier and is not the library soft score.',
|
|
19
26
|
'',
|
|
20
27
|
'| Rank | Participant | Tier | Score | Reason |',
|
|
@@ -25,7 +32,7 @@ export async function renderTriage(triage, config) {
|
|
|
25
32
|
const tier = t.hardTriggered ? '**HARD**' : t.softFlagged ? 'soft' : 'clean';
|
|
26
33
|
// Escape pipe characters in reason text to avoid breaking the table
|
|
27
34
|
const reason = t.reason.replace(/\|/g, '\\|');
|
|
28
|
-
lines.push(`| ${i + 1} | ${t.participantId} | ${tier} | ${t.score} | ${reason} |`);
|
|
35
|
+
lines.push(`| ${i + 1} | ${t.participantId} | ${tier} | ${formatScore(t.score)} | ${reason} |`);
|
|
29
36
|
});
|
|
30
37
|
|
|
31
38
|
lines.push('');
|
package/src/cli/report.js
CHANGED
|
@@ -12,6 +12,7 @@ import { ingest } from './ingest.js';
|
|
|
12
12
|
import { computeSummary } from './analyzers/summary.js';
|
|
13
13
|
import { detectEdgeExits } from './analyzers/edge-exit.js';
|
|
14
14
|
import { rankTriage } from './analyzers/triage.js';
|
|
15
|
+
import { resolveScoreWeights, formulaText } from './analyzers/score-weights.js';
|
|
15
16
|
import { applyPhaseScope, describePhaseScope, findUnmatchedPhaseScopePhases } from './analyzers/phase-scope.js';
|
|
16
17
|
import { VERSION } from '../shared/constants.js';
|
|
17
18
|
import { checkForUpdate, formatUpdateNotice, formatCollectedVersionNotice } from './update-check.js';
|
|
@@ -86,14 +87,18 @@ export async function run(args) {
|
|
|
86
87
|
// "flagged": the tier counts below come from the LIBRARY's two-tier
|
|
87
88
|
// screening (hard count thresholds / soft score vs its threshold), while
|
|
88
89
|
// triage.md is ORDERED by the CLI's separate composite triage score
|
|
89
|
-
// (5×paste + 5×copy + 3×sidebar + 1×tab-away
|
|
90
|
-
//
|
|
90
|
+
// (by default 5×paste + 5×copy + 3×sidebar + 1×tab-away; config.scoreWeights
|
|
91
|
+
// can change it) within each tier. Label both explicitly so the console
|
|
92
|
+
// summary can't be read as "top N of triage.md".
|
|
93
|
+
const scoreWeights = resolveScoreWeights(config.scoreWeights);
|
|
91
94
|
console.log(`\nAnalyzing...`);
|
|
92
95
|
console.log(` Hard-flagged (hard signal crossed its count threshold): ${flaggedHard}`);
|
|
93
96
|
console.log(` Soft-flagged (library soft score >= its threshold): ${flaggedSoft}`);
|
|
94
97
|
console.log(` Clean: ${clean}`);
|
|
95
98
|
console.log(` Triage.md orders tier-first (hard > soft > clean), then by the CLI`);
|
|
96
|
-
console.log(
|
|
99
|
+
console.log(scoreWeights.isDefault
|
|
100
|
+
? ` triage score (5xpaste + 5xcopy + 3xsidebar + 1xtab-away) within a tier.`
|
|
101
|
+
: ` triage score (${formulaText(scoreWeights.weights, 'x')}, from scoreWeights) within a tier.`);
|
|
97
102
|
|
|
98
103
|
// 4. Render outputs
|
|
99
104
|
console.log(`\nRendering...`);
|
|
@@ -107,6 +112,8 @@ export async function run(args) {
|
|
|
107
112
|
const { renderExtensions } = await import('./renderers/extensions.js');
|
|
108
113
|
|
|
109
114
|
await renderSummaryCSV(summaries, triage, config);
|
|
115
|
+
const { renderScoreWeights } = await import('./renderers/score-weights.js');
|
|
116
|
+
renderScoreWeights(config);
|
|
110
117
|
await renderTriage(triage, config);
|
|
111
118
|
await renderEventLog(participants, config);
|
|
112
119
|
await renderExtensions(participants, config);
|
|
@@ -35,7 +35,7 @@ import { buildReplayMeta } from '../replay/persistence.js';
|
|
|
35
35
|
class CyborgHunterReplayExtension {
|
|
36
36
|
static info = {
|
|
37
37
|
name: 'cyborg-hunter-replay',
|
|
38
|
-
version: '0.
|
|
38
|
+
version: '0.9.1',
|
|
39
39
|
data: {} // per-trial return is {}; session meta goes via addProperties
|
|
40
40
|
};
|
|
41
41
|
|
|
@@ -10,7 +10,7 @@ class CyborgHunterExtension {
|
|
|
10
10
|
// and package.json). Hand-bumped on each release; if you forget, the
|
|
11
11
|
// jsPsych developer console shows a stale number — the actual version
|
|
12
12
|
// attached to data is read from window.CyborgHunter.VERSION at runtime.
|
|
13
|
-
version: '0.
|
|
13
|
+
version: '0.9.1',
|
|
14
14
|
data: { integrity: { type: 'object' } }
|
|
15
15
|
};
|
|
16
16
|
|
package/src/shared/constants.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Default thresholds and preset configurations for Cyborg Hunter.
|
|
3
3
|
// Single source of truth — used by both the browser library and CLI report tool.
|
|
4
4
|
|
|
5
|
-
export const VERSION = "0.
|
|
5
|
+
export const VERSION = "0.9.1";
|
|
6
6
|
|
|
7
7
|
// Default detection thresholds shared across presets.
|
|
8
8
|
// Researchers can override any value at init() time.
|
package/src/shared/schema.js
CHANGED
|
@@ -34,6 +34,7 @@ export const TRIAL_REPORT_FIELDS = {
|
|
|
34
34
|
export const DEFAULT_CLI_CONFIG = {
|
|
35
35
|
dataDir: "./data",
|
|
36
36
|
replayDir: null, // replay artifacts dir; defaults to dataDir
|
|
37
|
+
scoreWeights: null, // report-score weights, merged per key onto the defaults (cli/analyzers/score-weights.js)
|
|
37
38
|
filePattern: "*.json",
|
|
38
39
|
participantIdField: "participantId",
|
|
39
40
|
trialIdField: "trialId",
|
package/src/shared/validation.js
CHANGED
|
@@ -95,7 +95,7 @@ function safeStr(v) {
|
|
|
95
95
|
* Finds the closest matching key using Levenshtein distance.
|
|
96
96
|
* Returns null if no key is within edit distance 3 (too different to suggest).
|
|
97
97
|
*/
|
|
98
|
-
function findClosestKey(input, keys) {
|
|
98
|
+
export function findClosestKey(input, keys) {
|
|
99
99
|
let best = null;
|
|
100
100
|
let bestDist = Infinity;
|
|
101
101
|
for (const key of keys) {
|