atris 3.80.0 → 3.82.1
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/LICENSE +21 -0
- package/README.md +10 -0
- package/atris/atris.md +14 -0
- package/atris/features/README.md +15 -0
- package/atris/policies/README.md +24 -0
- package/atris/skills/README.md +61 -45
- package/atris/skills/design/SKILL.md +6 -5
- package/atris/skills/taste/SKILL.md +39 -0
- package/atris/skills/taste/notes.md +29 -0
- package/atris/skills/taste/principles.md +23 -0
- package/atris/skills/wiki/SKILL.md +14 -2
- package/atris/skills/wiki/index-lines.md +30 -0
- package/atris/skills/wiki/measure.md +20 -0
- package/atris/skills/wiki/note-shape.md +16 -0
- package/atris/skills/wiki/upkeep.md +16 -0
- package/atris/team/README.md +38 -0
- package/atris.md +14 -0
- package/bin/atris.js +1 -1
- package/commands/activate.js +1 -1
- package/commands/auth.js +2 -2
- package/commands/autoland.js +25 -26
- package/commands/autopilot-front.js +46 -1
- package/commands/autopilot.js +55 -25
- package/commands/clean.js +22 -11
- package/commands/computer.js +166 -2413
- package/commands/doc-health.js +48 -1
- package/commands/improve.js +16 -3
- package/commands/init.js +4 -0
- package/commands/integrations.js +69 -1616
- package/commands/mission.js +109 -13
- package/commands/release.js +41 -10
- package/commands/security-review.js +5 -1
- package/commands/site-deploy.js +5 -1
- package/commands/status.js +1 -1
- package/commands/sync.js +12 -8
- package/commands/task.js +144 -118
- package/commands/unknowns.js +9 -5
- package/commands/visualize.js +1 -12
- package/commands/workflow.js +4 -3
- package/commands/worktree.js +68 -1602
- package/commands/xp.js +23 -2113
- package/commands/youtube.js +192 -3110
- package/lib/business-core.js +3 -4
- package/lib/campaign-run-lock.js +12 -1
- package/lib/computer-core.js +2447 -0
- package/lib/engine-ask.js +1 -1
- package/lib/engine-receipt-sweep.js +64 -0
- package/lib/file-lock.js +175 -0
- package/lib/fleet.js +1 -1
- package/lib/integrations-core.js +1695 -0
- package/lib/json-file.js +22 -1
- package/lib/loop-doctor.js +36 -2
- package/lib/map-refs.js +68 -8
- package/lib/member-engine.js +1 -1
- package/lib/member-map-fold.js +49 -11
- package/lib/member-map-inbox.js +2 -1
- package/lib/mission-core.js +9 -2
- package/lib/recall-exam.js +171 -0
- package/lib/roster-markdown.js +2 -2
- package/lib/runner-command.js +5 -5
- package/lib/security-audit-classes.js +20 -2
- package/lib/security-audit.js +31 -1
- package/lib/task-db.js +105 -37
- package/lib/taste-notes.js +35 -0
- package/lib/team-lineup.js +3 -3
- package/lib/todo.js +2 -0
- package/lib/wiki.js +11 -3
- package/lib/worktree-core.js +1517 -0
- package/lib/xp-core.js +2172 -0
- package/lib/youtube-core.js +3178 -0
- package/lib/zen-apply.js +3 -1
- package/package.json +10 -3
- package/utils/auth.js +18 -4
package/commands/auth.js
CHANGED
|
@@ -737,7 +737,7 @@ async function loginAtris(options = {}) {
|
|
|
737
737
|
|
|
738
738
|
openBrowser(loginUrl);
|
|
739
739
|
|
|
740
|
-
const code = await promptUser('CLI code: ');
|
|
740
|
+
const code = await promptUser('CLI code: ', { secret: true });
|
|
741
741
|
if (!code) {
|
|
742
742
|
console.error('Error: Code is required.');
|
|
743
743
|
process.exit(1);
|
|
@@ -774,7 +774,7 @@ async function loginAtris(options = {}) {
|
|
|
774
774
|
} else if (methodChoice === '2') {
|
|
775
775
|
console.log('\nGet your token from: https://atris.ai/auth/cli\n');
|
|
776
776
|
|
|
777
|
-
const tokenInput = await promptUser('API token: ');
|
|
777
|
+
const tokenInput = await promptUser('API token: ', { secret: true });
|
|
778
778
|
|
|
779
779
|
if (!tokenInput) {
|
|
780
780
|
console.error('Error: Token is required.');
|
package/commands/autoland.js
CHANGED
|
@@ -9,7 +9,8 @@ const { localDay } = require('../lib/local-day');
|
|
|
9
9
|
const autoland = require('../lib/autoland');
|
|
10
10
|
const { gateForHuman, plainLandingReason } = require('../lib/voice-gate');
|
|
11
11
|
const { evaluateAutoAccept } = require('../lib/auto-accept-certified');
|
|
12
|
-
const { sweepEngineAskReceipts } = require('../lib/engine-receipt-sweep');
|
|
12
|
+
const { sweepEngineAskReceipts, sweepFleetReceipts } = require('../lib/engine-receipt-sweep');
|
|
13
|
+
const { acquireFileLock } = require('../lib/file-lock');
|
|
13
14
|
const { readSshAgents, findLeakedSshAgents, findAgentStartLines, agentStartLineText, stopLeakedSshAgents, closeLostRuns, readResourceHistory, errorReason, leakGrowthNote } = require('../lib/engine-resource');
|
|
14
15
|
const { validateRecentAskReceipts } = require('../lib/engine-validate');
|
|
15
16
|
const { operatorReady, hasAgentJargon, explainResult } = autoland;
|
|
@@ -731,15 +732,6 @@ function runDigest(root, args, { forceSend = false } = {}) {
|
|
|
731
732
|
return 0;
|
|
732
733
|
}
|
|
733
734
|
|
|
734
|
-
function pidAlive(pid) {
|
|
735
|
-
try {
|
|
736
|
-
process.kill(Number(pid), 0);
|
|
737
|
-
return true;
|
|
738
|
-
} catch {
|
|
739
|
-
return false;
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
|
|
743
735
|
async function runTick(root, args) {
|
|
744
736
|
const json = args.includes('--json');
|
|
745
737
|
const policy = autoland.readPolicy(root);
|
|
@@ -756,24 +748,23 @@ async function runTick(root, args) {
|
|
|
756
748
|
// double-alarms, and double-digests. A stale lock (dead pid or >55min)
|
|
757
749
|
// never wedges the loop.
|
|
758
750
|
const lockPath = path.join(root, '.atris', 'state', 'autoland.tick.lock');
|
|
751
|
+
let lock;
|
|
759
752
|
try {
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
771
|
-
fs.writeFileSync(lockPath, JSON.stringify({ pid: process.pid, at: Date.now() }));
|
|
772
|
-
} catch {}
|
|
753
|
+
lock = acquireFileLock(lockPath, { staleAfterMs: 55 * 60 * 1000 });
|
|
754
|
+
} catch {
|
|
755
|
+
lock = null;
|
|
756
|
+
}
|
|
757
|
+
if (lock && !lock.ok) {
|
|
758
|
+
receipt.skipped_reason = 'tick_already_running';
|
|
759
|
+
if (json) console.log(JSON.stringify(receipt));
|
|
760
|
+
else console.log('autoland tick: another tick is still running; skipped.');
|
|
761
|
+
return 0;
|
|
762
|
+
}
|
|
773
763
|
try {
|
|
774
764
|
return await runTickBody(root, { json, policy, receipt });
|
|
775
765
|
} finally {
|
|
776
|
-
|
|
766
|
+
// Only removes the lock while it is still this tick's.
|
|
767
|
+
if (lock) lock.release();
|
|
777
768
|
}
|
|
778
769
|
}
|
|
779
770
|
|
|
@@ -864,8 +855,10 @@ function digestTickStatus(receipt) {
|
|
|
864
855
|
|
|
865
856
|
function engineReceiptSweepNote(receipt) {
|
|
866
857
|
const finalized = Number(receipt.engine_receipt_sweep?.finalized) || 0;
|
|
867
|
-
|
|
868
|
-
|
|
858
|
+
const flights = Number(receipt.fleet_receipt_sweep?.finalized) || 0;
|
|
859
|
+
const engineNote = finalized ? `, finalized ${finalized} stale engine receipt${finalized === 1 ? '' : 's'} as presumed dead` : '';
|
|
860
|
+
const fleetNote = flights ? `, marked ${flights} crashed flight${flights === 1 ? '' : 's'} presumed dead` : '';
|
|
861
|
+
return engineNote + fleetNote;
|
|
869
862
|
}
|
|
870
863
|
|
|
871
864
|
// Read only: how many ssh-agents look leaked right now, and which readers
|
|
@@ -1041,6 +1034,12 @@ async function runTickBody(root, { json, policy, receipt, engineValidationDeps =
|
|
|
1041
1034
|
} catch (err) {
|
|
1042
1035
|
receipt.engine_receipt_sweep_error = String((err && err.message) || err).slice(0, 200);
|
|
1043
1036
|
}
|
|
1037
|
+
try {
|
|
1038
|
+
const fleetSweep = sweepFleetReceipts(root);
|
|
1039
|
+
if (fleetSweep.scanned > 0) receipt.fleet_receipt_sweep = fleetSweep;
|
|
1040
|
+
} catch (err) {
|
|
1041
|
+
receipt.fleet_receipt_sweep_error = String((err && err.message) || err).slice(0, 200);
|
|
1042
|
+
}
|
|
1044
1043
|
|
|
1045
1044
|
runSshAgentSweep(root, receipt);
|
|
1046
1045
|
runLostRunSweep(root, receipt);
|
|
@@ -12,6 +12,7 @@ const { wantsJson, hasYesFlag } = require('../lib/noninteractive');
|
|
|
12
12
|
const { isUnboundScratchFolder, refuseUnboundScratch } = require('../lib/scratch-root');
|
|
13
13
|
const { resolveWorkspaceRoot } = require('../lib/mission-root');
|
|
14
14
|
const { personName } = require('../lib/first-minute');
|
|
15
|
+
const { acquireFileLock } = require('../lib/file-lock');
|
|
15
16
|
|
|
16
17
|
const CLI_PATH = path.join(__dirname, '..', 'bin', 'atris.js');
|
|
17
18
|
const DEFAULT_LEG_WALL_SECONDS = 3600;
|
|
@@ -21,6 +22,7 @@ const MAX_FAST_FAILS = 5;
|
|
|
21
22
|
|
|
22
23
|
function statePath(root) { return path.join(root, '.atris', 'state', 'autopilot.json'); }
|
|
23
24
|
function stopPath(root) { return path.join(root, '.atris', 'state', 'autopilot.stop'); }
|
|
25
|
+
function lockPath(root) { return path.join(root, '.atris', 'state', 'autopilot.lock'); }
|
|
24
26
|
|
|
25
27
|
function readState(root) {
|
|
26
28
|
try { return JSON.parse(fs.readFileSync(statePath(root), 'utf8')); } catch { return null; }
|
|
@@ -42,6 +44,26 @@ function requestStop(root) {
|
|
|
42
44
|
}
|
|
43
45
|
function clearStop(root) { try { fs.unlinkSync(stopPath(root)); } catch {} }
|
|
44
46
|
|
|
47
|
+
// When `autopilot stop` wrote its marker, in ms, or null without one.
|
|
48
|
+
function stopRequestedAtMs(root) {
|
|
49
|
+
let stat;
|
|
50
|
+
try { stat = fs.statSync(stopPath(root)); } catch { return null; }
|
|
51
|
+
let stamped = NaN;
|
|
52
|
+
try { stamped = Date.parse(fs.readFileSync(stopPath(root), 'utf8').trim()); } catch {}
|
|
53
|
+
return Number.isFinite(stamped) ? Math.max(stamped, stat.mtimeMs) : stat.mtimeMs;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// A stop marker left over from before this start is cleared. One written
|
|
57
|
+
// after this process launched is a real stop that raced the start, so it is
|
|
58
|
+
// kept and honored. Returns true when such a stop is pending.
|
|
59
|
+
function clearStaleStop(root, startRequestedAtMs) {
|
|
60
|
+
const at = stopRequestedAtMs(root);
|
|
61
|
+
if (at == null) return false;
|
|
62
|
+
if (at >= startRequestedAtMs) return true;
|
|
63
|
+
clearStop(root);
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
45
67
|
function pidAlive(pid) {
|
|
46
68
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
47
69
|
try { process.kill(pid, 0); return true; } catch { return false; }
|
|
@@ -261,8 +283,28 @@ async function autopilotFront(args = []) {
|
|
|
261
283
|
console.log(`Autopilot already running (pid ${existing.pid}). Stop it first: atris autopilot stop`);
|
|
262
284
|
return 1;
|
|
263
285
|
}
|
|
286
|
+
// The state check above is read-then-write; two starts in the same instant
|
|
287
|
+
// both passed it. The lock file is created exclusively, so only one wins.
|
|
288
|
+
const lock = acquireFileLock(lockPath(root));
|
|
289
|
+
if (!lock.ok) {
|
|
290
|
+
console.log(`Autopilot already running (pid ${lock.holder?.pid || '?'}). Stop it first: atris autopilot stop`);
|
|
291
|
+
return 1;
|
|
292
|
+
}
|
|
293
|
+
try {
|
|
294
|
+
return await runFrontLoop(root, args, lock);
|
|
295
|
+
} finally {
|
|
296
|
+
lock.release();
|
|
297
|
+
}
|
|
298
|
+
}
|
|
264
299
|
|
|
265
|
-
|
|
300
|
+
async function runFrontLoop(root, args, lock) {
|
|
301
|
+
// The moment this process launched. A stop issued after it is not stale.
|
|
302
|
+
const startRequestedAtMs = Date.now() - Math.round(process.uptime() * 1000);
|
|
303
|
+
if (clearStaleStop(root, startRequestedAtMs)) {
|
|
304
|
+
clearStop(root);
|
|
305
|
+
console.log('\nAutopilot off (stop requested). Legs run: 0.');
|
|
306
|
+
return 0;
|
|
307
|
+
}
|
|
266
308
|
const budgetSeconds = runBudgetSeconds(args);
|
|
267
309
|
const legWallDefault = positiveNumber(readValueFlag(args, '--leg-wall')) || DEFAULT_LEG_WALL_SECONDS;
|
|
268
310
|
// Honor the legacy contract callers still speak: `--iterations=N` caps the
|
|
@@ -283,6 +325,8 @@ async function autopilotFront(args = []) {
|
|
|
283
325
|
console.log(`\nAutopilot off (${reason}). Legs run: ${state.legs}.`);
|
|
284
326
|
clearState(root);
|
|
285
327
|
clearStop(root);
|
|
328
|
+
// process.exit skips the caller's finally, so release here.
|
|
329
|
+
try { lock.release(); } catch {}
|
|
286
330
|
process.exit(130);
|
|
287
331
|
};
|
|
288
332
|
process.on('SIGINT', onSignal);
|
|
@@ -344,6 +388,7 @@ module.exports = {
|
|
|
344
388
|
stopRequested,
|
|
345
389
|
requestStop,
|
|
346
390
|
clearStop,
|
|
391
|
+
clearStaleStop,
|
|
347
392
|
readState,
|
|
348
393
|
writeState,
|
|
349
394
|
watchStopFile,
|
package/commands/autopilot.js
CHANGED
|
@@ -1867,24 +1867,41 @@ function isDoPhaseTimeoutMessage(message) {
|
|
|
1867
1867
|
* un-struck bullet whose normalized text contains the normalized title
|
|
1868
1868
|
* prefix; `- **T33:** …` becomes `- [x] **T33:** …`, `- [ ]` becomes `- [x]`.
|
|
1869
1869
|
* Returns true if a bullet was marked.
|
|
1870
|
+
*
|
|
1871
|
+
* A rendered TODO.md (it carries the "Regenerated from durable Atris task
|
|
1872
|
+
* state" marker) is a view of the task database, so there the job itself is
|
|
1873
|
+
* finished, the way `atris task done` finishes it, and the view re-rendered.
|
|
1874
|
+
* Returns false when no single open or claimed job matches: re-rendering alone
|
|
1875
|
+
* used to return true, and the tick reported a job advanced that never moved.
|
|
1870
1876
|
*/
|
|
1871
1877
|
function markTodoBulletDone(cwd, taskTitle) {
|
|
1878
|
+
const needle = taskMatchNeedle(taskTitle);
|
|
1879
|
+
let isRenderedView = false;
|
|
1872
1880
|
try {
|
|
1873
|
-
// A rendered TODO.md carries the "Regenerated from durable Atris task
|
|
1874
|
-
// state" marker; those are projections and never hand-edited. Legacy
|
|
1875
|
-
// hand-written TODO.md files (no marker) keep the bullet-mark fallback.
|
|
1876
1881
|
const renderedTodoPath = path.join(cwd, 'atris', 'TODO.md');
|
|
1877
|
-
|
|
1882
|
+
isRenderedView = fs.existsSync(renderedTodoPath)
|
|
1878
1883
|
&& fs.readFileSync(renderedTodoPath, 'utf8').includes('Regenerated from durable Atris task state');
|
|
1879
|
-
|
|
1884
|
+
} catch {
|
|
1885
|
+
isRenderedView = false;
|
|
1886
|
+
}
|
|
1887
|
+
if (isRenderedView) {
|
|
1888
|
+
if (!needle) return false;
|
|
1889
|
+
try {
|
|
1880
1890
|
require('node:sqlite');
|
|
1881
|
-
const
|
|
1882
|
-
|
|
1891
|
+
const out = require('./task').completeTodoTaskByTitle(cwd, taskTitle, {
|
|
1892
|
+
matches: (title) => {
|
|
1893
|
+
const norm = normalizeForMatch(title);
|
|
1894
|
+
// The reverse test covers a tick title with a prefix ("T33: ..."); a short job title must not match by it.
|
|
1895
|
+
return Boolean(norm) && (norm.includes(needle) || (norm.length >= 12 && needle.includes(norm)));
|
|
1896
|
+
},
|
|
1897
|
+
actor: 'autopilot',
|
|
1898
|
+
proof: `autopilot tick finished "${String(taskTitle || '').slice(0, 200)}" and its completion receipt is in today's journal`,
|
|
1899
|
+
});
|
|
1900
|
+
return Boolean(out && out.completed);
|
|
1901
|
+
} catch {
|
|
1902
|
+
// Older Node runtimes without node:sqlite fall through to the markdown edit.
|
|
1883
1903
|
}
|
|
1884
|
-
} catch {
|
|
1885
|
-
// Older Node runtimes and legacy workspaces retain the markdown fallback.
|
|
1886
1904
|
}
|
|
1887
|
-
const needle = taskMatchNeedle(taskTitle);
|
|
1888
1905
|
if (!needle) return false;
|
|
1889
1906
|
for (const name of ['TODO.md', 'todo.md']) {
|
|
1890
1907
|
const todoPath = path.join(cwd, 'atris', name);
|
|
@@ -2211,26 +2228,38 @@ function logCompletion(description) {
|
|
|
2211
2228
|
ensureLogDirectory();
|
|
2212
2229
|
const { logFile, dateFormatted } = getLogPath();
|
|
2213
2230
|
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2231
|
+
// The next C number is read and the entry written as one step under a lock file, so two ticks
|
|
2232
|
+
// finishing at once cannot both take the same number. A lock that cannot be had in a few seconds
|
|
2233
|
+
// does not cost the entry: it is written anyway, as before.
|
|
2234
|
+
const write = () => {
|
|
2235
|
+
if (!fs.existsSync(logFile)) {
|
|
2236
|
+
createLogFile(logFile, dateFormatted);
|
|
2237
|
+
}
|
|
2217
2238
|
|
|
2218
|
-
|
|
2239
|
+
let content = fs.readFileSync(logFile, 'utf8');
|
|
2219
2240
|
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2241
|
+
const completionMatch = content.match(/\*\*C(\d+):/g);
|
|
2242
|
+
const nextId = completionMatch
|
|
2243
|
+
? Math.max(...completionMatch.map(m => parseInt(m.match(/\d+/)[0]))) + 1
|
|
2244
|
+
: 1;
|
|
2224
2245
|
|
|
2225
|
-
|
|
2246
|
+
const entry = `- **C${nextId}:** ${description} [reviewed]`;
|
|
2226
2247
|
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2248
|
+
// Readers look for "## Completed ✅" (and, since this fix, a bare "## Completed" too).
|
|
2249
|
+
if (/^## Completed\b/m.test(content)) {
|
|
2250
|
+
content = content.replace(/^(## Completed[^\n]*\n)/m, `$1\n${entry}\n`);
|
|
2251
|
+
} else {
|
|
2252
|
+
content += `\n## Completed ✅\n\n${entry}\n`;
|
|
2253
|
+
}
|
|
2232
2254
|
|
|
2233
|
-
|
|
2255
|
+
fs.writeFileSync(logFile, content);
|
|
2256
|
+
};
|
|
2257
|
+
try {
|
|
2258
|
+
require('../lib/handoff-wake').withFileLock(`${logFile}.lock`, write, { staleMs: 60 * 1000, waitMs: 5 * 1000 });
|
|
2259
|
+
} catch (err) {
|
|
2260
|
+
if (!/stayed locked/.test(String(err && err.message))) throw err;
|
|
2261
|
+
write();
|
|
2262
|
+
}
|
|
2234
2263
|
}
|
|
2235
2264
|
|
|
2236
2265
|
/**
|
|
@@ -4055,6 +4084,7 @@ module.exports = {
|
|
|
4055
4084
|
diffAdvancedRepoHeads,
|
|
4056
4085
|
reconcileTimedOutTick,
|
|
4057
4086
|
markTodoBulletDone,
|
|
4087
|
+
logCompletion,
|
|
4058
4088
|
findCompletionReceipt,
|
|
4059
4089
|
isDoPhaseTimeoutMessage,
|
|
4060
4090
|
validateVerifyCommandShape,
|
package/commands/clean.js
CHANGED
|
@@ -359,23 +359,34 @@ function findStaleTasks(atrisDir) {
|
|
|
359
359
|
* Detects both out-of-bounds AND drift (symbol moved to different line)
|
|
360
360
|
* Returns { healed: number, unhealable: array }
|
|
361
361
|
*/
|
|
362
|
-
// The boot map routes; atris/refs/MAP-NOTES.md holds the deep file:line refs
|
|
363
|
-
//
|
|
362
|
+
// The boot map routes; atris/refs/MAP-NOTES.md holds the deep file:line refs,
|
|
363
|
+
// or indexes area notes under atris/refs/map/ that hold them. Heal every one.
|
|
364
|
+
// Entries from a notes file carry `map` so reports can say where.
|
|
364
365
|
const MAP_NOTES_FILE = path.join('refs', 'MAP-NOTES.md');
|
|
366
|
+
const MAP_NOTES_DIR = path.join('refs', 'map');
|
|
367
|
+
|
|
368
|
+
function mapNoteFiles(atrisDir) {
|
|
369
|
+
const files = [path.join(atrisDir, MAP_NOTES_FILE)];
|
|
370
|
+
try {
|
|
371
|
+
for (const name of fs.readdirSync(path.join(atrisDir, MAP_NOTES_DIR)).sort()) {
|
|
372
|
+
if (name.endsWith('.md')) files.push(path.join(atrisDir, MAP_NOTES_DIR, name));
|
|
373
|
+
}
|
|
374
|
+
} catch { /* no area notes */ }
|
|
375
|
+
return files.filter((file) => { try { return fs.statSync(file).isFile(); } catch { return false; } });
|
|
376
|
+
}
|
|
365
377
|
|
|
366
378
|
function healBrokenMapRefs(cwd, atrisDir, dryRun = false, homeDir = os.homedir()) {
|
|
367
379
|
const mapFile = path.join(atrisDir, 'MAP.md');
|
|
368
380
|
if (!fs.existsSync(mapFile)) return { healed: 0, unhealable: [] };
|
|
369
381
|
const result = healMapFileRefs(cwd, mapFile, dryRun, homeDir);
|
|
370
|
-
const notesFile
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
};
|
|
382
|
+
for (const notesFile of mapNoteFiles(atrisDir)) {
|
|
383
|
+
const notes = healMapFileRefs(cwd, notesFile, dryRun, homeDir);
|
|
384
|
+
const map = path.relative(cwd, notesFile).split(path.sep).join('/');
|
|
385
|
+
result.healed += notes.healed;
|
|
386
|
+
result.unhealable = [...result.unhealable, ...notes.unhealable.map((entry) => ({ ...entry, map }))];
|
|
387
|
+
result.replacements = [...(result.replacements || []), ...notes.replacements.map((entry) => ({ ...entry, map }))];
|
|
388
|
+
}
|
|
389
|
+
return result;
|
|
379
390
|
}
|
|
380
391
|
|
|
381
392
|
function healMapFileRefs(cwd, mapFile, dryRun, homeDir) {
|