sneakoscope 3.1.8 → 3.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/.sks-build-stamp.json +4 -4
- package/dist/bin/sks.js +1 -1
- package/dist/cli/args.js +17 -0
- package/dist/cli/command-registry.js +16 -13
- package/dist/cli/router.js +8 -5
- package/dist/commands/doctor.js +27 -2
- package/dist/core/commands/basic-cli.js +4 -1
- package/dist/core/commands/mad-sks-command.js +36 -13
- package/dist/core/commands/naruto-command.js +4 -1
- package/dist/core/commands/pipeline-command.js +3 -4
- package/dist/core/commands/qa-loop-command.js +36 -1
- package/dist/core/commands/research-command.js +61 -1
- package/dist/core/commands/team-command.js +63 -3
- package/dist/core/decision-contract.js +28 -4
- package/dist/core/doctor/command-alias-cleanup.js +64 -0
- package/dist/core/feature-fixtures.js +2 -0
- package/dist/core/feature-registry.js +2 -2
- package/dist/core/fsx.js +1 -1
- package/dist/core/naruto/naruto-work-graph.js +4 -1
- package/dist/core/pipeline-internals/runtime-core.js +50 -4
- package/dist/core/pipeline-internals/runtime-gates.js +10 -1
- package/dist/core/proof/route-proof-gate.js +1 -1
- package/dist/core/qa-loop.js +227 -11
- package/dist/core/questions.js +239 -2
- package/dist/core/routes.js +3 -4
- package/dist/core/version.js +1 -1
- package/dist/scripts/agent-native-release-gate.js +13 -4
- package/package.json +1 -1
|
@@ -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 =
|
|
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
|
-
|
|
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
|
|
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', '
|
|
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
|
+
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
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)
|