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/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
Sneakoscope Codex (`sks`, displayed as `ㅅㅋㅅ`) is a Codex CLI/App harness for repeatable agent workflows. It adds terminal commands, Codex App `$` prompt commands, tmux-native CLI workspaces, Team/QA/Research routes, inspectable pipeline plans, a maximum-speed Computer Use lane, a fast Goal bridge for native `/goal` persistence, Context7 evidence checks, DB safety, TriWiki context tracking, design-system SSOT routing, lightweight skill dreaming, Honest Mode, and release-readiness gates.
|
|
5
|
+
Sneakoscope Codex (`sks`, displayed as `ㅅㅋㅅ`) is a Codex CLI/App harness for repeatable agent workflows. It adds terminal commands, Codex App `$` prompt commands, tmux-native CLI workspaces, Team/QA/Research routes, inspectable pipeline plans, a maximum-speed Computer Use lane, an imagegen/gpt-image-2 UI/UX review route, a fast Goal bridge for native `/goal` persistence, Context7 evidence checks, DB safety, TriWiki context tracking, design-system SSOT routing, lightweight skill dreaming, Honest Mode, and release-readiness gates.
|
|
6
6
|
|
|
7
7
|
## Quick Start
|
|
8
8
|
|
|
@@ -43,7 +43,7 @@ sks selftest --mock
|
|
|
43
43
|
| Area | What it does |
|
|
44
44
|
| --- | --- |
|
|
45
45
|
| CLI runtime | Bare `sks` opens or reuses the default tmux Codex CLI workspace. `sks tmux open` remains the explicit form for session/workspace flags, and `sks --mad` launches the explicit full-access high-reasoning profile. |
|
|
46
|
-
| Codex App commands | Installs generated skills so `$Team`, `$From-Chat-IMG`, `$DFix`, `$QA-LOOP`, `$PPT`, `$Goal`, `$DB`, `$Wiki`, `$Help`, and related routes are visible in prompt workflows. `sks codex-app remote-control` wraps Codex CLI 0.130.0+ headless remote control without falling back to older app-server internals. |
|
|
46
|
+
| Codex App commands | Installs generated skills so `$Team`, `$From-Chat-IMG`, `$DFix`, `$QA-LOOP`, `$PPT`, `$Image-UX-Review`, `$UX-Review`, `$Goal`, `$DB`, `$Wiki`, `$Help`, and related routes are visible in prompt workflows. `sks codex-app remote-control` wraps Codex CLI 0.130.0+ headless remote control without falling back to older app-server internals. |
|
|
47
47
|
| OpenClaw agents | Generates an OpenClaw skill package so OpenClaw agents can attach `sneakoscope-codex`, enable the `shell` tool, and discover/use SKS commands from the target repo root. |
|
|
48
48
|
| Pipeline plans | Writes `pipeline-plan.json` for stateful routes so the runtime lane, kept stages, skipped stages, verification commands, and no-unrequested-fallback invariant are visible with `sks pipeline plan`. |
|
|
49
49
|
| Team orchestration | Runs substantial work through score-based ambiguity handling, scouts, TriWiki refresh, debate, runtime task graphs, worker inboxes, implementation, review, cleanup, reflection, and Honest Mode; narrow work should use Proof Field evidence to skip unrelated pipeline work instead of expanding Team. |
|
|
@@ -51,6 +51,7 @@ sks selftest --mock
|
|
|
51
51
|
| From-Chat-IMG | Turns chat screenshots plus original attachments into source-bound work orders, then requires scoped QA evidence before completion. |
|
|
52
52
|
| QA loop | Dogfoods UI/API behavior with safety gates, Codex Computer Use-only UI evidence, safe fixes, and rechecks. |
|
|
53
53
|
| PPT pipeline | Uses `$PPT` for simple, restrained, information-first HTML/PDF presentation artifacts, first asking delivery context, audience profile, STP strategy, decision context, and 3+ pain-point to solution/aha mappings before source research, design-system work, HTML/PDF export, and render QA. Independent strategy/render/file-write phases run in parallel where inputs allow and are recorded in `ppt-parallel-report.json`; editable source HTML is preserved under `source-html/`, PPT-only temporary build files are cleaned after completion, installed skills/MCPs outside the `$PPT` allowlist are ignored, generated image assets may use `$imagegen` only when sealed in the contract, and `ppt-style-tokens.json` records the design SSOT plus fused source inputs. |
|
|
54
|
+
| Image UX Review | Uses `$Image-UX-Review` / `$UX-Review` for UI/UX audits where source screenshots are first turned into generated annotated review images through Codex App `$imagegen`/`gpt-image-2`; those generated images are then read back into `image-ux-issue-ledger.json`, optional requested fixes are rechecked, and text-only screenshot critique cannot pass `image-ux-review-gate.json`. |
|
|
54
55
|
| Computer Use fast lane | Uses `$Computer-Use` / `$CU` for UI/browser/visual work that needs maximum speed: skip Team debate and upfront TriWiki loops, use Codex Computer Use directly, then refresh/validate TriWiki and run Honest Mode at final closeout. |
|
|
55
56
|
| Goal | Provides a fast SKS bridge overlay for Codex native persisted `/goal` create, pause, resume, and clear controls; implementation continues through the selected SKS execution route. |
|
|
56
57
|
| TriWiki voxels | Maintains `.sneakoscope/wiki/context-pack.json` as the context SSOT with coordinate anchors, voxel metadata, `attention.use_first`, `attention.hydrate_first`, and prompt-bound mistake recall ledgers. |
|
|
@@ -225,7 +226,8 @@ Answer `y` to install `sneakoscope@latest`, then rerun `sks --mad`. Answer `n` t
|
|
|
225
226
|
### Team Missions
|
|
226
227
|
|
|
227
228
|
```sh
|
|
228
|
-
sks team "implement this feature"
|
|
229
|
+
sks team "implement this feature"
|
|
230
|
+
sks team "wide refactor" executor:5 reviewer:6
|
|
229
231
|
sks team watch latest
|
|
230
232
|
sks team lane latest --agent analysis_scout_1 --follow
|
|
231
233
|
sks team message latest --from analysis_scout_1 --to executor_1 --message "handoff note"
|
|
@@ -235,6 +237,8 @@ sks team dashboard latest
|
|
|
235
237
|
sks team log latest
|
|
236
238
|
```
|
|
237
239
|
|
|
240
|
+
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.
|
|
241
|
+
|
|
238
242
|
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
243
|
|
|
240
244
|
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.
|
|
4
|
+
"version": "0.7.51",
|
|
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
|
@@ -41,12 +41,22 @@ import {
|
|
|
41
41
|
writePptBuildArtifacts,
|
|
42
42
|
writePptRouteArtifacts
|
|
43
43
|
} from '../core/ppt.mjs';
|
|
44
|
+
import {
|
|
45
|
+
IMAGE_UX_REVIEW_GATE_ARTIFACT,
|
|
46
|
+
IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT,
|
|
47
|
+
IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT,
|
|
48
|
+
IMAGE_UX_REVIEW_ITERATION_REPORT_ARTIFACT,
|
|
49
|
+
IMAGE_UX_REVIEW_POLICY_ARTIFACT,
|
|
50
|
+
IMAGE_UX_REVIEW_SCREEN_INVENTORY_ARTIFACT,
|
|
51
|
+
writeImageUxReviewRouteArtifacts
|
|
52
|
+
} from '../core/image-ux-review.mjs';
|
|
44
53
|
import { contextCapsule } from '../core/triwiki-attention.mjs';
|
|
45
54
|
import { rgbaKey, rgbaToWikiCoord, validateWikiCoordinateIndex } from '../core/wiki-coordinate.mjs';
|
|
46
55
|
import { ALLOWED_REASONING_EFFORTS, AWESOME_DESIGN_MD_REFERENCE, CODEX_APP_IMAGE_GENERATION_DOC_URL, CODEX_COMPUTER_USE_EVIDENCE_SOURCE, CODEX_COMPUTER_USE_ONLY_POLICY, COMMAND_CATALOG, DESIGN_SYSTEM_SSOT, DOLLAR_COMMAND_ALIASES, DOLLAR_COMMANDS, 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, GETDESIGN_REFERENCE, PPT_PIPELINE_SKILL_ALLOWLIST, RECOMMENDED_SKILLS, ROUTES, USAGE_TOPICS, context7ConfigToml, hasContext7ConfigText, hasFromChatImgSignal, looksLikeAnswerOnlyRequest, noUnrequestedFallbackCodePolicyText, reflectionRequiredForRoute, reasoningInstruction, routePrompt, routeReasoning, routeRequiresSubagents, speedLanePolicyText, stackCurrentDocsPolicy, triwikiContextTracking } from '../core/routes.mjs';
|
|
47
56
|
import { PIPELINE_PLAN_ARTIFACT, buildPipelinePlan, context7Evidence, evaluateStop, projectGateStatus, recordContext7Evidence, recordSubagentEvidence, validatePipelinePlan, writePipelinePlan } from '../core/pipeline.mjs';
|
|
48
57
|
import { TEAM_DECOMPOSITION_ARTIFACT, TEAM_GRAPH_ARTIFACT, TEAM_INBOX_DIR, TEAM_RUNTIME_TASKS_ARTIFACT, validateTeamRuntimeArtifacts, writeTeamRuntimeArtifacts } from '../core/team-dag.mjs';
|
|
49
58
|
import { appendTeamEvent, initTeamLive, parseTeamSpecText, readTeamDashboard, readTeamLive, readTeamTranscriptTail, renderTeamAgentLane } from '../core/team-live.mjs';
|
|
59
|
+
import { evaluateTeamReviewPolicyGate } from '../core/team-review-policy.mjs';
|
|
50
60
|
import { ARTIFACT_FILES, validateDogfoodReport, validateEffortDecision, validateFromChatImgVisualMap, validateSkillCandidate, validateSkillInjectionDecision, validateTeamDashboardState, validateWorkOrderLedger } from '../core/artifact-schemas.mjs';
|
|
51
61
|
import { selectEffort, writeEffortDecision } from '../core/effort-orchestrator.mjs';
|
|
52
62
|
import { createWorkOrderLedger } from '../core/work-order-ledger.mjs';
|
|
@@ -99,7 +109,7 @@ export async function main(args) {
|
|
|
99
109
|
if (String(cmd).toLowerCase() === 'dfix') return dfixHelp();
|
|
100
110
|
const handlers = {
|
|
101
111
|
postinstall: () => postinstall({ bootstrap }), wizard: () => wizard(tail), ui: () => wizard(tail), 'update-check': () => updateCheck(tail), help: () => help(tail), commands: () => commands(tail), usage: () => usage(tail), root: () => rootCommand(tail), quickstart: () => quickstartCommand(), 'codex-app': () => codexAppHelp(tail), 'codex-lb': () => codexLbCommand(sub, rest), auth: () => codexLbCommand(sub, rest), openclaw: () => openClawCommand(tail), bootstrap: () => bootstrap(tail), deps: () => deps(sub, rest),
|
|
102
|
-
'qa-loop': () => qaLoopCommand(sub, rest), ppt: () => pptCommand(sub, rest), context7: () => context7Command(sub, rest), pipeline: () => pipeline(sub, rest), guard: () => guard(sub, rest), conflicts: () => conflicts(sub, rest), versioning: () => versioning(sub, rest), reasoning: () => reasoningCommand(tail), aliases: () => aliases(), setup: () => setup(tail), 'fix-path': () => fixPath(tail), doctor: () => doctor(tail), init: () => init(tail), selftest: () => selftest(tail),
|
|
112
|
+
'qa-loop': () => qaLoopCommand(sub, rest), ppt: () => pptCommand(sub, rest), 'image-ux-review': () => imageUxReviewCommand(sub, rest), 'ux-review': () => imageUxReviewCommand(sub, rest), 'visual-review': () => imageUxReviewCommand(sub, rest), 'ui-ux-review': () => imageUxReviewCommand(sub, rest), context7: () => context7Command(sub, rest), pipeline: () => pipeline(sub, rest), guard: () => guard(sub, rest), conflicts: () => conflicts(sub, rest), versioning: () => versioning(sub, rest), reasoning: () => reasoningCommand(tail), aliases: () => aliases(), setup: () => setup(tail), 'fix-path': () => fixPath(tail), doctor: () => doctor(tail), init: () => init(tail), selftest: () => selftest(tail),
|
|
103
113
|
goal: () => goalCommand(sub, rest), research: () => researchCommand(sub, rest), hook: () => emitHook(sub), profile: () => profileCommand(sub, rest), hproof: () => hproofCommand(sub, rest), 'validate-artifacts': () => validateArtifactsCommand(tail), perf: () => perfCommand(sub, rest), 'proof-field': () => proofFieldCommand(sub, rest), 'skill-dream': () => skillDreamCommand(sub, rest), 'code-structure': () => codeStructureCommand(sub, rest), memory: () => memoryCommand(sub, rest), gx: () => gxCommand(sub, rest),
|
|
104
114
|
team: () => team(tail), db: () => dbCommand(sub, rest), eval: () => evalCommand(sub, rest), harness: () => harnessCommand(sub, rest), wiki: () => wikiCommand(sub, rest), gc: () => gcCommand(tail), stats: () => statsCommand(tail)
|
|
105
115
|
};
|
|
@@ -184,7 +194,7 @@ Usage:
|
|
|
184
194
|
sks goal create "task"
|
|
185
195
|
sks goal pause|resume|clear <mission-id|latest>
|
|
186
196
|
sks goal status <mission-id|latest>
|
|
187
|
-
sks team "task" [executor:5 reviewer:
|
|
197
|
+
sks team "task" [executor:5 reviewer:6 user:1] [--json]
|
|
188
198
|
sks team log|tail|watch|lane|status|dashboard [mission-id|latest]
|
|
189
199
|
sks team event [mission-id|latest] --agent <name> --phase <phase> --message "..."
|
|
190
200
|
sks team message [mission-id|latest] --from <agent> --to <agent|all> --message "..."
|
|
@@ -328,7 +338,7 @@ async function updateCheck(args = []) {
|
|
|
328
338
|
if (result.update_available) console.log('Run: npm i -g sneakoscope');
|
|
329
339
|
}
|
|
330
340
|
|
|
331
|
-
const DOLLAR_DEFAULT_PIPELINE_TEXT = 'Default pipeline: questions -> $Answer, small design/content -> $DFix, presentation/PDF artifacts -> $PPT, Computer Use UI/browser speed work -> $Computer-Use, code -> $Team. Use $From-Chat-IMG only for chat screenshot plus original attachments. Use $MAD-SKS only as an explicit scoped DB authorization modifier that can be combined with another $ route. No route may invent unrequested fallback implementation code.';
|
|
341
|
+
const DOLLAR_DEFAULT_PIPELINE_TEXT = 'Default pipeline: questions -> $Answer, small design/content -> $DFix, presentation/PDF artifacts -> $PPT, image-generation UI/UX reviews -> $Image-UX-Review/$UX-Review, Computer Use UI/browser speed work -> $Computer-Use, code -> $Team. Use $From-Chat-IMG only for chat screenshot plus original attachments. Use $MAD-SKS only as an explicit scoped DB authorization modifier that can be combined with another $ route. No route may invent unrequested fallback implementation code.';
|
|
332
342
|
|
|
333
343
|
function commands(args = []) {
|
|
334
344
|
if (flag(args, '--json')) return console.log(JSON.stringify({ aliases: ['sks', 'sneakoscope'], dollar_commands: DOLLAR_COMMANDS, app_skill_aliases: DOLLAR_COMMAND_ALIASES, commands: COMMAND_CATALOG }, null, 2));
|
|
@@ -476,6 +486,66 @@ async function pptCommand(sub = 'status', args = []) {
|
|
|
476
486
|
throw new Error(`Unknown ppt command: ${action}`);
|
|
477
487
|
}
|
|
478
488
|
|
|
489
|
+
async function imageUxReviewCommand(sub = 'status', args = []) {
|
|
490
|
+
const root = await sksRoot();
|
|
491
|
+
const action = sub || 'status';
|
|
492
|
+
if (action === 'help' || action === '--help' || action === '-h') {
|
|
493
|
+
console.log(`SKS Image UX Review
|
|
494
|
+
|
|
495
|
+
Prompt commands:
|
|
496
|
+
$Image-UX-Review <target>
|
|
497
|
+
$UX-Review <target>
|
|
498
|
+
|
|
499
|
+
Inspect artifacts:
|
|
500
|
+
sks image-ux-review status latest --json
|
|
501
|
+
|
|
502
|
+
Core loop:
|
|
503
|
+
source UI screenshot -> $imagegen/gpt-image-2 generated annotated review image -> image-ux-issue-ledger.json -> optional requested fixes -> changed-screen recheck
|
|
504
|
+
`);
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
if (action !== 'status') throw new Error(`Unknown image-ux-review command: ${action}`);
|
|
508
|
+
const missionArg = args.find((arg) => !String(arg).startsWith('--')) || 'latest';
|
|
509
|
+
const id = await resolveMissionId(root, missionArg);
|
|
510
|
+
if (!id) throw new Error('Usage: sks image-ux-review status <mission-id|latest> [--json]');
|
|
511
|
+
const { dir } = await loadMission(root, id);
|
|
512
|
+
const gate = await readJson(path.join(dir, IMAGE_UX_REVIEW_GATE_ARTIFACT), null);
|
|
513
|
+
const policy = await readJson(path.join(dir, IMAGE_UX_REVIEW_POLICY_ARTIFACT), null);
|
|
514
|
+
const inventory = await readJson(path.join(dir, IMAGE_UX_REVIEW_SCREEN_INVENTORY_ARTIFACT), null);
|
|
515
|
+
const generatedReviewLedger = await readJson(path.join(dir, IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT), null);
|
|
516
|
+
const issueLedger = await readJson(path.join(dir, IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT), null);
|
|
517
|
+
const iterationReport = await readJson(path.join(dir, IMAGE_UX_REVIEW_ITERATION_REPORT_ARTIFACT), null);
|
|
518
|
+
const status = {
|
|
519
|
+
ok: Boolean(gate?.passed),
|
|
520
|
+
mission_id: id,
|
|
521
|
+
gate,
|
|
522
|
+
policy,
|
|
523
|
+
inventory,
|
|
524
|
+
generated_review_ledger: generatedReviewLedger,
|
|
525
|
+
issue_ledger: issueLedger,
|
|
526
|
+
iteration_report: iterationReport,
|
|
527
|
+
files: {
|
|
528
|
+
policy: path.join(dir, IMAGE_UX_REVIEW_POLICY_ARTIFACT),
|
|
529
|
+
inventory: path.join(dir, IMAGE_UX_REVIEW_SCREEN_INVENTORY_ARTIFACT),
|
|
530
|
+
generated_review_ledger: path.join(dir, IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT),
|
|
531
|
+
issue_ledger: path.join(dir, IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT),
|
|
532
|
+
iteration_report: path.join(dir, IMAGE_UX_REVIEW_ITERATION_REPORT_ARTIFACT),
|
|
533
|
+
gate: path.join(dir, IMAGE_UX_REVIEW_GATE_ARTIFACT)
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
if (flag(args, '--json')) return console.log(JSON.stringify(status, null, 2));
|
|
537
|
+
console.log('SKS Image UX Review status\n');
|
|
538
|
+
console.log(`Mission: ${id}`);
|
|
539
|
+
console.log(`Gate: ${status.ok ? 'passed' : 'not passed'}`);
|
|
540
|
+
console.log(`Policy: ${path.relative(root, status.files.policy)}`);
|
|
541
|
+
console.log(`Screens: ${path.relative(root, status.files.inventory)}`);
|
|
542
|
+
console.log(`Images: ${path.relative(root, status.files.generated_review_ledger)}`);
|
|
543
|
+
console.log(`Issues: ${path.relative(root, status.files.issue_ledger)}`);
|
|
544
|
+
console.log(`Loop: ${path.relative(root, status.files.iteration_report)}`);
|
|
545
|
+
console.log(`Gate: ${path.relative(root, status.files.gate)}`);
|
|
546
|
+
if (gate?.blockers?.length) console.log(`Blockers:${' '.repeat(1)}${gate.blockers.join(', ')}`);
|
|
547
|
+
}
|
|
548
|
+
|
|
479
549
|
async function pipeline(sub = 'status', args = []) {
|
|
480
550
|
const root = await sksRoot();
|
|
481
551
|
const action = sub || 'status';
|
|
@@ -753,6 +823,25 @@ async function materializeAfterPipelineAnswer(root, id, dir, mission, route, rou
|
|
|
753
823
|
}
|
|
754
824
|
};
|
|
755
825
|
}
|
|
826
|
+
if (route?.id === 'ImageUXReview') {
|
|
827
|
+
await writeImageUxReviewRouteArtifacts(dir, contract);
|
|
828
|
+
await appendJsonlBounded(path.join(dir, 'events.jsonl'), {
|
|
829
|
+
ts: nowIso(),
|
|
830
|
+
type: 'image_ux_review.materialized',
|
|
831
|
+
route: route.id,
|
|
832
|
+
gate: IMAGE_UX_REVIEW_GATE_ARTIFACT,
|
|
833
|
+
generated_review_ledger: IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT
|
|
834
|
+
});
|
|
835
|
+
return {
|
|
836
|
+
phase: 'IMAGE_UX_REVIEW_READY',
|
|
837
|
+
prompt: routeContext.task || mission.prompt || '',
|
|
838
|
+
state: {
|
|
839
|
+
image_ux_review_gate_ready: true,
|
|
840
|
+
image_ux_review_policy_ready: true,
|
|
841
|
+
...madSksState
|
|
842
|
+
}
|
|
843
|
+
};
|
|
844
|
+
}
|
|
756
845
|
if (route?.id !== 'Team') return Object.keys(madSksState).length ? { state: madSksState } : {};
|
|
757
846
|
const spec = parseTeamSpecText(routeContext.task || mission.prompt || '');
|
|
758
847
|
const prompt = spec.prompt || routeContext.task || mission.prompt || '';
|
|
@@ -1472,9 +1561,10 @@ function usage(args = []) {
|
|
|
1472
1561
|
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
1562
|
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
1563
|
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:
|
|
1564
|
+
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
1565
|
'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
1566
|
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.'],
|
|
1567
|
+
'image-ux-review': ['Image UX Review', '', ' $Image-UX-Review localhost 화면을 이미지 생성 리뷰 루프로 검수해줘', ' $UX-Review 이 스크린샷을 gpt-image-2 콜아웃 리뷰로 분석하고 고쳐줘', ' sks image-ux-review status latest --json', '', '$Image-UX-Review captures or receives source UI screenshots, runs Codex App $imagegen/gpt-image-2 to create generated annotated review images with numbered callouts, then extracts those generated images into image-ux-issue-ledger.json. Text-only screenshot critique cannot pass image-ux-review-gate.json; missing generated review images remain an explicit blocker.'],
|
|
1478
1568
|
goal: ['Goal', '', ' sks goal create "task"', ' sks goal status latest', ' sks goal pause latest', ' sks goal resume latest', ' sks goal clear latest'],
|
|
1479
1569
|
'codex-app': ['Codex App', '', ' sks bootstrap', ' sks codex-app check', ' sks codex-app remote-control --status', ' sks dollar-commands', ' cat .codex/SNEAKOSCOPE.md'],
|
|
1480
1570
|
dollar: ['Dollar Commands', '', formatDollarCommandsCompact(' '), '', 'Terminal: sks dollar-commands [--json]'],
|
|
@@ -1967,6 +2057,12 @@ async function selftest() {
|
|
|
1967
2057
|
const tmp = tmpdir();
|
|
1968
2058
|
process.chdir(tmp);
|
|
1969
2059
|
await initProject(tmp, {});
|
|
2060
|
+
const latestMissionTmp = tmpdir();
|
|
2061
|
+
await ensureDir(path.join(latestMissionTmp, '.sneakoscope', 'missions', 'M-20260509-193839-6917'));
|
|
2062
|
+
await ensureDir(path.join(latestMissionTmp, '.sneakoscope', 'missions', 'M-20260509-193839-0551'));
|
|
2063
|
+
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' });
|
|
2064
|
+
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' });
|
|
2065
|
+
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
2066
|
if (readMaxCycles(['--max-cycles', 'Infinity'], 8) !== 8) throw new Error('selftest failed: non-finite max cycles not sanitized');
|
|
1971
2067
|
if (readMaxCycles(['--max-cycles', '0'], 8) !== 1) throw new Error('selftest failed: zero max cycles not bounded');
|
|
1972
2068
|
const loopMission = await createMission(tmp, { mode: 'team', prompt: 'compliance loop guard selftest' });
|
|
@@ -2435,11 +2531,13 @@ async function selftest() {
|
|
|
2435
2531
|
if (fromChatImgSkillText.includes('Computer Use/browser visual inspection')) throw new Error('selftest failed: from-chat-img skill still allows browser visual inspection wording');
|
|
2436
2532
|
const fromChatImgSkillMeta = await safeReadText(path.join(tmp, '.agents', 'skills', 'from-chat-img', 'agents', 'openai.yaml'));
|
|
2437
2533
|
if (!fromChatImgSkillMeta.includes('model_reasoning_effort: xhigh')) throw new Error('selftest failed: from-chat-img skill metadata is not xhigh');
|
|
2438
|
-
for (const supportSkill of ['reasoning-router', 'pipeline-runner', 'context7-docs', 'seo-geo-optimizer', 'reflection', 'design-system-builder', 'design-ui-editor', 'getdesign-reference', 'imagegen']) {
|
|
2534
|
+
for (const supportSkill of ['reasoning-router', 'pipeline-runner', 'context7-docs', 'seo-geo-optimizer', 'reflection', 'design-system-builder', 'design-ui-editor', 'getdesign-reference', 'imagegen', 'image-ux-review', 'visual-review', 'ui-ux-review']) {
|
|
2439
2535
|
if (!(await exists(path.join(tmp, '.agents', 'skills', supportSkill, 'SKILL.md')))) throw new Error(`selftest failed: ${supportSkill} skill not installed`);
|
|
2440
2536
|
}
|
|
2441
2537
|
const imagegenSkillText = await safeReadText(path.join(tmp, '.agents', 'skills', 'imagegen', 'SKILL.md'));
|
|
2442
2538
|
if (!imagegenSkillText.includes(CODEX_APP_IMAGE_GENERATION_DOC_URL) || !imagegenSkillText.includes('$imagegen') || !imagegenSkillText.includes('gpt-image-2') || !imagegenSkillText.includes('OPENAI_API_KEY')) throw new Error('selftest failed: imagegen skill missing official Codex App image generation priority');
|
|
2539
|
+
const imageUxReviewSkillText = await safeReadText(path.join(tmp, '.agents', 'skills', 'image-ux-review', 'SKILL.md'));
|
|
2540
|
+
if (!imageUxReviewSkillText.includes('gpt-image-2') || !imageUxReviewSkillText.includes('$imagegen') || !imageUxReviewSkillText.includes('generated annotated review image') || !imageUxReviewSkillText.includes('Text-only screenshot critique cannot satisfy this route') || !imageUxReviewSkillText.includes(IMAGE_UX_REVIEW_GATE_ARTIFACT) || !imageUxReviewSkillText.includes(IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT)) throw new Error('selftest failed: image-ux-review skill missing gpt-image-2 generated-image review gate guidance');
|
|
2443
2541
|
const getdesignSkillText = await safeReadText(path.join(tmp, '.agents', 'skills', 'getdesign-reference', 'SKILL.md')); if (!getdesignSkillText.includes(AWESOME_DESIGN_MD_REFERENCE.url) || !getdesignSkillText.includes('only design decision SSOT') || !getdesignSkillText.includes('source inputs')) throw new Error('selftest failed: getdesign-reference skill missing design SSOT source-input guidance');
|
|
2444
2542
|
const designSystemBuilderSkillText = await safeReadText(path.join(tmp, '.agents', 'skills', 'design-system-builder', 'SKILL.md')); if (!designSystemBuilderSkillText.includes(AWESOME_DESIGN_MD_REFERENCE.url) || !designSystemBuilderSkillText.includes('Fuse those inputs into one design.md SSOT') || !designSystemBuilderSkillText.includes('competing authorities')) throw new Error('selftest failed: design-system-builder skill missing fused design SSOT guidance');
|
|
2445
2543
|
const designSysPromptText = await safeReadText(path.join(packageRoot(), 'docs', 'Design-Sys-Prompt.md')); if (!designSysPromptText.includes('Design SSOT contract') || !designSysPromptText.includes('builder prompt') || !designSysPromptText.includes('not a competing design authority')) throw new Error('selftest failed: Design-Sys-Prompt missing design SSOT contract');
|
|
@@ -2455,7 +2553,9 @@ async function selftest() {
|
|
|
2455
2553
|
if (new Set(DOLLAR_COMMANDS.map((c) => c.command)).size !== DOLLAR_COMMANDS.length) throw new Error('selftest failed: duplicate dollar commands');
|
|
2456
2554
|
if (!DOLLAR_COMMAND_ALIASES.some((alias) => alias.canonical === '$QA-LOOP' && alias.app_skill === '$qa-loop')) throw new Error('selftest failed: $QA-LOOP picker skill missing');
|
|
2457
2555
|
if (!DOLLAR_COMMAND_ALIASES.some((alias) => alias.canonical === '$Team' && alias.app_skill === '$from-chat-img')) throw new Error('selftest failed: $From-Chat-IMG picker skill missing');
|
|
2556
|
+
if (!DOLLAR_COMMAND_ALIASES.some((alias) => alias.canonical === '$Image-UX-Review' && alias.app_skill === '$visual-review')) throw new Error('selftest failed: $Image-UX-Review picker alias missing');
|
|
2458
2557
|
if (!DOLLAR_COMMANDS.some((entry) => entry.command === '$From-Chat-IMG')) throw new Error('selftest failed: $From-Chat-IMG missing from dollar command list');
|
|
2558
|
+
if (!DOLLAR_COMMANDS.some((entry) => entry.command === '$Image-UX-Review') || !DOLLAR_COMMANDS.some((entry) => entry.command === '$UX-Review')) throw new Error('selftest failed: Image UX Review missing from dollar command list');
|
|
2459
2559
|
if (DOLLAR_COMMAND_ALIASES.some((alias) => ['$agent-team', '$qaloop', '$wiki-refresh', '$wikirefresh'].includes(alias.app_skill))) throw new Error('selftest failed: duplicate picker aliases still present');
|
|
2460
2560
|
if (routePrompt('$agent-team run specialists')) throw new Error('selftest failed: deprecated $agent-team route still resolved');
|
|
2461
2561
|
if (routePrompt('$QA-LOOP run UI E2E')?.id !== 'QALoop' || routePrompt('$QALoop deployed smoke')) throw new Error('selftest failed: QA-LOOP route is not standardized to $QA-LOOP');
|
|
@@ -2463,6 +2563,12 @@ async function selftest() {
|
|
|
2463
2563
|
if (routePrompt('$MAD-SKS Supabase MCP main 작업')?.id !== 'MadSKS') throw new Error('selftest failed: $MAD-SKS route did not resolve');
|
|
2464
2564
|
if (routePrompt('$MAD-SKS $Team Supabase MCP main 작업')?.id !== 'Team') throw new Error('selftest failed: $MAD-SKS did not compose with $Team');
|
|
2465
2565
|
if (routePrompt('$DB Supabase 점검 $MAD-SKS')?.id !== 'DB') throw new Error('selftest failed: trailing $MAD-SKS changed primary route');
|
|
2566
|
+
const imageUxRoute = routePrompt('$Image-UX-Review localhost 화면 검수');
|
|
2567
|
+
if (imageUxRoute?.id !== 'ImageUXReview') throw new Error('selftest failed: $Image-UX-Review did not route to ImageUXReview');
|
|
2568
|
+
if (routePrompt('$UX-Review 스크린샷 gpt-image-2 콜아웃 리뷰')?.id !== 'ImageUXReview') throw new Error('selftest failed: $UX-Review did not route to ImageUXReview');
|
|
2569
|
+
if (routePrompt('UI UX를 gpt-image-2 이미지 생성 콜아웃으로 리뷰해줘')?.id !== 'ImageUXReview') throw new Error('selftest failed: image-generation UI/UX review prompt did not route to ImageUXReview');
|
|
2570
|
+
if (routeRequiresSubagents(imageUxRoute, '$Image-UX-Review localhost 화면 검수')) throw new Error('selftest failed: ImageUXReview route should not require subagents');
|
|
2571
|
+
if (!reflectionRequiredForRoute(imageUxRoute)) throw new Error('selftest failed: ImageUXReview route should require reflection');
|
|
2466
2572
|
const madStandaloneTmp = tmpdir();
|
|
2467
2573
|
await initProject(madStandaloneTmp, {});
|
|
2468
2574
|
const madStandalonePayload = JSON.stringify({ cwd: madStandaloneTmp, prompt: '$MAD-SKS main 권한 열어줘' });
|
|
@@ -2491,7 +2597,8 @@ async function selftest() {
|
|
|
2491
2597
|
if (!DOLLAR_DEFAULT_PIPELINE_TEXT.includes('$Team')) throw new Error('selftest failed: dollar-commands missing Team default routing guidance');
|
|
2492
2598
|
if (!DOLLAR_DEFAULT_PIPELINE_TEXT.includes('$From-Chat-IMG')) throw new Error('selftest failed: dollar-commands missing From-Chat-IMG guidance');
|
|
2493
2599
|
if (!DOLLAR_DEFAULT_PIPELINE_TEXT.includes('$MAD-SKS')) throw new Error('selftest failed: dollar-commands missing MAD-SKS scoped override guidance');
|
|
2494
|
-
if (!
|
|
2600
|
+
if (!DOLLAR_DEFAULT_PIPELINE_TEXT.includes('$Image-UX-Review')) throw new Error('selftest failed: dollar-commands missing Image UX Review guidance');
|
|
2601
|
+
if (!COMMAND_CATALOG.some((c) => c.name === 'context7') || !COMMAND_CATALOG.some((c) => c.name === 'pipeline') || !COMMAND_CATALOG.some((c) => c.name === 'qa-loop') || !COMMAND_CATALOG.some((c) => c.name === 'image-ux-review') || !COMMAND_CATALOG.some((c) => c.name === 'root') || !COMMAND_CATALOG.some((c) => c.name === 'openclaw')) throw new Error('selftest failed: context7/pipeline/qa-loop/image-ux-review/root/openclaw commands missing from catalog');
|
|
2495
2602
|
const openClawTmp = tmpdir();
|
|
2496
2603
|
const openClawResult = await installOpenClawSkill({ targetDir: path.join(openClawTmp, 'skills', OPENCLAW_SKILL_NAME) });
|
|
2497
2604
|
if (!openClawResult.ok) throw new Error(`selftest failed: OpenClaw skill install blocked: ${openClawResult.reason}`);
|
|
@@ -2522,6 +2629,25 @@ async function selftest() {
|
|
|
2522
2629
|
const hookGoalTmp = tmpdir();
|
|
2523
2630
|
await initProject(hookGoalTmp, {});
|
|
2524
2631
|
const hookBin = path.join(packageRoot(), 'bin', 'sks.mjs');
|
|
2632
|
+
const hookImageUxTmp = tmpdir();
|
|
2633
|
+
await initProject(hookImageUxTmp, {});
|
|
2634
|
+
const hookImageUxPayload = JSON.stringify({ cwd: hookImageUxTmp, prompt: '$Image-UX-Review localhost 화면을 gpt-image-2 콜아웃 리뷰로 검수해줘' });
|
|
2635
|
+
const hookImageUxResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], { cwd: hookImageUxTmp, input: hookImageUxPayload, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 256 * 1024 });
|
|
2636
|
+
if (hookImageUxResult.code !== 0) throw new Error(`selftest failed: $Image-UX-Review hook exited ${hookImageUxResult.code}: ${hookImageUxResult.stderr}`);
|
|
2637
|
+
const hookImageUxJson = JSON.parse(hookImageUxResult.stdout);
|
|
2638
|
+
const imageUxContext = hookImageUxJson.hookSpecificOutput?.additionalContext || '';
|
|
2639
|
+
if (!imageUxContext.includes('$Image-UX-Review route prepared') || !imageUxContext.includes('Codex App $imagegen/gpt-image-2')) throw new Error('selftest failed: $Image-UX-Review hook did not prepare imagegen loop context');
|
|
2640
|
+
const hookImageUxState = await readJson(stateFile(hookImageUxTmp), {});
|
|
2641
|
+
if (hookImageUxState.mode !== 'IMAGE_UX_REVIEW' || hookImageUxState.stop_gate !== IMAGE_UX_REVIEW_GATE_ARTIFACT || hookImageUxState.subagents_required !== false || hookImageUxState.reflection_required !== true) throw new Error('selftest failed: $Image-UX-Review hook did not set direct image UX review state');
|
|
2642
|
+
const imageUxMissionDir = missionDir(hookImageUxTmp, hookImageUxState.mission_id);
|
|
2643
|
+
const imageUxGate = await readJson(path.join(imageUxMissionDir, IMAGE_UX_REVIEW_GATE_ARTIFACT));
|
|
2644
|
+
const imageUxGeneratedLedger = await readJson(path.join(imageUxMissionDir, IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT));
|
|
2645
|
+
if (imageUxGate.passed || imageUxGate.imagegen_review_images_generated || !imageUxGate.blockers?.includes('source_screenshots_not_captured_yet') || !imageUxGate.blockers?.includes('no_source_screenshots_for_imagegen_review')) throw new Error('selftest failed: Image UX review gate did not block missing source/generated review images');
|
|
2646
|
+
if (imageUxGeneratedLedger.provider?.model !== 'gpt-image-2' || imageUxGeneratedLedger.passed) throw new Error('selftest failed: Image UX generated review ledger did not record required gpt-image-2 blocker state');
|
|
2647
|
+
const imageUxStatusResult = await runProcess(process.execPath, [hookBin, 'image-ux-review', 'status', 'latest', '--json'], { cwd: hookImageUxTmp, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
|
|
2648
|
+
if (imageUxStatusResult.code !== 0) throw new Error(`selftest failed: sks image-ux-review status failed: ${imageUxStatusResult.stderr || imageUxStatusResult.stdout}`);
|
|
2649
|
+
const imageUxStatus = JSON.parse(imageUxStatusResult.stdout);
|
|
2650
|
+
if (imageUxStatus.ok || imageUxStatus.generated_review_ledger?.provider?.model !== 'gpt-image-2' || !imageUxStatus.files?.gate?.endsWith(IMAGE_UX_REVIEW_GATE_ARTIFACT)) throw new Error('selftest failed: sks image-ux-review status did not report gpt-image-2 gate blockers');
|
|
2525
2651
|
const hookPayload = JSON.stringify({ cwd: hookGoalTmp, prompt: '$Goal 로그인 세션 만료 UX 개선' });
|
|
2526
2652
|
const hookResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], { cwd: hookGoalTmp, input: hookPayload, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 256 * 1024 });
|
|
2527
2653
|
if (hookResult.code !== 0) throw new Error(`selftest failed: $Goal hook exited ${hookResult.code}: ${hookResult.stderr}`);
|
|
@@ -2650,7 +2776,7 @@ async function selftest() {
|
|
|
2650
2776
|
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
2777
|
const hookPaymentTeamTmp = tmpdir();
|
|
2652
2778
|
await initProject(hookPaymentTeamTmp, {});
|
|
2653
|
-
const hookPaymentTeamPayload = JSON.stringify({ cwd: hookPaymentTeamTmp, prompt: '$Team 결제 재시도 정책과 로그인 세션 만료 버그 수정 executor:2
|
|
2779
|
+
const hookPaymentTeamPayload = JSON.stringify({ cwd: hookPaymentTeamTmp, prompt: '$Team 결제 재시도 정책과 로그인 세션 만료 버그 수정 executor:2 user:1' });
|
|
2654
2780
|
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
2781
|
if (hookPaymentTeamResult.code !== 0) throw new Error(`selftest failed: payment/auth Team hook exited ${hookPaymentTeamResult.code}: ${hookPaymentTeamResult.stderr}`);
|
|
2656
2782
|
const hookPaymentTeamJson = JSON.parse(hookPaymentTeamResult.stdout);
|
|
@@ -2662,7 +2788,7 @@ async function selftest() {
|
|
|
2662
2788
|
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
2789
|
const hookTeamTmp = tmpdir();
|
|
2664
2790
|
await initProject(hookTeamTmp, {});
|
|
2665
|
-
const hookTeamPayload = JSON.stringify({ cwd: hookTeamTmp, prompt: '$Team 발표자료 만들어줘 executor:2
|
|
2791
|
+
const hookTeamPayload = JSON.stringify({ cwd: hookTeamTmp, prompt: '$Team 발표자료 만들어줘 executor:2 user:1' });
|
|
2666
2792
|
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
2793
|
if (hookTeamResult.code !== 0) throw new Error(`selftest failed: $Team hook exited ${hookTeamResult.code}: ${hookTeamResult.stderr}`);
|
|
2668
2794
|
const hookTeamJson = JSON.parse(hookTeamResult.stdout);
|
|
@@ -3077,8 +3203,12 @@ async function selftest() {
|
|
|
3077
3203
|
const { id: teamId, dir: teamDir } = await createMission(tmp, { mode: 'team', prompt: '병렬 구현 팀 테스트' });
|
|
3078
3204
|
const teamPlan = buildTeamPlan(teamId, '병렬 구현 팀 테스트');
|
|
3079
3205
|
await writeJsonAtomic(path.join(teamDir, 'team-plan.json'), teamPlan);
|
|
3080
|
-
if (teamPlan.agent_session_count !==
|
|
3081
|
-
if (teamPlan.role_counts.executor !== 3 || teamPlan.role_counts.user !== 1 || teamPlan.role_counts.reviewer !==
|
|
3206
|
+
if (teamPlan.agent_session_count !== 5) throw new Error('selftest failed: team default sessions not 5');
|
|
3207
|
+
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');
|
|
3208
|
+
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');
|
|
3209
|
+
const underProvisionedReviewCount = 2;
|
|
3210
|
+
const blockedReviewGate = evaluateTeamReviewPolicyGate({ roleCounts: { reviewer: underProvisionedReviewCount }, agentSessions: 3, roster: { validation_team: [{ id: 'reviewer_1', role: 'reviewer' }] } });
|
|
3211
|
+
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
3212
|
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
3213
|
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
3214
|
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 +3238,13 @@ async function selftest() {
|
|
|
3108
3238
|
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
3239
|
const customTeamPlan = buildTeamPlan(teamId, '병렬 구현 팀 테스트', { agentSessions: 5 });
|
|
3110
3240
|
if (customTeamPlan.agent_session_count !== 5) throw new Error('selftest failed: custom team sessions not honored');
|
|
3111
|
-
if (parseTeamCreateArgs(['--agents', '4', '작업']).agentSessions !==
|
|
3241
|
+
if (parseTeamCreateArgs(['--agents', '4', '작업']).agentSessions !== 5) throw new Error('selftest failed: team --agents parsing');
|
|
3112
3242
|
const maxAgentParsed = parseTeamCreateArgs(['--max-agents', '작업']);
|
|
3113
3243
|
if (maxAgentParsed.agentSessions !== 6 || maxAgentParsed.roleCounts.executor !== 6) throw new Error('selftest failed: team --max-agents parsing');
|
|
3114
3244
|
const maxTextParsed = parseTeamSpecText('가용가능한 최대 agents로 분석하고 구현');
|
|
3115
3245
|
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:
|
|
3117
|
-
if (roleParsed.roleCounts.executor !== 5 || roleParsed.roleCounts.reviewer !==
|
|
3246
|
+
const roleParsed = parseTeamCreateArgs(['executor:5', 'reviewer:6', 'user:1', '작업']);
|
|
3247
|
+
if (roleParsed.roleCounts.executor !== 5 || roleParsed.roleCounts.reviewer !== 6 || roleParsed.agentSessions !== 6 || roleParsed.prompt !== '작업') throw new Error('selftest failed: team role-count parsing');
|
|
3118
3248
|
const openTmuxFlagParsed = parseTeamCreateArgs(['--open-tmux', '작업']);
|
|
3119
3249
|
if (openTmuxFlagParsed.prompt !== '작업') throw new Error('selftest failed: team --open-tmux leaked into prompt');
|
|
3120
3250
|
const roleTeamPlan = buildTeamPlan(teamId, '역할 팀 테스트', { roleCounts: roleParsed.roleCounts });
|
|
@@ -3195,7 +3325,7 @@ async function selftest() {
|
|
|
3195
3325
|
await appendTeamEvent(teamDir, { agent: 'analysis_scout_1', phase: 'parallel_analysis_scouting', message: 'selftest mapped repo slice' });
|
|
3196
3326
|
await appendTeamEvent(teamDir, { agent: 'team_consensus', phase: 'planning_debate', message: 'selftest mapped options' });
|
|
3197
3327
|
const teamDashboard = await readTeamDashboard(teamDir);
|
|
3198
|
-
if (teamDashboard?.agent_session_count !==
|
|
3328
|
+
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
3329
|
await writeTeamDashboardState(teamDir, { missionId: teamId, mission: { id: teamId, mode: 'team' }, effort: 'high', phase: 'verification' });
|
|
3200
3330
|
const teamDashboardState = await readJson(path.join(teamDir, ARTIFACT_FILES.team_dashboard_state), {});
|
|
3201
3331
|
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:
|
|
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:
|
|
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:
|
|
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 ||
|
|
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
|
|
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:
|
|
1740
|
-
- This mission: ${plan.agent_session_count ||
|
|
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 ||
|
|
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.
|
|
8
|
+
export const PACKAGE_VERSION = '0.7.51';
|
|
9
9
|
export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
|
|
10
10
|
export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11
11
|
|