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.
Files changed (119) hide show
  1. package/README.md +34 -24
  2. package/install.js +2868 -251
  3. package/package.json +15 -3
  4. package/src/dashboard/build.js +1541 -0
  5. package/src/dashboard/dashboard.js +276 -0
  6. package/src/dashboard/index.js +319 -0
  7. package/src/dashboard/lib/activation-log.js +297 -0
  8. package/src/dashboard/lib/active-context-parser.js +189 -0
  9. package/src/dashboard/lib/agent-command.js +93 -0
  10. package/src/dashboard/lib/agent-detect.js +255 -0
  11. package/src/dashboard/lib/agent-detector.js +92 -0
  12. package/src/dashboard/lib/agent-session.js +483 -0
  13. package/src/dashboard/lib/anti-identity-detector.js +116 -0
  14. package/src/dashboard/lib/approval-registry.js +170 -0
  15. package/src/dashboard/lib/auto-learnings-parser.js +183 -0
  16. package/src/dashboard/lib/cli-usage-parser.js +92 -0
  17. package/src/dashboard/lib/config-parser.js +109 -0
  18. package/src/dashboard/lib/connect-recommender.js +131 -0
  19. package/src/dashboard/lib/correlations-parser.js +231 -0
  20. package/src/dashboard/lib/daily-note-resolver.js +228 -0
  21. package/src/dashboard/lib/date-utils.js +43 -0
  22. package/src/dashboard/lib/distribution-parser.js +137 -0
  23. package/src/dashboard/lib/dossier-parser.js +64 -0
  24. package/src/dashboard/lib/drift-history.js +88 -0
  25. package/src/dashboard/lib/drift-score.js +119 -0
  26. package/src/dashboard/lib/engine-default-fingerprints.json +53 -0
  27. package/src/dashboard/lib/engine-health.js +173 -0
  28. package/src/dashboard/lib/engine-writer.js +1831 -0
  29. package/src/dashboard/lib/execution-plan.js +125 -0
  30. package/src/dashboard/lib/experiments-parser.js +429 -0
  31. package/src/dashboard/lib/feed-parser.js +294 -0
  32. package/src/dashboard/lib/forked-future.js +60 -0
  33. package/src/dashboard/lib/goal-forecast.js +427 -0
  34. package/src/dashboard/lib/goals-parser.js +67 -0
  35. package/src/dashboard/lib/health-protocols-parser.js +133 -0
  36. package/src/dashboard/lib/hook-activity.js +48 -0
  37. package/src/dashboard/lib/hook-indexer.js +169 -0
  38. package/src/dashboard/lib/hourly-activity-parser.js +143 -0
  39. package/src/dashboard/lib/identity-parser.js +85 -0
  40. package/src/dashboard/lib/ingestion.js +418 -0
  41. package/src/dashboard/lib/library-indexer-v2.js +226 -0
  42. package/src/dashboard/lib/library-indexer.js +105 -0
  43. package/src/dashboard/lib/library-search.js +290 -0
  44. package/src/dashboard/lib/log-activation.sh +61 -0
  45. package/src/dashboard/lib/memory-curator.js +97 -0
  46. package/src/dashboard/lib/memory-gardener.js +177 -0
  47. package/src/dashboard/lib/memory-gepa.js +102 -0
  48. package/src/dashboard/lib/memory-index.js +519 -0
  49. package/src/dashboard/lib/memory-rerank.js +72 -0
  50. package/src/dashboard/lib/memory-text.js +136 -0
  51. package/src/dashboard/lib/metrics-log-parser.js +76 -0
  52. package/src/dashboard/lib/moves-usage-parser.js +184 -0
  53. package/src/dashboard/lib/onboarding-forge.js +70 -0
  54. package/src/dashboard/lib/override-outcome-parser.js +68 -0
  55. package/src/dashboard/lib/override-summary.js +73 -0
  56. package/src/dashboard/lib/paths.js +192 -0
  57. package/src/dashboard/lib/pattern-manifest-loader.js +29 -0
  58. package/src/dashboard/lib/phantom-strategy.js +129 -0
  59. package/src/dashboard/lib/pid-markers.js +80 -0
  60. package/src/dashboard/lib/project-scanner.js +121 -0
  61. package/src/dashboard/lib/promise-wall.js +88 -0
  62. package/src/dashboard/lib/record-score.js +173 -0
  63. package/src/dashboard/lib/redaction.js +140 -0
  64. package/src/dashboard/lib/refusal-parser.js +44 -0
  65. package/src/dashboard/lib/regenerate-manifest.js +206 -0
  66. package/src/dashboard/lib/rewind-snapshots.js +689 -0
  67. package/src/dashboard/lib/roast-wall-parser.js +200 -0
  68. package/src/dashboard/lib/run-registry.js +286 -0
  69. package/src/dashboard/lib/safe-write.js +63 -0
  70. package/src/dashboard/lib/session-log-parser.js +145 -0
  71. package/src/dashboard/lib/session-time-parser.js +158 -0
  72. package/src/dashboard/lib/skill-index.js +171 -0
  73. package/src/dashboard/lib/skill-indexer.js +118 -0
  74. package/src/dashboard/lib/skill-recommender.js +689 -0
  75. package/src/dashboard/lib/state-core/backfill.js +298 -0
  76. package/src/dashboard/lib/state-core/dryrun.js +188 -0
  77. package/src/dashboard/lib/state-core/event-log.js +615 -0
  78. package/src/dashboard/lib/state-core/events.js +265 -0
  79. package/src/dashboard/lib/state-core/projections.js +376 -0
  80. package/src/dashboard/lib/state-core/start-close.js +162 -0
  81. package/src/dashboard/lib/state-core/trial.js +96 -0
  82. package/src/dashboard/lib/strategy-parser.js +248 -0
  83. package/src/dashboard/lib/strategy-protocol-parser.js +135 -0
  84. package/src/dashboard/lib/streak-parser.js +95 -0
  85. package/src/dashboard/lib/suggested-now.js +254 -0
  86. package/src/dashboard/lib/tool-awareness.js +125 -0
  87. package/src/dashboard/lib/transcript-parser.js +371 -0
  88. package/src/dashboard/lib/vault-graph-parser.js +205 -0
  89. package/src/dashboard/lib/view-generator.js +163 -0
  90. package/src/dashboard/lib/voice-dna-parser.js +57 -0
  91. package/src/dashboard/lib/walkthrough-script.js +140 -0
  92. package/src/dashboard/lib/workflow-graph-parser.js +170 -0
  93. package/src/dashboard/lib/workflow-library-parser.js +146 -0
  94. package/src/dashboard/server.js +2402 -0
  95. package/src/dashboard/shortcut.js +284 -0
  96. package/src/dashboard/static/setup-poc.html +306 -0
  97. package/src/dashboard/static/walkthrough-poc.html +580 -0
  98. package/src/dashboard/styles.css +1201 -0
  99. package/src/dashboard/templates/index.html +278 -0
  100. package/src/dashboard/ui/dist/assets/hanken-grotesk-latin-ext-wght-normal-Dg-wlmqe.woff2 +0 -0
  101. package/src/dashboard/ui/dist/assets/hanken-grotesk-latin-wght-normal-CaVRRdDk.woff2 +0 -0
  102. package/src/dashboard/ui/dist/assets/hanken-grotesk-vietnamese-wght-normal-CHiFlh_0.woff2 +0 -0
  103. package/src/dashboard/ui/dist/assets/hero.png +0 -0
  104. package/src/dashboard/ui/dist/assets/index-ByVKPvLf.js +34 -0
  105. package/src/dashboard/ui/dist/assets/index-CCoKjUcC.js +161 -0
  106. package/src/dashboard/ui/dist/assets/index-CZWNQDt5.css +1 -0
  107. package/src/dashboard/ui/dist/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  108. package/src/dashboard/ui/dist/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  109. package/src/dashboard/ui/dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  110. package/src/dashboard/ui/dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  111. package/src/dashboard/ui/dist/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  112. package/src/dashboard/ui/dist/assets/mover-system.css +62 -0
  113. package/src/dashboard/ui/dist/assets/xterm-CqkleIqs.js +1 -0
  114. package/src/dashboard/ui/dist/icon.svg +4 -0
  115. package/src/dashboard/ui/dist/index.html +18 -0
  116. package/src/dashboard/ui/dist/manifest.webmanifest +1 -0
  117. package/src/dashboard/ui/dist/registerSW.js +1 -0
  118. package/src/dashboard/ui/dist/sw.js +1 -0
  119. 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 };