sneakoscope 3.1.8 → 3.1.9
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/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/.sks-build-stamp.json +4 -4
- package/dist/bin/sks.js +1 -1
- package/dist/cli/args.js +17 -0
- package/dist/cli/command-registry.js +16 -13
- package/dist/cli/router.js +8 -5
- package/dist/commands/doctor.js +27 -2
- package/dist/core/commands/basic-cli.js +4 -1
- package/dist/core/commands/mad-sks-command.js +36 -13
- package/dist/core/commands/naruto-command.js +4 -1
- package/dist/core/commands/pipeline-command.js +3 -4
- package/dist/core/commands/qa-loop-command.js +36 -1
- package/dist/core/commands/research-command.js +61 -1
- package/dist/core/commands/team-command.js +63 -3
- package/dist/core/decision-contract.js +28 -4
- package/dist/core/doctor/command-alias-cleanup.js +64 -0
- package/dist/core/feature-fixtures.js +2 -0
- package/dist/core/feature-registry.js +2 -2
- package/dist/core/fsx.js +1 -1
- package/dist/core/naruto/naruto-work-graph.js +4 -1
- package/dist/core/pipeline-internals/runtime-core.js +50 -4
- package/dist/core/pipeline-internals/runtime-gates.js +10 -1
- package/dist/core/proof/route-proof-gate.js +1 -1
- package/dist/core/qa-loop.js +227 -11
- package/dist/core/questions.js +239 -2
- package/dist/core/routes.js +3 -4
- package/dist/core/version.js +1 -1
- package/dist/scripts/agent-native-release-gate.js +13 -4
- package/package.json +1 -1
package/dist/core/qa-loop.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { exists, nowIso, readJson, readText, writeJsonAtomic, writeTextAtomic, PACKAGE_VERSION } from './fsx.js';
|
|
3
|
-
import { CODEX_WEB_VERIFICATION_EVIDENCE_SOURCE, CODEX_WEB_VERIFICATION_POLICY, evidenceMentionsForbiddenBrowserAutomation, evidenceMentionsForbiddenWebComputerUseEvidence } from './routes.js';
|
|
3
|
+
import { CODEX_APP_IMAGE_GENERATION_DOC_URL, CODEX_IMAGEGEN_REQUIRED_POLICY, CODEX_WEB_VERIFICATION_EVIDENCE_SOURCE, CODEX_WEB_VERIFICATION_POLICY, evidenceMentionsForbiddenBrowserAutomation, evidenceMentionsForbiddenWebComputerUseEvidence } from './routes.js';
|
|
4
4
|
import { appendAgentLedgerEvent, initializeAgentCentralLedger } from './agents/agent-central-ledger.js';
|
|
5
5
|
import { resolveCodexAppExecutionProfile } from './codex-app/codex-app-execution-profile.js';
|
|
6
6
|
import { resolveCodexNativeInvocationPlan } from './codex-native/codex-native-invocation-router.js';
|
|
7
|
+
import { imageDimensions, sha256File } from './wiki-image/image-hash.js';
|
|
7
8
|
export const QA_LOOP_ROUTE = 'QALoop';
|
|
9
|
+
export const QA_LOOP_VISUAL_EVIDENCE_ARTIFACT = 'qa-loop/visual-evidence.json';
|
|
8
10
|
const QA_REPORT_SUFFIX = 'qa-report.md';
|
|
9
11
|
const UI_CHROME_EXTENSION_FIRST_ACK = 'use_codex_chrome_extension_first_no_computer_use_for_web_ui_or_mark_unverified';
|
|
12
|
+
const GPT_IMAGE_2_ANNOTATED_REVIEW_REQUIRED_ACK = 'yes_gpt_image_2_annotated_review';
|
|
13
|
+
const IMAGE_FILE_RE = /\.(png|jpe?g|webp|gif)$/i;
|
|
10
14
|
export const QA_NATIVE_AGENT_PERSONAS = Object.freeze([
|
|
11
15
|
{
|
|
12
16
|
id: 'qa_verifier_ui',
|
|
@@ -108,6 +112,9 @@ function promptText(prompt = '') {
|
|
|
108
112
|
function lowerPrompt(prompt = '') {
|
|
109
113
|
return promptText(prompt).toLowerCase();
|
|
110
114
|
}
|
|
115
|
+
function qaPromptWantsGptImage2AnnotatedReview(prompt = '') {
|
|
116
|
+
return /(gpt-image-2|gpt\s*image\s*2|imagegen|\$imagegen|annotated\s+review|annotated\s+image|callout|generated\s+review\s+image|이미지\s*리뷰|생성\s*이미지|주석\s*이미지|콜아웃)/i.test(promptText(prompt));
|
|
117
|
+
}
|
|
111
118
|
function firstUrl(prompt = '') {
|
|
112
119
|
return promptText(prompt).match(/https?:\/\/[^\s)\]}>,]+/i)?.[0] || '';
|
|
113
120
|
}
|
|
@@ -152,6 +159,16 @@ export function inferQaLoopAnswers(prompt = '') {
|
|
|
152
159
|
const local = environment === 'local_dev_server';
|
|
153
160
|
const login = loginPolicyFromPrompt(text);
|
|
154
161
|
const scope = qaScopeFromPrompt(text);
|
|
162
|
+
const wantsGptImage2Review = isUiScope(scope) && qaPromptWantsGptImage2AnnotatedReview(text);
|
|
163
|
+
const acceptance = [
|
|
164
|
+
'앱 첫 화면 또는 지정된 대상이 정상 로드된다.',
|
|
165
|
+
'주요 내비게이션과 핵심 화면 진입에서 콘솔/화면상 치명 오류가 없다.',
|
|
166
|
+
'검증하지 못한 UI/API 범위는 통과로 주장하지 않고 QA 리포트에 남긴다.'
|
|
167
|
+
];
|
|
168
|
+
if (isUiScope(scope))
|
|
169
|
+
acceptance.push('UI E2E 통과 증거는 실제 Codex Chrome Extension screenshot artifact path와 sha256을 기록해야 한다.');
|
|
170
|
+
if (wantsGptImage2Review)
|
|
171
|
+
acceptance.push('gpt-image-2 annotated review image가 필요한 경우 실제 Codex App $imagegen/gpt-image-2 출력 파일 path, sha256, model, provider를 기록해야 한다.');
|
|
155
172
|
return {
|
|
156
173
|
GOAL_PRECISE: text ? `현재 요청 범위에서 QA-LOOP를 안전하게 실행한다: ${text}` : '현재 로컬 개발 환경에서 핵심 사용자 흐름을 안전하게 QA한다.',
|
|
157
174
|
QA_SCOPE: scope,
|
|
@@ -165,13 +182,10 @@ export function inferQaLoopAnswers(prompt = '') {
|
|
|
165
182
|
...login,
|
|
166
183
|
CREDENTIAL_STORAGE_ACK: 'never_store_credentials_in_artifacts_or_wiki',
|
|
167
184
|
UI_CHROME_EXTENSION_ACK: UI_CHROME_EXTENSION_FIRST_ACK,
|
|
185
|
+
QA_VISUAL_REVIEW_IMAGEGEN_REQUIRED: wantsGptImage2Review ? GPT_IMAGE_2_ANNOTATED_REVIEW_REQUIRED_ACK : 'not_required',
|
|
168
186
|
TEAM_MODE_ALLOWED: 'no_parent_only',
|
|
169
187
|
MAX_QA_CYCLES: '1',
|
|
170
|
-
ACCEPTANCE_CRITERIA:
|
|
171
|
-
'앱 첫 화면 또는 지정된 대상이 정상 로드된다.',
|
|
172
|
-
'주요 내비게이션과 핵심 화면 진입에서 콘솔/화면상 치명 오류가 없다.',
|
|
173
|
-
'검증하지 못한 UI/API 범위는 통과로 주장하지 않고 QA 리포트에 남긴다.'
|
|
174
|
-
],
|
|
188
|
+
ACCEPTANCE_CRITERIA: acceptance,
|
|
175
189
|
NON_GOALS: [
|
|
176
190
|
'결제, 실제 이메일/SMS 발송, 관리자 권한 변경, 데이터 삭제, 프로덕션 데이터 변경은 테스트하지 않는다.'
|
|
177
191
|
],
|
|
@@ -290,10 +304,22 @@ export function qaUiRequired(a = {}) {
|
|
|
290
304
|
export function qaApiRequired(a = {}) {
|
|
291
305
|
return a.QA_SCOPE === 'all_available' ? hasApiTarget(a) : isApiScope(a.QA_SCOPE);
|
|
292
306
|
}
|
|
307
|
+
export function qaGptImage2AnnotatedReviewRequired(contractOrAnswers = {}, prompt = '') {
|
|
308
|
+
const answers = contractOrAnswers?.answers || contractOrAnswers || {};
|
|
309
|
+
if (!qaUiRequired(answers))
|
|
310
|
+
return false;
|
|
311
|
+
const explicit = String(answers.QA_VISUAL_REVIEW_IMAGEGEN_REQUIRED || answers.GPT_IMAGE_2_ANNOTATED_REVIEW_REQUIRED || '').trim();
|
|
312
|
+
if (/^(yes|true|required|yes_gpt_image_2_annotated_review)$/i.test(explicit))
|
|
313
|
+
return true;
|
|
314
|
+
if (/^(no|false|not_required|none)$/i.test(explicit))
|
|
315
|
+
return false;
|
|
316
|
+
return qaPromptWantsGptImage2AnnotatedReview(`${prompt || ''}\n${answers.GOAL_PRECISE || ''}\n${JSON.stringify(answers.ACCEPTANCE_CRITERIA || [])}`);
|
|
317
|
+
}
|
|
293
318
|
export function defaultQaGate(contract = {}, opts = {}) {
|
|
294
319
|
const a = contract.answers || {};
|
|
295
320
|
const uiRequired = qaUiRequired(a);
|
|
296
321
|
const apiRequired = qaApiRequired(a);
|
|
322
|
+
const gptImage2ReviewRequired = qaGptImage2AnnotatedReviewRequired(contract, contract.prompt);
|
|
297
323
|
const reportFile = opts.reportFile || qaReportFilename();
|
|
298
324
|
const corrective = a.QA_CORRECTIVE_POLICY !== 'report_only_no_code_changes';
|
|
299
325
|
return {
|
|
@@ -311,6 +337,17 @@ export function defaultQaGate(contract = {}, opts = {}) {
|
|
|
311
337
|
ui_chrome_extension_evidence: !uiRequired,
|
|
312
338
|
ui_computer_use_evidence: false,
|
|
313
339
|
ui_evidence_source: uiRequired ? null : 'not_required',
|
|
340
|
+
ui_chrome_extension_screenshot_required: uiRequired,
|
|
341
|
+
ui_chrome_extension_screenshot_captured: !uiRequired,
|
|
342
|
+
ui_chrome_extension_screenshot_artifact: null,
|
|
343
|
+
ui_chrome_extension_screenshot_sha256: null,
|
|
344
|
+
gpt_image_2_annotated_review_required: gptImage2ReviewRequired,
|
|
345
|
+
gpt_image_2_annotated_review_generated: !gptImage2ReviewRequired,
|
|
346
|
+
gpt_image_2_annotated_review_artifact: null,
|
|
347
|
+
gpt_image_2_annotated_review_sha256: null,
|
|
348
|
+
gpt_image_2_annotated_review_model: gptImage2ReviewRequired ? null : 'not_required',
|
|
349
|
+
gpt_image_2_annotated_review_provider: gptImage2ReviewRequired ? null : 'not_required',
|
|
350
|
+
qa_visual_evidence_artifact: QA_LOOP_VISUAL_EVIDENCE_ARTIFACT,
|
|
314
351
|
desktop_app_handoff_required: false,
|
|
315
352
|
desktop_app_handoff_status: 'not_requested',
|
|
316
353
|
desktop_app_handoff_artifact: null,
|
|
@@ -360,13 +397,48 @@ export async function writeQaLoopArtifacts(dir, mission, contract) {
|
|
|
360
397
|
codex_app_execution_profile: executionProfile ? compactExecutionProfile(executionProfile) : null,
|
|
361
398
|
codex_native_invocation: codexNativeInvocation,
|
|
362
399
|
target: { scope: a.QA_SCOPE, environment: a.TARGET_ENVIRONMENT, base_url: a.TARGET_BASE_URL, api_base_url: a.API_BASE_URL },
|
|
363
|
-
safety: { mutation_policy: a.QA_MUTATION_POLICY, deployed_destructive_tests_allowed: 'never', credentials: 'temp_only_never_saved', ui_evidence: 'codex_chrome_extension_first_required_for_web_ui_e2e' },
|
|
400
|
+
safety: { mutation_policy: a.QA_MUTATION_POLICY, deployed_destructive_tests_allowed: 'never', credentials: 'temp_only_never_saved', ui_evidence: 'codex_chrome_extension_first_required_for_web_ui_e2e', visual_review: 'gpt_image_2_annotated_review_required_when_contract_requests_it' },
|
|
364
401
|
checklist
|
|
365
402
|
});
|
|
403
|
+
await writeJsonAtomic(path.join(dir, QA_LOOP_VISUAL_EVIDENCE_ARTIFACT), buildQaLoopVisualEvidenceArtifact(mission, contract));
|
|
366
404
|
await writeJsonAtomic(path.join(dir, 'qa-gate.json'), defaultQaGate(contract, { reportFile, executionProfile, codexNativeInvocation }));
|
|
367
405
|
await writeTextAtomic(path.join(dir, reportFile), qaReportTemplate(mission, contract, checklist));
|
|
368
406
|
return { checklist_count: checklist.length, report_file: reportFile };
|
|
369
407
|
}
|
|
408
|
+
export async function ensureQaLoopVisualEvidenceContract(dir, mission = {}, contract = {}) {
|
|
409
|
+
const visualPath = path.join(dir, QA_LOOP_VISUAL_EVIDENCE_ARTIFACT);
|
|
410
|
+
if (!(await exists(visualPath))) {
|
|
411
|
+
await writeJsonAtomic(visualPath, buildQaLoopVisualEvidenceArtifact(mission, contract));
|
|
412
|
+
}
|
|
413
|
+
const gatePath = path.join(dir, 'qa-gate.json');
|
|
414
|
+
const gate = await readJson(gatePath, null);
|
|
415
|
+
if (!gate)
|
|
416
|
+
return;
|
|
417
|
+
const defaults = defaultQaGate(contract, { reportFile: qaReportFileFromGate(gate) || qaReportFilename() });
|
|
418
|
+
const keys = [
|
|
419
|
+
'ui_chrome_extension_screenshot_required',
|
|
420
|
+
'ui_chrome_extension_screenshot_captured',
|
|
421
|
+
'ui_chrome_extension_screenshot_artifact',
|
|
422
|
+
'ui_chrome_extension_screenshot_sha256',
|
|
423
|
+
'gpt_image_2_annotated_review_required',
|
|
424
|
+
'gpt_image_2_annotated_review_generated',
|
|
425
|
+
'gpt_image_2_annotated_review_artifact',
|
|
426
|
+
'gpt_image_2_annotated_review_sha256',
|
|
427
|
+
'gpt_image_2_annotated_review_model',
|
|
428
|
+
'gpt_image_2_annotated_review_provider',
|
|
429
|
+
'qa_visual_evidence_artifact'
|
|
430
|
+
];
|
|
431
|
+
const next = { ...gate };
|
|
432
|
+
let changed = false;
|
|
433
|
+
for (const key of keys) {
|
|
434
|
+
if (next[key] === undefined) {
|
|
435
|
+
next[key] = defaults[key];
|
|
436
|
+
changed = true;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
if (changed)
|
|
440
|
+
await writeJsonAtomic(gatePath, next);
|
|
441
|
+
}
|
|
370
442
|
export async function evaluateQaGate(dir) {
|
|
371
443
|
const gate = await readJson(path.join(dir, 'qa-gate.json'), {});
|
|
372
444
|
const reportFile = qaReportFileFromGate(gate);
|
|
@@ -400,6 +472,10 @@ export async function evaluateQaGate(dir) {
|
|
|
400
472
|
reasons.push('forbidden_browser_automation_evidence');
|
|
401
473
|
if (evidenceMentionsForbiddenWebComputerUseEvidence({ evidence: gate.evidence, ui_evidence_source: gate.ui_evidence_source }))
|
|
402
474
|
reasons.push('computer_use_web_evidence_forbidden');
|
|
475
|
+
reasons.push(...await missingQaLoopVisualEvidence(dir, gate));
|
|
476
|
+
}
|
|
477
|
+
else if (gate.gpt_image_2_annotated_review_required === true) {
|
|
478
|
+
reasons.push(...await missingQaLoopVisualEvidence(dir, gate));
|
|
403
479
|
}
|
|
404
480
|
if (gate.desktop_app_handoff_required === true) {
|
|
405
481
|
if (!['pending', 'launched_pending_confirmation', 'completed'].includes(String(gate.desktop_app_handoff_status || '')))
|
|
@@ -424,8 +500,9 @@ export async function evaluateQaGate(dir) {
|
|
|
424
500
|
reasons.push('qa_report_missing');
|
|
425
501
|
if (!(await exists(path.join(dir, 'qa-ledger.json'))))
|
|
426
502
|
reasons.push('qa_ledger_missing');
|
|
427
|
-
const
|
|
428
|
-
const
|
|
503
|
+
const uniqueReasons = [...new Set(reasons)];
|
|
504
|
+
const passed = gate.passed === true && uniqueReasons.length === 0;
|
|
505
|
+
const result = { checked_at: nowIso(), passed, reasons: uniqueReasons, gate };
|
|
429
506
|
await writeJsonAtomic(path.join(dir, 'qa-gate.evaluated.json'), result);
|
|
430
507
|
return result;
|
|
431
508
|
}
|
|
@@ -514,12 +591,19 @@ ARTIFACTS: update qa-ledger.json, ${report}, qa-gate.json, and qa-loop/cycle-${c
|
|
|
514
591
|
CONTRACT:
|
|
515
592
|
${JSON.stringify(contract, null, 2)}
|
|
516
593
|
${imageContractText}${appHandoffText}${executionProfileText}
|
|
594
|
+
VISUAL EVIDENCE CONTRACT:
|
|
595
|
+
- For web UI QA, do not set chrome_extension_preflight_passed/ui_chrome_extension_evidence to true unless the Codex Chrome Extension path is ready and ${QA_LOOP_VISUAL_EVIDENCE_ARTIFACT} records a real saved Chrome Extension screenshot artifact with path, sha256, and dimensions.
|
|
596
|
+
- If decision-contract.json answers set QA_VISUAL_REVIEW_IMAGEGEN_REQUIRED=${GPT_IMAGE_2_ANNOTATED_REVIEW_REQUIRED_ACK}, use Codex App $imagegen/gpt-image-2 (${CODEX_APP_IMAGE_GENERATION_DOC_URL}) to produce a real generated annotated review image from the Chrome Extension screenshot. Record its path, sha256, model=gpt-image-2, provider=Codex App $imagegen, and source_screenshot_artifact in ${QA_LOOP_VISUAL_EVIDENCE_ARTIFACT} and qa-gate.json.
|
|
597
|
+
- Do not substitute prose-only critique, Playwright/Selenium/Puppeteer/Browser Use screenshots, Computer Use browser screenshots, placeholder images, fake fixtures, or direct API fallback as full web UI visual evidence.
|
|
517
598
|
Previous tail:
|
|
518
599
|
${String(previous || '').slice(-2500)}
|
|
519
600
|
`;
|
|
520
601
|
}
|
|
521
602
|
export async function qaStatus(dir) {
|
|
522
|
-
const
|
|
603
|
+
const mission = await readJson(path.join(dir, 'mission.json'), {});
|
|
604
|
+
const contract = await readJson(path.join(dir, 'decision-contract.json'), { prompt: mission.prompt, answers: {}, sealed_hash: null });
|
|
605
|
+
await ensureQaLoopVisualEvidenceContract(dir, mission, contract).catch(() => undefined);
|
|
606
|
+
const gate = await evaluateQaGate(dir).catch(async () => await readJson(path.join(dir, 'qa-gate.evaluated.json'), await readJson(path.join(dir, 'qa-gate.json'), null)));
|
|
523
607
|
const ledger = await readJson(path.join(dir, 'qa-ledger.json'), null);
|
|
524
608
|
const appHandoff = await readJson(path.join(dir, 'qa-loop', 'app-handoff.json'), null);
|
|
525
609
|
const appConfirmation = await readJson(path.join(dir, 'qa-loop', 'app-handoff-confirmation.json'), null);
|
|
@@ -545,6 +629,138 @@ function qaChecklist(a) {
|
|
|
545
629
|
cases.push(['report.evidence', 'Record pass/fail/blocked/skipped with evidence.'], ['report.corrective_loop', 'Record fixes, rechecks, unresolved findings, deferred blockers.'], ['report.honest', 'Run Honest Mode.']);
|
|
546
630
|
return cases.map(([id, title]) => ({ id, title, status: 'pending', evidence: [] }));
|
|
547
631
|
}
|
|
632
|
+
export function buildQaLoopVisualEvidenceArtifact(mission = {}, contract = {}) {
|
|
633
|
+
const answers = contract.answers || {};
|
|
634
|
+
const uiRequired = qaUiRequired(answers);
|
|
635
|
+
const gptImage2ReviewRequired = qaGptImage2AnnotatedReviewRequired(contract, contract.prompt || mission.prompt);
|
|
636
|
+
return {
|
|
637
|
+
schema: 'sks.qa-loop-visual-evidence.v1',
|
|
638
|
+
generated_at: nowIso(),
|
|
639
|
+
mission_id: mission.id || contract.mission_id || null,
|
|
640
|
+
contract_hash: contract.sealed_hash || null,
|
|
641
|
+
required: uiRequired || gptImage2ReviewRequired,
|
|
642
|
+
chrome_extension_screenshot: {
|
|
643
|
+
required: uiRequired,
|
|
644
|
+
status: uiRequired ? 'pending' : 'not_required',
|
|
645
|
+
evidence_source: CODEX_WEB_VERIFICATION_EVIDENCE_SOURCE,
|
|
646
|
+
artifact_path: null,
|
|
647
|
+
sha256: null,
|
|
648
|
+
width: null,
|
|
649
|
+
height: null,
|
|
650
|
+
privacy: 'local-only'
|
|
651
|
+
},
|
|
652
|
+
gpt_image_2_annotated_review: {
|
|
653
|
+
required: gptImage2ReviewRequired,
|
|
654
|
+
status: gptImage2ReviewRequired ? 'pending' : 'not_required',
|
|
655
|
+
model: gptImage2ReviewRequired ? 'gpt-image-2' : 'not_required',
|
|
656
|
+
provider: gptImage2ReviewRequired ? 'Codex App $imagegen' : 'not_required',
|
|
657
|
+
source_screenshot_artifact: null,
|
|
658
|
+
artifact_path: null,
|
|
659
|
+
sha256: null,
|
|
660
|
+
width: null,
|
|
661
|
+
height: null,
|
|
662
|
+
required_output: gptImage2ReviewRequired ? 'generated_annotated_review_image_with_numbered_callouts_severity_labels_and_visual_marks' : 'not_required',
|
|
663
|
+
docs_url: CODEX_APP_IMAGE_GENERATION_DOC_URL,
|
|
664
|
+
privacy: 'local-only'
|
|
665
|
+
},
|
|
666
|
+
blockers: uiRequired ? ['chrome_extension_screenshot_missing'] : [],
|
|
667
|
+
notes: [
|
|
668
|
+
'QA-LOOP web visual evidence must be backed by real saved local image files.',
|
|
669
|
+
CODEX_WEB_VERIFICATION_POLICY,
|
|
670
|
+
CODEX_IMAGEGEN_REQUIRED_POLICY
|
|
671
|
+
]
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
async function missingQaLoopVisualEvidence(dir, gate = {}) {
|
|
675
|
+
const visual = await readJson(path.join(dir, QA_LOOP_VISUAL_EVIDENCE_ARTIFACT), null);
|
|
676
|
+
const reasons = [];
|
|
677
|
+
const uiRequired = gate.ui_e2e_required === true;
|
|
678
|
+
if (uiRequired) {
|
|
679
|
+
const screenshot = visual?.chrome_extension_screenshot || {};
|
|
680
|
+
if (gate.ui_chrome_extension_screenshot_captured !== true && !positiveVisualStatus(screenshot.status, ['captured', 'attached', 'verified']))
|
|
681
|
+
reasons.push('ui_chrome_extension_screenshot_missing');
|
|
682
|
+
const screenshotPath = firstNonEmpty(gate.ui_chrome_extension_screenshot_artifact, gate.chrome_extension_screenshot_artifact, gate.ui_chrome_extension_screenshot?.path, gate.chrome_extension_screenshot?.path, screenshot.artifact_path, screenshot.path);
|
|
683
|
+
const screenshotSha = firstNonEmpty(gate.ui_chrome_extension_screenshot_sha256, gate.chrome_extension_screenshot_sha256, gate.ui_chrome_extension_screenshot?.sha256, gate.chrome_extension_screenshot?.sha256, screenshot.sha256);
|
|
684
|
+
const screenshotDims = {
|
|
685
|
+
width: firstNonEmpty(gate.ui_chrome_extension_screenshot_width, gate.ui_chrome_extension_screenshot?.width, gate.chrome_extension_screenshot?.width, screenshot.width),
|
|
686
|
+
height: firstNonEmpty(gate.ui_chrome_extension_screenshot_height, gate.ui_chrome_extension_screenshot?.height, gate.chrome_extension_screenshot?.height, screenshot.height)
|
|
687
|
+
};
|
|
688
|
+
if (!screenshotPath)
|
|
689
|
+
reasons.push('ui_chrome_extension_screenshot_artifact_missing');
|
|
690
|
+
else
|
|
691
|
+
reasons.push(...await imageEvidenceFileReasons(dir, screenshotPath, screenshotSha, 'ui_chrome_extension_screenshot', screenshotDims));
|
|
692
|
+
const screenshotSource = firstNonEmpty(gate.ui_chrome_extension_screenshot_source, screenshot.evidence_source, gate.ui_evidence_source);
|
|
693
|
+
if (screenshotSource !== CODEX_WEB_VERIFICATION_EVIDENCE_SOURCE)
|
|
694
|
+
reasons.push('ui_chrome_extension_screenshot_source_not_codex_chrome_extension');
|
|
695
|
+
}
|
|
696
|
+
const review = visual?.gpt_image_2_annotated_review || {};
|
|
697
|
+
const gptImage2ReviewRequired = gate.gpt_image_2_annotated_review_required === true || review.required === true;
|
|
698
|
+
if (gptImage2ReviewRequired) {
|
|
699
|
+
if (gate.gpt_image_2_annotated_review_generated !== true && !positiveVisualStatus(review.status, ['generated', 'attached', 'verified']))
|
|
700
|
+
reasons.push('gpt_image_2_annotated_review_image_missing');
|
|
701
|
+
const reviewPath = firstNonEmpty(gate.gpt_image_2_annotated_review_artifact, gate.imagegen_annotated_review_artifact, gate.gpt_image_2_annotated_review?.path, gate.gpt_image_2_annotated_review_image?.path, review.artifact_path, review.path);
|
|
702
|
+
const reviewSha = firstNonEmpty(gate.gpt_image_2_annotated_review_sha256, gate.gpt_image_2_annotated_review?.sha256, gate.gpt_image_2_annotated_review_image?.sha256, review.sha256);
|
|
703
|
+
const reviewDims = {
|
|
704
|
+
width: firstNonEmpty(gate.gpt_image_2_annotated_review_width, gate.gpt_image_2_annotated_review?.width, gate.gpt_image_2_annotated_review_image?.width, review.width),
|
|
705
|
+
height: firstNonEmpty(gate.gpt_image_2_annotated_review_height, gate.gpt_image_2_annotated_review?.height, gate.gpt_image_2_annotated_review_image?.height, review.height)
|
|
706
|
+
};
|
|
707
|
+
if (!reviewPath)
|
|
708
|
+
reasons.push('gpt_image_2_annotated_review_artifact_missing');
|
|
709
|
+
else
|
|
710
|
+
reasons.push(...await imageEvidenceFileReasons(dir, reviewPath, reviewSha, 'gpt_image_2_annotated_review', reviewDims));
|
|
711
|
+
const model = firstNonEmpty(gate.gpt_image_2_annotated_review_model, gate.gpt_image_2_annotated_review?.model, gate.gpt_image_2_annotated_review_image?.model, review.model, review.provider?.model);
|
|
712
|
+
if (model !== 'gpt-image-2')
|
|
713
|
+
reasons.push('gpt_image_2_annotated_review_model_missing');
|
|
714
|
+
const provider = firstNonEmpty(gate.gpt_image_2_annotated_review_provider, gate.gpt_image_2_annotated_review?.provider, gate.gpt_image_2_annotated_review_image?.provider, review.provider, review.provider_surface);
|
|
715
|
+
if (!provider || !/codex\s+app|\$imagegen|codex_app_imagegen/i.test(String(provider)))
|
|
716
|
+
reasons.push('gpt_image_2_annotated_review_provider_not_codex_app_imagegen');
|
|
717
|
+
if (/mock|fake|fixture|placeholder|text[-_ ]?only|direct\s+api|openai_images_api|responses_image_generation/i.test(String(provider)))
|
|
718
|
+
reasons.push('gpt_image_2_annotated_review_provider_forbidden');
|
|
719
|
+
const sourceScreenshot = firstNonEmpty(gate.gpt_image_2_source_screenshot_artifact, gate.gpt_image_2_annotated_review?.source_screenshot_artifact, gate.gpt_image_2_annotated_review_image?.source_screenshot_artifact, review.source_screenshot_artifact, gate.ui_chrome_extension_screenshot_artifact);
|
|
720
|
+
if (!sourceScreenshot)
|
|
721
|
+
reasons.push('gpt_image_2_source_screenshot_artifact_missing');
|
|
722
|
+
}
|
|
723
|
+
return [...new Set(reasons)];
|
|
724
|
+
}
|
|
725
|
+
function positiveVisualStatus(status, accepted) {
|
|
726
|
+
return accepted.includes(String(status || '').trim().toLowerCase());
|
|
727
|
+
}
|
|
728
|
+
function firstNonEmpty(...values) {
|
|
729
|
+
for (const value of values) {
|
|
730
|
+
if (typeof value === 'string' && value.trim())
|
|
731
|
+
return value.trim();
|
|
732
|
+
if (value && typeof value !== 'string')
|
|
733
|
+
return value;
|
|
734
|
+
}
|
|
735
|
+
return null;
|
|
736
|
+
}
|
|
737
|
+
async function imageEvidenceFileReasons(dir, artifactPath, declaredSha, prefix, declaredDims = {}) {
|
|
738
|
+
const reasons = [];
|
|
739
|
+
const resolved = resolveEvidencePath(dir, artifactPath);
|
|
740
|
+
if (!resolved)
|
|
741
|
+
return [`${prefix}_artifact_path_invalid`];
|
|
742
|
+
if (!IMAGE_FILE_RE.test(resolved))
|
|
743
|
+
reasons.push(`${prefix}_artifact_not_image_file`);
|
|
744
|
+
if (!(await exists(resolved)))
|
|
745
|
+
return [...reasons, `${prefix}_artifact_file_missing`];
|
|
746
|
+
const sha = await sha256File(resolved).catch(() => null);
|
|
747
|
+
if (!declaredSha)
|
|
748
|
+
reasons.push(`${prefix}_sha256_missing`);
|
|
749
|
+
else if (sha && String(declaredSha) !== sha)
|
|
750
|
+
reasons.push(`${prefix}_sha256_mismatch`);
|
|
751
|
+
const dims = await imageDimensions(resolved).catch(() => null);
|
|
752
|
+
const width = Number(dims?.width ?? declaredDims?.width);
|
|
753
|
+
const height = Number(dims?.height ?? declaredDims?.height);
|
|
754
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0)
|
|
755
|
+
reasons.push(`${prefix}_dimensions_missing`);
|
|
756
|
+
return reasons;
|
|
757
|
+
}
|
|
758
|
+
function resolveEvidencePath(dir, artifactPath) {
|
|
759
|
+
const value = String(artifactPath || '').trim().replace(/^file:\/\//i, '');
|
|
760
|
+
if (!value || /^https?:\/\//i.test(value))
|
|
761
|
+
return null;
|
|
762
|
+
return path.isAbsolute(value) ? value : path.resolve(dir, value);
|
|
763
|
+
}
|
|
548
764
|
function missionRootFromDir(dir) {
|
|
549
765
|
const normalized = path.resolve(String(dir || ''));
|
|
550
766
|
const marker = `${path.sep}.sneakoscope${path.sep}missions${path.sep}`;
|
|
@@ -564,7 +780,7 @@ function compactExecutionProfile(profile) {
|
|
|
564
780
|
}
|
|
565
781
|
function qaReportTemplate(mission, contract, checklist) {
|
|
566
782
|
const a = contract.answers || {};
|
|
567
|
-
return `# QA-LOOP Report\n\nMission: ${mission.id}\nTarget: ${a.TARGET_BASE_URL || 'unset'}\nScope: ${a.QA_SCOPE || 'unset'}\nEnvironment: ${a.TARGET_ENVIRONMENT || 'unset'}\n\n## Safety\n\n- Deployed destructive tests: never\n- Credentials: temp-only, never saved\n- UI evidence: ${CODEX_WEB_VERIFICATION_POLICY}\n\n## Checklist\n\n${checklist.map((item) => `- [ ] ${item.id}: ${item.title}`).join('\n')}\n\n## Findings\n\nTBD\n\n## Corrections And Rechecks\n\nTBD\n\n## Honest Mode\n\nTBD\n`;
|
|
783
|
+
return `# QA-LOOP Report\n\nMission: ${mission.id}\nTarget: ${a.TARGET_BASE_URL || 'unset'}\nScope: ${a.QA_SCOPE || 'unset'}\nEnvironment: ${a.TARGET_ENVIRONMENT || 'unset'}\n\n## Safety\n\n- Deployed destructive tests: never\n- Credentials: temp-only, never saved\n- UI evidence: ${CODEX_WEB_VERIFICATION_POLICY}\n- Visual evidence ledger: ${QA_LOOP_VISUAL_EVIDENCE_ARTIFACT}\n\n## Checklist\n\n${checklist.map((item) => `- [ ] ${item.id}: ${item.title}`).join('\n')}\n\n## Findings\n\nTBD\n\n## Corrections And Rechecks\n\nTBD\n\n## Honest Mode\n\nTBD\n`;
|
|
568
784
|
}
|
|
569
785
|
function positiveCount(value) {
|
|
570
786
|
const n = Number(value || 0);
|
package/dist/core/questions.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { writeJsonAtomic, writeTextAtomic } from './fsx.js';
|
|
2
|
+
import { nowIso, sha256, writeJsonAtomic, writeTextAtomic } from './fsx.js';
|
|
3
3
|
import { buildQaLoopQuestionSchema } from './qa-loop.js';
|
|
4
4
|
import { CODEX_COMPUTER_USE_ONLY_POLICY, CODEX_WEB_VERIFICATION_POLICY, FROM_CHAT_IMG_CHECKLIST_ARTIFACT, FROM_CHAT_IMG_COVERAGE_ARTIFACT, FROM_CHAT_IMG_QA_LOOP_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT, hasFromChatImgSignal } from './routes.js';
|
|
5
|
+
export const REQUEST_INTAKE_ARTIFACT = 'request-intake.json';
|
|
5
6
|
export function buildQuestionSchemaForRoute(route, prompt) {
|
|
6
7
|
if (String(route?.id || '') === 'QALoop')
|
|
7
8
|
return buildQaLoopQuestionSchema(prompt);
|
|
@@ -222,6 +223,8 @@ export function inferAnswersForPrompt(prompt, explicitAnswers = {}) {
|
|
|
222
223
|
const versionWork = /버전|version|bump|release|publish:dry|npm\s+pack/.test(lower);
|
|
223
224
|
const installWork = /bootstrap|postinstall|doctor|deps|tmux|homebrew|first install|최초\s*설치|설치\s*ux|셋업|setup/.test(lower);
|
|
224
225
|
const questionGateWork = /모호|ambiguity|clarification|질문|triwiki|추론|infer|predict|예측|answers?\.json|decision-contract/.test(lower);
|
|
226
|
+
const requestIntakeWork = /(모호|ambiguity|ambiguous|vague|rough|의도|intent|요청사항|requirements?|프롬프트|prompt|triwiki|위키|wiki)/.test(lower)
|
|
227
|
+
&& /(파이프라인|pipeline|변환|transform|rewrite|compile|리스트|list|누락|missing|의도|intent|request[- ]?intake|intake)/.test(lower);
|
|
225
228
|
const uiuxWork = /\b(ui|modal|screen|button|visual|design|layout|component|prototype|frontend)\b|화면|버튼|모달|디자인|레이아웃|컴포넌트|프론트|시각|발표자료|디자인\s*시스템/.test(lower);
|
|
226
229
|
const presentationWork = looksLikePresentationArtifactPrompt(lower);
|
|
227
230
|
const dbWork = new RegExp(["\\bdb\\b", "database", "schema", "migration", "tab" + "le", "col" + "umn", "rls", "supabase", "postgres", "sql", "테이블", "마이그레이션", "스키마", "컬럼", "열", "행", "데이터베이스"].join("|")).test(lower);
|
|
@@ -247,12 +250,13 @@ export function inferAnswersForPrompt(prompt, explicitAnswers = {}) {
|
|
|
247
250
|
&& !versionWork
|
|
248
251
|
&& !presentationWork
|
|
249
252
|
&& !chatCaptureWork;
|
|
250
|
-
const kind = versionWork ? 'version' : chatCaptureWork ? 'chat_capture' : triwikiAuditWork ? 'triwiki_audit' : effectivePrioritySignalWork ? 'priority' : questionGateWork ? 'questions' : installWork ? 'install' : null;
|
|
253
|
+
const kind = versionWork ? 'version' : chatCaptureWork ? 'chat_capture' : triwikiAuditWork ? 'triwiki_audit' : effectivePrioritySignalWork ? 'priority' : requestIntakeWork ? 'request_intake' : questionGateWork ? 'questions' : installWork ? 'install' : null;
|
|
251
254
|
const goals = {
|
|
252
255
|
version: version ? `sneakoscope 버전을 ${version}로 올린다` : 'sneakoscope 버전을 다음 patch 버전으로 올린다',
|
|
253
256
|
chat_capture: 'From-Chat-IMG로 채팅 요구사항과 첨부 원본 이미지를 매칭해 고객사 작업 지시서를 만들고 반영한다',
|
|
254
257
|
triwiki_audit: 'TriWiki가 반복 실수를 막는지 검수하고, 실패 경로를 코드와 검증으로 개선한다',
|
|
255
258
|
priority: '강한 불만과 반복 요청을 TriWiki 우선순위 신호로 기록한다',
|
|
259
|
+
request_intake: '모호한 사용자 요청을 TriWiki 기반 request-intake로 해석하고, 누락 없는 요구사항 목록과 실행용 변환 프롬프트를 pipeline에 전달한다',
|
|
256
260
|
questions: '예측 가능한 답은 추론하고 실제 모호한 항목만 질문한다',
|
|
257
261
|
presentation: '청중과 STP 전략에 맞는 HTML 기반 발표자료/PDF 산출물을 만든다',
|
|
258
262
|
install: 'SKS 최초 설치와 bootstrap을 한 번에 준비 상태까지 연결한다'
|
|
@@ -262,6 +266,7 @@ export function inferAnswersForPrompt(prompt, explicitAnswers = {}) {
|
|
|
262
266
|
chat_capture: ['From-Chat-IMG activates chat-image intake only here', 'all visible chat requirements are listed before implementation', `${FROM_CHAT_IMG_COVERAGE_ARTIFACT} maps every customer request, screenshot region, and attachment to work-order item(s)`, `${FROM_CHAT_IMG_CHECKLIST_ARTIFACT} is updated as each request, image match, work item, scoped QA-LOOP, and verification step is completed`, `${FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT} records temporary TriWiki-backed session context with retention metadata`, `${FROM_CHAT_IMG_QA_LOOP_ARTIFACT} proves QA-LOOP ran over the exact customer-request work-order range after implementation`, 'unresolved_items is empty before Team completion', 'scoped_qa_loop_completed is true with zero unresolved QA findings', 'Web/browser visual inspection uses Codex Chrome Extension readiness first; native Mac/non-web visual inspection uses Codex Computer Use when available', CODEX_WEB_VERIFICATION_POLICY, CODEX_COMPUTER_USE_ONLY_POLICY, 'client requests follow normal SKS gates and verification'],
|
|
263
267
|
triwiki_audit: ['TriWiki ingestion, voxel attention, and contract consumption paths are inspected against current code', 'repeat-mistake prevention gaps are fixed in the relevant code path or blocked with evidence', 'regression coverage proves fresh/high-weight mistake memory can influence future missions', 'final status separates supported behavior from anything still unverified'],
|
|
264
268
|
priority: ['strong feedback raises required_weight', 'request topics are counted in wiki packs', 'future inference uses priority signals'],
|
|
269
|
+
request_intake: ['request-intake.json records original prompt, interpreted intent, source-order requirements, wiki context used, and transformed prompt', 'pipeline-plan.json attaches request_intake metadata and execution_prompt from request-intake.transformed_prompt', 'decision-contract.json preserves the same request-intake artifact when a contract is sealed'],
|
|
265
270
|
questions: ['predictable answers are inferred', 'partial answers can seal contracts', 'only unresolved changing slots remain visible'],
|
|
266
271
|
presentation: ['audience profile and STP strategy are explicit before artifact creation', 'target pain points map to proposed solution moments', 'decision context and likely objections are sealed before storyboarding', 'presentation format, device, and delivery context are fixed before design work'],
|
|
267
272
|
install: ['bootstrap/deps initialize readiness', 'missing runtime deps show repair actions', 'readiness output is concrete']
|
|
@@ -381,6 +386,222 @@ export function inferAnswersForPrompt(prompt, explicitAnswers = {}) {
|
|
|
381
386
|
}
|
|
382
387
|
return { answers: inferred, notes };
|
|
383
388
|
}
|
|
389
|
+
export function buildRequestIntake(prompt, explicitAnswers = {}, opts = {}) {
|
|
390
|
+
const originalPrompt = String(prompt || '').trim();
|
|
391
|
+
const inferred = inferAnswersForPrompt(originalPrompt, explicitAnswers);
|
|
392
|
+
const answers = { ...(inferred.answers || {}), ...(explicitAnswers || {}) };
|
|
393
|
+
const ambiguity = buildAmbiguityAssessment(originalPrompt, explicitAnswers);
|
|
394
|
+
const wikiContext = summarizeWikiContext(opts.wikiContext);
|
|
395
|
+
const promptRequirements = promptRequirementItems(originalPrompt);
|
|
396
|
+
const semanticRequirements = semanticRequirementItems(originalPrompt);
|
|
397
|
+
const acceptance = toStringList(answers.ACCEPTANCE_CRITERIA);
|
|
398
|
+
const constraints = toStringList(answers.RISK_BOUNDARY);
|
|
399
|
+
const nonGoals = toStringList(answers.NON_GOALS);
|
|
400
|
+
const requirements = dedupeRequirementItems([
|
|
401
|
+
...promptRequirements,
|
|
402
|
+
...semanticRequirements,
|
|
403
|
+
...acceptance.map((text, index) => requirementItem('acceptance', index + 1, text, 'inferred_acceptance_criteria')),
|
|
404
|
+
...constraints.map((text, index) => requirementItem('constraint', index + 1, text, 'inferred_safety_constraint'))
|
|
405
|
+
]);
|
|
406
|
+
const goal = String(answers.GOAL_PRECISE || originalPrompt || '사용자 요청을 현재 코드 기준으로 구현한다').trim();
|
|
407
|
+
const targetSignals = extractTargetSignals(originalPrompt);
|
|
408
|
+
const riskSignals = extractRiskSignals(originalPrompt, constraints);
|
|
409
|
+
const transformedPrompt = buildTransformedPrompt({
|
|
410
|
+
originalPrompt,
|
|
411
|
+
goal,
|
|
412
|
+
wikiContext,
|
|
413
|
+
requirements,
|
|
414
|
+
constraints,
|
|
415
|
+
acceptance,
|
|
416
|
+
nonGoals,
|
|
417
|
+
targetSignals,
|
|
418
|
+
riskSignals,
|
|
419
|
+
ambiguity
|
|
420
|
+
});
|
|
421
|
+
return {
|
|
422
|
+
schema: 'sks.request-intake.v1',
|
|
423
|
+
generated_at: nowIso(),
|
|
424
|
+
prompt_hash: sha256(originalPrompt).slice(0, 16),
|
|
425
|
+
original_prompt: originalPrompt,
|
|
426
|
+
interpreted_intent: {
|
|
427
|
+
goal,
|
|
428
|
+
source: 'prompt_plus_triwiki_current_code_defaults',
|
|
429
|
+
confidence: ambiguity.ready_for_contract ? 'high' : ambiguity.overall_score <= 0.35 ? 'medium' : 'needs_human_only_if_scope_changes'
|
|
430
|
+
},
|
|
431
|
+
ambiguity_assessment: ambiguity,
|
|
432
|
+
wiki_context_used: wikiContext,
|
|
433
|
+
request_items: requirements,
|
|
434
|
+
requirements,
|
|
435
|
+
constraints,
|
|
436
|
+
acceptance_criteria: acceptance,
|
|
437
|
+
non_goals: nonGoals,
|
|
438
|
+
target_signals: targetSignals,
|
|
439
|
+
risk_signals: riskSignals,
|
|
440
|
+
transformed_prompt: transformedPrompt,
|
|
441
|
+
pipeline_usage: {
|
|
442
|
+
artifact: REQUEST_INTAKE_ARTIFACT,
|
|
443
|
+
read_before_pipeline_stage: true,
|
|
444
|
+
use_transformed_prompt_for_execution: true,
|
|
445
|
+
preserve_original_prompt: true,
|
|
446
|
+
ask_user_only_for_scope_safety_behavior_or_acceptance_changes: true
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
function summarizeWikiContext(wikiContext = null) {
|
|
451
|
+
const claims = Array.isArray(wikiContext?.claims) ? wikiContext.claims : [];
|
|
452
|
+
const useFirstIds = new Set((wikiContext?.attention?.use_first || []).map((row) => Array.isArray(row) ? row[0] : row?.id).filter(Boolean));
|
|
453
|
+
const hydrateFirstIds = new Set((wikiContext?.attention?.hydrate_first || []).map((row) => Array.isArray(row) ? row[0] : row?.id).filter(Boolean));
|
|
454
|
+
return {
|
|
455
|
+
source: wikiContext ? '.sneakoscope/wiki/context-pack.json' : 'unavailable',
|
|
456
|
+
attention_mode: wikiContext?.attention?.mode || null,
|
|
457
|
+
use_first_ids: [...useFirstIds].slice(0, 8),
|
|
458
|
+
hydrate_first_ids: [...hydrateFirstIds].slice(0, 8),
|
|
459
|
+
claims: claims.slice(0, 8).map((claim) => ({
|
|
460
|
+
id: claim.id || claim.claim_id || null,
|
|
461
|
+
trust: claim.trust_score || claim.trust || null,
|
|
462
|
+
source: claim.source || claim.source_path || null,
|
|
463
|
+
summary: String(claim.claim || claim.summary || claim.text || '').slice(0, 320)
|
|
464
|
+
}))
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
function promptRequirementItems(prompt) {
|
|
468
|
+
const cleaned = prompt
|
|
469
|
+
.replace(/(?:^|\s)\$[A-Za-z0-9_-]+(?:\s|$)/g, ' ')
|
|
470
|
+
.replace(/\s+/g, ' ')
|
|
471
|
+
.trim();
|
|
472
|
+
if (!cleaned)
|
|
473
|
+
return [];
|
|
474
|
+
const sourceChunks = cleaned
|
|
475
|
+
.split(/\n+|(?:^|\s)[-*]\s+/)
|
|
476
|
+
.flatMap((chunk) => splitLooseClauses(chunk))
|
|
477
|
+
.map((chunk) => chunk.trim())
|
|
478
|
+
.filter(Boolean);
|
|
479
|
+
const chunks = sourceChunks.length ? sourceChunks : [cleaned];
|
|
480
|
+
return chunks.slice(0, 12).map((text, index) => requirementItem('prompt', index + 1, text, 'source_prompt_order'));
|
|
481
|
+
}
|
|
482
|
+
function splitLooseClauses(text) {
|
|
483
|
+
const compact = String(text || '').trim();
|
|
484
|
+
if (compact.length < 80)
|
|
485
|
+
return [compact].filter(Boolean);
|
|
486
|
+
const pieces = compact.split(/\s+(?=(?:그리고|또|또한|그걸로|해당|이거는|이건|너가|우리|다음|then|and)\b)/i);
|
|
487
|
+
return pieces.length > 1 ? pieces : [compact];
|
|
488
|
+
}
|
|
489
|
+
function semanticRequirementItems(prompt) {
|
|
490
|
+
const text = String(prompt || '');
|
|
491
|
+
const lower = text.toLowerCase();
|
|
492
|
+
const items = [];
|
|
493
|
+
const add = (text, confidence = 0.88) => items.push({
|
|
494
|
+
id: `REQ-S${String(items.length + 1).padStart(2, '0')}`,
|
|
495
|
+
kind: 'semantic_requirement',
|
|
496
|
+
source: 'semantic_prompt_signal',
|
|
497
|
+
text,
|
|
498
|
+
required: true,
|
|
499
|
+
confidence
|
|
500
|
+
});
|
|
501
|
+
if (/모호|ambiguous|rough|대충|애매/.test(lower))
|
|
502
|
+
add('모호한 사용자 입력을 그대로 실행하지 말고 intent intake 단계에서 명확한 실행 요청으로 해석한다.');
|
|
503
|
+
if (/위키|triwiki|wiki|memory|기억/.test(lower))
|
|
504
|
+
add('TriWiki/context-pack과 현재 코드 기본값을 참고해 사용자 의도와 반복 선호를 보강한다.');
|
|
505
|
+
if (/의도|intent|목적/.test(lower) && /명확|파악|extract|understand/.test(lower))
|
|
506
|
+
add('사용자 의도를 Goal, Context, Constraints, Done-when 형태로 명확히 정리한다.');
|
|
507
|
+
if (/리스트|list|누락|빠짐|requirements?|요청사항/.test(lower))
|
|
508
|
+
add('요청사항을 source order 기준으로 누락 없이 리스트업하고 각 항목을 실행 작업으로 매핑한다.');
|
|
509
|
+
if (/프롬프트|prompt/.test(lower) && /변환|바꿔|rewrite|transform|compile|최적/.test(lower))
|
|
510
|
+
add('원문 요청을 파이프라인 실행에 적합한 transformed prompt로 변환한다.');
|
|
511
|
+
if (/파이프라인|pipeline/.test(lower) && /(태워|전달|route|run|execute|투입)/.test(lower))
|
|
512
|
+
add('변환된 prompt와 request-intake artifact를 실제 route pipeline에 전달한다.');
|
|
513
|
+
return items;
|
|
514
|
+
}
|
|
515
|
+
function requirementItem(kind, index, text, source) {
|
|
516
|
+
return {
|
|
517
|
+
id: `REQ-${String(index).padStart(3, '0')}`,
|
|
518
|
+
kind,
|
|
519
|
+
source,
|
|
520
|
+
text: String(text || '').trim(),
|
|
521
|
+
required: true,
|
|
522
|
+
confidence: source === 'source_prompt_order' ? 1 : 0.86
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
function dedupeRequirementItems(items = []) {
|
|
526
|
+
const seen = new Set();
|
|
527
|
+
const out = [];
|
|
528
|
+
for (const item of items) {
|
|
529
|
+
const text = String(item.text || '').replace(/\s+/g, ' ').trim();
|
|
530
|
+
if (!text)
|
|
531
|
+
continue;
|
|
532
|
+
const key = text.toLowerCase();
|
|
533
|
+
if (seen.has(key))
|
|
534
|
+
continue;
|
|
535
|
+
seen.add(key);
|
|
536
|
+
out.push({ ...item, id: `REQ-${String(out.length + 1).padStart(3, '0')}`, text });
|
|
537
|
+
}
|
|
538
|
+
return out;
|
|
539
|
+
}
|
|
540
|
+
function toStringList(value) {
|
|
541
|
+
if (Array.isArray(value))
|
|
542
|
+
return value.map((item) => String(item || '').trim()).filter(Boolean);
|
|
543
|
+
const text = String(value || '').trim();
|
|
544
|
+
if (!text)
|
|
545
|
+
return [];
|
|
546
|
+
return text.split(/\n+/).map((item) => item.trim()).filter(Boolean);
|
|
547
|
+
}
|
|
548
|
+
function extractTargetSignals(prompt) {
|
|
549
|
+
const text = String(prompt || '');
|
|
550
|
+
const paths = [...text.matchAll(/(?:src|test|tests|scripts|docs|README|CHANGELOG|package\.json|\.sneakoscope|\.agents|\.codex)\/?[A-Za-z0-9._/\-]*/g)].map((m) => m[0]);
|
|
551
|
+
const commands = [...text.matchAll(/\$[A-Za-z0-9_-]+/g)].map((m) => m[0]);
|
|
552
|
+
const urls = [...text.matchAll(/https?:\/\/[^\s)\]}>,]+/g)].map((m) => m[0]);
|
|
553
|
+
return {
|
|
554
|
+
paths: [...new Set(paths)].slice(0, 12),
|
|
555
|
+
dollar_commands: [...new Set(commands)].slice(0, 12),
|
|
556
|
+
urls: [...new Set(urls)].slice(0, 8)
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
function extractRiskSignals(prompt, constraints = []) {
|
|
560
|
+
const lower = String(prompt || '').toLowerCase();
|
|
561
|
+
const risks = [];
|
|
562
|
+
if (promptHasRisk(lower))
|
|
563
|
+
risks.push('high_risk_surface_detected');
|
|
564
|
+
if (promptNeedsExplicitRiskBoundary(lower))
|
|
565
|
+
risks.push('explicit_risk_boundary_required');
|
|
566
|
+
if (/db|database|supabase|postgres|sql|schema|migration|데이터베이스|스키마|마이그레이션/.test(lower))
|
|
567
|
+
risks.push('database_safety');
|
|
568
|
+
if (/browser|webapp|localhost|chrome|스크린샷|화면|ui|ux|visual|웹/.test(lower))
|
|
569
|
+
risks.push('visual_or_browser_evidence');
|
|
570
|
+
if (/publish|release|version|배포|릴리즈|버전/.test(lower))
|
|
571
|
+
risks.push('release_surface');
|
|
572
|
+
return {
|
|
573
|
+
risks: [...new Set(risks)],
|
|
574
|
+
constraints
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
function buildTransformedPrompt(input) {
|
|
578
|
+
const requirementLines = (input.requirements || []).map((item, index) => `${index + 1}. ${item.text}`);
|
|
579
|
+
const wikiLines = (input.wikiContext?.claims || []).slice(0, 5).map((claim) => `- ${claim.id || 'wiki-claim'}: ${claim.summary}`);
|
|
580
|
+
return [
|
|
581
|
+
'# SKS Wiki-Informed Execution Prompt',
|
|
582
|
+
'',
|
|
583
|
+
'## Goal',
|
|
584
|
+
input.goal,
|
|
585
|
+
'',
|
|
586
|
+
'## Original Prompt',
|
|
587
|
+
input.originalPrompt || '(empty)',
|
|
588
|
+
'',
|
|
589
|
+
'## Wiki Context To Use First',
|
|
590
|
+
...(wikiLines.length ? wikiLines : ['- No current TriWiki claims were available; rely on current code and conservative SKS defaults.']),
|
|
591
|
+
'',
|
|
592
|
+
'## Requirements',
|
|
593
|
+
...(requirementLines.length ? requirementLines : ['1. Implement the user request using current code context.']),
|
|
594
|
+
'',
|
|
595
|
+
'## Constraints',
|
|
596
|
+
...((input.constraints || []).length ? input.constraints.map((row) => `- ${row}`) : ['- Preserve existing behavior unless the request explicitly changes it.', '- Do not create unrequested fallback implementation code.', '- Do not run destructive or live-data mutation commands.']),
|
|
597
|
+
'',
|
|
598
|
+
'## Done When',
|
|
599
|
+
...((input.acceptance || []).length ? input.acceptance.map((row) => `- ${row}`) : ['- Relevant implementation is complete.', '- Focused verification passes or unavailable checks are explicitly justified.', '- Final response states changed, verified, and unverified items.']),
|
|
600
|
+
'',
|
|
601
|
+
'## Pipeline Instruction',
|
|
602
|
+
'Read request-intake.json first, execute this transformed prompt through the selected SKS route, refresh/validate TriWiki after findings or artifact changes, and finish with SKS Honest Mode.'
|
|
603
|
+
].join('\n');
|
|
604
|
+
}
|
|
384
605
|
export function buildQuestionSchema(prompt) {
|
|
385
606
|
const lower = String(prompt || '').toLowerCase();
|
|
386
607
|
const domainHints = [];
|
|
@@ -438,6 +659,7 @@ export function buildQuestionSchema(prompt) {
|
|
|
438
659
|
slots.push({ id: 'DB_MIGRATION_APPLY_ALLOWED', question: 'migration 적용이 필요할 경우 어디까지 허용하나요?', required: true, type: 'enum', options: ['no', 'local_only', 'preview_branch_only'] }, { id: 'DB_READ_ONLY_QUERY_LIMIT', question: 'MCP/SQL read-only 조회 시 기본 LIMIT를 몇으로 둘까요?', required: true, type: 'string' });
|
|
439
660
|
}
|
|
440
661
|
const inferred = inferAnswersForPrompt(prompt);
|
|
662
|
+
const requestIntake = buildRequestIntake(prompt, inferred.answers);
|
|
441
663
|
const inferredSlots = new Set(Object.keys(inferred.answers));
|
|
442
664
|
const askedSlots = [];
|
|
443
665
|
return {
|
|
@@ -446,6 +668,7 @@ export function buildQuestionSchema(prompt) {
|
|
|
446
668
|
prompt,
|
|
447
669
|
domain_hints: domainHints,
|
|
448
670
|
ambiguity_assessment: ambiguity,
|
|
671
|
+
request_intake: requestIntake,
|
|
449
672
|
inferred_answers: inferred.answers,
|
|
450
673
|
inference_notes: inferred.notes,
|
|
451
674
|
slots: askedSlots
|
|
@@ -478,6 +701,20 @@ export function questionsMarkdown(schema) {
|
|
|
478
701
|
lines.push(`- unresolved dimensions: ${(schema.ambiguity_assessment.unresolved_dimensions || []).join(', ') || 'none'}`);
|
|
479
702
|
lines.push(`- legacy question budget ignored: ${schema.ambiguity_assessment.question_budget}`);
|
|
480
703
|
}
|
|
704
|
+
if (schema.request_intake) {
|
|
705
|
+
const intake = schema.request_intake;
|
|
706
|
+
lines.push('');
|
|
707
|
+
lines.push('## Request Intake');
|
|
708
|
+
lines.push('');
|
|
709
|
+
lines.push(`- artifact: ${REQUEST_INTAKE_ARTIFACT}`);
|
|
710
|
+
lines.push(`- interpreted goal: ${intake.interpreted_intent?.goal || '(none)'}`);
|
|
711
|
+
lines.push(`- requirement count: ${(intake.requirements || []).length}`);
|
|
712
|
+
lines.push(`- wiki context: ${intake.wiki_context_used?.source || 'unavailable'}`);
|
|
713
|
+
lines.push('');
|
|
714
|
+
lines.push('```markdown');
|
|
715
|
+
lines.push(intake.transformed_prompt || '');
|
|
716
|
+
lines.push('```');
|
|
717
|
+
}
|
|
481
718
|
if (schema.inferred_answers && Object.keys(schema.inferred_answers).length) {
|
|
482
719
|
lines.push('');
|
|
483
720
|
lines.push('## Inferred Answers');
|