atris 3.34.0 → 3.36.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 (163) hide show
  1. package/AGENTS.md +35 -0
  2. package/FOR_AGENTS.md +5 -3
  3. package/README.md +5 -3
  4. package/atris/GETTING_STARTED.md +1 -1
  5. package/atris/atris.md +3 -0
  6. package/atris/policies/day-loop-voice.md +102 -0
  7. package/atris/policies/outbound-artifact-gate.md +2 -0
  8. package/atris/skills/design/SKILL.md +56 -32
  9. package/atris/skills/endgame/SKILL.md +12 -6
  10. package/atris/skills/engines/SKILL.md +35 -8
  11. package/atris/skills/fable-method/SKILL.md +66 -0
  12. package/atris/skills/improve/SKILL.md +65 -45
  13. package/atris/skills/render-cli/SKILL.md +88 -0
  14. package/atris/skills/youtube/SKILL.md +10 -1
  15. package/atris.md +6 -2
  16. package/ax +617 -31
  17. package/bin/atris.js +719 -266
  18. package/commands/activate.js +194 -88
  19. package/commands/aeo.js +52 -0
  20. package/commands/agents.js +166 -0
  21. package/commands/autoland.js +718 -72
  22. package/commands/autopilot-front.js +20 -2
  23. package/commands/autopilot.js +118 -2
  24. package/commands/avail.js +407 -0
  25. package/commands/bench.js +188 -0
  26. package/commands/brain.js +3 -0
  27. package/commands/brief.js +651 -0
  28. package/commands/business-sync.js +192 -6
  29. package/commands/business.js +91 -8
  30. package/commands/clean.js +50 -24
  31. package/commands/close.js +1083 -0
  32. package/commands/cloud.js +245 -0
  33. package/commands/codex-goal.js +26 -2
  34. package/commands/compile.js +292 -1
  35. package/commands/computer.js +150 -3
  36. package/commands/dream.js +365 -0
  37. package/commands/drill.js +371 -0
  38. package/commands/drive.js +187 -0
  39. package/commands/engine.js +1061 -29
  40. package/commands/experiments.js +28 -0
  41. package/commands/feed.js +202 -0
  42. package/commands/feedback.js +34 -12
  43. package/commands/fleet-report.js +206 -0
  44. package/commands/github.js +38 -0
  45. package/commands/gm.js +285 -3
  46. package/commands/goal.js +247 -0
  47. package/commands/improve.js +642 -26
  48. package/commands/init.js +83 -43
  49. package/commands/integrations.js +39 -11
  50. package/commands/interview.js +209 -0
  51. package/commands/land.js +253 -52
  52. package/commands/lesson.js +112 -1
  53. package/commands/lifecycle.js +39 -3
  54. package/commands/linear.js +38 -0
  55. package/commands/log.js +84 -1
  56. package/commands/loops.js +220 -16
  57. package/commands/meet.js +220 -0
  58. package/commands/member.js +899 -66
  59. package/commands/mission.js +3512 -332
  60. package/commands/next.js +137 -0
  61. package/commands/now.js +240 -21
  62. package/commands/one-lap.js +776 -0
  63. package/commands/orb.js +314 -0
  64. package/commands/pack-craft.js +179 -0
  65. package/commands/pack.js +823 -0
  66. package/commands/play.js +3 -2
  67. package/commands/probe.js +30 -3
  68. package/commands/pulse.js +241 -46
  69. package/commands/push.js +260 -82
  70. package/commands/radar.js +259 -14
  71. package/commands/rainmaker.js +49 -0
  72. package/commands/report.js +415 -0
  73. package/commands/scout.js +147 -0
  74. package/commands/search.js +363 -0
  75. package/commands/serve.js +54 -0
  76. package/commands/skill.js +47 -3
  77. package/commands/slop.js +50 -2
  78. package/commands/soul.js +1 -1
  79. package/commands/status.js +50 -5
  80. package/commands/stream.js +861 -0
  81. package/commands/stripe.js +38 -0
  82. package/commands/study.js +693 -0
  83. package/commands/supabase.js +39 -0
  84. package/commands/sync.js +67 -54
  85. package/commands/task.js +2275 -182
  86. package/commands/team.js +73 -0
  87. package/commands/truth.js +29 -3
  88. package/commands/unknowns.js +627 -0
  89. package/commands/update.js +44 -0
  90. package/commands/vercel.js +38 -0
  91. package/commands/verify.js +96 -0
  92. package/commands/watch.js +303 -0
  93. package/commands/wish.js +500 -0
  94. package/commands/workflow.js +11 -5
  95. package/commands/worktree.js +299 -20
  96. package/commands/write.js +399 -0
  97. package/commands/xp.js +29 -11
  98. package/lib/auto-accept-certified.js +391 -43
  99. package/lib/autoland.js +353 -52
  100. package/lib/ax-auto-lane.js +79 -0
  101. package/lib/bench/context.js +147 -0
  102. package/lib/bench/engines.js +141 -0
  103. package/lib/bench/report.js +140 -0
  104. package/lib/bench/runner.js +512 -0
  105. package/lib/brief-ledger.js +350 -0
  106. package/lib/cloud-mission.js +259 -0
  107. package/lib/codex-flight.js +154 -0
  108. package/lib/default-runner.js +45 -0
  109. package/lib/default-verifier.js +70 -0
  110. package/lib/engine-registry.js +232 -0
  111. package/lib/experiments/daily.js +640 -0
  112. package/lib/fleet.js +2431 -38
  113. package/lib/improve-vitals-html.js +171 -0
  114. package/lib/known-commands.js +58 -0
  115. package/lib/loop-doctor.js +416 -0
  116. package/lib/member-switches.js +144 -0
  117. package/lib/memory-view.js +14 -5
  118. package/lib/mission-room.js +1 -0
  119. package/lib/mission-root.js +52 -0
  120. package/lib/mission-runtime-loop.js +7 -0
  121. package/lib/next-moves.js +327 -10
  122. package/lib/official-cli-integration.js +174 -0
  123. package/lib/one-lap-validator.js +60 -0
  124. package/lib/orb-context.js +477 -0
  125. package/lib/orb-scorecard.js +224 -0
  126. package/lib/outbound-send-gate.js +165 -0
  127. package/lib/permission-grants.js +293 -0
  128. package/lib/policy-lessons.js +52 -1
  129. package/lib/pulse.js +277 -3
  130. package/lib/receipt-block.js +168 -0
  131. package/lib/receipt-evidence.js +65 -4
  132. package/lib/review-integrity.js +147 -0
  133. package/lib/router-brain.js +352 -0
  134. package/lib/runner-command.js +33 -0
  135. package/lib/self-drive.js +258 -0
  136. package/lib/short-name.js +103 -0
  137. package/lib/spawn-env.js +18 -0
  138. package/lib/state-detection.js +56 -1
  139. package/lib/sync-status.js +59 -0
  140. package/lib/task-db.js +319 -27
  141. package/lib/task-proof.js +43 -1
  142. package/lib/task-receipt.js +93 -0
  143. package/lib/team-presence.js +260 -0
  144. package/lib/tool-result-encode.js +7 -0
  145. package/lib/trust-tiers.js +90 -0
  146. package/lib/usage.js +107 -0
  147. package/lib/voice-gate.js +163 -0
  148. package/lib/wish-audit.js +1368 -0
  149. package/lib/wish-delegate.js +1840 -0
  150. package/lib/wish-design.js +110 -0
  151. package/lib/wish-stats.js +183 -0
  152. package/lib/wish-store.js +354 -0
  153. package/lib/zip.js +221 -0
  154. package/package.json +3 -1
  155. package/templates/loops/atris/loops/LOOPS.md +55 -0
  156. package/templates/loops/atris/loops/TICK.md +24 -0
  157. package/templates/loops/atris/loops/feedback.md +22 -0
  158. package/templates/loops/atris/loops/quality.md +22 -0
  159. package/templates/loops/atris/wiki/systems/loops.md +41 -0
  160. package/utils/api.js +5 -1
  161. package/utils/auth.js +57 -21
  162. package/utils/update-check.js +27 -6
  163. package/atris/learnings.jsonl +0 -1
@@ -3,6 +3,13 @@ const path = require('path');
3
3
  const { loadCredentials } = require('../utils/auth');
4
4
  const { apiRequestJson } = require('../utils/api');
5
5
  const { loadBusinesses, saveBusinesses, businessMatchesSlug } = require('./business');
6
+ const {
7
+ isLocalMember,
8
+ memberArgFromArgv,
9
+ readLoopFlag,
10
+ setMemberAwake,
11
+ setMemberLoopAwake,
12
+ } = require('../lib/member-switches');
6
13
 
7
14
  /**
8
15
  * Resolve business slug from CLI arg or local .atris/business.json.
@@ -23,6 +30,29 @@ function resolveSlug() {
23
30
  return slug;
24
31
  }
25
32
 
33
+ /**
34
+ * Per-member / per-loop switch path for atris sleep|wake <member> [--loop <id>].
35
+ * Returns true when handled so callers skip business-level lifecycle.
36
+ */
37
+ function handleMemberSwitch(awake) {
38
+ const name = memberArgFromArgv(process.argv);
39
+ if (!name || name === '--help' || name === '-h' || name === 'help') return false;
40
+ if (!isLocalMember(name)) return false;
41
+
42
+ const loopId = readLoopFlag(process.argv);
43
+ if (loopId) {
44
+ setMemberLoopAwake(name, loopId, awake);
45
+ console.log(awake
46
+ ? `${name} loop ${loopId} is awake`
47
+ : `${name} loop ${loopId} is asleep`);
48
+ return true;
49
+ }
50
+
51
+ setMemberAwake(name, awake);
52
+ console.log(awake ? `${name} is awake` : `${name} is asleep`);
53
+ return true;
54
+ }
55
+
26
56
  async function resolveBusiness(token, slug) {
27
57
  const businesses = loadBusinesses();
28
58
 
@@ -91,12 +121,15 @@ async function waitForBusinessComputer(token, business, maxWait = 90000) {
91
121
  * @returns {Promise<void>}
92
122
  */
93
123
  async function sleepAtris() {
124
+ if (handleMemberSwitch(false)) return;
125
+
94
126
  const slug = resolveSlug();
95
127
 
96
128
  if (!slug || slug === '--help' || slug === '-h' || slug === 'help') {
97
- console.log('Usage: atris sleep [business]');
129
+ console.log('Usage: atris sleep [business|member] [--loop <loop-id>]');
98
130
  console.log('');
99
131
  console.log(' Pause a workspace to save compute. Storage only.');
132
+ console.log(' Or flip a local member / member-loop switch asleep.');
100
133
  process.exit(0);
101
134
  }
102
135
 
@@ -131,7 +164,7 @@ async function sleepAtris() {
131
164
  }
132
165
 
133
166
  console.log(`Workspace '${slug}' is now sleeping. Context saved. Wake it with: atris wake ${slug}`);
134
- console.log('Compute paused. Storage only — pennies/day.');
167
+ console.log('Compute paused. Storage only - pennies/day.');
135
168
  }
136
169
 
137
170
  /**
@@ -139,12 +172,15 @@ async function sleepAtris() {
139
172
  * @returns {Promise<void>}
140
173
  */
141
174
  async function wakeAtris() {
175
+ if (handleMemberSwitch(true)) return;
176
+
142
177
  const slug = resolveSlug();
143
178
 
144
179
  if (!slug || slug === '--help' || slug === '-h' || slug === 'help') {
145
- console.log('Usage: atris wake [business]');
180
+ console.log('Usage: atris wake [business|member] [--loop <loop-id>]');
146
181
  console.log('');
147
182
  console.log(' Wake a sleeping workspace. Agents resume automatically.');
183
+ console.log(' Or flip a local member / member-loop switch awake.');
148
184
  process.exit(0);
149
185
  }
150
186
 
@@ -0,0 +1,38 @@
1
+ const { createOfficialCliCommand } = require('../lib/official-cli-integration');
2
+
3
+ const linearCommand = createOfficialCliCommand({
4
+ name: 'linear',
5
+ binary: 'linear',
6
+ versionArgs: ['--version'],
7
+ authArgs: ['auth', 'status'],
8
+ installHint: 'install the linear cli and ensure `linear` is on PATH',
9
+ loginHint: 'linear auth login',
10
+ commands: [
11
+ {
12
+ usage: 'issue list',
13
+ match: ['issue', 'list'],
14
+ forward: ['issue', 'list'],
15
+ description: 'list issues',
16
+ },
17
+ {
18
+ usage: 'issue create',
19
+ match: ['issue', 'create'],
20
+ forward: ['issue', 'create'],
21
+ description: 'create an issue',
22
+ },
23
+ {
24
+ usage: 'issue view',
25
+ match: ['issue', 'view'],
26
+ forward: ['issue', 'view'],
27
+ description: 'show issue details',
28
+ },
29
+ {
30
+ usage: 'issue update',
31
+ match: ['issue', 'update'],
32
+ forward: ['issue', 'update'],
33
+ description: 'update an issue',
34
+ },
35
+ ],
36
+ });
37
+
38
+ module.exports = { linearCommand };
package/commands/log.js CHANGED
@@ -52,4 +52,87 @@ function logAtris() {
52
52
  });
53
53
  }
54
54
 
55
- module.exports = { logAtris };
55
+ function localDateString(now = new Date()) {
56
+ const year = String(now.getFullYear());
57
+ const month = String(now.getMonth() + 1).padStart(2, '0');
58
+ const day = String(now.getDate()).padStart(2, '0');
59
+ return `${year}-${month}-${day}`;
60
+ }
61
+
62
+ function digestDate(args) {
63
+ const equalsArg = args.find((arg) => String(arg).startsWith('--date='));
64
+ const dateIndex = args.indexOf('--date');
65
+ const date = equalsArg
66
+ ? equalsArg.slice('--date='.length)
67
+ : dateIndex >= 0
68
+ ? args[dateIndex + 1]
69
+ : localDateString();
70
+
71
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(String(date || ''))) {
72
+ throw new Error('Usage: atris logs [today] [--date YYYY-MM-DD] [--json]');
73
+ }
74
+ return date;
75
+ }
76
+
77
+ function readDigestSection(root, title, relativePath, maxLines = null) {
78
+ const absolutePath = path.join(root, relativePath);
79
+ if (!fs.existsSync(absolutePath) || !fs.statSync(absolutePath).isFile()) return null;
80
+
81
+ const content = fs.readFileSync(absolutePath, 'utf8');
82
+ const lines = content.split(/\r?\n/);
83
+ if (lines[lines.length - 1] === '') lines.pop();
84
+
85
+ if (maxLines !== null && lines.length > maxLines) {
86
+ return {
87
+ title,
88
+ path: relativePath.split(path.sep).join('/'),
89
+ lines: [...lines.slice(0, maxLines), `truncated after ${maxLines} lines`],
90
+ };
91
+ }
92
+
93
+ return {
94
+ title,
95
+ path: relativePath.split(path.sep).join('/'),
96
+ lines,
97
+ };
98
+ }
99
+
100
+ function logsDigest(args = [], options = {}) {
101
+ const root = options.cwd || process.cwd();
102
+ const date = digestDate(args);
103
+ const sections = [];
104
+ const workspacePath = path.join('atris', 'logs', date.slice(0, 4), `${date}.md`);
105
+ const workspaceSection = readDigestSection(root, 'Workspace journal', workspacePath);
106
+ if (workspaceSection) sections.push(workspaceSection);
107
+
108
+ const teamRoot = path.join(root, 'atris', 'team');
109
+ if (fs.existsSync(teamRoot) && fs.statSync(teamRoot).isDirectory()) {
110
+ const members = fs.readdirSync(teamRoot, { withFileTypes: true })
111
+ .filter((entry) => entry.isDirectory())
112
+ .map((entry) => entry.name)
113
+ .sort((left, right) => left.localeCompare(right));
114
+
115
+ for (const member of members) {
116
+ const relativePath = path.join('atris', 'team', member, 'logs', `${date}.md`);
117
+ const section = readDigestSection(root, `Team: ${member}`, relativePath, 80);
118
+ if (section) sections.push(section);
119
+ }
120
+ }
121
+
122
+ const payload = { date, sections };
123
+ if (args.includes('--json')) {
124
+ console.log(JSON.stringify(payload, null, 2));
125
+ } else if (sections.length === 0) {
126
+ console.log(`No logs for ${date} yet.`);
127
+ } else {
128
+ console.log(sections.map((section) => [
129
+ `=== ${section.title} ===`,
130
+ section.path,
131
+ ...section.lines,
132
+ ].join('\n')).join('\n\n'));
133
+ }
134
+
135
+ return payload;
136
+ }
137
+
138
+ module.exports = { logAtris, logsDigest };
package/commands/loops.js CHANGED
@@ -1,8 +1,11 @@
1
1
  /**
2
- * atris loops - one view of every background loop: what runs, what died,
3
- * what you can start or stop.
2
+ * atris loops - project loop audit/scaffold plus legacy background loop board.
4
3
  *
5
- * atris loops - the board (registry jobs, launchd agents, missions)
4
+ * atris loops - audit atris/loops in this project
5
+ * atris loops init - scaffold the loop system into this project
6
+ * atris self-improve - alias for atris loops init
7
+ * atris loops tick - print the loop tick protocol
8
+ * atris loops board - legacy board: registry jobs, launchd agents, mission
6
9
  * atris loops stop <id> - disable a registry job or boot out a launchd agent
7
10
  * atris loops start <id> - enable a registry job or kickstart a launchd agent
8
11
  */
@@ -12,6 +15,16 @@ const os = require('os');
12
15
  const path = require('path');
13
16
  const { execSync, spawnSync } = require('child_process');
14
17
 
18
+ const MAX_LOG_AGE_DAYS = 14;
19
+ const TEMPLATE_ROOT = path.join(__dirname, '..', 'templates', 'loops');
20
+ const SCAFFOLD_FILES = [
21
+ 'atris/loops/LOOPS.md',
22
+ 'atris/loops/TICK.md',
23
+ 'atris/loops/feedback.md',
24
+ 'atris/loops/quality.md',
25
+ 'atris/wiki/systems/loops.md',
26
+ ];
27
+
15
28
  function heartbeatDir() {
16
29
  return process.env.ATRIS_HEARTBEAT_DIR || path.join(os.homedir(), '.atris', 'heartbeat');
17
30
  }
@@ -127,6 +140,7 @@ function showBoard(cwd) {
127
140
  console.log('');
128
141
  console.log('control: atris loops stop <id> · atris loops start <id>');
129
142
  console.log('ids: registry job id, or a com.atris.* launchd label');
143
+ return 0;
130
144
  }
131
145
 
132
146
  function saveRegistry(registry) {
@@ -178,35 +192,225 @@ function controlLoop(id, action) {
178
192
  process.exit(1);
179
193
  }
180
194
  console.log(`${action === 'stop' ? 'Stopped' : 'Started'} ${target}.`);
181
- return;
195
+ return 0;
182
196
  }
183
197
  const ok = setRegistryJob(target, action === 'start');
184
198
  if (!ok) {
185
- console.error(`No registry job named "${target}". Run: atris loops`);
199
+ console.error(`No registry job named "${target}". Run: atris loops board`);
186
200
  process.exit(1);
187
201
  }
188
202
  console.log(`${action === 'stop' ? 'Disabled' : 'Enabled'} ${target} in the heartbeat registry (backup written).`);
203
+ return 0;
204
+ }
205
+
206
+ function todayIso() {
207
+ return new Date().toISOString().slice(0, 10);
208
+ }
209
+
210
+ function renderTemplate(text) {
211
+ return text.split('{{DATE}}').join(todayIso());
212
+ }
213
+
214
+ function initLoops(cwd = process.cwd()) {
215
+ const created = [];
216
+ const skipped = [];
217
+
218
+ for (const relPath of SCAFFOLD_FILES) {
219
+ const source = path.join(TEMPLATE_ROOT, relPath);
220
+ const target = path.join(cwd, relPath);
221
+ if (fs.existsSync(target)) {
222
+ skipped.push(relPath);
223
+ continue;
224
+ }
225
+ const content = renderTemplate(fs.readFileSync(source, 'utf8'));
226
+ fs.mkdirSync(path.dirname(target), { recursive: true });
227
+ fs.writeFileSync(target, content, 'utf8');
228
+ created.push(relPath);
229
+ }
230
+
231
+ console.log('atris loops init');
232
+ console.log('');
233
+ console.log('created:');
234
+ if (created.length) created.forEach(file => console.log(` ${file}`));
235
+ else console.log(' none');
236
+ console.log('');
237
+ console.log('skipped:');
238
+ if (skipped.length) skipped.forEach(file => console.log(` ${file}`));
239
+ else console.log(' none');
240
+ console.log('');
241
+ console.log('Next: fill Owner/Wiki/Runner TODOs, then run atris loops audit');
242
+ return 0;
243
+ }
244
+
245
+ function stripAnchor(linkTarget) {
246
+ return String(linkTarget || '').split('#')[0];
247
+ }
248
+
249
+ function wikiTargetPath(loopFilePath, wikiLink) {
250
+ const cleaned = stripAnchor(wikiLink);
251
+ if (!cleaned || /^[a-z]+:\/\//i.test(cleaned)) return null;
252
+ return path.resolve(path.dirname(loopFilePath), cleaned);
253
+ }
254
+
255
+ function checkCommandFrom(text) {
256
+ return text.match(/\*\*Check:\*\*\s*`([^`]+)`/)?.[1]?.trim() || null;
257
+ }
258
+
259
+ function runLoopCheck(commandText, cwd) {
260
+ const result = spawnSync(commandText, {
261
+ cwd,
262
+ shell: true,
263
+ encoding: 'utf8',
264
+ timeout: 240000,
265
+ });
266
+ return {
267
+ command: commandText,
268
+ exitCode: typeof result.status === 'number' ? result.status : 1,
269
+ error: result.error ? result.error.message : null,
270
+ };
271
+ }
272
+
273
+ function structuralProblems({ root, filePath, text, nowMs }) {
274
+ const problems = [];
275
+
276
+ const owner = text.match(/\*\*Owner:\*\*\s*`team\/([^`]+)`/)?.[1]?.trim();
277
+ if (!owner) problems.push('no Owner field');
278
+ else if (!fs.existsSync(path.join(root, 'team', owner))) problems.push(`owner team/${owner} does not exist`);
279
+
280
+ const wiki = text.match(/\*\*Wiki:\*\*\s*\[[^\]]+\]\(([^)]+)\)/)?.[1]?.trim();
281
+ if (!wiki) {
282
+ problems.push('no Wiki field');
283
+ } else {
284
+ const wikiPath = wikiTargetPath(filePath, wiki);
285
+ if (!wikiPath || !fs.existsSync(wikiPath)) problems.push(`wiki page ${wiki} missing`);
286
+ }
287
+
288
+ if (!/\*\*Runner:\*\*\s*\S/.test(text)) problems.push('no Runner field');
289
+ if (!/\*\*Protects:\*\*/.test(text)) problems.push('no Protects field');
290
+ if (!/\*\*Signal/.test(text)) problems.push('no Signal field');
291
+ if (!/\*\*Cadence:\*\*/.test(text)) problems.push('no Cadence field');
292
+
293
+ const logDates = [...text.matchAll(/^- (\d{4}-\d{2}-\d{2}):/gm)].map(match => match[1]).sort();
294
+ if (logDates.length === 0) {
295
+ problems.push('no dated Log entries');
296
+ } else {
297
+ const newest = logDates[logDates.length - 1];
298
+ const ageDays = (nowMs - Date.parse(`${newest}T00:00:00Z`)) / 86400000;
299
+ if (ageDays > MAX_LOG_AGE_DAYS) problems.push(`log stale: newest ${newest}`);
300
+ }
301
+
302
+ return { owner, problems };
303
+ }
304
+
305
+ function auditLoops(cwd = process.cwd()) {
306
+ const loopsDir = path.join(cwd, 'atris', 'loops');
307
+ if (!fs.existsSync(loopsDir)) {
308
+ console.log('No atris/loops directory found.');
309
+ console.log('Run: atris loops init');
310
+ console.log('');
311
+ console.log('SELF-IMPROVING: NOT YET — 1 open');
312
+ return 1;
313
+ }
314
+
315
+ const files = fs.readdirSync(loopsDir)
316
+ .filter(file => file.endsWith('.md') && file !== 'LOOPS.md' && file !== 'TICK.md')
317
+ .sort();
318
+
319
+ if (!files.length) {
320
+ console.log('No loop files found in atris/loops.');
321
+ console.log('Run: atris loops init');
322
+ console.log('');
323
+ console.log('SELF-IMPROVING: NOT YET — 1 open');
324
+ return 1;
325
+ }
326
+
327
+ console.log('atris loops audit');
328
+ console.log('');
329
+
330
+ let open = 0;
331
+ const nowMs = Date.now();
332
+ for (const file of files) {
333
+ const filePath = path.join(loopsDir, file);
334
+ const text = fs.readFileSync(filePath, 'utf8');
335
+ const { problems } = structuralProblems({ root: cwd, filePath, text, nowMs });
336
+ const checkCommand = checkCommandFrom(text);
337
+ const check = checkCommand ? runLoopCheck(checkCommand, cwd) : null;
338
+ if (check && check.exitCode !== 0) problems.push(`check failed: exit ${check.exitCode}`);
339
+
340
+ if (problems.length) open += 1;
341
+ const loopName = file.replace(/\.md$/, '');
342
+ console.log(`${problems.length ? '❌' : '✅'} ${loopName}`);
343
+ for (const problem of problems) console.log(` ↳ ${problem}`);
344
+ if (check) {
345
+ const errorSuffix = check.error ? ` (${check.error})` : '';
346
+ console.log(` ↳ check: \`${check.command}\` exit ${check.exitCode}${errorSuffix}`);
347
+ }
348
+ }
349
+
350
+ console.log('');
351
+ if (open === 0) {
352
+ console.log('SELF-IMPROVING: YES');
353
+ return 0;
354
+ }
355
+ console.log(`SELF-IMPROVING: NOT YET — ${open} open`);
356
+ return 1;
357
+ }
358
+
359
+ function tickLoops(cwd = process.cwd()) {
360
+ const localTick = path.join(cwd, 'atris', 'loops', 'TICK.md');
361
+ const packagedTick = path.join(TEMPLATE_ROOT, 'atris', 'loops', 'TICK.md');
362
+ const source = fs.existsSync(localTick) ? localTick : packagedTick;
363
+ console.log(fs.readFileSync(source, 'utf8').trimEnd());
364
+ return 0;
365
+ }
366
+
367
+ function showLoopsHelp() {
368
+ console.log('Usage: atris loops [audit|init|tick|board|start|stop]');
369
+ console.log('');
370
+ console.log('Self-improving loop commands:');
371
+ console.log(' atris loops - audit atris/loops in this project');
372
+ console.log(' atris loops audit - same audit explicitly');
373
+ console.log(' atris loops init - scaffold atris/loops + loop wiki');
374
+ console.log(' atris self-improve - alias for atris loops init');
375
+ console.log(' atris loops tick - print atris/loops/TICK.md protocol');
376
+ console.log('');
377
+ console.log('Background loop board:');
378
+ console.log(' atris loops board - registry jobs, launchd agents, mission');
379
+ console.log(' atris loops stop <id> - disable a registry job or stop a launchd agent');
380
+ console.log(' atris loops start <id> - enable a registry job or start a launchd agent');
381
+ return 0;
189
382
  }
190
383
 
191
384
  function loopsCommand(subcommand, ...args) {
192
385
  switch (subcommand) {
193
386
  case undefined:
387
+ case 'audit':
388
+ return auditLoops(process.cwd());
389
+ case 'init':
390
+ return initLoops(process.cwd());
391
+ case 'tick':
392
+ return tickLoops(process.cwd());
194
393
  case 'board':
195
394
  case 'list':
196
- showBoard(process.cwd());
197
- break;
395
+ return showBoard(process.cwd());
198
396
  case 'stop':
199
- controlLoop(args[0], 'stop');
200
- break;
397
+ return controlLoop(args[0], 'stop');
201
398
  case 'start':
202
- controlLoop(args[0], 'start');
203
- break;
399
+ return controlLoop(args[0], 'start');
400
+ case 'help':
401
+ case '--help':
402
+ case '-h':
403
+ return showLoopsHelp();
204
404
  default:
205
- console.log('Loop commands:');
206
- console.log(' atris loops - board: registry jobs, launchd agents, mission');
207
- console.log(' atris loops stop <id> - disable a registry job or stop a launchd agent');
208
- console.log(' atris loops start <id> - enable a registry job or start a launchd agent');
405
+ return showLoopsHelp();
209
406
  }
210
407
  }
211
408
 
212
- module.exports = { loopsCommand, showBoard };
409
+ module.exports = {
410
+ loopsCommand,
411
+ showBoard,
412
+ initLoops,
413
+ auditLoops,
414
+ tickLoops,
415
+ structuralProblems,
416
+ };