atris 3.71.2 → 3.72.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.
Files changed (52) hide show
  1. package/atris/skills/campaigns/SKILL.md +1 -1
  2. package/bin/atris.js +8 -2
  3. package/commands/autoland.js +102 -2
  4. package/commands/campaign.js +263 -11
  5. package/commands/clean.js +60 -4
  6. package/commands/gm.js +1273 -7
  7. package/commands/improve.js +3 -2
  8. package/commands/member.js +28 -3
  9. package/commands/mission.js +82 -27
  10. package/commands/pulse.js +89 -5
  11. package/commands/sync.js +2 -2
  12. package/commands/task.js +49 -13
  13. package/commands/worktree.js +24 -2
  14. package/commands/zen.js +183 -0
  15. package/lib/auto-accept-certified.js +9 -4
  16. package/lib/campaign-cadence.js +53 -14
  17. package/lib/campaign-core.js +98 -17
  18. package/lib/campaign-keep.js +91 -0
  19. package/lib/campaign-lanes.js +11 -6
  20. package/lib/campaign-loop.js +173 -32
  21. package/lib/campaign-reading.js +14 -1
  22. package/lib/campaign-rsi.js +697 -0
  23. package/lib/campaign-run-lock.js +9 -1
  24. package/lib/campaign-schedule.js +124 -0
  25. package/lib/campaign-schema.js +3 -1
  26. package/lib/campaign-store.js +34 -2
  27. package/lib/campaign-trace.js +77 -29
  28. package/lib/engine-resource.js +5 -1
  29. package/lib/gm-bet.js +2219 -0
  30. package/lib/gm-phone-page.js +1102 -0
  31. package/lib/gm-phone.js +682 -0
  32. package/lib/gm-season.js +1162 -0
  33. package/lib/gm-target.js +292 -0
  34. package/lib/job-code-verify.js +169 -0
  35. package/lib/known-commands.js +1 -1
  36. package/lib/member-autoland.js +105 -14
  37. package/lib/member-progress.js +2 -2
  38. package/lib/member-report.js +11 -1
  39. package/lib/member-self-next-job.js +6 -1
  40. package/lib/member-up.js +17 -8
  41. package/lib/merged-review-close.js +206 -0
  42. package/lib/mission-code-provenance.js +118 -0
  43. package/lib/mission-protected-lane.js +52 -4
  44. package/lib/pulse.js +2 -1
  45. package/lib/qr.js +338 -0
  46. package/lib/rsi-record.js +88 -0
  47. package/lib/task-receipt.js +24 -6
  48. package/lib/yes-lock.js +530 -0
  49. package/lib/zen-apply.js +511 -0
  50. package/lib/zen-workspace.js +121 -0
  51. package/lib/zen.js +395 -0
  52. package/package.json +1 -1
package/commands/clean.js CHANGED
@@ -634,6 +634,42 @@ function findStalePages(cwd, atrisDir) {
634
634
  return stalePages;
635
635
  }
636
636
 
637
+ /**
638
+ * When a source last changed. A checkout or rebase touches mtimes without
639
+ * changing content, so a file git holds unchanged counts from its last commit.
640
+ */
641
+ function contentChangedAt(sourcePath, mtime) {
642
+ const dir = path.dirname(sourcePath);
643
+ const name = path.basename(sourcePath);
644
+ const status = spawnSync('git', ['status', '--porcelain', '--', name], { cwd: dir, encoding: 'utf8' });
645
+ if (status.status !== 0 || status.stdout.trim()) return mtime;
646
+ const log = spawnSync('git', ['log', '-1', '--format=%cI', '--', name], { cwd: dir, encoding: 'utf8' });
647
+ const committed = log.status === 0 ? log.stdout.trim() : '';
648
+ return committed ? new Date(committed) : mtime;
649
+ }
650
+
651
+ function looksLikePath(text) {
652
+ if (!text || /\s/.test(text) || /^[a-z][a-z0-9+.-]*:\/\//i.test(text)) return false;
653
+ return text.includes('/') || /\.[A-Za-z0-9]+$/.test(text);
654
+ }
655
+
656
+ // A relative source can be written from the repo root, the page's own folder,
657
+ // or any folder in between (a brief citing "briefs/x.md" from the wiki root).
658
+ // The first place it exists wins; if none, the repo-root path is reported.
659
+ function resolveSource(cwd, pagePath, filePart) {
660
+ if (path.isAbsolute(filePart)) return filePart;
661
+ const root = path.resolve(cwd);
662
+ const fromRoot = path.join(root, filePart);
663
+ if (fs.existsSync(fromRoot)) return fromRoot;
664
+ let dir = path.dirname(path.resolve(pagePath));
665
+ while (dir.startsWith(root + path.sep)) {
666
+ const candidate = path.join(dir, filePart);
667
+ if (fs.existsSync(candidate)) return candidate;
668
+ dir = path.dirname(dir);
669
+ }
670
+ return fromRoot;
671
+ }
672
+
637
673
  /**
638
674
  * Parse frontmatter from a markdown file and check if any source is newer than last_compiled.
639
675
  * Returns { page, staleSource, compiledDate, sourceDate } or null if not stale.
@@ -675,15 +711,19 @@ function checkPageStaleness(cwd, filePath) {
675
711
  .replace(/\s*\([^)]*\)\s*$/, '') // drop trailing "(annotation)"
676
712
  .replace(/:\d+(-\d+)?$/, '') // drop trailing ":N" or ":N-M"
677
713
  .trim();
678
- const sourcePath = path.isAbsolute(filePart) ? filePart : path.join(cwd, filePart);
714
+ // A plain-language source ("Voyage AI documentation") or a web link names
715
+ // no file, so it can never be missing on disk. Only paths are checked.
716
+ if (!looksLikePath(filePart)) continue;
717
+ const sourcePath = resolveSource(cwd, filePath, filePart);
679
718
  try {
680
719
  const stat = fs.statSync(sourcePath);
681
- if (stat.mtime > compiledDate) {
720
+ const changed = stat.mtime > compiledDate ? contentChangedAt(sourcePath, stat.mtime) : null;
721
+ if (changed && changed > compiledDate) {
682
722
  return {
683
723
  page: filePath,
684
724
  staleSource: source,
685
725
  compiledDate: compiledMatch[1],
686
- sourceDate: stat.mtime.toISOString().split('T')[0]
726
+ sourceDate: changed.toISOString().split('T')[0]
687
727
  };
688
728
  }
689
729
  } catch {
@@ -700,6 +740,22 @@ function checkPageStaleness(cwd, filePath) {
700
740
  return null;
701
741
  }
702
742
 
743
+ // A day can already be archived when a late entry lands in its journal.
744
+ // Renaming over the archive would erase it, so the late entry is added to the
745
+ // end instead, unless the archive already holds it word for word.
746
+ function moveIntoArchive(src, dest) {
747
+ if (!fs.existsSync(dest)) {
748
+ fs.renameSync(src, dest);
749
+ return;
750
+ }
751
+ const late = fs.readFileSync(src, 'utf8');
752
+ const kept = fs.readFileSync(dest, 'utf8');
753
+ if (late.trim() && !kept.includes(late.trim())) {
754
+ fs.writeFileSync(dest, `${kept.replace(/\n*$/, '\n')}\n${late}`);
755
+ }
756
+ fs.rmSync(src);
757
+ }
758
+
703
759
  /**
704
760
  * Archive journals older than 30 days
705
761
  */
@@ -734,7 +790,7 @@ function archiveOldJournals(atrisDir, dryRun = false) {
734
790
  }
735
791
  const src = path.join(yearDir, file);
736
792
  const dest = path.join(archiveYearDir, file);
737
- fs.renameSync(src, dest);
793
+ moveIntoArchive(src, dest);
738
794
  }
739
795
  archived++;
740
796
  }