sneakoscope 0.7.49 → 0.7.51
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 +7 -3
- package/package.json +1 -1
- package/src/cli/main.mjs +144 -14
- package/src/cli/maintenance-commands.mjs +19 -10
- package/src/core/fsx.mjs +1 -1
- package/src/core/image-ux-review.mjs +298 -0
- package/src/core/init.mjs +22 -9
- package/src/core/mission.mjs +14 -2
- package/src/core/pipeline.mjs +102 -12
- package/src/core/routes.mjs +39 -4
- package/src/core/team-live.mjs +7 -2
- package/src/core/team-review-policy.mjs +49 -0
- package/src/core/tmux-ui.mjs +38 -14
package/src/core/pipeline.mjs
CHANGED
|
@@ -14,11 +14,13 @@ import { MISTAKE_RECALL_ARTIFACT, mistakeRecallGateStatus } from './mistake-reca
|
|
|
14
14
|
import { recordSkillDreamEvent, skillDreamPolicyText, writeSkillForgeReport } from './skill-forge.mjs';
|
|
15
15
|
import { writeResearchPlan } from './research.mjs';
|
|
16
16
|
import { PPT_REQUIRED_GATE_FIELDS } from './ppt.mjs';
|
|
17
|
+
import { IMAGE_UX_REVIEW_GATE_ARTIFACT, IMAGE_UX_REVIEW_POLICY_ARTIFACT, IMAGE_UX_REVIEW_SCREEN_INVENTORY_ARTIFACT, IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT, IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT, IMAGE_UX_REVIEW_ITERATION_REPORT_ARTIFACT, IMAGE_UX_REVIEW_REQUIRED_GATE_FIELDS, writeImageUxReviewRouteArtifacts } from './image-ux-review.mjs';
|
|
17
18
|
import { SPEED_LANE_POLICY } from './proof-field.mjs';
|
|
18
19
|
import { permissionGateSummary } from './permission-gates.mjs';
|
|
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
|
+
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, imageUxReviewPipelinePolicyText, noUnrequestedFallbackCodePolicyText, outcomeRubricPolicyText, pptPipelineAllowlistPolicyText, reflectionRequiredForRoute, reasoningInstruction, routeNeedsContext7, routePrompt, routeReasoning, routeRequiresSubagents, speedLanePolicyText, stripDollarCommand, stripMadSksSignal, subagentExecutionPolicyText, stackCurrentDocsPolicyText, triwikiContextTracking, triwikiContextTrackingText, triwikiStagePolicyText } from './routes.mjs';
|
|
20
21
|
import { TEAM_DECOMPOSITION_ARTIFACT, TEAM_GRAPH_ARTIFACT, TEAM_INBOX_DIR, TEAM_RUNTIME_TASKS_ARTIFACT, teamRuntimePlanMetadata, teamRuntimeRequiredArtifacts, validateTeamRuntimeArtifacts, writeTeamRuntimeArtifacts } from './team-dag.mjs';
|
|
21
22
|
import { formatRoleCounts, initTeamLive, parseTeamSpecText } from './team-live.mjs';
|
|
23
|
+
import { evaluateTeamReviewPolicyGate, MIN_TEAM_REVIEWER_LANES, MIN_TEAM_REVIEW_POLICY_TEXT, teamReviewPolicy } from './team-review-policy.mjs';
|
|
22
24
|
|
|
23
25
|
export { routePrompt };
|
|
24
26
|
|
|
@@ -281,9 +283,10 @@ export function promptPipelineContext(prompt, route = routePrompt(prompt)) {
|
|
|
281
283
|
'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
284
|
];
|
|
283
285
|
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
|
|
286
|
+
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
287
|
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
288
|
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.`);
|
|
289
|
+
if (route?.id === 'ImageUXReview') lines.push(`Image UX Review route: ${imageUxReviewPipelinePolicyText()} Use ${IMAGE_UX_REVIEW_POLICY_ARTIFACT}, ${IMAGE_UX_REVIEW_SCREEN_INVENTORY_ARTIFACT}, ${IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT}, ${IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT}, ${IMAGE_UX_REVIEW_ITERATION_REPORT_ARTIFACT}, and ${IMAGE_UX_REVIEW_GATE_ARTIFACT} as the route evidence set. The route may suggest safe fixes only when the user requested fixing; otherwise report findings and blockers.`);
|
|
287
290
|
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.');
|
|
288
291
|
if (route?.id === 'DB') lines.push('DB route: scan/check database risk first; destructive DB operations remain forbidden.');
|
|
289
292
|
if (route?.id === 'GX') lines.push('GX route: use deterministic vgraph/beta render, validate, drift, and snapshot artifacts.');
|
|
@@ -336,6 +339,7 @@ export async function prepareRoute(root, prompt, state = {}) {
|
|
|
336
339
|
if (route.id === 'ComputerUse') return withSkillDreamContext(await prepareComputerUseFastRoute(route, task), dreamContext);
|
|
337
340
|
if (route.id === 'Wiki') return withSkillDreamContext(await prepareWikiQuickRoute(route, task), dreamContext);
|
|
338
341
|
if (route.id === 'Goal') return withSkillDreamContext(await prepareGoal(root, route, task, routeNeedsContext7(route, prompt)), dreamContext);
|
|
342
|
+
if (route.id === 'ImageUXReview') return withSkillDreamContext(await prepareImageUxReview(root, route, task, routeNeedsContext7(route, prompt)), dreamContext);
|
|
339
343
|
const required = routeNeedsContext7(route, prompt);
|
|
340
344
|
const reasoning = routeReasoning(route, prompt);
|
|
341
345
|
const subagentsRequired = routeRequiresSubagents(route, prompt);
|
|
@@ -428,6 +432,45 @@ async function prepareWikiQuickRoute(route, task) {
|
|
|
428
432
|
};
|
|
429
433
|
}
|
|
430
434
|
|
|
435
|
+
async function prepareImageUxReview(root, route, task, required) {
|
|
436
|
+
const { id, dir, mission } = await createMission(root, { mode: 'image-ux-review', prompt: task });
|
|
437
|
+
const contract = {
|
|
438
|
+
prompt: task,
|
|
439
|
+
answers: {
|
|
440
|
+
TARGET_SURFACE: task,
|
|
441
|
+
IMAGE_UX_REVIEW_SOURCE_IMAGES: []
|
|
442
|
+
},
|
|
443
|
+
sealed_hash: null,
|
|
444
|
+
mission_id: id
|
|
445
|
+
};
|
|
446
|
+
const artifacts = await writeImageUxReviewRouteArtifacts(dir, contract);
|
|
447
|
+
await writeJsonAtomic(path.join(dir, 'route-context.json'), {
|
|
448
|
+
route: route.id,
|
|
449
|
+
command: route.command,
|
|
450
|
+
mode: route.mode,
|
|
451
|
+
task,
|
|
452
|
+
mission_id: mission.id,
|
|
453
|
+
required_skills: route.requiredSkills,
|
|
454
|
+
context7_required: required,
|
|
455
|
+
context_tracking: triwikiContextTracking(),
|
|
456
|
+
stop_gate: route.stopGate,
|
|
457
|
+
artifact_policy: 'imagegen_generated_review_image_required_before_issue_extraction'
|
|
458
|
+
});
|
|
459
|
+
const pipelinePlan = await writePipelinePlan(dir, { missionId: id, route, task, required, ambiguity: { required: false, status: 'direct_route' } });
|
|
460
|
+
await setCurrent(root, routeState(id, route, 'IMAGE_UX_REVIEW_READY', required, {
|
|
461
|
+
prompt: task,
|
|
462
|
+
implementation_allowed: true,
|
|
463
|
+
ambiguity_gate_required: false,
|
|
464
|
+
ambiguity_gate_passed: true,
|
|
465
|
+
stop_gate: route.stopGate,
|
|
466
|
+
image_ux_review_gate_ready: true,
|
|
467
|
+
image_ux_review_policy_ready: true,
|
|
468
|
+
pipeline_plan_ready: validatePipelinePlan(pipelinePlan).ok,
|
|
469
|
+
pipeline_plan_path: PIPELINE_PLAN_ARTIFACT
|
|
470
|
+
}));
|
|
471
|
+
return routeContext(route, id, task, required, `Capture or attach source UI screenshots, run Codex App $imagegen/gpt-image-2 to generate annotated review images, extract those generated images into ${IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT}, then update ${IMAGE_UX_REVIEW_GATE_ARTIFACT}. Initial gate blockers: ${(artifacts.gate.blockers || []).join(', ') || 'none'}.`);
|
|
472
|
+
}
|
|
473
|
+
|
|
431
474
|
export async function activeRouteContext(root, state) {
|
|
432
475
|
if (!state?.route && !state?.mode) return '';
|
|
433
476
|
const id = state.route || state.mode;
|
|
@@ -448,7 +491,7 @@ export async function activeRouteContext(root, state) {
|
|
|
448
491
|
? ' Context7 evidence is still required before completion: use resolve-library-id, then query-docs (or legacy get-library-docs).'
|
|
449
492
|
: '';
|
|
450
493
|
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 ||
|
|
494
|
+
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
495
|
}
|
|
453
496
|
if (state.subagents_required && !(await hasSubagentEvidence(root, state))) {
|
|
454
497
|
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 +731,11 @@ async function materializeAutoSealedTeam(root, id, dir, route, task, contractHas
|
|
|
688
731
|
mission_id: id,
|
|
689
732
|
task: cleanTask,
|
|
690
733
|
agent_session_count: agentSessions,
|
|
691
|
-
default_agent_session_count:
|
|
734
|
+
default_agent_session_count: MIN_TEAM_REVIEWER_LANES,
|
|
692
735
|
role_counts: roleCounts,
|
|
693
736
|
session_policy: `Use at most ${agentSessions} subagent sessions at a time; the parent orchestrator is not counted.`,
|
|
737
|
+
review_policy: teamReviewPolicy(),
|
|
738
|
+
review_gate: evaluateTeamReviewPolicyGate({ roleCounts, agentSessions, roster }),
|
|
694
739
|
bundle_size: roster.bundle_size,
|
|
695
740
|
roster,
|
|
696
741
|
contract_hash: contractHash,
|
|
@@ -698,7 +743,8 @@ async function materializeAutoSealedTeam(root, id, dir, route, task, contractHas
|
|
|
698
743
|
phases: ['parallel_analysis_scouts', 'triwiki_stage_refresh', 'debate_team', 'runtime_task_graph', 'development_team', 'review'],
|
|
699
744
|
analysis_team: `Read-only parallel scouting with exactly ${roster.bundle_size} analysis_scout_N agents.`,
|
|
700
745
|
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
|
|
746
|
+
development_team: `Fresh parallel development bundle with exactly ${roster.bundle_size} executor_N developers implementing disjoint slices.`,
|
|
747
|
+
review_team: `Validation runs at least ${MIN_TEAM_REVIEWER_LANES} independent reviewer/QA lanes before integration or final.`
|
|
702
748
|
},
|
|
703
749
|
context_tracking: triwikiContextTracking(),
|
|
704
750
|
team_runtime: teamRuntimePlanMetadata(),
|
|
@@ -709,7 +755,7 @@ async function materializeAutoSealedTeam(root, id, dir, route, task, contractHas
|
|
|
709
755
|
{ 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
756
|
{ 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
757
|
{ 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:
|
|
758
|
+
{ 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
759
|
{ id: 'session_cleanup', goal: `Close or account for Team subagent sessions and write ${TEAM_SESSION_CLEANUP_ARTIFACT}.`, agents: ['parent_orchestrator'] }
|
|
714
760
|
],
|
|
715
761
|
live_visibility: {
|
|
@@ -724,7 +770,7 @@ async function materializeAutoSealedTeam(root, id, dir, route, task, contractHas
|
|
|
724
770
|
await writeJsonAtomic(path.join(dir, 'team-plan.json'), plan);
|
|
725
771
|
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
772
|
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`);
|
|
773
|
+
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
774
|
await initTeamLive(id, dir, cleanTask, { agentSessions, roleCounts, roster });
|
|
729
775
|
const runtime = await writeTeamRuntimeArtifacts(dir, plan, { contractHash });
|
|
730
776
|
await writeMemorySweepReport(root, dir, { missionId: id }).catch(() => null);
|
|
@@ -774,16 +820,19 @@ async function prepareTeam(root, route, task, required, opts = {}) {
|
|
|
774
820
|
mission_id: id,
|
|
775
821
|
task: cleanTask,
|
|
776
822
|
agent_session_count: agentSessions,
|
|
777
|
-
default_agent_session_count:
|
|
823
|
+
default_agent_session_count: MIN_TEAM_REVIEWER_LANES,
|
|
778
824
|
role_counts: roleCounts,
|
|
779
825
|
session_policy: `Use at most ${agentSessions} subagent sessions at a time; the parent orchestrator is not counted.`,
|
|
826
|
+
review_policy: teamReviewPolicy(),
|
|
827
|
+
review_gate: evaluateTeamReviewPolicyGate({ roleCounts, agentSessions, roster }),
|
|
780
828
|
bundle_size: roster.bundle_size,
|
|
781
829
|
roster,
|
|
782
830
|
team_model: {
|
|
783
831
|
phases: ['parallel_analysis_scouts', 'triwiki_stage_refresh', 'debate_team', 'triwiki_stage_refresh', 'runtime_task_graph', 'development_team', 'triwiki_stage_refresh', 'review'],
|
|
784
832
|
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
833
|
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
|
|
834
|
+
development_team: `Fresh parallel development bundle with exactly ${roster.bundle_size} executor_N developers implementing disjoint slices; validation_team reviews afterward.`,
|
|
835
|
+
review_team: `Validation runs at least ${MIN_TEAM_REVIEWER_LANES} independent reviewer/QA lanes before integration or final.`
|
|
787
836
|
},
|
|
788
837
|
context_tracking: triwikiContextTracking(),
|
|
789
838
|
team_runtime: teamRuntimePlanMetadata(),
|
|
@@ -795,7 +844,7 @@ async function prepareTeam(root, route, task, required, opts = {}) {
|
|
|
795
844
|
{ id: 'consensus', goal: `Seal one objective with acceptance criteria and disjoint implementation slices, then refresh/validate TriWiki so implementation receives current consensus context.` },
|
|
796
845
|
{ 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
846
|
{ 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:
|
|
847
|
+
{ 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
848
|
{ 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
849
|
],
|
|
801
850
|
live_visibility: {
|
|
@@ -810,7 +859,7 @@ async function prepareTeam(root, route, task, required, opts = {}) {
|
|
|
810
859
|
await writeJsonAtomic(path.join(dir, 'team-plan.json'), plan);
|
|
811
860
|
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
861
|
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.
|
|
862
|
+
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
863
|
await initTeamLive(id, dir, cleanTask, { agentSessions, roleCounts, roster });
|
|
815
864
|
const runtime = await writeTeamRuntimeArtifacts(dir, plan, {});
|
|
816
865
|
await writeMemorySweepReport(root, dir, { missionId: id }).catch(() => null);
|
|
@@ -823,7 +872,7 @@ async function prepareTeam(root, route, task, required, opts = {}) {
|
|
|
823
872
|
: {};
|
|
824
873
|
const pipelinePlan = await writePipelinePlan(dir, { missionId: id, route, task: cleanTask, required, ambiguity: { required: false, status: 'direct_team_cli' } });
|
|
825
874
|
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.`);
|
|
875
|
+
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
876
|
}
|
|
828
877
|
|
|
829
878
|
async function prepareResearch(root, route, task, required) {
|
|
@@ -1362,11 +1411,15 @@ function missingRequiredGateFields(file, state, gate = {}) {
|
|
|
1362
1411
|
return gate[key] !== true;
|
|
1363
1412
|
});
|
|
1364
1413
|
}
|
|
1414
|
+
if (file === IMAGE_UX_REVIEW_GATE_ARTIFACT || mode === 'IMAGE_UX_REVIEW') {
|
|
1415
|
+
return IMAGE_UX_REVIEW_REQUIRED_GATE_FIELDS.filter((key) => gate[key] !== true);
|
|
1416
|
+
}
|
|
1365
1417
|
return [];
|
|
1366
1418
|
}
|
|
1367
1419
|
|
|
1368
1420
|
async function missingRequiredGateArtifacts(root, file, state, gate = {}) {
|
|
1369
1421
|
const mode = String(state?.mode || '').toUpperCase();
|
|
1422
|
+
if (file === IMAGE_UX_REVIEW_GATE_ARTIFACT || mode === 'IMAGE_UX_REVIEW') return missingImageUxReviewArtifacts(root, state, gate);
|
|
1370
1423
|
if (file !== 'team-gate.json' && mode !== 'TEAM') return [];
|
|
1371
1424
|
const missing = [];
|
|
1372
1425
|
if (gate.team_roster_confirmed === true && !(await exists(path.join(missionDir(root, state.mission_id), 'team-roster.json')))) missing.push('team-roster.json');
|
|
@@ -1386,6 +1439,42 @@ async function missingRequiredGateArtifacts(root, file, state, gate = {}) {
|
|
|
1386
1439
|
return missing;
|
|
1387
1440
|
}
|
|
1388
1441
|
|
|
1442
|
+
async function missingImageUxReviewArtifacts(root, state = {}, gate = {}) {
|
|
1443
|
+
const missing = [];
|
|
1444
|
+
const id = state?.mission_id;
|
|
1445
|
+
if (!id) return [`${IMAGE_UX_REVIEW_GATE_ARTIFACT}:mission_id`];
|
|
1446
|
+
const dir = missionDir(root, id);
|
|
1447
|
+
const required = [
|
|
1448
|
+
[IMAGE_UX_REVIEW_POLICY_ARTIFACT, 'policy_created'],
|
|
1449
|
+
[IMAGE_UX_REVIEW_SCREEN_INVENTORY_ARTIFACT, 'screen_inventory_created'],
|
|
1450
|
+
[IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT, 'imagegen_review_images_generated'],
|
|
1451
|
+
[IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT, 'issue_ledger_created'],
|
|
1452
|
+
[IMAGE_UX_REVIEW_ITERATION_REPORT_ARTIFACT, 'bounded_iteration_complete']
|
|
1453
|
+
];
|
|
1454
|
+
for (const [artifact, field] of required) {
|
|
1455
|
+
if (gate[field] === true && !(await exists(path.join(dir, artifact)))) missing.push(artifact);
|
|
1456
|
+
}
|
|
1457
|
+
const generated = await readJson(path.join(dir, IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT), null);
|
|
1458
|
+
if (gate.imagegen_review_images_generated === true) {
|
|
1459
|
+
if (!generated) missing.push(IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT);
|
|
1460
|
+
else {
|
|
1461
|
+
if (generated.passed !== true) missing.push(`${IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT}:passed`);
|
|
1462
|
+
if (!Array.isArray(generated.generated_review_images) || generated.generated_review_images.length === 0) missing.push(`${IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT}:generated_review_images`);
|
|
1463
|
+
if (String(generated.provider?.model || '') !== 'gpt-image-2') missing.push(`${IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT}:gpt-image-2`);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
const issues = await readJson(path.join(dir, IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT), null);
|
|
1467
|
+
if (gate.generated_review_images_analyzed === true || gate.p0_p1_zero === true) {
|
|
1468
|
+
if (!issues) missing.push(IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT);
|
|
1469
|
+
else {
|
|
1470
|
+
if (issues.passed !== true && gate.p0_p1_zero === true) missing.push(`${IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT}:passed`);
|
|
1471
|
+
if (issues.extraction_source !== IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT) missing.push(`${IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT}:extraction_source`);
|
|
1472
|
+
if (Number(issues.blocking_issue_count || 0) !== 0 && gate.p0_p1_zero === true) missing.push(`${IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT}:blocking_issue_count`);
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
return missing;
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1389
1478
|
function fromChatImgCoverageRequired(state = {}, gate = {}) {
|
|
1390
1479
|
return state?.from_chat_img_required === true || gate?.from_chat_img_required === true;
|
|
1391
1480
|
}
|
|
@@ -1461,6 +1550,7 @@ function gateFilesForState(state) {
|
|
|
1461
1550
|
if (state.mode === 'GX') return ['gx-gate.json'];
|
|
1462
1551
|
if (state.mode === 'QALOOP') return ['qa-gate.json'];
|
|
1463
1552
|
if (state.mode === 'PPT') return ['ppt-gate.json'];
|
|
1553
|
+
if (state.mode === 'IMAGE_UX_REVIEW') return [IMAGE_UX_REVIEW_GATE_ARTIFACT];
|
|
1464
1554
|
return ['done-gate.json'];
|
|
1465
1555
|
}
|
|
1466
1556
|
|
package/src/core/routes.mjs
CHANGED
|
@@ -7,9 +7,10 @@ export const FROM_CHAT_IMG_CHECKLIST_ARTIFACT = 'from-chat-img-checklist.md';
|
|
|
7
7
|
export const FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT = 'from-chat-img-temp-triwiki.json';
|
|
8
8
|
export const FROM_CHAT_IMG_QA_LOOP_ARTIFACT = 'from-chat-img-qa-loop.json';
|
|
9
9
|
export const FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS = 5;
|
|
10
|
-
export const USAGE_TOPICS = 'install|setup|bootstrap|root|deps|tmux|auto-review|team|qa-loop|ppt|goal|research|db|codex-app|openclaw|dfix|design|imagegen|dollar|context7|pipeline|reasoning|guard|conflicts|versioning|eval|harness|hproof|gx|wiki|code-structure|proof-field|skill-dream';
|
|
10
|
+
export const USAGE_TOPICS = 'install|setup|bootstrap|root|deps|tmux|auto-review|team|qa-loop|ppt|image-ux-review|goal|research|db|codex-app|openclaw|dfix|design|imagegen|dollar|context7|pipeline|reasoning|guard|conflicts|versioning|eval|harness|hproof|gx|wiki|code-structure|proof-field|skill-dream';
|
|
11
11
|
export const CODEX_COMPUTER_USE_EVIDENCE_SOURCE = 'codex_computer_use';
|
|
12
12
|
export const CODEX_APP_IMAGE_GENERATION_DOC_URL = 'https://developers.openai.com/codex/app/features#image-generation';
|
|
13
|
+
export const OPENAI_IMAGE_GENERATION_DOC_URL = 'https://developers.openai.com/api/docs/guides/image-generation';
|
|
13
14
|
export const CODEX_COMPUTER_USE_ONLY_POLICY = 'Pipeline UI/browser verification and visual inspection must use Codex Computer Use only. Do not use Playwright, Chrome MCP, Browser Use, Selenium, Puppeteer, or any other browser automation substitute; if Codex Computer Use is unavailable, mark the UI/browser evidence unverified instead of substituting another tool.';
|
|
14
15
|
export const FORBIDDEN_BROWSER_AUTOMATION_RE = /\b(playwright|chrome\s+mcp|browser\s+use|selenium|puppeteer)\b/i;
|
|
15
16
|
|
|
@@ -95,6 +96,10 @@ export function getdesignReferencePolicyText() {
|
|
|
95
96
|
return `Design SSOT policy: ${DESIGN_SYSTEM_SSOT.authority_file} is the single design decision authority. If it is missing, create or update it through ${DESIGN_SYSTEM_SSOT.builder_prompt}; getdesign.md (${GETDESIGN_REFERENCE.url}), its official docs, and curated DESIGN.md examples at ${AWESOME_DESIGN_MD_REFERENCE.url} are source inputs to fuse into that SSOT or into route-local style tokens, not parallel authorities. Prefer the official Codex skill when available (${GETDESIGN_REFERENCE.codex_skill_install}); otherwise use the generated getdesign-reference skill plus official Web/API/CLI/SDK docs and curated DESIGN.md examples as inputs. Do not claim an official getdesign MCP server is configured unless a current official MCP surface is actually available.`;
|
|
96
97
|
}
|
|
97
98
|
|
|
99
|
+
export function imageUxReviewPipelinePolicyText() {
|
|
100
|
+
return `Image UX review pipeline: the core mechanism is not text-only screenshot critique. Capture or receive source UI screenshots, then use Codex App imagegen/$imagegen with gpt-image-2 (${CODEX_APP_IMAGE_GENERATION_DOC_URL}) to create new annotated review images from those screenshots as reference inputs. The generated review image must visibly mark numbered callouts, P0/P1/P2/P3 labels, eye-flow, hierarchy, contrast, alignment, density, affordance problems, and a small corrected mini-comp or before/after strip when useful. Then analyze that generated review image with vision/OCR and convert the visible callouts into image-ux-issue-ledger.json rows. Missing generated review images block image-ux-review-gate.json; never pass this route from a hand-written text-only substitute. For larger API-backed batches, use the official OpenAI image generation/editing path only when OPENAI_API_KEY and the route contract allow API-priced generation (${OPENAI_IMAGE_GENERATION_DOC_URL}).`;
|
|
101
|
+
}
|
|
102
|
+
|
|
98
103
|
export const RECOMMENDED_SKILLS = [
|
|
99
104
|
'reasoning-router',
|
|
100
105
|
'pipeline-runner',
|
|
@@ -107,6 +112,7 @@ export const RECOMMENDED_SKILLS = [
|
|
|
107
112
|
'design-ui-editor',
|
|
108
113
|
'getdesign-reference',
|
|
109
114
|
'imagegen',
|
|
115
|
+
'image-ux-review',
|
|
110
116
|
'computer-use',
|
|
111
117
|
'computer-use-fast',
|
|
112
118
|
'db-safety-guard',
|
|
@@ -270,7 +276,7 @@ export const ROUTES = [
|
|
|
270
276
|
context7Policy: 'optional',
|
|
271
277
|
reasoningPolicy: 'high',
|
|
272
278
|
stopGate: 'team-gate.json',
|
|
273
|
-
cliEntrypoint: 'sks team "task" [executor:5 reviewer:
|
|
279
|
+
cliEntrypoint: 'sks team "task" [executor:5 reviewer:6 user:1] | sks team log|tail|watch|lane|status|event|message|cleanup-tmux',
|
|
274
280
|
examples: ['$Team executor:5 agree on the best plan and implement it', '$From-Chat-IMG 채팅+첨부 이미지 작업 지시서']
|
|
275
281
|
},
|
|
276
282
|
{
|
|
@@ -301,6 +307,22 @@ export const ROUTES = [
|
|
|
301
307
|
cliEntrypoint: 'Codex App prompt route only: $PPT <topic>',
|
|
302
308
|
examples: ['$PPT 우리 SaaS 소개자료를 HTML 기반 PDF로 만들어줘', '$PPT 투자자용 피치덱 만들어줘']
|
|
303
309
|
},
|
|
310
|
+
{
|
|
311
|
+
id: 'ImageUXReview',
|
|
312
|
+
command: '$Image-UX-Review',
|
|
313
|
+
mode: 'IMAGE_UX_REVIEW',
|
|
314
|
+
route: 'image-generation UI/UX review loop',
|
|
315
|
+
description: 'Review UI/UX through the imagegen/gpt-image-2 visual critique loop: source screenshots become generated annotated review images, those images become issue ledgers, then fixes are rechecked.',
|
|
316
|
+
requiredSkills: ['image-ux-review', 'imagegen', 'computer-use', 'pipeline-runner', REFLECTION_SKILL_NAME, 'honest-mode'],
|
|
317
|
+
dollarAliases: ['$UX-Review'],
|
|
318
|
+
appSkillAliases: ['ux-review', 'visual-review', 'ui-ux-review'],
|
|
319
|
+
lifecycle: ['target_and_capture_inventory', 'source_screenshots', 'gpt_image_2_annotated_review_image', 'generated_image_text_extraction', 'issue_ledger', 'optional_safe_fixes', 'changed_screen_recheck', 'post_route_reflection', 'honest_mode'],
|
|
320
|
+
context7Policy: 'if_external_docs',
|
|
321
|
+
reasoningPolicy: 'high',
|
|
322
|
+
stopGate: 'image-ux-review-gate.json',
|
|
323
|
+
cliEntrypoint: 'Codex App prompt route: $Image-UX-Review <target>; inspect with sks image-ux-review status latest',
|
|
324
|
+
examples: ['$Image-UX-Review localhost 화면을 이미지 생성 리뷰 루프로 검수해줘', '$UX-Review 이 스크린샷을 gpt-image-2 콜아웃 리뷰로 분석하고 고쳐줘']
|
|
325
|
+
},
|
|
304
326
|
{
|
|
305
327
|
id: 'ComputerUse',
|
|
306
328
|
command: '$Computer-Use',
|
|
@@ -466,6 +488,7 @@ export const COMMAND_CATALOG = [
|
|
|
466
488
|
{ name: 'dfix', usage: 'sks dfix', description: 'Explain $DFix ultralight design/content fix mode.' },
|
|
467
489
|
{ name: 'qa-loop', usage: 'sks qa-loop prepare|answer|run|status ...', description: 'Dogfood UI/API as human proxy with safety gates, safe fixes, rechecks, Codex Computer Use-only UI evidence, report.' },
|
|
468
490
|
{ name: 'ppt', usage: 'sks ppt build|status <mission-id|latest> [--json]', description: 'Build or inspect $PPT HTML/PDF artifacts from a sealed presentation decision contract.' },
|
|
491
|
+
{ name: 'image-ux-review', usage: 'sks image-ux-review status <mission-id|latest> [--json]', description: 'Inspect $Image-UX-Review gpt-image-2/imagegen annotated UI/UX review artifacts.' },
|
|
469
492
|
{ name: 'context7', usage: 'sks context7 check|setup|tools|resolve|docs|evidence ...', description: 'Check, configure, and call the local Context7 MCP requirement.' },
|
|
470
493
|
{ name: 'pipeline', usage: 'sks pipeline status|resume|plan|answer ...', description: 'Inspect the active skill-first route, materialized execution plan, ambiguity gates, and completion gates.' },
|
|
471
494
|
{ name: 'guard', usage: 'sks guard check [--json]', description: 'Check SKS harness self-protection lock, fingerprints, and source-repo exception state.' },
|
|
@@ -489,7 +512,7 @@ export const COMMAND_CATALOG = [
|
|
|
489
512
|
{ 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
513
|
{ 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
514
|
{ 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:
|
|
515
|
+
{ 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
516
|
{ 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
517
|
{ name: 'gx', usage: 'sks gx init|render|validate|drift|snapshot [name]', description: 'Create and verify deterministic SVG/HTML visual context cartridges.' },
|
|
495
518
|
{ name: 'profile', usage: 'sks profile show|set <model>', description: 'Inspect or set the current SKS model profile metadata.' },
|
|
@@ -554,6 +577,15 @@ export function looksLikePresentationArtifactRequest(prompt = '') {
|
|
|
554
577
|
return /만들|작성|생성|제작|디자인|export|pdf|html|create|generate|build|write|make/i.test(text) || /\b(ppt|presentation|deck|slides?)\b/.test(lower);
|
|
555
578
|
}
|
|
556
579
|
|
|
580
|
+
export function looksLikeImageUxReviewRequest(prompt = '') {
|
|
581
|
+
const text = String(prompt || '');
|
|
582
|
+
const reviewCue = /(ui\/?ux|ux|ui|screen|screenshot|visual|interface|화면|스크린|캡처|비주얼|인터페이스|사용성|유아이|유엑스)/i.test(text)
|
|
583
|
+
&& /(review|critique|audit|inspect|analy[sz]e|검수|리뷰|분석|평가|진단)/i.test(text);
|
|
584
|
+
const imagegenCue = /(gpt-image-2|imagegen|\$imagegen|image\s*generation|generated\s*review|annotated\s*review|callout|이미지\s*생성|생성\s*이미지|콜아웃|주석\s*이미지)/i.test(text);
|
|
585
|
+
const commandCue = /\$?(?:image-ux-review|ux-review|visual-review|ui-ux-review)\b/i.test(text);
|
|
586
|
+
return commandCue || (reviewCue && imagegenCue);
|
|
587
|
+
}
|
|
588
|
+
|
|
557
589
|
export function routePrompt(prompt) {
|
|
558
590
|
const command = dollarCommand(prompt);
|
|
559
591
|
const text = String(prompt || '');
|
|
@@ -574,6 +606,7 @@ export function routePrompt(prompt) {
|
|
|
574
606
|
}
|
|
575
607
|
if (hasFromChatImgSignal(text)) return routeById('Team');
|
|
576
608
|
if (looksLikePresentationArtifactRequest(text)) return routeById('PPT');
|
|
609
|
+
if (looksLikeImageUxReviewRequest(text)) return routeById('ImageUXReview');
|
|
577
610
|
if (looksLikeComputerUseFastLane(text)) return routeById('ComputerUse');
|
|
578
611
|
if (looksLikeFastDesignFix(text)) return routeById('DFix');
|
|
579
612
|
if (looksLikeQuestionShapedDirective(text)) return routeById('Team');
|
|
@@ -644,6 +677,7 @@ export function routeRequiresSubagents(route, prompt = '') {
|
|
|
644
677
|
if (route.id === 'SKS') return looksLikeTeamDefaultWork(prompt);
|
|
645
678
|
if (route.id === 'Help' || route.id === 'Answer' || route.id === 'Wiki' || route.id === 'ComputerUse') return false;
|
|
646
679
|
if (route.id === 'PPT') return false;
|
|
680
|
+
if (route.id === 'ImageUXReview') return false;
|
|
647
681
|
if (route.id === 'Research' || route.id === 'AutoResearch') return true;
|
|
648
682
|
if (route.id === 'Goal') return looksLikeExecutionWork(prompt) || looksLikeTeamDefaultWork(stripDollarCommand(prompt));
|
|
649
683
|
if (route.id === 'DB' || route.id === 'GX') return looksLikeExecutionWork(prompt);
|
|
@@ -653,7 +687,7 @@ export function routeRequiresSubagents(route, prompt = '') {
|
|
|
653
687
|
|
|
654
688
|
export function reflectionRequiredForRoute(route) {
|
|
655
689
|
const id = String(route?.id || route?.mode || route?.route || route || '').replace(/^\$/, '');
|
|
656
|
-
return /^(team|qaloop|qa-loop|ppt|research|autoresearch|db|database|madsks|mad-sks|gx)$/i.test(id);
|
|
690
|
+
return /^(team|qaloop|qa-loop|ppt|imageuxreview|image-ux-review|research|autoresearch|db|database|madsks|mad-sks|gx)$/i.test(id);
|
|
657
691
|
}
|
|
658
692
|
|
|
659
693
|
export function looksLikeCodeChangingWork(prompt = '') {
|
|
@@ -696,6 +730,7 @@ export function routeReasoning(route, prompt = '') {
|
|
|
696
730
|
const base = ALLOWED_REASONING_EFFORTS.has(route?.reasoningPolicy) ? route.reasoningPolicy : 'medium';
|
|
697
731
|
if (hasFromChatImgSignal(text)) return reasoning('xhigh', 'from_chat_img_image_work_order_analysis');
|
|
698
732
|
if (route?.id === 'Research' || route?.id === 'AutoResearch') return reasoning('xhigh', 'research_or_experiment_route');
|
|
733
|
+
if (route?.id === 'ImageUXReview') return reasoning('high', 'image_generation_visual_review_route');
|
|
699
734
|
if (/\b(research|autoresearch|hypothesis|falsify|novelty|frontier|benchmark|experiment|SEO|GEO|ranking|연구|실험|가설|검증)\b/i.test(text)) return reasoning('xhigh', 'research_level_prompt');
|
|
700
735
|
if (base === 'xhigh') return reasoning('xhigh', 'route_policy_xhigh');
|
|
701
736
|
if (base === 'high' || /\b(architecture|design|migration|database|security|parallel|orchestrat|refactor|algorithm|logic|tradeoff|검토|설계|마이그레이션|보안|병렬|팀|논리)\b/i.test(text)) return reasoning('high', 'logical_or_safety_work');
|
package/src/core/team-live.mjs
CHANGED
|
@@ -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:
|
|
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
|
|
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
|
+
}
|
package/src/core/tmux-ui.mjs
CHANGED
|
@@ -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
|
-
|
|
487
|
-
const
|
|
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
|
|
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
|
-
|
|
494
|
+
ids.push(id);
|
|
501
495
|
}
|
|
502
|
-
|
|
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,
|
|
528
|
+
command: teamAgentCommand(launch.root, missionId, agentId, teamLanePhase(agentId), promptFile),
|
|
505
529
|
style: teamLaneStyle(agentId),
|
|
506
530
|
title: teamLaneTitle(agentId)
|
|
507
531
|
}));
|