daily-focus 0.1.0
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/LICENSE +21 -0
- package/README.md +170 -0
- package/SETUP.md +501 -0
- package/dist/audit-4AUZVELT.js +215 -0
- package/dist/chunk-2PLWVW5T.js +987 -0
- package/dist/chunk-6VWWJZKJ.js +27 -0
- package/dist/chunk-EXGTFXNV.js +77 -0
- package/dist/chunk-G4K24I4M.js +425 -0
- package/dist/chunk-NDWZUZJE.js +179 -0
- package/dist/chunk-QKT2FAB5.js +132 -0
- package/dist/chunk-V2UN3JYL.js +140 -0
- package/dist/cli.js +203 -0
- package/dist/init-ZJCI2723.js +122 -0
- package/dist/sample-M7WNKQOV.js +7 -0
- package/dist/seed-7DIDKXT7.js +50 -0
- package/dist/server-B4FWZHS2.js +4814 -0
- package/dist/service-ILBMIZB7.js +240 -0
- package/package.json +66 -0
- package/prompts/README.md +122 -0
- package/prompts/assistant.md +62 -0
- package/prompts/morning-brief-personal.md +442 -0
- package/prompts/morning-brief-work.md +630 -0
- package/public/app.js +5058 -0
- package/public/index.html +36 -0
- package/public/style.css +2515 -0
- package/schema/items.schema.json +99 -0
- package/tools/dfcal/Info.plist +16 -0
- package/tools/dfcal/build.sh +52 -0
- package/tools/dfcal/main.swift +133 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import {
|
|
2
|
+
linkState
|
|
3
|
+
} from "./chunk-QKT2FAB5.js";
|
|
4
|
+
import {
|
|
5
|
+
Store,
|
|
6
|
+
fingerprintId,
|
|
7
|
+
parseBrief,
|
|
8
|
+
readArchiveIndex,
|
|
9
|
+
resolveItems,
|
|
10
|
+
runContractChecks
|
|
11
|
+
} from "./chunk-2PLWVW5T.js";
|
|
12
|
+
import {
|
|
13
|
+
BRIEF_REFRESH_GRACE_HOURS,
|
|
14
|
+
describeSchedule,
|
|
15
|
+
describeTime,
|
|
16
|
+
loadConfig,
|
|
17
|
+
nextRunDate,
|
|
18
|
+
resolveSchedule,
|
|
19
|
+
runsOn
|
|
20
|
+
} from "./chunk-G4K24I4M.js";
|
|
21
|
+
import {
|
|
22
|
+
command
|
|
23
|
+
} from "./chunk-6VWWJZKJ.js";
|
|
24
|
+
import {
|
|
25
|
+
workingMsBetween
|
|
26
|
+
} from "./chunk-EXGTFXNV.js";
|
|
27
|
+
|
|
28
|
+
// scripts/audit.ts
|
|
29
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
30
|
+
import { resolve } from "node:path";
|
|
31
|
+
var config = loadConfig();
|
|
32
|
+
var store = new Store(config);
|
|
33
|
+
var failures = 0;
|
|
34
|
+
var warnings = 0;
|
|
35
|
+
function pass(label, detail = "") {
|
|
36
|
+
console.log(` \x1B[32m\u2713\x1B[0m ${label}${detail ? ` \u2014 ${detail}` : ""}`);
|
|
37
|
+
}
|
|
38
|
+
function fail(label, detail = "") {
|
|
39
|
+
failures++;
|
|
40
|
+
console.log(` \x1B[31m\u2717\x1B[0m ${label}${detail ? ` \u2014 ${detail}` : ""}`);
|
|
41
|
+
}
|
|
42
|
+
function warn(label, detail = "") {
|
|
43
|
+
warnings++;
|
|
44
|
+
console.log(` \x1B[33m!\x1B[0m ${label}${detail ? ` \u2014 ${detail}` : ""}`);
|
|
45
|
+
}
|
|
46
|
+
function section(title) {
|
|
47
|
+
console.log(`
|
|
48
|
+
\x1B[1m${title}\x1B[0m`);
|
|
49
|
+
}
|
|
50
|
+
async function findPrevious(current) {
|
|
51
|
+
const flag = process.argv.indexOf("--against");
|
|
52
|
+
if (flag !== -1 && process.argv[flag + 1]) {
|
|
53
|
+
const path = resolve(process.argv[flag + 1].replace(/^~/, process.env.HOME ?? "~"));
|
|
54
|
+
return { brief: JSON.parse(await readFile(path, "utf8")), from: path };
|
|
55
|
+
}
|
|
56
|
+
let names;
|
|
57
|
+
try {
|
|
58
|
+
names = (await readdir(config.archiveDir)).filter((n) => /^items-\d{4}-\d{2}-\d{2}\.json$/.test(n));
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
for (const name of names.sort().reverse()) {
|
|
63
|
+
const path = resolve(config.archiveDir, name);
|
|
64
|
+
try {
|
|
65
|
+
const brief2 = JSON.parse(await readFile(path, "utf8"));
|
|
66
|
+
if (brief2.generatedAt !== current.generatedAt) return { brief: brief2, from: path };
|
|
67
|
+
} catch {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
section("Store");
|
|
74
|
+
var promptLink = await linkState(config.promptFile, config.promptSource);
|
|
75
|
+
if (promptLink.state === "own") pass("prompt.md", "a real file, not linked to the repo");
|
|
76
|
+
else if (promptLink.state === "linked") pass("prompt.md", `linked to the ${config.profile} prompt`);
|
|
77
|
+
else if (promptLink.state === "elsewhere") warn("prompt.md", `linked to ${promptLink.target}, not the ${config.profile} prompt \u2014 restart the dashboard, or run \`${command("init")}\``);
|
|
78
|
+
else if (promptLink.state === "dangling") fail("prompt.md", `links to ${promptLink.target}, which does not exist \u2014 restart the dashboard, or run \`${command("init")}\``);
|
|
79
|
+
else fail("prompt.md", `missing \u2014 restart the dashboard, or run \`${command("init")}\``);
|
|
80
|
+
var raw = await readFile(config.itemsFile, "utf8");
|
|
81
|
+
var parsed = parseBrief(raw);
|
|
82
|
+
section("Payload");
|
|
83
|
+
if (!parsed.brief) {
|
|
84
|
+
fail("items.json parses", parsed.error ?? "unknown error");
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
var brief = parsed.brief;
|
|
88
|
+
pass("items.json parses", `${brief.items.length} items, by ${brief.generatedBy ?? "unknown"}`);
|
|
89
|
+
for (const warning of parsed.warnings) warn("parser warning", warning);
|
|
90
|
+
if (parsed.warnings.length === 0) pass("no items were dropped or repaired");
|
|
91
|
+
var auditedAt = /* @__PURE__ */ new Date();
|
|
92
|
+
var schedule = resolveSchedule(config);
|
|
93
|
+
var isRunDay = (d) => runsOn(schedule, d);
|
|
94
|
+
var provenance = {
|
|
95
|
+
config: "from DAILY_FOCUS_AGENT_DAYS",
|
|
96
|
+
default: config.agent.at ? `the dashboard's own, at ${describeTime(config.agent.at)} \u2014 set DAILY_FOCUS_AGENT_DAYS to change the days` : "assumed \u2014 set DAILY_FOCUS_AGENT_DAYS to change the days"
|
|
97
|
+
}[schedule.source];
|
|
98
|
+
pass("agent schedule", `${describeSchedule(schedule)} (${provenance})`);
|
|
99
|
+
var ranAt = new Date(brief.generatedAt);
|
|
100
|
+
var ageHours = (auditedAt.getTime() - ranAt.getTime()) / 36e5;
|
|
101
|
+
var scheduledAgeHours = workingMsBetween(ranAt, auditedAt, isRunDay) / 36e5;
|
|
102
|
+
var age = `${Math.round(ageHours * 10) / 10}h old`;
|
|
103
|
+
if (scheduledAgeHours >= config.staleAfterHours + BRIEF_REFRESH_GRACE_HOURS) fail("brief is fresh", `${Math.round(ageHours)}h old`);
|
|
104
|
+
else {
|
|
105
|
+
const next = nextRunDate(schedule, auditedAt);
|
|
106
|
+
pass("brief is fresh", isRunDay(auditedAt) ? age : `${age}, and no run was due today (next: ${next})`);
|
|
107
|
+
}
|
|
108
|
+
section("Contract");
|
|
109
|
+
var ids = brief.items.map((i) => i.id);
|
|
110
|
+
var duplicates = ids.filter((id, i) => ids.indexOf(id) !== i);
|
|
111
|
+
if (duplicates.length) fail("ids are unique", [...new Set(duplicates)].join(", "));
|
|
112
|
+
else pass("ids are unique");
|
|
113
|
+
var untyped = brief.items.filter((i) => !i.id || !i.title || !i.source || !i.kind);
|
|
114
|
+
if (untyped.length) fail("every item has id/title/source/kind", `${untyped.length} missing`);
|
|
115
|
+
else pass("every item has id/title/source/kind");
|
|
116
|
+
var priorities = brief.items.filter((i) => i.priority !== void 0).map((i) => i.priority);
|
|
117
|
+
var sortedPriorities = [...priorities].sort((a, b) => a - b);
|
|
118
|
+
var gapless = sortedPriorities.every((p, i) => p === i + 1);
|
|
119
|
+
if (priorities.length > 3) fail("at most three priorities", `${priorities.length} set`);
|
|
120
|
+
else if (priorities.length && !gapless) warn("priorities are 1..n with no gaps", sortedPriorities.join(", "));
|
|
121
|
+
else pass("priorities", priorities.length ? sortedPriorities.join(", ") : "none set");
|
|
122
|
+
var badUrls = brief.items.filter((i) => i.url && !/^https?:\/\//.test(i.url));
|
|
123
|
+
if (badUrls.length) fail("all urls are http(s)", `${badUrls.length} bad`);
|
|
124
|
+
else pass("all urls are http(s)");
|
|
125
|
+
var timedEvents = brief.items.filter((i) => i.kind === "event" && i.start && !/^\d{4}-\d{2}-\d{2}$/.test(i.start));
|
|
126
|
+
var noOffset = timedEvents.filter((i) => !/(Z|[+-]\d{2}:?\d{2})$/.test(i.start));
|
|
127
|
+
if (noOffset.length) fail("timed events carry a timezone offset", `${noOffset.length} without`);
|
|
128
|
+
else pass("timed events carry a timezone offset", `${timedEvents.length} checked`);
|
|
129
|
+
if (!config.freeWindows) {
|
|
130
|
+
pass("free windows off", brief.dayStart || brief.dayEnd ? "dayStart / dayEnd present but unused" : "no day bounds, none needed");
|
|
131
|
+
} else if (brief.dayStart || brief.dayEnd) {
|
|
132
|
+
pass("working day set from the calendar", `${brief.dayStart ?? "default"} \u2192 ${brief.dayEnd ?? "default"}`);
|
|
133
|
+
} else {
|
|
134
|
+
warn("working day not set", "falling back to config; fine only if today is standard");
|
|
135
|
+
}
|
|
136
|
+
section("Contract checks");
|
|
137
|
+
var focus = await store.readFocus();
|
|
138
|
+
var history = await readArchiveIndex(config);
|
|
139
|
+
var issues = runContractChecks({
|
|
140
|
+
brief,
|
|
141
|
+
items: resolveItems(brief.items, await store.readActions(), /* @__PURE__ */ new Date()),
|
|
142
|
+
focus,
|
|
143
|
+
history
|
|
144
|
+
});
|
|
145
|
+
if (issues.length === 0) pass("no contract issues", focus?.objective ? `objective: ${focus.objective}` : "no objective set");
|
|
146
|
+
for (const issue of issues) fail("contract", issue);
|
|
147
|
+
var aligned = brief.items.filter((i) => i.advancesObjective);
|
|
148
|
+
if (aligned.length) {
|
|
149
|
+
console.log(` ${aligned.length} item(s) advance the objective:`);
|
|
150
|
+
for (const item of aligned) console.log(` \xB7 ${item.priority ? `#${item.priority} ` : ""}${item.title}`);
|
|
151
|
+
}
|
|
152
|
+
section("Action log");
|
|
153
|
+
var actions = await store.readActions();
|
|
154
|
+
var generatedAt = new Date(brief.generatedAt);
|
|
155
|
+
var priorActions = actions.filter((a) => new Date(a.at).getTime() < generatedAt.getTime());
|
|
156
|
+
var asGenerated = resolveItems(brief.items, priorActions, generatedAt);
|
|
157
|
+
var resurrected = asGenerated.filter((i) => i.status === "done" || i.status === "dismissed");
|
|
158
|
+
var stillSnoozed = asGenerated.filter((i) => i.status === "snoozed");
|
|
159
|
+
if (resurrected.length) {
|
|
160
|
+
fail("nothing already handled was re-raised", `${resurrected.length} item(s)`);
|
|
161
|
+
for (const item of resurrected) console.log(` \xB7 ${item.status}: ${item.title}`);
|
|
162
|
+
} else {
|
|
163
|
+
pass(
|
|
164
|
+
"nothing already handled was re-raised",
|
|
165
|
+
`${priorActions.length} of ${actions.length} action(s) predated this brief`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
if (stillSnoozed.length) warn("snoozed items present", `${stillSnoozed.length} \u2014 fine only if their date has arrived`);
|
|
169
|
+
section("Continuity");
|
|
170
|
+
var previous = await findPrevious(brief);
|
|
171
|
+
if (!previous) {
|
|
172
|
+
warn("no earlier brief to compare against", "id stability unverified");
|
|
173
|
+
} else {
|
|
174
|
+
console.log(` comparing against ${previous.from}`);
|
|
175
|
+
const previousIds = new Set(previous.brief.items.map((i) => i.id));
|
|
176
|
+
const currentIds = new Set(ids);
|
|
177
|
+
const reproduced = [...previousIds].filter((id) => currentIds.has(id));
|
|
178
|
+
const dropped = [...previousIds].filter((id) => !currentIds.has(id));
|
|
179
|
+
const added = [...currentIds].filter((id) => !previousIds.has(id));
|
|
180
|
+
const actedOn = new Set(actions.filter((a) => a.action !== "note").map((a) => a.id));
|
|
181
|
+
const addedFingerprints = new Map(added.map((id) => [fingerprintId(id), id]));
|
|
182
|
+
const handled = dropped.filter((id) => actedOn.has(id));
|
|
183
|
+
const drifted = dropped.filter((id) => !actedOn.has(id) && addedFingerprints.has(fingerprintId(id)));
|
|
184
|
+
const cut = dropped.filter((id) => !actedOn.has(id) && !addedFingerprints.has(fingerprintId(id)));
|
|
185
|
+
pass(`${reproduced.length} id(s) reproduced exactly`);
|
|
186
|
+
if (handled.length) pass(`${handled.length} id(s) gone because you actioned them`);
|
|
187
|
+
if (drifted.length) {
|
|
188
|
+
fail(`${drifted.length} id(s) changed shape`, "completed items under the old id will return");
|
|
189
|
+
for (const id of drifted) console.log(` \xB7 ${id} \u2192 ${addedFingerprints.get(fingerprintId(id))}`);
|
|
190
|
+
} else {
|
|
191
|
+
pass("no id changed shape", `${added.length} new id(s) are genuinely new`);
|
|
192
|
+
}
|
|
193
|
+
if (cut.length) {
|
|
194
|
+
console.log(` ${cut.length} id(s) dropped by editorial choice \u2014 expected when the agent cuts to fit:`);
|
|
195
|
+
for (const id of cut.slice(0, 10)) console.log(` \xB7 ${id}`);
|
|
196
|
+
}
|
|
197
|
+
const previousFirstSeen = new Map(previous.brief.items.map((i) => [i.id, i.firstSeen]));
|
|
198
|
+
const lost = brief.items.filter(
|
|
199
|
+
(i) => previousFirstSeen.get(i.id) && previousFirstSeen.get(i.id) !== i.firstSeen
|
|
200
|
+
);
|
|
201
|
+
if (lost.length) {
|
|
202
|
+
fail("firstSeen carried forward", `${lost.length} id(s) changed`);
|
|
203
|
+
for (const item of lost.slice(0, 5)) {
|
|
204
|
+
console.log(` \xB7 ${item.id}: ${previousFirstSeen.get(item.id)} \u2192 ${item.firstSeen}`);
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
pass("firstSeen carried forward", `${reproduced.length} id(s) checked`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
console.log(
|
|
211
|
+
`
|
|
212
|
+
${failures === 0 ? "\x1B[32mPASS\x1B[0m" : "\x1B[31mFAIL\x1B[0m"} \u2014 ${failures} failure(s), ${warnings} warning(s)
|
|
213
|
+
`
|
|
214
|
+
);
|
|
215
|
+
process.exit(failures === 0 ? 0 : 1);
|