atris 3.16.0 → 3.17.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.
- package/README.md +33 -7
- package/atris/skills/atris/SKILL.md +15 -2
- package/atris/skills/atris-feedback/SKILL.md +7 -0
- package/atris/skills/design/SKILL.md +29 -2
- package/atris/skills/engines/SKILL.md +44 -0
- package/atris/skills/flow/SKILL.md +1 -1
- package/atris/skills/wake/SKILL.md +37 -0
- package/atris/skills/youtube/SKILL.md +13 -39
- package/atris/team/validator/MEMBER.md +1 -0
- package/atris/wiki/concepts/agent-activation-contract.md +3 -3
- package/atris/wiki/concepts/workspace-initialization-contract.md +3 -3
- package/atris/wiki/index.md +1 -0
- package/atris.md +43 -19
- package/bin/atris.js +446 -43
- package/commands/agent-spawn.js +480 -0
- package/commands/analytics.js +6 -3
- package/commands/apps.js +11 -0
- package/commands/autopilot.js +466 -20
- package/commands/brain.js +74 -7
- package/commands/brainstorm.js +9 -58
- package/commands/clean.js +1 -4
- package/commands/compile.js +574 -0
- package/commands/console.js +8 -3
- package/commands/deck.js +135 -0
- package/commands/init.js +22 -11
- package/commands/lesson.js +76 -0
- package/commands/member.js +252 -48
- package/commands/mission.js +405 -13
- package/commands/now.js +4 -2
- package/commands/probe.js +444 -0
- package/commands/pulse.js +504 -0
- package/commands/radar.js +1 -0
- package/commands/recap.js +233 -0
- package/commands/run.js +615 -22
- package/commands/skill.js +6 -2
- package/commands/slop.js +173 -0
- package/commands/spaceship.js +39 -0
- package/commands/sync.js +0 -2
- package/commands/task.js +458 -43
- package/commands/verify.js +7 -3
- package/lib/activity-stream.js +166 -0
- package/lib/auto-accept-certified.js +23 -1
- package/lib/context-gatherer.js +170 -0
- package/lib/escape-regexp.js +13 -0
- package/lib/file-ops.js +6 -3
- package/lib/journal.js +1 -1
- package/lib/lesson-contradiction.js +113 -0
- package/lib/policy-lessons.js +3 -2
- package/lib/pulse.js +401 -0
- package/lib/runner-command.js +156 -0
- package/lib/slides-deck.js +236 -0
- package/lib/state-detection.js +40 -3
- package/lib/task-db.js +101 -4
- package/lib/task-proof.js +1 -1
- package/lib/todo-fallback.js +2 -1
- package/lib/todo-sections.js +33 -0
- package/package.json +1 -2
- package/utils/api.js +14 -2
- package/atris/atrisDev.md +0 -717
package/commands/member.js
CHANGED
|
@@ -1994,6 +1994,28 @@ function repoRelative(root, filePath) {
|
|
|
1994
1994
|
return path.relative(root, filePath).replace(/\\/g, '/');
|
|
1995
1995
|
}
|
|
1996
1996
|
|
|
1997
|
+
function dateFromLogPath(relativePath) {
|
|
1998
|
+
const match = String(relativePath || '').match(/\b(\d{4}-\d{2}-\d{2})\b/);
|
|
1999
|
+
return match ? match[1] : null;
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
function compareLogEvidenceRecentFirst(a, b) {
|
|
2003
|
+
const dateA = a?.date || '';
|
|
2004
|
+
const dateB = b?.date || '';
|
|
2005
|
+
if (dateA !== dateB) return dateB.localeCompare(dateA);
|
|
2006
|
+
const pathCompare = String(a?.path || '').localeCompare(String(b?.path || ''));
|
|
2007
|
+
if (pathCompare !== 0) return pathCompare;
|
|
2008
|
+
return Number(a?.line || 0) - Number(b?.line || 0);
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
function compareRepeatedFailureSignals(a, b) {
|
|
2012
|
+
const countCompare = Number(b?.count || 0) - Number(a?.count || 0);
|
|
2013
|
+
if (countCompare !== 0) return countCompare;
|
|
2014
|
+
const evidenceCompare = compareLogEvidenceRecentFirst(a?.evidence?.[0], b?.evidence?.[0]);
|
|
2015
|
+
if (evidenceCompare !== 0) return evidenceCompare;
|
|
2016
|
+
return String(a?.pattern || '').localeCompare(String(b?.pattern || ''));
|
|
2017
|
+
}
|
|
2018
|
+
|
|
1997
2019
|
function listFilesBounded(rootDir, { maxFiles = 220, extensions = ['.md', '.txt', '.json', '.jsonl'] } = {}) {
|
|
1998
2020
|
const files = [];
|
|
1999
2021
|
const stack = [rootDir];
|
|
@@ -2034,12 +2056,13 @@ function stripAutoImproverTitleNoise(text) {
|
|
|
2034
2056
|
return compactSentence(clean, 180);
|
|
2035
2057
|
}
|
|
2036
2058
|
|
|
2037
|
-
function isAutoImproverGeneratedLogLine(line) {
|
|
2059
|
+
function isAutoImproverGeneratedLogLine(line, relativePath = '') {
|
|
2038
2060
|
const text = String(line || '').trim();
|
|
2039
2061
|
if (!text) return false;
|
|
2062
|
+
if (String(relativePath || '').startsWith('atris/team/auto-improver/logs/') && /^-\s*summary:\s*/i.test(text)) return true;
|
|
2040
2063
|
if (/\bauto[- ]improver\b/i.test(text) && /\b(dogfood|receipt|prevented|pain_)\b/i.test(text)) return true;
|
|
2041
2064
|
if (/\bauto_improver\b/i.test(text)) return true;
|
|
2042
|
-
if (/^-\s*candidate:\s
|
|
2065
|
+
if (/^-\s*candidate:\s*/i.test(text)) return true;
|
|
2043
2066
|
if (/recurring log pattern:\s*(candidate:|recurring log pattern:)/i.test(text)) return true;
|
|
2044
2067
|
return false;
|
|
2045
2068
|
}
|
|
@@ -2062,12 +2085,15 @@ function collectAutoImproverLogSignals(root) {
|
|
|
2062
2085
|
const failureRegex = /\b(error|failed|failure|blocked|timeout|regression|crash|missing proof|naraka|suffering)\b/i;
|
|
2063
2086
|
const unclearRegex = /\b(tbd|unclear|unknown|needs user|needs owner|needs proof|no next|blocked)\b/i;
|
|
2064
2087
|
const counts = new Map();
|
|
2088
|
+
const unclearActions = [];
|
|
2065
2089
|
let filesScanned = 0;
|
|
2066
2090
|
let linesScanned = 0;
|
|
2067
2091
|
let unclearNextActions = 0;
|
|
2068
2092
|
for (const scanRoot of roots) {
|
|
2069
2093
|
for (const filePath of listFilesBounded(scanRoot)) {
|
|
2070
2094
|
const relative = repoRelative(root, filePath);
|
|
2095
|
+
const logDate = dateFromLogPath(relative);
|
|
2096
|
+
if (relative.startsWith('atris/logs/archive/')) continue;
|
|
2071
2097
|
const isRuntimeLog = relative.startsWith('atris/logs/')
|
|
2072
2098
|
|| /^atris\/team\/[^/]+\/logs\//.test(relative)
|
|
2073
2099
|
|| /^atris\/team\/[^/]+\/goals\.(md|json)$/.test(relative)
|
|
@@ -2076,37 +2102,52 @@ function collectAutoImproverLogSignals(root) {
|
|
|
2076
2102
|
const text = safeReadText(filePath);
|
|
2077
2103
|
if (!text) continue;
|
|
2078
2104
|
filesScanned += 1;
|
|
2079
|
-
const
|
|
2105
|
+
const allLines = text.split(/\r?\n/);
|
|
2106
|
+
const lineOffset = Math.max(0, allLines.length - 500);
|
|
2107
|
+
const lines = allLines.slice(-500);
|
|
2080
2108
|
linesScanned += lines.length;
|
|
2081
2109
|
for (let index = 0; index < lines.length; index += 1) {
|
|
2110
|
+
const sourceLine = lineOffset + index + 1;
|
|
2082
2111
|
const line = lines[index];
|
|
2083
|
-
if (isAutoImproverGeneratedLogLine(line)) continue;
|
|
2112
|
+
if (isAutoImproverGeneratedLogLine(line, relative)) continue;
|
|
2084
2113
|
// Declared verification receipts ("check: <command> ...") describe what
|
|
2085
2114
|
// was verified, not what broke. Wiki upkeep sweeps write one per page,
|
|
2086
2115
|
// so counting them as failures spawns bogus recurring-pattern tasks
|
|
2087
2116
|
// (CLI-199 came from 13 such lines in atris/wiki/log.md).
|
|
2088
2117
|
if (/^\s*[-*]?\s*check:\s/i.test(line)) continue;
|
|
2089
2118
|
if (/\b(errors?|fail(?:ed|ures?)|blocked|timeouts?)\s*:\s*0\b/i.test(line)) continue;
|
|
2090
|
-
if (
|
|
2119
|
+
if (/\bblocked\s*(?:->|→|to)\s*ready\b/i.test(line)) continue;
|
|
2120
|
+
if (unclearRegex.test(line)) {
|
|
2121
|
+
unclearNextActions += 1;
|
|
2122
|
+
unclearActions.push({
|
|
2123
|
+
path: relative,
|
|
2124
|
+
date: logDate,
|
|
2125
|
+
line: sourceLine,
|
|
2126
|
+
text: compactSentence(line, 180),
|
|
2127
|
+
});
|
|
2128
|
+
}
|
|
2091
2129
|
if (!failureRegex.test(line)) continue;
|
|
2092
2130
|
const pattern = normalizeFailurePattern(line);
|
|
2093
2131
|
if (!pattern) continue;
|
|
2094
2132
|
const existing = counts.get(pattern) || { pattern, count: 0, evidence: [] };
|
|
2095
2133
|
existing.count += 1;
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
}
|
|
2134
|
+
existing.evidence.push({
|
|
2135
|
+
path: relative,
|
|
2136
|
+
date: logDate,
|
|
2137
|
+
line: sourceLine,
|
|
2138
|
+
text: compactSentence(line, 180),
|
|
2139
|
+
});
|
|
2103
2140
|
counts.set(pattern, existing);
|
|
2104
2141
|
}
|
|
2105
2142
|
}
|
|
2106
2143
|
}
|
|
2107
2144
|
const repeated = [...counts.values()]
|
|
2108
2145
|
.filter((item) => item.count >= 2)
|
|
2109
|
-
.
|
|
2146
|
+
.map((item) => ({
|
|
2147
|
+
...item,
|
|
2148
|
+
evidence: item.evidence.sort(compareLogEvidenceRecentFirst).slice(0, 5),
|
|
2149
|
+
}))
|
|
2150
|
+
.sort(compareRepeatedFailureSignals)
|
|
2110
2151
|
.slice(0, 8);
|
|
2111
2152
|
return {
|
|
2112
2153
|
files_scanned: filesScanned,
|
|
@@ -2114,9 +2155,19 @@ function collectAutoImproverLogSignals(root) {
|
|
|
2114
2155
|
repeated_failures: repeated,
|
|
2115
2156
|
repeated_failure_count: repeated.length,
|
|
2116
2157
|
unclear_next_action_count: unclearNextActions,
|
|
2158
|
+
unclear_next_actions: unclearActions.sort(compareLogEvidenceRecentFirst).slice(0, 5),
|
|
2117
2159
|
};
|
|
2118
2160
|
}
|
|
2119
2161
|
|
|
2162
|
+
function autoImproverTaskWaitingForHuman(task, status) {
|
|
2163
|
+
if (status !== 'review') return false;
|
|
2164
|
+
const metadata = task?.metadata || {};
|
|
2165
|
+
const review = task?.review || {};
|
|
2166
|
+
const certified = metadata.agent_certified === true || review.agent_certified === true;
|
|
2167
|
+
const approval = lowerCompact(review.approval_status || metadata.approval_status || task?.approval_status || '');
|
|
2168
|
+
return certified && (!approval || approval === 'pending' || approval === 'agent_certified');
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2120
2171
|
function collectAutoImproverTaskSignals(root) {
|
|
2121
2172
|
const projectionPath = path.join(root, '.atris', 'state', 'tasks.projection.json');
|
|
2122
2173
|
const projection = safeReadJson(projectionPath);
|
|
@@ -2135,10 +2186,11 @@ function collectAutoImproverTaskSignals(root) {
|
|
|
2135
2186
|
status: status || null,
|
|
2136
2187
|
owner: task.claimed_by || task.assigned_to || task.owner || task.metadata?.assigned_to || null,
|
|
2137
2188
|
};
|
|
2189
|
+
const waitingForHuman = autoImproverTaskWaitingForHuman(task, status);
|
|
2138
2190
|
if (status === 'blocked') blockedTasks.push(sample);
|
|
2139
2191
|
if (status === 'review') reviewTasks.push(sample);
|
|
2140
|
-
if (openStatuses.has(status)) staleTasks.push(sample);
|
|
2141
|
-
if (/\b(tbd|unclear|unknown|needs proof|needs owner|blocked|stale|no next)\b/i.test(`${title} ${task.notes || ''}`)) {
|
|
2192
|
+
if (openStatuses.has(status) && !waitingForHuman) staleTasks.push(sample);
|
|
2193
|
+
if (openStatuses.has(status) && !waitingForHuman && /\b(tbd|unclear|unknown|needs proof|needs owner|blocked|stale|no next)\b/i.test(`${title} ${task.notes || ''}`)) {
|
|
2142
2194
|
unclearTasks.push(sample);
|
|
2143
2195
|
}
|
|
2144
2196
|
}
|
|
@@ -2366,7 +2418,9 @@ function collectAutoImproverScan(root = process.cwd()) {
|
|
|
2366
2418
|
title: 'Unclear next-action language is accumulating',
|
|
2367
2419
|
problem: 'Several logs or tasks mention blocked/unclear/proof-needed states without a crisp next move.',
|
|
2368
2420
|
recommendation: 'Convert the highest-value unclear item into one task with owner, proof, and stop rule.',
|
|
2369
|
-
evidence: taskSignals.unclear_tasks.length
|
|
2421
|
+
evidence: taskSignals.unclear_tasks.length
|
|
2422
|
+
? taskSignals.unclear_tasks
|
|
2423
|
+
: (logSignals.unclear_next_actions.length ? logSignals.unclear_next_actions : logSignals.repeated_failures.slice(0, 3)),
|
|
2370
2424
|
score: 18,
|
|
2371
2425
|
});
|
|
2372
2426
|
}
|
|
@@ -2416,21 +2470,43 @@ function collectAutoImproverScan(root = process.cwd()) {
|
|
|
2416
2470
|
};
|
|
2417
2471
|
}
|
|
2418
2472
|
|
|
2473
|
+
// Lifecycle filter for wake dedupe: a task that already crossed the review boundary
|
|
2474
|
+
// (done/failed/archived, or human-accepted) must never be re-selected as the wake target —
|
|
2475
|
+
// that loop produced an endless "existing_task_found OBL-1433" no-op spiral (OBL-1469).
|
|
2476
|
+
const AUTO_IMPROVER_INACTIVE_STATUSES = new Set(['done', 'failed', 'archived']);
|
|
2477
|
+
|
|
2478
|
+
function autoImproverTaskIsActionable(task) {
|
|
2479
|
+
const status = String(task?.status || '').toLowerCase();
|
|
2480
|
+
if (AUTO_IMPROVER_INACTIVE_STATUSES.has(status)) return false;
|
|
2481
|
+
const approval = String(task?.metadata?.approval_status || task?.approval_status || '').toLowerCase();
|
|
2482
|
+
if (approval === 'accepted') return false;
|
|
2483
|
+
return true;
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2419
2486
|
function findExistingAutoImproverTask(title) {
|
|
2420
2487
|
const projection = safeReadJson(path.join(process.cwd(), '.atris', 'state', 'tasks.projection.json'));
|
|
2421
2488
|
const tasks = Array.isArray(projection?.tasks) ? projection.tasks : [];
|
|
2422
|
-
const key = lowerCompact(title);
|
|
2489
|
+
const key = lowerCompact(stripAutoImproverTitleNoise(title));
|
|
2423
2490
|
if (!key) return null;
|
|
2424
2491
|
return tasks.find((task) => {
|
|
2425
|
-
|
|
2492
|
+
if (!autoImproverTaskIsActionable(task)) return false;
|
|
2493
|
+
const taskTitle = lowerCompact(stripAutoImproverTitleNoise(task.title || ''));
|
|
2426
2494
|
if (!taskTitle) return false;
|
|
2427
2495
|
return taskTitle.includes(key) || key.includes(taskTitle);
|
|
2428
2496
|
}) || null;
|
|
2429
2497
|
}
|
|
2430
2498
|
|
|
2431
|
-
function
|
|
2499
|
+
function autoImproverTaskTitle(candidate) {
|
|
2432
2500
|
const core = stripAutoImproverTitleNoise(candidate?.title || 'Prevent top dogfood failure');
|
|
2433
|
-
|
|
2501
|
+
return `Auto-improver: ${compactSentence(core, 92)}`;
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2504
|
+
function existingAutoImproverTaskForCandidate(candidate) {
|
|
2505
|
+
return findExistingAutoImproverTask(autoImproverTaskTitle(candidate));
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
function createAutoImproverTask(candidate, receiptPath) {
|
|
2509
|
+
const title = autoImproverTaskTitle(candidate);
|
|
2434
2510
|
const existing = findExistingAutoImproverTask(title);
|
|
2435
2511
|
if (existing) {
|
|
2436
2512
|
return {
|
|
@@ -2496,11 +2572,18 @@ async function runAutoImproverWake(name, paths, { execute = false, confirmed = f
|
|
|
2496
2572
|
const scan = collectAutoImproverScan(process.cwd());
|
|
2497
2573
|
const mode = execute ? 'execute' : 'dry_run';
|
|
2498
2574
|
const candidate = scan.prevented_fire_candidate;
|
|
2499
|
-
|
|
2500
|
-
let
|
|
2501
|
-
|
|
2575
|
+
const existingTask = candidate ? existingAutoImproverTaskForCandidate(candidate) : null;
|
|
2576
|
+
let createdTask = existingTask ? {
|
|
2577
|
+
ok: true,
|
|
2578
|
+
existing: true,
|
|
2579
|
+
task_ref: taskRef(existingTask),
|
|
2580
|
+
task: existingTask,
|
|
2581
|
+
command: `atris task show ${taskRef(existingTask)} --json`,
|
|
2582
|
+
} : null;
|
|
2583
|
+
let decision = candidate ? (existingTask ? 'existing_task_found' : 'scan_found_problem') : 'scan_clean';
|
|
2584
|
+
let reason = candidate ? (existingTask ? 'auto_improver_task_already_exists' : `top_candidate:${candidate.source}`) : 'no_prevented_fire_candidate';
|
|
2502
2585
|
let nextCommand = candidate
|
|
2503
|
-
? `atris member wake ${name} --execute --confirm-autonomy-policy --json`
|
|
2586
|
+
? (existingTask ? createdTask.command : `atris member wake ${name} --execute --confirm-autonomy-policy --json`)
|
|
2504
2587
|
: `atris member wake ${name} --json`;
|
|
2505
2588
|
|
|
2506
2589
|
let payload = {
|
|
@@ -2571,27 +2654,42 @@ async function runAutoImproverWake(name, paths, { execute = false, confirmed = f
|
|
|
2571
2654
|
: 'No bounded prevented-fire task was created yet.',
|
|
2572
2655
|
},
|
|
2573
2656
|
};
|
|
2657
|
+
const previousLatest = safeReadJson(path.join(process.cwd(), '.atris', 'state', 'auto-improver-dogfood-latest.json'));
|
|
2574
2658
|
const finalWrite = writeAutoImproverReceipt(name, payload, plannedReceiptPath);
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2659
|
+
// A no-op scan identical to the previous tick earns a receipt but not a journal entry —
|
|
2660
|
+
// the cadence loop was appending the same "found: N, prevented: 0" row every ~4 minutes
|
|
2661
|
+
// and flooding the daily log (2026-06-10 had 100+ identical entries).
|
|
2662
|
+
const createdNewTask = Boolean(payload.created_task) && payload.created_task.existing === false;
|
|
2663
|
+
const duplicateNoop = Boolean(previousLatest)
|
|
2664
|
+
&& !createdNewTask
|
|
2665
|
+
&& previousLatest.decision === decision
|
|
2666
|
+
&& previousLatest.proof?.found_problems === payload.proof.found_problems
|
|
2667
|
+
&& previousLatest.pain?.before === payload.pain.before
|
|
2668
|
+
&& (previousLatest.created_task?.task_ref || null) === (payload.created_task?.task_ref || null);
|
|
2669
|
+
let logPath = null;
|
|
2670
|
+
let memberLogPath = null;
|
|
2671
|
+
if (!duplicateNoop) {
|
|
2672
|
+
logPath = appendProjectLog('Auto-improver dogfood scan', {
|
|
2673
|
+
member: name,
|
|
2674
|
+
mode,
|
|
2675
|
+
found: payload.proof.found_problems,
|
|
2676
|
+
prevented: payload.proof.prevented_suffering,
|
|
2677
|
+
pain_before: payload.pain.before,
|
|
2678
|
+
pain_after: payload.pain.after,
|
|
2679
|
+
candidate: candidate ? stripAutoImproverTitleNoise(candidate.title) : '',
|
|
2680
|
+
task: payload.created_task?.task_ref || '',
|
|
2681
|
+
receipt: repoRelative(process.cwd(), finalWrite.receiptPath),
|
|
2682
|
+
});
|
|
2683
|
+
memberLogPath = appendMemberGoalLog(paths.memberDir, name, 'Auto-improver dogfood scan', {
|
|
2684
|
+
mode,
|
|
2685
|
+
found: payload.proof.found_problems,
|
|
2686
|
+
prevented: payload.proof.prevented_suffering,
|
|
2687
|
+
pain_before: payload.pain.before,
|
|
2688
|
+
pain_after: payload.pain.after,
|
|
2689
|
+
receipt: repoRelative(process.cwd(), finalWrite.receiptPath),
|
|
2690
|
+
next: nextCommand,
|
|
2691
|
+
});
|
|
2692
|
+
}
|
|
2595
2693
|
|
|
2596
2694
|
return {
|
|
2597
2695
|
ok: true,
|
|
@@ -2610,6 +2708,7 @@ async function runAutoImproverWake(name, paths, { execute = false, confirmed = f
|
|
|
2610
2708
|
latest_path: finalWrite.latestPath,
|
|
2611
2709
|
log_path: logPath,
|
|
2612
2710
|
member_log_path: memberLogPath,
|
|
2711
|
+
journal_skipped: duplicateNoop ? 'duplicate_noop' : null,
|
|
2613
2712
|
created_task: payload.created_task,
|
|
2614
2713
|
};
|
|
2615
2714
|
}
|
|
@@ -2752,14 +2851,17 @@ function appendProjectLog(title, fields = {}) {
|
|
|
2752
2851
|
// --- YAML Frontmatter Parser (shared with skill.js) ---
|
|
2753
2852
|
|
|
2754
2853
|
function parseFrontmatter(content) {
|
|
2755
|
-
|
|
2854
|
+
// \r?\n so a CRLF-line-ending MEMBER.md (Windows-edited) still parses; otherwise
|
|
2855
|
+
// the delimiter never matched (and trailing \r broke the per-line key matchers),
|
|
2856
|
+
// silently dropping the member's entire frontmatter — role, skills, permissions, tools.
|
|
2857
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
2756
2858
|
if (!match) return null;
|
|
2757
2859
|
|
|
2758
2860
|
const yaml = match[1];
|
|
2759
2861
|
const result = {};
|
|
2760
2862
|
let currentKey = null;
|
|
2761
2863
|
|
|
2762
|
-
for (const line of yaml.split(
|
|
2864
|
+
for (const line of yaml.split(/\r?\n/)) {
|
|
2763
2865
|
const listMatch = line.match(/^\s+-\s+(.+)$/);
|
|
2764
2866
|
if (listMatch && currentKey) {
|
|
2765
2867
|
if (!Array.isArray(result[currentKey])) result[currentKey] = [];
|
|
@@ -3592,12 +3694,23 @@ function memberGoalFromMission(name, ...args) {
|
|
|
3592
3694
|
goal.mission_id = runtime.id || goal.mission_id || null;
|
|
3593
3695
|
goal.mission_north_star = purpose.northStar;
|
|
3594
3696
|
goal.history = Array.isArray(goal.history) ? goal.history : [];
|
|
3595
|
-
|
|
3697
|
+
const historyEntry = {
|
|
3596
3698
|
at: stampIso(),
|
|
3597
3699
|
event: existing ? 'goal_from_mission_reused' : 'goal_from_mission_created',
|
|
3598
3700
|
mission_id: runtime.id || null,
|
|
3599
3701
|
mission_status: runtime.status || null,
|
|
3600
|
-
}
|
|
3702
|
+
};
|
|
3703
|
+
// Collapse repeated no-op reuse ticks (same mission state) instead of recording an identical entry
|
|
3704
|
+
// every cadence — that grew one member's goals.json to 2,200+ duplicate entries / 13K lines. Real
|
|
3705
|
+
// transitions (created, changed mission id/status) are still kept; only consecutive dupes are dropped.
|
|
3706
|
+
const lastEntry = goal.history[goal.history.length - 1];
|
|
3707
|
+
const isDuplicateReuse = existing && lastEntry
|
|
3708
|
+
&& lastEntry.event === historyEntry.event
|
|
3709
|
+
&& lastEntry.mission_id === historyEntry.mission_id
|
|
3710
|
+
&& lastEntry.mission_status === historyEntry.mission_status;
|
|
3711
|
+
if (!isDuplicateReuse) goal.history.push(historyEntry);
|
|
3712
|
+
// Backstop cap so no event type can grow the file without bound (bloated files self-heal on write).
|
|
3713
|
+
if (goal.history.length > 50) goal.history = goal.history.slice(-50);
|
|
3601
3714
|
if (!existing) state.goals.push(goal);
|
|
3602
3715
|
state.goals = [goal, ...state.goals.filter((item) => item !== goal)];
|
|
3603
3716
|
writeMemberGoals(paths, state);
|
|
@@ -7205,6 +7318,94 @@ function memberStatus(name, ...args) {
|
|
|
7205
7318
|
);
|
|
7206
7319
|
}
|
|
7207
7320
|
|
|
7321
|
+
function memberHistory(name, ...args) {
|
|
7322
|
+
const { spawnSync } = require('child_process');
|
|
7323
|
+
|
|
7324
|
+
const paths = requireMemberDir(name);
|
|
7325
|
+
const asJson = hasFlag(args, '--json');
|
|
7326
|
+
const limitArg = readNumberFlag(args, '--limit', null);
|
|
7327
|
+
const limit = limitArg !== null ? Math.max(1, limitArg) : null;
|
|
7328
|
+
|
|
7329
|
+
// resolve files to track: MEMBER.md + SOUL.md if present
|
|
7330
|
+
const filesToTrack = [];
|
|
7331
|
+
if (fs.existsSync(paths.memberFile)) {
|
|
7332
|
+
filesToTrack.push(paths.memberFile);
|
|
7333
|
+
}
|
|
7334
|
+
const soulPath = path.join(paths.memberDir, 'SOUL.md');
|
|
7335
|
+
if (fs.existsSync(soulPath)) {
|
|
7336
|
+
filesToTrack.push(soulPath);
|
|
7337
|
+
}
|
|
7338
|
+
|
|
7339
|
+
if (filesToTrack.length === 0) {
|
|
7340
|
+
// no files exist yet (unborn member); empty history ok
|
|
7341
|
+
const payload = {
|
|
7342
|
+
ok: true,
|
|
7343
|
+
action: 'history',
|
|
7344
|
+
member: name,
|
|
7345
|
+
files: [],
|
|
7346
|
+
};
|
|
7347
|
+
printJsonOrText(payload, [`identity history: ${name}`, '(no files found)'], asJson);
|
|
7348
|
+
return;
|
|
7349
|
+
}
|
|
7350
|
+
|
|
7351
|
+
// run git log for each file
|
|
7352
|
+
const cwd = process.cwd();
|
|
7353
|
+
const fileHistories = [];
|
|
7354
|
+
|
|
7355
|
+
for (const filePath of filesToTrack) {
|
|
7356
|
+
const relativePath = path.relative(cwd, filePath);
|
|
7357
|
+
const result = spawnSync('git', ['log', '--follow', '--pretty=format:%h|%ai|%s', '--', relativePath], {
|
|
7358
|
+
cwd,
|
|
7359
|
+
encoding: 'utf8',
|
|
7360
|
+
});
|
|
7361
|
+
|
|
7362
|
+
let commits = [];
|
|
7363
|
+
if (result.status === 0 && result.stdout) {
|
|
7364
|
+
const lines = result.stdout.trim().split('\n').filter(Boolean);
|
|
7365
|
+
commits = lines.map((line) => {
|
|
7366
|
+
const [hash, date, subject] = line.split('|');
|
|
7367
|
+
return { hash: hash || '', date: date || '', subject: subject || '' };
|
|
7368
|
+
});
|
|
7369
|
+
|
|
7370
|
+
// apply limit if specified
|
|
7371
|
+
if (limit !== null) {
|
|
7372
|
+
commits = commits.slice(0, limit);
|
|
7373
|
+
}
|
|
7374
|
+
}
|
|
7375
|
+
|
|
7376
|
+
fileHistories.push({
|
|
7377
|
+
path: relativePath,
|
|
7378
|
+
commits,
|
|
7379
|
+
});
|
|
7380
|
+
}
|
|
7381
|
+
|
|
7382
|
+
// render output
|
|
7383
|
+
if (asJson) {
|
|
7384
|
+
const payload = {
|
|
7385
|
+
ok: true,
|
|
7386
|
+
action: 'history',
|
|
7387
|
+
member: name,
|
|
7388
|
+
files: fileHistories,
|
|
7389
|
+
};
|
|
7390
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
7391
|
+
} else {
|
|
7392
|
+
console.log('');
|
|
7393
|
+
console.log(`identity history: ${name}`);
|
|
7394
|
+
console.log('');
|
|
7395
|
+
for (const fileHist of fileHistories) {
|
|
7396
|
+
console.log(`${fileHist.path}:`);
|
|
7397
|
+
if (fileHist.commits.length === 0) {
|
|
7398
|
+
console.log(' (no git history)');
|
|
7399
|
+
} else {
|
|
7400
|
+
for (const commit of fileHist.commits) {
|
|
7401
|
+
console.log(` ${commit.hash} ${commit.date} ${commit.subject}`);
|
|
7402
|
+
}
|
|
7403
|
+
}
|
|
7404
|
+
console.log('');
|
|
7405
|
+
}
|
|
7406
|
+
}
|
|
7407
|
+
}
|
|
7408
|
+
|
|
7208
7409
|
// --- Command Dispatcher ---
|
|
7209
7410
|
|
|
7210
7411
|
async function memberCommand(subcommand, ...args) {
|
|
@@ -7254,6 +7455,8 @@ async function memberCommand(subcommand, ...args) {
|
|
|
7254
7455
|
return memberBlock(args[0], args[1], ...args.slice(2));
|
|
7255
7456
|
case 'status':
|
|
7256
7457
|
return memberStatus(args[0], ...args.slice(1));
|
|
7458
|
+
case 'history':
|
|
7459
|
+
return memberHistory(args[0], ...args.slice(1));
|
|
7257
7460
|
case 'supervisor':
|
|
7258
7461
|
return memberSupervisorCommand(args[0], ...args.slice(1));
|
|
7259
7462
|
case 'objective-generator':
|
|
@@ -7285,6 +7488,7 @@ async function memberCommand(subcommand, ...args) {
|
|
|
7285
7488
|
console.log(' review <name> <id> Accept/discard an experiment with proof');
|
|
7286
7489
|
console.log(' block <name> <id> Mark an experiment blocked with a human/orchestrator ask');
|
|
7287
7490
|
console.log(' status <name> Show goal, open experiment, value, ask, and recent log');
|
|
7491
|
+
console.log(' history <name> Show git history of member identity files (MEMBER.md, SOUL.md)');
|
|
7288
7492
|
console.log(' supervisor recommendations Show advisory supervisor recommendations');
|
|
7289
7493
|
console.log(' objective-generator proposals Show autonomous objective proposal');
|
|
7290
7494
|
console.log(' generalist proof Show latest cross-domain generalist proof');
|