moveros 4.1.0 → 4.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/install.js +436 -41
- package/package.json +1 -1
- package/src/hooks/background-log-prompt.md +42 -0
- package/src/hooks/session-log-reminder.sh +55 -5
- package/src/structure/01_Projects/_Template Project/project_state.md +24 -19
- package/src/system/Mover_Global_Rules.md +5 -3
- package/src/workflows/analyse-day.md +11 -0
- package/src/workflows/log.md +18 -6
- package/src/workflows/morning.md +17 -0
- package/src/workflows/plan-tomorrow.md +13 -0
package/install.js
CHANGED
|
@@ -327,10 +327,11 @@ function validateKey(key) {
|
|
|
327
327
|
// ─── CLI ─────────────────────────────────────────────────────────────────────
|
|
328
328
|
function parseArgs() {
|
|
329
329
|
const args = process.argv.slice(2);
|
|
330
|
-
const opts = { vault: "", key: "" };
|
|
330
|
+
const opts = { vault: "", key: "", update: false };
|
|
331
331
|
for (let i = 0; i < args.length; i++) {
|
|
332
332
|
if (args[i] === "--vault" && args[i + 1]) opts.vault = args[++i];
|
|
333
333
|
else if (args[i] === "--key" && args[i + 1]) opts.key = args[++i];
|
|
334
|
+
else if (args[i] === "--update" || args[i] === "-u") opts.update = true;
|
|
334
335
|
else if (args[i] === "--help" || args[i] === "-h") {
|
|
335
336
|
ln();
|
|
336
337
|
ln(` ${bold("mover os")} ${dim("installer")}`);
|
|
@@ -340,6 +341,7 @@ function parseArgs() {
|
|
|
340
341
|
ln(` ${dim("Options")}`);
|
|
341
342
|
ln(` --key KEY License key (skip interactive prompt)`);
|
|
342
343
|
ln(` --vault PATH Obsidian vault path (skip detection)`);
|
|
344
|
+
ln(` --update, -u Quick update (auto-detect vault + agents, no prompts)`);
|
|
343
345
|
ln();
|
|
344
346
|
process.exit(0);
|
|
345
347
|
}
|
|
@@ -347,6 +349,181 @@ function parseArgs() {
|
|
|
347
349
|
return opts;
|
|
348
350
|
}
|
|
349
351
|
|
|
352
|
+
// ─── Obsidian vault detection ───────────────────────────────────────────────
|
|
353
|
+
function detectObsidianVaults() {
|
|
354
|
+
const configPaths = {
|
|
355
|
+
win32: path.join(os.homedir(), "AppData", "Roaming", "Obsidian", "obsidian.json"),
|
|
356
|
+
darwin: path.join(os.homedir(), "Library", "Application Support", "obsidian", "obsidian.json"),
|
|
357
|
+
linux: path.join(os.homedir(), ".config", "obsidian", "obsidian.json"),
|
|
358
|
+
};
|
|
359
|
+
const configPath = configPaths[process.platform];
|
|
360
|
+
if (!configPath || !fs.existsSync(configPath)) return [];
|
|
361
|
+
try {
|
|
362
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
363
|
+
if (!config.vaults) return [];
|
|
364
|
+
return Object.values(config.vaults)
|
|
365
|
+
.map((v) => v.path)
|
|
366
|
+
.filter((p) => p && fs.existsSync(p))
|
|
367
|
+
.sort();
|
|
368
|
+
} catch {
|
|
369
|
+
return [];
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ─── Change detection (update mode) ─────────────────────────────────────────
|
|
374
|
+
function detectChanges(bundleDir, vaultPath, selectedAgentIds) {
|
|
375
|
+
const home = os.homedir();
|
|
376
|
+
const result = { workflows: [], hooks: [], rules: null, templates: [] };
|
|
377
|
+
|
|
378
|
+
// --- Workflows: compare source vs first installed destination ---
|
|
379
|
+
const wfSrc = path.join(bundleDir, "src", "workflows");
|
|
380
|
+
const wfDests = [
|
|
381
|
+
selectedAgentIds.includes("claude-code") && path.join(home, ".claude", "commands"),
|
|
382
|
+
selectedAgentIds.includes("cursor") && path.join(home, ".cursor", "commands"),
|
|
383
|
+
selectedAgentIds.includes("antigravity") && path.join(home, ".gemini", "antigravity", "global_workflows"),
|
|
384
|
+
].filter(Boolean);
|
|
385
|
+
const wfDest = wfDests.find((d) => fs.existsSync(d));
|
|
386
|
+
|
|
387
|
+
if (fs.existsSync(wfSrc)) {
|
|
388
|
+
for (const file of fs.readdirSync(wfSrc).filter((f) => f.endsWith(".md"))) {
|
|
389
|
+
const srcContent = fs.readFileSync(path.join(wfSrc, file), "utf8");
|
|
390
|
+
const destFile = wfDest && path.join(wfDest, file);
|
|
391
|
+
if (!destFile || !fs.existsSync(destFile)) {
|
|
392
|
+
result.workflows.push({ file, status: "new" });
|
|
393
|
+
} else {
|
|
394
|
+
const destContent = fs.readFileSync(destFile, "utf8");
|
|
395
|
+
result.workflows.push({
|
|
396
|
+
file,
|
|
397
|
+
status: srcContent === destContent ? "unchanged" : "changed",
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// --- Hooks: compare with CRLF normalization ---
|
|
404
|
+
const hooksSrc = path.join(bundleDir, "src", "hooks");
|
|
405
|
+
const hooksDest = path.join(home, ".claude", "hooks");
|
|
406
|
+
if (fs.existsSync(hooksSrc) && selectedAgentIds.includes("claude-code")) {
|
|
407
|
+
for (const file of fs.readdirSync(hooksSrc).filter((f) => f.endsWith(".sh") || f.endsWith(".md"))) {
|
|
408
|
+
const srcContent = fs.readFileSync(path.join(hooksSrc, file), "utf8")
|
|
409
|
+
.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
410
|
+
const destFile = path.join(hooksDest, file);
|
|
411
|
+
if (!fs.existsSync(destFile)) {
|
|
412
|
+
result.hooks.push({ file, status: "new" });
|
|
413
|
+
} else {
|
|
414
|
+
const destContent = fs.readFileSync(destFile, "utf8")
|
|
415
|
+
.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
416
|
+
result.hooks.push({
|
|
417
|
+
file,
|
|
418
|
+
status: srcContent === destContent ? "unchanged" : "changed",
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// --- Rules: compare source vs first installed destination ---
|
|
425
|
+
const rulesSrc = path.join(bundleDir, "src", "system", "Mover_Global_Rules.md");
|
|
426
|
+
const rulesDests = [
|
|
427
|
+
selectedAgentIds.includes("claude-code") && path.join(home, ".claude", "CLAUDE.md"),
|
|
428
|
+
selectedAgentIds.includes("cursor") && path.join(home, ".cursor", "rules", "mover-os.mdc"),
|
|
429
|
+
selectedAgentIds.includes("gemini-cli") && path.join(home, ".gemini", "GEMINI.md"),
|
|
430
|
+
].filter(Boolean);
|
|
431
|
+
const rulesDest = rulesDests.find((d) => fs.existsSync(d));
|
|
432
|
+
if (fs.existsSync(rulesSrc) && rulesDest) {
|
|
433
|
+
const srcContent = fs.readFileSync(rulesSrc, "utf8");
|
|
434
|
+
const destContent = fs.readFileSync(rulesDest, "utf8");
|
|
435
|
+
result.rules = srcContent === destContent ? "unchanged" : "changed";
|
|
436
|
+
} else {
|
|
437
|
+
result.rules = "unchanged";
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// --- Templates: non-Engine vault files ---
|
|
441
|
+
const structDir = path.join(bundleDir, "src", "structure");
|
|
442
|
+
if (fs.existsSync(structDir)) {
|
|
443
|
+
const walkTemplates = (dir, rel) => {
|
|
444
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
445
|
+
const entryRel = path.join(rel, entry.name);
|
|
446
|
+
if (entry.isDirectory()) {
|
|
447
|
+
walkTemplates(path.join(dir, entry.name), entryRel);
|
|
448
|
+
} else {
|
|
449
|
+
const relNorm = entryRel.replace(/\\/g, "/");
|
|
450
|
+
if (relNorm.includes("02_Areas") && relNorm.includes("Engine")) continue;
|
|
451
|
+
const srcContent = fs.readFileSync(path.join(dir, entry.name), "utf8");
|
|
452
|
+
const destFile = path.join(vaultPath, entryRel);
|
|
453
|
+
if (!fs.existsSync(destFile)) {
|
|
454
|
+
result.templates.push({ file: entryRel, status: "new" });
|
|
455
|
+
} else {
|
|
456
|
+
const destContent = fs.readFileSync(destFile, "utf8");
|
|
457
|
+
result.templates.push({
|
|
458
|
+
file: entryRel,
|
|
459
|
+
status: srcContent === destContent ? "unchanged" : "changed",
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
walkTemplates(structDir, "");
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return result;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function countChanges(changes) {
|
|
472
|
+
let n = 0;
|
|
473
|
+
n += changes.workflows.filter((f) => f.status !== "unchanged").length;
|
|
474
|
+
n += changes.hooks.filter((f) => f.status !== "unchanged").length;
|
|
475
|
+
if (changes.rules === "changed") n++;
|
|
476
|
+
n += changes.templates.filter((f) => f.status !== "unchanged").length;
|
|
477
|
+
return n;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function displayChangeSummary(changes, installedVersion, newVersion) {
|
|
481
|
+
// Version line
|
|
482
|
+
if (installedVersion && newVersion && installedVersion !== newVersion) {
|
|
483
|
+
barLn(` Installed: ${dim(installedVersion)} → ${bold(newVersion)}`);
|
|
484
|
+
} else if (installedVersion === newVersion) {
|
|
485
|
+
barLn(` Version: ${dim(installedVersion)} (files may differ)`);
|
|
486
|
+
}
|
|
487
|
+
barLn();
|
|
488
|
+
|
|
489
|
+
// Workflows
|
|
490
|
+
const wfChanged = changes.workflows.filter((f) => f.status === "changed");
|
|
491
|
+
const wfNew = changes.workflows.filter((f) => f.status === "new");
|
|
492
|
+
const wfUnchanged = changes.workflows.filter((f) => f.status === "unchanged");
|
|
493
|
+
barLn(` Workflows ${dim(`(${wfChanged.length + wfNew.length} changed, ${wfUnchanged.length} unchanged)`)}:`);
|
|
494
|
+
for (const f of wfChanged) barLn(` ${yellow("✦")} /${f.file.replace(".md", "")}`);
|
|
495
|
+
for (const f of wfNew) barLn(` ${green("+")} /${f.file.replace(".md", "")} ${dim("(new)")}`);
|
|
496
|
+
if (wfChanged.length === 0 && wfNew.length === 0) barLn(` ${dim("all up to date")}`);
|
|
497
|
+
barLn();
|
|
498
|
+
|
|
499
|
+
// Hooks
|
|
500
|
+
if (changes.hooks.length > 0) {
|
|
501
|
+
const hkChanged = changes.hooks.filter((f) => f.status === "changed");
|
|
502
|
+
const hkNew = changes.hooks.filter((f) => f.status === "new");
|
|
503
|
+
const hkUnchanged = changes.hooks.filter((f) => f.status === "unchanged");
|
|
504
|
+
barLn(` Hooks ${dim(`(${hkChanged.length + hkNew.length} changed, ${hkUnchanged.length} unchanged)`)}:`);
|
|
505
|
+
for (const f of hkChanged) barLn(` ${yellow("✦")} ${f.file}`);
|
|
506
|
+
for (const f of hkNew) barLn(` ${green("+")} ${f.file} ${dim("(new)")}`);
|
|
507
|
+
if (hkChanged.length === 0 && hkNew.length === 0) barLn(` ${dim("all up to date")}`);
|
|
508
|
+
barLn();
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Rules
|
|
512
|
+
barLn(` Rules: ${changes.rules === "changed" ? yellow("changed") : dim("unchanged")}`);
|
|
513
|
+
|
|
514
|
+
// Templates
|
|
515
|
+
const tmplChanged = changes.templates.filter((f) => f.status === "changed");
|
|
516
|
+
const tmplNew = changes.templates.filter((f) => f.status === "new");
|
|
517
|
+
if (tmplChanged.length > 0 || tmplNew.length > 0) {
|
|
518
|
+
barLn(` Templates ${dim(`(${tmplChanged.length + tmplNew.length} changed)`)}:`);
|
|
519
|
+
for (const f of tmplChanged) barLn(` ${yellow("✦")} ${f.file.replace(/\\/g, "/")}`);
|
|
520
|
+
for (const f of tmplNew) barLn(` ${green("+")} ${f.file.replace(/\\/g, "/")} ${dim("(new)")}`);
|
|
521
|
+
} else {
|
|
522
|
+
barLn(` Templates: ${dim("unchanged")}`);
|
|
523
|
+
}
|
|
524
|
+
barLn();
|
|
525
|
+
}
|
|
526
|
+
|
|
350
527
|
// ─── Engine file detection ──────────────────────────────────────────────────
|
|
351
528
|
function detectEngineFiles(vaultPath) {
|
|
352
529
|
const engineDir = path.join(vaultPath, "02_Areas", "Engine");
|
|
@@ -967,13 +1144,14 @@ function installTemplateFiles(bundleDir, vaultPath) {
|
|
|
967
1144
|
return count;
|
|
968
1145
|
}
|
|
969
1146
|
|
|
970
|
-
function installWorkflows(bundleDir, destDir) {
|
|
1147
|
+
function installWorkflows(bundleDir, destDir, selectedWorkflows) {
|
|
971
1148
|
const srcDir = path.join(bundleDir, "src", "workflows");
|
|
972
1149
|
if (!fs.existsSync(srcDir)) return 0;
|
|
973
1150
|
|
|
974
1151
|
fs.mkdirSync(destDir, { recursive: true });
|
|
975
1152
|
let count = 0;
|
|
976
1153
|
for (const file of fs.readdirSync(srcDir).filter((f) => f.endsWith(".md"))) {
|
|
1154
|
+
if (selectedWorkflows && !selectedWorkflows.has(file)) continue;
|
|
977
1155
|
if (linkOrCopy(path.join(srcDir, file), path.join(destDir, file))) count++;
|
|
978
1156
|
}
|
|
979
1157
|
return count;
|
|
@@ -1012,7 +1190,16 @@ function installHooksForClaude(bundleDir, vaultPath) {
|
|
|
1012
1190
|
|
|
1013
1191
|
let count = 0;
|
|
1014
1192
|
for (const file of fs.readdirSync(hooksSrc).filter((f) => f.endsWith(".sh"))) {
|
|
1015
|
-
|
|
1193
|
+
const dst = path.join(hooksDst, file);
|
|
1194
|
+
// Read, strip \r (CRLF→LF), write — prevents "command not found" on macOS/Linux
|
|
1195
|
+
const content = fs.readFileSync(path.join(hooksSrc, file), "utf8").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
1196
|
+
fs.writeFileSync(dst, content, { mode: 0o755 });
|
|
1197
|
+
count++;
|
|
1198
|
+
}
|
|
1199
|
+
// Copy hook template files (.md)
|
|
1200
|
+
for (const file of fs.readdirSync(hooksSrc).filter((f) => f.endsWith(".md"))) {
|
|
1201
|
+
const dst = path.join(hooksDst, file);
|
|
1202
|
+
fs.copyFileSync(path.join(hooksSrc, file), dst);
|
|
1016
1203
|
count++;
|
|
1017
1204
|
}
|
|
1018
1205
|
|
|
@@ -1057,10 +1244,12 @@ function installClaudeCode(bundleDir, vaultPath, skillOpts) {
|
|
|
1057
1244
|
|
|
1058
1245
|
const claudeDir = path.join(home, ".claude");
|
|
1059
1246
|
fs.mkdirSync(claudeDir, { recursive: true });
|
|
1060
|
-
if (
|
|
1247
|
+
if (!skillOpts?.skipRules) {
|
|
1248
|
+
if (installRules(bundleDir, path.join(claudeDir, "CLAUDE.md"))) steps.push("rules");
|
|
1249
|
+
}
|
|
1061
1250
|
|
|
1062
1251
|
const cmdsDir = path.join(claudeDir, "commands");
|
|
1063
|
-
const wfCount = installWorkflows(bundleDir, cmdsDir);
|
|
1252
|
+
const wfCount = installWorkflows(bundleDir, cmdsDir, skillOpts?.workflows);
|
|
1064
1253
|
if (wfCount > 0) steps.push(`${wfCount} commands`);
|
|
1065
1254
|
|
|
1066
1255
|
if (skillOpts && skillOpts.install) {
|
|
@@ -1069,7 +1258,7 @@ function installClaudeCode(bundleDir, vaultPath, skillOpts) {
|
|
|
1069
1258
|
if (skCount > 0) steps.push(`${skCount} skills`);
|
|
1070
1259
|
}
|
|
1071
1260
|
|
|
1072
|
-
if (vaultPath) {
|
|
1261
|
+
if (vaultPath && !skillOpts?.skipHooks) {
|
|
1073
1262
|
const hkCount = installHooksForClaude(bundleDir, vaultPath);
|
|
1074
1263
|
if (hkCount > 0) steps.push(`${hkCount} hooks`);
|
|
1075
1264
|
}
|
|
@@ -1105,19 +1294,21 @@ function installCursor(bundleDir, vaultPath, skillOpts) {
|
|
|
1105
1294
|
const home = os.homedir();
|
|
1106
1295
|
const steps = [];
|
|
1107
1296
|
|
|
1108
|
-
if (
|
|
1109
|
-
if (
|
|
1110
|
-
|
|
1297
|
+
if (!skillOpts?.skipRules) {
|
|
1298
|
+
if (vaultPath) {
|
|
1299
|
+
if (installRules(bundleDir, path.join(vaultPath, ".cursorrules"))) steps.push("rules");
|
|
1300
|
+
}
|
|
1111
1301
|
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1302
|
+
const cursorRulesDir = path.join(home, ".cursor", "rules");
|
|
1303
|
+
fs.mkdirSync(cursorRulesDir, { recursive: true });
|
|
1304
|
+
const src = path.join(bundleDir, "src", "system", "Mover_Global_Rules.md");
|
|
1305
|
+
if (fs.existsSync(src)) {
|
|
1306
|
+
fs.copyFileSync(src, path.join(cursorRulesDir, "mover-os.mdc"));
|
|
1307
|
+
}
|
|
1117
1308
|
}
|
|
1118
1309
|
|
|
1119
1310
|
const cmdsDir = path.join(home, ".cursor", "commands");
|
|
1120
|
-
const wfCount = installWorkflows(bundleDir, cmdsDir);
|
|
1311
|
+
const wfCount = installWorkflows(bundleDir, cmdsDir, skillOpts?.workflows);
|
|
1121
1312
|
if (wfCount > 0) steps.push(`${wfCount} commands`);
|
|
1122
1313
|
|
|
1123
1314
|
if (skillOpts && skillOpts.install) {
|
|
@@ -1253,7 +1444,7 @@ function installAntigravity(bundleDir, vaultPath, skillOpts, writtenFiles) {
|
|
|
1253
1444
|
}
|
|
1254
1445
|
|
|
1255
1446
|
const wfDir = path.join(geminiDir, "antigravity", "global_workflows");
|
|
1256
|
-
const wfCount = installWorkflows(bundleDir, wfDir);
|
|
1447
|
+
const wfCount = installWorkflows(bundleDir, wfDir, skillOpts?.workflows);
|
|
1257
1448
|
if (wfCount > 0) steps.push(`${wfCount} workflows`);
|
|
1258
1449
|
|
|
1259
1450
|
if (skillOpts && skillOpts.install) {
|
|
@@ -1414,6 +1605,93 @@ async function main() {
|
|
|
1414
1605
|
process.exit(1);
|
|
1415
1606
|
}
|
|
1416
1607
|
|
|
1608
|
+
// ── Headless quick update (--update flag) ──
|
|
1609
|
+
if (opts.update) {
|
|
1610
|
+
// Auto-detect vault
|
|
1611
|
+
let vaultPath = opts.vault;
|
|
1612
|
+
if (!vaultPath) {
|
|
1613
|
+
const obsVaults = detectObsidianVaults();
|
|
1614
|
+
vaultPath = obsVaults.find((p) =>
|
|
1615
|
+
fs.existsSync(path.join(p, ".mover-version"))
|
|
1616
|
+
);
|
|
1617
|
+
if (!vaultPath) {
|
|
1618
|
+
outro(red("No Mover OS vault found. Use: npx moveros --update --vault /path"));
|
|
1619
|
+
process.exit(1);
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
if (vaultPath.startsWith("~")) vaultPath = path.join(os.homedir(), vaultPath.slice(1));
|
|
1623
|
+
vaultPath = path.resolve(vaultPath);
|
|
1624
|
+
barLn(dim(`Vault: ${vaultPath}`));
|
|
1625
|
+
|
|
1626
|
+
// Auto-detect agents
|
|
1627
|
+
const detectedAgents = AGENTS.filter((a) => a.detect());
|
|
1628
|
+
if (detectedAgents.length === 0) {
|
|
1629
|
+
outro(red("No AI agents detected."));
|
|
1630
|
+
process.exit(1);
|
|
1631
|
+
}
|
|
1632
|
+
const selectedIds = detectedAgents.map((a) => a.id);
|
|
1633
|
+
barLn(dim(`Agents: ${detectedAgents.map((a) => a.name).join(", ")}`));
|
|
1634
|
+
barLn();
|
|
1635
|
+
|
|
1636
|
+
// Detect changes
|
|
1637
|
+
const changes = detectChanges(bundleDir, vaultPath, selectedIds);
|
|
1638
|
+
const totalChanged = countChanges(changes);
|
|
1639
|
+
|
|
1640
|
+
// Read versions
|
|
1641
|
+
const vfPath = path.join(vaultPath, ".mover-version");
|
|
1642
|
+
const installedVer = fs.existsSync(vfPath) ? fs.readFileSync(vfPath, "utf8").trim() : null;
|
|
1643
|
+
let newVer = `V${VERSION}`;
|
|
1644
|
+
try {
|
|
1645
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(bundleDir, "package.json"), "utf8"));
|
|
1646
|
+
newVer = pkg.version || newVer;
|
|
1647
|
+
} catch {}
|
|
1648
|
+
|
|
1649
|
+
displayChangeSummary(changes, installedVer, newVer);
|
|
1650
|
+
|
|
1651
|
+
if (totalChanged === 0) {
|
|
1652
|
+
outro(green("Already up to date."));
|
|
1653
|
+
return;
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
// Apply all changes
|
|
1657
|
+
barLn(bold("Updating..."));
|
|
1658
|
+
barLn();
|
|
1659
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1660
|
+
|
|
1661
|
+
// Vault structure
|
|
1662
|
+
createVaultStructure(vaultPath);
|
|
1663
|
+
|
|
1664
|
+
// Templates
|
|
1665
|
+
installTemplateFiles(bundleDir, vaultPath);
|
|
1666
|
+
|
|
1667
|
+
// Per-agent installation
|
|
1668
|
+
const writtenFiles = new Set();
|
|
1669
|
+
const skillOpts = { install: true, categories: null, workflows: null };
|
|
1670
|
+
for (const agent of detectedAgents) {
|
|
1671
|
+
const fn = AGENT_INSTALLERS[agent.id];
|
|
1672
|
+
if (!fn) continue;
|
|
1673
|
+
const sp = spinner(agent.name);
|
|
1674
|
+
const usesWrittenFiles = agent.id === "antigravity" || agent.id === "gemini-cli";
|
|
1675
|
+
const steps = usesWrittenFiles
|
|
1676
|
+
? fn(bundleDir, vaultPath, skillOpts, writtenFiles)
|
|
1677
|
+
: fn(bundleDir, vaultPath, skillOpts);
|
|
1678
|
+
await sleep(200);
|
|
1679
|
+
if (steps.length > 0) {
|
|
1680
|
+
sp.stop(`${agent.name} ${dim(steps.join(", "))}`);
|
|
1681
|
+
} else {
|
|
1682
|
+
sp.stop(`${agent.name} ${dim("configured")}`);
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
// Update version marker
|
|
1687
|
+
fs.writeFileSync(path.join(vaultPath, ".mover-version"), `V${VERSION}\n`, "utf8");
|
|
1688
|
+
|
|
1689
|
+
barLn();
|
|
1690
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
1691
|
+
outro(`${green("Done.")} ${totalChanged} files updated in ${elapsed}s. Run ${bold("/update")} if version bumped.`);
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1417
1695
|
// ── License key ──
|
|
1418
1696
|
let key = opts.key;
|
|
1419
1697
|
|
|
@@ -1459,26 +1737,45 @@ async function main() {
|
|
|
1459
1737
|
let vaultPath = opts.vault;
|
|
1460
1738
|
|
|
1461
1739
|
if (!vaultPath) {
|
|
1462
|
-
const
|
|
1463
|
-
path.join(os.homedir(), "Documents", "Obsidian"),
|
|
1464
|
-
path.join(os.homedir(), "Obsidian"),
|
|
1465
|
-
path.join(os.homedir(), "Documents", "Mover-OS"),
|
|
1466
|
-
];
|
|
1740
|
+
const obsVaults = detectObsidianVaults();
|
|
1467
1741
|
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1742
|
+
if (obsVaults.length > 0) {
|
|
1743
|
+
question("Select your Obsidian vault");
|
|
1744
|
+
barLn();
|
|
1745
|
+
|
|
1746
|
+
const vaultItems = obsVaults.map((p) => {
|
|
1747
|
+
const name = path.basename(p);
|
|
1748
|
+
const hasMover =
|
|
1749
|
+
fs.existsSync(path.join(p, ".mover-version")) ||
|
|
1750
|
+
fs.existsSync(path.join(p, "02_Areas", "Engine"));
|
|
1751
|
+
return {
|
|
1752
|
+
id: p,
|
|
1753
|
+
name: `${name}${hasMover ? dim(" (Mover OS)") : ""}`,
|
|
1754
|
+
tier: dim(p),
|
|
1755
|
+
};
|
|
1756
|
+
});
|
|
1757
|
+
vaultItems.push({
|
|
1758
|
+
id: "__manual__",
|
|
1759
|
+
name: "Enter path manually",
|
|
1760
|
+
tier: "Type or paste a custom vault path",
|
|
1761
|
+
});
|
|
1475
1762
|
|
|
1476
|
-
|
|
1763
|
+
const selected = await interactiveSelect(vaultItems, { multi: false });
|
|
1477
1764
|
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1765
|
+
if (selected === "__manual__") {
|
|
1766
|
+
vaultPath = await textInput({
|
|
1767
|
+
label: "Where is your Obsidian vault?",
|
|
1768
|
+
initial: path.join(os.homedir(), "Mover-OS"),
|
|
1769
|
+
});
|
|
1770
|
+
} else {
|
|
1771
|
+
vaultPath = selected;
|
|
1772
|
+
}
|
|
1773
|
+
} else {
|
|
1774
|
+
vaultPath = await textInput({
|
|
1775
|
+
label: "Where is your Obsidian vault?",
|
|
1776
|
+
initial: path.join(os.homedir(), "Mover-OS"),
|
|
1777
|
+
});
|
|
1778
|
+
}
|
|
1482
1779
|
} else {
|
|
1483
1780
|
barLn(dim(`Vault: ${vaultPath}`));
|
|
1484
1781
|
barLn();
|
|
@@ -1706,6 +2003,102 @@ async function main() {
|
|
|
1706
2003
|
process.exit(0);
|
|
1707
2004
|
}
|
|
1708
2005
|
|
|
2006
|
+
// ── Change detection + selection (update mode only) ──
|
|
2007
|
+
let selectedWorkflows = null; // null = install all
|
|
2008
|
+
let skipHooks = false;
|
|
2009
|
+
let skipRules = false;
|
|
2010
|
+
let skipTemplates = false;
|
|
2011
|
+
|
|
2012
|
+
if (updateMode) {
|
|
2013
|
+
const changes = detectChanges(bundleDir, vaultPath, selectedIds);
|
|
2014
|
+
const totalChanged = countChanges(changes);
|
|
2015
|
+
|
|
2016
|
+
// Read versions for display
|
|
2017
|
+
const versionFilePath = path.join(vaultPath, ".mover-version");
|
|
2018
|
+
const installedVersion = fs.existsSync(versionFilePath)
|
|
2019
|
+
? fs.readFileSync(versionFilePath, "utf8").trim()
|
|
2020
|
+
: null;
|
|
2021
|
+
let newVersion = `V${VERSION}`;
|
|
2022
|
+
try {
|
|
2023
|
+
const pkgPath = path.join(bundleDir, "package.json");
|
|
2024
|
+
if (fs.existsSync(pkgPath)) {
|
|
2025
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
2026
|
+
newVersion = pkg.version || newVersion;
|
|
2027
|
+
}
|
|
2028
|
+
} catch {}
|
|
2029
|
+
|
|
2030
|
+
barLn();
|
|
2031
|
+
question("Change Summary");
|
|
2032
|
+
barLn();
|
|
2033
|
+
displayChangeSummary(changes, installedVersion, newVersion);
|
|
2034
|
+
|
|
2035
|
+
if (totalChanged === 0) {
|
|
2036
|
+
barLn(green(" Already up to date."));
|
|
2037
|
+
barLn();
|
|
2038
|
+
} else {
|
|
2039
|
+
const applyChoice = await interactiveSelect(
|
|
2040
|
+
[
|
|
2041
|
+
{ id: "all", name: "Yes, update all changed files", tier: "" },
|
|
2042
|
+
{ id: "select", name: "Select individually", tier: "" },
|
|
2043
|
+
{ id: "cancel", name: "Cancel", tier: "" },
|
|
2044
|
+
],
|
|
2045
|
+
{ multi: false, defaultIndex: 0 }
|
|
2046
|
+
);
|
|
2047
|
+
|
|
2048
|
+
if (applyChoice === "cancel") {
|
|
2049
|
+
outro("Cancelled.");
|
|
2050
|
+
process.exit(0);
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
if (applyChoice === "select") {
|
|
2054
|
+
// Build list of only changed/new files for individual selection
|
|
2055
|
+
const changedItems = [];
|
|
2056
|
+
const changedPreSelected = [];
|
|
2057
|
+
for (const f of changes.workflows.filter((x) => x.status !== "unchanged")) {
|
|
2058
|
+
const id = `wf:${f.file}`;
|
|
2059
|
+
changedItems.push({ id, name: `/${f.file.replace(".md", "")}`, tier: dim(f.status === "new" ? "new workflow" : "workflow") });
|
|
2060
|
+
changedPreSelected.push(id);
|
|
2061
|
+
}
|
|
2062
|
+
for (const f of changes.hooks.filter((x) => x.status !== "unchanged")) {
|
|
2063
|
+
const id = `hook:${f.file}`;
|
|
2064
|
+
changedItems.push({ id, name: f.file, tier: dim(f.status === "new" ? "new hook" : "hook") });
|
|
2065
|
+
changedPreSelected.push(id);
|
|
2066
|
+
}
|
|
2067
|
+
if (changes.rules === "changed") {
|
|
2068
|
+
changedItems.push({ id: "rules", name: "Global Rules", tier: dim("rules") });
|
|
2069
|
+
changedPreSelected.push("rules");
|
|
2070
|
+
}
|
|
2071
|
+
for (const f of changes.templates.filter((x) => x.status !== "unchanged")) {
|
|
2072
|
+
const id = `tmpl:${f.file}`;
|
|
2073
|
+
changedItems.push({ id, name: f.file.replace(/\\/g, "/"), tier: dim(f.status === "new" ? "new template" : "template") });
|
|
2074
|
+
changedPreSelected.push(id);
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
if (changedItems.length > 0) {
|
|
2078
|
+
question("Select files to update");
|
|
2079
|
+
barLn();
|
|
2080
|
+
const selectedFileIds = await interactiveSelect(changedItems, {
|
|
2081
|
+
multi: true,
|
|
2082
|
+
preSelected: changedPreSelected,
|
|
2083
|
+
});
|
|
2084
|
+
|
|
2085
|
+
// Build workflow filter Set
|
|
2086
|
+
const selectedWfFiles = selectedFileIds
|
|
2087
|
+
.filter((id) => id.startsWith("wf:"))
|
|
2088
|
+
.map((id) => id.slice(3));
|
|
2089
|
+
if (selectedWfFiles.length < changes.workflows.filter((x) => x.status !== "unchanged").length) {
|
|
2090
|
+
selectedWorkflows = new Set(selectedWfFiles);
|
|
2091
|
+
}
|
|
2092
|
+
// Check if hooks/rules/templates were deselected
|
|
2093
|
+
skipHooks = !selectedFileIds.some((id) => id.startsWith("hook:"));
|
|
2094
|
+
skipRules = !selectedFileIds.includes("rules");
|
|
2095
|
+
skipTemplates = !selectedFileIds.some((id) => id.startsWith("tmpl:"));
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
// "all" = selectedWorkflows stays null, skip flags stay false
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
|
|
1709
2102
|
// ── Skills ──
|
|
1710
2103
|
const allSkills = findSkills(bundleDir);
|
|
1711
2104
|
let installSkills = false;
|
|
@@ -1797,13 +2190,15 @@ async function main() {
|
|
|
1797
2190
|
totalSteps++;
|
|
1798
2191
|
|
|
1799
2192
|
// 2. Template files (runs in both modes — only creates missing files, never overwrites)
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
2193
|
+
if (!skipTemplates) {
|
|
2194
|
+
sp = spinner(updateMode ? "New template files" : "Engine templates");
|
|
2195
|
+
const templatesInstalled = installTemplateFiles(bundleDir, vaultPath);
|
|
2196
|
+
await sleep(200);
|
|
2197
|
+
sp.stop(updateMode
|
|
2198
|
+
? `New template files${templatesInstalled > 0 ? dim(` ${templatesInstalled} added`) : dim(" all present")}`
|
|
2199
|
+
: `Engine templates${templatesInstalled > 0 ? dim(` ${templatesInstalled} files`) : dim(" up to date")}`);
|
|
2200
|
+
totalSteps++;
|
|
2201
|
+
}
|
|
1807
2202
|
|
|
1808
2203
|
// 3. CLAUDE.md (skip on update)
|
|
1809
2204
|
if (!updateMode) {
|
|
@@ -1820,7 +2215,7 @@ async function main() {
|
|
|
1820
2215
|
|
|
1821
2216
|
// 4. Per-agent installation
|
|
1822
2217
|
const writtenFiles = new Set(); // Track shared files to avoid double-writes (e.g. GEMINI.md)
|
|
1823
|
-
const skillOpts = { install: installSkills, categories: selectedCategories, statusLine: installStatusLine };
|
|
2218
|
+
const skillOpts = { install: installSkills, categories: selectedCategories, statusLine: installStatusLine, workflows: selectedWorkflows, skipHooks, skipRules, skipTemplates };
|
|
1824
2219
|
for (const agent of selectedAgents) {
|
|
1825
2220
|
const fn = AGENT_INSTALLERS[agent.id];
|
|
1826
2221
|
if (!fn) continue;
|
package/package.json
CHANGED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
You are a session logger for Mover OS. Process this session log entry.
|
|
2
|
+
|
|
3
|
+
Timestamps: current={{CURRENT_TS}}, pre-compaction={{COMPACT_TS}}.
|
|
4
|
+
Session backup: {{SUMMARY_PATH}}
|
|
5
|
+
Working directory: {{WORKING_DIR}}
|
|
6
|
+
Project: {{PROJECT_NAME}}
|
|
7
|
+
|
|
8
|
+
## Steps
|
|
9
|
+
|
|
10
|
+
0. Read the log workflow at: {{LOG_WORKFLOW}}
|
|
11
|
+
Use Step 4 for session log FORMAT, Step 6C for System State Snapshot FORMAT (metadata header + narrative paragraph). Follow these formats exactly.
|
|
12
|
+
|
|
13
|
+
1. Read the session backup summary at the path above.
|
|
14
|
+
|
|
15
|
+
2. Read the Daily Note at: {{DAILY_NOTE}}
|
|
16
|
+
If it does not exist, create it with a basic template.
|
|
17
|
+
Find the `## Session Log` section. Insert the new entry WITHIN that section (not at end of file).
|
|
18
|
+
Mark as [COMPACTED].
|
|
19
|
+
Format: `### Session Log [ATTENDED] — ~HH:MM-HH:MM [Claude Code] [{{PROJECT_NAME}}] [COMPACTED]`
|
|
20
|
+
Then subsections with ~timestamps and 3-4 line descriptions.
|
|
21
|
+
|
|
22
|
+
3. If plan.md exists at: {{PLAN_MD}}
|
|
23
|
+
Read it, mark completed tasks [~] (NOT [x]) with [UNVERIFIED] tag, append to EXECUTION LOG.
|
|
24
|
+
The [~] means "code written, not yet verified." Only /morning or the user promotes to [x].
|
|
25
|
+
|
|
26
|
+
4. If project_state.md exists at: {{PROJECT_STATE}}
|
|
27
|
+
Append new Solutions Ledger entries (deduplicate first), Changelog entry, and System State Snapshot.
|
|
28
|
+
Snapshot format: structured metadata (Mode, Focus, Risk, Next Milestone, Blocker) AND a narrative paragraph (8-12 sentences). See log.md Step 6C.
|
|
29
|
+
|
|
30
|
+
5. Read Active_Context at: {{ACTIVE_CONTEXT}}
|
|
31
|
+
Prepend 1-line summary to Active Sessions (keep 5 max, remove oldest).
|
|
32
|
+
Update log_last_run to {{CURRENT_TS}}.
|
|
33
|
+
|
|
34
|
+
6. Git commit changed files if inside a repo.
|
|
35
|
+
Git add specific files only. Never git add . at root. Never commit Dailies.
|
|
36
|
+
|
|
37
|
+
## Constraints
|
|
38
|
+
|
|
39
|
+
- Append-only (never delete)
|
|
40
|
+
- Cite sources
|
|
41
|
+
- Mark entries [COMPACTED]
|
|
42
|
+
- Windows: no && in commands
|
|
@@ -58,8 +58,6 @@ if [ -f "$PENDING_FILE" ]; then
|
|
|
58
58
|
COMPACT_TS=$(grep '^\*\*Timestamp:\*\*' "$SUMMARY_PATH" 2>/dev/null | sed 's/\*\*Timestamp:\*\* //')
|
|
59
59
|
fi
|
|
60
60
|
CURRENT_TS=$(date '+%Y-%m-%dT%H:%M')
|
|
61
|
-
CURRENT_DATE=$(date '+%Y-%m-%d')
|
|
62
|
-
CURRENT_MONTH=$(date '+%Y-%m')
|
|
63
61
|
WORKING_DIR=$(pwd)
|
|
64
62
|
|
|
65
63
|
# Resolve vault root: walk up from working dir looking for 02_Areas/Engine/
|
|
@@ -73,9 +71,36 @@ if [ -f "$PENDING_FILE" ]; then
|
|
|
73
71
|
CHECK_DIR=$(dirname "$CHECK_DIR")
|
|
74
72
|
done
|
|
75
73
|
|
|
74
|
+
# Determine which daily note to use:
|
|
75
|
+
# Use pre-compaction date (session date), NOT wall-clock time.
|
|
76
|
+
# This prevents 1am sessions from creating a new day's note.
|
|
77
|
+
SESSION_DATE=""
|
|
78
|
+
if [ -n "$COMPACT_TS" ]; then
|
|
79
|
+
SESSION_DATE=$(echo "$COMPACT_TS" | grep -oE '[0-9]{4}-[0-9]{2}-[0-9]{2}')
|
|
80
|
+
fi
|
|
81
|
+
# Fallback: find the latest existing daily note
|
|
82
|
+
if [ -z "$SESSION_DATE" ]; then
|
|
83
|
+
DAILIES_DIR="${VAULT_ROOT}/02_Areas/Engine/Dailies"
|
|
84
|
+
if [ -d "$DAILIES_DIR" ]; then
|
|
85
|
+
LATEST_NOTE=$(find "$DAILIES_DIR" -name "Daily - *.md" -type f 2>/dev/null | sort -r | head -1)
|
|
86
|
+
if [ -n "$LATEST_NOTE" ]; then
|
|
87
|
+
SESSION_DATE=$(echo "$LATEST_NOTE" | grep -oE '[0-9]{4}-[0-9]{2}-[0-9]{2}')
|
|
88
|
+
fi
|
|
89
|
+
fi
|
|
90
|
+
fi
|
|
91
|
+
# Final fallback: use current date
|
|
92
|
+
if [ -z "$SESSION_DATE" ]; then
|
|
93
|
+
SESSION_DATE=$(date '+%Y-%m-%d')
|
|
94
|
+
fi
|
|
95
|
+
SESSION_MONTH=$(echo "$SESSION_DATE" | cut -c1-7)
|
|
96
|
+
|
|
76
97
|
# Derive key file paths
|
|
77
|
-
DAILY_NOTE="${VAULT_ROOT}/02_Areas/Engine/Dailies/${
|
|
98
|
+
DAILY_NOTE="${VAULT_ROOT}/02_Areas/Engine/Dailies/${SESSION_MONTH}/Daily - ${SESSION_DATE}.md"
|
|
78
99
|
ACTIVE_CONTEXT="${VAULT_ROOT}/02_Areas/Engine/Active_Context.md"
|
|
100
|
+
LOG_WORKFLOW="$HOME/.claude/commands/log.md"
|
|
101
|
+
if [ ! -f "$LOG_WORKFLOW" ]; then
|
|
102
|
+
LOG_WORKFLOW="${VAULT_ROOT}/01_Projects/Mover OS Bundle/src/workflows/log.md"
|
|
103
|
+
fi
|
|
79
104
|
|
|
80
105
|
# Detect project context (if inside 01_Projects/[Name]/)
|
|
81
106
|
PROJECT_NAME=""
|
|
@@ -98,7 +123,32 @@ if [ -f "$PENDING_FILE" ]; then
|
|
|
98
123
|
;;
|
|
99
124
|
esac
|
|
100
125
|
|
|
101
|
-
|
|
126
|
+
# Load instructions from template file (keeps this script readable)
|
|
127
|
+
PROMPT_TEMPLATE="$HOME/.claude/hooks/background-log-prompt.md"
|
|
128
|
+
if [ ! -f "$PROMPT_TEMPLATE" ]; then
|
|
129
|
+
PROMPT_TEMPLATE="${VAULT_ROOT}/01_Projects/Mover OS Bundle/src/hooks/background-log-prompt.md"
|
|
130
|
+
fi
|
|
131
|
+
if [ -f "$PROMPT_TEMPLATE" ]; then
|
|
132
|
+
AGENT_INSTRUCTIONS=$(sed \
|
|
133
|
+
-e "s|{{CURRENT_TS}}|${CURRENT_TS}|g" \
|
|
134
|
+
-e "s|{{COMPACT_TS}}|${COMPACT_TS:-unknown}|g" \
|
|
135
|
+
-e "s|{{SUMMARY_PATH}}|${SUMMARY_PATH}|g" \
|
|
136
|
+
-e "s|{{WORKING_DIR}}|${WORKING_DIR}|g" \
|
|
137
|
+
-e "s|{{PROJECT_NAME}}|${PROJECT_NAME:-Unknown}|g" \
|
|
138
|
+
-e "s|{{DAILY_NOTE}}|${DAILY_NOTE}|g" \
|
|
139
|
+
-e "s|{{PLAN_MD}}|${PLAN_MD:-N/A}|g" \
|
|
140
|
+
-e "s|{{PROJECT_STATE}}|${PROJECT_STATE:-N/A}|g" \
|
|
141
|
+
-e "s|{{ACTIVE_CONTEXT}}|${ACTIVE_CONTEXT}|g" \
|
|
142
|
+
-e "s|{{LOG_WORKFLOW}}|${LOG_WORKFLOW}|g" \
|
|
143
|
+
"$PROMPT_TEMPLATE" | tr '\n' ' ')
|
|
144
|
+
else
|
|
145
|
+
AGENT_INSTRUCTIONS="Session logger: read ${SUMMARY_PATH}, update Daily Note at ${DAILY_NOTE}, update project_state at ${PROJECT_STATE:-N/A}, update Active_Context at ${ACTIVE_CONTEXT}. Append-only. Mark [COMPACTED]."
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
# Escape double quotes for JSON
|
|
149
|
+
AGENT_INSTRUCTIONS=$(echo "$AGENT_INSTRUCTIONS" | sed 's/"/\\"/g')
|
|
150
|
+
|
|
151
|
+
echo "{\"decision\":\"block\",\"reason\":\"POST-COMPACTION BACKGROUND LOG: Context was just compacted. Spawn a BACKGROUND Task agent (subagent_type=general-purpose, model=sonnet, run_in_background=true) with your session context summary + the instructions below. Then mark as logged: echo done > /tmp/mover_logdone_${SESSION_KEY}. Continue with your work. INSTRUCTIONS: ${AGENT_INSTRUCTIONS}\"}"
|
|
102
152
|
exit 0
|
|
103
153
|
fi
|
|
104
154
|
fi
|
|
@@ -131,5 +181,5 @@ if [ -n "$TRANSCRIPT" ] && [ -f "$TRANSCRIPT" ]; then
|
|
|
131
181
|
fi
|
|
132
182
|
fi
|
|
133
183
|
|
|
134
|
-
echo '{"decision":"block","reason":"SESSION REMINDER:
|
|
184
|
+
echo '{"decision":"block","reason":"SESSION REMINDER: In ONE sentence, ask the user if they would like to run /log before ending. Do NOT run /log automatically. Do NOT use the Skill tool. Just ask the question and continue with your normal response."}'
|
|
135
185
|
exit 0
|
|
@@ -1,19 +1,24 @@
|
|
|
1
|
-
# PROJECT STATE: [Project Name]
|
|
2
|
-
*Status: [Status e.g., Planning]*
|
|
3
|
-
|
|
4
|
-
## 1. THE BRAIN DUMP (Notes)
|
|
5
|
-
* **Mission:** [What are we doing?]
|
|
6
|
-
* **Current Blocker:** [What is stopping us?]
|
|
7
|
-
|
|
8
|
-
## 2. THE SOLUTIONS LEDGER
|
|
9
|
-
* **[Topic]:** [Problem] -> [Solution]
|
|
10
|
-
|
|
11
|
-
## 3. CHANGELOG
|
|
12
|
-
* [YYYY-MM-DD] Project Initialized -> Template copied.
|
|
13
|
-
|
|
14
|
-
## 4. SYSTEM STATE SNAPSHOTS
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
1
|
+
# PROJECT STATE: [Project Name]
|
|
2
|
+
*Status: [Status e.g., Planning]*
|
|
3
|
+
|
|
4
|
+
## 1. THE BRAIN DUMP (Notes)
|
|
5
|
+
* **Mission:** [What are we doing?]
|
|
6
|
+
* **Current Blocker:** [What is stopping us?]
|
|
7
|
+
|
|
8
|
+
## 2. THE SOLUTIONS LEDGER
|
|
9
|
+
* **[Topic]:** [Problem] -> [Solution]
|
|
10
|
+
|
|
11
|
+
## 3. CHANGELOG
|
|
12
|
+
* [YYYY-MM-DD] Project Initialized -> Template copied.
|
|
13
|
+
|
|
14
|
+
## 4. SYSTEM STATE SNAPSHOTS
|
|
15
|
+
|
|
16
|
+
### [YYYY-MM-DD HH:MM] Snapshot
|
|
17
|
+
- **Mode:** [Planning / Execution / Debugging]
|
|
18
|
+
- **Focus:** [Current primary task]
|
|
19
|
+
- **Risk:** [Low / Medium / High / Critical]
|
|
20
|
+
- **Next Milestone:** [What's next]
|
|
21
|
+
- **Blocker:** [What's blocking, or None]
|
|
22
|
+
|
|
23
|
+
**Narrative:** [8-12 sentences. What changed, why, what unblocked,
|
|
24
|
+
what's next. Concrete and direct.]
|
|
@@ -328,17 +328,19 @@ You are the guardian of `plan.md`.
|
|
|
328
328
|
- **Read-First:** Your memory is `plan.md`. Read it before acting.
|
|
329
329
|
- **Append-Only:** FORBIDDEN from deleting history/completed tasks.
|
|
330
330
|
- **Real-Time Sync:** After task completion:
|
|
331
|
-
1. Mark `[
|
|
332
|
-
2. Append to `## EXECUTION LOG`: `* [YYYY-MM-DD] {Task} - {Result}`
|
|
331
|
+
1. Mark `[~]` in `plan.md` with `[UNVERIFIED]` tag
|
|
332
|
+
2. Append to `## EXECUTION LOG`: `* [YYYY-MM-DD] {Task} - {Result} [UNVERIFIED]`
|
|
333
333
|
3. Suggest Git commit
|
|
334
334
|
|
|
335
|
+
`[~]` means "code written, not yet verified working." Only `/morning` or user promotes to `[x]`.
|
|
336
|
+
|
|
335
337
|
**Passive Detection Protocol (Always Active):**
|
|
336
338
|
|
|
337
339
|
These triggers fire during EVERY conversation, not just workflows. **DETECT > WRITE > CONTINUE.** Don't ask. Don't announce. Write the entry, then continue the conversation.
|
|
338
340
|
|
|
339
341
|
| Trigger | What to detect | Where to write |
|
|
340
342
|
|---------|---------------|---------------|
|
|
341
|
-
| User reports action | "I did it", "just finished", "sent it", "got back from" | Daily Note `## Log` + mark `[
|
|
343
|
+
| User reports action | "I did it", "just finished", "sent it", "got back from" | Daily Note `## Log` + mark `[~] [UNVERIFIED]` in plan.md if applicable |
|
|
342
344
|
| Decision made | "Let's go with X", "I've decided", agreement on direction | Relevant file (plan.md, project_state.md, Active_Context) |
|
|
343
345
|
| Commitment made | "I'll send", "I promised", "by Friday" | Active_Context `## 8.5 COMMITMENTS` |
|
|
344
346
|
| Waiting on someone | "He said he'd", "waiting on", "they'll get back to me" | Active_Context `## 3.5 WAITING FOR` |
|
|
@@ -20,6 +20,17 @@ description: End-of-day analysis. Reads Session Log, audits strategy, updates Da
|
|
|
20
20
|
|
|
21
21
|
Global Rules are pre-loaded as CLAUDE.md / GEMINI.md. Output `[Rules Loaded - DATE]`.
|
|
22
22
|
|
|
23
|
+
GET CURRENT TIME:
|
|
24
|
+
```bash
|
|
25
|
+
date '+%Y-%m-%d %H:%M'
|
|
26
|
+
```
|
|
27
|
+
Save as CURRENT_TIME. Extract the hour.
|
|
28
|
+
|
|
29
|
+
DETERMINE "TODAY" (session-aware date logic):
|
|
30
|
+
- If the hour is **before 05:00** (midnight-5am): the user is still on "yesterday." Find and analyse YESTERDAY's Daily Note, not today's. The analysis belongs to the day that was lived, not the clock date.
|
|
31
|
+
- If the hour is **05:00 or later**: analyse today's Daily Note as normal.
|
|
32
|
+
- Save the resolved date as ANALYSE_DATE. Use this for all Daily Note lookups.
|
|
33
|
+
|
|
23
34
|
LOAD (Memory Hierarchy):
|
|
24
35
|
1. Auto_Learnings.md (P0)
|
|
25
36
|
2. Active_Context.md (P1)
|
package/src/workflows/log.md
CHANGED
|
@@ -33,7 +33,13 @@ pwd
|
|
|
33
33
|
```
|
|
34
34
|
Save as WORKING_DIR. Extract project name: if inside `01_Projects/[Name]/`, capture `[Name]`. Otherwise use the directory basename.
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
DETERMINE SESSION DATE (session-aware, not wall-clock):
|
|
37
|
+
- Extract the hour from SESSION_END_TIME.
|
|
38
|
+
- If before **05:00** (midnight-5am): the session belongs to YESTERDAY. Use yesterday's date for the Daily Note.
|
|
39
|
+
- If **05:00 or later**: use today's date.
|
|
40
|
+
- Save as SESSION_DATE.
|
|
41
|
+
|
|
42
|
+
DAILY NOTE PATH (derive from SESSION_DATE):
|
|
37
43
|
```
|
|
38
44
|
{VAULT_ROOT}/02_Areas/Engine/Dailies/YYYY-MM/Daily - YYYY-MM-DD.md
|
|
39
45
|
```
|
|
@@ -164,9 +170,10 @@ If no Daily Note exists: output the Session Log in chat for the user to paste.
|
|
|
164
170
|
|
|
165
171
|
**Target:** Active project's `plan.md`
|
|
166
172
|
|
|
167
|
-
1. Mark completed tasks with `[x]
|
|
173
|
+
1. Mark completed tasks with `[~]` and `[UNVERIFIED]` tag (NOT `[x]`).
|
|
174
|
+
`[~]` means "code written, not yet verified." Only `/morning` or user promotes to `[x]`.
|
|
168
175
|
2. Append to `## EXECUTION LOG`:
|
|
169
|
-
`* [YYYY-MM-DD] {Task} - {Result with context}`
|
|
176
|
+
`* [YYYY-MM-DD] {Task} - {Result with context} [UNVERIFIED]`
|
|
170
177
|
Include failure context: "Failed with Error X, fixed with Y".
|
|
171
178
|
3. Do NOT rewrite the roadmap. Do NOT delete any lines.
|
|
172
179
|
|
|
@@ -202,9 +209,14 @@ APPEND (never overwrite) a new snapshot:
|
|
|
202
209
|
|
|
203
210
|
```markdown
|
|
204
211
|
### [YYYY-MM-DD HH:MM] Snapshot
|
|
205
|
-
**
|
|
206
|
-
|
|
207
|
-
|
|
212
|
+
- **Mode:** [Planning / Execution / Debugging]
|
|
213
|
+
- **Focus:** [Current primary task]
|
|
214
|
+
- **Risk:** [Low / Medium / High / Critical]
|
|
215
|
+
- **Next Milestone:** [What's next]
|
|
216
|
+
- **Blocker:** [What's blocking, or None]
|
|
217
|
+
|
|
218
|
+
**Narrative:** [8-12 sentences. What changed, why, what unblocked,
|
|
219
|
+
what's next. Concrete and direct.]
|
|
208
220
|
```
|
|
209
221
|
|
|
210
222
|
If no project_state.md in scope: skip this step.
|
package/src/workflows/morning.md
CHANGED
|
@@ -99,6 +99,23 @@ Keep this under 30 seconds. Use file modification dates, not full reads. Delegat
|
|
|
99
99
|
|
|
100
100
|
---
|
|
101
101
|
|
|
102
|
+
## 2.6 VERIFICATION CHECK (Promote Unverified Tasks)
|
|
103
|
+
|
|
104
|
+
If a project context is active (user is inside a project directory, or today's plan references a project):
|
|
105
|
+
1. Find the active project's `plan.md`
|
|
106
|
+
2. Scan for tasks marked `[~]` with `[UNVERIFIED]` tag
|
|
107
|
+
|
|
108
|
+
If found:
|
|
109
|
+
- List them: "Last session marked these as done but unverified:"
|
|
110
|
+
- `[~] Task description [UNVERIFIED]`
|
|
111
|
+
- Ask: "Confirm these are working? (yes / list issues)"
|
|
112
|
+
- If confirmed: promote all to `[x]`, remove `[UNVERIFIED]` tag, append to EXECUTION LOG: `* [YYYY-MM-DD] Verified: [task list]`
|
|
113
|
+
- If issues listed: keep as `[~]`, add issue note to task line, surface in today's plan as priority
|
|
114
|
+
|
|
115
|
+
If no `[~]` items found: skip silently.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
102
119
|
## 2.7 PLAN ALIGNMENT CHECK (Escalating Friction — Level 1)
|
|
103
120
|
|
|
104
121
|
If today's Daily Note exists:
|
|
@@ -18,6 +18,19 @@ description: Generates the Daily Note for the next day based on Strategy, previo
|
|
|
18
18
|
|
|
19
19
|
Global Rules are pre-loaded as CLAUDE.md / GEMINI.md. Output `[Rules Loaded - DATE]`.
|
|
20
20
|
|
|
21
|
+
GET CURRENT TIME:
|
|
22
|
+
```bash
|
|
23
|
+
date '+%Y-%m-%d %H:%M'
|
|
24
|
+
```
|
|
25
|
+
Save as CURRENT_TIME. Extract the hour.
|
|
26
|
+
|
|
27
|
+
DETERMINE "TODAY" (session-aware date logic):
|
|
28
|
+
- If the hour is **before 05:00** (midnight-5am): the user is still on "yesterday" in their mind. Check if a Daily Note exists for yesterday's date. If yes, treat YESTERDAY as "today" for planning purposes. "Tomorrow" = the current calendar date.
|
|
29
|
+
- If the hour is **05:00 or later**: treat the current calendar date as "today." "Tomorrow" = current date + 1.
|
|
30
|
+
- **Rule:** Only `/plan-tomorrow` creates new Daily Notes. The note it creates is always for the NEXT waking day. Never create a note for the day after that.
|
|
31
|
+
|
|
32
|
+
Save the resolved dates as TODAY_DATE and TOMORROW_DATE.
|
|
33
|
+
|
|
21
34
|
LOAD (Memory Hierarchy):
|
|
22
35
|
1. Auto_Learnings.md (P0)
|
|
23
36
|
2. Active_Context.md (P1)
|