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.
@@ -1,8 +1,9 @@
1
1
  import path from 'node:path';
2
- import { nowIso, sksRoot, writeJsonAtomic } from '../fsx.js';
2
+ import { nowIso, readJson, sksRoot, writeJsonAtomic } from '../fsx.js';
3
3
  import { findLatestMission } from '../mission.js';
4
4
  import { narutoCommand } from './naruto-command.js';
5
5
  import { teamLegacyObserveCommand, teamLegacySubcommands } from './team-legacy-observe-command.js';
6
+ import { SSOT_GUARD_ARTIFACT } from '../safety/ssot-guard.js';
6
7
  export async function team(args = []) {
7
8
  if (teamLegacySubcommands.has(String(args[0] || ''))) {
8
9
  return teamLegacyObserveCommand(String(args[0]), args.slice(1));
@@ -13,9 +14,13 @@ async function redirectTeamCreateToNaruto(args = []) {
13
14
  const root = await sksRoot();
14
15
  const list = (args || []).map((arg) => String(arg));
15
16
  const narutoArgs = list[0] === 'run' ? list : ['run', ...list];
17
+ const jsonRequested = list.includes('--json');
16
18
  console.warn('SKS Team is deprecated for new execution missions; redirecting to $Naruto.');
17
- const result = await narutoCommand(narutoArgs);
19
+ const result = jsonRequested
20
+ ? await withSuppressedConsoleLog(() => narutoCommand(narutoArgs))
21
+ : await narutoCommand(narutoArgs);
18
22
  const missionId = result?.mission_id || await findLatestMission(root);
23
+ const nativeAgentRun = missionId ? await buildTeamNativeAgentCompatibility(root, missionId, result) : null;
19
24
  if (missionId) {
20
25
  await writeJsonAtomic(path.join(root, '.sneakoscope', 'missions', missionId, 'team-alias-to-naruto.json'), {
21
26
  schema: 'sks.team-alias-to-naruto.v1',
@@ -26,10 +31,65 @@ async function redirectTeamCreateToNaruto(args = []) {
26
31
  route_command: '$Naruto',
27
32
  deprecated_route: '$Team',
28
33
  parallel_write_policy: result?.parallel_write_policy || result?.run?.parallel_write_policy || null,
34
+ ssot_guard_artifact: SSOT_GUARD_ARTIFACT,
29
35
  created_at: nowIso(),
30
36
  args: list
31
37
  });
32
38
  }
33
- return result;
39
+ const finalResult = {
40
+ ...result,
41
+ mock: result?.mock === true || result?.backend === 'fake',
42
+ ...(nativeAgentRun ? { native_agent_run: nativeAgentRun } : {})
43
+ };
44
+ if (jsonRequested)
45
+ console.log(JSON.stringify(finalResult, null, 2));
46
+ return finalResult;
47
+ }
48
+ async function withSuppressedConsoleLog(fn) {
49
+ const originalLog = console.log;
50
+ console.log = () => undefined;
51
+ try {
52
+ return await fn();
53
+ }
54
+ finally {
55
+ console.log = originalLog;
56
+ }
57
+ }
58
+ async function buildTeamNativeAgentCompatibility(root, missionId, result) {
59
+ const ledgerRoot = path.join(root, '.sneakoscope', 'missions', missionId, 'agents');
60
+ const [schedulerState, proof, parallelWritePolicy] = await Promise.all([
61
+ readJson(path.join(ledgerRoot, 'agent-scheduler-state.json'), null),
62
+ readJson(path.join(ledgerRoot, 'agent-proof-evidence.json'), null),
63
+ readJson(path.join(ledgerRoot, 'agent-parallel-write-policy.json'), null)
64
+ ]);
65
+ if (!schedulerState || !proof)
66
+ return null;
67
+ return {
68
+ schema: result?.run?.schema || 'sks.agent-run.v1',
69
+ ok: result?.run?.ok === true || result?.ok === true,
70
+ mission_id: missionId,
71
+ route: '$Team',
72
+ backend: result?.backend || result?.run?.backend || proof.backend || null,
73
+ ledger_root: path.relative(root, ledgerRoot),
74
+ target_active_slots: schedulerState.target_active_slots ?? result?.target_active_slots ?? null,
75
+ scheduler: {
76
+ state: schedulerState
77
+ },
78
+ proof: {
79
+ ...proof,
80
+ route: '$Team',
81
+ route_command: 'sks team',
82
+ route_blackbox_kind: 'actual_team_command',
83
+ real_route_command_used: true
84
+ },
85
+ parallel_write_policy: parallelWritePolicy || result?.parallel_write_policy || result?.run?.parallel_write_policy || null,
86
+ redirected_to: '$Naruto',
87
+ compatibility: {
88
+ schema: 'sks.team-native-agent-compatibility.v1',
89
+ ok: true,
90
+ source: 'team-alias-to-naruto',
91
+ ledger_root: path.relative(root, ledgerRoot)
92
+ }
93
+ };
34
94
  }
35
95
  //# sourceMappingURL=team-command.js.map
@@ -1,7 +1,7 @@
1
1
  import path from 'node:path';
2
2
  import { readJson, writeJsonAtomic, nowIso, sha256 } from './fsx.js';
3
3
  import { validateQaLoopAnswers } from './qa-loop.js';
4
- import { inferAnswersForPrompt } from './questions.js';
4
+ import { buildRequestIntake, inferAnswersForPrompt, REQUEST_INTAKE_ARTIFACT } from './questions.js';
5
5
  import { bindMistakeRecallToAnswers, buildMistakeRecallLedger, mistakeRecallContractSummary, writeMistakeRecallArtifacts } from './mistake-recall.js';
6
6
  function isEmptyAnswer(v, slot = {}) {
7
7
  if (v === undefined || v === null)
@@ -41,7 +41,7 @@ export function validateAnswers(schema, answers) {
41
41
  errors.push(...validateQaLoopAnswers(schema, answers));
42
42
  return { ok: errors.length === 0, errors, resolved, totalRequired: schema.slots.filter((s) => s.required).length };
43
43
  }
44
- export function buildDecisionContract({ mission, schema, answers, mistakeRecall = null }) {
44
+ export function buildDecisionContract({ mission, schema, answers, mistakeRecall = null, requestIntake = null }) {
45
45
  const madSks = answers.MAD_SKS_MODE === 'explicit_invocation_only';
46
46
  const defaults = {
47
47
  if_multiple_valid_implementations: 'choose_smallest_reversible_change',
@@ -115,6 +115,14 @@ export function buildDecisionContract({ mission, schema, answers, mistakeRecall
115
115
  acceptance_criteria: Array.isArray(answers.ACCEPTANCE_CRITERIA) ? answers.ACCEPTANCE_CRITERIA : String(answers.ACCEPTANCE_CRITERIA || '').split('\n').map((x) => x.trim()).filter(Boolean),
116
116
  non_goals: Array.isArray(answers.NON_GOALS) ? answers.NON_GOALS : String(answers.NON_GOALS || '').split('\n').map((x) => x.trim()).filter(Boolean),
117
117
  test_scope: answers.TEST_SCOPE,
118
+ request_intake: requestIntake ? {
119
+ artifact: REQUEST_INTAKE_ARTIFACT,
120
+ prompt_hash: requestIntake.prompt_hash || null,
121
+ interpreted_intent: requestIntake.interpreted_intent || null,
122
+ requirements: requestIntake.requirements || [],
123
+ transformed_prompt: requestIntake.transformed_prompt || null,
124
+ wiki_context_used: requestIntake.wiki_context_used || null
125
+ } : null,
118
126
  triwiki_mistake_recall: mistakeRecallContractSummary(mistakeRecall),
119
127
  approved_defaults: defaults,
120
128
  decision_ladder: [
@@ -135,6 +143,7 @@ export function buildDecisionContract({ mission, schema, answers, mistakeRecall
135
143
  return contract;
136
144
  }
137
145
  export async function sealContract(missionDir, mission) {
146
+ const root = rootFromMissionDir(missionDir);
138
147
  const schema = await readJson(path.join(missionDir, 'required-answers.schema.json'), {});
139
148
  const explicitAnswers = await readJson(path.join(missionDir, 'answers.json'), {});
140
149
  const inferred = inferAnswersForPrompt(mission?.prompt || schema?.prompt || '', explicitAnswers);
@@ -143,7 +152,7 @@ export async function sealContract(missionDir, mission) {
143
152
  ...inferred.answers,
144
153
  ...explicitAnswers
145
154
  };
146
- const root = rootFromMissionDir(missionDir);
155
+ const requestIntake = await readOrBuildRequestIntake(missionDir, root, mission?.prompt || schema?.prompt || '', baseAnswers);
147
156
  const mistakeRecall = await buildMistakeRecallLedger(root, {
148
157
  prompt: mission?.prompt || schema?.prompt || '',
149
158
  answers: baseAnswers
@@ -152,7 +161,7 @@ export async function sealContract(missionDir, mission) {
152
161
  const validation = validateAnswers(schema, answers);
153
162
  if (!validation.ok)
154
163
  return { ok: false, validation };
155
- const contract = buildDecisionContract({ mission, schema, answers, mistakeRecall });
164
+ const contract = buildDecisionContract({ mission, schema, answers, mistakeRecall, requestIntake });
156
165
  const mistakeRecallConsumption = await writeMistakeRecallArtifacts(missionDir, mistakeRecall, contract);
157
166
  await writeJsonAtomic(path.join(missionDir, 'resolved-answers.json'), {
158
167
  explicit_answers: explicitAnswers,
@@ -163,12 +172,27 @@ export async function sealContract(missionDir, mission) {
163
172
  artifact: 'mistake-recall-ledger.json',
164
173
  consumed: mistakeRecallConsumption.ok,
165
174
  required: mistakeRecall.required
175
+ },
176
+ request_intake: {
177
+ artifact: REQUEST_INTAKE_ARTIFACT,
178
+ prompt_hash: requestIntake?.prompt_hash || null,
179
+ transformed_prompt_available: Boolean(requestIntake?.transformed_prompt)
166
180
  }
167
181
  });
168
182
  await writeJsonAtomic(path.join(missionDir, 'decision-contract.json'), contract);
169
183
  await writeJsonAtomic(path.join(missionDir, 'answer-validation.json'), validation);
170
184
  return { ok: true, validation, contract };
171
185
  }
186
+ async function readOrBuildRequestIntake(missionDir, root, prompt, answers = {}) {
187
+ const file = path.join(missionDir, REQUEST_INTAKE_ARTIFACT);
188
+ const existing = await readJson(file, null);
189
+ if (existing)
190
+ return existing;
191
+ const wikiContext = await readJson(path.join(root, '.sneakoscope', 'wiki', 'context-pack.json'), null);
192
+ const intake = buildRequestIntake(prompt, answers, { wikiContext });
193
+ await writeJsonAtomic(file, intake);
194
+ return intake;
195
+ }
172
196
  function rootFromMissionDir(missionDir) {
173
197
  const resolved = path.resolve(missionDir);
174
198
  const parts = resolved.split(path.sep);
@@ -0,0 +1,64 @@
1
+ import path from 'node:path';
2
+ import { COMMAND_CATALOG } from '../routes.js';
3
+ import { nowIso, writeJsonAtomic } from '../fsx.js';
4
+ import { COMMANDS, LEGACY_COMMAND_ALIASES, commandNames } from '../../cli/command-registry.js';
5
+ export const COMMAND_ALIAS_CLEANUP_SCHEMA = 'sks.command-alias-cleanup.v1';
6
+ export async function runDoctorCommandAliasCleanup(opts) {
7
+ const report = commandAliasCleanupReport(opts);
8
+ if (opts.fix)
9
+ await writeJsonAtomic(report.report_path, report);
10
+ return report;
11
+ }
12
+ export function commandAliasCleanupReport(opts) {
13
+ const root = opts.root;
14
+ const legacyAliases = Object.entries(LEGACY_COMMAND_ALIASES).map(([alias, canonical]) => ({
15
+ alias,
16
+ canonical
17
+ }));
18
+ const registeredAliasCommands = legacyAliases
19
+ .filter((entry) => Object.prototype.hasOwnProperty.call(COMMANDS, entry.alias))
20
+ .map((entry) => entry.alias);
21
+ const catalogAliasRows = legacyAliases
22
+ .filter((entry) => COMMAND_CATALOG.some((row) => row.name === entry.alias))
23
+ .map((entry) => entry.alias);
24
+ const canonical = commandNames();
25
+ const missingCanonicalTargets = legacyAliases
26
+ .filter((entry) => !canonical.includes(entry.canonical))
27
+ .map((entry) => `${entry.alias}->${entry.canonical}`);
28
+ const blockers = [
29
+ ...registeredAliasCommands.map((alias) => `legacy_alias_registered_as_command:${alias}`),
30
+ ...catalogAliasRows.map((alias) => `legacy_alias_visible_in_command_catalog:${alias}`),
31
+ ...missingCanonicalTargets.map((entry) => `legacy_alias_missing_target:${entry}`)
32
+ ];
33
+ const ok = blockers.length === 0;
34
+ return {
35
+ schema: COMMAND_ALIAS_CLEANUP_SCHEMA,
36
+ ok,
37
+ status: ok ? 'clean' : 'blocked',
38
+ generated_at: nowIso(),
39
+ root,
40
+ fix: Boolean(opts.fix),
41
+ report_path: path.join(root, '.sneakoscope', 'reports', 'command-alias-cleanup.json'),
42
+ canonical_command_count: canonical.length,
43
+ legacy_alias_count: legacyAliases.length,
44
+ aliases: legacyAliases,
45
+ detected: {
46
+ registered_alias_commands: registeredAliasCommands,
47
+ catalog_alias_rows: catalogAliasRows,
48
+ missing_canonical_targets: missingCanonicalTargets
49
+ },
50
+ actions: ok
51
+ ? [{
52
+ action: opts.fix ? 'doctor_fix_verified_aliases_consolidated' : 'verify_aliases_consolidated',
53
+ ok: true,
54
+ detail: 'Legacy command names dispatch through COMMAND_ALIASES and are not registered as duplicate command rows.'
55
+ }]
56
+ : [{
57
+ action: 'source_registry_cleanup_required',
58
+ ok: false,
59
+ detail: 'Remove duplicate alias rows from COMMANDS and COMMAND_CATALOG, then map them through LEGACY_COMMAND_ALIASES.'
60
+ }],
61
+ blockers
62
+ };
63
+ }
64
+ //# sourceMappingURL=command-alias-cleanup.js.map
@@ -39,6 +39,7 @@ const FIXTURES = Object.freeze({
39
39
  'cli-qa-loop': fixture('mock', 'sks qa-loop status latest --json', ['qa-loop-proof.json', 'completion-proof.json'], 'pass'),
40
40
  'cli-ppt': fixture('mock', 'sks ppt fixture --mock --json', ['ppt-imagegen-review-gate.json', 'completion-proof.json'], 'pass'),
41
41
  'cli-image-ux-review': fixture('mock', 'sks image-ux-review status latest --json', ['image-ux-generated-review-ledger.json', 'image-voxel-ledger.json'], 'pass'),
42
+ 'cli-computer-use': fixture('real_optional', 'sks computer-use status --json', [], 'pass'),
42
43
  'cli-pipeline': fixture('mock', 'sks pipeline status latest --json', ['pipeline-plan.json'], 'pass'),
43
44
  'cli-validate-artifacts': fixture('mock', 'sks validate-artifacts latest --json', ['validation-report.json'], 'pass'),
44
45
  'cli-hproof': fixture('mock', 'sks hproof check latest', ['completion-proof.json'], 'pass'),
@@ -68,6 +69,7 @@ const FIXTURES = Object.freeze({
68
69
  'cli-zellij': fixture('mock', 'npm run zellij:capability --silent', [], 'pass'),
69
70
  'cli-tmux': fixture('mock', 'removed runtime migration notice: sks tmux --json', [], 'pass'),
70
71
  'cli-mad': fixture('mock', 'sks mad --help', [], 'pass'),
72
+ 'cli-mad-sks': fixture('static', 'sks mad-sks status --json', [], 'pass'),
71
73
  'cli-auto-review': fixture('mock', 'sks auto-review status --json', [], 'pass'),
72
74
  'cli-commit': fixture('mock', 'sks commit --dry-run', [], 'pass'),
73
75
  'cli-commit-and-push': fixture('mock', 'sks commit-and-push --dry-run', [], 'pass'),
@@ -813,7 +813,7 @@ function commandCategory(name) {
813
813
  return 'proof-route';
814
814
  if (['qa-loop', 'research', 'recallpulse', 'skill-dream', 'eval', 'perf'].includes(name))
815
815
  return 'loop';
816
- if (['codex', 'codex-app', 'codex-native', 'codex-lb', 'auth', 'hooks', 'context7'].includes(name))
816
+ if (['codex', 'codex-app', 'codex-native', 'codex-lb', 'hooks', 'context7', 'computer-use'].includes(name))
817
817
  return 'integration';
818
818
  if (['db', 'guard', 'conflicts', 'harness', 'versioning'].includes(name))
819
819
  return 'safety';
@@ -826,7 +826,7 @@ function commandCategory(name) {
826
826
  function commandMaturity(name) {
827
827
  if (['help', 'version', 'commands', 'usage', 'root', 'quickstart', 'setup', 'doctor', 'selftest', 'update-check', 'fast-mode'].includes(name))
828
828
  return 'stable';
829
- if (['codex', 'codex-app', 'codex-lb', 'hooks', 'features', 'all-features', 'wiki', 'wrongness', 'team', 'pipeline', 'goal', 'db', 'guard'].includes(name))
829
+ if (['codex', 'codex-app', 'codex-native', 'codex-lb', 'hooks', 'features', 'all-features', 'wiki', 'wrongness', 'team', 'pipeline', 'goal', 'db', 'guard', 'computer-use', 'mad-sks'].includes(name))
830
830
  return 'beta';
831
831
  return 'labs';
832
832
  }
package/dist/core/fsx.js 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
  import { fileURLToPath } from 'node:url';
8
- export const PACKAGE_VERSION = '3.1.8';
8
+ export const PACKAGE_VERSION = '3.1.9';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11
  export function nowIso() {
@@ -29,7 +29,10 @@ export function buildNarutoWorkGraph(input = {}) {
29
29
  const readonly = input.readonly === true;
30
30
  const writeCapable = input.writeCapable !== false && !readonly;
31
31
  const minimumFanout = writeCapable ? requestedClones * 2 : requestedClones;
32
- const totalWorkItems = Math.max(minimumFanout, normalizePositiveInt(input.totalWorkItems, minimumFanout));
32
+ const requestedWorkItems = normalizePositiveInt(input.totalWorkItems, minimumFanout);
33
+ const totalWorkItems = input.honorExplicitTotalWorkItems === true
34
+ ? Math.max(requestedClones, requestedWorkItems)
35
+ : Math.max(minimumFanout, requestedWorkItems);
33
36
  const kindCycle = writeCapable ? WRITE_CAPABLE_KIND_CYCLE : READONLY_KIND_CYCLE;
34
37
  const basePath = normalizeNarutoPath(input.leaseBasePath || '.sneakoscope/naruto/patch-envelopes');
35
38
  const targetPaths = normalizePaths(input.targetPaths || []);
@@ -3,7 +3,7 @@ import path from 'node:path';
3
3
  import { appendJsonl, exists, nowIso, readJson, readText, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
4
4
  import { containsUserQuestion, noQuestionContinuationReason } from '../no-question-guard.js';
5
5
  import { createMission, missionDir, setCurrent } from '../mission.js';
6
- import { buildQuestionSchemaForRoute, writeQuestions } from '../questions.js';
6
+ import { buildQuestionSchemaForRoute, buildRequestIntake, REQUEST_INTAKE_ARTIFACT, writeQuestions } from '../questions.js';
7
7
  import { sealContract } from '../decision-contract.js';
8
8
  import { scanDbSafety } from '../db-safety.js';
9
9
  import { GOAL_WORKFLOW_ARTIFACT, writeGoalWorkflow } from '../goal-workflow.js';
@@ -81,6 +81,8 @@ function reflectionInstructionText(commandPrefix = 'sks') {
81
81
  export function buildPipelinePlan(input = {}) {
82
82
  const route = input.route || routePrompt(input.task || '$SKS');
83
83
  const task = String(input.task || '').trim();
84
+ const requestIntake = input.requestIntake || null;
85
+ const executionPrompt = String(requestIntake?.transformed_prompt || task || '').trim();
84
86
  const ambiguity = normalizeAmbiguity(input.ambiguity, route);
85
87
  const proof = normalizeProofField(input.proofField);
86
88
  const lane = selectPipelineLane(route, task, proof);
@@ -106,6 +108,18 @@ export function buildPipelinePlan(input = {}) {
106
108
  reflection_required: reflectionRequiredForRoute(route)
107
109
  },
108
110
  task,
111
+ request_intake: requestIntake ? {
112
+ artifact: REQUEST_INTAKE_ARTIFACT,
113
+ prompt_hash: requestIntake.prompt_hash || null,
114
+ interpreted_goal: requestIntake.interpreted_intent?.goal || null,
115
+ requirement_count: Array.isArray(requestIntake.requirements) ? requestIntake.requirements.length : 0,
116
+ transformed_prompt_available: Boolean(requestIntake.transformed_prompt),
117
+ wiki_context_used: requestIntake.wiki_context_used?.source || null
118
+ } : {
119
+ artifact: REQUEST_INTAKE_ARTIFACT,
120
+ status: 'not_attached'
121
+ },
122
+ execution_prompt: executionPrompt,
109
123
  ambiguity_gate: ambiguity,
110
124
  runtime_lane: lane,
111
125
  stages,
@@ -130,10 +144,37 @@ export function buildPipelinePlan(input = {}) {
130
144
  };
131
145
  }
132
146
  export async function writePipelinePlan(dir, input = {}) {
133
- const plan = buildPipelinePlan(input);
147
+ const requestIntake = input.requestIntake || await writeRequestIntakeArtifact(dir, input);
148
+ const plan = buildPipelinePlan({ ...input, requestIntake });
134
149
  await writeJsonAtomic(path.join(dir, PIPELINE_PLAN_ARTIFACT), plan);
135
150
  return plan;
136
151
  }
152
+ export async function writeRequestIntakeArtifact(dir, input = {}) {
153
+ const file = path.join(dir, REQUEST_INTAKE_ARTIFACT);
154
+ if (!input.requestIntake && !input.forceRequestIntakeRewrite) {
155
+ const existing = await readJson(file, null);
156
+ if (existing)
157
+ return existing;
158
+ }
159
+ const root = input.root || rootFromMissionDir(dir);
160
+ const wikiContext = input.wikiContext !== undefined
161
+ ? input.wikiContext
162
+ : await readJson(path.join(root, '.sneakoscope', 'wiki', 'context-pack.json'), null);
163
+ const intake = input.requestIntake || buildRequestIntake(input.task || '', {}, {
164
+ wikiContext,
165
+ route: input.route || null
166
+ });
167
+ await writeJsonAtomic(file, intake);
168
+ return intake;
169
+ }
170
+ function rootFromMissionDir(dir) {
171
+ const resolved = path.resolve(dir);
172
+ const parts = resolved.split(path.sep);
173
+ const idx = parts.lastIndexOf('.sneakoscope');
174
+ if (idx > 0)
175
+ return parts.slice(0, idx).join(path.sep) || path.sep;
176
+ return path.resolve(resolved, '..', '..', '..');
177
+ }
137
178
  export function validatePipelinePlan(plan = {}) {
138
179
  const issues = [];
139
180
  if (plan.schema_version !== PIPELINE_PLAN_SCHEMA_VERSION)
@@ -336,12 +377,13 @@ function planVerification(route, proof) {
336
377
  function planNextActions(route, task, ambiguity, lane, agentPolicy = normalizeAgentPolicy(route, task, {})) {
337
378
  if (ambiguity.required && !ambiguity.passed) {
338
379
  return [
380
+ `read ${REQUEST_INTAKE_ARTIFACT} and preserve its source-order requirements`,
339
381
  'auto-seal execution contract from inferred answers',
340
382
  ...(looksLikeProblemSolvingRequest(task) ? ['run Solution Scout web search for similar fixes before editing'] : []),
341
383
  'continue with decision-contract.json'
342
384
  ];
343
385
  }
344
- const actions = ['read pipeline-plan.json before work', 'execute kept stages only', 'run listed verification'];
386
+ const actions = [`read ${REQUEST_INTAKE_ARTIFACT} and use its transformed_prompt`, 'read pipeline-plan.json before work', 'execute kept stages only', 'run listed verification'];
345
387
  if (agentPolicy.required)
346
388
  actions.splice(1, 0, 'run sks agents run latest --json before implementation');
347
389
  if (!lane.fast_lane_allowed && routeRequiresSubagents(route, task)) {
@@ -375,6 +417,7 @@ export function promptPipelineContext(prompt, route = null) {
375
417
  'Hook visibility limit: hooks can inject context/status or block/continue a turn, but they cannot create arbitrary live chat bubbles; use team events, mission files, or normal assistant updates for live transcript details.',
376
418
  'Ambient Goal continuation: even without an explicit $Goal keyword, use Codex native /goal persistence when it helps keep long work resumable and complete; do not let it replace or skip the selected SKS route gates.',
377
419
  'Route contract: execution routes infer contract answers from the prompt, TriWiki/current-code defaults, and conservative SKS policy. DFix and Answer bypass stateful execution because they do not start implementation.',
420
+ `Wiki-informed request intake: when a mission exists, read ${REQUEST_INTAKE_ARTIFACT} before execution; preserve every source-order requirement, apply TriWiki attention/use_first and hydrate_first context, and execute request_intake.transformed_prompt through the selected route instead of relying on the vague original wording alone.`,
378
421
  'Plan-first interaction: when ambiguity questions are truly required, show the user only the missing human decision(s), then seal the decision contract internally and execute/verify.',
379
422
  'Question-shaped directive policy: before using Answer, decide whether a question is a real information request or an implicit instruction/complaint about broken behavior. Rhetorical bug reports, mandatory-policy statements, and "why is this not happening?" execution complaints must route to Naruto, not Answer.',
380
423
  'Best-practice prompt shape: extract Goal, Context, Constraints, and Done-when before implementation; keep questions compact and only ask for answers that can change scope, safety, user-facing behavior, or acceptance criteria.',
@@ -658,7 +701,8 @@ async function activePipelinePlanNote(root, state = {}) {
658
701
  const kept = plan.stage_summary?.kept ?? plan.kept_stages?.length ?? 0;
659
702
  const skipped = plan.stage_summary?.skipped ?? plan.skipped_stages?.length ?? 0;
660
703
  const next = Array.isArray(plan.next_actions) && plan.next_actions.length ? ` Next planned action: ${plan.next_actions[0]}.` : '';
661
- return ` Pipeline plan: .sneakoscope/missions/${state.mission_id}/${PIPELINE_PLAN_ARTIFACT} (${lane}; kept=${kept}, skipped=${skipped}).${next}`;
704
+ const intake = plan.request_intake?.artifact ? ` Request intake: .sneakoscope/missions/${state.mission_id}/${plan.request_intake.artifact}; execution prompt=${plan.request_intake.transformed_prompt_available ? 'available' : 'missing'}.` : '';
705
+ return ` Pipeline plan: .sneakoscope/missions/${state.mission_id}/${PIPELINE_PLAN_ARTIFACT} (${lane}; kept=${kept}, skipped=${skipped}).${intake}${next}`;
662
706
  }
663
707
  async function prepareGoal(root, route, task, required) {
664
708
  const { id, dir, mission } = await createMission(root, { mode: 'goal', prompt: task });
@@ -1150,6 +1194,8 @@ function routeContext(route, id, task, required, next) {
1150
1194
  ${route.command} route prepared.
1151
1195
  Mission: ${id}
1152
1196
  Task: ${visibleTask}
1197
+ Request intake: .sneakoscope/missions/${id}/${REQUEST_INTAKE_ARTIFACT}
1198
+ Execution prompt: request-intake.transformed_prompt
1153
1199
  Pipeline plan: .sneakoscope/missions/${id}/${PIPELINE_PLAN_ARTIFACT}
1154
1200
  Required skills: ${route.requiredSkills.join(', ')}
1155
1201
  Stop gate: ${route.stopGate}
@@ -4,6 +4,7 @@ import { appendJsonl, exists, nowIso, readJson, readText, writeJsonAtomic } from
4
4
  import { containsUserQuestion, noQuestionContinuationReason } from '../no-question-guard.js';
5
5
  import { missionDir } from '../mission.js';
6
6
  import { evaluateResearchGate } from '../research.js';
7
+ import { evaluateQaGate } from '../qa-loop.js';
7
8
  import { PPT_REQUIRED_GATE_FIELDS } from '../ppt.js';
8
9
  import { validateFinalHonestModeReport } from '../artifact-schemas.js';
9
10
  import { IMAGE_UX_REVIEW_GATE_ARTIFACT, IMAGE_UX_REVIEW_POLICY_ARTIFACT, IMAGE_UX_REVIEW_SCREEN_INVENTORY_ARTIFACT, IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT, IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT, IMAGE_UX_REVIEW_ITERATION_REPORT_ARTIFACT, IMAGE_UX_REVIEW_REQUIRED_GATE_FIELDS, IMAGE_UX_REVIEW_REFERENCE_GATE_FIELDS, IMAGE_UX_REVIEW_HONEST_MODE_ARTIFACT, imageUxReviewGateAllowsReferenceCloseout } from '../image-ux-review.js';
@@ -392,7 +393,9 @@ function missingRequiredGateFields(file, state, gate = {}) {
392
393
  if (file === 'qa-gate.json' || mode === 'QALOOP') {
393
394
  const required = ['clarification_contract_sealed', 'qa_report_written', 'qa_ledger_complete', 'checklist_completed', 'safety_reviewed', 'deployed_destructive_tests_blocked', 'credentials_not_persisted', 'honest_mode_complete'];
394
395
  if (gate.ui_e2e_required === true)
395
- required.push('chrome_extension_preflight_passed', 'ui_chrome_extension_evidence');
396
+ required.push('chrome_extension_preflight_passed', 'ui_chrome_extension_evidence', 'ui_chrome_extension_screenshot_captured');
397
+ if (gate.gpt_image_2_annotated_review_required === true)
398
+ required.push('gpt_image_2_annotated_review_generated');
396
399
  return required.filter((key) => gate[key] !== true);
397
400
  }
398
401
  if (file === 'ppt-gate.json' || mode === 'PPT') {
@@ -421,6 +424,12 @@ async function missingRequiredGateArtifacts(root, file, state, gate = {}) {
421
424
  return [];
422
425
  return (evaluated.reasons || ['research_gate_blocked']).map((reason) => `research-gate:${reason}`);
423
426
  }
427
+ if (file === 'qa-gate.json' || mode === 'QALOOP') {
428
+ const evaluated = await evaluateQaGate(missionDir(root, state.mission_id));
429
+ if (evaluated.passed === true)
430
+ return [];
431
+ return (evaluated.reasons || ['qa_gate_blocked']).map((reason) => `qa-gate:${reason}`);
432
+ }
424
433
  if (file === IMAGE_UX_REVIEW_GATE_ARTIFACT || mode === 'IMAGE_UX_REVIEW')
425
434
  return missingImageUxReviewArtifacts(root, state, gate);
426
435
  if (file === 'naruto-gate.json' || mode === 'NARUTO')
@@ -41,7 +41,7 @@ export async function validateRouteCompletionProof(root, { missionId = null, rou
41
41
  if (agentCount < 5)
42
42
  issues.push('agent_count_below_5');
43
43
  if (agentCount > maxAgentCount)
44
- issues.push(`agent_count_above_${maxAgentCount}`);
44
+ issues.push(normalizedRoute === '$Naruto' ? 'agent_count_above_100' : 'agent_count_above_20');
45
45
  if (agents.all_sessions_closed !== true)
46
46
  issues.push('agent_sessions_not_closed');
47
47
  if (agents.no_overlap_ok !== true)