atris 3.74.2 → 3.74.4

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 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.
@@ -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 = new Date().toISOString().slice(0, 10);
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 = new Date().toISOString().slice(0, 10);
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: ${new Date().toISOString().split('T')[0]}*\n`,
2812
+ `---\n*Created: ${localDay()}*\n`,
2807
2813
  ].join(''));
2808
2814
  console.log(` Local scaffold: ${bizDir}/`);
2809
2815
  }
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: new Date().toISOString().slice(0, 10),
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,
@@ -3385,7 +3385,10 @@ function logDateForScan(relativePath, filePath) {
3385
3385
  const named = dateFromLogPath(relativePath);
3386
3386
  if (named) return named;
3387
3387
  try {
3388
- return fs.statSync(filePath).mtime.toISOString().slice(0, 10);
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/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: new Date().toISOString().slice(0, 10),
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 = new Date().toISOString().slice(0, 10);
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: new Date().toISOString().slice(0, 10),
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/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(' everything is kept: git for-each-ref refs/archive refs/heads/zen-backup'));
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
  }
@@ -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
- return date.toISOString().slice(0, 10);
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 = {}) {
@@ -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.toISOString().slice(0, 10);
50
+ const today = localDay(now);
42
51
  return close.foldEvents(close.readEvents(cwd), { now })
43
- .filter((f) => (f.status === 'closed' && String(f.closed_at || '').startsWith(today))
44
- || (f.status === 'dissolved' && String(f.dissolved_at || '').startsWith(today)))
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 [];
@@ -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).toISOString().slice(0, 10);
129
- const today = new Date(nowMs).toISOString().slice(0, 10);
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/yes-lock.js CHANGED
@@ -674,21 +674,42 @@ const PUSH_HINT = 'a branch push runs without a yes as: git push origin <branch>
674
674
 
675
675
  // git reads, plus local work that cannot move a ref other than the branch it is on
676
676
  const GIT_SEALED_READS = new Set(['status', 'diff', 'log', 'show', 'blame', 'rev-parse', 'ls-files', 'grep', 'describe', 'rev-list', 'merge-base', 'cat-file', 'ls-tree', 'show-ref', 'for-each-ref', 'shortlog', 'check-ignore', 'help', 'version']);
677
- const GIT_SEALED_LOCAL = new Set(['add', 'rm', 'mv', 'restore']);
677
+ const GIT_SEALED_LOCAL = new Set(['add', 'rm', 'mv', 'restore', 'update-index', 'write-tree']);
678
678
  const GIT_SEALED_GLOBALS = new Set(['--no-pager', '-P', '--no-optional-locks', '--literal-pathspecs', '--version', '--help', '-h']);
679
679
  // the shape of a branch name git accepts, without the corners that read as something else
680
680
  const BRANCH_NAME = /^(?!-)(?!\.)(?!.*\.\.)(?!.*\/\/)(?!.*\/\.)(?!.*@\{)(?!.*\.lock(?:\/|$))[A-Za-z0-9._/-]+(?<![./])$/;
681
681
 
682
- // Only -C and a few output switches go before the command. -c,
683
- // --config-env, and --exec-path are held: config can name a program git runs
684
- // (core.fsmonitor, core.pager), and git puts its own folder first on PATH for
685
- // that program, so a git inside it would skip the wrapper.
682
+ // -c a woken run may pass: a value that only turns something off, or a key
683
+ // that only changes how output looks. The engines harden their own git calls
684
+ // this way (hooks, fsmonitor, askpass, protocols, gc all off). A -c that
685
+ // names a program, path, command, alias, helper, pager, editor, or route is
686
+ // held, since git runs that program with its own folder first on PATH, where
687
+ // a git inside it skips the wrapper; so are the keys whose value is a name,
688
+ // URL, or refspec (alias, remote, url, push, include, credential, branch
689
+ // routing), where even an empty value can reroute (url.<x>.insteadOf=).
690
+ const GIT_DISPLAY_KEYS = new Set(['format.pretty', 'log.showsignature', 'status.showuntrackedfiles', 'core.quotepath', 'core.safecrlf', 'safe.barerepository', 'color.ui']);
691
+ const GIT_ROUTE_KEYS = /^(alias|remote|url|push|include|includeif|credential)\.|^branch\..+\.(remote|pushremote|merge)$/;
692
+
693
+ function sealedGitConfigOk(pair) {
694
+ const eq = String(pair).indexOf('=');
695
+ const key = (eq < 0 ? String(pair) : String(pair).slice(0, eq)).trim().toLowerCase();
696
+ const value = eq < 0 ? 'true' : String(pair).slice(eq + 1).trim();
697
+ if (GIT_DISPLAY_KEYS.has(key)) return true;
698
+ if (key === 'core.hookspath') return value === '/dev/null';
699
+ if (GIT_ROUTE_KEYS.test(key)) return false;
700
+ return /^(|false|never|0|no|off)$/i.test(value);
701
+ }
702
+
703
+ // Only -C, -c that turns something off, and a few output switches go before
704
+ // the command. --config-env and --exec-path are held.
686
705
  function sealedGitPrefix(args) {
687
706
  const prefix = [];
688
707
  for (let i = 0; i < args.length; i += 1) {
689
708
  const a = args[i];
690
- if (a === '-C') {
691
- prefix.push(a, String(args[i + 1] === undefined ? '' : args[i + 1]));
709
+ if (a === '-C' || a === '-c') {
710
+ const value = String(args[i + 1] === undefined ? '' : args[i + 1]);
711
+ if (a === '-c' && !sealedGitConfigOk(value)) return { bad: `-c ${value}` };
712
+ prefix.push(a, value);
692
713
  i += 1;
693
714
  continue;
694
715
  }
@@ -726,9 +747,11 @@ function sealedGit(args, ctx) {
726
747
  if (!p.sub) return pass();
727
748
  if (GIT_SEALED_READS.has(p.sub) || GIT_SEALED_LOCAL.has(p.sub)) return pass();
728
749
  // the questions below are asked of the repo the command names
729
- const repo = ctx.forRepo(p.prefix.filter((a, i, all) => a === '-C' || all[i - 1] === '-C'));
750
+ const repo = ctx.forRepo(p.prefix.filter((a, i, all) => a === '-C' || a === '-c' || all[i - 1] === '-C' || all[i - 1] === '-c'));
730
751
  const rest = p.rest;
731
752
  switch (p.sub) {
753
+ // a new repo has no remote; a template or a git folder elsewhere could bring hooks
754
+ case 'init': return rest.some(a => /^--(template|separate-git-dir)(=|$)/.test(a)) ? hold('git init with a template or a git folder elsewhere can bring hooks') : pass();
732
755
  case 'push': return sealedPush(p, repo);
733
756
  case 'fetch': return sealedFetch(rest, repo);
734
757
  case 'commit': return isMainRef(repo.currentBranch()) ? hold('a commit here would land on master') : pass();
@@ -764,7 +787,9 @@ function sealedPush(p, repo) {
764
787
  words.push(a);
765
788
  }
766
789
  if (words.length !== 2 || words[0] !== 'origin') return hold(`a handed-off job pushes one branch to origin; ${PUSH_HINT}`);
767
- const head = words[1].match(/^HEAD:refs\/heads\/(.+)$/);
790
+ // HEAD:<branch> is written out as HEAD:refs/heads/<branch>, so a name that
791
+ // matches a tag on origin can never update the tag
792
+ const head = words[1].match(/^HEAD:(?:refs\/heads\/(?=.))?(?!refs\/)(.+)$/);
768
793
  const branch = head ? head[1] : words[1];
769
794
  if (!head && (branch.includes(':') || branch.startsWith('refs/'))) return hold(`git push ${words[1]} names more than a branch; ${PUSH_HINT}`);
770
795
  if (isMainRef(branch)) return hold(`git push to master lands on master; ${PUSH_HINT}`);
@@ -964,6 +989,8 @@ const ATRIS_SEALED = {
964
989
  function sealedAtris(args, missionId) {
965
990
  const [cmd = '', sub = ''] = args;
966
991
  if (args.length === 1 && ['--version', '-v', '--help', '-h'].includes(cmd)) return pass();
992
+ // the boot read: atris prints the welcome screen for its spec file and stops
993
+ if (args.length === 1 && (cmd === 'atris.md' || cmd.endsWith('/atris.md'))) return pass();
967
994
  const allowed = Object.prototype.hasOwnProperty.call(ATRIS_SEALED, cmd) ? ATRIS_SEALED[cmd] : null;
968
995
  if (!allowed) return hold(`atris ${cmd} is not on the list a handed-off job may run`);
969
996
  const subOk = allowed === true || ['help', '--help', '-h'].includes(sub)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atris",
3
- "version": "3.74.2",
3
+ "version": "3.74.4",
4
4
  "description": "you say what you want in plain words. atris builds it, checks it, and shows you proof.",
5
5
  "mcpName": "io.github.atrislabs/atris",
6
6
  "main": "bin/atris.js",