atris 3.37.0 → 3.38.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 +2 -0
- package/atris/PERSONA.md +3 -7
- package/atris.md +10 -2
- package/ax +158 -33
- package/bin/atris.js +49 -2
- package/commands/activate.js +16 -3
- package/commands/agent-spawn.js +0 -7
- package/commands/autoland.js +92 -14
- package/commands/autopilot-front.js +0 -4
- package/commands/autopilot.js +0 -12
- package/commands/brain.js +0 -7
- package/commands/brief.js +0 -2
- package/commands/business-sync.js +0 -1
- package/commands/business.js +107 -12
- package/commands/clean.js +31 -2
- package/commands/close.js +46 -11
- package/commands/cloud.js +0 -1
- package/commands/codex-goal.js +0 -2
- package/commands/computer.js +6 -5
- package/commands/console.js +46 -9
- package/commands/deck.js +29 -8
- package/commands/dream.js +0 -7
- package/commands/drill.js +0 -1
- package/commands/engine.js +10 -18
- package/commands/experiments.js +0 -4
- package/commands/fleet-report.js +12 -4
- package/commands/game.js +261 -0
- package/commands/gm.js +10 -2
- package/commands/goal.js +0 -6
- package/commands/harvest.js +0 -1
- package/commands/improve.js +29 -8
- package/commands/init.js +18 -34
- package/commands/integrations.js +0 -7
- package/commands/land.js +71 -8
- package/commands/launchpad.js +0 -2
- package/commands/lesson.js +17 -7
- package/commands/loop-front.js +0 -2
- package/commands/loops.js +6 -5
- package/commands/meet.js +0 -2
- package/commands/member.js +68 -3
- package/commands/mission.js +359 -102
- package/commands/next.js +0 -1
- package/commands/now.js +0 -12
- package/commands/one-lap.js +8 -8
- package/commands/orb.js +0 -4
- package/commands/pack-craft.js +0 -1
- package/commands/pack.js +670 -77
- package/commands/play.js +0 -2
- package/commands/probe.js +10 -4
- package/commands/pulse.js +73 -48
- package/commands/push.js +0 -4
- package/commands/radar.js +14 -11
- package/commands/recover.js +340 -0
- package/commands/report.js +0 -1
- package/commands/router.js +319 -0
- package/commands/run-front.js +0 -2
- package/commands/search.js +0 -3
- package/commands/skill.js +7 -2
- package/commands/slop.js +225 -7
- package/commands/status.js +6 -0
- package/commands/stream.js +0 -2
- package/commands/study.js +0 -3
- package/commands/sync-checkout.js +51 -0
- package/commands/sync.js +11 -40
- package/commands/task.js +125 -39
- package/commands/teach.js +86 -0
- package/commands/unknowns.js +0 -10
- package/commands/update.js +0 -1
- package/commands/visualize.js +0 -5
- package/commands/watch.js +0 -6
- package/commands/wiki.js +41 -11
- package/commands/wish.js +0 -1
- package/commands/worktree.js +87 -14
- package/commands/xp.js +1 -6
- package/commands/youtube.js +0 -2
- package/lib/accept-verify-gate.js +122 -0
- package/lib/activity-stream.js +0 -2
- package/lib/auto-accept-certified.js +145 -2
- package/lib/autoland.js +0 -3
- package/lib/ax-auto-lane.js +94 -10
- package/lib/bench/context.js +0 -2
- package/lib/bench/report.js +0 -1
- package/lib/bench/runner.js +0 -11
- package/lib/brief-ledger.js +0 -6
- package/lib/business-simulate.js +382 -0
- package/lib/chat-log-scan.js +0 -5
- package/lib/checkout-sync.js +52 -0
- package/lib/claude-boot-block.js +48 -0
- package/lib/cloud-mission.js +0 -6
- package/lib/codex-flight.js +0 -1
- package/lib/conductor-artifacts.js +25 -0
- package/lib/context-gatherer.js +0 -1
- package/lib/deck-compose.js +0 -1
- package/lib/deck-history.js +0 -1
- package/lib/deck-layout.js +0 -5
- package/lib/deck-review.js +0 -7
- package/lib/deck-schema.js +0 -1
- package/lib/default-verifier.js +0 -2
- package/lib/engine-registry.js +16 -8
- package/lib/experiments/daily.js +0 -6
- package/lib/fleet.js +210 -27
- package/lib/functional-owner.js +0 -3
- package/lib/inspect-fields.js +0 -1
- package/lib/known-commands.js +2 -2
- package/lib/learnings.js +0 -2
- package/lib/loop-doctor.js +11 -17
- package/lib/manifest.js +0 -2
- package/lib/member-alive.js +0 -1
- package/lib/member-switches.js +0 -5
- package/lib/mission-artifact.js +0 -1
- package/lib/mission-protected-lane.js +293 -0
- package/lib/mission-room.js +0 -2
- package/lib/mission-runtime-loop.js +0 -1
- package/lib/next-moves.js +0 -18
- package/lib/official-cli-integration.js +0 -4
- package/lib/orb-context.js +6 -3
- package/lib/orb-scorecard.js +0 -1
- package/lib/outbound-send-gate.js +0 -6
- package/lib/permission-grants.js +0 -6
- package/lib/policy-lessons.js +0 -2
- package/lib/pulse.js +68 -15
- package/lib/review-integrity.js +0 -2
- package/lib/router-brain.js +0 -2
- package/lib/runner-command.js +0 -4
- package/lib/runtime-bootstrap.js +0 -1
- package/lib/scorecard.js +0 -1
- package/lib/self-drive.js +1 -1
- package/lib/state-detection.js +0 -6
- package/lib/task-db.js +0 -4
- package/lib/team-presence.js +0 -1
- package/lib/voice-gate.js +63 -2
- package/lib/wiki.js +152 -9
- package/lib/wish-audit.js +20 -6
- package/lib/wish-delegate.js +26 -20
- package/lib/wish-stats.js +0 -5
- package/lib/wish-store.js +0 -1
- package/lib/workspace-safety.js +0 -2
- package/lib/zip.js +0 -1
- package/package.json +1 -1
package/lib/pulse.js
CHANGED
|
@@ -288,8 +288,16 @@ function shouldFallbackToAutopilot({ missionReason, autopilotFallback = true, no
|
|
|
288
288
|
// actual delta (new commit or newly-dirtied files), never the whole dirty tree —
|
|
289
289
|
// crediting pre-existing dirt re-rewards the same change every tick (the reward
|
|
290
290
|
// inflation bug). The caller computes producedWork from a before/after snapshot.
|
|
291
|
-
|
|
291
|
+
//
|
|
292
|
+
// A crashed engine must also score -1. Before this, a hard `mission error` tick
|
|
293
|
+
// produced no work and ran no verifier, so it scored 0 — indistinguishable from
|
|
294
|
+
// a healthy idle tick. The 2026-07-16 heartbeat death proved the cost: 25
|
|
295
|
+
// consecutive error ticks left reward_sum flat, nothing escalated, and the loop
|
|
296
|
+
// stopped with a ledger that still read green. `actorReason: 'error'` is a
|
|
297
|
+
// failure, not a rest.
|
|
298
|
+
function scoreTick({ verifyPassed, producedWork, actorOk, actorReason } = {}) {
|
|
292
299
|
if (verifyPassed === false) return -1;
|
|
300
|
+
if (actorOk === false && actorReason === 'error') return -1;
|
|
293
301
|
if (!producedWork) return 0;
|
|
294
302
|
return verifyPassed === true ? 1 : 0;
|
|
295
303
|
}
|
|
@@ -575,7 +583,7 @@ function releaseLock(root) {
|
|
|
575
583
|
} catch {}
|
|
576
584
|
}
|
|
577
585
|
|
|
578
|
-
// ---
|
|
586
|
+
// --- scheduled tick script + launchd plist (pure string generation) ---
|
|
579
587
|
|
|
580
588
|
// Minimal shell wrapper modeled on the proven commander tick.sh: deadline
|
|
581
589
|
// self-removal, then hand off to `atris pulse tick` (all real logic lives in JS,
|
|
@@ -593,6 +601,8 @@ function buildTickScript(opts = {}) {
|
|
|
593
601
|
runnerCommandTemplate = '',
|
|
594
602
|
verifyCmd = 'npm test',
|
|
595
603
|
pathDirs = [],
|
|
604
|
+
launchLabel = '',
|
|
605
|
+
launchPlistPath = '',
|
|
596
606
|
} = opts;
|
|
597
607
|
if (!root) throw new Error('buildTickScript: root is required');
|
|
598
608
|
if (!stateHome) throw new Error('buildTickScript: stateHome is required');
|
|
@@ -636,16 +646,26 @@ STATE="${stateHome}"
|
|
|
636
646
|
LOG_DIR="$STATE/logs"
|
|
637
647
|
DEADLINE_EPOCH="${deadlineEpoch}"
|
|
638
648
|
MARKER="${marker}"
|
|
649
|
+
LAUNCH_LABEL="${launchLabel}"
|
|
650
|
+
LAUNCH_PLIST="${launchPlistPath}"
|
|
651
|
+
|
|
652
|
+
# Claude Max subscription auth comes from the macOS keychain. An API key would
|
|
653
|
+
# override it and route autonomous work onto metered API billing.
|
|
654
|
+
unset ANTHROPIC_API_KEY
|
|
639
655
|
|
|
640
|
-
#
|
|
656
|
+
# launchd has a minimal PATH; restore the dirs the engine's bare-name spawns need.
|
|
641
657
|
${pathExport}
|
|
642
658
|
|
|
643
659
|
mkdir -p "$LOG_DIR"
|
|
644
660
|
|
|
645
661
|
now="$(date +%s)"
|
|
646
662
|
if [ "$now" -ge "$DEADLINE_EPOCH" ]; then
|
|
663
|
+
if [ -n "$LAUNCH_LABEL" ]; then
|
|
664
|
+
launchctl bootout "gui/$(id -u)/$LAUNCH_LABEL" 2>/dev/null || true
|
|
665
|
+
[ -n "$LAUNCH_PLIST" ] && rm -f "$LAUNCH_PLIST"
|
|
666
|
+
fi
|
|
647
667
|
crontab -l 2>/dev/null | grep -v "$MARKER" | crontab - 2>/dev/null || true
|
|
648
|
-
echo "$(date -Iseconds) pulse expired; removed
|
|
668
|
+
echo "$(date -Iseconds) pulse expired; removed scheduler" >> "$LOG_DIR/control.log"
|
|
649
669
|
expired_at="$(date -Iseconds)"
|
|
650
670
|
journal_year="$(date +%Y)"
|
|
651
671
|
journal_day="$(date +%F)"
|
|
@@ -693,6 +713,48 @@ fi
|
|
|
693
713
|
`;
|
|
694
714
|
}
|
|
695
715
|
|
|
716
|
+
function xmlEscape(value) {
|
|
717
|
+
return String(value)
|
|
718
|
+
.replace(/&/g, '&')
|
|
719
|
+
.replace(/</g, '<')
|
|
720
|
+
.replace(/>/g, '>')
|
|
721
|
+
.replace(/"/g, '"')
|
|
722
|
+
.replace(/'/g, ''');
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function launchIntervalFromCadence({ cadenceMinutes, cron } = {}) {
|
|
726
|
+
const minutes = Number(cadenceMinutes);
|
|
727
|
+
if (Number.isInteger(minutes) && minutes > 0) return { seconds: minutes * 60, fallback: false };
|
|
728
|
+
const normalized = normalizeCronCadence(cron || DEFAULT_CADENCE_CRON);
|
|
729
|
+
const everyMinutes = normalized.match(/^\*\/(\d+) \* \* \* \*$/);
|
|
730
|
+
if (everyMinutes) return { seconds: Number(everyMinutes[1]) * 60, fallback: false };
|
|
731
|
+
if (/^\d+ \* \* \* \*$/.test(normalized)) return { seconds: 3600, fallback: false };
|
|
732
|
+
return { seconds: 3600, fallback: true };
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function buildLaunchAgentPlist(opts = {}) {
|
|
736
|
+
const { label, scriptPath, stateHome } = opts;
|
|
737
|
+
if (!label) throw new Error('buildLaunchAgentPlist: label is required');
|
|
738
|
+
if (!scriptPath) throw new Error('buildLaunchAgentPlist: scriptPath is required');
|
|
739
|
+
if (!stateHome) throw new Error('buildLaunchAgentPlist: stateHome is required');
|
|
740
|
+
const interval = launchIntervalFromCadence(opts).seconds;
|
|
741
|
+
const logPath = path.join(stateHome, 'logs', 'launchd.log');
|
|
742
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
743
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
744
|
+
<plist version="1.0">
|
|
745
|
+
<dict>
|
|
746
|
+
<key>Label</key><string>${xmlEscape(label)}</string>
|
|
747
|
+
<key>ProgramArguments</key>
|
|
748
|
+
<array><string>/bin/bash</string><string>${xmlEscape(scriptPath)}</string></array>
|
|
749
|
+
<key>StartInterval</key><integer>${interval}</integer>
|
|
750
|
+
<key>AbandonProcessGroup</key><true/>
|
|
751
|
+
<key>StandardOutPath</key><string>${xmlEscape(logPath)}</string>
|
|
752
|
+
<key>StandardErrorPath</key><string>${xmlEscape(logPath)}</string>
|
|
753
|
+
</dict>
|
|
754
|
+
</plist>
|
|
755
|
+
`;
|
|
756
|
+
}
|
|
757
|
+
|
|
696
758
|
function buildCrontabLine(opts = {}) {
|
|
697
759
|
const { cron = DEFAULT_CADENCE_CRON, scriptPath, marker = PULSE_MARKER } = opts;
|
|
698
760
|
if (!scriptPath) throw new Error('buildCrontabLine: scriptPath is required');
|
|
@@ -703,28 +765,17 @@ module.exports = {
|
|
|
703
765
|
PULSE_RECEIPT_SCHEMA,
|
|
704
766
|
SCORECARD_SCHEMA,
|
|
705
767
|
PULSE_MARKER,
|
|
706
|
-
LEGACY_STATE_DIRNAME,
|
|
707
768
|
DEFAULT_CADENCE_CRON,
|
|
708
769
|
STALE_TICK_MS,
|
|
709
|
-
LIVENESS_STALE_MS,
|
|
710
770
|
stateDir,
|
|
711
|
-
pulseRepoSlug,
|
|
712
|
-
pulseStateHome,
|
|
713
|
-
pulseMarker,
|
|
714
771
|
legacyPulseStateHome,
|
|
715
|
-
readTickScriptRoot,
|
|
716
|
-
legacyPulseStateMatchesRoot,
|
|
717
772
|
resolvePulseSlot,
|
|
718
773
|
pulseReceiptsPath,
|
|
719
774
|
scorecardsPath,
|
|
720
|
-
pulseCounterPath,
|
|
721
|
-
pulseLockDir,
|
|
722
775
|
buildPulseReceipt,
|
|
723
776
|
buildPulseScorecardRow,
|
|
724
|
-
buildOrbFailureScorecardRow,
|
|
725
777
|
buildInterruptedPulseReceipt,
|
|
726
778
|
ingestOrbScorecard,
|
|
727
|
-
orbRunKey,
|
|
728
779
|
scoreTick,
|
|
729
780
|
fsSnapshot,
|
|
730
781
|
diffFsSnapshots,
|
|
@@ -744,6 +795,8 @@ module.exports = {
|
|
|
744
795
|
acquireLock,
|
|
745
796
|
releaseLock,
|
|
746
797
|
buildTickScript,
|
|
798
|
+
buildLaunchAgentPlist,
|
|
799
|
+
launchIntervalFromCadence,
|
|
747
800
|
buildCrontabLine,
|
|
748
801
|
normalizeCronCadence,
|
|
749
802
|
};
|
package/lib/review-integrity.js
CHANGED
package/lib/router-brain.js
CHANGED
package/lib/runner-command.js
CHANGED
|
@@ -230,11 +230,7 @@ module.exports = {
|
|
|
230
230
|
RUNNER_PROFILE_DEFS,
|
|
231
231
|
RUNNER_PROFILE_ALIASES,
|
|
232
232
|
RUNNER_PROFILE_NAMES,
|
|
233
|
-
resolveRunnerProfileName,
|
|
234
233
|
resolveRunnerProfile,
|
|
235
|
-
resolveRunnerModel: resolveClaudeRunnerModel,
|
|
236
|
-
resolveRunnerBin: resolveClaudeRunnerBin,
|
|
237
|
-
resolveRunnerCommandTemplate: resolveClaudeRunnerCommandTemplate,
|
|
238
234
|
resolveClaudeRunnerModel,
|
|
239
235
|
resolveClaudeRunnerBin,
|
|
240
236
|
resolveClaudeRunnerCommandTemplate,
|
package/lib/runtime-bootstrap.js
CHANGED
package/lib/scorecard.js
CHANGED
package/lib/self-drive.js
CHANGED
package/lib/state-detection.js
CHANGED
|
@@ -406,11 +406,5 @@ function loadContext(workspaceDir) {
|
|
|
406
406
|
module.exports = {
|
|
407
407
|
detectWorkspaceState,
|
|
408
408
|
loadContext,
|
|
409
|
-
getTodayInboxItems,
|
|
410
|
-
getTodayLogPath,
|
|
411
|
-
getBacklogTasks,
|
|
412
|
-
getInProgressTasks,
|
|
413
|
-
getCompletedTasks,
|
|
414
|
-
getTaskCounts,
|
|
415
409
|
getTaskGlance
|
|
416
410
|
};
|
package/lib/task-db.js
CHANGED
|
@@ -2051,7 +2051,6 @@ module.exports = {
|
|
|
2051
2051
|
doneTask,
|
|
2052
2052
|
archiveTask,
|
|
2053
2053
|
relabelArchivedTasks,
|
|
2054
|
-
isJune10BacklogResetMarker,
|
|
2055
2054
|
readyTask,
|
|
2056
2055
|
setTaskResult,
|
|
2057
2056
|
reviseTask,
|
|
@@ -2065,10 +2064,7 @@ module.exports = {
|
|
|
2065
2064
|
taskProjection,
|
|
2066
2065
|
renderTodoMarkdown,
|
|
2067
2066
|
normalizeTaskRef,
|
|
2068
|
-
shortestUniqueTaskRef,
|
|
2069
2067
|
taskDisplayRefMap,
|
|
2070
2068
|
withTaskDisplayRefs,
|
|
2071
2069
|
newId,
|
|
2072
|
-
// Test surface
|
|
2073
|
-
_SCHEMA: SCHEMA,
|
|
2074
2070
|
};
|
package/lib/team-presence.js
CHANGED
package/lib/voice-gate.js
CHANGED
|
@@ -8,6 +8,17 @@ const {
|
|
|
8
8
|
} = require('./autoland');
|
|
9
9
|
|
|
10
10
|
const EM_DASH = /\u2014/;
|
|
11
|
+
const JARGON = /\b(?:idempotent|deterministic|substrate|orchestration|invariant|canonical|materialize|heuristic)\b/gi;
|
|
12
|
+
const JARGON_SUGGESTIONS = {
|
|
13
|
+
idempotent: 'safe to repeat',
|
|
14
|
+
deterministic: 'predictable',
|
|
15
|
+
substrate: 'base',
|
|
16
|
+
orchestration: 'coordination',
|
|
17
|
+
invariant: 'rule that must stay true',
|
|
18
|
+
canonical: 'standard',
|
|
19
|
+
materialize: 'create',
|
|
20
|
+
heuristic: 'rule of thumb',
|
|
21
|
+
};
|
|
11
22
|
const NUMBER_WORDS = [
|
|
12
23
|
'no', 'one', 'two', 'three', 'four', 'five', 'six',
|
|
13
24
|
'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve',
|
|
@@ -30,7 +41,7 @@ function findingsFor(text, pattern, rule, why) {
|
|
|
30
41
|
return findings;
|
|
31
42
|
}
|
|
32
43
|
|
|
33
|
-
function
|
|
44
|
+
function scanWords(text) {
|
|
34
45
|
const value = String(text || '');
|
|
35
46
|
const findings = findingsFor(value, EM_DASH, 'em-dash', 'use plain punctuation');
|
|
36
47
|
if (hasAgentJargon(value)) {
|
|
@@ -62,6 +73,55 @@ function scanText(text) {
|
|
|
62
73
|
return findings;
|
|
63
74
|
}
|
|
64
75
|
|
|
76
|
+
function paragraphsFor(text) {
|
|
77
|
+
return String(text || '')
|
|
78
|
+
.trim()
|
|
79
|
+
.split(/\r?\n(?:[ \t]*\r?\n)+/)
|
|
80
|
+
.map(paragraph => paragraph.trim())
|
|
81
|
+
.filter(Boolean);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function sentenceCount(text) {
|
|
85
|
+
const value = String(text || '').replace(/\s+/g, ' ').trim();
|
|
86
|
+
if (!value) return 0;
|
|
87
|
+
const endings = value.match(/[.!?]+(?=\s|$)/g) || [];
|
|
88
|
+
return endings.length + (/[.!?]+$/.test(value) ? 0 : 1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function scanShape(text) {
|
|
92
|
+
const paragraphs = paragraphsFor(text);
|
|
93
|
+
const findings = [];
|
|
94
|
+
for (const paragraph of paragraphs) {
|
|
95
|
+
if (sentenceCount(paragraph) > 2) {
|
|
96
|
+
findings.push({
|
|
97
|
+
rule: 'dense-block',
|
|
98
|
+
why: 'add a blank line after every two sentences',
|
|
99
|
+
snippet: paragraph,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (paragraphs.length > 3) {
|
|
104
|
+
findings.push({
|
|
105
|
+
rule: 'too-many-ideas',
|
|
106
|
+
why: 'split this into messages with no more than three paragraphs each',
|
|
107
|
+
snippet: paragraphs.slice(3).join('\n\n'),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
for (const match of String(text || '').matchAll(JARGON)) {
|
|
111
|
+
const suggestion = JARGON_SUGGESTIONS[match[0].toLowerCase()];
|
|
112
|
+
findings.push({
|
|
113
|
+
rule: 'jargon',
|
|
114
|
+
why: `replace "${match[0]}" with "${suggestion}"`,
|
|
115
|
+
snippet: match[0],
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return findings;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function scanText(text) {
|
|
122
|
+
return [...scanWords(text), ...scanShape(text)];
|
|
123
|
+
}
|
|
124
|
+
|
|
65
125
|
function cleanEmDashes(text) {
|
|
66
126
|
return String(text || '')
|
|
67
127
|
.replace(/\s*\u2014\s*/g, ', ')
|
|
@@ -86,13 +146,14 @@ function titleCanReplaceId(title) {
|
|
|
86
146
|
}
|
|
87
147
|
|
|
88
148
|
function gateForHuman(text, opts = {}) {
|
|
149
|
+
const shapeIssues = scanShape(text);
|
|
89
150
|
let cleaned = cleanEmDashes(text);
|
|
90
151
|
cleaned = dejargon(cleaned, { preserveTickets: true });
|
|
91
152
|
if (titleCanReplaceId(opts.title)) {
|
|
92
153
|
cleaned = cleaned.replace(globalPattern(voicePatterns.rawUlid), '');
|
|
93
154
|
}
|
|
94
155
|
cleaned = cleanAfterRemoval(cleaned);
|
|
95
|
-
const issues =
|
|
156
|
+
const issues = [...scanWords(cleaned), ...shapeIssues];
|
|
96
157
|
return { ok: issues.length === 0, text: cleaned, issues };
|
|
97
158
|
}
|
|
98
159
|
|
package/lib/wiki.js
CHANGED
|
@@ -11,6 +11,13 @@ const LEGACY_WIKI_BRIEFS_SUBDIR = 'syntheses';
|
|
|
11
11
|
const WIKI_SUBDIRS = ['people', 'systems', 'concepts', WIKI_BRIEFS_SUBDIR];
|
|
12
12
|
const WIKI_STATUS_FILE = 'STATUS.md';
|
|
13
13
|
const WIKI_CONTENT_SUBDIRS = WIKI_SUBDIRS.map((subdir) => path.join(WIKI_ROOT, subdir));
|
|
14
|
+
const WIKI_METABOLISM_MARKER = path.join('.atris', 'state', 'wiki.metabolism.json');
|
|
15
|
+
const DEFAULT_WIKI_METABOLISM_BUDGETS = Object.freeze({
|
|
16
|
+
index: 40,
|
|
17
|
+
log: 60,
|
|
18
|
+
folder: 30,
|
|
19
|
+
});
|
|
20
|
+
const CONSOLIDATION_GROWTH_THRESHOLD = 10;
|
|
14
21
|
|
|
15
22
|
function getWikiRoot(mode = 'public') {
|
|
16
23
|
return mode === 'private' ? PRIVATE_WIKI_ROOT : WIKI_ROOT;
|
|
@@ -577,7 +584,122 @@ function isConfidence(value) {
|
|
|
577
584
|
return Number.isFinite(n) && n >= 0 && n <= 1;
|
|
578
585
|
}
|
|
579
586
|
|
|
580
|
-
function
|
|
587
|
+
function countBullets(filePath, topLevelOnly = false) {
|
|
588
|
+
if (!fs.existsSync(filePath)) return 0;
|
|
589
|
+
const pattern = topLevelOnly ? /^-\s+\S/ : /^\s*-\s+\S/;
|
|
590
|
+
return fs.readFileSync(filePath, 'utf8')
|
|
591
|
+
.split('\n')
|
|
592
|
+
.filter((line) => pattern.test(line))
|
|
593
|
+
.length;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function countWikiMetabolismEntries(projectRoot = process.cwd(), mode = 'public') {
|
|
597
|
+
const wikiDir = path.join(projectRoot, getWikiRoot(mode));
|
|
598
|
+
const counts = {
|
|
599
|
+
'index.md': countBullets(path.join(wikiDir, 'index.md')),
|
|
600
|
+
'log.md': countBullets(path.join(wikiDir, 'log.md'), true),
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
for (const subdir of WIKI_SUBDIRS) {
|
|
604
|
+
counts[`${subdir}/`] = walkMarkdownFiles(path.join(wikiDir, subdir)).length;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
return counts;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function readWikiMetabolismMarker(projectRoot = process.cwd()) {
|
|
611
|
+
const markerPath = path.join(projectRoot, WIKI_METABOLISM_MARKER);
|
|
612
|
+
if (!fs.existsSync(markerPath)) return null;
|
|
613
|
+
|
|
614
|
+
try {
|
|
615
|
+
const marker = JSON.parse(fs.readFileSync(markerPath, 'utf8'));
|
|
616
|
+
if (!marker || typeof marker !== 'object' || !marker.entryCountAtConsolidation || typeof marker.entryCountAtConsolidation !== 'object') {
|
|
617
|
+
return null;
|
|
618
|
+
}
|
|
619
|
+
return marker;
|
|
620
|
+
} catch {
|
|
621
|
+
return null;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function writeWikiMetabolismMarker(projectRoot = process.cwd(), entryCounts = {}, lastConsolidatedAt = new Date().toISOString()) {
|
|
626
|
+
const markerPath = path.join(projectRoot, WIKI_METABOLISM_MARKER);
|
|
627
|
+
const marker = {
|
|
628
|
+
lastConsolidatedAt,
|
|
629
|
+
entryCountAtConsolidation: { ...entryCounts },
|
|
630
|
+
};
|
|
631
|
+
fs.mkdirSync(path.dirname(markerPath), { recursive: true });
|
|
632
|
+
fs.writeFileSync(markerPath, `${JSON.stringify(marker, null, 2)}\n`, 'utf8');
|
|
633
|
+
return marker;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function wikiBudgetForSurface(surface, budgets) {
|
|
637
|
+
if (surface === 'index.md') return Number.isFinite(budgets.index) ? budgets.index : DEFAULT_WIKI_METABOLISM_BUDGETS.index;
|
|
638
|
+
if (surface === 'log.md') return Number.isFinite(budgets.log) ? budgets.log : DEFAULT_WIKI_METABOLISM_BUDGETS.log;
|
|
639
|
+
const folder = surface.replace(/\/$/, '');
|
|
640
|
+
if (budgets.folders && Number.isFinite(budgets.folders[folder])) return budgets.folders[folder];
|
|
641
|
+
if (Number.isFinite(budgets[folder])) return budgets[folder];
|
|
642
|
+
return Number.isFinite(budgets.folder) ? budgets.folder : DEFAULT_WIKI_METABOLISM_BUDGETS.folder;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function wikiMetabolismSurfacePath(surface, mode = 'public') {
|
|
646
|
+
return `${getWikiRoot(mode)}/${surface}`;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function findWikiMetabolismFindings(projectRoot = process.cwd(), mode = 'public', budgets = DEFAULT_WIKI_METABOLISM_BUDGETS) {
|
|
650
|
+
const counts = countWikiMetabolismEntries(projectRoot, mode);
|
|
651
|
+
const marker = readWikiMetabolismMarker(projectRoot);
|
|
652
|
+
const findings = [];
|
|
653
|
+
|
|
654
|
+
for (const [surface, count] of Object.entries(counts)) {
|
|
655
|
+
const budget = wikiBudgetForSurface(surface, budgets || {});
|
|
656
|
+
const page = wikiMetabolismSurfacePath(surface, mode);
|
|
657
|
+
if (count > budget) {
|
|
658
|
+
findings.push({
|
|
659
|
+
page,
|
|
660
|
+
surface,
|
|
661
|
+
code: 'over-budget',
|
|
662
|
+
count,
|
|
663
|
+
budget,
|
|
664
|
+
message: `${surface} has ${count} entries, over budget ${budget}`,
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const baseline = Number(marker?.entryCountAtConsolidation?.[surface]);
|
|
669
|
+
const growth = Number.isFinite(baseline) ? count - baseline : 0;
|
|
670
|
+
if (growth >= CONSOLIDATION_GROWTH_THRESHOLD) {
|
|
671
|
+
findings.push({
|
|
672
|
+
page,
|
|
673
|
+
surface,
|
|
674
|
+
code: 'consolidation-due',
|
|
675
|
+
count,
|
|
676
|
+
baselineCount: baseline,
|
|
677
|
+
growth,
|
|
678
|
+
message: `${surface} grew by ${growth} entries since the last consolidation`,
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
return findings;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function wikiMetabolismNudge(projectRoot = process.cwd(), mode = 'public') {
|
|
687
|
+
try {
|
|
688
|
+
const findings = findWikiMetabolismFindings(projectRoot, mode);
|
|
689
|
+
return findings.length > 0
|
|
690
|
+
? 'the wiki has grown a lot since its last pruning; run: atris wiki consolidate'
|
|
691
|
+
: '';
|
|
692
|
+
} catch {
|
|
693
|
+
return '';
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function recordWikiConsolidation(projectRoot = process.cwd(), mode = 'public') {
|
|
698
|
+
const counts = countWikiMetabolismEntries(projectRoot, mode);
|
|
699
|
+
return writeWikiMetabolismMarker(projectRoot, counts);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function validateAgentReadableWikiPages(projectRoot = process.cwd(), mode = 'public', options = {}) {
|
|
581
703
|
const findings = [];
|
|
582
704
|
const pages = readWikiPages(projectRoot, mode);
|
|
583
705
|
const stalePages = new Set(findStaleWikiPages(projectRoot, mode).map((item) => item.page));
|
|
@@ -609,9 +731,12 @@ function validateAgentReadableWikiPages(projectRoot = process.cwd(), mode = 'pub
|
|
|
609
731
|
}
|
|
610
732
|
}
|
|
611
733
|
|
|
734
|
+
findings.push(...findWikiMetabolismFindings(projectRoot, mode, options.budgets));
|
|
735
|
+
|
|
612
736
|
return {
|
|
613
737
|
ok: findings.length === 0,
|
|
614
738
|
pageCount: pages.length,
|
|
739
|
+
surfaceCounts: countWikiMetabolismEntries(projectRoot, mode),
|
|
615
740
|
findingCount: findings.length,
|
|
616
741
|
findings,
|
|
617
742
|
};
|
|
@@ -802,18 +927,29 @@ Output:
|
|
|
802
927
|
- No hedging if the wiki is stale or messy`;
|
|
803
928
|
}
|
|
804
929
|
|
|
930
|
+
function buildConsolidatePrompt(mode = 'public') {
|
|
931
|
+
const wikiRoot = getWikiRoot(mode);
|
|
932
|
+
return `Atris wiki consolidation pass
|
|
933
|
+
|
|
934
|
+
Read ${wikiRoot}/index.md, ${wikiRoot}/log.md, and every page under the wiki subfolders.
|
|
935
|
+
|
|
936
|
+
Pruning rules:
|
|
937
|
+
- UPDATE facts that changed.
|
|
938
|
+
- DELETE stale facts, duplicates, and mechanics.
|
|
939
|
+
- ADD nothing new.
|
|
940
|
+
|
|
941
|
+
Keep rules:
|
|
942
|
+
- Entries marked as user-stated conventions or explicit remember-this survive.
|
|
943
|
+
- Every kept entry keeps its provenance as a source link or date.
|
|
944
|
+
|
|
945
|
+
Consolidate overlapping entries, preserve the strongest source-backed version, and leave index.md and page links consistent.`;
|
|
946
|
+
}
|
|
947
|
+
|
|
805
948
|
module.exports = {
|
|
806
949
|
WIKI_ROOT,
|
|
807
|
-
CONTEXT_ROOT,
|
|
808
950
|
PRIVATE_WIKI_ROOT,
|
|
809
|
-
|
|
810
|
-
LEGACY_WIKI_ROOT,
|
|
811
|
-
WIKI_SUBDIRS,
|
|
812
|
-
WIKI_CONTENT_SUBDIRS,
|
|
813
|
-
WIKI_SCHEMA: buildWikiSchema(),
|
|
814
|
-
WIKI_STATUS_FILE,
|
|
951
|
+
WIKI_METABOLISM_MARKER,
|
|
815
952
|
getWikiRoot,
|
|
816
|
-
getContextRoot,
|
|
817
953
|
ensureWikiScaffold,
|
|
818
954
|
ensureContextScaffold,
|
|
819
955
|
findLocalWikiDir,
|
|
@@ -822,6 +958,12 @@ module.exports = {
|
|
|
822
958
|
readWikiPages,
|
|
823
959
|
findStaleWikiPages,
|
|
824
960
|
findWikiOrphans,
|
|
961
|
+
countWikiMetabolismEntries,
|
|
962
|
+
readWikiMetabolismMarker,
|
|
963
|
+
writeWikiMetabolismMarker,
|
|
964
|
+
findWikiMetabolismFindings,
|
|
965
|
+
wikiMetabolismNudge,
|
|
966
|
+
recordWikiConsolidation,
|
|
825
967
|
validateAgentReadableWikiPages,
|
|
826
968
|
findSuggestedSources,
|
|
827
969
|
stageWikiIngest,
|
|
@@ -830,4 +972,5 @@ module.exports = {
|
|
|
830
972
|
buildIngestPrompt,
|
|
831
973
|
buildQueryPrompt,
|
|
832
974
|
buildLintPrompt,
|
|
975
|
+
buildConsolidatePrompt,
|
|
833
976
|
};
|
package/lib/wish-audit.js
CHANGED
|
@@ -490,6 +490,24 @@ function resolveRole(role, root) {
|
|
|
490
490
|
return resolver(role, root);
|
|
491
491
|
}
|
|
492
492
|
|
|
493
|
+
// Route-time validator policy: the routing decision must not depend on which
|
|
494
|
+
// validator binaries happen to exist on this machine. Prefer a ready distinct
|
|
495
|
+
// validator, else fall back to the registry's policy order; the dispatch
|
|
496
|
+
// flight reports an honest validator failure if the engine is still missing
|
|
497
|
+
// when validation actually runs.
|
|
498
|
+
function policyValidator(root, excludeId = '') {
|
|
499
|
+
try {
|
|
500
|
+
const ranked = engineRegistry.engineRegistryView(root)
|
|
501
|
+
.filter((engine) => engine.roles.includes('validator'))
|
|
502
|
+
.filter((engine) => engine.id !== String(excludeId || ''))
|
|
503
|
+
.sort((a, b) => Number(a.fallback_order) - Number(b.fallback_order)
|
|
504
|
+
|| String(a.id).localeCompare(String(b.id)));
|
|
505
|
+
return ranked.find((engine) => engine.health && engine.health.status === 'ready') || ranked[0] || null;
|
|
506
|
+
} catch {
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
493
511
|
function resolveExecutor(root, engineOverride = '') {
|
|
494
512
|
if (engineOverride) {
|
|
495
513
|
return engineRegistry.resolveEngineForRoleWithPreference('executor', root, engineOverride);
|
|
@@ -1239,7 +1257,8 @@ function auditWish(text, root = process.cwd(), options = {}) {
|
|
|
1239
1257
|
const question = 'Which working reviewer should validate it?';
|
|
1240
1258
|
const answered = answeredRepeatedQuestionValue(text, root, question, 'reviewer');
|
|
1241
1259
|
if (answered) validator = { id: slugify(answered), name: answered };
|
|
1242
|
-
else
|
|
1260
|
+
else if (options.assumeValidator) validator = policyValidator(root, executor && executor.id);
|
|
1261
|
+
if (!validator) questions.push(question);
|
|
1243
1262
|
}
|
|
1244
1263
|
|
|
1245
1264
|
let missing = missingNamedInputs(originalText, root);
|
|
@@ -1346,9 +1365,6 @@ function verifyOutcomeText(planOrCommand) {
|
|
|
1346
1365
|
}
|
|
1347
1366
|
|
|
1348
1367
|
module.exports = {
|
|
1349
|
-
BUDGET_LABELS,
|
|
1350
|
-
JOURNAL_RESULT_TEXT,
|
|
1351
|
-
TEST_VERIFY_COMMAND,
|
|
1352
1368
|
WAITING_INPUT_STATUSES,
|
|
1353
1369
|
WISH_MISSION_RUNNER,
|
|
1354
1370
|
WISH_SWEEP_LIMIT,
|
|
@@ -1357,8 +1373,6 @@ module.exports = {
|
|
|
1357
1373
|
deriveVerifyPlan,
|
|
1358
1374
|
inferBudgetTier,
|
|
1359
1375
|
missingNamedInputs,
|
|
1360
|
-
namedTestFiles,
|
|
1361
|
-
PATH_SLOT_QUESTION,
|
|
1362
1376
|
quoteText,
|
|
1363
1377
|
sharesMeaningfulWords,
|
|
1364
1378
|
validateEngineOverride,
|