sneakoscope 0.7.2 → 0.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -230,7 +230,7 @@ sks code-structure scan --json
230
230
 
231
231
  `sks pipeline plan` is the 0.7 runtime map. It reads or refreshes `.sneakoscope/missions/<id>/pipeline-plan.json`, then shows which lane is active, which stages are kept or skipped, which verification commands are required, and whether the no-unrequested-fallback invariant is present.
232
232
 
233
- `sks proof-field scan` is SKS's lightweight outcome rubric: it maps the goal to proof cones, records unrelated work that can be skipped with evidence, reports a simplicity score, and names escalation triggers for when the route must return to the full Team/Honest proof path. When `execution_lane.lane` is `proof_field_fast_lane`, SKS can keep the parent-owned minimal patch plus listed verification and skip Team debate, fresh executor teams, broad route rework, and unrelated checks. Database, security, visual-forensic, unknown, broad, failed, or unsupported-claim signals fail closed to the normal Team/Honest path. Use `sks pipeline plan --proof-field` after changed files are known to bind that Proof Field decision to the mission plan.
233
+ `sks proof-field scan` is SKS's lightweight outcome rubric: it maps the goal to proof cones, records unrelated work that can be skipped with evidence, reports a simplicity score, and names escalation triggers for when the route must return to the full Team/Honest proof path. The rubric embeds Hyperplan-style adversarial pressure as compact lenses instead of a new command: challenge framing, subtract surface, demand evidence, test integration risk, and consider one simpler alternative. When `execution_lane.lane` is `proof_field_fast_lane`, SKS can keep the parent-owned minimal patch plus listed verification and skip Team debate, fresh executor teams, broad route rework, and unrelated checks. Database, security, visual-forensic, unknown, broad, failed, or unsupported-claim signals fail closed to the normal Team/Honest path. Use `sks pipeline plan --proof-field` after changed files are known to bind that Proof Field decision to the mission plan.
234
234
 
235
235
  `sks skill-dream` keeps generated skill complexity bounded without doing a heavy evaluation on every prompt. Route use writes compact counters to `.sneakoscope/skills/dream-state.json`; after the configured count/cooldown threshold, or when you run `sks skill-dream run`, SKS scans `.agents/skills` and writes `.sneakoscope/reports/skill-dream-latest.json` with keep, merge, prune, and improvement candidates. The report is intentionally advisory: deleting or merging skills requires explicit approval.
236
236
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "0.7.2",
4
+ "version": "0.7.6",
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
@@ -2552,6 +2552,14 @@ async function selftest() {
2552
2552
  ].join('\n');
2553
2553
  const visibleQuestionDecision = await evaluateStop(hookTeamTmp, hookTeamState, { last_assistant_message: visibleQuestionsBlock }, { noQuestion: false });
2554
2554
  if (!visibleQuestionDecision?.continue) throw new Error('selftest failed: visible Required questions block was not accepted by clarification stop gate');
2555
+ const hookTeamPreToolBlocked = await runProcess(process.execPath, [hookBin, 'hook', 'pre-tool'], { cwd: hookTeamTmp, input: JSON.stringify({ cwd: hookTeamTmp, command: 'npm run selftest' }), env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
2556
+ if (hookTeamPreToolBlocked.code !== 0) throw new Error(`selftest failed: pending clarification pre-tool hook exited ${hookTeamPreToolBlocked.code}: ${hookTeamPreToolBlocked.stderr}`);
2557
+ const hookTeamPreToolBlockedJson = JSON.parse(hookTeamPreToolBlocked.stdout);
2558
+ if (hookTeamPreToolBlockedJson.decision !== 'block' || !String(hookTeamPreToolBlockedJson.reason || '').includes('ambiguity gate is paused')) throw new Error('selftest failed: pending clarification allowed implementation tool use before answers');
2559
+ const hookTeamAnswerToolAllowed = await runProcess(process.execPath, [hookBin, 'hook', 'pre-tool'], { cwd: hookTeamTmp, input: JSON.stringify({ cwd: hookTeamTmp, command: 'node ./bin/sks.mjs pipeline answer latest answers.json' }), env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
2560
+ if (hookTeamAnswerToolAllowed.code !== 0) throw new Error(`selftest failed: pipeline-answer pre-tool hook exited ${hookTeamAnswerToolAllowed.code}: ${hookTeamAnswerToolAllowed.stderr}`);
2561
+ const hookTeamAnswerToolAllowedJson = JSON.parse(hookTeamAnswerToolAllowed.stdout);
2562
+ if (hookTeamAnswerToolAllowedJson.decision === 'block') throw new Error('selftest failed: pending clarification blocked the pipeline answer command');
2555
2563
  const nonGoalsSlot = hookTeamSchema.slots.find((s) => s.id === 'NON_GOALS');
2556
2564
  if (nonGoalsSlot && !nonGoalsSlot.allow_empty) throw new Error('selftest failed: NON_GOALS does not allow an empty array answer');
2557
2565
  if (!nonGoalsSlot && !Array.isArray(hookTeamSchema.inferred_answers?.NON_GOALS)) throw new Error('selftest failed: NON_GOALS was neither asked nor inferred');
@@ -3065,6 +3073,9 @@ async function selftest() {
3065
3073
  if (installUxSchema.domain_hints.includes('uiux') || installUxSlotIds.includes('VISUAL_REGRESSION_REQUIRED')) throw new Error('selftest failed: CLI UX install prompt should not ask visual UI questions');
3066
3074
  if (installUxSlotIds.some((id) => /^(D|SUPA)/.test(id) && id !== 'DEPENDENCY_CHANGE_ALLOWED')) throw new Error('selftest failed: non-data MCP setup prompt asked guarded slots');
3067
3075
  if (installUxSlotIds.includes('MID_RUN_UNKNOWN_POLICY')) throw new Error('selftest failed: no-question fallback ladder should be inferred, not asked');
3076
+ const dbQuestionGateSchema = buildQuestionSchema('DB_SCHEMA_CHANGE_ALLOWED DATABASE_TARGET_ENVIRONMENT DATABASE_WRITE_MODE SUPABASE_MCP_POLICY DB_READ_ONLY_QUERY_LIMIT 이런 질문은 사용자에게 묻지 말고 알아서 판단해줘');
3077
+ const dbQuestionGateSlotIds = dbQuestionGateSchema.slots.map((s) => s.id);
3078
+ if (dbQuestionGateSlotIds.length) throw new Error(`selftest failed: predictable DB safety prompt should auto-seal, got ${dbQuestionGateSlotIds.join(',')}`);
3068
3079
  const { id, dir, mission } = await createMission(tmp, { mode: 'goal', prompt: '로그인 세션 만료 UX 개선 supabase db' });
3069
3080
  const schema = buildQuestionSchema(mission.prompt);
3070
3081
  await writeQuestions(dir, schema);
@@ -3105,7 +3116,7 @@ async function selftest() {
3105
3116
  if (!harnessReport.forgetting.fixture.passed || !harnessReport.warp.views.includes('Harness Experiments View') || !harnessReport.reliability.tool_error_taxonomy.includes('Unknown')) throw new Error('selftest failed: harness growth fixture incomplete');
3106
3117
  const proofField = await proofFieldFixture();
3107
3118
  if (!proofField.validation.ok || !validateProofFieldReport(proofField.report).ok) throw new Error('selftest failed: proof field report invalid');
3108
- if (!proofField.checks.route_cone_selected || !proofField.checks.cli_cone_selected || !proofField.checks.catastrophic_guard_present || !proofField.checks.negative_release_work_recorded || !proofField.checks.outcome_rubric_present || !proofField.checks.simplicity_score_usable || !proofField.checks.execution_fast_lane_selected) throw new Error('selftest failed: proof field fixture checks incomplete');
3119
+ if (!proofField.checks.route_cone_selected || !proofField.checks.cli_cone_selected || !proofField.checks.catastrophic_guard_present || !proofField.checks.negative_release_work_recorded || !proofField.checks.outcome_rubric_present || !proofField.checks.adversarial_lenses_present || !proofField.checks.simplicity_score_usable || !proofField.checks.execution_fast_lane_selected) throw new Error('selftest failed: proof field fixture checks incomplete');
3109
3120
  if (!speedLanePolicyText().includes('proof_field_fast_lane') || !proofField.report.execution_lane?.skip_when_fast?.includes('planning_debate')) throw new Error('selftest failed: Proof Field speed lane policy missing');
3110
3121
  const fastPipelinePlan = buildPipelinePlan({ route: routePrompt('$Team small CLI help update'), task: 'small CLI help surface update', proofField: proofField.report });
3111
3122
  if (!validatePipelinePlan(fastPipelinePlan).ok || fastPipelinePlan.runtime_lane?.lane !== 'proof_field_fast_lane' || !fastPipelinePlan.skipped_stages.includes('planning_debate') || !fastPipelinePlan.invariants.includes('no_unrequested_fallback_code')) throw new Error('selftest failed: pipeline plan did not encode fast lane stage skips and fallback guard');
@@ -1540,7 +1540,7 @@ export function buildTeamPlan(id, prompt, opts = {}) {
1540
1540
  team_model: {
1541
1541
  phases: ['parallel_analysis_scouts', 'triwiki_stage_refresh', 'debate_team', 'triwiki_stage_refresh', 'runtime_task_graph', 'development_team', 'triwiki_stage_refresh', 'review', 'session_cleanup'],
1542
1542
  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.`,
1543
- debate_team: `Read-only role debate with exactly ${roster.bundle_size} participants composed from user, planner, reviewer, and executor voices.`,
1543
+ 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.`,
1544
1544
  development_team: `Fresh parallel development bundle with exactly ${roster.bundle_size} executor_N developers implementing disjoint slices; validation_team reviews afterward.`
1545
1545
  },
1546
1546
  team_runtime: teamRuntimePlanMetadata(),
@@ -1584,7 +1584,7 @@ export function buildTeamPlan(id, prompt, opts = {}) {
1584
1584
  },
1585
1585
  {
1586
1586
  id: 'planning_debate',
1587
- goal: 'Debate team reads the current TriWiki pack, maps user inconvenience, code risk, constraints, DB safety, tests, and viable approaches, and hydrates low-trust claims from source immediately.',
1587
+ goal: 'Debate team reads the current TriWiki pack, maps user inconvenience, code risk, constraints, DB safety, tests, and viable approaches, applies compact Hyperplan-derived lenses, and hydrates low-trust claims from source immediately.',
1588
1588
  agents: roster.debate_team.map((agent) => agent.id),
1589
1589
  max_parallel_subagents: agentSessions,
1590
1590
  write_policy: 'read-only'
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.2';
8
+ export const PACKAGE_VERSION = '0.7.6';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11
 
@@ -165,6 +165,9 @@ async function hookPreTool(root, state, payload, noQuestion) {
165
165
  if (dbDecision.action === 'block' || dbDecision.action === 'confirm') {
166
166
  return { decision: 'block', permissionDecision: 'deny', reason: dbBlockReason(dbDecision) };
167
167
  }
168
+ if (clarificationGateLocked(state) && !clarificationAnswerToolAllowed(payload)) {
169
+ return { decision: 'block', permissionDecision: 'deny', reason: clarificationPauseBlockReason(state) };
170
+ }
168
171
  const command = extractCommand(payload);
169
172
  if (noQuestion && looksInteractiveCommand(command)) return { decision: 'block', reason: interactiveCommandReason(command) };
170
173
  return { continue: true };
@@ -226,6 +229,9 @@ async function hookPermission(root, state, payload, noQuestion) {
226
229
  if (dbDecision.action === 'block' || dbDecision.action === 'confirm') {
227
230
  return { decision: 'deny', permissionDecision: 'deny', reason: dbBlockReason(dbDecision) };
228
231
  }
232
+ if (clarificationGateLocked(state) && !clarificationAnswerToolAllowed(payload)) {
233
+ return { decision: 'deny', permissionDecision: 'deny', reason: clarificationPauseBlockReason(state) };
234
+ }
229
235
  if (!noQuestion) return { continue: true };
230
236
  return {
231
237
  decision: 'deny',
@@ -234,6 +240,39 @@ async function hookPermission(root, state, payload, noQuestion) {
234
240
  };
235
241
  }
236
242
 
243
+ function clarificationGateLocked(state = {}) {
244
+ if (isClarificationAwaiting(state)) return true;
245
+ return Boolean(
246
+ state?.mission_id
247
+ && state.implementation_allowed === false
248
+ && state.ambiguity_gate_required === true
249
+ && state.ambiguity_gate_passed !== true
250
+ );
251
+ }
252
+
253
+ function clarificationAnswerToolAllowed(payload = {}) {
254
+ const command = extractCommand(payload);
255
+ if (/\bpipeline\s+answer\b/i.test(command) && /\b(?:sks|sks\.mjs|bin\/sks\.mjs|node)\b/i.test(command)) return true;
256
+ if (!payloadMentionsAnswersJson(payload)) return false;
257
+ if (!command) return true;
258
+ if (/\bpipeline\s+answer\b/i.test(command)) return true;
259
+ return !/\b(npm|git|selftest|packcheck|release:check|publish:dry|publish:npm|doctor|team|qa-loop|wiki|db|test)\b/i.test(command);
260
+ }
261
+
262
+ function payloadMentionsAnswersJson(payload = {}) {
263
+ try {
264
+ return /\banswers\.json\b/i.test(JSON.stringify(payload || {}));
265
+ } catch {
266
+ return false;
267
+ }
268
+ }
269
+
270
+ function clarificationPauseBlockReason(state = {}) {
271
+ const id = state?.mission_id || 'latest';
272
+ const route = state.route_command || state.route || state.mode || 'route';
273
+ return `SKS ${route} ambiguity gate is paused and waiting for explicit user answers. Do not run implementation, tests, route materialization, or unrelated tools yet. The only allowed actions are creating .sneakoscope/missions/${id}/answers.json from the user's reply and running "sks pipeline answer ${id} answers.json"; elapsed time or repeated hook resumes never count as answers.`;
274
+ }
275
+
237
276
  async function hookStop(root, state, payload, noQuestion) {
238
277
  const last = extractLastMessage(payload);
239
278
  if (!noQuestion && (hasDfixLightCompletion(last) || await consumeLightRouteStop(root, payload))) {
@@ -562,7 +562,7 @@ async function prepareTeam(root, route, task, required) {
562
562
  team_model: {
563
563
  phases: ['parallel_analysis_scouts', 'triwiki_stage_refresh', 'debate_team', 'triwiki_stage_refresh', 'runtime_task_graph', 'development_team', 'triwiki_stage_refresh', 'review'],
564
564
  analysis_team: `Read-only parallel scouting with exactly ${roster.bundle_size} analysis_scout_N agents. Each scout owns one investigation slice and returns TriWiki-ready findings with source paths, risks, and suggested implementation slices.`,
565
- debate_team: `Read-only role debate with exactly ${roster.bundle_size} participants composed from user, planner, reviewer, and executor voices.`,
565
+ 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.`,
566
566
  development_team: `Fresh parallel development bundle with exactly ${roster.bundle_size} executor_N developers implementing disjoint slices; validation_team reviews afterward.`
567
567
  },
568
568
  context_tracking: triwikiContextTracking(),
@@ -571,7 +571,7 @@ async function prepareTeam(root, route, task, required) {
571
571
  { id: 'team_roster_confirmation', goal: `Before any implementation, materialize the Team roster from default SKS counts or explicit user counts, write team-roster.json, and surface role counts ${formatRoleCounts(roleCounts)}. Implementation cannot be considered complete unless team-gate.json has team_roster_confirmed=true.`, agents: ['parent_orchestrator'], output: 'team-roster.json' },
572
572
  { id: 'parallel_analysis_scouting', goal: `Before scouting, read TriWiki context. ${fromChatImgRequired ? `From-Chat-IMG active: use Codex Computer Use visual inspection, list every visible customer request, match every screenshot image region to attachments, write ${FROM_CHAT_IMG_COVERAGE_ARTIFACT}, ${FROM_CHAT_IMG_CHECKLIST_ARTIFACT}, and ${FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT}, then require scoped QA-LOOP evidence in ${FROM_CHAT_IMG_QA_LOOP_ARTIFACT} after the customer-request work is done. ${CODEX_COMPUTER_USE_ONLY_POLICY}` : `From-Chat-IMG inactive: do not assume ordinary images are chat captures. ${CODEX_COMPUTER_USE_ONLY_POLICY}`} Spawn exactly ${roster.bundle_size} read-only analysis_scout_N agents in parallel, using the full available session budget without exceeding ${agentSessions}. Split repo/docs/tests/API/user-flow/risk investigation into independent slices, hydrate relevant low-trust claims from source, and record source-backed findings.`, agents: roster.analysis_team.map((agent) => agent.id), max_parallel_subagents: agentSessions, write_policy: 'read-only' },
573
573
  { id: 'triwiki_refresh', goal: `Parent orchestrator updates Team analysis artifacts, then runs ${triwikiContextTracking().refresh_command} or ${triwikiContextTracking().pack_command}, prunes with ${triwikiContextTracking().prune_command} when stale/oversized wiki state would pollute handoffs, and runs ${triwikiContextTracking().validate_command} so the next stage uses current TriWiki context.`, agents: ['parent_orchestrator'], output: '.sneakoscope/wiki/context-pack.json' },
574
- { id: 'planning_debate', goal: `Before debate, read the refreshed TriWiki pack. Debate team of exactly ${roster.bundle_size} participants maps user inconvenience, options, constraints, affected files, DB/test risk, and tradeoffs while hydrating low-trust claims from source.`, agents: roster.debate_team.map((agent) => agent.id) },
574
+ { id: 'planning_debate', goal: `Before debate, read the refreshed TriWiki pack. Debate team of exactly ${roster.bundle_size} participants maps user inconvenience, options, constraints, affected files, DB/test risk, and tradeoffs while applying compact Hyperplan-derived lenses: challenge framing, subtract surface, demand evidence, test integration risk, and consider one simpler alternative. Hydrate low-trust claims from source.`, agents: roster.debate_team.map((agent) => agent.id) },
575
575
  { id: 'consensus', goal: `Seal one objective with acceptance criteria and disjoint implementation slices, then refresh/validate TriWiki so implementation receives current consensus context.` },
576
576
  { id: 'runtime_task_graph_compile', goal: `Compile the agreed Team plan into ${TEAM_GRAPH_ARTIFACT}, ${TEAM_RUNTIME_TASKS_ARTIFACT}, and ${TEAM_DECOMPOSITION_ARTIFACT}; remap symbolic plan nodes to concrete task ids, allocate role/path/domain worker lanes, and write ${TEAM_INBOX_DIR} before executor work starts.`, agents: ['parent_orchestrator'], output: [TEAM_GRAPH_ARTIFACT, TEAM_RUNTIME_TASKS_ARTIFACT, TEAM_DECOMPOSITION_ARTIFACT, TEAM_INBOX_DIR] },
577
577
  { id: 'parallel_implementation', goal: `Before implementation, read relevant TriWiki context and current source. Close debate agents, then spawn a fresh ${roster.bundle_size}-person executor development team with non-overlapping write ownership. Refresh TriWiki after implementation changes or blockers.`, agents: roster.development_team.map((agent) => agent.id) },
@@ -590,7 +590,7 @@ async function prepareTeam(root, route, task, required) {
590
590
  await writeJsonAtomic(path.join(dir, 'team-plan.json'), plan);
591
591
  await writeJsonAtomic(path.join(dir, 'team-roster.json'), { schema_version: 1, mission_id: id, role_counts: roleCounts, agent_sessions: agentSessions, bundle_size: roster.bundle_size, roster, confirmed: true, source: 'default_or_prompt_team_spec' });
592
592
  const contextTracking = triwikiContextTracking();
593
- await writeTextAtomic(path.join(dir, 'team-workflow.md'), `# SKS Team Workflow\n\nTask: ${cleanTask}\n\nAgent session budget: ${agentSessions}\nBundle size: ${roster.bundle_size}\nRole counts: ${formatRoleCounts(roleCounts)}\nReasoning: high for team logic, temporary for this route only.\nContext tracking: ${contextTracking.ssot} SSOT, ${contextTracking.default_pack}; use relevant TriWiki context before every work stage, refresh/validate after findings, and preserve hydratable source anchors.\n\n1. Run exactly ${roster.bundle_size} read-only analysis_scout_N agents and write team-analysis.md.\n2. Refresh/validate TriWiki before debate.\n3. Run exactly ${roster.bundle_size} debate participants, then write consensus and implementation slices.\n4. Compile ${TEAM_GRAPH_ARTIFACT}, ${TEAM_RUNTIME_TASKS_ARTIFACT}, ${TEAM_DECOMPOSITION_ARTIFACT}, and ${TEAM_INBOX_DIR} so worker handoff uses concrete runtime task ids.\n5. Close debate agents before starting a fresh ${roster.bundle_size}-person executor team.\n6. Review, integrate, verify, and record evidence.\n7. Close/clean remaining Team sessions, finalize live transcript state, and write ${TEAM_SESSION_CLEANUP_ARTIFACT} before reflection/final.\n\nNo unrequested fallback implementation code is allowed in any stage, executor lane, review lane, MAD route, or MAD-SKS route. If the requested path cannot be implemented inside the sealed contract, block with evidence instead of adding substitute behavior.\n\nLive visibility:\n- sks team log ${id}\n- sks team tail ${id}\n- sks team watch ${id}\n- sks team lane ${id} --agent analysis_scout_1 --follow\n- sks team event ${id} --agent <name> --phase <phase> --message \"...\"\n`);
593
+ await writeTextAtomic(path.join(dir, 'team-workflow.md'), `# SKS Team Workflow\n\nTask: ${cleanTask}\n\nAgent session budget: ${agentSessions}\nBundle size: ${roster.bundle_size}\nRole counts: ${formatRoleCounts(roleCounts)}\nReasoning: high for team logic, temporary for this route only.\nContext tracking: ${contextTracking.ssot} SSOT, ${contextTracking.default_pack}; use relevant TriWiki context before every work stage, refresh/validate after findings, and preserve hydratable source anchors.\n\n1. Run exactly ${roster.bundle_size} read-only analysis_scout_N agents and write team-analysis.md.\n2. Refresh/validate TriWiki before debate.\n3. Run exactly ${roster.bundle_size} debate participants through the compact Hyperplan-derived adversarial lens pass, then write consensus and implementation slices.\n4. Compile ${TEAM_GRAPH_ARTIFACT}, ${TEAM_RUNTIME_TASKS_ARTIFACT}, ${TEAM_DECOMPOSITION_ARTIFACT}, and ${TEAM_INBOX_DIR} so worker handoff uses concrete runtime task ids.\n5. Close debate agents before starting a fresh ${roster.bundle_size}-person executor team.\n6. Review, integrate, verify, and record evidence.\n7. Close/clean remaining Team sessions, finalize live transcript state, and write ${TEAM_SESSION_CLEANUP_ARTIFACT} before reflection/final.\n\nNo unrequested fallback implementation code is allowed in any stage, executor lane, review lane, MAD route, or MAD-SKS route. If the requested path cannot be implemented inside the sealed contract, block with evidence instead of adding substitute behavior.\n\nLive visibility:\n- sks team log ${id}\n- sks team tail ${id}\n- sks team watch ${id}\n- sks team lane ${id} --agent analysis_scout_1 --follow\n- sks team event ${id} --agent <name> --phase <phase> --message \"...\"\n`);
594
594
  await initTeamLive(id, dir, cleanTask, { agentSessions, roleCounts, roster });
595
595
  const runtime = await writeTeamRuntimeArtifacts(dir, plan, {});
596
596
  await writeMemorySweepReport(root, dir, { missionId: id }).catch(() => null);
@@ -689,6 +689,7 @@ function clarificationVisibleResponseContract(id) {
689
689
  VISIBLE RESPONSE CONTRACT:
690
690
  - This turn is clarification-only.
691
691
  - Do not call tools, do not start implementation, and do not advance to the next route phase.
692
+ - Elapsed time, repeated hook resumes, or assistant self-continuation do not count as answers.
692
693
  - Reply to the user with the Required questions block so it is visible in chat.
693
694
  - Tell the user they can answer directly by slot id; after they answer, convert the reply to answers.json and run \`${answerCommand}\`.`;
694
695
  }
@@ -13,10 +13,10 @@ export const INVARIANT_LEDGER = Object.freeze([
13
13
  ]);
14
14
 
15
15
  export const OUTCOME_RUBRIC = Object.freeze([
16
- { id: 'goal_fit', description: 'The selected work directly satisfies the user goal without drifting into adjacent pipeline work.' },
17
- { id: 'minimum_surface', description: 'Only touched surfaces inside the proof cone are included; unrelated routes, docs, DB, visual, or release work are skipped with evidence.' },
18
- { id: 'bounded_verification', description: 'Verification is enough to prove the selected cone and no broader than the risk requires.' },
19
- { id: 'escalation_defined', description: 'The path names the exact failure signals that should promote the work back to the full Team/Honest proof path.' }
16
+ { id: 'goal_fit', description: 'The selected work directly satisfies the user goal without drifting into adjacent pipeline work.', adversarial_lens: 'creative: challenge the literal framing and keep only the user goal that survives.' },
17
+ { id: 'minimum_surface', description: 'Only touched surfaces inside the proof cone are included; unrelated routes, docs, DB, visual, or release work are skipped with evidence.', adversarial_lens: 'skeptic+architect: subtract unneeded surface and reject coupling that does not pay for itself now.' },
18
+ { id: 'bounded_verification', description: 'Verification is enough to prove the selected cone and no broader than the risk requires.', adversarial_lens: 'researcher+validator: require current evidence and test the integration edge that can actually break.' },
19
+ { id: 'escalation_defined', description: 'The path names the exact failure signals that should promote the work back to the full Team/Honest proof path.', adversarial_lens: 'validator+architect: name the failure signal before work starts and fail closed when it appears.' }
20
20
  ]);
21
21
 
22
22
  export const SPEED_LANE_POLICY = Object.freeze({
@@ -115,8 +115,10 @@ export function validateProofFieldReport(report = {}) {
115
115
  if (report.schema_version !== PROOF_FIELD_SCHEMA_VERSION) issues.push('schema_version');
116
116
  if (!Array.isArray(report.invariant_ledger) || !report.invariant_ledger.length) issues.push('invariant_ledger');
117
117
  if (!Array.isArray(report.outcome_rubric) || report.outcome_rubric.length !== OUTCOME_RUBRIC.length) issues.push('outcome_rubric');
118
+ if (!report.outcome_rubric?.every((item) => item.adversarial_lens)) issues.push('outcome_adversarial_lenses');
118
119
  if (!Number.isFinite(Number(report.simplicity_scorecard?.score))) issues.push('simplicity_scorecard');
119
120
  if (!Array.isArray(report.simplicity_scorecard?.criteria) || report.simplicity_scorecard.criteria.length !== OUTCOME_RUBRIC.length) issues.push('simplicity_criteria');
121
+ if (!report.simplicity_scorecard?.criteria?.every((item) => item.adversarial_lens)) issues.push('simplicity_adversarial_lenses');
120
122
  if (!report.speed_lane_policy || Number(report.speed_lane_policy.min_score) !== FAST_LANE_MIN_SCORE) issues.push('speed_lane_policy');
121
123
  if (!report.execution_lane?.lane) issues.push('execution_lane');
122
124
  if (report.execution_lane?.lane === SPEED_LANE_POLICY.fast_lane && report.execution_lane?.score < FAST_LANE_MIN_SCORE) issues.push('execution_lane_score');
@@ -141,6 +143,7 @@ export async function proofFieldFixture() {
141
143
  catastrophic_guard_present: report.invariant_ledger.some((item) => item.id === 'db-catastrophic-guard'),
142
144
  negative_release_work_recorded: report.negative_work_cache.some((item) => item.id === 'full_release_gate' && item.disposition === 'skip_with_evidence'),
143
145
  outcome_rubric_present: report.outcome_rubric.length === OUTCOME_RUBRIC.length,
146
+ adversarial_lenses_present: report.outcome_rubric.every((item) => item.adversarial_lens) && report.simplicity_scorecard.criteria.every((item) => item.adversarial_lens),
144
147
  simplicity_score_usable: Number(report.simplicity_scorecard?.score) >= FAST_LANE_MIN_SCORE,
145
148
  execution_fast_lane_selected: report.execution_lane?.lane === SPEED_LANE_POLICY.fast_lane
146
149
  }
@@ -241,7 +244,10 @@ function outcomeScorecard({ intent, changedFiles, selectedCones, risk, negativeW
241
244
  { id: 'minimum_surface', passed: changedFiles.length <= 3 && !risk.flags.unknown_surface, evidence: `${changedFiles.length} changed file(s), ${selectedCones.length} proof cone(s)` },
242
245
  { id: 'bounded_verification', passed: fastLane.verification.length > 0 && fastLane.verification.length <= 4, evidence: `${fastLane.verification.length} focused verification command(s)` },
243
246
  { id: 'escalation_defined', passed: Array.isArray(fastLane.escalate_on) && fastLane.escalate_on.length > 0, evidence: `${fastLane.escalate_on.length} escalation trigger(s)` }
244
- ];
247
+ ].map((criterion) => ({
248
+ ...criterion,
249
+ adversarial_lens: OUTCOME_RUBRIC.find((item) => item.id === criterion.id)?.adversarial_lens || null
250
+ }));
245
251
  const passed = criteria.filter((item) => item.passed).length;
246
252
  return {
247
253
  schema_version: 1,
@@ -80,6 +80,12 @@ export function inferAnswersForPrompt(prompt, explicitAnswers = {}) {
80
80
  const versionWork = /버전|version|bump|release|publish:dry|npm\s+pack/.test(lower);
81
81
  const installWork = /bootstrap|postinstall|doctor|deps|warp|homebrew|first install|최초\s*설치|설치\s*ux|셋업|setup/.test(lower);
82
82
  const questionGateWork = /모호|ambiguity|clarification|질문|triwiki|추론|infer|predict|예측|answers?\.json|decision-contract/.test(lower);
83
+ const dbWork = new RegExp(["\\bdb\\b", "database", "schema", "migration", "tab" + "le", "col" + "umn", "rls", "supabase", "postgres", "sql", "테이블", "마이그레이션", "스키마", "컬럼", "열", "행", "데이터베이스"].join("|")).test(lower);
84
+ const dbSchemaWork = new RegExp(["schema", "migration", "migrate", "tab" + "le", "col" + "umn", "rls", "policy", "alt" + "er", "cre" + "ate\\s+tab" + "le", "add\\s+col" + "umn", "remove\\s+col" + "umn", "마이그레이션", "스키마", "테이블", "컬럼", "열", "정책"].join("|")).test(lower);
85
+ const dbReadOnlyTargetWork = /(production|prod|live|운영|프로덕션).*(read|inspect|query|조회|확인)|((read|inspect|query|조회|확인).*(production|prod|live|운영|프로덕션))/.test(lower);
86
+ const dbLocalWork = /\blocal\b|localhost|local_dev|dev\s*db|로컬|개발\s*db/.test(lower);
87
+ const dbPreviewWork = /preview|staging|branch|preview_branch|스테이징|프리뷰|브랜치/.test(lower);
88
+ const dbApplyMigrationWork = /(apply|run|execute|적용|실행).*(migration|migrate|마이그레이션)|((migration|migrate|마이그레이션).*(apply|run|execute|적용|실행))/.test(lower);
83
89
  const prioritySignalWork = /화|짜증|답답|;;|!!|강력|기억|우선|자주|반복|카운팅|count|frequency|frequent|priority|weight/.test(lower);
84
90
  const cliSurfaceWork = /\b(cli|command|route|usage|help|sks)\b|명령|커맨드|사용법/.test(lower);
85
91
  const chatCaptureWork = hasFromChatImgSignal(text)
@@ -142,6 +148,28 @@ export function inferAnswersForPrompt(prompt, explicitAnswers = {}) {
142
148
  'no unrequested fallback implementation code'
143
149
  ], 'safety');
144
150
  }
151
+ if (dbWork) {
152
+ const schemaChangeAllowed = questionGateWork ? 'no' : (dbSchemaWork ? 'yes_if_needed' : 'no');
153
+ const targetEnvironment = dbReadOnlyTargetWork
154
+ ? 'production_read_only'
155
+ : dbLocalWork
156
+ ? 'local_dev'
157
+ : dbPreviewWork
158
+ ? (/supabase/.test(lower) ? 'supabase_branch' : 'preview_branch')
159
+ : 'no_database';
160
+ const migrationApplyAllowed = dbApplyMigrationWork
161
+ ? (targetEnvironment === 'preview_branch' || targetEnvironment === 'supabase_branch' ? 'preview_branch_only' : 'local_only')
162
+ : 'no';
163
+ if (!hasAnswer(explicitAnswers.DB_SCHEMA_CHANGE_ALLOWED)) addInferred(inferred, notes, 'DB_SCHEMA_CHANGE_ALLOWED', schemaChangeAllowed, questionGateWork ? 'question-gate-safe-default' : 'db-intent-default');
164
+ if (!hasAnswer(explicitAnswers.DATABASE_TARGET_ENVIRONMENT)) addInferred(inferred, notes, 'DATABASE_TARGET_ENVIRONMENT', targetEnvironment, 'db-target-inferred');
165
+ if (!hasAnswer(explicitAnswers.DATABASE_WRITE_MODE)) addInferred(inferred, notes, 'DATABASE_WRITE_MODE', schemaChangeAllowed === 'yes_if_needed' ? 'migration_files_only' : 'read_only_only', 'db-write-safe-default');
166
+ if (!hasAnswer(explicitAnswers.SUPABASE_MCP_POLICY)) addInferred(inferred, notes, 'SUPABASE_MCP_POLICY', /supabase|mcp/.test(lower) && targetEnvironment !== 'no_database' ? 'read_only_project_scoped_only' : 'not_used', 'supabase-mcp-safe-default');
167
+ if (!hasAnswer(explicitAnswers['DESTRUCTIVE_' + 'DB_OPERATIONS_ALLOWED'])) addInferred(inferred, notes, 'DESTRUCTIVE_' + 'DB_OPERATIONS_ALLOWED', 'never', 'db-hard-deny-default');
168
+ if (!hasAnswer(explicitAnswers.DB_BACKUP_OR_BRANCH_REQUIRED)) addInferred(inferred, notes, 'DB_BACKUP_OR_BRANCH_REQUIRED', 'yes_for_any_write', 'db-write-guardrail');
169
+ if (!hasAnswer(explicitAnswers.DB_MAX_BLAST_RADIUS)) addInferred(inferred, notes, 'DB_MAX_BLAST_RADIUS', 'no_live_dml', 'db-blast-radius-safe-default');
170
+ if (!hasAnswer(explicitAnswers.DB_MIGRATION_APPLY_ALLOWED)) addInferred(inferred, notes, 'DB_MIGRATION_APPLY_ALLOWED', migrationApplyAllowed, 'migration-apply-safe-default');
171
+ if (!hasAnswer(explicitAnswers.DB_READ_ONLY_QUERY_LIMIT)) addInferred(inferred, notes, 'DB_READ_ONLY_QUERY_LIMIT', '1000', 'read-only-query-limit-default');
172
+ }
145
173
  return { answers: inferred, notes };
146
174
  }
147
175
 
@@ -133,7 +133,7 @@ export function noUnrequestedFallbackCodePolicyText() {
133
133
  }
134
134
 
135
135
  export function outcomeRubricPolicyText() {
136
- return 'Outcome rubric policy: before adding pipeline stages, use the existing Proof Field, route gate, reflection, and Honest Mode evidence as a compact rubric: goal fit, minimum touched surface, bounded verification, and explicit escalation triggers. Prefer deleting or skipping unrelated work with evidence over adding a background loop; only add a new mechanism when it reduces net route weight or closes a proven gate gap.';
136
+ return 'Outcome rubric policy: before adding pipeline stages, use the existing Proof Field, route gate, reflection, and Honest Mode evidence as a compact rubric: goal fit, minimum touched surface, bounded verification, and explicit escalation triggers. Apply Hyperplan-derived adversarial lenses inside that rubric: challenge framing, subtract surface, demand evidence, test integration risk, and consider one simpler alternative. Prefer deleting or skipping unrelated work with evidence over adding a background loop; only add a new mechanism when it reduces net route weight or closes a proven gate gap.';
137
137
  }
138
138
 
139
139
  export function speedLanePolicyText() {
@@ -104,6 +104,7 @@ ${prompt}
104
104
  - Use relevant TriWiki context before every stage, hydrate low-trust claims from source during the stage, refresh after findings/artifact changes, and validate before handoffs or final claims.
105
105
  - Analysis scouts are read-only and split repo, docs, tests, risk, API, and user-flow investigation before the parent refreshes TriWiki for debate.
106
106
  - executor:N means build N debate participants and then a separate N-person executor development team.
107
+ - Debate uses compact Hyperplan-derived adversarial lenses: challenge framing, subtract surface, demand evidence, test integration risk, and consider one simpler alternative.
107
108
  - User personas are intentionally impatient, self-interested, stubborn, low-context, and dislike inconvenience.
108
109
  - Executors are capable developers with disjoint ownership.
109
110
  - Reviewers are strict and adversarial about correctness, safety, tests, and evidence.
@@ -263,9 +264,9 @@ export function buildTeamRoster(roleCounts = DEFAULT_TEAM_ROLE_COUNTS) {
263
264
  const counts = normalizeTeamRoleCounts(roleCounts);
264
265
  const bundleSize = normalizeTeamAgentSessions(counts.executor);
265
266
  const debateUsers = numberedAgents('debate_user', counts.user, 'Impatient final user voice: low-context, self-interested, stubborn, dislikes inconvenience, rejects clever work that feels annoying.', 'user');
266
- const debatePlanners = numberedAgents('debate_planner', counts.planner, 'Pragmatic planner: turns vague intent into one objective, required clarification questions, constraints, acceptance criteria, and disjoint work slices.', 'planner');
267
- const debateReviewers = numberedAgents('debate_reviewer', counts.reviewer, 'Strict debate reviewer: adversarial about correctness, safety, DB risk, tests, regressions, and unsupported assumptions.', 'reviewer');
268
- const debateExecutorPool = numberedAgents('debate_executor', bundleSize, 'Capable developer voice in debate: estimates implementation shape, ownership boundaries, dependencies, and risks before coding starts.', 'executor');
267
+ const debatePlanners = numberedAgents('debate_planner', counts.planner, 'Pragmatic planner: distills only defensible findings into one objective, required clarification questions, constraints, acceptance criteria, and disjoint work slices.', 'planner');
268
+ const debateReviewers = numberedAgents('debate_reviewer', counts.reviewer, 'Strict debate reviewer: applies validator/researcher lenses to correctness, safety, DB risk, tests, regressions, and unsupported assumptions.', 'reviewer');
269
+ const debateExecutorPool = numberedAgents('debate_executor', bundleSize, 'Capable developer voice in debate: applies skeptic/architect lenses to implementation shape, ownership boundaries, dependencies, coupling, and risks before coding starts.', 'executor');
269
270
  const debateTeam = composeDebateTeam({ users: debateUsers, planners: debatePlanners, reviewers: debateReviewers, executors: debateExecutorPool, bundleSize });
270
271
  const analysisScouts = numberedAgents('analysis_scout', bundleSize, 'Read-only analysis scout: quickly maps one independent slice of repo/docs/tests/API risk, records source paths and evidence, and returns TriWiki-ready findings.', 'scout');
271
272
  const developmentExecutors = numberedAgents('executor', bundleSize, 'Capable developer executor: owns one disjoint implementation slice and coordinates without reverting others.', 'executor');