atris 3.43.0 → 3.44.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/atris/skills/design/SKILL.md +7 -1
- package/atris/skills/engines/SKILL.md +44 -13
- package/atris/team/customer-lead/MEMBER.md +45 -0
- package/atris/team/customer-lead/SOUL.md +33 -0
- package/atris/team/customer-lead/START_HERE.md +7 -0
- package/atris/team/customer-lead/skills/customer-commitments/SKILL.md +45 -0
- package/atris/team/improver/MEMBER.md +33 -0
- package/bin/atris.js +36 -3
- package/commands/autoland.js +15 -1
- package/commands/caretaker.js +303 -0
- package/commands/clean.js +76 -0
- package/commands/engine-watch.js +212 -0
- package/commands/engine.js +99 -11
- package/commands/founder.js +304 -0
- package/commands/human-missions.js +844 -0
- package/commands/init.js +16 -7
- package/commands/mission.js +124 -69
- package/commands/slop.js +34 -3
- package/commands/task.js +51 -4
- package/commands/team.js +329 -13
- package/commands/verify.js +99 -6
- package/commands/worktree.js +119 -4
- package/lib/auto-accept-certified.js +302 -0
- package/lib/cloud-mission.js +59 -2
- package/lib/conductor-artifacts.js +1 -1
- package/lib/dispatch-scout.js +383 -0
- package/lib/engine-ask.js +645 -0
- package/lib/engine-job-lifecycle.js +65 -0
- package/lib/engine-receipt-sweep.js +98 -0
- package/lib/engine-registry.js +2 -2
- package/lib/engine-validate.js +374 -0
- package/lib/fleet.js +459 -106
- package/lib/known-commands.js +2 -2
- package/lib/member-alive.js +2 -2
- package/lib/policy-lessons.js +70 -0
- package/lib/receipt-evidence.js +56 -1
- package/lib/runner-command.js +1 -1
- package/lib/secret-gateway.js +588 -0
- package/lib/team-presence.js +13 -1
- package/lib/voice-gate.js +6 -0
- package/lib/wish-audit.js +5 -205
- package/lib/wish-delegate.js +5 -2
- package/package.json +6 -1
package/commands/init.js
CHANGED
|
@@ -602,9 +602,12 @@ function initAtris() {
|
|
|
602
602
|
|
|
603
603
|
|
|
604
604
|
// Copy team members (MEMBER.md format — directory per member with skills/tools/context)
|
|
605
|
-
const starterMembers = ['navigator', 'executor', 'validator', 'mission-lead', 'improver'];
|
|
605
|
+
const starterMembers = ['navigator', 'executor', 'validator', 'mission-lead', 'improver', 'customer-lead'];
|
|
606
|
+
const starterMemberFiles = ['MEMBER.md', 'SOUL.md', 'START_HERE.md'];
|
|
607
|
+
const starterMemberDirs = ['skills', 'tools', 'context'];
|
|
606
608
|
starterMembers.forEach(name => {
|
|
607
|
-
const
|
|
609
|
+
const sourceMemberDir = path.join(__dirname, '..', 'atris', 'team', name);
|
|
610
|
+
const sourceFile = path.join(sourceMemberDir, 'MEMBER.md');
|
|
608
611
|
const targetMemberDir = path.join(teamDir, name);
|
|
609
612
|
const targetFile = path.join(targetMemberDir, 'MEMBER.md');
|
|
610
613
|
const legacyFile = path.join(teamDir, `${name}.md`);
|
|
@@ -614,11 +617,17 @@ function initAtris() {
|
|
|
614
617
|
|
|
615
618
|
if (fs.existsSync(sourceFile)) {
|
|
616
619
|
fs.mkdirSync(targetMemberDir, { recursive: true });
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
620
|
+
starterMemberDirs.forEach(dirName => {
|
|
621
|
+
const sourceDir = path.join(sourceMemberDir, dirName);
|
|
622
|
+
const targetDir = path.join(targetMemberDir, dirName);
|
|
623
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
624
|
+
if (fs.existsSync(sourceDir)) fs.cpSync(sourceDir, targetDir, { recursive: true });
|
|
625
|
+
});
|
|
626
|
+
starterMemberFiles.forEach(fileName => {
|
|
627
|
+
const source = path.join(sourceMemberDir, fileName);
|
|
628
|
+
if (fs.existsSync(source)) fs.copyFileSync(source, path.join(targetMemberDir, fileName));
|
|
629
|
+
});
|
|
630
|
+
markReady('team', name, `✓ Created team/${name}/ (identity + skills/ + tools/ + context/)`);
|
|
622
631
|
}
|
|
623
632
|
});
|
|
624
633
|
|
package/commands/mission.js
CHANGED
|
@@ -46,6 +46,7 @@ const {
|
|
|
46
46
|
renderEmailLine,
|
|
47
47
|
renderMorningCardRow,
|
|
48
48
|
} = require('../lib/receipt-block');
|
|
49
|
+
const { findCachedMissionStepReceipt } = require('../lib/receipt-evidence');
|
|
49
50
|
const {
|
|
50
51
|
pruneRuns,
|
|
51
52
|
runsPruneLines,
|
|
@@ -79,7 +80,7 @@ const {
|
|
|
79
80
|
missionVerifierTimeoutMs,
|
|
80
81
|
resolveDefaultVerifier,
|
|
81
82
|
} = require('../lib/default-verifier');
|
|
82
|
-
const { redirectToWorkspaceRoot } = require('../lib/mission-root');
|
|
83
|
+
const { resolveWorkspaceRoot, redirectToWorkspaceRoot } = require('../lib/mission-root');
|
|
83
84
|
const { readJson, writeJson } = require('../lib/json-file');
|
|
84
85
|
const {
|
|
85
86
|
normalizeHumanAsks,
|
|
@@ -908,6 +909,11 @@ function loadMissionMap(root = process.cwd()) {
|
|
|
908
909
|
return map;
|
|
909
910
|
}
|
|
910
911
|
|
|
912
|
+
function hasLocalMissionState(root = process.cwd()) {
|
|
913
|
+
return readJsonLines(statePaths(root).missionsJsonl)
|
|
914
|
+
.some((mission) => mission && mission.id && mission.cloud !== true);
|
|
915
|
+
}
|
|
916
|
+
|
|
911
917
|
function terminalNextAction(status) {
|
|
912
918
|
if (status === 'complete') return 'mission complete';
|
|
913
919
|
if (status === 'stopped') return 'mission stopped';
|
|
@@ -9151,6 +9157,8 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
9151
9157
|
const tickStart = stampIso();
|
|
9152
9158
|
const tickWorktreeBefore = gitWorktreeSnapshot(cwd);
|
|
9153
9159
|
let result = { status: 'skipped', reason: 'unknown', tick_index: tickIdx, ran: false, started_at: tickStart };
|
|
9160
|
+
let cachedStep = findCachedMissionStepReceipt(cwd, { missionId: mission.id, tickIndex: tickIdx });
|
|
9161
|
+
let cachedVerifierResult = cachedStep ? cachedStep.verifier_result : null;
|
|
9154
9162
|
const tickSelection = resolveMissionTickRunner(runtimeMission, cwd);
|
|
9155
9163
|
const tickRuntimeMission = tickSelection.mission;
|
|
9156
9164
|
const tickRunnerName = String(tickRuntimeMission.runner || '').trim().toLowerCase();
|
|
@@ -9182,7 +9190,17 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
9182
9190
|
}
|
|
9183
9191
|
|
|
9184
9192
|
// Active-hours gate
|
|
9185
|
-
if (
|
|
9193
|
+
if (cachedStep) {
|
|
9194
|
+
result = {
|
|
9195
|
+
...result,
|
|
9196
|
+
...cachedStep.tick,
|
|
9197
|
+
tick_index: tickIdx,
|
|
9198
|
+
cached: true,
|
|
9199
|
+
reason: 'receipt-cache-hit',
|
|
9200
|
+
ran: cachedStep.tick.ran !== false,
|
|
9201
|
+
status: cachedStep.tick.status || 'ran',
|
|
9202
|
+
};
|
|
9203
|
+
} else if (engineBackedTick && !tickEngineId) {
|
|
9186
9204
|
result = { ...result, status: 'errored', reason: 'no-ready-engine' };
|
|
9187
9205
|
} else if (!isWithinActiveHours(mission.active_hours)) {
|
|
9188
9206
|
result = { ...result, status: 'skipped', reason: 'quiet-hours' };
|
|
@@ -9399,7 +9417,7 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
9399
9417
|
}
|
|
9400
9418
|
}
|
|
9401
9419
|
|
|
9402
|
-
if (tickEngineId) {
|
|
9420
|
+
if (tickEngineId && !cachedStep) {
|
|
9403
9421
|
result.rate_limit_info = lastRateLimit;
|
|
9404
9422
|
const engineHealth = recordMissionEngineTickOutcome(tickEngineId, result, cwd);
|
|
9405
9423
|
if (engineHealth) result.engine_health = engineHealth.health;
|
|
@@ -9415,12 +9433,18 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
9415
9433
|
tickIdx,
|
|
9416
9434
|
frozen,
|
|
9417
9435
|
};
|
|
9418
|
-
|
|
9419
|
-
|
|
9420
|
-
|
|
9436
|
+
let verifierResult = null;
|
|
9437
|
+
if (cachedStep) {
|
|
9438
|
+
verifierResult = cachedVerifierResult;
|
|
9439
|
+
if (verifierResult) result.verifier_passed = verifierResult.passed;
|
|
9440
|
+
} else {
|
|
9441
|
+
await verifyMissionRunTickPhase(context);
|
|
9442
|
+
result = context.currentTick.result;
|
|
9443
|
+
verifierResult = context.currentTick.verifierResult;
|
|
9444
|
+
}
|
|
9421
9445
|
pauseReason = context.pauseReason;
|
|
9422
9446
|
context.currentTick = null;
|
|
9423
|
-
let receiptPath = null;
|
|
9447
|
+
let receiptPath = cachedStep ? cachedStep.receipt_path : null;
|
|
9424
9448
|
|
|
9425
9449
|
// Review-lane drain: always-on loops sweep the agent-safe review actions
|
|
9426
9450
|
// each tick so proof-backed work reaches certified on cadence with zero
|
|
@@ -9430,27 +9454,32 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
9430
9454
|
? { skipped: true, reason: 'no-drain-flag' }
|
|
9431
9455
|
: runReviewLaneDrain(cwd);
|
|
9432
9456
|
}
|
|
9433
|
-
const tickWorktree =
|
|
9457
|
+
const tickWorktree = cachedStep?.tick?.worktree
|
|
9458
|
+
|| worktreeReceipt(tickWorktreeBefore, gitWorktreeSnapshot(cwd), { verifier: frozen.verifier, baseline: runWorktreeBaseline });
|
|
9434
9459
|
|
|
9435
9460
|
// Layer classification needs the receipt text AND the worktree receipt, so it
|
|
9436
9461
|
// runs here — after both exist — covering the claude and atris2 branches alike.
|
|
9437
|
-
|
|
9438
|
-
|
|
9439
|
-
|
|
9440
|
-
|
|
9462
|
+
if (!(cachedStep && cachedStep.tick && cachedStep.tick.layer)) {
|
|
9463
|
+
const tickReceiptText = result.atris2?.receipt_text || result.claude?.receipt_text || result.drill?.receipt_text || '';
|
|
9464
|
+
const layerInfo = extractLayerFromReceiptText(tickReceiptText, tickWorktree?.new_since_baseline_sample);
|
|
9465
|
+
result.layer = layerInfo.layer;
|
|
9466
|
+
result.layer_source = layerInfo.source;
|
|
9467
|
+
}
|
|
9441
9468
|
|
|
9442
9469
|
// Persist tick to mission state + write structured receipt
|
|
9443
9470
|
const finishedAt = stampIso();
|
|
9444
|
-
const tickRecord = { ...result, started_at: tickStart, finished_at: finishedAt, worktree: tickWorktree };
|
|
9471
|
+
const tickRecord = { ...result, started_at: result.started_at || tickStart, finished_at: result.finished_at || finishedAt, worktree: tickWorktree };
|
|
9445
9472
|
ticks.push(tickRecord);
|
|
9446
|
-
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
|
|
9453
|
-
|
|
9473
|
+
if (!receiptPath) {
|
|
9474
|
+
receiptPath = writeReceipt(runtimeMission, {
|
|
9475
|
+
kind: 'mission_run_tick',
|
|
9476
|
+
tick: tickRecord,
|
|
9477
|
+
frozen,
|
|
9478
|
+
verifier_result: verifierResult,
|
|
9479
|
+
rate_limit_info: lastRateLimit,
|
|
9480
|
+
worktree: tickWorktree,
|
|
9481
|
+
});
|
|
9482
|
+
}
|
|
9454
9483
|
|
|
9455
9484
|
const xpReadyAction = missionXpReadyAction(mission, receiptPath);
|
|
9456
9485
|
const budgetRemainingSeconds = missionFullBudgetRemainingSeconds(mission);
|
|
@@ -9938,56 +9967,72 @@ function tickMission(args) {
|
|
|
9938
9967
|
const tickStart = stampIso();
|
|
9939
9968
|
const lastTickIndex = Number(mission.last_tick_index || 0);
|
|
9940
9969
|
const tickIdx = lastTickIndex + 1;
|
|
9941
|
-
|
|
9942
|
-
|
|
9943
|
-
|
|
9944
|
-
|
|
9970
|
+
// Interrupt between writeReceipt and saveMission leaves a completed receipt
|
|
9971
|
+
// for this tick_index while last_tick_index stays behind. Reuse it on resume
|
|
9972
|
+
// instead of re-deriving the step from scratch.
|
|
9973
|
+
const cachedStep = findCachedMissionStepReceipt(cwd, { missionId: mission.id, tickIndex: tickIdx });
|
|
9945
9974
|
const effectiveVerifier = effectiveMissionVerifier(mission);
|
|
9946
9975
|
const verifierCommand = verify
|
|
9947
9976
|
? String(verifyOverride || effectiveVerifier || '').trim()
|
|
9948
9977
|
: '';
|
|
9949
|
-
if (verifierCommand) assertMissionVerifier(verifierCommand, asJson);
|
|
9978
|
+
if (!cachedStep && verifierCommand) assertMissionVerifier(verifierCommand, asJson);
|
|
9950
9979
|
|
|
9951
9980
|
let verifierResult = null;
|
|
9952
|
-
|
|
9953
|
-
|
|
9954
|
-
|
|
9955
|
-
|
|
9956
|
-
|
|
9957
|
-
|
|
9958
|
-
|
|
9959
|
-
|
|
9960
|
-
|
|
9961
|
-
|
|
9962
|
-
|
|
9963
|
-
|
|
9964
|
-
|
|
9965
|
-
|
|
9966
|
-
|
|
9967
|
-
|
|
9968
|
-
|
|
9969
|
-
|
|
9970
|
-
|
|
9971
|
-
|
|
9972
|
-
|
|
9973
|
-
|
|
9974
|
-
|
|
9975
|
-
|
|
9976
|
-
|
|
9977
|
-
|
|
9978
|
-
|
|
9979
|
-
|
|
9980
|
-
|
|
9981
|
-
|
|
9982
|
-
|
|
9983
|
-
lane: mission.lane || 'workspace',
|
|
9981
|
+
let tickRecord = null;
|
|
9982
|
+
let receiptPath = null;
|
|
9983
|
+
if (cachedStep) {
|
|
9984
|
+
tickRecord = {
|
|
9985
|
+
...cachedStep.tick,
|
|
9986
|
+
cached: true,
|
|
9987
|
+
reason: 'receipt-cache-hit',
|
|
9988
|
+
};
|
|
9989
|
+
verifierResult = cachedStep.verifier_result;
|
|
9990
|
+
receiptPath = cachedStep.receipt_path;
|
|
9991
|
+
} else {
|
|
9992
|
+
const tickWorktreeBefore = gitWorktreeSnapshot(cwd);
|
|
9993
|
+
const worktreeBaseline = loadMissionWorktreeBaseline(mission.id, cwd);
|
|
9994
|
+
const protectedLaneGuard = inspectMissionTickProtectedDiff(mission, tickWorktreeBefore, cwd);
|
|
9995
|
+
|
|
9996
|
+
if (protectedLaneGuard.allowed && verify && verifierCommand) {
|
|
9997
|
+
verifierResult = runVerifier(verifierCommand);
|
|
9998
|
+
}
|
|
9999
|
+
const tickWorktree = worktreeReceipt(tickWorktreeBefore, gitWorktreeSnapshot(cwd), { verifier: verifierCommand || effectiveVerifier, baseline: worktreeBaseline });
|
|
10000
|
+
|
|
10001
|
+
// Same layer classification as the run-tick path; manual ticks carry their
|
|
10002
|
+
// receipt text in --summary.
|
|
10003
|
+
const layerInfo = extractLayerFromReceiptText(summary || '', tickWorktree?.new_since_baseline_sample);
|
|
10004
|
+
const guardPauseReason = protectedLaneGuard.unreadable
|
|
10005
|
+
? 'mission-diff-unreadable'
|
|
10006
|
+
: 'protected-lane-review';
|
|
10007
|
+
tickRecord = {
|
|
10008
|
+
status: protectedLaneGuard.allowed ? 'ran' : protectedLaneGuard.status,
|
|
10009
|
+
reason: protectedLaneGuard.allowed ? 'tick-recorded' : guardPauseReason,
|
|
10010
|
+
tick_index: tickIdx,
|
|
10011
|
+
ran: protectedLaneGuard.allowed,
|
|
9984
10012
|
started_at: tickStart,
|
|
9985
|
-
|
|
9986
|
-
|
|
9987
|
-
|
|
9988
|
-
|
|
9989
|
-
|
|
9990
|
-
|
|
10013
|
+
claude: { skipped: true, reason: 'orchestrator-is-caller-session' },
|
|
10014
|
+
summary: summary || null,
|
|
10015
|
+
layer: layerInfo.layer,
|
|
10016
|
+
layer_source: layerInfo.source,
|
|
10017
|
+
verifier_passed: verifierResult ? !!verifierResult.passed : null,
|
|
10018
|
+
protected_lane_guard: protectedLaneGuard,
|
|
10019
|
+
finished_at: stampIso(),
|
|
10020
|
+
worktree: tickWorktree,
|
|
10021
|
+
};
|
|
10022
|
+
receiptPath = writeReceipt(mission, {
|
|
10023
|
+
kind: 'mission_tick',
|
|
10024
|
+
tick: tickRecord,
|
|
10025
|
+
frozen: {
|
|
10026
|
+
verifier: verifierCommand || effectiveVerifier || '',
|
|
10027
|
+
lane: mission.lane || 'workspace',
|
|
10028
|
+
started_at: tickStart,
|
|
10029
|
+
},
|
|
10030
|
+
verifier_result: verifierResult,
|
|
10031
|
+
protected_lane_guard: protectedLaneGuard,
|
|
10032
|
+
rate_limit_info: null,
|
|
10033
|
+
worktree: tickWorktree,
|
|
10034
|
+
});
|
|
10035
|
+
}
|
|
9991
10036
|
|
|
9992
10037
|
let status = 'running';
|
|
9993
10038
|
let nextAction = (verifierCommand || effectiveVerifier)
|
|
@@ -9995,7 +10040,8 @@ function tickMission(args) {
|
|
|
9995
10040
|
: (mission.always_on && missionTaskSpine(mission)?.has_task
|
|
9996
10041
|
? nextCandidateTickAction(mission)
|
|
9997
10042
|
: 'attach task, verifier, or proof');
|
|
9998
|
-
const nextGoalChain = advanceMissionGoalChain(mission.goal_chain, summary, verifierResult);
|
|
10043
|
+
const nextGoalChain = advanceMissionGoalChain(mission.goal_chain, summary || tickRecord.summary, verifierResult);
|
|
10044
|
+
const protectedLaneGuard = tickRecord.protected_lane_guard || { allowed: true };
|
|
9999
10045
|
if (!protectedLaneGuard.allowed) {
|
|
10000
10046
|
status = 'paused';
|
|
10001
10047
|
nextAction = protectedLaneGuard.unreadable
|
|
@@ -10072,9 +10118,10 @@ function tickMission(args) {
|
|
|
10072
10118
|
const atrisGoalState = refreshAtrisGoalController(process.cwd(), { missionId: outputMission.id });
|
|
10073
10119
|
const codexGoalState = refreshCodexGoalController(process.cwd());
|
|
10074
10120
|
printJsonOrText(
|
|
10075
|
-
{ ok: true, action: 'mission_tick', mission: outputMission, tick: tickRecord, verifier_result: verifierResult, blocker, receipt_path: receiptPath, log_path: logPath, atris_goal_state: atrisGoalState, codex_goal_state: codexGoalState, continuation_goal: continuationGoal, operator_summary_warning: operatorSummaryWarning },
|
|
10121
|
+
{ ok: true, action: 'mission_tick', mission: outputMission, tick: tickRecord, verifier_result: verifierResult, blocker, receipt_path: receiptPath, log_path: logPath, atris_goal_state: atrisGoalState, codex_goal_state: codexGoalState, continuation_goal: continuationGoal, operator_summary_warning: operatorSummaryWarning, cached: Boolean(tickRecord.cached) },
|
|
10076
10122
|
[
|
|
10077
|
-
...
|
|
10123
|
+
...(tickRecord.cached ? [`Reused receipt for tick ${tickIdx}: ${receiptPath}`] : []),
|
|
10124
|
+
...missionTickResultLines(outputMission, tickIdx, receiptPath, verifierResult, summary || tickRecord.summary),
|
|
10078
10125
|
...(missionBlockerReceiptLine(blocker) ? [missionBlockerReceiptLine(blocker)] : []),
|
|
10079
10126
|
...(continuationGoal?.mission ? [`Next goal: ${continuationGoal.mission.objective}`] : []),
|
|
10080
10127
|
],
|
|
@@ -11026,8 +11073,16 @@ function answerMissionHumanAsk(ref, askIndex, answer, note = '') {
|
|
|
11026
11073
|
}
|
|
11027
11074
|
|
|
11028
11075
|
function missionCommand(args) {
|
|
11029
|
-
const
|
|
11030
|
-
const
|
|
11076
|
+
const simpleCardArgs = args.filter((value) => value !== '--json');
|
|
11077
|
+
const isBareMission = simpleCardArgs.length === 0;
|
|
11078
|
+
if (args[0] === 'answer') {
|
|
11079
|
+
return require('./human-missions').answerCommand(args.slice(1));
|
|
11080
|
+
}
|
|
11081
|
+
if (isBareMission && !hasLocalMissionState(resolveWorkspaceRoot())) {
|
|
11082
|
+
return require('./human-missions').currentMissionCommand(args);
|
|
11083
|
+
}
|
|
11084
|
+
const subcommand = isBareMission ? 'status' : (args[0] || 'status');
|
|
11085
|
+
const rest = isBareMission ? args : args.slice(1);
|
|
11031
11086
|
// Every mission verb resolves its state store from process.cwd(). Running one
|
|
11032
11087
|
// from a subdirectory used to create a nested .atris store the fleet never
|
|
11033
11088
|
// reads (proven footgun: a nested .atris appeared under
|
package/commands/slop.js
CHANGED
|
@@ -134,10 +134,12 @@ function addProjectRule(rule, root = process.cwd()) {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
// Map<absFile, Set<changedLineNumber>> from the working-tree (or --cached) git diff.
|
|
137
|
-
function gitChangedLines(staged, cwd = process.cwd()) {
|
|
137
|
+
function gitChangedLines(staged, cwd = process.cwd(), range = null) {
|
|
138
138
|
const map = new Map();
|
|
139
139
|
let out;
|
|
140
|
-
try {
|
|
140
|
+
try {
|
|
141
|
+
out = execFileSync('git', ['diff', '--unified=0', ...(staged ? ['--cached'] : []), ...(range ? [range] : [])], { encoding: 'utf8', cwd });
|
|
142
|
+
}
|
|
141
143
|
catch { return map; }
|
|
142
144
|
let cur = null;
|
|
143
145
|
for (const line of out.split('\n')) {
|
|
@@ -239,6 +241,35 @@ function scanFile(file, rules = RULES, pairRules = PAIR_RULES) {
|
|
|
239
241
|
return findings;
|
|
240
242
|
}
|
|
241
243
|
|
|
244
|
+
// Programmatic scanner for landing gates. Callers provide the exact candidate
|
|
245
|
+
// paths, so this never widens into a repo scan. An optional changed-lines map
|
|
246
|
+
// keeps branch-diff gates on the lines the candidate actually added or edited.
|
|
247
|
+
function scanPaths(targets, options = {}) {
|
|
248
|
+
const root = path.resolve(options.root || process.cwd());
|
|
249
|
+
const rules = options.rules || RULES.concat(loadProjectRules(root));
|
|
250
|
+
const pairRules = options.pairRules || PAIR_RULES;
|
|
251
|
+
const changedLines = options.changedLines instanceof Map ? options.changedLines : null;
|
|
252
|
+
const seen = new Set();
|
|
253
|
+
const files = [];
|
|
254
|
+
for (const target of Array.isArray(targets) ? targets : [targets]) {
|
|
255
|
+
if (!target) continue;
|
|
256
|
+
for (const file of walk(path.resolve(root, target), [])) {
|
|
257
|
+
const absolute = path.resolve(file);
|
|
258
|
+
if (seen.has(absolute)) continue;
|
|
259
|
+
seen.add(absolute);
|
|
260
|
+
files.push(absolute);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
let findings = files.flatMap((file) => scanFile(file, rules, pairRules));
|
|
264
|
+
if (changedLines) {
|
|
265
|
+
findings = findings.filter((finding) => {
|
|
266
|
+
const lines = changedLines.get(path.resolve(finding.file));
|
|
267
|
+
return lines && lines.has(finding.line);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
return { root, files, findings };
|
|
271
|
+
}
|
|
272
|
+
|
|
242
273
|
function detect(argv) {
|
|
243
274
|
const json = argv.includes('--json');
|
|
244
275
|
const quiet = argv.includes('--quiet');
|
|
@@ -583,4 +614,4 @@ function slopCommand(argv) {
|
|
|
583
614
|
return 0;
|
|
584
615
|
}
|
|
585
616
|
|
|
586
|
-
module.exports = { slopCommand, detect, scanFile, RULES, PAIR_RULES, loadProjectRules, addProjectRule, gitChangedLines, applyFixes, installHook, findDeadCode, findOrphanedExports, listJsFiles };
|
|
617
|
+
module.exports = { slopCommand, detect, scanFile, scanPaths, RULES, PAIR_RULES, loadProjectRules, addProjectRule, gitChangedLines, applyFixes, installHook, findDeadCode, findOrphanedExports, listJsFiles };
|
package/commands/task.js
CHANGED
|
@@ -11,6 +11,7 @@ const os = require('os');
|
|
|
11
11
|
const { hasFlag } = require('../lib/arg-parser');
|
|
12
12
|
const { taskProofState } = require('../lib/task-proof');
|
|
13
13
|
const {
|
|
14
|
+
candidatePolicyGate,
|
|
14
15
|
evaluateAutoAccept,
|
|
15
16
|
isAgentCertified,
|
|
16
17
|
isAutoCertifyVerifyCommandAllowed,
|
|
@@ -443,6 +444,21 @@ function failTask(label, reason, detail, exitCode = 2) {
|
|
|
443
444
|
process.exit(exitCode);
|
|
444
445
|
}
|
|
445
446
|
|
|
447
|
+
function refuseCandidatePolicyGate(label, gate) {
|
|
448
|
+
if (jsonModeActive()) {
|
|
449
|
+
printJson({ ok: false, command: label, ...gate });
|
|
450
|
+
} else {
|
|
451
|
+
console.error(`${label}: ${gate.reason}: ${gate.message}`);
|
|
452
|
+
if (Array.isArray(gate.offenders)) {
|
|
453
|
+
gate.offenders.forEach((offender) => console.error(` ${offender}`));
|
|
454
|
+
}
|
|
455
|
+
if (Array.isArray(gate.lesson_ids) && gate.lesson_ids.length) {
|
|
456
|
+
console.error(` lessons: ${gate.lesson_ids.join(', ')}`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
process.exit(1);
|
|
460
|
+
}
|
|
461
|
+
|
|
446
462
|
function proofFlagValue(args) {
|
|
447
463
|
const proof = flag(args, '--proof');
|
|
448
464
|
return typeof proof === 'string' ? proof.trim() : '';
|
|
@@ -5254,6 +5270,12 @@ function autoAcceptCertifiedSmallReviews(taskDb, db, projection) {
|
|
|
5254
5270
|
results.push({ ...evaluation, action: 'queued', task_id: task?.id || item.id || null });
|
|
5255
5271
|
continue;
|
|
5256
5272
|
}
|
|
5273
|
+
const policyGate = candidatePolicyGate(task, { executeDetectors: true });
|
|
5274
|
+
if (!policyGate.ok) {
|
|
5275
|
+
results.push({ ...policyGate, action: 'queued', task_id: task?.id || item.id || null });
|
|
5276
|
+
continue;
|
|
5277
|
+
}
|
|
5278
|
+
evaluation.candidate_gate = policyGate.gate;
|
|
5257
5279
|
const accepted = acceptReviewTask(taskDb, db, task.id, {
|
|
5258
5280
|
actor: REVIEW_AUTO_ACCEPT_ACTOR,
|
|
5259
5281
|
proof: evaluation.proof,
|
|
@@ -9436,6 +9458,16 @@ function cmdReady(args) {
|
|
|
9436
9458
|
if (missionXpIssue) {
|
|
9437
9459
|
failTask('atris task ready', MISSION_XP_END_TO_END_REASON, missionXpIssue);
|
|
9438
9460
|
}
|
|
9461
|
+
const readyPolicyTask = {
|
|
9462
|
+
...beforeTask,
|
|
9463
|
+
workspace_root: process.cwd(),
|
|
9464
|
+
metadata: {
|
|
9465
|
+
...(beforeTask && beforeTask.metadata || {}),
|
|
9466
|
+
...(resultFields.files ? { changed_files: resultFields.files } : {}),
|
|
9467
|
+
},
|
|
9468
|
+
};
|
|
9469
|
+
const readyPolicyGate = candidatePolicyGate(readyPolicyTask, { executeDetectors: true });
|
|
9470
|
+
if (!readyPolicyGate.ok) refuseCandidatePolicyGate('atris task ready', readyPolicyGate);
|
|
9439
9471
|
const resultTrace = buildAutomaticResultTrace(taskDb, db, taskId, {
|
|
9440
9472
|
actor,
|
|
9441
9473
|
proof: String(proof),
|
|
@@ -9497,12 +9529,12 @@ function cmdReady(args) {
|
|
|
9497
9529
|
handoff.codex_prompt = reviewChat.codex_prompt;
|
|
9498
9530
|
handoff.verification_focus = reviewChat.verification_focus;
|
|
9499
9531
|
}
|
|
9500
|
-
// Mined
|
|
9501
|
-
//
|
|
9502
|
-
// the task stalls in the review lane. Advisory only — never blocks ready.
|
|
9532
|
+
// Mined proof lessons without runnable detectors remain coaching. Promoted,
|
|
9533
|
+
// path-scoped detector lessons already passed the hard gate above.
|
|
9503
9534
|
const { readPolicyLessons, policyHintsForProof } = require('../lib/policy-lessons');
|
|
9504
9535
|
const policyHints = policyHintsForProof(String(proof), readPolicyLessons(taskDb.workspaceRoot()), taskDb.workspaceRoot());
|
|
9505
9536
|
if (policyHints.length) handoff.policy_hints = policyHints;
|
|
9537
|
+
if (readyPolicyGate.gate.advisories.length) handoff.policy_gate = readyPolicyGate.gate;
|
|
9506
9538
|
if (wantsJson(args)) {
|
|
9507
9539
|
printJson({
|
|
9508
9540
|
ok: true,
|
|
@@ -9998,6 +10030,11 @@ function cmdCertifyVerified(args, options = {}) {
|
|
|
9998
10030
|
results.push({ ref, action: 'verify_failed', reason: run.reason, verify });
|
|
9999
10031
|
continue;
|
|
10000
10032
|
}
|
|
10033
|
+
const policyGate = candidatePolicyGate(task, { verifyCache, executeDetectors: true });
|
|
10034
|
+
if (!policyGate.ok) {
|
|
10035
|
+
results.push({ ...policyGate, ref, action: 'skipped' });
|
|
10036
|
+
continue;
|
|
10037
|
+
}
|
|
10001
10038
|
const builderProof = String(review.proof || metadata.latest_agent_proof || '').slice(0, 200);
|
|
10002
10039
|
const readied = taskDb.readyTask(db, {
|
|
10003
10040
|
id: task.id,
|
|
@@ -10036,7 +10073,12 @@ function cmdCertifyVerified(args, options = {}) {
|
|
|
10036
10073
|
console.log('certify-verified: no review rows to consider.');
|
|
10037
10074
|
} else {
|
|
10038
10075
|
for (const r of results) {
|
|
10039
|
-
|
|
10076
|
+
const gateDetail = r.reason === 'slop_gate' && Array.isArray(r.offenders)
|
|
10077
|
+
? ` [${r.offenders.join('; ')}]`
|
|
10078
|
+
: (r.reason === 'lesson_gate' && Array.isArray(r.lesson_ids)
|
|
10079
|
+
? ` [lessons: ${r.lesson_ids.join(', ')}]`
|
|
10080
|
+
: '');
|
|
10081
|
+
console.log(`${r.action.padEnd(14)} ${r.ref}${r.verify ? ` \`${r.verify}\`` : ''}${r.reason ? ` (${r.reason})` : ''}${gateDetail}`);
|
|
10040
10082
|
}
|
|
10041
10083
|
console.log(`certified ${certified}; humans keep denied lanes and rows without a runnable check.`);
|
|
10042
10084
|
}
|
|
@@ -10348,6 +10390,8 @@ function evaluateSweepAutoAccept(task, root) {
|
|
|
10348
10390
|
if (denied) return { eligible: false, ref, reason: denied };
|
|
10349
10391
|
const proof = autoAcceptSweepLatestProof(task);
|
|
10350
10392
|
if (!proof) return { eligible: false, ref, reason: 'no_proof' };
|
|
10393
|
+
const policyGate = candidatePolicyGate(task, { executeDetectors: true });
|
|
10394
|
+
if (!policyGate.ok) return { ...policyGate, ref };
|
|
10351
10395
|
|
|
10352
10396
|
// 1. An explicit, stored verifier (`atris task ready --verify`, or a prior
|
|
10353
10397
|
// certify-verified stamp) is the strongest signal: re-run it live, right
|
|
@@ -10367,6 +10411,7 @@ function evaluateSweepAutoAccept(task, root) {
|
|
|
10367
10411
|
policy: 'sweep_auto_accept_verified_command',
|
|
10368
10412
|
proof,
|
|
10369
10413
|
verify: storedVerify,
|
|
10414
|
+
candidate_gate: policyGate.gate,
|
|
10370
10415
|
proved_by: [`${storedVerify} exited 0`],
|
|
10371
10416
|
happened: autoAcceptSweepHappened(task),
|
|
10372
10417
|
};
|
|
@@ -10382,6 +10427,7 @@ function evaluateSweepAutoAccept(task, root) {
|
|
|
10382
10427
|
policy: 'sweep_auto_accept_verified',
|
|
10383
10428
|
proof,
|
|
10384
10429
|
evidence: verifier.evidence,
|
|
10430
|
+
candidate_gate: policyGate.gate,
|
|
10385
10431
|
proved_by: verifier.proved_by,
|
|
10386
10432
|
happened: autoAcceptSweepHappened(task),
|
|
10387
10433
|
};
|
|
@@ -10405,6 +10451,7 @@ function evaluateSweepAutoAccept(task, root) {
|
|
|
10405
10451
|
policy: 'sweep_auto_accept_verified_derived',
|
|
10406
10452
|
proof,
|
|
10407
10453
|
verify: derived,
|
|
10454
|
+
candidate_gate: policyGate.gate,
|
|
10408
10455
|
proved_by: [`${derived} exited 0`],
|
|
10409
10456
|
happened: autoAcceptSweepHappened(task),
|
|
10410
10457
|
};
|