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,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const os = require("os");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
|
|
7
|
+
function moverDir() {
|
|
8
|
+
return path.join(os.homedir(), ".mover");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function moverConfigPath() {
|
|
12
|
+
return path.join(moverDir(), "config.json");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function moverPatternManifestPath() {
|
|
16
|
+
return path.join(moverDir(), "pattern-manifest.json");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function dashboardOutDir() {
|
|
20
|
+
return path.join(moverDir(), "dashboard");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function dashboardIndexPath() {
|
|
24
|
+
return path.join(dashboardOutDir(), "index.html");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function dashboardBuildLogPath() {
|
|
28
|
+
return path.join(dashboardOutDir(), "build.log");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function dashboardSnoozesPath() {
|
|
32
|
+
return path.join(dashboardOutDir(), "snoozes.json");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Vault-relative paths (caller passes vault root)
|
|
36
|
+
function engineDir(vault) { return path.join(vault, "02_Areas", "Engine"); }
|
|
37
|
+
|
|
38
|
+
function engineFile(vault, name) { return path.join(engineDir(vault), name); }
|
|
39
|
+
|
|
40
|
+
function dailiesDir(vault) { return path.join(engineDir(vault), "Dailies"); }
|
|
41
|
+
|
|
42
|
+
function weeklyReviewsDir(vault) { return path.join(engineDir(vault), "Weekly Reviews"); }
|
|
43
|
+
|
|
44
|
+
function projectsDir(vault) { return path.join(vault, "01_Projects"); }
|
|
45
|
+
|
|
46
|
+
function libraryDir(vault) { return path.join(vault, "03_Library"); }
|
|
47
|
+
|
|
48
|
+
function inboxDir(vault) { return path.join(vault, "00_Inbox"); }
|
|
49
|
+
|
|
50
|
+
function archivesDir(vault) { return path.join(vault, "04_Archives"); }
|
|
51
|
+
|
|
52
|
+
// Engine file shortcuts
|
|
53
|
+
function activeContextPath(vault) { return engineFile(vault, "Active_Context.md"); }
|
|
54
|
+
function strategyPath(vault) { return engineFile(vault, "Strategy.md"); }
|
|
55
|
+
function identityPrimePath(vault) { return engineFile(vault, "Identity_Prime.md"); }
|
|
56
|
+
function goalsPath(vault) { return engineFile(vault, "Goals.md"); }
|
|
57
|
+
function autoLearningsPath(vault) { return engineFile(vault, "Auto_Learnings.md"); }
|
|
58
|
+
// Dossier filename varies by user: Mover_Dossier.md (default), Architect_Dossier.md, Red_Team_Dossier.md.
|
|
59
|
+
// Returns the first existing file from candidate list, or default if none exist.
|
|
60
|
+
function moverDossierPath(vault) {
|
|
61
|
+
const candidates = ["Mover_Dossier.md", "Architect_Dossier.md", "Red_Team_Dossier.md"];
|
|
62
|
+
for (const name of candidates) {
|
|
63
|
+
const p = engineFile(vault, name);
|
|
64
|
+
try { if (fs.existsSync(p)) return p; } catch (_) {}
|
|
65
|
+
}
|
|
66
|
+
return engineFile(vault, candidates[0]);
|
|
67
|
+
}
|
|
68
|
+
function voiceDnaPath(vault) { return engineFile(vault, "Voice_DNA.md"); }
|
|
69
|
+
function metricsLogPath(vault) { return engineFile(vault, "Metrics_Log.md"); }
|
|
70
|
+
function healthAnalysisPath(vault) { return engineFile(vault, "Health_Analysis_Full.md"); }
|
|
71
|
+
|
|
72
|
+
// Daily note resolver — supports YYYY-MM/Daily - YYYY-MM-DD.md format
|
|
73
|
+
function dailyNotePath(vault, date) {
|
|
74
|
+
const yyyy = date.getFullYear();
|
|
75
|
+
const mm = String(date.getMonth() + 1).padStart(2, "0");
|
|
76
|
+
const dd = String(date.getDate()).padStart(2, "0");
|
|
77
|
+
return path.join(dailiesDir(vault), `${yyyy}-${mm}`, `Daily - ${yyyy}-${mm}-${dd}.md`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Library subdirs
|
|
81
|
+
function libraryCheatsheetsDir(vault) { return path.join(libraryDir(vault), "Cheatsheets"); }
|
|
82
|
+
function libraryPrinciplesDir(vault) { return path.join(libraryDir(vault), "Principles"); }
|
|
83
|
+
function librarySopsDir(vault) { return path.join(libraryDir(vault), "SOPs"); }
|
|
84
|
+
function libraryEntitiesDir(vault) { return path.join(libraryDir(vault), "Entities"); }
|
|
85
|
+
function libraryEntitiesPeopleDir(vault) { return path.join(libraryEntitiesDir(vault), "People"); }
|
|
86
|
+
function libraryEntitiesOrgsDir(vault) { return path.join(libraryEntitiesDir(vault), "Organizations"); }
|
|
87
|
+
function libraryEntitiesPlacesDir(vault) { return path.join(libraryEntitiesDir(vault), "Places"); }
|
|
88
|
+
function libraryEntitiesDecisionsDir(vault) { return path.join(libraryEntitiesDir(vault), "Decisions"); }
|
|
89
|
+
function libraryInputsDir(vault) { return path.join(libraryDir(vault), "Inputs"); }
|
|
90
|
+
function libraryScriptsDir(vault) { return path.join(libraryDir(vault), "Scripts"); }
|
|
91
|
+
function libraryMocsDir(vault) { return path.join(libraryDir(vault), "MOCs"); }
|
|
92
|
+
|
|
93
|
+
function exists(p) {
|
|
94
|
+
try { fs.accessSync(p); return true; } catch { return false; }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Resolve the vault root with NO hardcoded/PII path (so lib CLI entrypoints never ship a machine
|
|
98
|
+
// path): env MOVER_VAULT → ~/.mover/config.json vaultPath → walk up from cwd for 02_Areas/Engine → cwd.
|
|
99
|
+
function resolveVault() {
|
|
100
|
+
const hasEngine = (v) => { try { return !!v && fs.existsSync(path.join(v, "02_Areas", "Engine")); } catch { return false; } };
|
|
101
|
+
if (hasEngine(process.env.MOVER_VAULT)) return process.env.MOVER_VAULT;
|
|
102
|
+
let cfg = {}; try { cfg = JSON.parse(fs.readFileSync(moverConfigPath(), "utf8")) || {}; } catch {}
|
|
103
|
+
if (hasEngine(cfg.vaultPath)) return cfg.vaultPath;
|
|
104
|
+
let dir = process.cwd();
|
|
105
|
+
for (let i = 0; i < 8; i++) {
|
|
106
|
+
if (hasEngine(dir)) return dir;
|
|
107
|
+
const up = path.dirname(dir);
|
|
108
|
+
if (up === dir) break;
|
|
109
|
+
dir = up;
|
|
110
|
+
}
|
|
111
|
+
return process.env.MOVER_VAULT || cfg.vaultPath || process.cwd();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function safeRead(p, fallback = null) {
|
|
115
|
+
try { return fs.readFileSync(p, "utf8"); } catch { return fallback; }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function safeReadJson(p, fallback = null) {
|
|
119
|
+
try { return JSON.parse(fs.readFileSync(p, "utf8")); } catch { return fallback; }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function safeStat(p) {
|
|
123
|
+
try { return fs.statSync(p); } catch { return null; }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function safeListDir(p, options = {}) {
|
|
127
|
+
try {
|
|
128
|
+
let entries = fs.readdirSync(p, { withFileTypes: true });
|
|
129
|
+
if (options.dirsOnly) entries = entries.filter(e => e.isDirectory());
|
|
130
|
+
if (options.filesOnly) entries = entries.filter(e => e.isFile());
|
|
131
|
+
if (options.extension) entries = entries.filter(e => e.isFile() && e.name.endsWith(options.extension));
|
|
132
|
+
if (options.skipUnderscore) entries = entries.filter(e => !e.name.startsWith("_") && !e.name.startsWith("."));
|
|
133
|
+
return entries.map(e => ({
|
|
134
|
+
name: e.name,
|
|
135
|
+
path: path.join(p, e.name),
|
|
136
|
+
isDir: e.isDirectory(),
|
|
137
|
+
isFile: e.isFile()
|
|
138
|
+
}));
|
|
139
|
+
} catch {
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = {
|
|
145
|
+
// Mover dirs
|
|
146
|
+
moverDir,
|
|
147
|
+
moverConfigPath,
|
|
148
|
+
moverPatternManifestPath,
|
|
149
|
+
dashboardOutDir,
|
|
150
|
+
dashboardIndexPath,
|
|
151
|
+
dashboardBuildLogPath,
|
|
152
|
+
dashboardSnoozesPath,
|
|
153
|
+
// Vault dirs
|
|
154
|
+
engineDir,
|
|
155
|
+
engineFile,
|
|
156
|
+
dailiesDir,
|
|
157
|
+
weeklyReviewsDir,
|
|
158
|
+
projectsDir,
|
|
159
|
+
libraryDir,
|
|
160
|
+
inboxDir,
|
|
161
|
+
archivesDir,
|
|
162
|
+
// Engine files
|
|
163
|
+
activeContextPath,
|
|
164
|
+
strategyPath,
|
|
165
|
+
identityPrimePath,
|
|
166
|
+
goalsPath,
|
|
167
|
+
autoLearningsPath,
|
|
168
|
+
moverDossierPath,
|
|
169
|
+
voiceDnaPath,
|
|
170
|
+
metricsLogPath,
|
|
171
|
+
healthAnalysisPath,
|
|
172
|
+
dailyNotePath,
|
|
173
|
+
// Library
|
|
174
|
+
libraryCheatsheetsDir,
|
|
175
|
+
libraryPrinciplesDir,
|
|
176
|
+
librarySopsDir,
|
|
177
|
+
libraryEntitiesDir,
|
|
178
|
+
libraryEntitiesPeopleDir,
|
|
179
|
+
libraryEntitiesOrgsDir,
|
|
180
|
+
libraryEntitiesPlacesDir,
|
|
181
|
+
libraryEntitiesDecisionsDir,
|
|
182
|
+
libraryInputsDir,
|
|
183
|
+
libraryScriptsDir,
|
|
184
|
+
libraryMocsDir,
|
|
185
|
+
// Helpers
|
|
186
|
+
resolveVault,
|
|
187
|
+
exists,
|
|
188
|
+
safeRead,
|
|
189
|
+
safeReadJson,
|
|
190
|
+
safeStat,
|
|
191
|
+
safeListDir
|
|
192
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { moverPatternManifestPath, safeReadJson, safeStat } = require("./paths");
|
|
4
|
+
|
|
5
|
+
// Fast-path read of ~/.mover/pattern-manifest.json. Returns {available, manifest, lastUpdated}.
|
|
6
|
+
|
|
7
|
+
function loadPatternManifest() {
|
|
8
|
+
const filePath = moverPatternManifestPath();
|
|
9
|
+
const stat = safeStat(filePath);
|
|
10
|
+
const data = safeReadJson(filePath);
|
|
11
|
+
if (!data) return { available: false, filePath, error: "pattern-manifest.json not found" };
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
available: true,
|
|
15
|
+
filePath,
|
|
16
|
+
lastUpdated: stat ? stat.mtime : null,
|
|
17
|
+
manifest: data,
|
|
18
|
+
singleTest: data.single_test || null,
|
|
19
|
+
sacrifice: data.sacrifice || null,
|
|
20
|
+
frictionLevel: data.friction_level || "L0",
|
|
21
|
+
zombieCount: data.zombie_count || 0,
|
|
22
|
+
zombieTasks: Array.isArray(data.zombie_tasks) ? data.zombie_tasks : [],
|
|
23
|
+
activePatterns: Array.isArray(data.active_patterns) ? data.active_patterns : [],
|
|
24
|
+
banKeywords: Array.isArray(data.ban_keywords) ? data.ban_keywords : [],
|
|
25
|
+
overrideHistory: Array.isArray(data.override_history) ? data.override_history : []
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = { loadPatternManifest };
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// phantom-strategy.js — Comparator between STATED strategy (Strategy.md
|
|
4
|
+
// sacrifice + Single Test) and DEMONSTRATED behavior (manifest overrides
|
|
5
|
+
// + Auto_Learnings patterns + recent session deviations).
|
|
6
|
+
//
|
|
7
|
+
// Returns ranked PhantomRow[] with severity-encoded mismatches and source
|
|
8
|
+
// citations. Pure function: takes parsed inputs, returns array.
|
|
9
|
+
|
|
10
|
+
function compute({ strategy = null, autoLearnings = null, manifest = null, dailyHistory = null } = {}) {
|
|
11
|
+
const rows = [];
|
|
12
|
+
const now = Date.now();
|
|
13
|
+
const weekMs = 7 * 24 * 3600 * 1000;
|
|
14
|
+
const monthMs = 30 * 24 * 3600 * 1000;
|
|
15
|
+
|
|
16
|
+
// Helper: count recent override entries that touch a particular text fragment.
|
|
17
|
+
const overrides = (manifest && (manifest.overrideHistory || manifest.override_history)) || [];
|
|
18
|
+
function recentOverridesMatching(stated, sinceMs) {
|
|
19
|
+
const fragment = stated.slice(0, 24).toLowerCase();
|
|
20
|
+
return overrides.filter((o) => {
|
|
21
|
+
const t = Date.parse(o.date || o.timestamp || "");
|
|
22
|
+
if (!Number.isFinite(t) || now - t > sinceMs) return false;
|
|
23
|
+
const haystack = ((o.reason || "") + " " + (o.what || "") + " " + (o.plan_item || "")).toLowerCase();
|
|
24
|
+
return haystack.includes(fragment);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getAutoLearningPattern(...keywords) {
|
|
29
|
+
if (!autoLearnings || !autoLearnings.activePatternsConf3Plus) return null;
|
|
30
|
+
const lower = keywords.map((k) => k.toLowerCase());
|
|
31
|
+
return autoLearnings.activePatternsConf3Plus.find((p) => {
|
|
32
|
+
const desc = ((p.description || p.text || "") + " " + (p.id || "")).toLowerCase();
|
|
33
|
+
return lower.some((k) => desc.includes(k));
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function pushRow(id, stated, demonstrated, severity, sources, evidence = {}) {
|
|
38
|
+
rows.push({
|
|
39
|
+
id,
|
|
40
|
+
stated: String(stated).slice(0, 200),
|
|
41
|
+
demonstrated: String(demonstrated).slice(0, 200),
|
|
42
|
+
severity,
|
|
43
|
+
evidence: {
|
|
44
|
+
sessions: evidence.sessions || 0,
|
|
45
|
+
distGap: evidence.distGap || 0,
|
|
46
|
+
sources: sources || []
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 1) Single Test mismatch
|
|
52
|
+
const singleTest = (manifest && manifest.singleTest) || (strategy && strategy.singleTest);
|
|
53
|
+
if (singleTest) {
|
|
54
|
+
const distPattern = getAutoLearningPattern("distribution", "polish", "system architecture", "spree");
|
|
55
|
+
const matches = recentOverridesMatching(singleTest, monthMs);
|
|
56
|
+
const severity = distPattern ? "high" : matches.length > 0 ? "med" : "low";
|
|
57
|
+
pushRow(
|
|
58
|
+
"single-test",
|
|
59
|
+
singleTest,
|
|
60
|
+
distPattern
|
|
61
|
+
? `Pattern detected: ${distPattern.description || distPattern.text || "behavioral drift"}`
|
|
62
|
+
: matches.length > 0
|
|
63
|
+
? `Overridden ${matches.length}× in last 30 days`
|
|
64
|
+
: "Holding (no logged override).",
|
|
65
|
+
severity,
|
|
66
|
+
[
|
|
67
|
+
...(distPattern
|
|
68
|
+
? [{ file: "Auto_Learnings.md", line: "active_patterns", quote: distPattern.description || "", age: "live" }]
|
|
69
|
+
: []),
|
|
70
|
+
...matches.slice(0, 3).map((m) => ({
|
|
71
|
+
file: "pattern-manifest.json",
|
|
72
|
+
line: "override_history",
|
|
73
|
+
quote: m.reason || m.what || "—",
|
|
74
|
+
age: m.date || "recent"
|
|
75
|
+
}))
|
|
76
|
+
],
|
|
77
|
+
{ sessions: matches.length }
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 2) Sacrifice rules
|
|
82
|
+
const sacrifices = manifest && manifest.sacrifice
|
|
83
|
+
? (Array.isArray(manifest.sacrifice) ? manifest.sacrifice : [manifest.sacrifice])
|
|
84
|
+
: strategy && strategy.sacrifice
|
|
85
|
+
? (Array.isArray(strategy.sacrifice) ? strategy.sacrifice : [strategy.sacrifice])
|
|
86
|
+
: [];
|
|
87
|
+
|
|
88
|
+
sacrifices.slice(0, 3).forEach((s, i) => {
|
|
89
|
+
const matches = recentOverridesMatching(s, weekMs);
|
|
90
|
+
const severity = matches.length >= 2 ? "high" : matches.length === 1 ? "med" : "low";
|
|
91
|
+
pushRow(
|
|
92
|
+
`sacrifice-${i}`,
|
|
93
|
+
s,
|
|
94
|
+
matches.length > 0
|
|
95
|
+
? `Broken ${matches.length}× in last 7 days`
|
|
96
|
+
: "Holding (no override this week).",
|
|
97
|
+
severity,
|
|
98
|
+
matches.slice(0, 2).map((m) => ({
|
|
99
|
+
file: "pattern-manifest.json",
|
|
100
|
+
line: "override_history",
|
|
101
|
+
quote: m.reason || m.what || "—",
|
|
102
|
+
age: m.date || "recent"
|
|
103
|
+
})),
|
|
104
|
+
{ sessions: matches.length }
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// 3) Weekly Review hygiene (if Strategy says weekly but it's been >7d)
|
|
109
|
+
if (strategy && strategy.fields && /weekly|friday|review/i.test(JSON.stringify(strategy.fields))) {
|
|
110
|
+
// If we had analyseDay overdue in inputs, we'd surface that too. For now,
|
|
111
|
+
// skip unless we can wire engineHealth in.
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 4) Distribution-related patterns from Auto_Learnings (catch what Strategy doesn't name)
|
|
115
|
+
const distPattern = getAutoLearningPattern("system architecture spree");
|
|
116
|
+
if (distPattern && !rows.some((r) => r.id === "single-test" && r.severity === "high")) {
|
|
117
|
+
pushRow(
|
|
118
|
+
"system-spree",
|
|
119
|
+
"Distribution work over system polish.",
|
|
120
|
+
distPattern.description || distPattern.text || "System Architecture Spree pattern active.",
|
|
121
|
+
distPattern.confidence >= 4 ? "high" : "med",
|
|
122
|
+
[{ file: "Auto_Learnings.md", line: `pattern:${distPattern.id || "spree"}`, quote: distPattern.description || "", age: `conf ${distPattern.confidence || "?"}` }]
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return rows.slice(0, 6); // cap at 6 for UI density
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = { compute };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* pid-markers.js — crash-orphan bookkeeping shared by AgentSessionManager
|
|
5
|
+
* (Chat/onboarding) and RunRegistry (Run tab).
|
|
6
|
+
*
|
|
7
|
+
* Every dashboard-spawned agent child is detached into its own process group
|
|
8
|
+
* and recorded here as one marker file per pid, tagged with the owning
|
|
9
|
+
* dashboard's pid. On startup each manager reaps any group whose OWNER is
|
|
10
|
+
* gone — children escaped by a crashed dashboard (kill -9, OOM, update crash)
|
|
11
|
+
* that no graceful shutdown path could ever reach.
|
|
12
|
+
*
|
|
13
|
+
* Extracted verbatim from agent-session.js (MF2, Codex r2 hardening) so the
|
|
14
|
+
* Run tab gets the same guarantee. One file per pid: no shared-file
|
|
15
|
+
* read-modify-write race across concurrent dashboards; a corrupt write only
|
|
16
|
+
* loses its own marker. Writes are atomic (tmp + rename). Both managers share
|
|
17
|
+
* ONE directory on purpose: whichever starts first reaps the other's orphans.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const os = require("os");
|
|
21
|
+
const path = require("path");
|
|
22
|
+
const fs = require("fs");
|
|
23
|
+
|
|
24
|
+
const MARKERS_DIR = path.join(os.homedir(), ".mover", "dashboard", "agent-pids");
|
|
25
|
+
|
|
26
|
+
function markerPath(pid) { return path.join(MARKERS_DIR, String(pid) + ".json"); }
|
|
27
|
+
|
|
28
|
+
function writeMarker(pid, data) {
|
|
29
|
+
try {
|
|
30
|
+
fs.mkdirSync(MARKERS_DIR, { recursive: true });
|
|
31
|
+
const f = markerPath(pid), tmp = f + ".tmp";
|
|
32
|
+
fs.writeFileSync(tmp, JSON.stringify(data));
|
|
33
|
+
fs.renameSync(tmp, f); // atomic: a reader never sees a half-written marker
|
|
34
|
+
} catch (_) {}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function removeMarker(pid) { try { fs.unlinkSync(markerPath(pid)); } catch (_) {} }
|
|
38
|
+
|
|
39
|
+
function readMarkers() {
|
|
40
|
+
let files; try { files = fs.readdirSync(MARKERS_DIR); } catch (_) { return []; }
|
|
41
|
+
const out = [];
|
|
42
|
+
for (const fn of files) {
|
|
43
|
+
if (!fn.endsWith(".json")) continue;
|
|
44
|
+
try { out.push(JSON.parse(fs.readFileSync(path.join(MARKERS_DIR, fn), "utf8"))); } catch (_) {}
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isAlive(pid) {
|
|
50
|
+
if (!pid) return false;
|
|
51
|
+
try { process.kill(pid, 0); return true; } catch (e) { return e && e.code === "EPERM"; }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// True if ANY member of the process group still exists (not just the leader) —
|
|
55
|
+
// process.kill(-pid, 0) throws ESRCH only when the whole group is gone.
|
|
56
|
+
function groupAlive(pid) {
|
|
57
|
+
if (!pid) return false;
|
|
58
|
+
try { process.kill(-pid, 0); return true; } catch (e) { return e && e.code === "EPERM"; }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function killGroup(pid, sig) {
|
|
62
|
+
if (!pid) return false;
|
|
63
|
+
try { process.kill(-pid, sig); return true; }
|
|
64
|
+
catch (_) { try { process.kill(pid, sig); return true; } catch (__) { return false; } }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Reap any recorded group whose owning dashboard is gone. Idempotent; safe to
|
|
68
|
+
// call from every manager's constructor (they share the directory).
|
|
69
|
+
function reapOrphans() {
|
|
70
|
+
for (const m of readMarkers()) {
|
|
71
|
+
if (!m || !m.pid) continue;
|
|
72
|
+
// Keep markers whose owning dashboard is still alive (this process OR a
|
|
73
|
+
// concurrent one). Only a marker whose owner is GONE is a real orphan.
|
|
74
|
+
if (m.ownerPid && isAlive(m.ownerPid)) continue;
|
|
75
|
+
if (groupAlive(m.pid)) killGroup(m.pid, "SIGKILL"); // owner gone → orphaned group
|
|
76
|
+
removeMarker(m.pid);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = { MARKERS_DIR, writeMarker, removeMarker, readMarkers, isAlive, groupAlive, killGroup, reapOrphans };
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const { projectsDir, safeListDir, safeRead, safeStat, exists } = require("./paths");
|
|
5
|
+
|
|
6
|
+
// Scan 01_Projects/* for active projects. Returns {available, projects: [...]}.
|
|
7
|
+
// Each project has plan task counts, recent project_state.md excerpt, lastModified.
|
|
8
|
+
|
|
9
|
+
function scanProjects(vault, options = {}) {
|
|
10
|
+
const dir = projectsDir(vault);
|
|
11
|
+
const entries = safeListDir(dir, { dirsOnly: true, skipUnderscore: true });
|
|
12
|
+
const limit = options.limit || 12;
|
|
13
|
+
|
|
14
|
+
const projects = [];
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
const projectPath = entry.path;
|
|
17
|
+
const planMd = exists(path.join(projectPath, "dev", "plan.md"))
|
|
18
|
+
? path.join(projectPath, "dev", "plan.md")
|
|
19
|
+
: (exists(path.join(projectPath, "plan.md")) ? path.join(projectPath, "plan.md") : null);
|
|
20
|
+
const stateMd = exists(path.join(projectPath, "dev", "project_state.md"))
|
|
21
|
+
? path.join(projectPath, "dev", "project_state.md")
|
|
22
|
+
: (exists(path.join(projectPath, "project_state.md")) ? path.join(projectPath, "project_state.md") : null);
|
|
23
|
+
const briefMd = exists(path.join(projectPath, "dev", "project_brief.md"))
|
|
24
|
+
? path.join(projectPath, "dev", "project_brief.md")
|
|
25
|
+
: (exists(path.join(projectPath, "project_brief.md")) ? path.join(projectPath, "project_brief.md") : null);
|
|
26
|
+
|
|
27
|
+
const project = {
|
|
28
|
+
name: entry.name,
|
|
29
|
+
path: projectPath,
|
|
30
|
+
planPath: planMd,
|
|
31
|
+
statePath: stateMd,
|
|
32
|
+
briefPath: briefMd,
|
|
33
|
+
hasPlanMd: !!planMd,
|
|
34
|
+
hasStateMd: !!stateMd
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const stat = safeStat(projectPath);
|
|
38
|
+
project.lastModified = stat ? stat.mtime : null;
|
|
39
|
+
|
|
40
|
+
if (planMd) {
|
|
41
|
+
const planText = safeRead(planMd, "");
|
|
42
|
+
const tasks = parseTasks(planText);
|
|
43
|
+
project.totalTasks = tasks.total;
|
|
44
|
+
project.doneTasks = tasks.done;
|
|
45
|
+
project.unverifiedTasks = tasks.unverified;
|
|
46
|
+
project.openTasks = tasks.open;
|
|
47
|
+
project.zombieTasks = tasks.zombieCount;
|
|
48
|
+
project.recentExecutionLog = extractRecentExecutionLog(planText, 3);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (stateMd) {
|
|
52
|
+
const stateText = safeRead(stateMd, "");
|
|
53
|
+
project.stateExcerpt = extractFirstParagraph(stateText);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
projects.push(project);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
projects.sort((a, b) => {
|
|
60
|
+
const aT = a.lastModified ? a.lastModified.getTime() : 0;
|
|
61
|
+
const bT = b.lastModified ? b.lastModified.getTime() : 0;
|
|
62
|
+
return bT - aT;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// activeCount = folders touched inside the dashboard's 30-day evidence
|
|
66
|
+
// window. totalCount counts every folder ever (incl. one-off errands), which
|
|
67
|
+
// reads as a lie next to "tests that earned real time".
|
|
68
|
+
const cutoff = Date.now() - 30 * 24 * 60 * 60 * 1000;
|
|
69
|
+
const activeCount = projects.filter((p) => p.lastModified && p.lastModified.getTime() >= cutoff).length;
|
|
70
|
+
return { available: true, projects: projects.slice(0, limit), totalCount: projects.length, activeCount };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function parseTasks(text) {
|
|
74
|
+
const lines = text.split("\n");
|
|
75
|
+
let total = 0, done = 0, unverified = 0, open = 0, zombieCount = 0;
|
|
76
|
+
for (const line of lines) {
|
|
77
|
+
const m = line.match(/^\s*[-*]\s*\[([ xX~])\]/);
|
|
78
|
+
if (!m) continue;
|
|
79
|
+
total++;
|
|
80
|
+
if (m[1] === "x" || m[1] === "X") done++;
|
|
81
|
+
else if (m[1] === "~") unverified++;
|
|
82
|
+
else open++;
|
|
83
|
+
if (/\[ZOMBIE\]/i.test(line)) zombieCount++;
|
|
84
|
+
}
|
|
85
|
+
return { total, done, unverified, open, zombieCount };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function extractFirstParagraph(text) {
|
|
89
|
+
if (!text) return null;
|
|
90
|
+
const lines = text.split("\n");
|
|
91
|
+
let collecting = false;
|
|
92
|
+
let buf = [];
|
|
93
|
+
for (const line of lines) {
|
|
94
|
+
if (/^#\s+/.test(line)) continue;
|
|
95
|
+
if (line.trim() === "") {
|
|
96
|
+
if (collecting) break;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (/^[-*]\s+/.test(line)) continue;
|
|
100
|
+
collecting = true;
|
|
101
|
+
buf.push(line.trim());
|
|
102
|
+
if (buf.join(" ").length > 280) break;
|
|
103
|
+
}
|
|
104
|
+
return buf.join(" ").slice(0, 280);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function extractRecentExecutionLog(text, count) {
|
|
108
|
+
const lines = text.split("\n");
|
|
109
|
+
const idx = lines.findIndex(l => /^##\s*EXECUTION LOG/i.test(l));
|
|
110
|
+
if (idx === -1) return [];
|
|
111
|
+
const after = lines.slice(idx + 1);
|
|
112
|
+
const items = [];
|
|
113
|
+
for (const line of after) {
|
|
114
|
+
if (/^##\s+/.test(line)) break;
|
|
115
|
+
const m = line.match(/^[-*]\s+\[?(\d{4}-\d{2}-\d{2})\]?\s*(.+)$/);
|
|
116
|
+
if (m) items.push({ date: m[1], text: m[2].trim() });
|
|
117
|
+
}
|
|
118
|
+
return items.slice(-count).reverse();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = { scanProjects };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// promise-wall.js — Aggregator for Promise Wall module.
|
|
4
|
+
// Pulls from Active_Context COMMITMENTS (S8.5) + WAITING FOR (S3.5) +
|
|
5
|
+
// Daily Notes commitments. Classifies by recipient + age. Pure function:
|
|
6
|
+
// takes already-parsed inputs and returns structured promise data.
|
|
7
|
+
|
|
8
|
+
function compute({ activeContext = null, dailyNoteHistory = [] }) {
|
|
9
|
+
const out = {
|
|
10
|
+
commitments: [],
|
|
11
|
+
waitingFor: [],
|
|
12
|
+
byRecipient: {},
|
|
13
|
+
overdue: [],
|
|
14
|
+
upcoming: [],
|
|
15
|
+
fulfilled: [],
|
|
16
|
+
stats: { total: 0, overdue: 0, dueSoon: 0, openWeekPlus: 0 }
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
if (!activeContext || !activeContext.available) return out;
|
|
20
|
+
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
const dayMs = 24 * 3600 * 1000;
|
|
23
|
+
|
|
24
|
+
// Outbound commitments (things YOU promised to others)
|
|
25
|
+
(activeContext.commitments || []).forEach((c) => {
|
|
26
|
+
const entry = {
|
|
27
|
+
kind: "commitment",
|
|
28
|
+
text: c.text || c.what || "",
|
|
29
|
+
to: c.to || c.recipient || "—",
|
|
30
|
+
due: c.due || c.dueBy || null,
|
|
31
|
+
added: c.added || c.created || null,
|
|
32
|
+
ageDays: c.added ? Math.max(0, Math.floor((now - Date.parse(c.added)) / dayMs)) : 0,
|
|
33
|
+
};
|
|
34
|
+
entry.status = classifyStatus(entry, now);
|
|
35
|
+
out.commitments.push(entry);
|
|
36
|
+
bucketByRecipient(out.byRecipient, entry);
|
|
37
|
+
if (entry.status === "overdue") out.overdue.push(entry);
|
|
38
|
+
if (entry.status === "due-soon") out.upcoming.push(entry);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Inbound waiting-for (things you're owed)
|
|
42
|
+
(activeContext.waitingFor || []).forEach((w) => {
|
|
43
|
+
const entry = {
|
|
44
|
+
kind: "waiting-for",
|
|
45
|
+
text: w.text || w.what || "",
|
|
46
|
+
from: w.from || w.who || "—",
|
|
47
|
+
expected: w.expected || w.expectedBy || null,
|
|
48
|
+
added: w.added || w.created || null,
|
|
49
|
+
ageDays: w.added ? Math.max(0, Math.floor((now - Date.parse(w.added)) / dayMs)) : 0,
|
|
50
|
+
};
|
|
51
|
+
entry.status = classifyStatus(entry, now);
|
|
52
|
+
out.waitingFor.push(entry);
|
|
53
|
+
bucketByRecipient(out.byRecipient, entry);
|
|
54
|
+
if (entry.status === "overdue") out.overdue.push(entry);
|
|
55
|
+
if (entry.status === "due-soon") out.upcoming.push(entry);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
out.stats.total = out.commitments.length + out.waitingFor.length;
|
|
59
|
+
out.stats.overdue = out.overdue.length;
|
|
60
|
+
out.stats.dueSoon = out.upcoming.length;
|
|
61
|
+
out.stats.openWeekPlus = [...out.commitments, ...out.waitingFor].filter(e => e.ageDays >= 7).length;
|
|
62
|
+
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function classifyStatus(entry, now) {
|
|
67
|
+
const dueRef = entry.due || entry.expected;
|
|
68
|
+
if (!dueRef) {
|
|
69
|
+
// Untimed — judge by age only.
|
|
70
|
+
if (entry.ageDays >= 14) return "overdue";
|
|
71
|
+
if (entry.ageDays >= 7) return "due-soon";
|
|
72
|
+
return "open";
|
|
73
|
+
}
|
|
74
|
+
const dueT = Date.parse(dueRef);
|
|
75
|
+
if (!Number.isFinite(dueT)) return "open";
|
|
76
|
+
const daysOut = Math.floor((dueT - now) / (24 * 3600 * 1000));
|
|
77
|
+
if (daysOut < 0) return "overdue";
|
|
78
|
+
if (daysOut <= 2) return "due-soon";
|
|
79
|
+
return "open";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function bucketByRecipient(bucket, entry) {
|
|
83
|
+
const key = entry.to || entry.from || "—";
|
|
84
|
+
if (!bucket[key]) bucket[key] = [];
|
|
85
|
+
bucket[key].push(entry);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = { compute };
|