sneakoscope 0.7.43 → 0.7.44
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 +1 -1
- package/package.json +1 -1
- package/src/cli/main.mjs +8 -2
- package/src/core/fsx.mjs +1 -1
- package/src/core/pipeline.mjs +96 -4
- package/src/core/questions.mjs +9 -2
- package/src/core/tmux-ui.mjs +89 -84
package/README.md
CHANGED
|
@@ -166,7 +166,7 @@ sks tmux check
|
|
|
166
166
|
sks tmux status --once
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
-
Bare `sks` creates or reuses the default named tmux session for Codex CLI and attaches to it in an interactive terminal. By default it launches Codex in the SKS fast-high runtime (`--model gpt-5.5 -c model_reasoning_effort="high"`) with a
|
|
169
|
+
Bare `sks` creates or reuses the default named tmux session for Codex CLI and attaches to it in an interactive terminal. By default it launches Codex in the SKS fast-high runtime (`--model gpt-5.5 -c model_reasoning_effort="high"`) with a static SKS 3D ASCII intro inside tmux; the animated intro is reserved for non-tmux unauthenticated Codex launches and can be disabled with `SKS_TMUX_LOGO_ANIMATION=0`. Override the runtime with `SKS_CODEX_MODEL`, `SKS_CODEX_REASONING`, or disable the default model profile with `SKS_CODEX_FAST_HIGH=0`. Use `sks tmux open` when you need explicit `--workspace` / `--session` flags, `sks tmux check` for readiness without launching, and `sks help` for CLI help. Use `--no-attach` or `SKS_TMUX_NO_AUTO_ATTACH=1` when you only want SKS to create/reuse the session and print the manual attach command.
|
|
170
170
|
|
|
171
171
|
Before opening tmux, SKS checks the installed Codex CLI against npm `@openai/codex@latest`. If a newer version exists, it asks `Y/n`; answering `y` updates automatically with `npm i -g @openai/codex@latest` and then opens tmux with the updated Codex CLI.
|
|
172
172
|
|
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.44",
|
|
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
|
@@ -2407,7 +2407,8 @@ async function selftest() {
|
|
|
2407
2407
|
if (!hookKoreanSksContext.includes('Route: $Team')) throw new Error('selftest failed: Korean implementation prompt did not promote to Team route');
|
|
2408
2408
|
if (hookKoreanSksContext.includes('SKS answer-only pipeline active')) throw new Error('selftest failed: Korean implementation prompt still used answer-only pipeline');
|
|
2409
2409
|
const hookKoreanSksState = await readJson(stateFile(hookKoreanSksTmp), {});
|
|
2410
|
-
if (hookKoreanSksState.phase !== '
|
|
2410
|
+
if (hookKoreanSksState.phase !== 'TEAM_PARALLEL_ANALYSIS_SCOUTING' || hookKoreanSksState.implementation_allowed !== true || !hookKoreanSksState.ambiguity_gate_passed || !hookKoreanSksState.team_plan_ready) throw new Error('selftest failed: Korean Team auto-seal did not materialize Team');
|
|
2411
|
+
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');
|
|
2411
2412
|
const hookPaymentTeamTmp = tmpdir();
|
|
2412
2413
|
await initProject(hookPaymentTeamTmp, {});
|
|
2413
2414
|
const hookPaymentTeamPayload = JSON.stringify({ cwd: hookPaymentTeamTmp, prompt: '$Team 결제 재시도 정책과 로그인 세션 만료 버그 수정 executor:2 reviewer:1 user:1' });
|
|
@@ -2418,9 +2419,10 @@ async function selftest() {
|
|
|
2418
2419
|
if (!hookPaymentTeamContext.includes('Ambiguity gate auto-sealed')) throw new Error('selftest failed: predictable payment/auth Team prompt did not auto-seal');
|
|
2419
2420
|
if (hookPaymentTeamContext.includes('PAYMENT_RETRY_POLICY') || hookPaymentTeamContext.includes('AUTH_PROTOCOL_CHANGE_ALLOWED')) throw new Error('selftest failed: predictable payment/auth policy defaults were asked instead of inferred');
|
|
2420
2421
|
const hookPaymentTeamState = await readJson(stateFile(hookPaymentTeamTmp), {});
|
|
2421
|
-
if (hookPaymentTeamState.phase !== '
|
|
2422
|
+
if (hookPaymentTeamState.phase !== 'TEAM_PARALLEL_ANALYSIS_SCOUTING' || hookPaymentTeamState.implementation_allowed !== true || !hookPaymentTeamState.ambiguity_gate_passed || !hookPaymentTeamState.team_plan_ready) throw new Error('selftest failed: predictable payment/auth Team did not materialize after auto-seal');
|
|
2422
2423
|
const hookPaymentTeamSchema = await readJson(path.join(missionDir(hookPaymentTeamTmp, hookPaymentTeamState.mission_id), 'required-answers.schema.json'));
|
|
2423
2424
|
if (hookPaymentTeamSchema.slots.length !== 0 || hookPaymentTeamSchema.inferred_answers?.PAYMENT_RETRY_POLICY === undefined || hookPaymentTeamSchema.inferred_answers?.AUTH_SESSION_EXPIRED_BEHAVIOR === undefined) throw new Error('selftest failed: predictable payment/auth defaults were not recorded as inferred answers');
|
|
2425
|
+
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');
|
|
2424
2426
|
const hookTeamTmp = tmpdir();
|
|
2425
2427
|
await initProject(hookTeamTmp, {});
|
|
2426
2428
|
const hookTeamPayload = JSON.stringify({ cwd: hookTeamTmp, prompt: '$Team 발표자료 만들어줘 executor:2 reviewer:1 user:1' });
|
|
@@ -2991,6 +2993,10 @@ async function selftest() {
|
|
|
2991
2993
|
if (buttonUxSlotIds.length) throw new Error(`selftest failed: clear small UI work should auto-seal, got ${buttonUxSlotIds.join(',')}`);
|
|
2992
2994
|
if (buttonUxSchema.inferred_answers.UI_STATE_BEHAVIOR !== 'infer_from_task_context_and_existing_design_system; preserve existing loading/error/empty/retry behavior unless explicitly requested; add only standard states required by the touched surface') throw new Error('selftest failed: UI state default inference missing');
|
|
2993
2995
|
if (buttonUxSchema.inferred_answers.VISUAL_REGRESSION_REQUIRED !== 'yes_if_available') throw new Error('selftest failed: visual regression default inference missing');
|
|
2996
|
+
const predictableAuthCliSchema = buildQuestionSchema('회전 아스키 아트는 제일 처음 인증 안됐을때만 codex cli처럼 애니메이션으로 보이게 하고 tmux에서는 정적 3d 아스키 아트로 보여줘');
|
|
2997
|
+
const predictableAuthCliSlotIds = predictableAuthCliSchema.slots.map((s) => s.id);
|
|
2998
|
+
if (predictableAuthCliSlotIds.length) throw new Error(`selftest failed: clear auth-worded CLI rendering work should auto-seal, got ${predictableAuthCliSlotIds.join(',')}`);
|
|
2999
|
+
if (!predictableAuthCliSchema.inferred_answers.RISK_BOUNDARY?.includes('no destructive commands or live data writes')) throw new Error('selftest failed: predictable auth-worded CLI work did not infer conservative risk boundary');
|
|
2994
3000
|
const vagueSchema = buildQuestionSchema('뭔가 개선해줘');
|
|
2995
3001
|
const vagueSlotIds = vagueSchema.slots.map((s) => s.id);
|
|
2996
3002
|
if (!vagueSlotIds.includes('INTENT_TARGET') || vagueSlotIds.includes('GOAL_PRECISE') || vagueSlotIds.includes('ACCEPTANCE_CRITERIA')) throw new Error(`selftest failed: vague work should ask dynamic intent questions only, got ${vagueSlotIds.join(',')}`);
|
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.44';
|
|
9
9
|
export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
|
|
10
10
|
export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11
11
|
|
package/src/core/pipeline.mjs
CHANGED
|
@@ -504,10 +504,14 @@ async function prepareClarificationGate(root, route, task, required, opts = {})
|
|
|
504
504
|
if (schema.slots.length === 0) {
|
|
505
505
|
await writeJsonAtomic(path.join(dir, 'answers.json'), schema.inferred_answers || {});
|
|
506
506
|
const result = await sealContract(dir, mission);
|
|
507
|
-
const
|
|
507
|
+
const materialized = result.ok && route?.id === 'Team'
|
|
508
|
+
? await materializeAutoSealedTeam(root, id, dir, route, task, result.contract?.sealed_hash || null)
|
|
509
|
+
: {};
|
|
510
|
+
const effectiveTask = materialized.prompt || task;
|
|
511
|
+
const plan = await writePipelinePlan(dir, { missionId: id, route, task: effectiveTask, required, ambiguity: { required: true, slots: 0, auto_sealed: result.ok, passed: result.ok, contract_hash: result.contract?.sealed_hash || null } });
|
|
508
512
|
await appendJsonl(path.join(dir, 'events.jsonl'), { ts: nowIso(), type: 'route.clarification.auto_sealed', route: route.id, slots: 0, ok: result.ok });
|
|
509
|
-
await setCurrent(root, routeState(id, route, result.ok ? `${route.mode}_CLARIFICATION_CONTRACT_SEALED` : `${route.mode}_CLARIFICATION_AWAITING_ANSWERS`, required, {
|
|
510
|
-
prompt:
|
|
513
|
+
await setCurrent(root, routeState(id, route, result.ok ? (materialized.phase || `${route.mode}_CLARIFICATION_CONTRACT_SEALED`) : `${route.mode}_CLARIFICATION_AWAITING_ANSWERS`, required, {
|
|
514
|
+
prompt: effectiveTask,
|
|
511
515
|
questions_allowed: false,
|
|
512
516
|
implementation_allowed: result.ok,
|
|
513
517
|
clarification_required: false,
|
|
@@ -517,8 +521,10 @@ async function prepareClarificationGate(root, route, task, required, opts = {})
|
|
|
517
521
|
pipeline_plan_ready: validatePipelinePlan(plan).ok,
|
|
518
522
|
pipeline_plan_path: PIPELINE_PLAN_ARTIFACT,
|
|
519
523
|
original_stop_gate: route.stopGate,
|
|
520
|
-
stop_gate: route.stopGate
|
|
524
|
+
stop_gate: route.stopGate,
|
|
525
|
+
...(materialized.state || {})
|
|
521
526
|
}));
|
|
527
|
+
const materializedLine = materialized.phase ? `\nTeam runtime artifacts were materialized immediately; state advanced to ${materialized.phase}.` : '';
|
|
522
528
|
return {
|
|
523
529
|
route,
|
|
524
530
|
additionalContext: `${promptPipelineContext(task, route)}
|
|
@@ -528,6 +534,7 @@ Mission: ${id}
|
|
|
528
534
|
Decision contract: .sneakoscope/missions/${id}/decision-contract.json
|
|
529
535
|
Resolved answers: .sneakoscope/missions/${id}/resolved-answers.json
|
|
530
536
|
Pipeline plan: .sneakoscope/missions/${id}/${PIPELINE_PLAN_ARTIFACT}
|
|
537
|
+
${materializedLine}
|
|
531
538
|
Next atomic action: continue the original route lifecycle with the sealed decision-contract.json.`
|
|
532
539
|
};
|
|
533
540
|
}
|
|
@@ -581,6 +588,91 @@ function applyMadSksAuthorizationToSchema(schema = {}) {
|
|
|
581
588
|
return schema;
|
|
582
589
|
}
|
|
583
590
|
|
|
591
|
+
async function materializeAutoSealedTeam(root, id, dir, route, task, contractHash = null) {
|
|
592
|
+
const spec = parseTeamSpecText(task);
|
|
593
|
+
const cleanTask = spec.prompt || task;
|
|
594
|
+
const fromChatImgRequired = hasFromChatImgSignal(cleanTask);
|
|
595
|
+
const { agentSessions, roleCounts, roster } = spec;
|
|
596
|
+
const plan = {
|
|
597
|
+
schema_version: 1,
|
|
598
|
+
mission_id: id,
|
|
599
|
+
task: cleanTask,
|
|
600
|
+
agent_session_count: agentSessions,
|
|
601
|
+
default_agent_session_count: 3,
|
|
602
|
+
role_counts: roleCounts,
|
|
603
|
+
session_policy: `Use at most ${agentSessions} subagent sessions at a time; the parent orchestrator is not counted.`,
|
|
604
|
+
bundle_size: roster.bundle_size,
|
|
605
|
+
roster,
|
|
606
|
+
contract_hash: contractHash,
|
|
607
|
+
team_model: {
|
|
608
|
+
phases: ['parallel_analysis_scouts', 'triwiki_stage_refresh', 'debate_team', 'runtime_task_graph', 'development_team', 'review'],
|
|
609
|
+
analysis_team: `Read-only parallel scouting with exactly ${roster.bundle_size} analysis_scout_N agents.`,
|
|
610
|
+
debate_team: `Read-only role debate with exactly ${roster.bundle_size} participants.`,
|
|
611
|
+
development_team: `Fresh parallel development bundle with exactly ${roster.bundle_size} executor_N developers implementing disjoint slices.`
|
|
612
|
+
},
|
|
613
|
+
context_tracking: triwikiContextTracking(),
|
|
614
|
+
team_runtime: teamRuntimePlanMetadata(),
|
|
615
|
+
phases: [
|
|
616
|
+
{ id: 'team_roster_confirmation', goal: `Materialize the Team roster from default SKS counts or explicit user counts, write team-roster.json, and surface role counts ${formatRoleCounts(roleCounts)}.`, agents: ['parent_orchestrator'], output: 'team-roster.json' },
|
|
617
|
+
{ id: 'parallel_analysis_scouting', goal: `Read TriWiki context, then spawn exactly ${roster.bundle_size} read-only analysis_scout_N agents in parallel. ${fromChatImgRequired ? `From-Chat-IMG active: ${CODEX_COMPUTER_USE_ONLY_POLICY}` : 'From-Chat-IMG inactive: do not assume ordinary images are chat captures.'}`, agents: roster.analysis_team.map((agent) => agent.id), max_parallel_subagents: agentSessions, write_policy: 'read-only' },
|
|
618
|
+
{ id: 'triwiki_refresh', goal: `Refresh or pack TriWiki and run ${triwikiContextTracking().validate_command}.`, agents: ['parent_orchestrator'], output: '.sneakoscope/wiki/context-pack.json' },
|
|
619
|
+
{ 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) },
|
|
620
|
+
{ 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] },
|
|
621
|
+
{ 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) },
|
|
622
|
+
{ id: 'review_integration', goal: 'Review, integrate, verify, and record evidence before final.', agents: roster.validation_team.map((agent) => agent.id) },
|
|
623
|
+
{ id: 'session_cleanup', goal: `Close or account for Team subagent sessions and write ${TEAM_SESSION_CLEANUP_ARTIFACT}.`, agents: ['parent_orchestrator'] }
|
|
624
|
+
],
|
|
625
|
+
live_visibility: {
|
|
626
|
+
markdown: 'team-live.md',
|
|
627
|
+
transcript: 'team-transcript.jsonl',
|
|
628
|
+
dashboard: 'team-dashboard.json',
|
|
629
|
+
tmux: 'CLI Team entrypoints open tmux live lanes for the visible Team agent budget when tmux is available.',
|
|
630
|
+
commands: ['sks team status latest', 'sks team log latest', 'sks team tail latest', 'sks team watch latest', 'sks team lane latest --agent <name> --follow']
|
|
631
|
+
},
|
|
632
|
+
required_artifacts: ['team-roster.json', 'team-analysis.md', ...(fromChatImgRequired ? [FROM_CHAT_IMG_COVERAGE_ARTIFACT, FROM_CHAT_IMG_CHECKLIST_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT, FROM_CHAT_IMG_QA_LOOP_ARTIFACT] : []), 'team-consensus.md', ...teamRuntimeRequiredArtifacts(), 'team-review.md', 'team-gate.json', TEAM_SESSION_CLEANUP_ARTIFACT, 'reflection.md', 'reflection-gate.json', 'team-live.md', 'team-transcript.jsonl', 'team-dashboard.json', '.sneakoscope/wiki/context-pack.json', 'context7-evidence.jsonl']
|
|
633
|
+
};
|
|
634
|
+
await writeJsonAtomic(path.join(dir, 'team-plan.json'), plan);
|
|
635
|
+
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' });
|
|
636
|
+
const contextTracking = triwikiContextTracking();
|
|
637
|
+
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`);
|
|
638
|
+
await initTeamLive(id, dir, cleanTask, { agentSessions, roleCounts, roster });
|
|
639
|
+
const runtime = await writeTeamRuntimeArtifacts(dir, plan, { contractHash });
|
|
640
|
+
await writeMemorySweepReport(root, dir, { missionId: id }).catch(() => null);
|
|
641
|
+
await writeSkillForgeReport(dir, { mission_id: id, route: 'team', task_signature: cleanTask }).catch(() => null);
|
|
642
|
+
await writeMistakeMemoryReport(dir, { mission_id: id, route: 'team', task: cleanTask }).catch(() => null);
|
|
643
|
+
await writeCodeStructureReport(root, dir, { missionId: id, exception: 'Team auto-seal records split-review risk; extraction happens only when the mission scope includes the touched file.' }).catch(() => null);
|
|
644
|
+
await writeJsonAtomic(path.join(dir, 'team-gate.json'), {
|
|
645
|
+
passed: false,
|
|
646
|
+
team_roster_confirmed: true,
|
|
647
|
+
analysis_artifact: false,
|
|
648
|
+
triwiki_refreshed: false,
|
|
649
|
+
triwiki_validated: false,
|
|
650
|
+
consensus_artifact: false,
|
|
651
|
+
...runtime.gate_fields,
|
|
652
|
+
implementation_team_fresh: false,
|
|
653
|
+
review_artifact: false,
|
|
654
|
+
integration_evidence: false,
|
|
655
|
+
session_cleanup: false,
|
|
656
|
+
context7_evidence: false,
|
|
657
|
+
...(fromChatImgRequired ? { from_chat_img_required: true, from_chat_img_request_coverage: false } : {}),
|
|
658
|
+
contract_hash: contractHash
|
|
659
|
+
});
|
|
660
|
+
await appendJsonl(path.join(dir, 'events.jsonl'), { ts: nowIso(), type: 'team.materialized_after_auto_sealed_ambiguity_gate', route: route.id, bundle_size: roster.bundle_size, agent_sessions: agentSessions });
|
|
661
|
+
return {
|
|
662
|
+
phase: 'TEAM_PARALLEL_ANALYSIS_SCOUTING',
|
|
663
|
+
prompt: cleanTask,
|
|
664
|
+
state: {
|
|
665
|
+
agent_sessions: agentSessions,
|
|
666
|
+
role_counts: roleCounts,
|
|
667
|
+
team_roster_confirmed: true,
|
|
668
|
+
team_plan_ready: true,
|
|
669
|
+
team_graph_ready: runtime.ok,
|
|
670
|
+
team_live_ready: true,
|
|
671
|
+
from_chat_img_required: fromChatImgRequired
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
|
|
584
676
|
async function prepareTeam(root, route, task, required) {
|
|
585
677
|
const spec = parseTeamSpecText(task);
|
|
586
678
|
const cleanTask = spec.prompt || task;
|
package/src/core/questions.mjs
CHANGED
|
@@ -131,6 +131,10 @@ function promptHasRisk(lower) {
|
|
|
131
131
|
return /(운영|production|prod|live|배포|publish|release|결제|payment|billing|auth|인증|보안|security|db|database|supabase|postgres|sql|schema|migration|마이그레이션|삭제|delete|drop|truncate|reset|권한|permission|credential|secret)/.test(lower);
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
function promptNeedsExplicitRiskBoundary(lower) {
|
|
135
|
+
return /(drop|truncate|wipe|reset\s+(?:db|database)|delete\s+all|all-row|전체\s*(?:삭제|초기화)|운영\s*(?:db|데이터|삭제|쓰기|변경)|production\s*(?:db|database|delete|write|mutation)|prod\s*(?:db|database|delete|write|mutation)|credential|secret|api\s*key|토큰\s*(?:노출|삭제|교체)|권한\s*(?:상승|확대)|permission\s*(?:escalation|widening))/.test(lower);
|
|
136
|
+
}
|
|
137
|
+
|
|
134
138
|
function promptHasContextTarget(text, lower) {
|
|
135
139
|
return promptHasTarget(text, lower)
|
|
136
140
|
|| /https?:\/\/\S+/.test(text)
|
|
@@ -146,8 +150,11 @@ export function buildAmbiguityAssessment(prompt, explicitAnswers = {}) {
|
|
|
146
150
|
const acceptance = promptHasExplicitAcceptance(lower) || hasAnswer(explicitAnswers.ACCEPTANCE_CRITERIA) || hasAnswer(explicitAnswers.SUCCESS_CRITERIA_OR_ACCEPTANCE);
|
|
147
151
|
const risk = promptHasRisk(lower);
|
|
148
152
|
const contextTarget = promptHasContextTarget(text, lower) || hasAnswer(explicitAnswers.CODEBASE_CONTEXT_TARGET);
|
|
153
|
+
const actionable = target && action && !underspecified;
|
|
154
|
+
const hardRiskNeedsBoundary = promptNeedsExplicitRiskBoundary(lower);
|
|
149
155
|
const predictableSafetyDefault = /(재시도|retry|세션\s*만료|session\s*expired|session\s*expiry|token\s*expired)/.test(lower);
|
|
150
|
-
const
|
|
156
|
+
const predictableImplementationBoundary = actionable && risk && !hardRiskNeedsBoundary;
|
|
157
|
+
const hasPolicy = hasAnswer(explicitAnswers.RISK_BOUNDARY) || hasAnswer(explicitAnswers.RISK_AND_BOUNDARY) || predictableSafetyDefault || predictableImplementationBoundary || /(하지\s*마|금지|no\s+|never|묻지|보존|preserve|safe|안전|검증|approval|승인|알아서|판단|추론|infer|default|기본)/.test(lower);
|
|
151
158
|
const hasMultipleChoiceRisk = /(\bor\b|또는|아니면|선택|둘 중|여러|multiple|대안)/.test(lower) && !/(알아서|판단|infer|추론|default|기본)/.test(lower);
|
|
152
159
|
|
|
153
160
|
const goalClarity = underspecified ? (target || action ? 0.45 : 0.2) : (target && action ? 0.9 : target || action ? 0.62 : 0.25);
|
|
@@ -169,7 +176,7 @@ export function buildAmbiguityAssessment(prompt, explicitAnswers = {}) {
|
|
|
169
176
|
const unresolved = [];
|
|
170
177
|
if (components.goal.clarity_score < CLARITY_FLOORS.goal) unresolved.push('intent_target_or_required_outcome');
|
|
171
178
|
if (components.success.clarity_score < CLARITY_FLOORS.success && (!target || !action || risk)) unresolved.push('success_criteria_or_acceptance');
|
|
172
|
-
if (components.constraints.clarity_score < CLARITY_FLOORS.constraints || hasMultipleChoiceRisk) unresolved.push('risk_boundary_or_choice');
|
|
179
|
+
if ((components.constraints.clarity_score < CLARITY_FLOORS.constraints && hardRiskNeedsBoundary) || hasMultipleChoiceRisk) unresolved.push('risk_boundary_or_choice');
|
|
173
180
|
if (components.context.clarity_score < CLARITY_FLOORS.context) unresolved.push('codebase_context_target');
|
|
174
181
|
const uniqueUnresolved = [...new Set(unresolved)];
|
|
175
182
|
return {
|
package/src/core/tmux-ui.mjs
CHANGED
|
@@ -6,99 +6,96 @@ import { getCodexInfo } from './codex-adapter.mjs';
|
|
|
6
6
|
import { codexAppIntegrationStatus, formatCodexAppStatus } from './codex-app.mjs';
|
|
7
7
|
|
|
8
8
|
export const SKS_TMUX_LOGO = [
|
|
9
|
-
' _______
|
|
10
|
-
' / _____/
|
|
11
|
-
' / /____
|
|
12
|
-
' \\____ \\ / / /
|
|
13
|
-
' ____/ /
|
|
14
|
-
'/_____/
|
|
15
|
-
'
|
|
16
|
-
'
|
|
9
|
+
' _______ __ __ _______',
|
|
10
|
+
' / _____/| / /_/ /| / _____/|',
|
|
11
|
+
' / /____| | / __ / | / /____| |',
|
|
12
|
+
' \\____ \\ | / / / /| | \\____ \\ |',
|
|
13
|
+
' ____/ / | |/_/ /_/ / | |____/ / | |',
|
|
14
|
+
'/_____/ |//_/\\__/ / |//_____/ |/',
|
|
15
|
+
'\\_____\\___/ \\_\\ \\_\\/___/ \\_____\\___/',
|
|
16
|
+
' SNEAKOSCOPE CODEX'
|
|
17
17
|
].join('\n');
|
|
18
18
|
|
|
19
19
|
const SKS_TMUX_LOGO_FRAMES = [
|
|
20
20
|
[
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
'
|
|
25
|
-
'
|
|
26
|
-
'
|
|
27
|
-
'
|
|
28
|
-
|
|
29
|
-
[
|
|
30
|
-
' //||',
|
|
31
|
-
' .// || ..',
|
|
32
|
-
' // || ..::',
|
|
33
|
-
' // || ..::::',
|
|
34
|
-
' // || ..::::',
|
|
35
|
-
' // ||::::',
|
|
36
|
-
' S K S'
|
|
37
|
-
].join('\n'),
|
|
38
|
-
[
|
|
39
|
-
' ______ __ ______',
|
|
40
|
-
' . / ___/| . / /__ . / ___/|',
|
|
41
|
-
' / / /__ | | / //_/ / / /__ | |',
|
|
42
|
-
' / /\\__ \\ | | / ,< / /\\__ \\ | |',
|
|
43
|
-
' / /___/ / | | / /| | / /___/ / | |',
|
|
44
|
-
' /_/_____/ |/ /_/ |_| /_/_____/ |/',
|
|
45
|
-
' SNEAKOSCOPE'
|
|
21
|
+
' ||',
|
|
22
|
+
' ||',
|
|
23
|
+
' ||',
|
|
24
|
+
' ||',
|
|
25
|
+
' ||',
|
|
26
|
+
' ||',
|
|
27
|
+
' SKS',
|
|
28
|
+
' SNEAKOSCOPE CODEX'
|
|
46
29
|
].join('\n'),
|
|
47
30
|
[
|
|
48
|
-
'
|
|
49
|
-
'
|
|
50
|
-
'
|
|
51
|
-
'
|
|
52
|
-
'
|
|
53
|
-
'
|
|
54
|
-
'
|
|
31
|
+
' //||',
|
|
32
|
+
' // || .',
|
|
33
|
+
' // || .:',
|
|
34
|
+
' // || .::',
|
|
35
|
+
' // || .:::',
|
|
36
|
+
' // ||.::::',
|
|
37
|
+
' S K S',
|
|
38
|
+
' SNEAKOSCOPE CODEX'
|
|
55
39
|
].join('\n'),
|
|
56
40
|
[
|
|
57
|
-
'
|
|
58
|
-
'
|
|
59
|
-
'
|
|
60
|
-
'
|
|
61
|
-
'
|
|
62
|
-
'/
|
|
63
|
-
'
|
|
64
|
-
'
|
|
41
|
+
' _______ __ __ _______',
|
|
42
|
+
' / _____/| / /_/ /| / _____/|',
|
|
43
|
+
' / /____| | / __ / | / /____| |',
|
|
44
|
+
' \\____ \\ | / / / /| | \\____ \\ |',
|
|
45
|
+
' ____/ / | |/_/ /_/ / | |____/ / | |',
|
|
46
|
+
'/_____/ |//_/\\__/ / |//_____/ |/',
|
|
47
|
+
'\\_____\\___/ \\_\\ \\_\\/___/ \\_____\\___/',
|
|
48
|
+
' SNEAKOSCOPE CODEX'
|
|
65
49
|
].join('\n'),
|
|
66
50
|
[
|
|
67
51
|
' _______ __ __ _______',
|
|
68
52
|
' / _____/ / /_/ / / _____/|',
|
|
69
53
|
' / /____ / __ / / /____ | |',
|
|
70
54
|
' \\____ \\ / / / / \\____ \\| |',
|
|
71
|
-
' ____/ / /_/ /_/ ____/
|
|
72
|
-
'/_____/ /_/\\__/
|
|
73
|
-
' \\_____\\
|
|
74
|
-
'
|
|
55
|
+
' ____/ / /_/ /_/ / ____/ / | |',
|
|
56
|
+
'/_____/ /_/\\__/ /_____/ |/',
|
|
57
|
+
' \\_____\\ \\_\\ \\_\\ \\_____\\___/',
|
|
58
|
+
' SNEAKOSCOPE CODEX'
|
|
75
59
|
].join('\n'),
|
|
76
60
|
[
|
|
77
|
-
'
|
|
78
|
-
'
|
|
79
|
-
'
|
|
80
|
-
'
|
|
81
|
-
'
|
|
82
|
-
'
|
|
83
|
-
'
|
|
61
|
+
' _______ __ __ _______',
|
|
62
|
+
' / _____/| / /_/ /| / _____/|',
|
|
63
|
+
' / /____| | / __ / | / /____| |',
|
|
64
|
+
' \\____ \\ | / / / /| | \\____ \\ |',
|
|
65
|
+
' ____/ / | |/_/ /_/ / | |____/ / | |',
|
|
66
|
+
'/_____/ |//_/\\__/ / |//_____/ |/',
|
|
67
|
+
'\\_____\\___/ \\_\\ \\_\\/___/ \\_____\\___/',
|
|
68
|
+
' SNEAKOSCOPE CODEX'
|
|
84
69
|
].join('\n'),
|
|
85
70
|
[
|
|
86
|
-
'
|
|
87
|
-
'
|
|
88
|
-
'
|
|
89
|
-
'
|
|
90
|
-
'
|
|
91
|
-
'
|
|
92
|
-
'
|
|
71
|
+
' _______ __ __ _______',
|
|
72
|
+
' |\\_____ \\ / /_/ / |\\_____ \\',
|
|
73
|
+
' | |____\\ \\/ __ / | |____\\ \\',
|
|
74
|
+
' | |\\____\\/ / / / | |\\____\\ \\',
|
|
75
|
+
' | | |___/ /_/ /__ | | |___/ /',
|
|
76
|
+
' \\|_|/____/\\__/__/ \\|_|/____/',
|
|
77
|
+
' S K S',
|
|
78
|
+
' SNEAKOSCOPE CODEX'
|
|
93
79
|
].join('\n'),
|
|
94
80
|
[
|
|
95
|
-
'
|
|
96
|
-
'
|
|
97
|
-
'
|
|
98
|
-
'
|
|
99
|
-
'
|
|
100
|
-
'
|
|
101
|
-
'
|
|
81
|
+
' ||\\\\',
|
|
82
|
+
' . || \\\\',
|
|
83
|
+
' ::. || \\\\',
|
|
84
|
+
' ::::. || \\\\',
|
|
85
|
+
' ::::::. || \\\\',
|
|
86
|
+
' :::::::::|| \\\\',
|
|
87
|
+
' S K S',
|
|
88
|
+
' SNEAKOSCOPE CODEX'
|
|
89
|
+
].join('\n'),
|
|
90
|
+
[
|
|
91
|
+
' ||',
|
|
92
|
+
' ||',
|
|
93
|
+
' ||',
|
|
94
|
+
' ||',
|
|
95
|
+
' ||',
|
|
96
|
+
' ||',
|
|
97
|
+
' SKS',
|
|
98
|
+
' SNEAKOSCOPE CODEX'
|
|
102
99
|
].join('\n'),
|
|
103
100
|
SKS_TMUX_LOGO
|
|
104
101
|
];
|
|
@@ -205,7 +202,7 @@ export function tmuxStatusKind(tmux = {}) {
|
|
|
205
202
|
export function codexLaunchCommand(root, codexBin, codexArgs = []) {
|
|
206
203
|
const extraArgs = Array.isArray(codexArgs) ? codexArgs : [];
|
|
207
204
|
return [
|
|
208
|
-
sksLogoIntroCommand(),
|
|
205
|
+
sksLogoIntroCommand(codexBin),
|
|
209
206
|
`printf '\\nProject: %s\\n' ${shellEscape(root)}`,
|
|
210
207
|
'printf \'Runtime: tmux session for Codex CLI\\n\'',
|
|
211
208
|
'printf \'Prompt: use canonical $ commands, for example $Team or $QA-LOOP\\n\\n\'',
|
|
@@ -215,17 +212,25 @@ export function codexLaunchCommand(root, codexBin, codexArgs = []) {
|
|
|
215
212
|
].join('; ');
|
|
216
213
|
}
|
|
217
214
|
|
|
218
|
-
export function sksLogoIntroCommand() {
|
|
215
|
+
export function sksLogoIntroCommand(codexBin = 'codex') {
|
|
219
216
|
const staticLogo = `clear; printf '\\033[1;38;5;51m%s\\033[0m\\n' ${shellEscape(SKS_TMUX_LOGO)}`;
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
217
|
+
const authenticatedCheck = `${shellEscape(codexBin)} login status >/dev/null 2>&1`;
|
|
218
|
+
const animated = [
|
|
219
|
+
'clear',
|
|
220
|
+
'trap \'printf "\\033[0m\\033[?25h"\' EXIT INT TERM',
|
|
221
|
+
`printf '\\033[?25l'`,
|
|
222
|
+
...SKS_TMUX_LOGO_ANIMATION_STEPS.flatMap((step) => {
|
|
223
|
+
const style = `${step.bold ? '1;' : ''}38;5;${step.color}`;
|
|
224
|
+
return [
|
|
225
|
+
`printf '\\033[H\\033[J\\033[${style}m%s\\033[0m\\n' ${shellEscape(SKS_TMUX_LOGO_FRAMES[step.frame])}`,
|
|
226
|
+
`sleep ${step.delay}`
|
|
227
|
+
];
|
|
228
|
+
}),
|
|
229
|
+
`printf '\\033[H\\033[J\\033[1;38;5;51m%s\\033[0m\\n' ${shellEscape(SKS_TMUX_LOGO)}`,
|
|
230
|
+
`printf '\\033[?25h'`,
|
|
231
|
+
'trap - EXIT INT TERM'
|
|
232
|
+
].join('; ');
|
|
233
|
+
return `if [ -n "\${TMUX:-}" ] || [ "\${SKS_TMUX_LOGO_ANIMATION:-1}" = "0" ] || ${authenticatedCheck}; then ${staticLogo}; else ${animated}; fi`;
|
|
229
234
|
}
|
|
230
235
|
|
|
231
236
|
function terminalTitleCommand(title = '') {
|