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 +1 -1
- package/package.json +1 -1
- package/src/cli/main.mjs +12 -1
- package/src/cli/maintenance-commands.mjs +2 -2
- package/src/core/fsx.mjs +1 -1
- package/src/core/hooks-runtime.mjs +39 -0
- package/src/core/pipeline.mjs +4 -3
- package/src/core/proof-field.mjs +11 -5
- package/src/core/questions.mjs +28 -0
- package/src/core/routes.mjs +1 -1
- package/src/core/team-live.mjs +4 -3
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.
|
|
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.
|
|
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))) {
|
package/src/core/pipeline.mjs
CHANGED
|
@@ -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
|
|
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
|
}
|
package/src/core/proof-field.mjs
CHANGED
|
@@ -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,
|
package/src/core/questions.mjs
CHANGED
|
@@ -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
|
|
package/src/core/routes.mjs
CHANGED
|
@@ -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() {
|
package/src/core/team-live.mjs
CHANGED
|
@@ -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:
|
|
267
|
-
const debateReviewers = numberedAgents('debate_reviewer', counts.reviewer, 'Strict debate reviewer:
|
|
268
|
-
const debateExecutorPool = numberedAgents('debate_executor', bundleSize, 'Capable developer voice in debate:
|
|
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');
|