mover-os 4.7.7 → 4.7.9
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 +34 -24
- package/install.js +2868 -251
- package/package.json +15 -3
- package/src/dashboard/build.js +1541 -0
- package/src/dashboard/dashboard.js +276 -0
- package/src/dashboard/index.js +319 -0
- package/src/dashboard/lib/activation-log.js +297 -0
- package/src/dashboard/lib/active-context-parser.js +189 -0
- package/src/dashboard/lib/agent-command.js +93 -0
- package/src/dashboard/lib/agent-detect.js +255 -0
- package/src/dashboard/lib/agent-detector.js +92 -0
- package/src/dashboard/lib/agent-session.js +483 -0
- package/src/dashboard/lib/anti-identity-detector.js +116 -0
- package/src/dashboard/lib/approval-registry.js +170 -0
- package/src/dashboard/lib/auto-learnings-parser.js +183 -0
- package/src/dashboard/lib/cli-usage-parser.js +92 -0
- package/src/dashboard/lib/config-parser.js +109 -0
- package/src/dashboard/lib/connect-recommender.js +131 -0
- package/src/dashboard/lib/correlations-parser.js +231 -0
- package/src/dashboard/lib/daily-note-resolver.js +228 -0
- package/src/dashboard/lib/date-utils.js +43 -0
- package/src/dashboard/lib/distribution-parser.js +137 -0
- package/src/dashboard/lib/dossier-parser.js +64 -0
- package/src/dashboard/lib/drift-history.js +88 -0
- package/src/dashboard/lib/drift-score.js +119 -0
- package/src/dashboard/lib/engine-default-fingerprints.json +53 -0
- package/src/dashboard/lib/engine-health.js +173 -0
- package/src/dashboard/lib/engine-writer.js +1831 -0
- package/src/dashboard/lib/execution-plan.js +125 -0
- package/src/dashboard/lib/experiments-parser.js +429 -0
- package/src/dashboard/lib/feed-parser.js +294 -0
- package/src/dashboard/lib/forked-future.js +60 -0
- package/src/dashboard/lib/goal-forecast.js +427 -0
- package/src/dashboard/lib/goals-parser.js +67 -0
- package/src/dashboard/lib/health-protocols-parser.js +133 -0
- package/src/dashboard/lib/hook-activity.js +48 -0
- package/src/dashboard/lib/hook-indexer.js +169 -0
- package/src/dashboard/lib/hourly-activity-parser.js +143 -0
- package/src/dashboard/lib/identity-parser.js +85 -0
- package/src/dashboard/lib/ingestion.js +418 -0
- package/src/dashboard/lib/library-indexer-v2.js +226 -0
- package/src/dashboard/lib/library-indexer.js +105 -0
- package/src/dashboard/lib/library-search.js +290 -0
- package/src/dashboard/lib/log-activation.sh +61 -0
- package/src/dashboard/lib/memory-curator.js +97 -0
- package/src/dashboard/lib/memory-gardener.js +177 -0
- package/src/dashboard/lib/memory-gepa.js +102 -0
- package/src/dashboard/lib/memory-index.js +519 -0
- package/src/dashboard/lib/memory-rerank.js +72 -0
- package/src/dashboard/lib/memory-text.js +136 -0
- package/src/dashboard/lib/metrics-log-parser.js +76 -0
- package/src/dashboard/lib/moves-usage-parser.js +184 -0
- package/src/dashboard/lib/onboarding-forge.js +70 -0
- package/src/dashboard/lib/override-outcome-parser.js +68 -0
- package/src/dashboard/lib/override-summary.js +73 -0
- package/src/dashboard/lib/paths.js +192 -0
- package/src/dashboard/lib/pattern-manifest-loader.js +29 -0
- package/src/dashboard/lib/phantom-strategy.js +129 -0
- package/src/dashboard/lib/pid-markers.js +80 -0
- package/src/dashboard/lib/project-scanner.js +121 -0
- package/src/dashboard/lib/promise-wall.js +88 -0
- package/src/dashboard/lib/record-score.js +173 -0
- package/src/dashboard/lib/redaction.js +140 -0
- package/src/dashboard/lib/refusal-parser.js +44 -0
- package/src/dashboard/lib/regenerate-manifest.js +206 -0
- package/src/dashboard/lib/rewind-snapshots.js +689 -0
- package/src/dashboard/lib/roast-wall-parser.js +200 -0
- package/src/dashboard/lib/run-registry.js +286 -0
- package/src/dashboard/lib/safe-write.js +63 -0
- package/src/dashboard/lib/session-log-parser.js +145 -0
- package/src/dashboard/lib/session-time-parser.js +158 -0
- package/src/dashboard/lib/skill-index.js +171 -0
- package/src/dashboard/lib/skill-indexer.js +118 -0
- package/src/dashboard/lib/skill-recommender.js +689 -0
- package/src/dashboard/lib/state-core/backfill.js +298 -0
- package/src/dashboard/lib/state-core/dryrun.js +188 -0
- package/src/dashboard/lib/state-core/event-log.js +615 -0
- package/src/dashboard/lib/state-core/events.js +265 -0
- package/src/dashboard/lib/state-core/projections.js +376 -0
- package/src/dashboard/lib/state-core/start-close.js +162 -0
- package/src/dashboard/lib/state-core/trial.js +96 -0
- package/src/dashboard/lib/strategy-parser.js +248 -0
- package/src/dashboard/lib/strategy-protocol-parser.js +135 -0
- package/src/dashboard/lib/streak-parser.js +95 -0
- package/src/dashboard/lib/suggested-now.js +254 -0
- package/src/dashboard/lib/tool-awareness.js +125 -0
- package/src/dashboard/lib/transcript-parser.js +371 -0
- package/src/dashboard/lib/vault-graph-parser.js +205 -0
- package/src/dashboard/lib/view-generator.js +163 -0
- package/src/dashboard/lib/voice-dna-parser.js +57 -0
- package/src/dashboard/lib/walkthrough-script.js +140 -0
- package/src/dashboard/lib/workflow-graph-parser.js +170 -0
- package/src/dashboard/lib/workflow-library-parser.js +146 -0
- package/src/dashboard/server.js +2402 -0
- package/src/dashboard/shortcut.js +284 -0
- package/src/dashboard/static/setup-poc.html +306 -0
- package/src/dashboard/static/walkthrough-poc.html +580 -0
- package/src/dashboard/styles.css +1201 -0
- package/src/dashboard/templates/index.html +278 -0
- package/src/dashboard/ui/dist/assets/hanken-grotesk-latin-ext-wght-normal-Dg-wlmqe.woff2 +0 -0
- package/src/dashboard/ui/dist/assets/hanken-grotesk-latin-wght-normal-CaVRRdDk.woff2 +0 -0
- package/src/dashboard/ui/dist/assets/hanken-grotesk-vietnamese-wght-normal-CHiFlh_0.woff2 +0 -0
- package/src/dashboard/ui/dist/assets/hero.png +0 -0
- package/src/dashboard/ui/dist/assets/index-ByVKPvLf.js +34 -0
- package/src/dashboard/ui/dist/assets/index-CCoKjUcC.js +161 -0
- package/src/dashboard/ui/dist/assets/index-CZWNQDt5.css +1 -0
- package/src/dashboard/ui/dist/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/src/dashboard/ui/dist/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/src/dashboard/ui/dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/src/dashboard/ui/dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/src/dashboard/ui/dist/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/src/dashboard/ui/dist/assets/mover-system.css +62 -0
- package/src/dashboard/ui/dist/assets/xterm-CqkleIqs.js +1 -0
- package/src/dashboard/ui/dist/icon.svg +4 -0
- package/src/dashboard/ui/dist/index.html +18 -0
- package/src/dashboard/ui/dist/manifest.webmanifest +1 -0
- package/src/dashboard/ui/dist/registerSW.js +1 -0
- package/src/dashboard/ui/dist/sw.js +1 -0
- package/src/dashboard/ui/dist/workbox-39fa566e.js +1 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
const { safeListDir, safeRead, safeStat, safeReadJson, exists } = require("./paths");
|
|
7
|
+
|
|
8
|
+
// Index Mover OS hook scripts and their registration state.
|
|
9
|
+
//
|
|
10
|
+
// Scripts live in <bundle>/src/hooks/. We try to locate the bundle:
|
|
11
|
+
// 1. Walking up from this file (lib/ → dashboard/ → src/ → bundle root)
|
|
12
|
+
// 2. Default vault layout <vault>/01_Projects/Mover OS Bundle/src/hooks/
|
|
13
|
+
//
|
|
14
|
+
// Registration state is read from ~/.claude/settings.json hooks block.
|
|
15
|
+
//
|
|
16
|
+
// Last-fired timestamps come from ~/.claude/tmp/mover_<hook>_<id>.* markers.
|
|
17
|
+
|
|
18
|
+
const HOOK_EVENTS = [
|
|
19
|
+
"SessionStart",
|
|
20
|
+
"Stop",
|
|
21
|
+
"PreToolUse",
|
|
22
|
+
"PostToolUse",
|
|
23
|
+
"PreCompact",
|
|
24
|
+
"UserPromptSubmit",
|
|
25
|
+
"SubagentStop",
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
function bundleHooksDir() {
|
|
29
|
+
// From src/dashboard/lib/hook-indexer.js, walk up to bundle root then into src/hooks
|
|
30
|
+
// lib/hook-indexer.js → lib → dashboard → src → bundle root
|
|
31
|
+
const candidate = path.resolve(__dirname, "..", "..", "hooks");
|
|
32
|
+
if (exists(candidate)) return candidate;
|
|
33
|
+
// Fallback: ~/.claude/hooks (if installed there)
|
|
34
|
+
const alt = path.join(os.homedir(), ".claude", "hooks");
|
|
35
|
+
if (exists(alt)) return alt;
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function claudeSettingsPath() {
|
|
40
|
+
return path.join(os.homedir(), ".claude", "settings.json");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function claudeTmpDir() {
|
|
44
|
+
return path.join(os.homedir(), ".claude", "tmp");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Extract a one-line description from the first commented lines of a script.
|
|
48
|
+
function extractDescription(content) {
|
|
49
|
+
if (!content) return "";
|
|
50
|
+
const lines = content.split(/\r?\n/);
|
|
51
|
+
// For .sh: lines start with #
|
|
52
|
+
// For .js: lines start with // or /* */
|
|
53
|
+
for (let i = 0; i < Math.min(20, lines.length); i++) {
|
|
54
|
+
const line = lines[i].trim();
|
|
55
|
+
if (!line) continue;
|
|
56
|
+
if (line.startsWith("#!")) continue;
|
|
57
|
+
if (line.startsWith("'use strict")) continue;
|
|
58
|
+
// Shell comment
|
|
59
|
+
const sh = line.match(/^#\s*(.+)$/);
|
|
60
|
+
if (sh && sh[1].length > 5) return sh[1].trim().slice(0, 140);
|
|
61
|
+
// JS line comment
|
|
62
|
+
const js = line.match(/^\/\/\s*(.+)$/);
|
|
63
|
+
if (js && js[1].length > 5) return js[1].trim().slice(0, 140);
|
|
64
|
+
// JS block comment start
|
|
65
|
+
const block = line.match(/^\/\*\s*(.+?)\s*\*?\/?$/);
|
|
66
|
+
if (block && block[1].length > 5) return block[1].trim().slice(0, 140);
|
|
67
|
+
}
|
|
68
|
+
return "";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function readRegisteredHooks() {
|
|
72
|
+
const settings = safeReadJson(claudeSettingsPath());
|
|
73
|
+
if (!settings || !settings.hooks) return { byEvent: {}, allPaths: new Set() };
|
|
74
|
+
|
|
75
|
+
const byEvent = {};
|
|
76
|
+
const allPaths = new Set();
|
|
77
|
+
|
|
78
|
+
for (const event of Object.keys(settings.hooks)) {
|
|
79
|
+
const entries = settings.hooks[event];
|
|
80
|
+
if (!Array.isArray(entries)) continue;
|
|
81
|
+
byEvent[event] = [];
|
|
82
|
+
for (const entry of entries) {
|
|
83
|
+
// shape: { matcher?: string, hooks: [{ type, command }] }
|
|
84
|
+
const hooks = Array.isArray(entry.hooks) ? entry.hooks : [];
|
|
85
|
+
for (const h of hooks) {
|
|
86
|
+
if (typeof h.command === "string") {
|
|
87
|
+
// Extract script filename from command
|
|
88
|
+
const scriptMatch = h.command.match(/([^\s"'\/]+\.(sh|js))/i);
|
|
89
|
+
if (scriptMatch) {
|
|
90
|
+
const filename = scriptMatch[1];
|
|
91
|
+
byEvent[event].push({ filename, command: h.command, matcher: entry.matcher || null });
|
|
92
|
+
allPaths.add(filename);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { byEvent, allPaths };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function readLastFired() {
|
|
103
|
+
const tmp = claudeTmpDir();
|
|
104
|
+
if (!safeStat(tmp)) return {};
|
|
105
|
+
const markers = safeListDir(tmp, { filesOnly: true });
|
|
106
|
+
const lastFired = {};
|
|
107
|
+
for (const m of markers) {
|
|
108
|
+
if (!m.name.startsWith("mover_")) continue;
|
|
109
|
+
const stat = safeStat(m.path);
|
|
110
|
+
if (!stat) continue;
|
|
111
|
+
// Marker filename heuristic: mover_<hookname>_<id>.<ext>
|
|
112
|
+
const m2 = m.name.match(/^mover_([a-z0-9-]+)_/i);
|
|
113
|
+
if (m2) {
|
|
114
|
+
const hookKey = m2[1];
|
|
115
|
+
const t = stat.mtime.getTime();
|
|
116
|
+
if (!lastFired[hookKey] || lastFired[hookKey] < t) {
|
|
117
|
+
lastFired[hookKey] = t;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return lastFired;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function indexHooks() {
|
|
125
|
+
const hooksDir = bundleHooksDir();
|
|
126
|
+
if (!hooksDir) {
|
|
127
|
+
return { available: false, count: 0, byEvent: {}, registered: [], unregistered: [] };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const entries = safeListDir(hooksDir, { filesOnly: true });
|
|
131
|
+
const scripts = entries.filter(e => e.name.endsWith(".sh") || e.name.endsWith(".js"));
|
|
132
|
+
|
|
133
|
+
const { byEvent: regByEvent, allPaths: registeredPaths } = readRegisteredHooks();
|
|
134
|
+
const lastFired = readLastFired();
|
|
135
|
+
|
|
136
|
+
const all = scripts.map(s => {
|
|
137
|
+
const content = safeRead(s.path);
|
|
138
|
+
const description = extractDescription(content);
|
|
139
|
+
const stat = safeStat(s.path);
|
|
140
|
+
const isRegistered = registeredPaths.has(s.name);
|
|
141
|
+
// Derive a "key" matching the marker convention (script stem)
|
|
142
|
+
const stem = s.name.replace(/\.(sh|js)$/, "");
|
|
143
|
+
const lastFiredAt = lastFired[stem] || null;
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
filename: s.name,
|
|
147
|
+
path: s.path,
|
|
148
|
+
type: s.name.endsWith(".js") ? "js" : "sh",
|
|
149
|
+
description,
|
|
150
|
+
registered: isRegistered,
|
|
151
|
+
lastFiredAt,
|
|
152
|
+
lastModified: stat ? stat.mtime.getTime() : null,
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
all.sort((a, b) => a.filename.localeCompare(b.filename));
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
available: true,
|
|
160
|
+
count: all.length,
|
|
161
|
+
byEvent: regByEvent,
|
|
162
|
+
registered: all.filter(h => h.registered),
|
|
163
|
+
unregistered: all.filter(h => !h.registered),
|
|
164
|
+
all,
|
|
165
|
+
events: HOOK_EVENTS,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = { indexHooks };
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { dailiesDir } = require("./paths");
|
|
6
|
+
const { localDateKey } = require("./date-utils");
|
|
7
|
+
|
|
8
|
+
// hourly-activity-parser.js — a 24-hour readiness-curve UI pattern applied
|
|
9
|
+
// to behavioral data.
|
|
10
|
+
//
|
|
11
|
+
// The reference pattern plots biometric readiness on a 24-hour curve. We
|
|
12
|
+
// don't have biometrics in the vault — we have BEHAVIORAL ACTIVITY: how
|
|
13
|
+
// often the user actually
|
|
14
|
+
// works (sessions, log entries) at each hour. That density curve answers the
|
|
15
|
+
// same question ("when am I sharpest?") from a different angle: when do you
|
|
16
|
+
// actually get things done historically.
|
|
17
|
+
//
|
|
18
|
+
// Returns: { available, hours: [{ hour: 0..23, count, isPeak }], peakHour,
|
|
19
|
+
// todayHours: [{ hour, count }], events: [{ hour, text, date }] }
|
|
20
|
+
//
|
|
21
|
+
// Sources:
|
|
22
|
+
// - Daily Notes ### HH:MM entries inside ## Session Log sections
|
|
23
|
+
// - Last 14 days (2 full weeks for a stable curve)
|
|
24
|
+
|
|
25
|
+
function compute({ vault, daysLookback = 14 } = {}) {
|
|
26
|
+
if (!vault) return { available: false, hours: [], peakHour: null, todayHours: [], events: [] };
|
|
27
|
+
|
|
28
|
+
const dDir = dailiesDir(vault);
|
|
29
|
+
if (!fs.existsSync(dDir)) return { available: false, hours: [], peakHour: null, todayHours: [], events: [] };
|
|
30
|
+
|
|
31
|
+
// Per-hour aggregated counts across the lookback window
|
|
32
|
+
const hourCounts = new Array(24).fill(0);
|
|
33
|
+
// Today's specific hour counts (for the bright overlay line)
|
|
34
|
+
const todayCounts = new Array(24).fill(0);
|
|
35
|
+
// Notable session-log events to plot as markers
|
|
36
|
+
const events = [];
|
|
37
|
+
|
|
38
|
+
const cutoff = Date.now() - daysLookback * 24 * 3600 * 1000;
|
|
39
|
+
const todayKey = localDateKey();
|
|
40
|
+
|
|
41
|
+
let monthDirs;
|
|
42
|
+
try {
|
|
43
|
+
monthDirs = fs.readdirSync(dDir).filter(d => /^\d{4}-\d{2}$/.test(d)).sort().reverse();
|
|
44
|
+
} catch (_) {
|
|
45
|
+
return { available: false, hours: [], peakHour: null, todayHours: [], events: [] };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let scanned = 0;
|
|
49
|
+
outer: for (const mDir of monthDirs) {
|
|
50
|
+
if (scanned >= daysLookback) break;
|
|
51
|
+
const monthPath = path.join(dDir, mDir);
|
|
52
|
+
let files;
|
|
53
|
+
try {
|
|
54
|
+
files = fs.readdirSync(monthPath).filter(f => /^Daily - \d{4}-\d{2}-\d{2}\.md$/.test(f)).sort().reverse();
|
|
55
|
+
} catch (_) { continue; }
|
|
56
|
+
for (const f of files) {
|
|
57
|
+
if (scanned >= daysLookback) break outer;
|
|
58
|
+
const m = f.match(/(\d{4})-(\d{2})-(\d{2})/);
|
|
59
|
+
if (!m) continue;
|
|
60
|
+
const dayKey = `${m[1]}-${m[2]}-${m[3]}`;
|
|
61
|
+
const dayTs = Date.parse(dayKey);
|
|
62
|
+
if (!Number.isFinite(dayTs) || dayTs < cutoff) continue;
|
|
63
|
+
const isToday = dayKey === todayKey;
|
|
64
|
+
scanned++;
|
|
65
|
+
|
|
66
|
+
let text;
|
|
67
|
+
try { text = fs.readFileSync(path.join(monthPath, f), "utf8"); }
|
|
68
|
+
catch (_) { continue; }
|
|
69
|
+
|
|
70
|
+
// Find the Session Log section. Mover OS uses 3 formats:
|
|
71
|
+
// ### Session Log [ATTENDED] — ~02:03-14:37 [May 13] [Claude Code] [Mover OS Bundle] [COMPACTED]
|
|
72
|
+
// ### Session Log [AUTO-STUB] — ~22:21 [Claude Code] [Mover OS Bundle] [hash]
|
|
73
|
+
// ### ~14:32 - Event Title (legacy / nested events)
|
|
74
|
+
// T242 bug hunt fix (B#11): `text.indexOf("## Session Log")` was a
|
|
75
|
+
// substring search — `### Session Log` (level 3, also valid) contains
|
|
76
|
+
// the literal `## Session Log` as a substring. Conversely, level-2
|
|
77
|
+
// `## Session Log` headers with no nested `###` sub-entries were
|
|
78
|
+
// matched by the section locator but the entries regex below
|
|
79
|
+
// returned nothing. Anchor to level-2 explicitly. Fallback: if no
|
|
80
|
+
// level-2 found, try level-3 (some daily notes use only ### form).
|
|
81
|
+
let slMatch = text.match(/^##\s+Session Log/m);
|
|
82
|
+
if (!slMatch) slMatch = text.match(/^###\s+Session Log/m);
|
|
83
|
+
if (!slMatch || typeof slMatch.index !== "number") continue;
|
|
84
|
+
const section = text.slice(slMatch.index);
|
|
85
|
+
|
|
86
|
+
// Format A: Session Log header with time range — count the START hour
|
|
87
|
+
const reSession = /^###\s+Session Log\s+\[[^\]]+\]\s+—\s+~?(\d{1,2}):(\d{2})(?:[-–]\s*(\d{1,2}):(\d{2}))?\s*(?:\[[^\]]+\])?\s*(?:\[([^\]]+)\])?/gm;
|
|
88
|
+
let mm;
|
|
89
|
+
while ((mm = reSession.exec(section)) !== null) {
|
|
90
|
+
const startHour = parseInt(mm[1], 10);
|
|
91
|
+
if (!Number.isFinite(startHour) || startHour < 0 || startHour > 23) continue;
|
|
92
|
+
hourCounts[startHour]++;
|
|
93
|
+
if (isToday) todayCounts[startHour]++;
|
|
94
|
+
const tag = mm[5] ? `[${mm[5]}]` : "";
|
|
95
|
+
const range = mm[3] ? ` ${mm[1]}:${mm[2]}-${mm[3]}:${mm[4]}` : ` ${mm[1]}:${mm[2]}`;
|
|
96
|
+
events.push({ hour: startHour, date: dayKey, text: `Session${range}${tag ? " " + tag : ""}`.trim(), isToday });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Format B: Legacy nested event headers — `### HH:MM - Title`
|
|
100
|
+
const reLegacy = /^###\s+~?(\d{1,2}):(\d{2})\s+-\s+(.+)$/gm;
|
|
101
|
+
while ((mm = reLegacy.exec(section)) !== null) {
|
|
102
|
+
const hour = parseInt(mm[1], 10);
|
|
103
|
+
if (!Number.isFinite(hour) || hour < 0 || hour > 23) continue;
|
|
104
|
+
const title = (mm[3] || "").trim();
|
|
105
|
+
// Skip Session Log headers re-matched (they contain " — ")
|
|
106
|
+
if (title.startsWith("Session Log")) continue;
|
|
107
|
+
hourCounts[hour]++;
|
|
108
|
+
if (isToday) todayCounts[hour]++;
|
|
109
|
+
if (title.length < 80) {
|
|
110
|
+
events.push({ hour, date: dayKey, text: title, isToday });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Compute peak hour
|
|
117
|
+
let peakHour = null;
|
|
118
|
+
let peakCount = 0;
|
|
119
|
+
for (let h = 0; h < 24; h++) {
|
|
120
|
+
if (hourCounts[h] > peakCount) {
|
|
121
|
+
peakCount = hourCounts[h];
|
|
122
|
+
peakHour = h;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const hours = hourCounts.map((c, h) => ({ hour: h, count: c, isPeak: h === peakHour && peakCount > 0 }));
|
|
127
|
+
const todayHours = todayCounts.map((c, h) => ({ hour: h, count: c }));
|
|
128
|
+
|
|
129
|
+
// Keep last 12 today-events for marker overlay
|
|
130
|
+
const todayEvents = events.filter(e => e.isToday).slice(0, 12);
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
available: scanned > 0,
|
|
134
|
+
daysScanned: scanned,
|
|
135
|
+
hours,
|
|
136
|
+
peakHour,
|
|
137
|
+
peakCount,
|
|
138
|
+
todayHours,
|
|
139
|
+
events: todayEvents
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = { compute };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { identityPrimePath, safeRead, safeStat } = require("./paths");
|
|
4
|
+
|
|
5
|
+
// Parse Identity_Prime.md. Extracts archetype, core drive, fatal flaw, the law, anti-identity, code lines.
|
|
6
|
+
|
|
7
|
+
function parseIdentity(vault) {
|
|
8
|
+
const filePath = identityPrimePath(vault);
|
|
9
|
+
const text = safeRead(filePath);
|
|
10
|
+
const stat = safeStat(filePath);
|
|
11
|
+
if (!text) return { available: false, filePath };
|
|
12
|
+
|
|
13
|
+
const out = { available: true, filePath, lastUpdated: stat ? stat.mtime : null, raw: text, fields: {} };
|
|
14
|
+
|
|
15
|
+
// Bold field extraction
|
|
16
|
+
const re = /\*\*([^*]+):\*\*\s*([^\n]+)/g;
|
|
17
|
+
let m;
|
|
18
|
+
while ((m = re.exec(text)) !== null) {
|
|
19
|
+
const k = m[1].trim().toLowerCase().replace(/[^a-z0-9_]+/g, "_").replace(/^_|_$/g, "");
|
|
20
|
+
out.fields[k] = m[2].trim();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Personal name from the H1 ("# IDENTITY PRIME: <name>") — the onboarding
|
|
24
|
+
// engine-writer writes the user's own answer there. Persona titles
|
|
25
|
+
// ("THE MOVER") and the unanswered placeholder ("You") are not names.
|
|
26
|
+
const h1 = text.match(/^#[^\n]*IDENTITY PRIME:\s*(.+?)\s*$/mi);
|
|
27
|
+
const h1name = h1 ? h1[1].trim() : "";
|
|
28
|
+
out.name = h1name && !/^the\s/i.test(h1name) && h1name.toLowerCase() !== "you" ? h1name : null;
|
|
29
|
+
|
|
30
|
+
// Common keys
|
|
31
|
+
out.archetype = out.fields.archetype || null;
|
|
32
|
+
out.coreDrive = out.fields.core_drive || null;
|
|
33
|
+
out.fatalFlaw = out.fields.fatal_flaw || null;
|
|
34
|
+
out.theLaw = out.fields.the_law || null;
|
|
35
|
+
out.antiIdentity = out.fields.anti_identity || null;
|
|
36
|
+
out.psychFirewall = out.fields.psychological_firewall || null;
|
|
37
|
+
|
|
38
|
+
// Sections
|
|
39
|
+
const sections = splitSections(text);
|
|
40
|
+
out.sections = sections;
|
|
41
|
+
out.code = extractListUnder(sections, "the code");
|
|
42
|
+
out.evolvedSelf = sectionBody(sections, "evolved self");
|
|
43
|
+
out.antiIdentityBody = sectionBody(sections, "anti-identity") || sectionBody(sections, "the mediocre");
|
|
44
|
+
out.primaryResistanceForms = extractListUnder(sections, "primary resistance forms");
|
|
45
|
+
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function splitSections(text) {
|
|
50
|
+
const sections = {};
|
|
51
|
+
const lines = text.split("\n");
|
|
52
|
+
let currentTitle = null;
|
|
53
|
+
let buf = [];
|
|
54
|
+
for (const line of lines) {
|
|
55
|
+
const h = line.match(/^##+\s+(.+?)\s*$/);
|
|
56
|
+
if (h) {
|
|
57
|
+
if (currentTitle) sections[currentTitle.toLowerCase()] = buf.join("\n");
|
|
58
|
+
currentTitle = h[1];
|
|
59
|
+
buf = [];
|
|
60
|
+
} else if (currentTitle) {
|
|
61
|
+
buf.push(line);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (currentTitle) sections[currentTitle.toLowerCase()] = buf.join("\n");
|
|
65
|
+
return sections;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function sectionBody(sections, needle) {
|
|
69
|
+
for (const [title, body] of Object.entries(sections)) {
|
|
70
|
+
if (title.includes(needle.toLowerCase())) return body;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function extractListUnder(sections, needle) {
|
|
76
|
+
const body = sectionBody(sections, needle);
|
|
77
|
+
if (!body) return [];
|
|
78
|
+
return body.split("\n")
|
|
79
|
+
.map(l => l.match(/^\s*[-*\d.]+\s+(.+)$/))
|
|
80
|
+
.filter(Boolean)
|
|
81
|
+
.map(m => m[1].trim())
|
|
82
|
+
.slice(0, 12);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = { parseIdentity };
|