atris 3.15.56 → 3.16.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 (44) hide show
  1. package/AGENTS.md +2 -2
  2. package/GETTING_STARTED.md +1 -1
  3. package/PERSONA.md +4 -4
  4. package/README.md +11 -11
  5. package/atris/skills/copy-editor/SKILL.md +30 -4
  6. package/atris/skills/improve/SKILL.md +18 -20
  7. package/atris/wiki/concepts/agent-activation-contract.md +5 -3
  8. package/atris/wiki/concepts/workspace-initialization-contract.md +4 -4
  9. package/atris/wiki/index.md +1 -0
  10. package/ax +522 -73
  11. package/bin/atris.js +32 -31
  12. package/commands/align.js +0 -14
  13. package/commands/apps.js +102 -1
  14. package/commands/autopilot.js +197 -22
  15. package/commands/brain.js +219 -34
  16. package/commands/brainstorm.js +0 -829
  17. package/commands/computer.js +45 -83
  18. package/commands/improve.js +501 -0
  19. package/commands/integrations.js +228 -0
  20. package/commands/lesson.js +44 -0
  21. package/commands/member.js +4498 -226
  22. package/commands/mission.js +302 -27
  23. package/commands/now.js +89 -1
  24. package/commands/radar.js +181 -56
  25. package/commands/skill.js +37 -6
  26. package/commands/soul.js +0 -4
  27. package/commands/task.js +5582 -517
  28. package/commands/terminal.js +14 -10
  29. package/commands/wiki.js +87 -1
  30. package/commands/workflow.js +288 -73
  31. package/commands/worktree.js +52 -15
  32. package/commands/xp.js +41 -65
  33. package/lib/auto-accept-certified.js +294 -0
  34. package/lib/file-ops.js +0 -184
  35. package/lib/member-alive.js +232 -0
  36. package/lib/policy-lessons.js +280 -0
  37. package/lib/receipt-evidence.js +64 -0
  38. package/lib/state-detection.js +34 -0
  39. package/lib/task-db.js +568 -16
  40. package/lib/task-proof.js +43 -0
  41. package/package.json +1 -1
  42. package/utils/auth.js +13 -4
  43. package/commands/research.js +0 -52
  44. package/lib/section-merge.js +0 -196
package/commands/xp.js CHANGED
@@ -7,6 +7,7 @@ const crypto = require('crypto');
7
7
  const { spawnSync } = require('child_process');
8
8
 
9
9
  const DEFAULT_GRAPH_DAYS = 365;
10
+ const MAX_SYNC_GRAPH_DAYS = 370;
10
11
  const INTENSITY_CHARS = [' ', '.', ':', '*', '#'];
11
12
  const ROW_LABELS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
12
13
  const TASK_EPISODES_FILE = path.join('.atris', 'state', 'task_episodes.jsonl');
@@ -29,7 +30,11 @@ const EARNING_MODEL = {
29
30
  schema: 'atris.agentxp_earning_model.v1',
30
31
  score_name: AGENT_XP_LABEL,
31
32
  primary_public_source: 'accepted_task_receipt',
32
- public_rule: 'No accepted proof-backed task episodes means no AgentXP leaderboard movement.',
33
+ public_rule: 'No human-accepted proof-backed task episodes means no AgentXP leaderboard movement.',
34
+ policies: {
35
+ task_done_with_proof: 'Records proof for review and RL context; emits no Career XP receipt until human accept.',
36
+ task_accept: 'Human accept with proof and positive reward is the Career XP receipt boundary.',
37
+ },
33
38
  weights: [
34
39
  {
35
40
  id: 'accepted_task_receipt',
@@ -210,6 +215,38 @@ function combineAgentXpContributionGraphs(graphs, windowDays = DEFAULT_GRAPH_DAY
210
215
  return graphFromDailyTotals(totals, windowDays);
211
216
  }
212
217
 
218
+ function sanitizeContributionGraphForSync(graph) {
219
+ if (!graph || typeof graph !== 'object') return null;
220
+ const rawDays = Array.isArray(graph.days) ? graph.days : [];
221
+ const days = rawDays
222
+ .slice(-MAX_SYNC_GRAPH_DAYS)
223
+ .map((day) => {
224
+ const date = typeof day?.date === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(day.date)
225
+ ? day.date
226
+ : null;
227
+ if (!date) return null;
228
+ const xp = Math.max(0, asNumber(day.xp ?? day.total_xp));
229
+ return {
230
+ date,
231
+ xp,
232
+ total_xp: Math.max(0, asNumber(day.total_xp, xp)),
233
+ intensity: Math.max(0, Math.min(4, asNumber(day.intensity))),
234
+ };
235
+ })
236
+ .filter(Boolean);
237
+ if (days.length === 0) return null;
238
+ return {
239
+ schema: 'atris.agent_xp_contribution_graph.v1',
240
+ metric_label: AGENT_XP_LABEL,
241
+ window_days: days.length,
242
+ total_xp: days.reduce((sum, day) => sum + day.xp, 0),
243
+ active_days: days.filter(day => day.xp > 0).length,
244
+ first_date: days[0]?.date || null,
245
+ last_date: days[days.length - 1]?.date || null,
246
+ days,
247
+ };
248
+ }
249
+
213
250
  function currentForm(payload) {
214
251
  const arenas = payload.current_form_by_arena || {};
215
252
  const local = arenas.local_workspace || {};
@@ -641,6 +678,7 @@ function buildAtrisActionSignalFromRows(rows = []) {
641
678
  included_in_total_agent_xp: false,
642
679
  public_leaderboard: false,
643
680
  role: 'rl_routing_only',
681
+ note: 'Task done/finish proof can count here as non-public action context; Career XP still requires human accept.',
644
682
  };
645
683
  }
646
684
 
@@ -1348,10 +1386,6 @@ function readTaskProjectionState(workspace) {
1348
1386
  }
1349
1387
  }
1350
1388
 
1351
- function readTaskProjection(workspace) {
1352
- return readTaskProjectionState(workspace).tasks;
1353
- }
1354
-
1355
1389
  function taskRef(task) {
1356
1390
  return task?.display_id || task?.legacy_ref || task?.id || 'task';
1357
1391
  }
@@ -1682,36 +1716,6 @@ function buildCareerXpSessionCapsule(args = []) {
1682
1716
  return capsule;
1683
1717
  }
1684
1718
 
1685
- function normalizeLocalScore(score, workspace) {
1686
- const card = score.profile_card || score.player_card || {};
1687
- const integrity = score.integrity || {};
1688
- const careerXp = asNumber(card.agent_xp ?? card.career_xp);
1689
- const leaderboardEligible = Boolean(
1690
- card.leaderboard_eligible ?? integrity.leaderboard_eligible
1691
- );
1692
-
1693
- return {
1694
- metric_label: AGENT_XP_LABEL,
1695
- agent_xp: careerXp,
1696
- career_xp: careerXp,
1697
- level: levelFromXp(careerXp),
1698
- operator: score.operator || null,
1699
- leaderboard_eligible: leaderboardEligible,
1700
- source: 'local_contribution_score',
1701
- workspace_root: score.workspace_root || workspace,
1702
- current_form_by_arena: {
1703
- local_workspace: {
1704
- ovr: asNumber(card.ovr || card.current_form),
1705
- current_form: asNumber(card.current_form || card.ovr),
1706
- visible_stats: Array.isArray(card.visible_stats) ? card.visible_stats : [],
1707
- leaderboard_eligible: leaderboardEligible,
1708
- integrity_status: score.label || integrity.status || 'unknown',
1709
- },
1710
- },
1711
- contribution_graph: score.contribution_graph || {},
1712
- };
1713
- }
1714
-
1715
1719
  function renderContributionGraph(graph) {
1716
1720
  if (!graph || !Array.isArray(graph.days)) return;
1717
1721
  console.log('');
@@ -1729,36 +1733,6 @@ function renderLocalActivity(activity) {
1729
1733
  );
1730
1734
  }
1731
1735
 
1732
- function loadLocalPayload(args) {
1733
- const workspace = path.resolve(readFlag(args, '--workspace', process.cwd()));
1734
- const operator = readFlag(
1735
- args,
1736
- '--operator',
1737
- process.env.ATRIS_OPERATOR || process.env.USER || os.userInfo().username
1738
- );
1739
- const script = path.join(workspace, 'scripts', 'contribution_score.py');
1740
-
1741
- if (!fs.existsSync(script)) {
1742
- throw new Error(`No local contribution scorer found at ${path.relative(process.cwd(), script)}`);
1743
- }
1744
-
1745
- const result = spawnSync(
1746
- process.env.PYTHON || 'python3',
1747
- [script, '--workspace', workspace, '--operator', operator, '--json'],
1748
- { cwd: workspace, encoding: 'utf8', maxBuffer: 20 * 1024 * 1024 }
1749
- );
1750
-
1751
- if (result.error) {
1752
- throw result.error;
1753
- }
1754
- if (result.status !== 0) {
1755
- const detail = (result.stderr || result.stdout || '').trim();
1756
- throw new Error(detail || `Local scorer exited ${result.status}`);
1757
- }
1758
-
1759
- return normalizeLocalScore(JSON.parse(result.stdout), workspace);
1760
- }
1761
-
1762
1736
  function earnedAgentXp(value) {
1763
1737
  return Math.max(0, asNumber(value));
1764
1738
  }
@@ -1945,6 +1919,7 @@ function buildAgentXpSyncPacket(args = []) {
1945
1919
  const publicXp = earnedAgentXp(totalXp);
1946
1920
  const currentForm = currentFormScore(totalXp);
1947
1921
  const levelProgress = agentXpLevelProgress(publicXp);
1922
+ const contributionGraph = sanitizeContributionGraphForSync(projection.contribution_graph);
1948
1923
  const workspaceRootHash = sha256(workspaces.map(item => item.workspace_root_hash || item.name).sort().join(':'));
1949
1924
  const entry = {
1950
1925
  user_id: player,
@@ -1964,6 +1939,7 @@ function buildAgentXpSyncPacket(args = []) {
1964
1939
  lock_reason: eligible ? null : 'not_enough_trusted_proof',
1965
1940
  public_adjustment: null,
1966
1941
  next_move: eligible ? 'Play the next proof-backed AgentXP mission.' : 'Complete one proof-backed AgentXP rep.',
1942
+ contribution_graph: contributionGraph,
1967
1943
  };
1968
1944
  const packet = {
1969
1945
  schema: 'atris.agentxp_sync_packet.v1',
@@ -0,0 +1,294 @@
1
+ 'use strict';
2
+
3
+ const { spawnSync } = require('child_process');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const { taskProofState } = require('./task-proof');
7
+
8
+ const AGENT_CERTIFICATION_REVIEW_PASSES = 2;
9
+ const AUTO_ACCEPT_HIGH_CONFIDENCE_PASSES = 3;
10
+ const DENIED_TAGS = new Set(['billing', 'deploy', 'feedback', 'voice', 'security', 'customer', 'external']);
11
+
12
+ const SIMPLE_VERIFY_TOKEN_RE = /^[a-zA-Z0-9_./:@=+-]+$/;
13
+ const GIT_WORKTREE_PATH_RE = /^[a-zA-Z0-9_./@=+-]+$/;
14
+ const GIT_REV_TOKEN_RE = /^[a-zA-Z0-9_./@=+~^-]+$/;
15
+
16
+ function hasUnsafePathSegment(token) {
17
+ const text = String(token || '');
18
+ return text.startsWith('/')
19
+ || /^[a-zA-Z]:[\\/]/.test(text)
20
+ || text.split(/[\\/]+/).includes('..');
21
+ }
22
+
23
+ function safeVerifyToken(token) {
24
+ return SIMPLE_VERIFY_TOKEN_RE.test(token) && !hasUnsafePathSegment(token);
25
+ }
26
+
27
+ function safeRelativePathToken(token) {
28
+ const text = String(token || '');
29
+ return Boolean(text)
30
+ && !text.startsWith('-')
31
+ && !text.includes(':')
32
+ && safeVerifyToken(text);
33
+ }
34
+
35
+ function safeGitWorktreePathToken(token) {
36
+ const text = String(token || '');
37
+ return Boolean(text)
38
+ && !text.startsWith('-')
39
+ && !text.includes(':')
40
+ && GIT_WORKTREE_PATH_RE.test(text)
41
+ && !text.split(/[\\/]+/).includes('..');
42
+ }
43
+
44
+ function safeGitRevToken(token) {
45
+ const text = String(token || '');
46
+ return Boolean(text)
47
+ && !text.startsWith('-')
48
+ && !text.includes(':')
49
+ && GIT_REV_TOKEN_RE.test(text)
50
+ && !text.split(/[\\/]+/).includes('..');
51
+ }
52
+
53
+ function safeNodePathArgs(args) {
54
+ return args.every(token => safeRelativePathToken(token));
55
+ }
56
+
57
+ function safeGitDiffCheckArgs(args) {
58
+ return args.length === 0 || (args.length <= 2 && args.every(safeGitRevToken));
59
+ }
60
+
61
+ function isAllowedGitDiffCheck(argv) {
62
+ const [bin, first, second, third, fourth] = argv;
63
+ if (bin !== 'git') return false;
64
+ if (first === 'diff' && second === '--check') {
65
+ return safeGitDiffCheckArgs(argv.slice(3));
66
+ }
67
+ if (first === '-C' && safeGitWorktreePathToken(second) && third === 'diff' && fourth === '--check') {
68
+ return safeGitDiffCheckArgs(argv.slice(5));
69
+ }
70
+ return false;
71
+ }
72
+
73
+ function isInsidePath(candidate, root) {
74
+ const relative = path.relative(root, candidate);
75
+ return relative === '' || (relative && !relative.startsWith('..') && !path.isAbsolute(relative));
76
+ }
77
+
78
+ function validateGitWorktreePath(argv, workspaceRoot) {
79
+ if (!(argv[0] === 'git' && argv[1] === '-C')) return { ok: true };
80
+ const workspace = path.resolve(workspaceRoot || process.cwd());
81
+ const target = path.resolve(workspace, argv[2]);
82
+ const allowedRoot = path.dirname(workspace);
83
+ if (!isInsidePath(target, workspace) && !isInsidePath(target, allowedRoot)) {
84
+ return { ok: false, reason: 'verify_command_not_allowed' };
85
+ }
86
+ if (!fs.existsSync(target)) return { ok: false, reason: 'verify_worktree_missing' };
87
+ return { ok: true };
88
+ }
89
+
90
+ function reviewPassCount(task) {
91
+ const metadata = task.metadata || {};
92
+ const review = task.review || {};
93
+ const count = Number(metadata.agent_review_pass_count ?? review.agent_review_pass_count);
94
+ return Number.isFinite(count) ? count : 0;
95
+ }
96
+
97
+ function isAgentCertified(task) {
98
+ const metadata = task.metadata || {};
99
+ const review = task.review || {};
100
+ const handoff = review.handoff && typeof review.handoff === 'object' ? review.handoff : {};
101
+ return metadata.agent_certified === true
102
+ || review.agent_certified === true
103
+ || handoff.native_goal_status === 'agent_certified'
104
+ || reviewPassCount(task) >= AGENT_CERTIFICATION_REVIEW_PASSES;
105
+ }
106
+
107
+ function latestProof(task) {
108
+ const metadata = task.metadata || {};
109
+ const review = task.review || {};
110
+ return String(metadata.latest_agent_proof || review.proof || '').trim();
111
+ }
112
+
113
+ function proofSentences(proof) {
114
+ return String(proof || '')
115
+ .replace(/([.!?])\s+/g, '$1\n')
116
+ .split(/[\n\r]+/)
117
+ .map((sentence) => sentence.trim())
118
+ .filter(Boolean);
119
+ }
120
+
121
+ function sentenceHasPullRequestReference(sentence) {
122
+ return /\bPR\s*#?\d+\b/i.test(sentence)
123
+ || /\bpull request\s*#?\d+\b/i.test(sentence)
124
+ || /\bgithub\.com\/\S+\/pull\/\d+\b/i.test(sentence)
125
+ || /#\d+\b/.test(sentence);
126
+ }
127
+
128
+ function sentenceHasUnmergedPullRequestBoundary(sentence) {
129
+ return sentenceHasPullRequestReference(sentence)
130
+ && (/\bopen\s+draft\b/i.test(sentence)
131
+ || /\bopen\/draft\b/i.test(sentence)
132
+ || /\bopen\s+and\s+draft\s*=\s*true\b/i.test(sentence)
133
+ || /\bdraft\s*=\s*true\b/i.test(sentence)
134
+ || /\bisDraft\s*[:=]\s*true\b/i.test(sentence)
135
+ || /\bmergedAt\s*[:=]\s*null\b/i.test(sentence)
136
+ || /\bclosed\s+(?:with\s+)?mergedAt\s*[:=]\s*null\b/i.test(sentence));
137
+ }
138
+
139
+ function proofHasUnmergedPullRequestBoundary(proof) {
140
+ return proofSentences(proof).some(sentenceHasUnmergedPullRequestBoundary);
141
+ }
142
+
143
+ function unmergedPullRequestBoundaryResult(ref, proof) {
144
+ return {
145
+ eligible: false,
146
+ ref,
147
+ reason: 'proof_unmerged_or_draft_pr_boundary',
148
+ next_action: 'revise the task out of Review or narrow the proof to draft/local-proof only before auto-accept',
149
+ proof,
150
+ };
151
+ }
152
+
153
+ function distinctReviewActors(task) {
154
+ const actors = new Set();
155
+ for (const event of task.events || []) {
156
+ if (!['proof_ready', 'reviewed'].includes(event.event_type)) continue;
157
+ const actor = event.actor || event.payload?.actor;
158
+ if (actor) actors.add(String(actor));
159
+ }
160
+ return actors;
161
+ }
162
+
163
+ function parseVerifyCommand(verify) {
164
+ const cmd = String(verify || '').trim();
165
+ if (!cmd) return { ok: false, reason: 'no_verify_command' };
166
+ if (/[;&|`$<>\n\r]/.test(cmd)) return { ok: false, reason: 'verify_command_not_allowed' };
167
+ const argv = cmd.split(/\s+/).filter(Boolean);
168
+ if (!argv.length || argv.some((token, index) => {
169
+ if (argv[0] === 'git' && argv[1] === '-C' && index === 2) {
170
+ return !safeGitWorktreePathToken(token);
171
+ }
172
+ if (argv[0] === 'git' && ['diff', '-C'].includes(argv[1]) && index >= (argv[1] === '-C' ? 5 : 3)) {
173
+ return !safeGitRevToken(token);
174
+ }
175
+ return !safeVerifyToken(token);
176
+ })) {
177
+ return { ok: false, reason: 'verify_command_not_allowed' };
178
+ }
179
+ const [bin, first, second] = argv;
180
+ const allowed = (bin === 'npm' && (
181
+ (first === 'test' && argv.length === 2)
182
+ || (first === 'run' && Boolean(second) && !second.startsWith('-') && argv.length === 3)
183
+ ))
184
+ || (bin === 'node' && (
185
+ (first === '--test' && safeNodePathArgs(argv.slice(2)))
186
+ || (first === '--check' && argv.length === 3 && safeRelativePathToken(second))
187
+ || (/^scripts\/[a-zA-Z0-9_./-]+$/.test(first || '') && safeNodePathArgs(argv.slice(1)))
188
+ ))
189
+ || (bin === 'tsc' && argv.length === 1)
190
+ || isAllowedGitDiffCheck(argv);
191
+ if (!allowed) return { ok: false, reason: 'verify_command_not_allowed' };
192
+ return { ok: true, argv };
193
+ }
194
+
195
+ function runVerifyCommand(verify, workspaceRoot) {
196
+ const parsed = parseVerifyCommand(verify);
197
+ if (!parsed.ok) return parsed;
198
+ const gitPathCheck = validateGitWorktreePath(parsed.argv, workspaceRoot);
199
+ if (!gitPathCheck.ok) return gitPathCheck;
200
+ const result = spawnSync(parsed.argv[0], parsed.argv.slice(1), {
201
+ cwd: workspaceRoot,
202
+ shell: false,
203
+ encoding: 'utf8',
204
+ timeout: 120000,
205
+ });
206
+ return {
207
+ ok: result.status === 0,
208
+ reason: result.status === 0 ? 'verify_passed' : 'verify_failed',
209
+ status: result.status,
210
+ stderr: String(result.stderr || '').slice(0, 400),
211
+ };
212
+ }
213
+
214
+ function strictVerifyMissingResult(ref) {
215
+ return {
216
+ eligible: false,
217
+ ref,
218
+ reason: 'strict_verify_missing',
219
+ next_action: 'rerun the review verifier and record a safe metadata.verify command before strict auto-accept',
220
+ review_chat_command: `atris task review-chat ${ref} --as codex-review`,
221
+ };
222
+ }
223
+
224
+ function evaluateAutoAccept(task, options = {}) {
225
+ const { strictVerify = false, minPasses = AGENT_CERTIFICATION_REVIEW_PASSES } = options;
226
+ const ref = task.display_id || task.legacy_ref || task.id;
227
+ if (task.status !== 'review') return { eligible: false, ref, reason: 'not_in_review' };
228
+ const metadata = task.metadata || {};
229
+ const review = task.review || {};
230
+ const approval = String(review.approval_status || metadata.approval_status || 'pending').toLowerCase();
231
+ if (approval && approval !== 'pending' && approval !== 'agent_certified') {
232
+ return { eligible: false, ref, reason: `approval_${approval}` };
233
+ }
234
+ if (metadata.auto_accepted_at) return { eligible: false, ref, reason: 'already_auto_accepted' };
235
+
236
+ const tag = String(task.tag || '').toLowerCase();
237
+ if (DENIED_TAGS.has(tag)) return { eligible: false, ref, reason: `denied_tag_${tag}` };
238
+
239
+ if (!isAgentCertified(task)) return { eligible: false, ref, reason: 'not_agent_certified' };
240
+
241
+ const passes = reviewPassCount(task);
242
+ if (passes < minPasses) return { eligible: false, ref, reason: 'insufficient_review_passes', passes };
243
+
244
+ const proof = latestProof(task);
245
+ if (proofHasUnmergedPullRequestBoundary(proof)) {
246
+ return unmergedPullRequestBoundaryResult(ref, proof);
247
+ }
248
+ const proofCheck = taskProofState(proof);
249
+ if (!proofCheck.ok) return { eligible: false, ref, reason: proofCheck.reason, proof };
250
+
251
+ const actors = distinctReviewActors(task);
252
+ const multiActor = actors.size >= 2;
253
+ const highConfidence = passes >= AUTO_ACCEPT_HIGH_CONFIDENCE_PASSES;
254
+ if (!multiActor && !highConfidence) {
255
+ return {
256
+ eligible: false,
257
+ ref,
258
+ reason: 'needs_second_reviewer_or_third_pass',
259
+ passes,
260
+ actors: [...actors],
261
+ };
262
+ }
263
+
264
+ if (strictVerify) {
265
+ const verify = metadata.verify;
266
+ if (!verify) return strictVerifyMissingResult(ref);
267
+ const workspaceRoot = task.workspace_root || process.cwd();
268
+ const verifyResult = runVerifyCommand(verify, workspaceRoot);
269
+ if (!verifyResult.ok) {
270
+ return { eligible: false, ref, reason: verifyResult.reason, verify, ...verifyResult };
271
+ }
272
+ }
273
+
274
+ return {
275
+ eligible: true,
276
+ ref,
277
+ reason: strictVerify
278
+ ? 'certified_strict_verify'
279
+ : (highConfidence ? 'certified_high_confidence' : 'certified_multi_actor'),
280
+ passes,
281
+ actors: [...actors],
282
+ proof,
283
+ policy: strictVerify ? 'strict_verify' : (highConfidence ? '3_passes' : '2_actors_2_passes'),
284
+ };
285
+ }
286
+
287
+ module.exports = {
288
+ AGENT_CERTIFICATION_REVIEW_PASSES,
289
+ AUTO_ACCEPT_HIGH_CONFIDENCE_PASSES,
290
+ DENIED_TAGS,
291
+ evaluateAutoAccept,
292
+ parseVerifyCommand,
293
+ runVerifyCommand,
294
+ };
package/lib/file-ops.js CHANGED
@@ -87,13 +87,6 @@ function createLogFile(logFile, dateFormatted) {
87
87
  fs.writeFileSync(logFile, initialContent);
88
88
  }
89
89
 
90
- function getTimeLabel() {
91
- const now = new Date();
92
- const hours = String(now.getHours()).padStart(2, '0');
93
- const minutes = String(now.getMinutes()).padStart(2, '0');
94
- return `${hours}:${minutes}`;
95
- }
96
-
97
90
  // Inbox operations
98
91
  function parseInboxItems(content) {
99
92
  const match = content.match(/## Inbox\n([\s\S]*?)(?=\n##|\n---|$)/);
@@ -139,11 +132,6 @@ function addInboxItemToContent(content, id, summary) {
139
132
  return replaceInboxSection(content, updatedItems);
140
133
  }
141
134
 
142
- function removeInboxItemFromContent(content, id) {
143
- const items = parseInboxItems(content).filter((item) => item.id !== id);
144
- return replaceInboxSection(content, items);
145
- }
146
-
147
135
  function getNextInboxId(content) {
148
136
  const items = parseInboxItems(content);
149
137
  if (items.length === 0) return 1;
@@ -158,185 +146,13 @@ function addInboxIdea(logFile, summary) {
158
146
  return nextId;
159
147
  }
160
148
 
161
- // Completion operations
162
- function parseCompletionItems(content) {
163
- const match = content.match(/## Completed ✅\n([\s\S]*?)(?=\n##|\n---|$)/);
164
- if (!match) {
165
- return [];
166
- }
167
- const body = match[1];
168
- const lines = body.split('\n');
169
- const items = [];
170
- lines.forEach((line) => {
171
- const trimmed = line.trim();
172
- if (!trimmed) return;
173
- if (trimmed.startsWith('(Empty')) return;
174
- const parsed = trimmed.match(/^- \*\*C(\d+):\*\*\s*(.+)$|^- \*\*C(\d+):\s+(.+)$/);
175
- if (parsed) {
176
- const id = parseInt(parsed[1] || parsed[3], 10);
177
- const text = parsed[2] || parsed[4];
178
- items.push({ id, text, line: trimmed });
179
- }
180
- });
181
- return items;
182
- }
183
-
184
- function replaceCompletedSection(content, items) {
185
- const regex = /(## Completed ✅\n)([\s\S]*?)(\n---|\n##|$)/;
186
- if (!regex.test(content)) {
187
- const lines = items.length ? items.map((item) => item.line).join('\n') : '';
188
- return `${content}\n\n## Completed ✅\n\n${lines}\n`;
189
- }
190
-
191
- return content.replace(regex, (match, header, body, suffix) => {
192
- const inner = items.length
193
- ? `\n${items.map((item) => item.line).join('\n')}\n`
194
- : '\n';
195
- return `${header}${inner}${suffix}`;
196
- });
197
- }
198
-
199
- function addCompletionItemToContent(content, id, summary) {
200
- const items = parseCompletionItems(content).filter((item) => item.id !== id);
201
- const newItem = { id, text: summary, line: `- **C${id}:** ${summary}` };
202
- const updatedItems = [...items, newItem];
203
- return replaceCompletedSection(content, updatedItems);
204
- }
205
-
206
- function getNextCompletionId(content) {
207
- const items = parseCompletionItems(content);
208
- if (items.length === 0) return 1;
209
- return items.reduce((max, item) => (item.id > max ? item.id : max), 0) + 1;
210
- }
211
-
212
- // Notes operations
213
- function insertIntoNotesSection(content, block) {
214
- const regex = /(## Notes\n)([\s\S]*?)(\n---|\n##|$)/;
215
- const match = content.match(regex);
216
- if (!match) {
217
- return `${content}\n\n## Notes\n\n${block}\n`;
218
- }
219
- const header = match[1];
220
- const body = match[2];
221
- const suffix = match[3];
222
- const trimmedBody = body.replace(/\s*$/, '');
223
- const newBody = trimmedBody
224
- ? `${trimmedBody}\n\n${block}\n`
225
- : `\n${block}\n`;
226
- return content.replace(regex, `${header}${newBody}${suffix}`);
227
- }
228
-
229
- function recordBrainstormSession(
230
- logFile,
231
- sourceLabel,
232
- topic,
233
- desiredOutcome,
234
- keyQuestions,
235
- focusAreas,
236
- constraints,
237
- references,
238
- tonePreference,
239
- nextSteps,
240
- sessionSummary
241
- ) {
242
- let content = fs.readFileSync(logFile, 'utf8');
243
- const lines = [
244
- `### Brainstorm Session — ${getTimeLabel()}`,
245
- `**Source:** ${sourceLabel}`,
246
- `**Topic:** ${topic}`,
247
- ];
248
- if (desiredOutcome) {
249
- lines.push(`**User Story / Desired Outcome:** ${desiredOutcome}`);
250
- }
251
- if (tonePreference) {
252
- lines.push(`**Vibe / Feelings:** ${tonePreference}`);
253
- }
254
- if (keyQuestions && keyQuestions.length > 0) {
255
- lines.push('**Key Questions:**');
256
- keyQuestions.forEach((item) => lines.push(`- ${item}`));
257
- }
258
- if (focusAreas && focusAreas.length > 0) {
259
- lines.push('**Focus Areas:**');
260
- focusAreas.forEach((item) => lines.push(`- ${item}`));
261
- }
262
- if (constraints) {
263
- lines.push(`**Constraints:** ${constraints}`);
264
- }
265
- if (references) {
266
- lines.push(`**Context / References:** ${references}`);
267
- }
268
- if (sessionSummary) {
269
- lines.push(`**Session Summary:** ${sessionSummary}`);
270
- }
271
- if (nextSteps && nextSteps.length > 0) {
272
- lines.push('**Next Steps:**');
273
- nextSteps.forEach((item) => lines.push(`- ${item}`));
274
- }
275
-
276
- const block = lines.join('\n');
277
- content = insertIntoNotesSection(content, block);
278
- fs.writeFileSync(logFile, content);
279
- }
280
-
281
- function recordAutopilotVision(logFile, sourceLabel, summary, successCriteria, riskNotes) {
282
- const content = fs.readFileSync(logFile, 'utf8');
283
- const lines = [
284
- `### Autopilot Vision — ${getTimeLabel()}`,
285
- `**Source:** ${sourceLabel}`,
286
- `**Summary:** ${summary}`,
287
- '**Success Criteria:**',
288
- ...successCriteria.map((item) => `- ${item}`),
289
- ];
290
- if (riskNotes && riskNotes.trim()) {
291
- lines.push(`**Risks / Notes:** ${riskNotes}`);
292
- }
293
- const block = lines.join('\n');
294
- const updated = insertIntoNotesSection(content, block);
295
- fs.writeFileSync(logFile, updated);
296
- }
297
-
298
- function recordAutopilotIteration(logFile, iteration, result, notes) {
299
- const content = fs.readFileSync(logFile, 'utf8');
300
- const lines = [
301
- `### Autopilot Iteration ${iteration} — ${getTimeLabel()}`,
302
- `**Validator Result:** ${result}`,
303
- ];
304
- if (notes && notes.trim()) {
305
- lines.push(`**Notes:** ${notes}`);
306
- }
307
- const block = lines.join('\n');
308
- const updated = insertIntoNotesSection(content, block);
309
- fs.writeFileSync(logFile, updated);
310
- }
311
-
312
- function recordAutopilotSuccess(logFile, inboxId, summary) {
313
- let content = fs.readFileSync(logFile, 'utf8');
314
- if (typeof inboxId === 'number' && !Number.isNaN(inboxId)) {
315
- content = removeInboxItemFromContent(content, inboxId);
316
- }
317
- const nextId = getNextCompletionId(content);
318
- content = addCompletionItemToContent(content, nextId, `Autopilot — ${summary}`);
319
- fs.writeFileSync(logFile, content);
320
- }
321
-
322
149
  module.exports = {
323
150
  getLogPath,
324
151
  ensureLogDirectory,
325
152
  createLogFile,
326
- getTimeLabel,
327
153
  parseInboxItems,
328
154
  replaceInboxSection,
329
155
  addInboxItemToContent,
330
- removeInboxItemFromContent,
331
156
  getNextInboxId,
332
157
  addInboxIdea,
333
- parseCompletionItems,
334
- replaceCompletedSection,
335
- addCompletionItemToContent,
336
- getNextCompletionId,
337
- insertIntoNotesSection,
338
- recordBrainstormSession,
339
- recordAutopilotVision,
340
- recordAutopilotIteration,
341
- recordAutopilotSuccess,
342
158
  };