atris 3.74.1 → 3.74.3
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/atris/atris.md +9 -0
- package/atris.md +9 -0
- package/commands/autopilot.js +9 -6
- package/commands/business.js +9 -3
- package/commands/clean.js +2 -1
- package/commands/close.js +7 -4
- package/commands/experiments.js +4 -1
- package/commands/gm.js +8 -1
- package/commands/improve.js +3 -1
- package/commands/interview.js +13 -5
- package/commands/land.js +5 -1
- package/commands/loops.js +5 -2
- package/commands/member.js +5 -1
- package/commands/play.js +3 -1
- package/commands/pull.js +4 -2
- package/commands/security-review.js +4 -1
- package/commands/soul.js +2 -1
- package/commands/sync.js +8 -2
- package/commands/task.js +8 -2
- package/commands/taste.js +2 -1
- package/commands/x-search.js +5 -3
- package/commands/youtube.js +5 -1
- package/commands/zen.js +1 -1
- package/lib/apply-gate.js +5 -3
- package/lib/auto-lessons.js +3 -1
- package/lib/experiments/daily.js +2 -1
- package/lib/gm-bet.js +32 -4
- package/lib/gm-phone-page.js +28 -2
- package/lib/improve-vitals-html.js +12 -3
- package/lib/local-day.js +10 -0
- package/lib/orb-scorecard.js +9 -2
- package/lib/scorecard.js +7 -6
- package/lib/wiki.js +4 -1
- package/package.json +1 -1
package/atris/atris.md
CHANGED
|
@@ -60,6 +60,15 @@ Labels used below:
|
|
|
60
60
|
- `guarded`: checked by code or a pre-commit hook; bypassing is a bug
|
|
61
61
|
- `expected`: convention; honor it or stop
|
|
62
62
|
|
|
63
|
+
## values
|
|
64
|
+
|
|
65
|
+
Four values decide close calls. When a rule above does not settle it, these do.
|
|
66
|
+
|
|
67
|
+
- **Acceleration.** Progress compounds. Land small working changes early and often so the next agent builds on them today, not next week.
|
|
68
|
+
- **Accuracy.** A win counts only when it is checked. Prove it with a test that failed before and passes after, or a second builder's review when the stakes call for it. Faster tests make faster work.
|
|
69
|
+
- **Ambition.** Take one more level. When the small fix is done, look for the bigger version of the same win.
|
|
70
|
+
- **Agency.** Own the missing piece. A bug, a broken tool, or a gap in your way is yours to fix or file, not a reason to stop.
|
|
71
|
+
|
|
63
72
|
## taste
|
|
64
73
|
|
|
65
74
|
What you ship should not read as generated. The test: if someone said "an AI made this," would they believe it instantly? If yes, that is the bug. The model has no words for restraint and it falls into gravity wells. Beat both.
|
package/atris.md
CHANGED
|
@@ -82,6 +82,15 @@ Labels used below:
|
|
|
82
82
|
- `guarded`: checked by code or a pre-commit hook; bypassing is a bug
|
|
83
83
|
- `expected`: convention; honor it or stop
|
|
84
84
|
|
|
85
|
+
## values
|
|
86
|
+
|
|
87
|
+
Four values decide close calls. When a rule above does not settle it, these do.
|
|
88
|
+
|
|
89
|
+
- **Acceleration.** Progress compounds. Land small working changes early and often so the next agent builds on them today, not next week.
|
|
90
|
+
- **Accuracy.** A win counts only when it is checked. Prove it with a test that failed before and passes after, or a second builder's review when the stakes call for it. Faster tests make faster work.
|
|
91
|
+
- **Ambition.** Take one more level. When the small fix is done, look for the bigger version of the same win.
|
|
92
|
+
- **Agency.** Own the missing piece. A bug, a broken tool, or a gap in your way is yours to fix or file, not a reason to stop.
|
|
93
|
+
|
|
85
94
|
## taste
|
|
86
95
|
|
|
87
96
|
What you ship should not read as generated. The test: if someone said "an AI made this," would they believe it instantly? If yes, that is the bug. The model has no words for restraint and it falls into gravity wells. Beat both.
|
package/commands/autopilot.js
CHANGED
|
@@ -1157,16 +1157,19 @@ function lessonSlug(text, maxLen = 40) {
|
|
|
1157
1157
|
return trimmed.replace(/-+$/g, '') || 'unknown';
|
|
1158
1158
|
}
|
|
1159
1159
|
|
|
1160
|
+
// Local calendar day (YYYY-MM-DD). toISOString() is the UTC day, which is
|
|
1161
|
+
// already tomorrow in the evening anywhere west of Greenwich.
|
|
1162
|
+
function localDay(date = new Date()) {
|
|
1163
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1160
1166
|
/**
|
|
1161
1167
|
* Write a lesson to atris/lessons.md
|
|
1162
1168
|
* Appends a line in format: - **[YYYY-MM-DD] slug** - pass/fail - explanation
|
|
1163
1169
|
*/
|
|
1164
1170
|
function writeLesson(cwd, slug, status, explanation) {
|
|
1165
1171
|
const lessonsPath = path.join(cwd, 'atris', 'lessons.md');
|
|
1166
|
-
|
|
1167
|
-
// evening lesson tomorrow anywhere west of Greenwich.
|
|
1168
|
-
const now = new Date();
|
|
1169
|
-
const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
|
1172
|
+
const today = localDay();
|
|
1170
1173
|
const lessonLine = `- **[${today}] ${slug}** - ${status} - ${explanation}`;
|
|
1171
1174
|
|
|
1172
1175
|
if (!fs.existsSync(lessonsPath)) {
|
|
@@ -2962,7 +2965,7 @@ function markLessonAttempted(cwd, slug, reason) {
|
|
|
2962
2965
|
if (!meta[slug] || typeof meta[slug] !== 'object') meta[slug] = {};
|
|
2963
2966
|
meta[slug].status = 'attempted';
|
|
2964
2967
|
meta[slug].attempts = (typeof meta[slug].attempts === 'number' ? meta[slug].attempts : 0) + 1;
|
|
2965
|
-
meta[slug].last_attempt =
|
|
2968
|
+
meta[slug].last_attempt = localDay();
|
|
2966
2969
|
if (reason) meta[slug].last_attempt_reason = String(reason);
|
|
2967
2970
|
try {
|
|
2968
2971
|
const atrisDir = path.join(cwd, 'atris');
|
|
@@ -3184,7 +3187,7 @@ function hasRecentVerifyPrePass(cwd, taskTitle, windowDays = 7) {
|
|
|
3184
3187
|
if (lessons.length === 0) return false;
|
|
3185
3188
|
const cutoff = new Date();
|
|
3186
3189
|
cutoff.setDate(cutoff.getDate() - windowDays);
|
|
3187
|
-
const cutoffDate = cutoff
|
|
3190
|
+
const cutoffDate = localDay(cutoff);
|
|
3188
3191
|
const needle = `"${taskTitle}"`;
|
|
3189
3192
|
for (const l of lessons) {
|
|
3190
3193
|
if (l.id !== 'verify-not-falsifiable') continue;
|
package/commands/business.js
CHANGED
|
@@ -1214,8 +1214,14 @@ async function startBusinessWorkspace(...args) {
|
|
|
1214
1214
|
return state;
|
|
1215
1215
|
}
|
|
1216
1216
|
|
|
1217
|
+
// Local calendar day; toISOString is UTC and dates evening work tomorrow
|
|
1218
|
+
// anywhere west of Greenwich.
|
|
1219
|
+
function localDay(now = new Date()) {
|
|
1220
|
+
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1217
1223
|
function defaultSharePath(cwd, role) {
|
|
1218
|
-
const stamp =
|
|
1224
|
+
const stamp = localDay();
|
|
1219
1225
|
const roleSlug = slugifyName(role || 'collaborator');
|
|
1220
1226
|
return path.join(cwd, 'atris', 'reports', `${stamp}-share-${roleSlug}.md`);
|
|
1221
1227
|
}
|
|
@@ -1494,7 +1500,7 @@ async function onboardBusiness(...flags) {
|
|
|
1494
1500
|
const intakeRel = path.relative(cwd, intakePath).replace(/\\/g, '/');
|
|
1495
1501
|
const linksRel = path.relative(cwd, linksPath).replace(/\\/g, '/');
|
|
1496
1502
|
const sourcesRel = path.relative(cwd, sourcesPath).replace(/\\/g, '/');
|
|
1497
|
-
const today =
|
|
1503
|
+
const today = localDay();
|
|
1498
1504
|
|
|
1499
1505
|
const briefSlug = `${bizMeta.slug}-starter-brief`;
|
|
1500
1506
|
const briefPath = path.join(cwd, 'atris', 'wiki', 'briefs', `${briefSlug}.md`);
|
|
@@ -2803,7 +2809,7 @@ async function createBusinessInternal(name, flags = [], mode = 'auto') {
|
|
|
2803
2809
|
description ? `\n> ${description}\n` : '',
|
|
2804
2810
|
'\n## The Business\n\n[What problem does this solve?]\n',
|
|
2805
2811
|
'## Revenue Model\n\n[How does this make money?]\n',
|
|
2806
|
-
`---\n*Created: ${
|
|
2812
|
+
`---\n*Created: ${localDay()}*\n`,
|
|
2807
2813
|
].join(''));
|
|
2808
2814
|
console.log(` Local scaffold: ${bizDir}/`);
|
|
2809
2815
|
}
|
package/commands/clean.js
CHANGED
|
@@ -3,6 +3,7 @@ const os = require('os');
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const { spawnSync } = require('child_process');
|
|
5
5
|
const escapeRegExp = require('../lib/escape-regexp');
|
|
6
|
+
const { localDay } = require('../lib/local-day');
|
|
6
7
|
const { wikiMetabolismNudge } = require('../lib/wiki');
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -723,7 +724,7 @@ function checkPageStaleness(cwd, filePath) {
|
|
|
723
724
|
page: filePath,
|
|
724
725
|
staleSource: source,
|
|
725
726
|
compiledDate: compiledMatch[1],
|
|
726
|
-
sourceDate: changed
|
|
727
|
+
sourceDate: localDay(changed)
|
|
727
728
|
};
|
|
728
729
|
}
|
|
729
730
|
} catch {
|
package/commands/close.js
CHANGED
|
@@ -85,8 +85,10 @@ function appendEvent(event, cwd = process.cwd()) {
|
|
|
85
85
|
fs.appendFileSync(ledgerPath(cwd), `${JSON.stringify(event)}\n`);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
// Local calendar day (YYYY-MM-DD). toISOString() is the UTC day, which is
|
|
89
|
+
// already tomorrow in the evening anywhere west of Greenwich.
|
|
90
|
+
function localDay(date) {
|
|
91
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
function parseDate(value) {
|
|
@@ -156,7 +158,8 @@ function foldEvents(events, options = {}) {
|
|
|
156
158
|
delete flag.parked_at;
|
|
157
159
|
delete flag.why;
|
|
158
160
|
} else if (event.kind === 'escalated') {
|
|
159
|
-
const
|
|
161
|
+
const at = event.at ? new Date(event.at) : null;
|
|
162
|
+
const day = event.day || (at && Number.isFinite(at.getTime()) ? localDay(at) : null);
|
|
160
163
|
if (day && !flag.escalated_days.includes(day)) flag.escalated_days.push(day);
|
|
161
164
|
} else if (event.kind === 'probed') {
|
|
162
165
|
flag.probe_runs.push({
|
|
@@ -753,7 +756,7 @@ function commandSnooze(args, context = {}) {
|
|
|
753
756
|
// Escalation core shared by the CLI verb and the hourly pulse tick: append
|
|
754
757
|
// one escalated event per overdue flag per day, return the resulting state.
|
|
755
758
|
function sweepState(cwd = process.cwd(), now = new Date(), options = {}) {
|
|
756
|
-
const today =
|
|
759
|
+
const today = localDay(now);
|
|
757
760
|
const autoClosed = autoCloseResolvedTaskReviewFlags(cwd, now);
|
|
758
761
|
const autoParked = migrateOpenSlots(cwd, now, options);
|
|
759
762
|
let flags = openFlags(cwd, { now });
|
package/commands/experiments.js
CHANGED
|
@@ -600,7 +600,10 @@ function buildBenchmarkArtifact(name, packDir, options) {
|
|
|
600
600
|
function recordExperimentRun(experimentId) {
|
|
601
601
|
const root = process.cwd();
|
|
602
602
|
const state = daily.readDailyState(root);
|
|
603
|
-
|
|
603
|
+
// Local calendar day; toISOString is UTC and records an evening run as
|
|
604
|
+
// tomorrow's anywhere west of Greenwich.
|
|
605
|
+
const now = new Date();
|
|
606
|
+
const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
|
604
607
|
state.last_run_date = today;
|
|
605
608
|
if (!Array.isArray(state.history)) state.history = [];
|
|
606
609
|
if (!state.history.includes(experimentId)) {
|
package/commands/gm.js
CHANGED
|
@@ -287,6 +287,12 @@ function pickSeedPlayer(workspaceRoot, tasks, args = []) {
|
|
|
287
287
|
return null;
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
+
// Local calendar day; toISOString is UTC and stamps an evening mission
|
|
291
|
+
// with tomorrow anywhere west of Greenwich.
|
|
292
|
+
function localDay(now = new Date()) {
|
|
293
|
+
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
|
294
|
+
}
|
|
295
|
+
|
|
290
296
|
function ensureStarterMission(taskDb, db, workspaceRoot, tasks, args = []) {
|
|
291
297
|
if (hasFlagOrValue(args, '--no-seed')) return { tasks, seeded: null };
|
|
292
298
|
if (!fs.existsSync(path.join(workspaceRoot, 'atris'))) return { tasks, seeded: null };
|
|
@@ -303,7 +309,7 @@ function ensureStarterMission(taskDb, db, workspaceRoot, tasks, args = []) {
|
|
|
303
309
|
assigned_to: player,
|
|
304
310
|
delegate_via: 'agentxp_gm',
|
|
305
311
|
auto_seeded_by: 'atris gm',
|
|
306
|
-
created_for_day:
|
|
312
|
+
created_for_day: localDay(),
|
|
307
313
|
},
|
|
308
314
|
});
|
|
309
315
|
taskDb.noteTask(db, {
|
|
@@ -1951,6 +1957,7 @@ async function gmCommand(...args) {
|
|
|
1951
1957
|
|
|
1952
1958
|
module.exports = {
|
|
1953
1959
|
displayCommand,
|
|
1960
|
+
ensureStarterMission,
|
|
1954
1961
|
gmCommand,
|
|
1955
1962
|
pickClub,
|
|
1956
1963
|
playAction,
|
package/commands/improve.js
CHANGED
|
@@ -25,6 +25,7 @@ const path = require('path');
|
|
|
25
25
|
const { spawnSync } = require('child_process');
|
|
26
26
|
const gitSpawn = require('../lib/git-spawn');
|
|
27
27
|
const ignoredCache = require('../lib/ignored-cache');
|
|
28
|
+
const { localDay } = require('../lib/local-day');
|
|
28
29
|
const { apiRequestJson, getApiBaseUrl } = require('../utils/api');
|
|
29
30
|
const { loadCredentials } = require('../utils/auth');
|
|
30
31
|
const pulse = require('../lib/pulse');
|
|
@@ -375,7 +376,7 @@ function agePhrase(ts, nowMs = Date.now()) {
|
|
|
375
376
|
if (delta < 60 * 60 * 1000) return `${plural(Math.round(delta / (60 * 1000)), 'minute')} ago`;
|
|
376
377
|
if (delta < DAY_MS) return `${plural(Math.round(delta / (60 * 60 * 1000)), 'hour')} ago`;
|
|
377
378
|
if (delta < 30 * DAY_MS) return `${plural(Math.round(delta / DAY_MS), 'day')} ago`;
|
|
378
|
-
return `on ${new Date(ms)
|
|
379
|
+
return `on ${localDay(new Date(ms))}`;
|
|
379
380
|
}
|
|
380
381
|
|
|
381
382
|
function plainSentence(value) {
|
|
@@ -1715,6 +1716,7 @@ async function run(argv = [], deps = {}) {
|
|
|
1715
1716
|
|
|
1716
1717
|
module.exports = {
|
|
1717
1718
|
run,
|
|
1719
|
+
agePhrase,
|
|
1718
1720
|
runImprove,
|
|
1719
1721
|
runImproveCore,
|
|
1720
1722
|
parseImproveArgs,
|
package/commands/interview.js
CHANGED
|
@@ -27,9 +27,17 @@ function findWorkspaceRoot(start) {
|
|
|
27
27
|
return null;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
// The local calendar day (YYYY-MM-DD), offsetDays back. toISOString() is the
|
|
31
|
+
// UTC day, which is already tomorrow in the evening west of Greenwich.
|
|
30
32
|
function today(offsetDays = 0) {
|
|
31
|
-
const d = new Date(
|
|
32
|
-
|
|
33
|
+
const d = new Date();
|
|
34
|
+
d.setDate(d.getDate() - offsetDays);
|
|
35
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function journalPath(ws, offsetDays = 0) {
|
|
39
|
+
const day = today(offsetDays);
|
|
40
|
+
return path.join(ws, 'atris', 'logs', day.slice(0, 4), `${day}.md`);
|
|
33
41
|
}
|
|
34
42
|
|
|
35
43
|
function existing(...candidates) {
|
|
@@ -178,8 +186,8 @@ async function interviewCommand(args = [], root = process.cwd(), { mode: forcedM
|
|
|
178
186
|
if (subjectRaw === 'me' || subjectRaw === '') {
|
|
179
187
|
mode = 'operator';
|
|
180
188
|
contextPaths = existing(
|
|
181
|
-
|
|
182
|
-
|
|
189
|
+
journalPath(ws),
|
|
190
|
+
journalPath(ws, 1),
|
|
183
191
|
path.join(ws, 'atris', 'now.md'),
|
|
184
192
|
path.join(ws, 'atris', 'TODO.md')
|
|
185
193
|
);
|
|
@@ -200,7 +208,7 @@ async function interviewCommand(args = [], root = process.cwd(), { mode: forcedM
|
|
|
200
208
|
path.join(memberDir, 'now.md'),
|
|
201
209
|
path.join(memberDir, 'logs'),
|
|
202
210
|
path.join(memberDir, 'proofs'),
|
|
203
|
-
|
|
211
|
+
journalPath(ws)
|
|
204
212
|
);
|
|
205
213
|
} else {
|
|
206
214
|
mode = 'expert';
|
package/commands/land.js
CHANGED
|
@@ -473,7 +473,10 @@ function landSummary(cwd = process.cwd(), ttlDays = DEFAULT_TTL_DAYS) {
|
|
|
473
473
|
}
|
|
474
474
|
|
|
475
475
|
function salvageDir(root) {
|
|
476
|
-
|
|
476
|
+
// Local calendar day; toISOString is UTC and files an evening backup
|
|
477
|
+
// under tomorrow anywhere west of Greenwich.
|
|
478
|
+
const now = new Date();
|
|
479
|
+
const stamp = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
|
477
480
|
const dir = path.join(root, '.atris', 'salvage', stamp);
|
|
478
481
|
fs.mkdirSync(dir, { recursive: true });
|
|
479
482
|
return dir;
|
|
@@ -1086,5 +1089,6 @@ module.exports = {
|
|
|
1086
1089
|
landCommand,
|
|
1087
1090
|
landSummary,
|
|
1088
1091
|
reap,
|
|
1092
|
+
salvageDir,
|
|
1089
1093
|
sharedBoard,
|
|
1090
1094
|
};
|
package/commands/loops.js
CHANGED
|
@@ -160,7 +160,7 @@ function setRegistryJob(id, enabled) {
|
|
|
160
160
|
delete job.disabled_reason;
|
|
161
161
|
} else {
|
|
162
162
|
job.disabled = true;
|
|
163
|
-
job.disabled_reason = `stopped via atris loops ${
|
|
163
|
+
job.disabled_reason = `stopped via atris loops ${todayIso()}`;
|
|
164
164
|
}
|
|
165
165
|
saveRegistry(registry);
|
|
166
166
|
return true;
|
|
@@ -204,8 +204,11 @@ function controlLoop(id, action) {
|
|
|
204
204
|
return 0;
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
// Local calendar day (YYYY-MM-DD). toISOString() is the UTC day, which is
|
|
208
|
+
// already tomorrow in the evening anywhere west of Greenwich.
|
|
207
209
|
function todayIso() {
|
|
208
|
-
|
|
210
|
+
const d = new Date();
|
|
211
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
209
212
|
}
|
|
210
213
|
|
|
211
214
|
function renderTemplate(text) {
|
package/commands/member.js
CHANGED
|
@@ -3385,7 +3385,10 @@ function logDateForScan(relativePath, filePath) {
|
|
|
3385
3385
|
const named = dateFromLogPath(relativePath);
|
|
3386
3386
|
if (named) return named;
|
|
3387
3387
|
try {
|
|
3388
|
-
|
|
3388
|
+
// Local calendar day, like the dated journal names beside it;
|
|
3389
|
+
// toISOString is UTC and calls an evening write tomorrow's.
|
|
3390
|
+
const mtime = fs.statSync(filePath).mtime;
|
|
3391
|
+
return `${mtime.getFullYear()}-${String(mtime.getMonth() + 1).padStart(2, '0')}-${String(mtime.getDate()).padStart(2, '0')}`;
|
|
3389
3392
|
} catch {
|
|
3390
3393
|
return null;
|
|
3391
3394
|
}
|
|
@@ -12015,6 +12018,7 @@ async function memberCommand(subcommand, ...args) {
|
|
|
12015
12018
|
}
|
|
12016
12019
|
|
|
12017
12020
|
module.exports = {
|
|
12021
|
+
logDateForScan,
|
|
12018
12022
|
memberRunDefaultWallSeconds,
|
|
12019
12023
|
memberRunWantsReferee,
|
|
12020
12024
|
proposalPromptForGoal,
|
package/commands/play.js
CHANGED
|
@@ -6,6 +6,7 @@ const fs = require('fs');
|
|
|
6
6
|
const { spawnSync } = require('child_process');
|
|
7
7
|
const { getSessionProfile, loadCredentials } = require('../utils/auth');
|
|
8
8
|
const { hasFlag: hasExactFlag } = require('../lib/arg-parser');
|
|
9
|
+
const { localDay } = require('../lib/local-day');
|
|
9
10
|
|
|
10
11
|
const AGENTXP_LEADERBOARD_URL = 'https://api.atris.ai/api/agentxp/leaderboard';
|
|
11
12
|
const AGENTXP_GLOBAL_SYNC_RULE = 'Run atris login, then sync. Owner-provided sync tokens are guided-demo fallback only.';
|
|
@@ -298,7 +299,7 @@ function ensureStarterMission(taskDb, db, workspaceRoot, player, tasks, args = [
|
|
|
298
299
|
assigned_to: player,
|
|
299
300
|
delegate_via: 'agentxp_play',
|
|
300
301
|
auto_seeded_by: 'atris play',
|
|
301
|
-
created_for_day:
|
|
302
|
+
created_for_day: localDay(),
|
|
302
303
|
},
|
|
303
304
|
});
|
|
304
305
|
taskDb.noteTask(db, {
|
|
@@ -487,4 +488,5 @@ async function playCommand(...args) {
|
|
|
487
488
|
|
|
488
489
|
module.exports = {
|
|
489
490
|
playCommand,
|
|
491
|
+
ensureStarterMission,
|
|
490
492
|
};
|
package/commands/pull.js
CHANGED
|
@@ -1023,7 +1023,9 @@ async function pullGeneralJournal(token, agentId) {
|
|
|
1023
1023
|
for (let i = 0; i < 3; i++) {
|
|
1024
1024
|
const d = new Date(today);
|
|
1025
1025
|
d.setDate(d.getDate() - i);
|
|
1026
|
-
|
|
1026
|
+
// Local calendar day, the same day log sync files the journal under.
|
|
1027
|
+
// toISOString() is the UTC day, already tomorrow in the evening here.
|
|
1028
|
+
dates.push(`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`);
|
|
1027
1029
|
}
|
|
1028
1030
|
|
|
1029
1031
|
let synced = 0;
|
|
@@ -1118,4 +1120,4 @@ async function pullMemberJournal(token, agentId, memberName, memberDir) {
|
|
|
1118
1120
|
return synced;
|
|
1119
1121
|
}
|
|
1120
1122
|
|
|
1121
|
-
module.exports = { buildPullConflictReviewPacket, mergeSmartPullFiles, normalizePullFilePath, pullAtris };
|
|
1123
|
+
module.exports = { buildPullConflictReviewPacket, mergeSmartPullFiles, normalizePullFilePath, pullAtris, pullGeneralJournal };
|
|
@@ -227,7 +227,10 @@ function securityReviewCommand(argv = []) {
|
|
|
227
227
|
// The landing: short, true, decision-ready. What you read after the overnight
|
|
228
228
|
// loop, the opposite of a finding dump.
|
|
229
229
|
function renderLanding(landing, threshold) {
|
|
230
|
-
|
|
230
|
+
// Local calendar day; toISOString is UTC and dates an evening run
|
|
231
|
+
// tomorrow anywhere west of Greenwich.
|
|
232
|
+
const now = new Date();
|
|
233
|
+
const date = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
|
231
234
|
const L = ['', ` ✈ security landing, ${date}`, ''];
|
|
232
235
|
if (landing.cleared) {
|
|
233
236
|
L.push(` CLEARED TO SHIP no unresolved findings at the ${threshold.toUpperCase()} line`);
|
package/commands/soul.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
const fs = require('fs');
|
|
14
14
|
const path = require('path');
|
|
15
|
+
const { localDay } = require('../lib/local-day');
|
|
15
16
|
|
|
16
17
|
function findAtrisDir() {
|
|
17
18
|
let dir = process.cwd();
|
|
@@ -272,7 +273,7 @@ function distillSoul(atrisDir) {
|
|
|
272
273
|
|
|
273
274
|
// Append distilled section to persona
|
|
274
275
|
const separator = '\n\n---\n\n';
|
|
275
|
-
const distilledBlock = `<!-- Distilled ${
|
|
276
|
+
const distilledBlock = `<!-- Distilled ${localDay()} | ${journalCount} journal entries, ${lessonItems.length} lessons, ${policyRules.length} policy rules -->\n\n` + distilled.join('\n');
|
|
276
277
|
|
|
277
278
|
if (persona) {
|
|
278
279
|
// Check if already has a distilled section, replace it
|
package/commands/sync.js
CHANGED
|
@@ -43,6 +43,12 @@ function _walkTemplateDir(dir, base = dir) {
|
|
|
43
43
|
return out;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
// Local calendar day (YYYY-MM-DD). toISOString() is the UTC day, which is
|
|
47
|
+
// already tomorrow in the evening anywhere west of Greenwich.
|
|
48
|
+
function localDay(date = new Date()) {
|
|
49
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
46
52
|
/**
|
|
47
53
|
* Substitute workspace metadata in template content.
|
|
48
54
|
*/
|
|
@@ -53,7 +59,7 @@ function _substituteParams(content, params) {
|
|
|
53
59
|
.replace(/\{\{owner_email\}\}/g, params.owner_email || '')
|
|
54
60
|
.replace(/\{\{business_id\}\}/g, params.business_id || '')
|
|
55
61
|
.replace(/\{\{workspace_id\}\}/g, params.workspace_id || '')
|
|
56
|
-
.replace(/\{\{today\}\}/g, params.today ||
|
|
62
|
+
.replace(/\{\{today\}\}/g, params.today || localDay())
|
|
57
63
|
.replace(/\{\{workspace_template\}\}/g, params.workspace_template || 'business');
|
|
58
64
|
}
|
|
59
65
|
|
|
@@ -337,7 +343,7 @@ function syncWorkspaceTemplate(targetRoot, bizMeta, options = {}) {
|
|
|
337
343
|
owner_email: bizMeta.owner_email || '',
|
|
338
344
|
business_id: bizMeta.business_id || '',
|
|
339
345
|
workspace_id: bizMeta.workspace_id || '',
|
|
340
|
-
today:
|
|
346
|
+
today: localDay(),
|
|
341
347
|
workspace_template: template.key,
|
|
342
348
|
};
|
|
343
349
|
const force = options.force != null ? options.force : process.argv.includes('--force');
|
package/commands/task.js
CHANGED
|
@@ -5857,7 +5857,7 @@ function delegateTask(args, options = {}) {
|
|
|
5857
5857
|
assigned_to: owner,
|
|
5858
5858
|
delegate_via: via,
|
|
5859
5859
|
swarlo_channel: via === 'swarlo' ? String(tag || 'tasks') : null,
|
|
5860
|
-
created_for_day:
|
|
5860
|
+
created_for_day: localDay(),
|
|
5861
5861
|
owner_resolution: ownerResolution.reason,
|
|
5862
5862
|
};
|
|
5863
5863
|
if (ownerResolution.requested_owner) metadata.requested_owner = ownerResolution.requested_owner;
|
|
@@ -6023,6 +6023,12 @@ function cmdKeep(args) {
|
|
|
6023
6023
|
console.log(`put away ${count} ${noun} that were finished or sitting still.`);
|
|
6024
6024
|
}
|
|
6025
6025
|
|
|
6026
|
+
// Local calendar day; toISOString is UTC and calls the evening tomorrow
|
|
6027
|
+
// anywhere west of Greenwich.
|
|
6028
|
+
function localDay(now = new Date()) {
|
|
6029
|
+
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
|
6030
|
+
}
|
|
6031
|
+
|
|
6026
6032
|
function cmdDay(args) {
|
|
6027
6033
|
const kept = require('../lib/task-list-keeper').keepWorkspaceTaskList(process.cwd());
|
|
6028
6034
|
if (keptCount(kept)) autoRenderTodoFromDb(process.cwd());
|
|
@@ -6042,7 +6048,7 @@ function cmdDay(args) {
|
|
|
6042
6048
|
failed: (projection.tasks || []).filter(task => task.status === 'failed').length,
|
|
6043
6049
|
stale_failed: staleFailed.length,
|
|
6044
6050
|
};
|
|
6045
|
-
const date =
|
|
6051
|
+
const date = localDay();
|
|
6046
6052
|
if (wantsJson(args)) {
|
|
6047
6053
|
printJson({
|
|
6048
6054
|
ok: true,
|
package/commands/taste.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { addTaste, listTaste } = require('../lib/taste-lessons');
|
|
4
|
+
const { localDay } = require('../lib/local-day');
|
|
4
5
|
|
|
5
6
|
const VALID_FLAGS = new Set(['--why', '--scope', '--example']);
|
|
6
7
|
|
|
@@ -86,7 +87,7 @@ function tasteCommand(args = [], options = {}) {
|
|
|
86
87
|
why,
|
|
87
88
|
scope,
|
|
88
89
|
example,
|
|
89
|
-
added:
|
|
90
|
+
added: localDay(),
|
|
90
91
|
root,
|
|
91
92
|
});
|
|
92
93
|
stdout.write(`saved the operator's ${entry.verdict} verdict for "${entry.subject}".\n`);
|
package/commands/x-search.js
CHANGED
|
@@ -472,9 +472,11 @@ function dateStamp(now) {
|
|
|
472
472
|
if (typeof now === 'string' && /^\d{4}-\d{2}-\d{2}/.test(now)) {
|
|
473
473
|
return now.slice(0, 10);
|
|
474
474
|
}
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
475
|
+
const parsed = now instanceof Date ? now : new Date(now || Date.now());
|
|
476
|
+
const value = Number.isNaN(parsed.getTime()) ? new Date() : parsed;
|
|
477
|
+
// Local calendar day. toISOString() is the UTC day, which is already
|
|
478
|
+
// tomorrow in the evening anywhere west of Greenwich.
|
|
479
|
+
return `${value.getFullYear()}-${String(value.getMonth() + 1).padStart(2, '0')}-${String(value.getDate()).padStart(2, '0')}`;
|
|
478
480
|
}
|
|
479
481
|
|
|
480
482
|
function fileXSearchBrief({ cwd, source, text, now } = {}) {
|
package/commands/youtube.js
CHANGED
|
@@ -2530,9 +2530,12 @@ function searchApplyNow(now) {
|
|
|
2530
2530
|
const value = now();
|
|
2531
2531
|
if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}/.test(value)) return value;
|
|
2532
2532
|
const ms = Number(value);
|
|
2533
|
-
if (Number.isFinite(ms) && ms > 0) return
|
|
2533
|
+
if (Number.isFinite(ms) && ms > 0) return dateStamp(ms);
|
|
2534
2534
|
return undefined;
|
|
2535
2535
|
}
|
|
2536
|
+
// The apply stub is dated by the local day, like the notes it follows;
|
|
2537
|
+
// left to itself the apply gate would stamp the UTC day.
|
|
2538
|
+
if (now === undefined || now === null || now instanceof Date || typeof now === 'number') return dateStamp(now);
|
|
2536
2539
|
return now;
|
|
2537
2540
|
}
|
|
2538
2541
|
|
|
@@ -3964,6 +3967,7 @@ async function youtubeCommand(argv = process.argv.slice(3), deps = {}) {
|
|
|
3964
3967
|
|
|
3965
3968
|
module.exports = {
|
|
3966
3969
|
DEFAULT_QUERY,
|
|
3970
|
+
searchApplyNow,
|
|
3967
3971
|
DEFAULT_TIMEOUT_MS,
|
|
3968
3972
|
parseYoutubeArgs,
|
|
3969
3973
|
buildYoutubePayload,
|
package/commands/zen.js
CHANGED
|
@@ -143,7 +143,7 @@ function printApply(result, out) {
|
|
|
143
143
|
}
|
|
144
144
|
line(dim(` ${teamLine(result.team_files)}`));
|
|
145
145
|
if (result.saved.length) {
|
|
146
|
-
line(dim('
|
|
146
|
+
line(dim(' nothing was thrown away. see what zen saved: git for-each-ref refs/archive refs/heads/zen-backup'));
|
|
147
147
|
}
|
|
148
148
|
line('');
|
|
149
149
|
}
|
package/lib/apply-gate.js
CHANGED
|
@@ -7,9 +7,11 @@ function dateStamp(now) {
|
|
|
7
7
|
if (typeof now === 'string' && /^\d{4}-\d{2}-\d{2}/.test(now)) {
|
|
8
8
|
return now.slice(0, 10);
|
|
9
9
|
}
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
const parsed = now instanceof Date ? now : new Date(now || Date.now());
|
|
11
|
+
const value = Number.isNaN(parsed.getTime()) ? new Date() : parsed;
|
|
12
|
+
// Local calendar day. toISOString() is the UTC day, which is already
|
|
13
|
+
// tomorrow in the evening anywhere west of Greenwich.
|
|
14
|
+
return `${value.getFullYear()}-${String(value.getMonth() + 1).padStart(2, '0')}-${String(value.getDate()).padStart(2, '0')}`;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
function parseApplyFields(text) {
|
package/lib/auto-lessons.js
CHANGED
|
@@ -134,7 +134,9 @@ function writeJsonAtomic(filePath, value) {
|
|
|
134
134
|
function isoDate(now) {
|
|
135
135
|
const date = now instanceof Date ? now : new Date(now || Date.now());
|
|
136
136
|
if (Number.isNaN(date.getTime())) throw new AutoLessonsError('cannot file automatic lessons with an invalid date');
|
|
137
|
-
|
|
137
|
+
// Local calendar day; toISOString is UTC and dates an evening lesson
|
|
138
|
+
// tomorrow anywhere west of Greenwich.
|
|
139
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
|
138
140
|
}
|
|
139
141
|
|
|
140
142
|
function typedLesson(signal, options = {}) {
|
package/lib/experiments/daily.js
CHANGED
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { spawnSync } = require('child_process');
|
|
6
|
+
const { localDay } = require('../local-day');
|
|
6
7
|
|
|
7
8
|
const SCORECARD_SCHEMA = 'atris.experiment.daily.v1';
|
|
8
9
|
const BENCH_SCHEMA = 'atris.bench.run.v1';
|
|
9
10
|
|
|
10
11
|
function todayDate() {
|
|
11
|
-
return
|
|
12
|
+
return localDay();
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
function statePath(root) {
|
package/lib/gm-bet.js
CHANGED
|
@@ -316,6 +316,7 @@ function buildQuestionPrompt(bundle, context, { answers = [], avoid = [] } = {})
|
|
|
316
316
|
`- fact: the plain reason this question matters now, one sentence he would say himself, drawn from the files below, with a number or at least four words in a row copied from them, under ${QUESTION_FACT_MAX} characters. it must lead straight to the question. if nothing in the files gives a reason for this question, set fact to "".`,
|
|
317
317
|
`- choices: 2 or 3 short concrete answers he can tap, each under ${QUESTION_CHOICE_MAX} characters.`,
|
|
318
318
|
`- never use these words: ${OWNER_BANNED_WORDS.join(', ')}.`,
|
|
319
|
+
'- talk to him as "you", for example "changes you redid". never use his name, and never say "the owner".',
|
|
319
320
|
'- a section marked stale is old: prefer the fresh ones, and never present a stale line as what is happening now.',
|
|
320
321
|
'- ask about the product and the people who use it or pay for it, for example "who do you most want using this?". never mention ideas, the team, or what happens to his answer.',
|
|
321
322
|
'- plain lowercase-friendly words, no hype, no em dashes.',
|
|
@@ -346,6 +347,25 @@ function buildQuestionRewrite(previous, reasons) {
|
|
|
346
347
|
].join('\n');
|
|
347
348
|
}
|
|
348
349
|
|
|
350
|
+
// The owner's first name, read at run time from deps or the workspace's git
|
|
351
|
+
// user, so a label a writer wrote about him by name reads to him as "you".
|
|
352
|
+
// Null when there is none to read, and then nothing is swapped.
|
|
353
|
+
function ownerFirstName(workspaceRoot, deps = {}) {
|
|
354
|
+
if (deps.ownerName !== undefined) return deps.ownerName || null;
|
|
355
|
+
try {
|
|
356
|
+
const name = require('child_process').execFileSync('git', ['config', 'user.name'], { cwd: workspaceRoot, timeout: 2000, stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim().split(/\s+/)[0];
|
|
357
|
+
return /^[A-Za-z][A-Za-z'-]{1,}$/.test(name) ? name : null;
|
|
358
|
+
} catch {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function toYou(label, name) {
|
|
364
|
+
if (!name || typeof label !== 'string') return label;
|
|
365
|
+
const esc = name.replace(/[-']/g, m => `\\${m}`);
|
|
366
|
+
return label.replace(new RegExp(`\\b${esc}'s\\b`, 'gi'), 'your').replace(new RegExp(`\\b${esc}\\b`, 'gi'), 'you');
|
|
367
|
+
}
|
|
368
|
+
|
|
349
369
|
// ---- reading free text: is it a question back, an answer, an instruction? ----
|
|
350
370
|
|
|
351
371
|
const SAY_READ_MS = 15000;
|
|
@@ -383,9 +403,10 @@ async function readSaid({ text, card, thread = [], hint = null, workspaceRoot, b
|
|
|
383
403
|
'- instruction: he wants something done, or has a new idea. reply: one plain sentence saying what will happen next.',
|
|
384
404
|
'- unclear: you cannot tell. reply: one short question back.',
|
|
385
405
|
'- a message that ends with "?" or asks why, what, or how is a question back, never an answer.',
|
|
386
|
-
'- you may add "suggestions": 0 to 3 short replies he could tap next, each under 40 characters.',
|
|
406
|
+
'- you may add "suggestions": 0 to 3 short replies he could tap next, each under 40 characters, never one of the buttons already on the card.',
|
|
387
407
|
card.kind === 'number' ? '- on this number card an instruction also has "intent": "stop" when he wants to stop tracking this number, "change" when he wants a different number, "later" when he wants to be asked another time, else "other". for "other", reply says what this card can do.' : '',
|
|
388
408
|
`- plain lowercase-friendly words, no em dashes, and never these words: ${OWNER_BANNED_WORDS.join(', ')}.`,
|
|
409
|
+
'- talk to him as "you", for example "changes you redid". never use his name, and never say "the owner".',
|
|
389
410
|
'',
|
|
390
411
|
`## the card on screen (${card.kind})`,
|
|
391
412
|
card.title || '',
|
|
@@ -424,6 +445,7 @@ async function generateScoreboardChoices({ workspaceRoot, bundle = null, deps =
|
|
|
424
445
|
'Answer with strict JSON only, no prose and no code fence: {"choices": [{"name": "...", "up": true}]}',
|
|
425
446
|
'- name: a short plain phrase under 40 characters. up: true when higher is better, false when lower is better.',
|
|
426
447
|
`- never use these words: ${OWNER_BANNED_WORDS.join(', ')}.`,
|
|
448
|
+
'- talk to him as "you", for example "changes you redid". never use his name, and never say "the owner".',
|
|
427
449
|
'',
|
|
428
450
|
"## this project's files",
|
|
429
451
|
context || '(no readme or goals found)',
|
|
@@ -2708,7 +2730,7 @@ function createBetDesk(deps) {
|
|
|
2708
2730
|
const board = readScoreboard(scoreboardFile, deps.workspaceRoot);
|
|
2709
2731
|
if (!board) {
|
|
2710
2732
|
if (state.passedQuestions.has('scoreboard')) return null;
|
|
2711
|
-
const list = (state.suggestions || fallbackNumbers()).slice(0, 3);
|
|
2733
|
+
const list = (state.suggestions || fallbackNumbers()).slice(0, 3).map(c => ({ ...c, name: you(c.name) }));
|
|
2712
2734
|
return {
|
|
2713
2735
|
kind: 'question',
|
|
2714
2736
|
id: 'question:scoreboard',
|
|
@@ -2812,7 +2834,7 @@ function createBetDesk(deps) {
|
|
|
2812
2834
|
const row = readyQuestions().find(r => !state.passedQuestions.has(r.id));
|
|
2813
2835
|
if (row) {
|
|
2814
2836
|
const q = row.question;
|
|
2815
|
-
return { kind: 'question', id: `question:${row.id}`, question: { key: row.id, ask: q.ask, line: q.fact, choices: q.choices.map((label, i) => ({ id: `a${i + 1}`, label })) } };
|
|
2837
|
+
return { kind: 'question', id: `question:${row.id}`, question: { key: row.id, ask: q.ask, line: q.fact, choices: q.choices.map((label, i) => ({ id: `a${i + 1}`, label: you(label) })) } };
|
|
2816
2838
|
}
|
|
2817
2839
|
const F = FALLBACK_QUESTION;
|
|
2818
2840
|
const said = readFocus(focusFile, deps.workspaceRoot)[F.key];
|
|
@@ -3266,6 +3288,9 @@ function createBetDesk(deps) {
|
|
|
3266
3288
|
t.turns = [...t.turns, ...turns].slice(-THREAD_TURNS);
|
|
3267
3289
|
}
|
|
3268
3290
|
const plainReply = text => (text && !ownerWord(text) ? text : null);
|
|
3291
|
+
let ownerName;
|
|
3292
|
+
const you = label => toYou(label, ownerName === undefined ? (ownerName = ownerFirstName(deps.workspaceRoot, deps)) : ownerName);
|
|
3293
|
+
const sameLabel = (a, b) => String(a).trim().toLowerCase() === String(b).trim().toLowerCase();
|
|
3269
3294
|
const plainSuggestions = list => (Array.isArray(list) ? list : [])
|
|
3270
3295
|
.filter(x => typeof x === 'string' && x.trim() && x.length <= 40 && !ownerWord(x) && !PROCESS_TALK.test(x))
|
|
3271
3296
|
.slice(0, 3);
|
|
@@ -3306,7 +3331,9 @@ function createBetDesk(deps) {
|
|
|
3306
3331
|
if (read && read.ok && (read.kind === 'answer' || read.kind === 'instruction')) return { act: read };
|
|
3307
3332
|
const kind = read && read.ok ? read.kind : 'unclear';
|
|
3308
3333
|
const reply = (read && read.ok && plainReply(read.reply)) || SAY_FALLBACK;
|
|
3309
|
-
const suggestions = read && read.ok && reply !== SAY_FALLBACK
|
|
3334
|
+
const suggestions = read && read.ok && reply !== SAY_FALLBACK
|
|
3335
|
+
? plainSuggestions((Array.isArray(read.suggestions) ? read.suggestions : []).map(you).filter(sug => !(card.buttons || []).some(b => sameLabel(b, sug))))
|
|
3336
|
+
: [];
|
|
3310
3337
|
addTurns(t, { who: 'owner', text }, { who: 'orb', text: reply });
|
|
3311
3338
|
return { stay: { ok: true, stay: true, kind: reply === SAY_FALLBACK ? 'unclear' : kind, reply, ...(suggestions.length ? { suggestions } : {}), thread: t.turns.slice() } };
|
|
3312
3339
|
}
|
|
@@ -3549,6 +3576,7 @@ module.exports = {
|
|
|
3549
3576
|
FALLBACK_QUESTION,
|
|
3550
3577
|
readScoreboard,
|
|
3551
3578
|
readSaid,
|
|
3579
|
+
generateScoreboardChoices,
|
|
3552
3580
|
scoreboardPath,
|
|
3553
3581
|
choiceWhy,
|
|
3554
3582
|
JOB_POLL_MS,
|
package/lib/gm-phone-page.js
CHANGED
|
@@ -174,6 +174,8 @@ body.has-card .foot { position: static; margin-top: 28px; }
|
|
|
174
174
|
/* a chosen answer holds its spot with an amber edge, and the card takes no taps */
|
|
175
175
|
.choice.chosen, .option.chosen, .field.chosen { border-color: var(--amber); background: rgba(255, 140, 66, 0.1); }
|
|
176
176
|
.chosen .send { visibility: hidden; }
|
|
177
|
+
.chosen .send.reading { visibility: visible; }
|
|
178
|
+
.bet h1.said { font-size: 20px; line-height: 1.4; font-weight: 400; letter-spacing: 0; }
|
|
177
179
|
.bet.holding { pointer-events: none; }
|
|
178
180
|
.muted { opacity: 0.35; transition: opacity 0.2s ease; }
|
|
179
181
|
|
|
@@ -890,10 +892,25 @@ body.has-card .foot { position: static; margin-top: 28px; }
|
|
|
890
892
|
return spot.sendId;
|
|
891
893
|
}
|
|
892
894
|
|
|
895
|
+
// while the words are read, one quiet ellipsis in the held spot: where
|
|
896
|
+
// send was in a box, or after a tapped reply. Nowhere else.
|
|
897
|
+
function reading(spot) {
|
|
898
|
+
var send = spot.querySelector('.send');
|
|
899
|
+
if (send) { send.textContent = '\u2026'; send.classList.add('reading'); return; }
|
|
900
|
+
spot.appendChild(el('span', 'reading', ' \u2026'));
|
|
901
|
+
}
|
|
902
|
+
function doneReading(spot) {
|
|
903
|
+
var send = spot.querySelector('.send.reading');
|
|
904
|
+
if (send) { send.textContent = 'send'; send.classList.remove('reading'); }
|
|
905
|
+
var dots = spot.querySelector('span.reading');
|
|
906
|
+
if (dots) dots.remove();
|
|
907
|
+
}
|
|
908
|
+
|
|
893
909
|
function say(spot, body) {
|
|
894
910
|
body.card = shownCard();
|
|
895
911
|
body.send = sendId(spot, body.text);
|
|
896
912
|
var heldAt = hold(spot);
|
|
913
|
+
reading(spot);
|
|
897
914
|
api('/bet/say', body).catch(offline).then(function (r) {
|
|
898
915
|
if (r.ok && !r.stay) { think(false, heldAt); return; }
|
|
899
916
|
if (!r.ok && r.reason !== 'needs_confirm') { unsent(spot, r.message || 'that did not send. tap send to try again'); return; }
|
|
@@ -906,6 +923,7 @@ body.has-card .foot { position: static; margin-top: 28px; }
|
|
|
906
923
|
// the same tap) tries again. Left empty, the box turns back into the
|
|
907
924
|
// button and the line goes with it.
|
|
908
925
|
function unsent(spot, why) {
|
|
926
|
+
doneReading(spot);
|
|
909
927
|
unhold();
|
|
910
928
|
var note = spot.nextElementSibling;
|
|
911
929
|
var input = spot.querySelector('input');
|
|
@@ -942,10 +960,18 @@ body.has-card .foot { position: static; margin-top: 28px; }
|
|
|
942
960
|
if (bet.last) { drawBetCard(bet.last); own = box.querySelector(':scope > .act, :scope > .options'); }
|
|
943
961
|
box.textContent = '';
|
|
944
962
|
bet.drawn = null;
|
|
945
|
-
|
|
963
|
+
var title = el('h1', null, r.stay ? r.reply : (r.message || 'that did not go through'));
|
|
964
|
+
box.appendChild(title);
|
|
965
|
+
// more than one line reads at the body size, in the same spot
|
|
966
|
+
var lines = document.createRange();
|
|
967
|
+
lines.selectNodeContents(title);
|
|
968
|
+
if (lines.getClientRects().length > 1) title.classList.add('said');
|
|
946
969
|
if (own) box.appendChild(own);
|
|
947
970
|
var act = el('div', 'act answers');
|
|
948
|
-
|
|
971
|
+
var have = own ? Array.prototype.map.call(own.querySelectorAll('button'), function (b) { return b.textContent.trim().toLowerCase(); }) : [];
|
|
972
|
+
if (r.stay) (r.suggestions || []).forEach(function (sug) {
|
|
973
|
+
if (have.indexOf(String(sug).trim().toLowerCase()) < 0) act.appendChild(sayChoice(sug, { text: sug }));
|
|
974
|
+
});
|
|
949
975
|
if (r.reason === 'needs_confirm') {
|
|
950
976
|
act.appendChild(sayChoice('start it', { text: said, confirm: true }));
|
|
951
977
|
act.appendChild(sayChoice('keep it as a note instead', { text: said, note: true }));
|
|
@@ -35,13 +35,22 @@ function commitsSinceMidnight(root) {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
// Local calendar day (YYYY-MM-DD) of a time, or null. Close events are stored
|
|
39
|
+
// as UTC ISO times, and the UTC day is already tomorrow in the evening west
|
|
40
|
+
// of Greenwich, so "today" is compared as local days on both sides.
|
|
41
|
+
function localDay(value) {
|
|
42
|
+
const d = value instanceof Date ? value : new Date(value || NaN);
|
|
43
|
+
if (Number.isNaN(d.getTime())) return null;
|
|
44
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
38
47
|
function closedToday(cwd, now) {
|
|
39
48
|
try {
|
|
40
49
|
const close = require('../commands/close');
|
|
41
|
-
const today = now
|
|
50
|
+
const today = localDay(now);
|
|
42
51
|
return close.foldEvents(close.readEvents(cwd), { now })
|
|
43
|
-
.filter((f) => (f.status === 'closed' &&
|
|
44
|
-
|| (f.status === 'dissolved' &&
|
|
52
|
+
.filter((f) => (f.status === 'closed' && localDay(f.closed_at) === today)
|
|
53
|
+
|| (f.status === 'dissolved' && localDay(f.dissolved_at) === today))
|
|
45
54
|
.map((f) => ({ what: f.what, status: f.status }));
|
|
46
55
|
} catch {
|
|
47
56
|
return [];
|
package/lib/local-day.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// The person's own calendar day as YYYY-MM-DD. toISOString() gives the UTC
|
|
4
|
+
// day, which is already tomorrow in the evening west of Greenwich.
|
|
5
|
+
function localDay(date = new Date()) {
|
|
6
|
+
const d = date instanceof Date ? date : new Date(date);
|
|
7
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
module.exports = { localDay };
|
package/lib/orb-scorecard.js
CHANGED
|
@@ -115,6 +115,13 @@ function pairOrbRuns(records) {
|
|
|
115
115
|
return { runs, orphans };
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
// Local calendar day (YYYY-MM-DD). Orb picks are dated by the day they were
|
|
119
|
+
// made; toISOString() is the UTC day, already tomorrow in the evening west of
|
|
120
|
+
// Greenwich.
|
|
121
|
+
function localDay(date) {
|
|
122
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
118
125
|
function readOrbScorecard(root, { days = DEFAULT_DAYS, now = Date.now() } = {}) {
|
|
119
126
|
const windowDays = Number(days);
|
|
120
127
|
if (!Number.isInteger(windowDays) || windowDays < 1) {
|
|
@@ -125,8 +132,8 @@ function readOrbScorecard(root, { days = DEFAULT_DAYS, now = Date.now() } = {})
|
|
|
125
132
|
|
|
126
133
|
const scorecard = emptyOrbScorecard(windowDays);
|
|
127
134
|
const cutoffMs = nowMs - (windowDays * DAY_MS);
|
|
128
|
-
const cutoffDay = new Date(cutoffMs)
|
|
129
|
-
const today = new Date(nowMs)
|
|
135
|
+
const cutoffDay = localDay(new Date(cutoffMs));
|
|
136
|
+
const today = localDay(new Date(nowMs));
|
|
130
137
|
scorecard.picks = readOrbPicks(root, cutoffDay, today);
|
|
131
138
|
|
|
132
139
|
const indexPath = path.join(root, '.atris', 'state', 'orb-runs', 'index.jsonl');
|
package/lib/scorecard.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { parseTodo } = require('./todo');
|
|
4
|
+
const { localDay } = require('./local-day');
|
|
4
5
|
|
|
5
6
|
const PRIVATE_MEMORY_ROOT = '.atris/presidio';
|
|
6
7
|
|
|
@@ -72,8 +73,8 @@ function listLogFiles(atrisDir, startDate, endDate = new Date()) {
|
|
|
72
73
|
const logsDir = path.join(atrisDir, 'logs');
|
|
73
74
|
if (!fs.existsSync(logsDir)) return [];
|
|
74
75
|
|
|
75
|
-
const startKey = startDate
|
|
76
|
-
const endKey = endDate
|
|
76
|
+
const startKey = localDay(startDate);
|
|
77
|
+
const endKey = localDay(endDate);
|
|
77
78
|
const files = [];
|
|
78
79
|
|
|
79
80
|
for (const year of fs.readdirSync(logsDir)) {
|
|
@@ -163,7 +164,7 @@ function countLessonsGenerated(atrisDir, pickedAt) {
|
|
|
163
164
|
const lessonsPath = path.join(atrisDir, 'lessons.md');
|
|
164
165
|
if (!fs.existsSync(lessonsPath)) return 0;
|
|
165
166
|
|
|
166
|
-
const startDateKey = startAt
|
|
167
|
+
const startDateKey = localDay(startAt);
|
|
167
168
|
return fs.readFileSync(lessonsPath, 'utf8')
|
|
168
169
|
.split('\n')
|
|
169
170
|
.reduce((count, line) => {
|
|
@@ -191,8 +192,8 @@ function buildScorecardData(atrisDir, { slug, pickedAt } = {}) {
|
|
|
191
192
|
|
|
192
193
|
return {
|
|
193
194
|
slug,
|
|
194
|
-
startDate: startAt
|
|
195
|
-
endDate:
|
|
195
|
+
startDate: localDay(startAt),
|
|
196
|
+
endDate: localDay(),
|
|
196
197
|
tasksShipped: Math.max(shipped, 0),
|
|
197
198
|
tasksAttempted: Math.max(attempted, shipped),
|
|
198
199
|
wallClockHours: Math.max(0, (Date.now() - startAt.getTime()) / (1000 * 60 * 60)),
|
|
@@ -221,7 +222,7 @@ function writeScorecard(atrisDir, data) {
|
|
|
221
222
|
const {
|
|
222
223
|
slug,
|
|
223
224
|
startDate,
|
|
224
|
-
endDate =
|
|
225
|
+
endDate = localDay(),
|
|
225
226
|
tasksShipped = 0,
|
|
226
227
|
tasksAttempted = 0,
|
|
227
228
|
wallClockHours = 0,
|
package/lib/wiki.js
CHANGED
|
@@ -31,8 +31,11 @@ function getWikiLinkRoot(mode = 'public') {
|
|
|
31
31
|
return mode === 'private' ? PRIVATE_WIKI_ROOT : 'atris/wiki';
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
// Local calendar day; toISOString is UTC and files an evening entry under
|
|
35
|
+
// tomorrow anywhere west of Greenwich, next to a local clock time.
|
|
34
36
|
function today() {
|
|
35
|
-
|
|
37
|
+
const now = new Date();
|
|
38
|
+
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
function nowTime() {
|
package/package.json
CHANGED