sneakoscope 0.7.49 → 0.7.50

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 CHANGED
@@ -225,7 +225,8 @@ Answer `y` to install `sneakoscope@latest`, then rerun `sks --mad`. Answer `n` t
225
225
  ### Team Missions
226
226
 
227
227
  ```sh
228
- sks team "implement this feature" executor:3 reviewer:1
228
+ sks team "implement this feature"
229
+ sks team "wide refactor" executor:5 reviewer:6
229
230
  sks team watch latest
230
231
  sks team lane latest --agent analysis_scout_1 --follow
231
232
  sks team message latest --from analysis_scout_1 --to executor_1 --message "handoff note"
@@ -235,6 +236,8 @@ sks team dashboard latest
235
236
  sks team log latest
236
237
  ```
237
238
 
239
+ By default, Team missions keep at least five QA/reviewer lanes active. Use explicit role counts only when you need to raise or otherwise pin the lane mix for a specific mission.
240
+
238
241
  Team mode prepares the mission, records live events, compiles runtime tasks and worker inboxes, writes schema-backed effort/work-order/dashboard artifacts, and opens a named tmux Team session with split live lanes when tmux is available. The default terminal output stays compact: mission id, agent count, role count, tmux status, watch command, and artifact directory. `sks team dashboard` renders the cockpit panes for mission overview, agent lanes, task DAG, QA/dogfood, artifacts/evidence, and performance.
239
242
 
240
243
  The tmux Team launch is a live orchestration screen in one tmux window: the first pane follows `sks team watch <mission-id> --follow` as the mission overview, and neighboring split panes follow individual `sks team lane <mission-id> --agent <name> --follow` views. Pane headers show only mission, lane, phase, follow command, and cleanup command. SKS gives lanes role-specific colors, labels, and terminal titles, so scouts, planning/debate voices, executors, reviewers, and safety lanes are visually distinct while detailed evidence is mirrored into `team-transcript.jsonl`, `team-live.md`, and `team-dashboard.json`.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "0.7.49",
4
+ "version": "0.7.50",
5
5
  "description": "Sneakoscope Codex: database-safe Codex CLI/App harness with Team, Goal, AutoResearch, TriWiki, and Honest Mode.",
6
6
  "type": "module",
7
7
  "homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
package/src/cli/main.mjs CHANGED
@@ -47,6 +47,7 @@ import { ALLOWED_REASONING_EFFORTS, AWESOME_DESIGN_MD_REFERENCE, CODEX_APP_IMAGE
47
47
  import { PIPELINE_PLAN_ARTIFACT, buildPipelinePlan, context7Evidence, evaluateStop, projectGateStatus, recordContext7Evidence, recordSubagentEvidence, validatePipelinePlan, writePipelinePlan } from '../core/pipeline.mjs';
48
48
  import { TEAM_DECOMPOSITION_ARTIFACT, TEAM_GRAPH_ARTIFACT, TEAM_INBOX_DIR, TEAM_RUNTIME_TASKS_ARTIFACT, validateTeamRuntimeArtifacts, writeTeamRuntimeArtifacts } from '../core/team-dag.mjs';
49
49
  import { appendTeamEvent, initTeamLive, parseTeamSpecText, readTeamDashboard, readTeamLive, readTeamTranscriptTail, renderTeamAgentLane } from '../core/team-live.mjs';
50
+ import { evaluateTeamReviewPolicyGate } from '../core/team-review-policy.mjs';
50
51
  import { ARTIFACT_FILES, validateDogfoodReport, validateEffortDecision, validateFromChatImgVisualMap, validateSkillCandidate, validateSkillInjectionDecision, validateTeamDashboardState, validateWorkOrderLedger } from '../core/artifact-schemas.mjs';
51
52
  import { selectEffort, writeEffortDecision } from '../core/effort-orchestrator.mjs';
52
53
  import { createWorkOrderLedger } from '../core/work-order-ledger.mjs';
@@ -184,7 +185,7 @@ Usage:
184
185
  sks goal create "task"
185
186
  sks goal pause|resume|clear <mission-id|latest>
186
187
  sks goal status <mission-id|latest>
187
- sks team "task" [executor:5 reviewer:2 user:1] [--json]
188
+ sks team "task" [executor:5 reviewer:6 user:1] [--json]
188
189
  sks team log|tail|watch|lane|status|dashboard [mission-id|latest]
189
190
  sks team event [mission-id|latest] --agent <name> --phase <phase> --message "..."
190
191
  sks team message [mission-id|latest] --from <agent> --to <agent|all> --message "..."
@@ -1472,7 +1473,7 @@ function usage(args = []) {
1472
1473
  deps: ['Dependencies', '', ' sks deps check [--json]', ' sks deps install [tmux|codex|context7|all] [--yes]', '', 'tmux on macOS uses Homebrew after Y/n approval for missing installs or Homebrew-managed upgrades. If PATH resolves an npm-managed tmux, SKS prompts for npm i -g tmux@latest instead. Unknown non-Homebrew tmux paths are reported as conflicts.'],
1473
1474
  tmux: ['tmux', '', ' sks', ' sks tmux open', ' sks tmux check', ' sks tmux status --once', ' sks deps install tmux', '', 'Running bare `sks` opens or reuses the default tmux Codex CLI session in fast-high mode: --model gpt-5.5 -c model_reasoning_effort="high". Override with SKS_CODEX_MODEL or SKS_CODEX_REASONING. Before launch, SKS checks npm @openai/codex@latest and prompts Y/n when the installed Codex CLI is missing or outdated. Use `sks tmux open` when you need explicit session/workspace flags, and `sks help` for CLI help.'],
1474
1475
  openclaw: ['OpenClaw', '', ' sks openclaw install', ' sks openclaw path', ' sks openclaw print SKILL.md', '', 'Installs an OpenClaw skill package under ~/.openclaw/skills/sneakoscope-codex so OpenClaw agents can attach skills: [sneakoscope-codex] with the shell tool and call local SKS commands from a project root.'],
1475
- team: ['Team', '', ' sks team "task" executor:5 reviewer:2 user:1', ' sks team watch latest', ' sks team lane latest --agent analysis_scout_1 --follow', ' sks team message latest --from analysis_scout_1 --to executor_1 --message "handoff note"', ' sks team cleanup-tmux latest', '', '$Team runs questions -> contract -> scouts -> TriWiki attention -> debate -> runtime graph/inbox -> fresh executors -> review -> cleanup -> reflection -> Honest.'],
1476
+ team: ['Team', '', ' sks team "task" executor:5 reviewer:6 user:1', ' sks team watch latest', ' sks team lane latest --agent analysis_scout_1 --follow', ' sks team message latest --from analysis_scout_1 --to executor_1 --message "handoff note"', ' sks team cleanup-tmux latest', '', '$Team runs questions -> contract -> scouts -> TriWiki attention -> debate -> runtime graph/inbox -> fresh executors -> review -> cleanup -> reflection -> Honest.'],
1476
1477
  'qa-loop': ['QA-LOOP', '', ' sks qa-loop prepare "QA this app"', ' sks qa-loop answer <MISSION_ID> answers.json', ' sks qa-loop run <MISSION_ID> --max-cycles 8', '', 'Report: YYYY-MM-DD-v<version>-qa-report.md'],
1477
1478
  ppt: ['PPT', '', ' $PPT 투자자용 피치덱을 HTML 기반 PDF로 만들어줘', ' $PPT 우리 SaaS 소개자료 만들어줘', ' sks ppt build latest --json', ' sks ppt status latest --json', '', '$PPT asks delivery context, audience profile, STP strategy, decision context, and 3+ pain-point/solution/aha mappings before source research, design-system work, HTML/PDF export, render QA, fact-ledger validation, and bounded review-loop validation. Independent strategy/render/file-write phases run in parallel where inputs allow and are recorded in ppt-parallel-report.json. The visual system must stay simple, restrained, and information-first; editable source HTML is kept under source-html/, PPT-only temporary build files are cleaned, and installed skills/MCPs outside the $PPT allowlist are ignored. Design uses getdesign-reference plus the built-in PPT design pipeline; imagegen/gpt-image-2 and Context7 are conditional only when the sealed PPT contract needs raster assets, slide visual critique, or current external docs. Missing required image-review evidence blocks instead of being simulated.'],
1478
1479
  goal: ['Goal', '', ' sks goal create "task"', ' sks goal status latest', ' sks goal pause latest', ' sks goal resume latest', ' sks goal clear latest'],
@@ -1967,6 +1968,12 @@ async function selftest() {
1967
1968
  const tmp = tmpdir();
1968
1969
  process.chdir(tmp);
1969
1970
  await initProject(tmp, {});
1971
+ const latestMissionTmp = tmpdir();
1972
+ await ensureDir(path.join(latestMissionTmp, '.sneakoscope', 'missions', 'M-20260509-193839-6917'));
1973
+ await ensureDir(path.join(latestMissionTmp, '.sneakoscope', 'missions', 'M-20260509-193839-0551'));
1974
+ await writeJsonAtomic(path.join(latestMissionTmp, '.sneakoscope', 'missions', 'M-20260509-193839-6917', 'mission.json'), { id: 'M-20260509-193839-6917', created_at: '2026-05-09T10:38:39.362Z' });
1975
+ await writeJsonAtomic(path.join(latestMissionTmp, '.sneakoscope', 'missions', 'M-20260509-193839-0551', 'mission.json'), { id: 'M-20260509-193839-0551', created_at: '2026-05-09T10:38:39.363Z' });
1976
+ if (await findLatestMission(latestMissionTmp) !== 'M-20260509-193839-0551') throw new Error('selftest failed: latest mission should use mission metadata time, not lexicographic id order');
1970
1977
  if (readMaxCycles(['--max-cycles', 'Infinity'], 8) !== 8) throw new Error('selftest failed: non-finite max cycles not sanitized');
1971
1978
  if (readMaxCycles(['--max-cycles', '0'], 8) !== 1) throw new Error('selftest failed: zero max cycles not bounded');
1972
1979
  const loopMission = await createMission(tmp, { mode: 'team', prompt: 'compliance loop guard selftest' });
@@ -2650,7 +2657,7 @@ async function selftest() {
2650
2657
  if (!(await exists(path.join(missionDir(hookKoreanSksTmp, hookKoreanSksState.mission_id), 'team-plan.json')))) throw new Error('selftest failed: Korean Team auto-seal did not write team-plan.json');
2651
2658
  const hookPaymentTeamTmp = tmpdir();
2652
2659
  await initProject(hookPaymentTeamTmp, {});
2653
- const hookPaymentTeamPayload = JSON.stringify({ cwd: hookPaymentTeamTmp, prompt: '$Team 결제 재시도 정책과 로그인 세션 만료 버그 수정 executor:2 reviewer:1 user:1' });
2660
+ const hookPaymentTeamPayload = JSON.stringify({ cwd: hookPaymentTeamTmp, prompt: '$Team 결제 재시도 정책과 로그인 세션 만료 버그 수정 executor:2 user:1' });
2654
2661
  const hookPaymentTeamResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], { cwd: hookPaymentTeamTmp, input: hookPaymentTeamPayload, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 256 * 1024 });
2655
2662
  if (hookPaymentTeamResult.code !== 0) throw new Error(`selftest failed: payment/auth Team hook exited ${hookPaymentTeamResult.code}: ${hookPaymentTeamResult.stderr}`);
2656
2663
  const hookPaymentTeamJson = JSON.parse(hookPaymentTeamResult.stdout);
@@ -2662,7 +2669,7 @@ async function selftest() {
2662
2669
  if (!(await exists(path.join(missionDir(hookPaymentTeamTmp, hookPaymentTeamState.mission_id), 'team-plan.json')))) throw new Error('selftest failed: predictable payment/auth Team auto-seal did not write team-plan.json');
2663
2670
  const hookTeamTmp = tmpdir();
2664
2671
  await initProject(hookTeamTmp, {});
2665
- const hookTeamPayload = JSON.stringify({ cwd: hookTeamTmp, prompt: '$Team 발표자료 만들어줘 executor:2 reviewer:1 user:1' });
2672
+ const hookTeamPayload = JSON.stringify({ cwd: hookTeamTmp, prompt: '$Team 발표자료 만들어줘 executor:2 user:1' });
2666
2673
  const hookTeamResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], { cwd: hookTeamTmp, input: hookTeamPayload, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 256 * 1024 });
2667
2674
  if (hookTeamResult.code !== 0) throw new Error(`selftest failed: $Team hook exited ${hookTeamResult.code}: ${hookTeamResult.stderr}`);
2668
2675
  const hookTeamJson = JSON.parse(hookTeamResult.stdout);
@@ -3077,8 +3084,12 @@ async function selftest() {
3077
3084
  const { id: teamId, dir: teamDir } = await createMission(tmp, { mode: 'team', prompt: '병렬 구현 팀 테스트' });
3078
3085
  const teamPlan = buildTeamPlan(teamId, '병렬 구현 팀 테스트');
3079
3086
  await writeJsonAtomic(path.join(teamDir, 'team-plan.json'), teamPlan);
3080
- if (teamPlan.agent_session_count !== 3) throw new Error('selftest failed: team default sessions not 3');
3081
- if (teamPlan.role_counts.executor !== 3 || teamPlan.role_counts.user !== 1 || teamPlan.role_counts.reviewer !== 1) throw new Error('selftest failed: team default role counts invalid');
3087
+ if (teamPlan.agent_session_count !== 5) throw new Error('selftest failed: team default sessions not 5');
3088
+ if (teamPlan.role_counts.executor !== 3 || teamPlan.role_counts.user !== 1 || teamPlan.role_counts.reviewer !== 5) throw new Error('selftest failed: team default role counts invalid');
3089
+ if (!teamPlan.review_gate?.passed || teamPlan.review_gate.required_reviewer_lanes !== 5) throw new Error('selftest failed: team review policy gate did not pass default plan');
3090
+ const underProvisionedReviewCount = 2;
3091
+ const blockedReviewGate = evaluateTeamReviewPolicyGate({ roleCounts: { reviewer: underProvisionedReviewCount }, agentSessions: 3, roster: { validation_team: [{ id: 'reviewer_1', role: 'reviewer' }] } });
3092
+ if (blockedReviewGate.passed || !blockedReviewGate.blockers.includes('validation_team_reviewers_below_required')) throw new Error('selftest failed: team review policy gate did not block under-provisioned review');
3082
3093
  if (teamPlan.phases[0]?.id !== 'team_roster_confirmation' || teamPlan.phases[1]?.id !== 'parallel_analysis_scouting' || teamPlan.phases[2]?.id !== 'triwiki_refresh') throw new Error('selftest failed: team plan is not roster-first then scout-first');
3083
3094
  if (teamPlan.roster.debate_team.length !== 3 || !teamPlan.roster.debate_team.some((agent) => agent.id === 'debate_user_1') || !teamPlan.roster.development_team.some((agent) => agent.id === 'executor_3')) throw new Error('selftest failed: team roster missing default agents');
3084
3095
  if (teamPlan.roster.analysis_team.length !== teamPlan.role_counts.executor || !teamPlan.roster.analysis_team.some((agent) => agent.id === 'analysis_scout_3')) throw new Error('selftest failed: team analysis scout roster missing default agents');
@@ -3108,13 +3119,13 @@ async function selftest() {
3108
3119
  if (!teamWorkflow.includes('before every stage') || !teamWorkflow.includes('after findings/artifact changes')) throw new Error('selftest failed: team workflow missing per-stage TriWiki policy');
3109
3120
  const customTeamPlan = buildTeamPlan(teamId, '병렬 구현 팀 테스트', { agentSessions: 5 });
3110
3121
  if (customTeamPlan.agent_session_count !== 5) throw new Error('selftest failed: custom team sessions not honored');
3111
- if (parseTeamCreateArgs(['--agents', '4', '작업']).agentSessions !== 4) throw new Error('selftest failed: team --agents parsing');
3122
+ if (parseTeamCreateArgs(['--agents', '4', '작업']).agentSessions !== 5) throw new Error('selftest failed: team --agents parsing');
3112
3123
  const maxAgentParsed = parseTeamCreateArgs(['--max-agents', '작업']);
3113
3124
  if (maxAgentParsed.agentSessions !== 6 || maxAgentParsed.roleCounts.executor !== 6) throw new Error('selftest failed: team --max-agents parsing');
3114
3125
  const maxTextParsed = parseTeamSpecText('가용가능한 최대 agents로 분석하고 구현');
3115
3126
  if (maxTextParsed.agentSessions !== 6 || maxTextParsed.roleCounts.executor !== 6) throw new Error('selftest failed: team max-agent text parsing');
3116
- const roleParsed = parseTeamCreateArgs(['executor:5', 'reviewer:2', 'user:1', '작업']);
3117
- if (roleParsed.roleCounts.executor !== 5 || roleParsed.roleCounts.reviewer !== 2 || roleParsed.agentSessions !== 5 || roleParsed.prompt !== '작업') throw new Error('selftest failed: team role-count parsing');
3127
+ const roleParsed = parseTeamCreateArgs(['executor:5', 'reviewer:6', 'user:1', '작업']);
3128
+ if (roleParsed.roleCounts.executor !== 5 || roleParsed.roleCounts.reviewer !== 6 || roleParsed.agentSessions !== 6 || roleParsed.prompt !== '작업') throw new Error('selftest failed: team role-count parsing');
3118
3129
  const openTmuxFlagParsed = parseTeamCreateArgs(['--open-tmux', '작업']);
3119
3130
  if (openTmuxFlagParsed.prompt !== '작업') throw new Error('selftest failed: team --open-tmux leaked into prompt');
3120
3131
  const roleTeamPlan = buildTeamPlan(teamId, '역할 팀 테스트', { roleCounts: roleParsed.roleCounts });
@@ -3195,7 +3206,7 @@ async function selftest() {
3195
3206
  await appendTeamEvent(teamDir, { agent: 'analysis_scout_1', phase: 'parallel_analysis_scouting', message: 'selftest mapped repo slice' });
3196
3207
  await appendTeamEvent(teamDir, { agent: 'team_consensus', phase: 'planning_debate', message: 'selftest mapped options' });
3197
3208
  const teamDashboard = await readTeamDashboard(teamDir);
3198
- if (teamDashboard?.agent_session_count !== 5 || teamDashboard?.role_counts?.executor !== 5) throw new Error('selftest failed: team dashboard session/role budget missing');
3209
+ if (teamDashboard?.agent_session_count !== 6 || teamDashboard?.role_counts?.executor !== 5 || teamDashboard?.role_counts?.reviewer !== 6) throw new Error('selftest failed: team dashboard session/role budget missing');
3199
3210
  await writeTeamDashboardState(teamDir, { missionId: teamId, mission: { id: teamId, mode: 'team' }, effort: 'high', phase: 'verification' });
3200
3211
  const teamDashboardState = await readJson(path.join(teamDir, ARTIFACT_FILES.team_dashboard_state), {});
3201
3212
  if (!validateTeamDashboardState(teamDashboardState).ok || !renderTeamDashboardState(teamDashboardState).includes('Mission / Goal View')) throw new Error('selftest failed: Team dashboard state missing required cockpit panes');
@@ -18,6 +18,7 @@ import { rgbaKey, rgbaToWikiCoord, validateWikiCoordinateIndex } from '../core/w
18
18
  import { ALLOWED_REASONING_EFFORTS, CODEX_COMPUTER_USE_ONLY_POLICY, DOLLAR_SKILL_NAMES, FROM_CHAT_IMG_CHECKLIST_ARTIFACT, FROM_CHAT_IMG_COVERAGE_ARTIFACT, FROM_CHAT_IMG_QA_LOOP_ARTIFACT, FROM_CHAT_IMG_SOURCE_INVENTORY_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS, FROM_CHAT_IMG_VISUAL_MAP_ARTIFACT, FROM_CHAT_IMG_WORK_ORDER_ARTIFACT, RECOMMENDED_SKILLS, ROUTES, hasFromChatImgSignal, routePrompt, stackCurrentDocsPolicy, triwikiContextTracking } from '../core/routes.mjs';
19
19
  import { TEAM_DECOMPOSITION_ARTIFACT, TEAM_GRAPH_ARTIFACT, TEAM_INBOX_DIR, TEAM_RUNTIME_TASKS_ARTIFACT, teamRuntimePlanMetadata, teamRuntimeRequiredArtifacts, writeTeamRuntimeArtifacts } from '../core/team-dag.mjs';
20
20
  import { appendTeamEvent, formatRoleCounts, initTeamLive, normalizeTeamSpec, parseTeamSpecArgs, readTeamControl, readTeamDashboard, readTeamLive, readTeamTranscriptTail, renderTeamAgentLane, renderTeamCleanupSummary, renderTeamWatch, requestTeamSessionCleanup, teamCleanupRequested } from '../core/team-live.mjs';
21
+ import { evaluateTeamReviewPolicyGate, MIN_TEAM_REVIEWER_LANES, MIN_TEAM_REVIEW_POLICY_TEXT, teamReviewPolicy } from '../core/team-review-policy.mjs';
21
22
  import { ARTIFACT_FILES, writeValidationReport } from '../core/artifact-schemas.mjs';
22
23
  import { writeEffortDecision } from '../core/effort-orchestrator.mjs';
23
24
  import { createWorkOrderLedger, writeWorkOrderLedger } from '../core/work-order-ledger.mjs';
@@ -1472,7 +1473,7 @@ export async function team(args) {
1472
1473
  const opts = parseTeamCreateArgs(cleanCreateArgs);
1473
1474
  const { prompt, agentSessions, roleCounts, roster } = opts;
1474
1475
  if (!prompt) {
1475
- console.error('Usage: sks team "task" [executor:5 reviewer:2 user:1] [--agents N] [--open-tmux] [--json]');
1476
+ console.error('Usage: sks team "task" [executor:5 reviewer:6 user:1] [--agents N] [--open-tmux] [--json]');
1476
1477
  console.error(' sks team log|tail|watch|lane|status|message|cleanup-tmux [mission-id|latest]');
1477
1478
  console.error(' sks team event [mission-id|latest] --agent <name> --phase <phase> --message "..."');
1478
1479
  console.error(' sks team message [mission-id|latest] --from <agent> --to <agent|all> --message "..."');
@@ -1544,6 +1545,7 @@ export async function team(args) {
1544
1545
  console.log(`Team mission created: ${id}`);
1545
1546
  console.log(`Agent sessions: ${agentSessions}`);
1546
1547
  console.log(`Role counts: ${formatRoleCounts(roleCounts)}`);
1548
+ console.log(`Review policy: minimum ${MIN_TEAM_REVIEWER_LANES} reviewer/QA validation lanes`);
1547
1549
  if (result.tmux.ready) {
1548
1550
  const tmuxState = result.tmux.created ? 'opened' : 'not opened; use --open-tmux for a tmux session';
1549
1551
  console.log(`tmux: ${tmuxState} ${result.tmux.opened_lane_count || result.tmux.agents.length} agent lane(s) in ${result.tmux.session || result.tmux.workspace}`);
@@ -1576,22 +1578,26 @@ export function buildTeamPlan(id, prompt, opts = {}) {
1576
1578
  mode: 'team',
1577
1579
  prompt,
1578
1580
  agent_session_count: agentSessions,
1579
- default_agent_session_count: 3,
1581
+ default_agent_session_count: MIN_TEAM_REVIEWER_LANES,
1580
1582
  role_counts: roleCounts,
1581
1583
  session_policy: `Use at most ${agentSessions} subagent sessions at a time; parent orchestrator is not counted.`,
1584
+ review_policy: teamReviewPolicy(),
1585
+ review_gate: evaluateTeamReviewPolicyGate({ roleCounts, agentSessions, roster }),
1582
1586
  bundle_size: roster.bundle_size,
1583
1587
  roster,
1584
1588
  team_model: {
1585
1589
  phases: ['parallel_analysis_scouts', 'triwiki_stage_refresh', 'debate_team', 'triwiki_stage_refresh', 'runtime_task_graph', 'development_team', 'triwiki_stage_refresh', 'review', 'session_cleanup'],
1586
1590
  analysis_team: `Read-only parallel scouting with exactly ${roster.bundle_size} analysis_scout_N agents. Each scout owns one investigation slice, records source paths/evidence, and returns TriWiki-ready findings before debate or implementation starts.`,
1587
1591
  debate_team: `Read-only role debate with exactly ${roster.bundle_size} participants composed from user, planner, reviewer, and executor voices applying compact Hyperplan-derived adversarial lenses.`,
1588
- development_team: `Fresh parallel development bundle with exactly ${roster.bundle_size} executor_N developers implementing disjoint slices; validation_team reviews afterward.`
1592
+ development_team: `Fresh parallel development bundle with exactly ${roster.bundle_size} executor_N developers implementing disjoint slices; validation_team reviews afterward.`,
1593
+ review_team: `Validation runs at least ${MIN_TEAM_REVIEWER_LANES} independent reviewer/QA lanes before integration or final.`
1589
1594
  },
1590
1595
  team_runtime: teamRuntimePlanMetadata(),
1591
1596
  persona_axioms: [
1592
1597
  'Final users are intentionally low-context, impatient, self-interested, stubborn, and hostile to inconvenience.',
1593
1598
  'Executors are capable developers and must receive disjoint write ownership.',
1594
1599
  'Reviewers are strict, skeptical, and block unsupported correctness, DB safety, test, or evidence claims.',
1600
+ MIN_TEAM_REVIEW_POLICY_TEXT,
1595
1601
  'Analysis scouts run before debate, then the debate team closes before a fresh development team starts parallel implementation.'
1596
1602
  ],
1597
1603
  reasoning: { effort: 'high', profile: 'sks-logic-high', temporary: true, restore_after_completion: true },
@@ -1659,8 +1665,9 @@ export function buildTeamPlan(id, prompt, opts = {}) {
1659
1665
  },
1660
1666
  {
1661
1667
  id: 'review_and_integrate',
1662
- goal: 'Strict reviewers read/validate current TriWiki context, check correctness, DB safety, tests, and evidence; user personas validate practical inconvenience; parent integrates final result and refreshes after review findings.',
1663
- agents: roster.validation_team.map((agent) => agent.id).concat(['parent_orchestrator'])
1668
+ goal: `Strict reviewers read/validate current TriWiki context, run at least ${MIN_TEAM_REVIEWER_LANES} independent reviewer/QA validation lanes, check correctness, DB safety, tests, and evidence; user personas validate practical inconvenience; parent integrates final result and refreshes after review findings.`,
1669
+ agents: roster.validation_team.map((agent) => agent.id).concat(['parent_orchestrator']),
1670
+ min_reviewer_lanes: MIN_TEAM_REVIEWER_LANES
1664
1671
  },
1665
1672
  {
1666
1673
  id: 'session_cleanup',
@@ -1689,6 +1696,7 @@ export function buildTeamPlan(id, prompt, opts = {}) {
1689
1696
  'Planning agents do not edit files.',
1690
1697
  'Implementation workers receive disjoint ownership scopes.',
1691
1698
  'Workers are told they are not alone in the codebase and must not revert others edits.',
1699
+ MIN_TEAM_REVIEW_POLICY_TEXT,
1692
1700
  'Team completion requires session cleanup evidence with zero outstanding subagent sessions before reflection.',
1693
1701
  'Context tracking uses the latest coordinate+voxel TriWiki pack as the SSOT throughout the whole pipeline; coordinate-only legacy packs are invalid, and team handoffs/final claims must preserve id, hash, source path, and RGBA/trig coordinate anchors.',
1694
1702
  'SKS hooks, DB safety rules, no-question run rules, and H-Proof gates remain active.',
@@ -1729,18 +1737,19 @@ ${plan.prompt}
1729
1737
  \`\`\`text
1730
1738
  ${plan.prompt_command || '$Team'} ${plan.prompt}
1731
1739
 
1732
- Use high reasoning for the Team route only, then return to the default/user-selected profile after completion. Use at most ${plan.agent_session_count || 3} subagent sessions at a time; the parent orchestrator is not counted.
1740
+ Use high reasoning for the Team route only, then return to the default/user-selected profile after completion. Use at most ${plan.agent_session_count || MIN_TEAM_REVIEWER_LANES} subagent sessions at a time; the parent orchestrator is not counted. ${plan.review_policy?.text || MIN_TEAM_REVIEW_POLICY_TEXT}
1733
1741
 
1734
- Before each stage, read the relevant latest coordinate+voxel TriWiki context pack and hydrate low-trust claims from source. Coordinate-only legacy packs are invalid; refresh and validate before using TriWiki for pipeline decisions. First run exactly ${plan.roster.bundle_size} read-only analysis_scout_N agents in parallel. Split repo, docs, tests, API, DB risk, UX friction, and implementation-surface investigation into independent slices, then capture source-backed findings in team-analysis.md. Refresh and validate TriWiki before debate. Then run the debate team with exactly ${plan.roster.bundle_size} participants using the refreshed pack. Use the concrete roster below: final-user voices are stubborn and inconvenience-averse, executor voices are capable developers, reviewers are strict, and planners force consensus. Synthesize one agreed objective with acceptance criteria and disjoint implementation slices, then refresh and validate TriWiki again. Compile the Team runtime graph into ${TEAM_GRAPH_ARTIFACT}, ${TEAM_RUNTIME_TASKS_ARTIFACT}, ${TEAM_DECOMPOSITION_ARTIFACT}, and ${TEAM_INBOX_DIR} so symbolic plan nodes become concrete runtime task ids before worker handoff. Close the debate team. Then form a fresh development team with exactly ${plan.roster.bundle_size} executor_N developers implementing slices in parallel with non-overlapping ownership. Refresh TriWiki after implementation changes or blockers. Review with the validation team, validate TriWiki again, integrate results in the parent thread, close or account for all Team sessions in team-session-cleanup.json, run verification, and report evidence.
1742
+ Before each stage, read the relevant latest coordinate+voxel TriWiki context pack and hydrate low-trust claims from source. Coordinate-only legacy packs are invalid; refresh and validate before using TriWiki for pipeline decisions. First run exactly ${plan.roster.bundle_size} read-only analysis_scout_N agents in parallel. Split repo, docs, tests, API, DB risk, UX friction, and implementation-surface investigation into independent slices, then capture source-backed findings in team-analysis.md. Refresh and validate TriWiki before debate. Then run the debate team with exactly ${plan.roster.bundle_size} participants using the refreshed pack. Use the concrete roster below: final-user voices are stubborn and inconvenience-averse, executor voices are capable developers, reviewers are strict, and planners force consensus. Synthesize one agreed objective with acceptance criteria and disjoint implementation slices, then refresh and validate TriWiki again. Compile the Team runtime graph into ${TEAM_GRAPH_ARTIFACT}, ${TEAM_RUNTIME_TASKS_ARTIFACT}, ${TEAM_DECOMPOSITION_ARTIFACT}, and ${TEAM_INBOX_DIR} so symbolic plan nodes become concrete runtime task ids before worker handoff. Close the debate team. Then form a fresh development team with exactly ${plan.roster.bundle_size} executor_N developers implementing slices in parallel with non-overlapping ownership. Refresh TriWiki after implementation changes or blockers. Review with at least ${plan.review_policy?.minimum_reviewer_lanes || MIN_TEAM_REVIEWER_LANES} independent reviewer/QA validation lanes, validate TriWiki again, integrate results in the parent thread, close or account for all Team sessions in team-session-cleanup.json, run verification, and report evidence.
1735
1743
  \`\`\`
1736
1744
 
1737
1745
  ## Session Budget
1738
1746
 
1739
- - Default: 3 subagent sessions.
1740
- - This mission: ${plan.agent_session_count || 3} subagent sessions.
1747
+ - Default: ${plan.default_agent_session_count || MIN_TEAM_REVIEWER_LANES} subagent sessions.
1748
+ - This mission: ${plan.agent_session_count || MIN_TEAM_REVIEWER_LANES} subagent sessions.
1741
1749
  - Bundle size: ${plan.roster.bundle_size}
1742
1750
  - Role counts: ${formatRoleCounts(plan.role_counts)}
1743
1751
  - The parent orchestrator is not counted.
1752
+ - Minimum review: ${plan.review_policy?.minimum_reviewer_lanes || MIN_TEAM_REVIEWER_LANES} independent reviewer/QA validation lanes before integration or final.
1744
1753
  - Use the full available session budget for analysis when independent slices exist; use fewer agents only when the work cannot be split cleanly.
1745
1754
  - Runtime graph: write ${TEAM_GRAPH_ARTIFACT}, ${TEAM_RUNTIME_TASKS_ARTIFACT}, ${TEAM_DECOMPOSITION_ARTIFACT}, and ${TEAM_INBOX_DIR}; worker handoff starts from concrete runtime task ids and scope-aware inboxes.
1746
1755
  - Before reflection/final, close or account for all Team subagent sessions and write ${TEAM_SESSION_CLEANUP_ARTIFACT}.
@@ -1890,7 +1899,7 @@ async function teamCommand(sub, args) {
1890
1899
  }
1891
1900
  console.log(`Team mission: ${id}`);
1892
1901
  console.log(`Updated: ${dashboard.updated_at || 'unknown'}`);
1893
- console.log(`Agent sessions: ${dashboard.agent_session_count || 3}`);
1902
+ console.log(`Agent sessions: ${dashboard.agent_session_count || MIN_TEAM_REVIEWER_LANES}`);
1894
1903
  if (dashboard.role_counts) console.log(`Role counts: ${formatRoleCounts(dashboard.role_counts)}`);
1895
1904
  for (const entry of dashboard.latest_messages || []) console.log(`${entry.ts} [${entry.phase}] ${entry.agent}: ${entry.message}`);
1896
1905
  return;
package/src/core/fsx.mjs CHANGED
@@ -5,7 +5,7 @@ import os from 'node:os';
5
5
  import crypto from 'node:crypto';
6
6
  import { spawn } from 'node:child_process';
7
7
 
8
- export const PACKAGE_VERSION = '0.7.49';
8
+ export const PACKAGE_VERSION = '0.7.50';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11
 
package/src/core/init.mjs CHANGED
@@ -6,6 +6,7 @@ import { DEFAULT_DB_SAFETY_POLICY } from './db-safety.mjs';
6
6
  import { isHarnessSourceProject, writeHarnessGuardPolicy } from './harness-guard.mjs';
7
7
  import { repairSksGeneratedArtifacts } from './harness-conflicts.mjs';
8
8
  import { installVersionGitHook } from './version-manager.mjs';
9
+ import { MIN_TEAM_REVIEWER_LANES, MIN_TEAM_REVIEW_POLICY_TEXT } from './team-review-policy.mjs';
9
10
  import { AWESOME_DESIGN_MD_REFERENCE, CODEX_APP_IMAGE_GENERATION_DOC_URL, CODEX_COMPUTER_USE_ONLY_POLICY, DESIGN_SYSTEM_SSOT, DOLLAR_COMMANDS, DOLLAR_COMMAND_ALIASES, DOLLAR_SKILL_NAMES, FROM_CHAT_IMG_CHECKLIST_ARTIFACT, FROM_CHAT_IMG_COVERAGE_ARTIFACT, FROM_CHAT_IMG_QA_LOOP_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS, GETDESIGN_REFERENCE, PPT_CONDITIONAL_SKILL_ALLOWLIST, PPT_PIPELINE_MCP_ALLOWLIST, PPT_PIPELINE_SKILL_ALLOWLIST, RECOMMENDED_DESIGN_REFERENCES, RECOMMENDED_MCP_SERVERS, RECOMMENDED_SKILLS, chatCaptureIntakeText, context7ConfigToml, getdesignReferencePolicyText, outcomeRubricPolicyText, pptPipelineAllowlistPolicyText, speedLanePolicyText, stackCurrentDocsPolicyText, triwikiContextTracking, triwikiContextTrackingText, triwikiStagePolicyText } from './routes.mjs';
10
11
  import { SKILL_DREAM_POLICY, skillDreamPolicyText } from './skill-forge.mjs';
11
12
 
@@ -93,7 +94,7 @@ function isSksManagedHook(hook) {
93
94
  return hook.type === 'command' && /\bhook\s+(?:user-prompt-submit|pre-tool|post-tool|permission-request|stop)\b/.test(command) && /\b(?:sks|sneakoscope|sks\.mjs)\b/.test(command);
94
95
  }
95
96
 
96
- const AGENTS_BLOCK = "\n# Sneakoscope Codex Managed Rules\n\nThis repository uses Sneakoscope Codex.\n\n## Core Rules\n\n- Codex native `/goal` workflows are the persisted continuation surface; Ralph is removed from the user-facing SKS surface.\n- Keep runtime state bounded: raw logs go to files, prompts get tails/summaries, and `sks gc` may prune stale artifacts.\n- Before substantive work, SKS checks npm for a newer package. If newer, ask update-now vs skip-for-this-conversation.\n- Versioning is managed by the SKS pre-commit hook; check `sks versioning status`. Bypass only with `SKS_DISABLE_VERSIONING=1`.\n- Installed harness files are immutable to LLM edits: `.codex/*`, `.agents/skills/`, `.codex/agents/`, `.sneakoscope/*policy*.json`, `AGENTS.md`, and `node_modules/sneakoscope`. The Sneakoscope engine source repo is the only automatic exception.\n- OMX/DCodex conflicts block setup/doctor. Show `sks conflicts prompt`; cleanup requires explicit human approval.\n- Do not stop at a plan when implementation was requested. Finish, verify, or report the hard blocker.\n- Do not create unrequested fallback implementation code. If the requested path is impossible, block with evidence instead of inventing substitute behavior.\n\n## Routes\n\n- General execution/code-changing prompts default to `$Team`: analysis scouts, TriWiki refresh/validate, read-only debate, consensus, concrete runtime task graph/inboxes, fresh executor team, review, integration, Honest Mode.\n- `$Computer-Use` / `$CU` is the maximum-speed Codex Computer Use lane for UI/browser/visual tasks: skip Team debate and upfront TriWiki loops, use Codex Computer Use directly, then refresh/validate TriWiki and run Honest Mode at final closeout.\n- `$Goal` is a fast bridge/overlay for Codex native `/goal` create/pause/resume/clear persistence controls; implementation continues through the selected SKS execution route.\n- TriWiki recall must stay bounded. Use `sks wiki sweep` to record demote, soft-forget, archive, delete, promote-to-skill, and promote-to-rule candidates instead of injecting every old claim.\n- Team missions must keep schema-backed evidence current: `work-order-ledger.json`, `effort-decision.json`, `team-dashboard-state.json`, and route-specific visual/dogfood artifacts where applicable. Use `sks validate-artifacts latest` before claiming those artifacts pass.\n- `$DFix` is only for tiny design/content edits and bypasses the main pipeline, Team, TriWiki/TriFix/reflection recording, and persistent route state; it still uses a one-line DFix-specific Honest check before final. `$PPT` is the restrained, information-first HTML/PDF presentation route and must seal delivery context, audience profile, STP, decision context, and 3+ pain-point/solution/aha mappings before design/render work. It must avoid over-designed visuals, carry detail through hierarchy, spacing, alignment, thin rules, source clarity, and subtle accents, preserve editable source HTML under `source-html/`, record `ppt-parallel-report.json`, and clean PPT-only temporary build files before completion. `$Answer`, `$Help`, and `$Wiki` stay lightweight.\n- For code work, surface route/guard/write scopes first, split independent worker scopes when available, and keep parent-owned integration and verification.\n- Design work reads `design.md` as the only design decision SSOT. If missing, create it through `design-system-builder` from `docs/Design-Sys-Prompt.md`; getdesign.md, getdesign-reference, and curated DESIGN.md examples from https://github.com/VoltAgent/awesome-design-md are source inputs to fuse into that SSOT or route-local style tokens, not parallel design authorities. Image/logo/raster assets use `imagegen`, which must prefer official Codex App built-in image generation via `$imagegen` / `gpt-image-2` before API generation.\n- Research, AutoResearch, performance, token, accuracy, SEO/GEO, or workflow-improvement claims need experiment/eval evidence. Do not claim live model accuracy without a scored dataset.\n- Treat handwritten files above 3000 lines as split-review risks. Run `sks code-structure scan` and prefer extraction before adding substantial logic.\n- Skill dreaming stays lightweight: route use records JSON counters in `.sneakoscope/skills/dream-state.json`, and full skill inventory/recommendation runs only after the configured count/cooldown threshold. Reports are recommendation-only; deleting or merging skills needs explicit user approval.\n\n## Evidence And Context\n\n- Context7 is required for external libraries, APIs, MCPs, package managers, SDKs, and generated docs: resolve-library-id then query-docs.\n- When tech stack, framework, package, runtime, or deployment-platform versions change, use Context7 or official vendor web docs, record current syntax/security/limit guidance as high-priority TriWiki claims, then refresh and validate before coding.\n- TriWiki is the context-tracking SSOT for long-running missions, Team handoffs, and context-pressure recovery. Read `.sneakoscope/wiki/context-pack.json` before each stage, use `attention.use_first` for compact high-trust recall, hydrate `attention.hydrate_first` from source before risky or lower-trust decisions, refresh after findings or artifact changes, and validate before handoffs/final claims.\n- Source priority: current code/tests/config, decision contract, vgraph, beta, GX render/snapshot metadata, LLM Wiki coordinate index, then model knowledge only if allowed.\n- Final response before stop: summarize what was done, what changed for the user/repo, what was verified, and what remains unverified or blocked; then run Honest Mode. Say what passed and what was not verified.\n- `$From-Chat-IMG` uses forensic visual effort, not ordinary Team effort. Completion is blocked until source inventory, visual mapping, work-order coverage, scoped dogfood/QA, and post-fix verification artifacts are present and valid.\n\n## Safety\n\n- Database access is high risk. Use read-only inspection by default; live data mutation is out of scope unless a sealed contract allows local or branch-only migration files.\n- MAD and MAD-SKS widen only explicit scoped permissions; they still do not authorize unrequested fallback implementation code.\n- Task completion requires relevant tests or justification, zero unsupported critical claims, accepted visual/wiki drift, and final evidence.\n\n## Codex App\n\nUse `.codex/SNEAKOSCOPE.md`, generated `.agents/skills`, `.codex/hooks.json`, and SKS dollar commands (`$sks`, `$team`, `$computer-use`, `$cu`, `$ppt`, `$goal`, `$dfix`, `$qa-loop`, etc.) as the app control surface.\n";
97
+ const AGENTS_BLOCK = "\n# Sneakoscope Codex Managed Rules\n\nThis repository uses Sneakoscope Codex.\n\n## Core Rules\n\n- Codex native `/goal` workflows are the persisted continuation surface; Ralph is removed from the user-facing SKS surface.\n- Keep runtime state bounded: raw logs go to files, prompts get tails/summaries, and `sks gc` may prune stale artifacts.\n- Before substantive work, SKS checks npm for a newer package. If newer, ask update-now vs skip-for-this-conversation.\n- Versioning is managed by the SKS pre-commit hook; check `sks versioning status`. Bypass only with `SKS_DISABLE_VERSIONING=1`.\n- Installed harness files are immutable to LLM edits: `.codex/*`, `.agents/skills/`, `.codex/agents/`, `.sneakoscope/*policy*.json`, `AGENTS.md`, and `node_modules/sneakoscope`. The Sneakoscope engine source repo is the only automatic exception.\n- OMX/DCodex conflicts block setup/doctor. Show `sks conflicts prompt`; cleanup requires explicit human approval.\n- Do not stop at a plan when implementation was requested. Finish, verify, or report the hard blocker.\n- Do not create unrequested fallback implementation code. If the requested path is impossible, block with evidence instead of inventing substitute behavior.\n\n## Routes\n\n- General execution/code-changing prompts default to `$Team`: analysis scouts, TriWiki refresh/validate, read-only debate, consensus, concrete runtime task graph/inboxes, fresh executor team, minimum five-lane Team review, integration, Honest Mode.\n- `$Computer-Use` / `$CU` is the maximum-speed Codex Computer Use lane for UI/browser/visual tasks: skip Team debate and upfront TriWiki loops, use Codex Computer Use directly, then refresh/validate TriWiki and run Honest Mode at final closeout.\n- `$Goal` is a fast bridge/overlay for Codex native `/goal` create/pause/resume/clear persistence controls; implementation continues through the selected SKS execution route.\n- TriWiki recall must stay bounded. Use `sks wiki sweep` to record demote, soft-forget, archive, delete, promote-to-skill, and promote-to-rule candidates instead of injecting every old claim.\n- Team missions must keep schema-backed evidence current: `work-order-ledger.json`, `effort-decision.json`, `team-dashboard-state.json`, and route-specific visual/dogfood artifacts where applicable. Team completion requires at least five independent reviewer/QA validation lanes before integration or final, even when a prompt requests fewer reviewers. Use `sks validate-artifacts latest` before claiming those artifacts pass.\n- `$DFix` is only for tiny design/content edits and bypasses the main pipeline, Team, TriWiki/TriFix/reflection recording, and persistent route state; it still uses a one-line DFix-specific Honest check before final. `$PPT` is the restrained, information-first HTML/PDF presentation route and must seal delivery context, audience profile, STP, decision context, and 3+ pain-point/solution/aha mappings before design/render work. It must avoid over-designed visuals, carry detail through hierarchy, spacing, alignment, thin rules, source clarity, and subtle accents, preserve editable source HTML under `source-html/`, record `ppt-parallel-report.json`, and clean PPT-only temporary build files before completion. `$Answer`, `$Help`, and `$Wiki` stay lightweight.\n- For code work, surface route/guard/write scopes first, split independent worker scopes when available, and keep parent-owned integration and verification.\n- Design work reads `design.md` as the only design decision SSOT. If missing, create it through `design-system-builder` from `docs/Design-Sys-Prompt.md`; getdesign.md, getdesign-reference, and curated DESIGN.md examples from https://github.com/VoltAgent/awesome-design-md are source inputs to fuse into that SSOT or route-local style tokens, not parallel design authorities. Image/logo/raster assets use `imagegen`, which must prefer official Codex App built-in image generation via `$imagegen` / `gpt-image-2` before API generation.\n- Research, AutoResearch, performance, token, accuracy, SEO/GEO, or workflow-improvement claims need experiment/eval evidence. Do not claim live model accuracy without a scored dataset.\n- Treat handwritten files above 3000 lines as split-review risks. Run `sks code-structure scan` and prefer extraction before adding substantial logic.\n- Skill dreaming stays lightweight: route use records JSON counters in `.sneakoscope/skills/dream-state.json`, and full skill inventory/recommendation runs only after the configured count/cooldown threshold. Reports are recommendation-only; deleting or merging skills needs explicit user approval.\n\n## Evidence And Context\n\n- Context7 is required for external libraries, APIs, MCPs, package managers, SDKs, and generated docs: resolve-library-id then query-docs.\n- When tech stack, framework, package, runtime, or deployment-platform versions change, use Context7 or official vendor web docs, record current syntax/security/limit guidance as high-priority TriWiki claims, then refresh and validate before coding.\n- TriWiki is the context-tracking SSOT for long-running missions, Team handoffs, and context-pressure recovery. Read `.sneakoscope/wiki/context-pack.json` before each stage, use `attention.use_first` for compact high-trust recall, hydrate `attention.hydrate_first` from source before risky or lower-trust decisions, refresh after findings or artifact changes, and validate before handoffs/final claims.\n- Source priority: current code/tests/config, decision contract, vgraph, beta, GX render/snapshot metadata, LLM Wiki coordinate index, then model knowledge only if allowed.\n- Final response before stop: summarize what was done, what changed for the user/repo, what was verified, and what remains unverified or blocked; then run Honest Mode. Say what passed and what was not verified.\n- `$From-Chat-IMG` uses forensic visual effort, not ordinary Team effort. Completion is blocked until source inventory, visual mapping, work-order coverage, scoped dogfood/QA, and post-fix verification artifacts are present and valid.\n\n## Safety\n\n- Database access is high risk. Use read-only inspection by default; live data mutation is out of scope unless a sealed contract allows local or branch-only migration files.\n- MAD and MAD-SKS widen only explicit scoped permissions; they still do not authorize unrequested fallback implementation code.\n- Task completion requires relevant tests or justification, zero unsupported critical claims, accepted visual/wiki drift, and final evidence.\n\n## Codex App\n\nUse `.codex/SNEAKOSCOPE.md`, generated `.agents/skills`, `.codex/hooks.json`, and SKS dollar commands (`$sks`, `$team`, `$computer-use`, `$cu`, `$ppt`, `$goal`, `$dfix`, `$qa-loop`, etc.) as the app control surface.\n";
97
98
 
98
99
  export async function initProject(root, opts = {}) {
99
100
  const created = [];
@@ -723,6 +724,7 @@ function codexAppQuickReference(scope, commandPrefix) {
723
724
  `Runtime root: ${commandPrefix} root shows whether SKS is using the nearest project root or the per-user global SKS runtime root; outside any project marker, runtime commands use the global root instead of writing .sneakoscope into the current random directory.`,
724
725
  `Context Tracking: TriWiki SSOT. Before each route phase read only the latest coordinate+voxel overlay pack at .sneakoscope/wiki/context-pack.json; coordinate-only legacy packs are invalid. Use attention.use_first for compact high-trust recall and hydrate attention.hydrate_first from source before risky/lower-trust decisions. During every stage hydrate low-trust claims from source/hash/RGBA anchors; after changes run ${commandPrefix} wiki refresh or pack; before handoff/final run ${commandPrefix} wiki validate .sneakoscope/wiki/context-pack.json.`,
725
726
  stackCurrentDocsPolicyText(commandPrefix),
727
+ `Team review: ${MIN_TEAM_REVIEW_POLICY_TEXT}`,
726
728
  `Team tmux view: ${commandPrefix} team "task" prepares live watch/lane commands without opening a Team tmux view by default; add --open-tmux when you explicitly want a named Team tmux session with an overview watch pane plus color-coded split per-agent lanes; ${commandPrefix} team lane latest --agent analysis_scout_1 --follow shows one agent's status, assigned runtime tasks, recent agent events, direct messages, and fallback global tail; ${commandPrefix} team message latest --from analysis_scout_1 --to executor_1 --message "handoff note" mirrors bounded agent communication into transcript/lane panes; ${commandPrefix} team cleanup-tmux latest marks the SKS session record complete and asks follow panes to show a cleanup summary then stop.`,
727
729
  `Runtime: open Codex App once, then run ${commandPrefix} bootstrap and ${commandPrefix} deps check. Bare ${commandPrefix} opens or reuses the default tmux/Codex CLI session; before launch it checks npm @openai/codex@latest and prompts Y/n when the installed Codex CLI is missing or outdated. ${commandPrefix} codex-app remote-control wraps the Codex CLI 0.130.0+ headless remote-control entrypoint. ${commandPrefix} tmux open is the explicit form for session/workspace flags.`,
728
730
  `Guard: generated harness files are immutable outside the engine source repo; check ${commandPrefix} guard check; conflicts use ${commandPrefix} conflicts prompt with human approval.`
@@ -735,7 +737,7 @@ export async function installSkills(root) {
735
737
  'answer': `---\nname: answer\ndescription: Answer-only research route for ordinary questions that should not start implementation.\n---\n\nUse for explanations, comparisons, status, facts, source-backed research, or docs guidance. Use repo/TriWiki first for project-local facts; hydrate low-trust claims from source. Browse or use Context7 for current external package/API/framework/MCP docs. End with a concise answer summary plus Honest Mode; do not create missions, subagents, or file edits.\n`,
736
738
  'sks': `---\nname: sks\ndescription: General Sneakoscope Codex command route for $SKS or $sks usage, setup, status, and workflow help.\n---\n\nUse local SKS commands: bootstrap, deps, commands, quickstart, codex-app, context7, guard, conflicts, reasoning, wiki, pipeline status, pipeline plan, skill-dream. Promote code-changing work to Team unless Answer/DFix/Help/Wiki/safety route fits. Surface route/guard/scope, use TriWiki, do not edit installed harness files outside this engine repo, and require human-approved conflict cleanup. ${skillDreamPolicyText()}\n`,
737
739
  'wiki': `---\nname: wiki\ndescription: Dollar-command route for $Wiki TriWiki refresh, pack, validate, and prune commands.\n---\n\nUse for $Wiki or Korean wiki-refresh requests. Refresh/update/갱신: run sks wiki refresh, then validate .sneakoscope/wiki/context-pack.json. Pack: run sks wiki pack, then validate. Prune/clean/정리: use sks wiki refresh --prune, or sks wiki prune --dry-run for inspection. Report claims, anchors, trust, attention.use_first/hydrate_first, validation, and blockers. Do not start ambiguity-gated implementation, subagents, or unrelated work.\n`,
738
- 'team': `---\nname: team\ndescription: SKS Team orchestration for $Team/code work; $From-Chat-IMG is the explicit chat-image alias.\n---\n\nUse for $Team/code work. Ambiguity gate first, but score goal, constraints, success criteria, and codebase context before asking; ask only the lowest-clarity scope/safety/behavior/acceptance question(s), otherwise auto-seal inferred answers. Read pipeline-plan.json or run sks pipeline plan to see the runtime lane, kept/skipped stages, and verification before implementation. Write team-roster.json; team-gate.json needs team_roster_confirmed=true. executor:N means N scouts, N debate voices, then fresh N executors. After consensus, compile team-graph.json, team-runtime-tasks.json, team-decomposition-report.json, and team-inbox/ so worker handoff uses concrete runtime task ids with role/path/domain/lane hints. Refresh/validate TriWiki before debate, implementation, review, and final; consume attention.use_first and hydrate attention.hydrate_first before risky decisions. ${outcomeRubricPolicyText()} ${speedLanePolicyText()} ${skillDreamPolicyText()} Log events and use sks team message for bounded inter-agent communication in transcript/lane panes. Color-coded tmux lanes distinguish overview/scout/planning/execution/review/safety sessions in one tmux window using split panes when tmux is available. $Team/$team plus sks --mad uses the MAD-SKS permission gate module: live server work, normal DB writes, Supabase MCP writes, direct SQL, schema cleanup, and needed migrations are open for the active invocation; only catastrophic DB wipe/all-row/project-management guards remain. End with cleanup-tmux or a cleanup event so follow panes show cleanup and stop; pass team-session-cleanup.json, then reflection and Honest Mode. Parent integrates/verifies.\n\n${chatCaptureIntakeText()}\n`,
740
+ 'team': `---\nname: team\ndescription: SKS Team orchestration for $Team/code work; $From-Chat-IMG is the explicit chat-image alias.\n---\n\nUse for $Team/code work. Ambiguity gate first, but score goal, constraints, success criteria, and codebase context before asking; ask only the lowest-clarity scope/safety/behavior/acceptance question(s), otherwise auto-seal inferred answers. Read pipeline-plan.json or run sks pipeline plan to see the runtime lane, kept/skipped stages, and verification before implementation. Write team-roster.json; team-gate.json needs team_roster_confirmed=true. executor:N means N scouts, N debate voices, then fresh N executors. ${MIN_TEAM_REVIEW_POLICY_TEXT} After consensus, compile team-graph.json, team-runtime-tasks.json, team-decomposition-report.json, and team-inbox/ so worker handoff uses concrete runtime task ids with role/path/domain/lane hints. Refresh/validate TriWiki before debate, implementation, review, and final; consume attention.use_first and hydrate attention.hydrate_first before risky decisions. ${outcomeRubricPolicyText()} ${speedLanePolicyText()} ${skillDreamPolicyText()} Log events and use sks team message for bounded inter-agent communication in transcript/lane panes. Color-coded tmux lanes distinguish overview/scout/planning/execution/review/safety sessions in one tmux window using split panes when tmux is available. $Team/$team plus sks --mad uses the MAD-SKS permission gate module: live server work, normal DB writes, Supabase MCP writes, direct SQL, schema cleanup, and needed migrations are open for the active invocation; only catastrophic DB wipe/all-row/project-management guards remain. End with cleanup-tmux or a cleanup event so follow panes show cleanup and stop; pass team-session-cleanup.json, then reflection and Honest Mode. Parent integrates/verifies.\n\n${chatCaptureIntakeText()}\n`,
739
741
  'from-chat-img': `---\nname: from-chat-img\ndescription: Explicit $From-Chat-IMG Team alias for chat screenshot plus attachment analysis.\n---\n\nUse only for From-Chat-IMG/$From-Chat-IMG. It enters the normal Team pipeline. Treat uploads as chat screenshot plus originals. Use Codex Computer Use visual inspection when available, list requirements first, match regions to attachments with confidence, write ${FROM_CHAT_IMG_COVERAGE_ARTIFACT}, ${FROM_CHAT_IMG_CHECKLIST_ARTIFACT}, ${FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT}, and ${FROM_CHAT_IMG_QA_LOOP_ARTIFACT}, then continue Team gates, review, reflection, and Honest Mode. ${CODEX_COMPUTER_USE_ONLY_POLICY} The ledger must account for every visible customer request, screenshot image region, and separate attachment; ${FROM_CHAT_IMG_CHECKLIST_ARTIFACT} must have a checked item for each request, image-region/attachment match, work item, scoped QA-LOOP, and verification step; ${FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT} stores temporary TriWiki-backed session context with expires_after_sessions=${FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS}. ${FROM_CHAT_IMG_QA_LOOP_ARTIFACT} must prove QA-LOOP ran over the exact customer-request work-order range after implementation, with every work item covered, post-fix verification complete, and zero unresolved findings. team-gate.json cannot pass From-Chat-IMG completion until unresolved_items is empty, every checklist box is checked, and scoped_qa_loop_completed=true.\n`,
740
742
  'qa-loop': `---\nname: qa-loop\ndescription: $QA-LOOP dogfoods UI/API as human proxy with safety gates, Codex Computer Use-only UI evidence, safe fixes, rechecks, and a QA report.\n---\n\nUse only $QA-LOOP. Ask scope, target, mutation, login. Credentials are runtime-only; never save secrets. UI-level E2E needs Codex Computer Use evidence or must be marked unverified; Chrome MCP, Browser Use, Playwright, Selenium, Puppeteer, and other browser automation do not satisfy UI/browser verification. Deployed targets are read-only; destructive removal is forbidden. After answer/run, dogfood real flows, apply safe contract-allowed code/test/docs fixes, recheck, and do not pass qa-gate.json with unresolved findings or without post_fix_verification_complete. Finish qa-ledger, date/version report, gate, completion summary, and Honest Mode.\n`,
741
743
  'ppt': `---\nname: ppt\ndescription: $PPT information-first HTML/PDF presentation pipeline with STP, audience, pain-point, format, research, design-system, and verification questions.\n---\n\nUse only when the user invokes $PPT or asks to create a presentation, deck, slides, pitch deck, proposal deck, HTML presentation, or PDF presentation artifact. Before artifact work, seal presentation-specific ambiguity answers: delivery context, target audience profile including role/average age/job/industry/topic familiarity/decision power, STP strategy, decision context and objections, and 3+ pain-point to solution mappings with expected aha moments. Presentation design must be simple, restrained, and information-first: avoid over-designed decoration, ornamental gradients, nested cards, and effects that compete with the message. Design detail should be embedded through typography hierarchy, spacing, alignment, thin rules, source clarity, and subtle accents. ${pptPipelineAllowlistPolicyText()} Use design.md as the only design decision SSOT. If design.md is missing, use docs/Design-Sys-Prompt.md plus getdesign-reference and curated DESIGN.md examples from ${AWESOME_DESIGN_MD_REFERENCE.url} only as source inputs, then fuse them into route-local PPT style tokens with a recorded design_ssot instead of treating references as parallel authorities. If generated image assets or slide visual critique are needed, use imagegen/gpt-image-2 only when that asset/review need is explicitly sealed in the $PPT contract; prefer Codex App built-in image generation (${CODEX_APP_IMAGE_GENERATION_DOC_URL}) and use the OpenAI Image API with OPENAI_API_KEY when CLI-side required image assets can be generated. Use web or Context7 evidence only when external facts/libraries/current docs are required by the PPT contract, record verified claims in ppt-fact-ledger.json, record generated image asset plans/results/blockers in ppt-image-asset-ledger.json, then create the PDF plus editable source HTML under source-html/, keep independent strategy/render/file-write phases parallel where inputs allow, record ppt-parallel-report.json, run the bounded ppt-review-policy/ppt-review-ledger/ppt-iteration-report loop, and verify readability, overlap, format fit, source coverage, export state, unsupported-claim status, image-asset completion, review-loop termination, and temporary build files cleanup. Finish with reflection and Honest Mode; do not skip STP/audience questions for presentation artifacts.\n`,
@@ -930,10 +932,10 @@ async function removeDirIfEmpty(dir) {
930
932
  async function installCodexAgents(root) {
931
933
  const agents = {
932
934
  'analysis-scout.toml': `name = "analysis_scout"\ndescription = "Read-only Team analysis scout. Maps one independent repo/docs/tests/API/risk/user-friction slice and returns TriWiki-ready source-backed findings before debate starts."\nmodel = "gpt-5.5"\nmodel_reasoning_effort = "high"\nsandbox_mode = "read-only"\ndeveloper_instructions = """\nYou are an SKS Team analysis scout.\nDo not edit files.\nOwn exactly one investigation slice assigned by the parent orchestrator.\nMap relevant source files, docs, tests, APIs, DB or safety risks, UX friction, and likely implementation boundaries.\nReturn concise source-backed claims suitable for team-analysis.md and TriWiki ingestion: claim, source path, evidence hash or quoted anchor, risk, confidence, and recommended implementation slice.\nDo not debate the final plan and do not implement code.\nAlso return a concise LIVE_EVENT line that the parent can record with sks team event.\n"""\n`,
933
- 'team-consensus.toml': `name = "team_consensus"\ndescription = "Planning and debate specialist for SKS Team mode. Maps options, constraints, role-persona risks, and proposes the agreed objective before implementation starts."\nmodel = "gpt-5.5"\nmodel_reasoning_effort = "high"\nsandbox_mode = "read-only"\ndeveloper_instructions = """\nYou are the SKS Team consensus specialist.\nDo not edit files.\nMap the affected code paths, viable approaches, constraints, risks, and acceptance criteria.\nRun the debate as role-persona synthesis: final users are low-context, self-interested, stubborn, and inconvenience-averse; executors are capable developers; reviewers are strict.\nArgue for the smallest coherent objective that can be handed to a fresh executor_N development team.\nReturn: recommended objective, rejected alternatives, implementation slices, required reviewers, user-friction risks, and unresolved risks.\nAlso return a concise LIVE_EVENT line that the parent can record with sks team event.\n"""\n`,
935
+ 'team-consensus.toml': `name = "team_consensus"\ndescription = "Planning and debate specialist for SKS Team mode. Maps options, constraints, role-persona risks, and proposes the agreed objective before implementation starts."\nmodel = "gpt-5.5"\nmodel_reasoning_effort = "high"\nsandbox_mode = "read-only"\ndeveloper_instructions = """\nYou are the SKS Team consensus specialist.\nDo not edit files.\nMap the affected code paths, viable approaches, constraints, risks, and acceptance criteria.\nRun the debate as role-persona synthesis: final users are low-context, self-interested, stubborn, and inconvenience-averse; executors are capable developers; reviewers are strict.\nArgue for the smallest coherent objective that can be handed to a fresh executor_N development team.\nPlan for at least ${MIN_TEAM_REVIEWER_LANES} independent reviewer/QA validation lanes before integration or final.\nReturn: recommended objective, rejected alternatives, implementation slices, required reviewers, user-friction risks, and unresolved risks.\nAlso return a concise LIVE_EVENT line that the parent can record with sks team event.\n"""\n`,
934
936
  'implementation-worker.toml': `name = "implementation_worker"\ndescription = "Implementation specialist for SKS Team mode. Owns one bounded write set and coordinates with other executor_N workers."\nmodel = "gpt-5.5"\nmodel_reasoning_effort = "high"\nsandbox_mode = "workspace-write"\ndeveloper_instructions = """\nYou are an SKS Team executor/developer in the fresh development bundle.\nYou are not alone in the codebase. Other executor_N workers may be editing disjoint files.\nOnly edit the files or module slice assigned to you.\nDo not revert or overwrite edits made by others.\nRead local patterns first, make the smallest correct change, avoid adding user friction, run focused verification for your slice, and report changed paths plus evidence.\nDo not create fallback implementation code, substitute behavior, mock behavior, or compatibility shims unless the user or sealed decision contract explicitly requested them.\nRespect all SKS hooks, DB safety rules, no-question run rules, and H-Proof completion gates.\nAlso return concise LIVE_EVENT lines for started, blocked, changed files, verification, and final result so the parent can record them.\n"""\n`,
935
937
  'db-safety-reviewer.toml': `name = "db_safety_reviewer"\ndescription = "Read-only database safety reviewer for SQL, migrations, Supabase, RLS, destructive-operation risk, and rollback safety."\nmodel = "gpt-5.5"\nmodel_reasoning_effort = "high"\nsandbox_mode = "read-only"\ndeveloper_instructions = """\nYou are a database safety reviewer.\nNever modify files or execute destructive commands.\nReview migrations, SQL, Supabase RLS, transaction boundaries, rollback safety, and MCP database tool usage.\nBlock DROP, TRUNCATE, mass DELETE/UPDATE, db reset, db push, project deletion, branch reset/merge/delete, RLS disabling, and live execute_sql writes.\nReturn concrete risks, exact file references, and required fixes.\nAlso return a concise LIVE_EVENT line that the parent can record with sks team event.\n"""\n`,
936
- 'qa-reviewer.toml': `name = "qa_reviewer"\ndescription = "Strict read-only verification reviewer for correctness, regressions, missing tests, user friction, and final evidence."\nmodel = "gpt-5.5"\nmodel_reasoning_effort = "high"\nsandbox_mode = "read-only"\ndeveloper_instructions = """\nYou are an SKS Team strict reviewer.\nDo not edit files.\nReview correctness, edge cases, regression risk, missing tests, unsupported claims, and whether the final evidence proves the claimed outcome.\nAlso evaluate practical friction from the viewpoint of a stubborn, low-context final user who dislikes inconvenience.\nPrioritize concrete findings with file references and focused verification suggestions.\nFlag any unrequested fallback implementation code, substitute behavior, mock behavior, or compatibility shim as a blocking finding unless the user or sealed decision contract explicitly requested it.\nReturn no findings if the implementation is sound, and clearly list residual test gaps.\nAlso return a concise LIVE_EVENT line that the parent can record with sks team event.\n"""\n`
938
+ 'qa-reviewer.toml': `name = "qa_reviewer"\ndescription = "Strict read-only verification reviewer for correctness, regressions, missing tests, user friction, and final evidence."\nmodel = "gpt-5.5"\nmodel_reasoning_effort = "high"\nsandbox_mode = "read-only"\ndeveloper_instructions = """\nYou are an SKS Team strict reviewer.\nDo not edit files.\nReview correctness, edge cases, regression risk, missing tests, unsupported claims, and whether the final evidence proves the claimed outcome.\nTeam review must cover at least ${MIN_TEAM_REVIEWER_LANES} independent reviewer/QA validation lanes before integration or final; flag missing review lane evidence.\nAlso evaluate practical friction from the viewpoint of a stubborn, low-context final user who dislikes inconvenience.\nPrioritize concrete findings with file references and focused verification suggestions.\nFlag any unrequested fallback implementation code, substitute behavior, mock behavior, or compatibility shim as a blocking finding unless the user or sealed decision contract explicitly requested it.\nReturn no findings if the implementation is sound, and clearly list residual test gaps.\nAlso return a concise LIVE_EVENT line that the parent can record with sks team event.\n"""\n`
937
939
  };
938
940
  const dir = path.join(root, '.codex', 'agents');
939
941
  await ensureDir(dir);
@@ -46,8 +46,20 @@ export async function findLatestMission(root) {
46
46
  if (!(await exists(dir))) return null;
47
47
  const fs = await import('node:fs/promises');
48
48
  const entries = await fs.readdir(dir, { withFileTypes: true });
49
- const ids = entries.filter((e) => e.isDirectory() && e.name.startsWith('M-')).map((e) => e.name).sort();
50
- return ids.at(-1) || null;
49
+ const ids = entries.filter((e) => e.isDirectory() && e.name.startsWith('M-')).map((e) => e.name);
50
+ const candidates = await Promise.all(ids.map(async (id) => {
51
+ const dirPath = missionDir(root, id);
52
+ const stat = await fs.stat(dirPath).catch(() => null);
53
+ const mission = await readJson(path.join(dirPath, 'mission.json'), {}).catch(() => ({}));
54
+ const createdMs = Date.parse(mission.created_at || mission.updated_at || '');
55
+ return {
56
+ id,
57
+ createdMs: Number.isFinite(createdMs) ? createdMs : 0,
58
+ mtimeMs: stat?.mtimeMs || 0
59
+ };
60
+ }));
61
+ candidates.sort((a, b) => (a.createdMs - b.createdMs) || (a.mtimeMs - b.mtimeMs) || a.id.localeCompare(b.id));
62
+ return candidates.at(-1)?.id || null;
51
63
  }
52
64
 
53
65
  export async function setCurrent(root, patch) {
@@ -19,6 +19,7 @@ import { permissionGateSummary } from './permission-gates.mjs';
19
19
  import { CODEX_APP_IMAGE_GENERATION_DOC_URL, CODEX_COMPUTER_USE_EVIDENCE_SOURCE, CODEX_COMPUTER_USE_ONLY_POLICY, FROM_CHAT_IMG_CHECKLIST_ARTIFACT, FROM_CHAT_IMG_COVERAGE_ARTIFACT, FROM_CHAT_IMG_QA_LOOP_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS, chatCaptureIntakeText, context7RequirementText, dollarCommand, evidenceMentionsForbiddenBrowserAutomation, getdesignReferencePolicyText, hasFromChatImgSignal, hasMadSksSignal, noUnrequestedFallbackCodePolicyText, outcomeRubricPolicyText, pptPipelineAllowlistPolicyText, reflectionRequiredForRoute, reasoningInstruction, routeNeedsContext7, routePrompt, routeReasoning, routeRequiresSubagents, speedLanePolicyText, stripDollarCommand, stripMadSksSignal, subagentExecutionPolicyText, stackCurrentDocsPolicyText, triwikiContextTracking, triwikiContextTrackingText, triwikiStagePolicyText } from './routes.mjs';
20
20
  import { TEAM_DECOMPOSITION_ARTIFACT, TEAM_GRAPH_ARTIFACT, TEAM_INBOX_DIR, TEAM_RUNTIME_TASKS_ARTIFACT, teamRuntimePlanMetadata, teamRuntimeRequiredArtifacts, validateTeamRuntimeArtifacts, writeTeamRuntimeArtifacts } from './team-dag.mjs';
21
21
  import { formatRoleCounts, initTeamLive, parseTeamSpecText } from './team-live.mjs';
22
+ import { evaluateTeamReviewPolicyGate, MIN_TEAM_REVIEWER_LANES, MIN_TEAM_REVIEW_POLICY_TEXT, teamReviewPolicy } from './team-review-policy.mjs';
22
23
 
23
24
  export { routePrompt };
24
25
 
@@ -281,7 +282,7 @@ export function promptPipelineContext(prompt, route = routePrompt(prompt)) {
281
282
  'Before final answer, include a user-visible completion summary that explains what changed and how it was verified, then run SKS Honest Mode: verify evidence/tests, state gaps, and confirm the goal is genuinely complete.'
282
283
  ];
283
284
  if (reflectionRequiredForRoute(route)) lines.push(reflectionInstructionText());
284
- if (route?.id === 'Team') lines.push(`Team route: scouts, TriWiki refresh, debate, consensus, runtime graph compile with concrete task ids and worker inboxes, close planning agents, fresh executors, review/integration, ${TEAM_SESSION_CLEANUP_ARTIFACT}, reflection, and Honest Mode.`);
285
+ if (route?.id === 'Team') lines.push(`Team route: scouts, TriWiki refresh, debate, consensus, runtime graph compile with concrete task ids and worker inboxes, close planning agents, fresh executors, minimum ${MIN_TEAM_REVIEWER_LANES}-lane review/integration, ${TEAM_SESSION_CLEANUP_ARTIFACT}, reflection, and Honest Mode. ${MIN_TEAM_REVIEW_POLICY_TEXT}`);
285
286
  if (route?.id === 'Goal') lines.push('Goal route: write SKS goal bridge artifacts, then use Codex native /goal persistence for create, pause, resume, and clear continuation controls.');
286
287
  if (route?.id === 'PPT') lines.push(`PPT route: before design or PDF work, seal delivery context, audience profile including average age/job/industry, STP strategy, decision context, and at least three pain-point to solution mappings. Keep the visual system simple, restrained, and information-first; design detail should come from hierarchy, spacing, alignment, rules, and subtle accents rather than decorative overdesign. ${pptPipelineAllowlistPolicyText()} If generated image assets or slide visual critique are needed, use gpt-image-2/imagegen only when that asset/review need is explicitly sealed in the $PPT contract, preferring Codex App built-in image generation (${CODEX_APP_IMAGE_GENERATION_DOC_URL}) and using the OpenAI Image API when OPENAI_API_KEY is available; do not fabricate image files or image-review results when gpt-image-2 is unavailable. Then build source ledger, fact ledger, image asset ledger, storyboard with aha moments, style tokens, editable source HTML under source-html/, PDF artifact, render QA, bounded review ledger/iteration report, PPT-only temporary build file cleanup, and ppt-parallel-report.json so independent strategy/render/file-write phases stay parallel-friendly, then reflection and Honest Mode.`);
287
288
  if (route?.id === 'AutoResearch') lines.push('AutoResearch route: load autoresearch-loop plus seo-geo-optimizer when SEO/GEO, discoverability, README, npm, GitHub stars, ranking, or AI-search visibility is relevant.');
@@ -448,7 +449,7 @@ export async function activeRouteContext(root, state) {
448
449
  ? ' Context7 evidence is still required before completion: use resolve-library-id, then query-docs (or legacy get-library-docs).'
449
450
  : '';
450
451
  const roles = state.role_counts ? ` Role counts: ${formatRoleCounts(state.role_counts)}.` : '';
451
- return `Active Team mission ${state.mission_id || 'latest'} must keep the user-visible live transcript updated. Agent session budget: ${state.agent_sessions || 3}.${roles} Run scouts, TriWiki refresh, debate, consensus, fresh development, review/integration, then close or account for every Team subagent session and write ${TEAM_SESSION_CLEANUP_ARTIFACT} before reflection/final. After each subagent status/result/handoff, run: sks team event ${state.mission_id || 'latest'} --agent <name> --phase <phase> --message "...". Inspect with sks team log/watch ${state.mission_id || 'latest'}.${reasoningNote}${context7}${planNote}`;
452
+ return `Active Team mission ${state.mission_id || 'latest'} must keep the user-visible live transcript updated. Agent session budget: ${state.agent_sessions || MIN_TEAM_REVIEWER_LANES}.${roles} Run scouts, TriWiki refresh, debate, consensus, fresh development, minimum ${MIN_TEAM_REVIEWER_LANES}-lane review/integration, then close or account for every Team subagent session and write ${TEAM_SESSION_CLEANUP_ARTIFACT} before reflection/final. ${MIN_TEAM_REVIEW_POLICY_TEXT} After each subagent status/result/handoff, run: sks team event ${state.mission_id || 'latest'} --agent <name> --phase <phase> --message "...". Inspect with sks team log/watch ${state.mission_id || 'latest'}.${reasoningNote}${context7}${planNote}`;
452
453
  }
453
454
  if (state.subagents_required && !(await hasSubagentEvidence(root, state))) {
454
455
  return `Active SKS route ${id} requires subagent execution evidence before code-changing work can be considered complete. Spawn worker/reviewer subagents for disjoint write scopes, or record an explicit unavailable/unsplittable subagent evidence event before editing.${reasoningNote}${planNote}`;
@@ -688,9 +689,11 @@ async function materializeAutoSealedTeam(root, id, dir, route, task, contractHas
688
689
  mission_id: id,
689
690
  task: cleanTask,
690
691
  agent_session_count: agentSessions,
691
- default_agent_session_count: 3,
692
+ default_agent_session_count: MIN_TEAM_REVIEWER_LANES,
692
693
  role_counts: roleCounts,
693
694
  session_policy: `Use at most ${agentSessions} subagent sessions at a time; the parent orchestrator is not counted.`,
695
+ review_policy: teamReviewPolicy(),
696
+ review_gate: evaluateTeamReviewPolicyGate({ roleCounts, agentSessions, roster }),
694
697
  bundle_size: roster.bundle_size,
695
698
  roster,
696
699
  contract_hash: contractHash,
@@ -698,7 +701,8 @@ async function materializeAutoSealedTeam(root, id, dir, route, task, contractHas
698
701
  phases: ['parallel_analysis_scouts', 'triwiki_stage_refresh', 'debate_team', 'runtime_task_graph', 'development_team', 'review'],
699
702
  analysis_team: `Read-only parallel scouting with exactly ${roster.bundle_size} analysis_scout_N agents.`,
700
703
  debate_team: `Read-only role debate with exactly ${roster.bundle_size} participants.`,
701
- development_team: `Fresh parallel development bundle with exactly ${roster.bundle_size} executor_N developers implementing disjoint slices.`
704
+ development_team: `Fresh parallel development bundle with exactly ${roster.bundle_size} executor_N developers implementing disjoint slices.`,
705
+ review_team: `Validation runs at least ${MIN_TEAM_REVIEWER_LANES} independent reviewer/QA lanes before integration or final.`
702
706
  },
703
707
  context_tracking: triwikiContextTracking(),
704
708
  team_runtime: teamRuntimePlanMetadata(),
@@ -709,7 +713,7 @@ async function materializeAutoSealedTeam(root, id, dir, route, task, contractHas
709
713
  { id: 'planning_debate', goal: 'Run read-only planning debate, map constraints and implementation slices, then seal one objective.', agents: roster.debate_team.map((agent) => agent.id) },
710
714
  { id: 'runtime_task_graph_compile', goal: `Compile the agreed Team plan into ${TEAM_GRAPH_ARTIFACT}, ${TEAM_RUNTIME_TASKS_ARTIFACT}, ${TEAM_DECOMPOSITION_ARTIFACT}, and ${TEAM_INBOX_DIR}.`, agents: ['parent_orchestrator'], output: [TEAM_GRAPH_ARTIFACT, TEAM_RUNTIME_TASKS_ARTIFACT, TEAM_DECOMPOSITION_ARTIFACT, TEAM_INBOX_DIR] },
711
715
  { id: 'parallel_implementation', goal: `Close debate agents, then spawn a fresh ${roster.bundle_size}-person executor development team with non-overlapping write ownership.`, agents: roster.development_team.map((agent) => agent.id) },
712
- { id: 'review_integration', goal: 'Review, integrate, verify, and record evidence before final.', agents: roster.validation_team.map((agent) => agent.id) },
716
+ { id: 'review_integration', goal: `${MIN_TEAM_REVIEW_POLICY_TEXT} Integrate, verify, and record evidence before final.`, agents: roster.validation_team.map((agent) => agent.id), min_reviewer_lanes: MIN_TEAM_REVIEWER_LANES },
713
717
  { id: 'session_cleanup', goal: `Close or account for Team subagent sessions and write ${TEAM_SESSION_CLEANUP_ARTIFACT}.`, agents: ['parent_orchestrator'] }
714
718
  ],
715
719
  live_visibility: {
@@ -724,7 +728,7 @@ async function materializeAutoSealedTeam(root, id, dir, route, task, contractHas
724
728
  await writeJsonAtomic(path.join(dir, 'team-plan.json'), plan);
725
729
  await writeJsonAtomic(path.join(dir, 'team-roster.json'), { schema_version: 1, mission_id: id, role_counts: roleCounts, agent_sessions: agentSessions, bundle_size: roster.bundle_size, roster, confirmed: true, source: 'auto_sealed_team_spec' });
726
730
  const contextTracking = triwikiContextTracking();
727
- await writeTextAtomic(path.join(dir, 'team-workflow.md'), `# SKS Team Workflow\n\nTask: ${cleanTask}\n\nAgent session budget: ${agentSessions}\nBundle size: ${roster.bundle_size}\nRole counts: ${formatRoleCounts(roleCounts)}\nContext tracking: ${contextTracking.ssot} SSOT, ${contextTracking.default_pack}.\n\nAuto-sealed ambiguity gate: no user question was required. Continue directly with Team scouting, debate, runtime task graph, implementation, review, cleanup, reflection, and Honest Mode.\n`);
731
+ await writeTextAtomic(path.join(dir, 'team-workflow.md'), `# SKS Team Workflow\n\nTask: ${cleanTask}\n\nAgent session budget: ${agentSessions}\nBundle size: ${roster.bundle_size}\nRole counts: ${formatRoleCounts(roleCounts)}\nReview policy: ${MIN_TEAM_REVIEW_POLICY_TEXT}\nContext tracking: ${contextTracking.ssot} SSOT, ${contextTracking.default_pack}.\n\nAuto-sealed ambiguity gate: no user question was required. Continue directly with Team scouting, debate, runtime task graph, implementation, minimum ${MIN_TEAM_REVIEWER_LANES}-lane review, cleanup, reflection, and Honest Mode.\n`);
728
732
  await initTeamLive(id, dir, cleanTask, { agentSessions, roleCounts, roster });
729
733
  const runtime = await writeTeamRuntimeArtifacts(dir, plan, { contractHash });
730
734
  await writeMemorySweepReport(root, dir, { missionId: id }).catch(() => null);
@@ -774,16 +778,19 @@ async function prepareTeam(root, route, task, required, opts = {}) {
774
778
  mission_id: id,
775
779
  task: cleanTask,
776
780
  agent_session_count: agentSessions,
777
- default_agent_session_count: 3,
781
+ default_agent_session_count: MIN_TEAM_REVIEWER_LANES,
778
782
  role_counts: roleCounts,
779
783
  session_policy: `Use at most ${agentSessions} subagent sessions at a time; the parent orchestrator is not counted.`,
784
+ review_policy: teamReviewPolicy(),
785
+ review_gate: evaluateTeamReviewPolicyGate({ roleCounts, agentSessions, roster }),
780
786
  bundle_size: roster.bundle_size,
781
787
  roster,
782
788
  team_model: {
783
789
  phases: ['parallel_analysis_scouts', 'triwiki_stage_refresh', 'debate_team', 'triwiki_stage_refresh', 'runtime_task_graph', 'development_team', 'triwiki_stage_refresh', 'review'],
784
790
  analysis_team: `Read-only parallel scouting with exactly ${roster.bundle_size} analysis_scout_N agents. Each scout owns one investigation slice and returns TriWiki-ready findings with source paths, risks, and suggested implementation slices.`,
785
791
  debate_team: `Read-only role debate with exactly ${roster.bundle_size} participants composed from user, planner, reviewer, and executor voices applying compact Hyperplan-derived adversarial lenses.`,
786
- development_team: `Fresh parallel development bundle with exactly ${roster.bundle_size} executor_N developers implementing disjoint slices; validation_team reviews afterward.`
792
+ development_team: `Fresh parallel development bundle with exactly ${roster.bundle_size} executor_N developers implementing disjoint slices; validation_team reviews afterward.`,
793
+ review_team: `Validation runs at least ${MIN_TEAM_REVIEWER_LANES} independent reviewer/QA lanes before integration or final.`
787
794
  },
788
795
  context_tracking: triwikiContextTracking(),
789
796
  team_runtime: teamRuntimePlanMetadata(),
@@ -795,7 +802,7 @@ async function prepareTeam(root, route, task, required, opts = {}) {
795
802
  { id: 'consensus', goal: `Seal one objective with acceptance criteria and disjoint implementation slices, then refresh/validate TriWiki so implementation receives current consensus context.` },
796
803
  { id: 'runtime_task_graph_compile', goal: `Compile the agreed Team plan into ${TEAM_GRAPH_ARTIFACT}, ${TEAM_RUNTIME_TASKS_ARTIFACT}, and ${TEAM_DECOMPOSITION_ARTIFACT}; remap symbolic plan nodes to concrete task ids, allocate role/path/domain worker lanes, and write ${TEAM_INBOX_DIR} before executor work starts.`, agents: ['parent_orchestrator'], output: [TEAM_GRAPH_ARTIFACT, TEAM_RUNTIME_TASKS_ARTIFACT, TEAM_DECOMPOSITION_ARTIFACT, TEAM_INBOX_DIR] },
797
804
  { id: 'parallel_implementation', goal: `Before implementation, read relevant TriWiki context and current source. Close debate agents, then spawn a fresh ${roster.bundle_size}-person executor development team with non-overlapping write ownership. Refresh TriWiki after implementation changes or blockers.`, agents: roster.development_team.map((agent) => agent.id) },
798
- { id: 'review_integration', goal: 'Before review and final output, read/validate current TriWiki context, integrate executor output, strict review correctness/DB safety/tests, validate user friction with validation_team, refresh after review findings, and record evidence.', agents: roster.validation_team.map((agent) => agent.id) },
805
+ { id: 'review_integration', goal: `Before review and final output, read/validate current TriWiki context, integrate executor output, run at least ${MIN_TEAM_REVIEWER_LANES} independent reviewer/QA validation lanes for correctness/DB safety/tests/evidence, validate user friction with validation_team, refresh after review findings, and record evidence.`, agents: roster.validation_team.map((agent) => agent.id), min_reviewer_lanes: MIN_TEAM_REVIEWER_LANES },
799
806
  { id: 'session_cleanup', goal: `Close or account for all Team subagent sessions, finalize live transcript state, and write ${TEAM_SESSION_CLEANUP_ARTIFACT} before reflection or final.`, agents: ['parent_orchestrator'] }
800
807
  ],
801
808
  live_visibility: {
@@ -810,7 +817,7 @@ async function prepareTeam(root, route, task, required, opts = {}) {
810
817
  await writeJsonAtomic(path.join(dir, 'team-plan.json'), plan);
811
818
  await writeJsonAtomic(path.join(dir, 'team-roster.json'), { schema_version: 1, mission_id: id, role_counts: roleCounts, agent_sessions: agentSessions, bundle_size: roster.bundle_size, roster, confirmed: true, source: 'default_or_prompt_team_spec' });
812
819
  const contextTracking = triwikiContextTracking();
813
- await writeTextAtomic(path.join(dir, 'team-workflow.md'), `# SKS Team Workflow\n\nTask: ${cleanTask}\n\nAgent session budget: ${agentSessions}\nBundle size: ${roster.bundle_size}\nRole counts: ${formatRoleCounts(roleCounts)}\nReasoning: high for team logic, temporary for this route only.\nContext tracking: ${contextTracking.ssot} SSOT, ${contextTracking.default_pack}; use relevant TriWiki context before every work stage, refresh/validate after findings, and preserve hydratable source anchors.\n\n1. Run exactly ${roster.bundle_size} read-only analysis_scout_N agents and write team-analysis.md.\n2. Refresh/validate TriWiki before debate.\n3. Run exactly ${roster.bundle_size} debate participants through the compact Hyperplan-derived adversarial lens pass, then write consensus and implementation slices.\n4. Compile ${TEAM_GRAPH_ARTIFACT}, ${TEAM_RUNTIME_TASKS_ARTIFACT}, ${TEAM_DECOMPOSITION_ARTIFACT}, and ${TEAM_INBOX_DIR} so worker handoff uses concrete runtime task ids.\n5. Close debate agents before starting a fresh ${roster.bundle_size}-person executor team.\n6. Review, integrate, verify, and record evidence.\n7. Close/clean remaining Team sessions, finalize live transcript state, and write ${TEAM_SESSION_CLEANUP_ARTIFACT} before reflection/final.\n\nNo unrequested fallback implementation code is allowed in any stage, executor lane, review lane, MAD route, or MAD-SKS route. If the requested path cannot be implemented inside the sealed contract, block with evidence instead of adding substitute behavior.\n\nLive visibility:\n- sks team log ${id}\n- sks team tail ${id}\n- sks team watch ${id}\n- sks team lane ${id} --agent analysis_scout_1 --follow\n- sks team event ${id} --agent <name> --phase <phase> --message \"...\"\n`);
820
+ await writeTextAtomic(path.join(dir, 'team-workflow.md'), `# SKS Team Workflow\n\nTask: ${cleanTask}\n\nAgent session budget: ${agentSessions}\nBundle size: ${roster.bundle_size}\nRole counts: ${formatRoleCounts(roleCounts)}\nReview policy: ${MIN_TEAM_REVIEW_POLICY_TEXT}\nReasoning: high for team logic, temporary for this route only.\nContext tracking: ${contextTracking.ssot} SSOT, ${contextTracking.default_pack}; use relevant TriWiki context before every work stage, refresh/validate after findings, and preserve hydratable source anchors.\n\n1. Run exactly ${roster.bundle_size} read-only analysis_scout_N agents and write team-analysis.md.\n2. Refresh/validate TriWiki before debate.\n3. Run exactly ${roster.bundle_size} debate participants through the compact Hyperplan-derived adversarial lens pass, then write consensus and implementation slices.\n4. Compile ${TEAM_GRAPH_ARTIFACT}, ${TEAM_RUNTIME_TASKS_ARTIFACT}, ${TEAM_DECOMPOSITION_ARTIFACT}, and ${TEAM_INBOX_DIR} so worker handoff uses concrete runtime task ids.\n5. Close debate agents before starting a fresh ${roster.bundle_size}-person executor team.\n6. Run at least ${MIN_TEAM_REVIEWER_LANES} independent reviewer/QA validation lanes, integrate, verify, and record evidence.\n7. Close/clean remaining Team sessions, finalize live transcript state, and write ${TEAM_SESSION_CLEANUP_ARTIFACT} before reflection/final.\n\nNo unrequested fallback implementation code is allowed in any stage, executor lane, review lane, MAD route, or MAD-SKS route. If the requested path cannot be implemented inside the sealed contract, block with evidence instead of adding substitute behavior.\n\nLive visibility:\n- sks team log ${id}\n- sks team tail ${id}\n- sks team watch ${id}\n- sks team lane ${id} --agent analysis_scout_1 --follow\n- sks team event ${id} --agent <name> --phase <phase> --message \"...\"\n`);
814
821
  await initTeamLive(id, dir, cleanTask, { agentSessions, roleCounts, roster });
815
822
  const runtime = await writeTeamRuntimeArtifacts(dir, plan, {});
816
823
  await writeMemorySweepReport(root, dir, { missionId: id }).catch(() => null);
@@ -823,7 +830,7 @@ async function prepareTeam(root, route, task, required, opts = {}) {
823
830
  : {};
824
831
  const pipelinePlan = await writePipelinePlan(dir, { missionId: id, route, task: cleanTask, required, ambiguity: { required: false, status: 'direct_team_cli' } });
825
832
  await setCurrent(root, routeState(id, route, 'TEAM_PARALLEL_ANALYSIS_SCOUTING', required, { prompt: cleanTask, implementation_allowed: true, ambiguity_gate_required: false, ambiguity_gate_passed: true, agent_sessions: agentSessions, role_counts: roleCounts, team_roster_confirmed: true, team_plan_ready: true, team_graph_ready: runtime.ok, context_tracking: 'triwiki', from_chat_img_required: fromChatImgRequired, pipeline_plan_ready: validatePipelinePlan(pipelinePlan).ok, pipeline_plan_path: PIPELINE_PLAN_ARTIFACT, ...madSksState }));
826
- return routeContext(route, id, cleanTask, required, `Run scouts, refresh/validate TriWiki, debate, close debate agents, form a fresh ${roster.bundle_size}-person executor team, then close/clean Team sessions and write ${TEAM_SESSION_CLEANUP_ARTIFACT} before reflection.`);
833
+ return routeContext(route, id, cleanTask, required, `Run scouts, refresh/validate TriWiki, debate, close debate agents, form a fresh ${roster.bundle_size}-person executor team, run minimum ${MIN_TEAM_REVIEWER_LANES}-lane review/integration, then close/clean Team sessions and write ${TEAM_SESSION_CLEANUP_ARTIFACT} before reflection.`);
827
834
  }
828
835
 
829
836
  async function prepareResearch(root, route, task, required) {
@@ -270,7 +270,7 @@ export const ROUTES = [
270
270
  context7Policy: 'optional',
271
271
  reasoningPolicy: 'high',
272
272
  stopGate: 'team-gate.json',
273
- cliEntrypoint: 'sks team "task" [executor:5 reviewer:2 user:1] | sks team log|tail|watch|lane|status|event|message|cleanup-tmux',
273
+ cliEntrypoint: 'sks team "task" [executor:5 reviewer:6 user:1] | sks team log|tail|watch|lane|status|event|message|cleanup-tmux',
274
274
  examples: ['$Team executor:5 agree on the best plan and implement it', '$From-Chat-IMG 채팅+첨부 이미지 작업 지시서']
275
275
  },
276
276
  {
@@ -489,7 +489,7 @@ export const COMMAND_CATALOG = [
489
489
  { name: 'validate-artifacts', usage: 'sks validate-artifacts [mission-id|latest] [--json]', description: 'Validate schema-backed mission artifacts for work orders, effort decisions, visual maps, dogfood reports, skills, mistake memory, Team dashboard state, and Honest Mode.' },
490
490
  { name: 'wiki', usage: 'sks wiki coords|pack|refresh|prune|validate ...', description: 'Build, refresh, prune, and validate RGBA/trig LLM Wiki context packs with attention.use_first and attention.hydrate_first for compact recall plus source hydration.' },
491
491
  { name: 'hproof', usage: 'sks hproof check [mission-id|latest]', description: 'Evaluate the H-Proof done gate for a mission.' },
492
- { name: 'team', usage: 'sks team "task" [executor:5 reviewer:2 user:1]|log|tail|watch|lane|status|dashboard|event|message|cleanup-tmux ...', description: 'Create and observe a scout-first Team mission with color-coded tmux lanes, transcript messages, and cleanup-aware follow panes.' },
492
+ { name: 'team', usage: 'sks team "task" [executor:5 reviewer:6 user:1]|log|tail|watch|lane|status|dashboard|event|message|cleanup-tmux ...', description: 'Create and observe a scout-first Team mission with at least five reviewer/QA validation lanes, color-coded tmux lanes, transcript messages, and cleanup-aware follow panes.' },
493
493
  { name: 'reasoning', usage: 'sks reasoning ["prompt"] [--json]', description: 'Show SKS temporary reasoning-effort routing: medium for simple tasks, high for logic, xhigh for research.' },
494
494
  { name: 'gx', usage: 'sks gx init|render|validate|drift|snapshot [name]', description: 'Create and verify deterministic SVG/HTML visual context cartridges.' },
495
495
  { name: 'profile', usage: 'sks profile show|set <model>', description: 'Inspect or set the current SKS model profile metadata.' },
@@ -1,11 +1,13 @@
1
1
  import path from 'node:path';
2
2
  import { appendJsonlBounded, nowIso, readJson, readText, writeJsonAtomic, writeTextAtomic } from './fsx.mjs';
3
3
  import { triwikiContextTracking, triwikiContextTrackingText } from './routes.mjs';
4
+ import { MIN_TEAM_REVIEWER_LANES, MIN_TEAM_REVIEW_STAGE_AGENT_SESSIONS } from './team-review-policy.mjs';
5
+ export { MIN_TEAM_REVIEWER_LANES, MIN_TEAM_REVIEW_POLICY_TEXT, MIN_TEAM_REVIEW_STAGE_AGENT_SESSIONS, evaluateTeamReviewPolicyGate, teamReviewPolicy } from './team-review-policy.mjs';
4
6
 
5
7
  const MAX_LIVE_BYTES = 192 * 1024;
6
8
  const TEAM_RUNTIME_TASKS_ARTIFACT = 'team-runtime-tasks.json';
7
9
  const DEFAULT_AGENTS = ['parent_orchestrator', 'analysis_scout', 'team_consensus', 'implementation_worker', 'db_safety_reviewer', 'qa_reviewer'];
8
- export const DEFAULT_TEAM_ROLE_COUNTS = { user: 1, planner: 1, reviewer: 1, executor: 3 };
10
+ export const DEFAULT_TEAM_ROLE_COUNTS = { user: 1, planner: 1, reviewer: MIN_TEAM_REVIEWER_LANES, executor: 3 };
9
11
  export const DEFAULT_MAX_TEAM_AGENT_SESSIONS = 6;
10
12
  const ROLE_ALIASES = {
11
13
  user: 'user',
@@ -107,6 +109,7 @@ ${prompt}
107
109
  - Debate uses compact Hyperplan-derived adversarial lenses: challenge framing, subtract surface, demand evidence, test integration risk, and consider one simpler alternative.
108
110
  - User personas are intentionally impatient, self-interested, stubborn, low-context, and dislike inconvenience.
109
111
  - Executors are capable developers with disjoint ownership.
112
+ - Team reviewer lane policy enforces at least ${MIN_TEAM_REVIEWER_LANES} strict reviewers and enough review-stage parallel capacity.
110
113
  - Reviewers are strict and adversarial about correctness, safety, tests, and evidence.
111
114
  - Every useful subagent status, debate result, handoff, review finding, and integration decision must be appended here.
112
115
  - Before reflection/final, close or account for all Team subagent sessions and write team-session-cleanup.json.
@@ -247,7 +250,8 @@ export function normalizeTeamSpec(opts = {}) {
247
250
  roleCounts.executor = normalizeTeamAgentSessions(opts.agentSessions, roleCounts.executor);
248
251
  }
249
252
  const bundleSize = normalizeTeamAgentSessions(roleCounts.executor, DEFAULT_TEAM_ROLE_COUNTS.executor);
250
- const agentSessions = normalizeTeamAgentSessions(opts.agentSessions ?? bundleSize);
253
+ const reviewStageSessions = normalizeTeamAgentSessions(roleCounts.reviewer, MIN_TEAM_REVIEW_STAGE_AGENT_SESSIONS);
254
+ const agentSessions = Math.max(normalizeTeamAgentSessions(opts.agentSessions ?? bundleSize), reviewStageSessions);
251
255
  return { agentSessions, bundleSize, roleCounts, roster: buildTeamRoster(roleCounts) };
252
256
  }
253
257
 
@@ -257,6 +261,7 @@ export function normalizeTeamRoleCounts(input = {}) {
257
261
  const role = normalizeTeamRole(key);
258
262
  if (role) counts[role] = normalizeTeamAgentSessions(value, counts[role] || 1);
259
263
  }
264
+ counts.reviewer = Math.max(MIN_TEAM_REVIEWER_LANES, counts.reviewer);
260
265
  return counts;
261
266
  }
262
267
 
@@ -0,0 +1,49 @@
1
+ export const MIN_TEAM_REVIEWER_LANES = 5;
2
+ export const MIN_TEAM_REVIEW_STAGE_AGENT_SESSIONS = MIN_TEAM_REVIEWER_LANES;
3
+ export const MIN_TEAM_REVIEW_POLICY_TEXT = `Minimum Team review policy: run at least ${MIN_TEAM_REVIEWER_LANES} independent reviewer/QA validation lanes before integration or final, even when the requested reviewer role count is lower.`;
4
+
5
+ function numericCount(value, fallback = 0) {
6
+ const number = Number(value);
7
+ return Number.isFinite(number) ? Math.max(0, Math.floor(number)) : fallback;
8
+ }
9
+
10
+ export function teamReviewPolicy() {
11
+ return {
12
+ gate: 'team_review_policy',
13
+ minimum_reviewer_lanes: MIN_TEAM_REVIEWER_LANES,
14
+ minimum_review_stage_agent_sessions: MIN_TEAM_REVIEW_STAGE_AGENT_SESSIONS,
15
+ text: MIN_TEAM_REVIEW_POLICY_TEXT
16
+ };
17
+ }
18
+
19
+ export function teamValidationReviewerCount(roster = {}) {
20
+ const validation = Array.isArray(roster?.validation_team) ? roster.validation_team : [];
21
+ return validation.filter((agent) => {
22
+ const id = String(agent?.id || agent || '');
23
+ const role = String(agent?.role || '');
24
+ return /review|qa|validation/i.test(`${role} ${id}`);
25
+ }).length;
26
+ }
27
+
28
+ export function evaluateTeamReviewPolicyGate({ roleCounts = {}, agentSessions = 0, roster = {} } = {}) {
29
+ const requestedReviewerLanes = numericCount(roleCounts.reviewer);
30
+ const requiredReviewerLanes = Math.max(MIN_TEAM_REVIEWER_LANES, requestedReviewerLanes);
31
+ const validationReviewerLanes = teamValidationReviewerCount(roster);
32
+ const sessionCount = numericCount(agentSessions);
33
+ const blockers = [];
34
+
35
+ if (requestedReviewerLanes < MIN_TEAM_REVIEWER_LANES) blockers.push('role_counts.reviewer_below_minimum');
36
+ if (validationReviewerLanes < requiredReviewerLanes) blockers.push('validation_team_reviewers_below_required');
37
+ if (sessionCount < requiredReviewerLanes) blockers.push('agent_sessions_below_review_required');
38
+
39
+ return {
40
+ gate: 'team_review_policy',
41
+ passed: blockers.length === 0,
42
+ blockers,
43
+ minimum_reviewer_lanes: MIN_TEAM_REVIEWER_LANES,
44
+ required_reviewer_lanes: requiredReviewerLanes,
45
+ requested_reviewer_lanes: requestedReviewerLanes,
46
+ validation_reviewer_lanes: validationReviewerLanes,
47
+ agent_sessions: sessionCount
48
+ };
49
+ }
@@ -4,6 +4,7 @@ import { spawnSync } from 'node:child_process';
4
4
  import { exists, nowIso, packageRoot, readJson, runProcess, sha256, sksRoot, which, writeJsonAtomic } from './fsx.mjs';
5
5
  import { getCodexInfo } from './codex-adapter.mjs';
6
6
  import { codexAppIntegrationStatus, formatCodexAppStatus } from './codex-app.mjs';
7
+ import { MIN_TEAM_REVIEWER_LANES } from './team-review-policy.mjs';
7
8
 
8
9
  export const SKS_TMUX_LOGO = [
9
10
  ' _______ __ __ _______',
@@ -483,25 +484,48 @@ function printTmuxLaunchBlocked(plan, opts = {}) {
483
484
  for (const blocker of Array.from(new Set(plan.blockers))) console.log(`- ${blocker}`);
484
485
  }
485
486
 
486
- export async function launchTmuxTeamView({ root, missionId, plan = {}, promptFile = null, json = false } = {}) {
487
- const launch = await buildTmuxLaunchPlan({ root, session: `sks-team-${missionId}` });
488
- const agents = [
489
- ...(plan.roster?.analysis_team || []),
490
- ...(plan.roster?.debate_team || []),
491
- ...(plan.roster?.development_team || []),
492
- ...(plan.roster?.validation_team || [])
493
- ];
494
- const uniqueAgents = [];
487
+ function uniqueAgentIds(agents = []) {
488
+ const ids = [];
495
489
  const seen = new Set();
496
490
  for (const agent of agents) {
497
- const id = agent.id || String(agent);
498
- if (seen.has(id)) continue;
491
+ const id = agent?.id || String(agent || '');
492
+ if (!id || seen.has(id)) continue;
499
493
  seen.add(id);
500
- uniqueAgents.push(id);
494
+ ids.push(id);
501
495
  }
502
- const commands = uniqueAgents.slice(0, Math.max(1, plan.agent_session_count || 3)).map((agentId, index) => ({
496
+ return ids;
497
+ }
498
+
499
+ function teamViewAgentIds(plan = {}) {
500
+ const roster = plan.roster || {};
501
+ const analysis = uniqueAgentIds(roster.analysis_team || []);
502
+ const debate = uniqueAgentIds(roster.debate_team || []);
503
+ const development = uniqueAgentIds(roster.development_team || []);
504
+ const validation = uniqueAgentIds(roster.validation_team || []);
505
+ const reviewers = validation.filter((id) => teamLaneStyle(id).role === 'review');
506
+ const reviewerTarget = Math.max(MIN_TEAM_REVIEWER_LANES, Number(plan.review_policy?.minimum_reviewer_lanes) || 0, Number(plan.role_counts?.reviewer) || 0);
507
+ const reviewLanes = reviewers.slice(0, reviewerTarget);
508
+ const representative = [analysis[0], development[0], debate[0]].filter(Boolean);
509
+ const ordered = [...reviewLanes, ...representative, ...analysis, ...debate, ...development, ...validation];
510
+ const limit = Math.max(Number(plan.agent_session_count) || MIN_TEAM_REVIEWER_LANES, reviewLanes.length + representative.length);
511
+ return uniqueAgentIds(ordered).slice(0, Math.max(1, limit));
512
+ }
513
+
514
+ function teamLanePhase(agentId = '') {
515
+ const role = teamLaneStyle(agentId).role;
516
+ if (role === 'review') return 'review';
517
+ if (role === 'execution') return 'implementation';
518
+ if (role === 'scout') return 'analysis';
519
+ if (role === 'safety') return 'safety';
520
+ return 'team';
521
+ }
522
+
523
+ export async function launchTmuxTeamView({ root, missionId, plan = {}, promptFile = null, json = false } = {}) {
524
+ const launch = await buildTmuxLaunchPlan({ root, session: `sks-team-${missionId}` });
525
+ const visibleAgents = teamViewAgentIds(plan);
526
+ const commands = visibleAgents.map((agentId) => ({
503
527
  agent: agentId,
504
- command: teamAgentCommand(launch.root, missionId, agentId, index === 0 ? 'analysis' : 'team', promptFile),
528
+ command: teamAgentCommand(launch.root, missionId, agentId, teamLanePhase(agentId), promptFile),
505
529
  style: teamLaneStyle(agentId),
506
530
  title: teamLaneTitle(agentId)
507
531
  }));