sneakoscope 5.3.0 → 5.5.1
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 +4 -0
- 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/bin/sks.js +1 -1
- package/dist/cli/install-helpers.js +17 -0
- package/dist/config/skills-manifest.json +58 -58
- package/dist/core/agents/agent-orchestrator.js +14 -2
- package/dist/core/agents/agent-proof-evidence.js +16 -0
- package/dist/core/bench.js +24 -2
- package/dist/core/codex-app/sks-menubar.js +83 -10
- package/dist/core/commands/agent-command.js +35 -1
- package/dist/core/commands/basic-cli.js +59 -0
- package/dist/core/commands/command-utils.js +14 -4
- package/dist/core/commands/goal-command.js +3 -0
- package/dist/core/commands/image-ux-review-command.js +4 -1
- package/dist/core/commands/mad-sks-command.js +10 -1
- package/dist/core/commands/naruto-command.js +12 -7
- package/dist/core/commands/ppt-command.js +40 -1
- package/dist/core/commands/qa-loop-command.js +4 -1
- package/dist/core/commands/release-command.js +3 -3
- package/dist/core/commands/research-command.js +4 -0
- package/dist/core/commands/route-success-helpers.js +2 -0
- package/dist/core/commands/run-command.js +22 -5
- package/dist/core/commands/seo-command.js +31 -4
- package/dist/core/db-safety.js +8 -1
- package/dist/core/feature-fixture-executor.js +262 -0
- package/dist/core/feature-fixtures.js +95 -52
- package/dist/core/fsx.js +1 -1
- package/dist/core/loops/loop-decomposer.js +4 -3
- package/dist/core/loops/loop-planner.js +7 -3
- package/dist/core/loops/loop-worker-runtime.js +6 -0
- package/dist/core/pipeline-internals/runtime-gates.js +39 -3
- package/dist/core/proof/selftest-proof-fixtures.js +8 -0
- package/dist/core/providers/glm/naruto/glm-naruto-requirement-coverage.js +18 -1
- package/dist/core/providers/glm/naruto/glm-naruto-requirement-ledger.js +28 -12
- package/dist/core/questions.js +12 -10
- package/dist/core/release/release-gate-node.js +2 -0
- package/dist/core/routes.js +11 -0
- package/dist/core/stop-gate/stop-gate-check.js +4 -0
- package/dist/core/stop-gate/stop-gate-writer.js +4 -0
- package/dist/core/trust-kernel/trust-report.js +10 -1
- package/dist/core/update/update-migration-state.js +15 -1
- package/dist/core/update-check.js +44 -2
- package/dist/core/version.js +1 -1
- package/dist/core/work-order-ledger.js +60 -0
- package/dist/scripts/gate-policy-audit-check.js +2 -2
- package/dist/scripts/packlist-performance-check.js +7 -1
- package/dist/scripts/release-dag-full-coverage-check.js +1 -1
- package/dist/scripts/release-gate-existence-audit.js +1 -1
- package/dist/scripts/release-gate-planner.js +1 -1
- package/dist/scripts/release-metadata-1-19-check.js +1 -1
- package/package.json +5 -1
- package/schemas/release/release-gate-node.schema.json +1 -0
package/dist/core/bench.js
CHANGED
|
@@ -121,17 +121,28 @@ export async function runCoreBench(root = process.cwd(), { iterations = 3, tier
|
|
|
121
121
|
for (const [label, args, commandRoot] of coreCommands(benchTrustMission)) {
|
|
122
122
|
const values = [];
|
|
123
123
|
const failures = [];
|
|
124
|
+
// TRUST_VALIDATE_BENCH_COMMAND measures latency of `sks trust validate` against a
|
|
125
|
+
// mock fixture mission. A --mock `$Naruto` run can never satisfy the real agent
|
|
126
|
+
// gate, so `sks trust validate` legitimately exits 1 (report.ok === false,
|
|
127
|
+
// status: 'blocked') every time regardless of environment. This row exists to
|
|
128
|
+
// measure command latency, not to assert the mock mission's trust status, so a
|
|
129
|
+
// well-formed trust-validation report (valid JSON with the expected schema) is
|
|
130
|
+
// accepted even on a nonzero exit; only a crash/unparseable-output counts as a
|
|
131
|
+
// bench failure for this specific command.
|
|
132
|
+
const acceptNonZeroExit = label === TRUST_VALIDATE_BENCH_COMMAND;
|
|
124
133
|
for (let i = 0; i < CORE_BENCH_WARMUP_ITERATIONS; i += 1) {
|
|
125
134
|
const result = await runBenchProcess(commandRoot || root, script, args);
|
|
126
|
-
if (result.code !== 0)
|
|
135
|
+
if (result.code !== 0 && !(acceptNonZeroExit && isWellFormedTrustValidation(result.stdout))) {
|
|
127
136
|
failures.push({ phase: 'warmup', code: result.code, stderr_tail: result.stderr.slice(-400), stdout_tail: result.stdout.slice(-400) });
|
|
137
|
+
}
|
|
128
138
|
}
|
|
129
139
|
for (let i = 0; i < measuredIterations; i += 1) {
|
|
130
140
|
const t0 = performance.now();
|
|
131
141
|
const result = await runBenchProcess(commandRoot || root, script, args);
|
|
132
142
|
values.push(performance.now() - t0);
|
|
133
|
-
if (result.code !== 0)
|
|
143
|
+
if (result.code !== 0 && !(acceptNonZeroExit && isWellFormedTrustValidation(result.stdout))) {
|
|
134
144
|
failures.push({ phase: 'measure', code: result.code, stderr_tail: result.stderr.slice(-400), stdout_tail: result.stdout.slice(-400) });
|
|
145
|
+
}
|
|
135
146
|
}
|
|
136
147
|
const p95 = Math.round(percentile(values, 95));
|
|
137
148
|
rows.push({
|
|
@@ -165,8 +176,19 @@ async function runBenchProcess(root, script, args) {
|
|
|
165
176
|
env: { SKS_SKIP_NPM_FRESHNESS_CHECK: '1', SKS_DISABLE_UPDATE_CHECK: '1', CI: 'true' }
|
|
166
177
|
});
|
|
167
178
|
}
|
|
179
|
+
function isWellFormedTrustValidation(stdout) {
|
|
180
|
+
const parsed = parseJsonOutput(stdout);
|
|
181
|
+
return Boolean(parsed && parsed.schema === 'sks.trust-validation.v1' && typeof parsed.status === 'string');
|
|
182
|
+
}
|
|
168
183
|
async function ensureBenchTrustMission(root, script) {
|
|
169
184
|
const benchRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'sks-core-bench-trust-')).catch(() => root);
|
|
185
|
+
// `sks run` blocks the agent gate (and skips writing completion-proof.json) when
|
|
186
|
+
// its cwd is not a git repo (git_collaboration.status -> not_git_repo). The bench
|
|
187
|
+
// trust-mission scratch dir must be a git repo so completion-proof.json/
|
|
188
|
+
// trust-report.json/run-classification.json actually get written and
|
|
189
|
+
// hasBenchTrustArtifacts() can find a usable mission instead of falling back to
|
|
190
|
+
// a nonexistent 'bench-fixture-missing' id (which always fails trust validate).
|
|
191
|
+
await runProcess('git', ['init', '-q', '.'], { cwd: benchRoot, timeoutMs: 10_000 }).catch(() => null);
|
|
170
192
|
const beforeMissionIds = await listMissionIds(benchRoot);
|
|
171
193
|
const result = await runProcess(process.execPath, [script, 'run', 'fixture', '--mock', '--json'], {
|
|
172
194
|
cwd: benchRoot,
|
|
@@ -215,11 +215,22 @@ export async function installSksMenuBar(opts = {}) {
|
|
|
215
215
|
else {
|
|
216
216
|
if (!target.used_previous_script && await readText(paths.action_script_path, '') !== actionScript) {
|
|
217
217
|
await writeTextAtomic(paths.action_script_path, actionScript);
|
|
218
|
-
await fs.chmod(paths.action_script_path, 0o755);
|
|
219
218
|
actions.push(`wrote ${paths.action_script_path}`);
|
|
220
219
|
}
|
|
221
|
-
|
|
222
|
-
|
|
220
|
+
}
|
|
221
|
+
// The Swift app executes the action script DIRECTLY (Process.executableURL points at the
|
|
222
|
+
// script itself), so a lost executable bit breaks every menu action even when the script
|
|
223
|
+
// content is current. Re-assert the bit on every install run — including the up-to-date
|
|
224
|
+
// fast path, which previously never touched permissions and therefore could never repair
|
|
225
|
+
// a 0644 script — and surface chmod failures instead of swallowing them.
|
|
226
|
+
if (await exists(paths.action_script_path)) {
|
|
227
|
+
const previouslyExecutable = await fs.access(paths.action_script_path, fs.constants.X_OK).then(() => true).catch(() => false);
|
|
228
|
+
const chmodError = await fs.chmod(paths.action_script_path, 0o755).then(() => null).catch((err) => (err?.message ? String(err.message) : String(err)));
|
|
229
|
+
if (chmodError) {
|
|
230
|
+
warnings.push(`action_script_chmod_failed:${chmodError}`);
|
|
231
|
+
}
|
|
232
|
+
else if (!previouslyExecutable) {
|
|
233
|
+
actions.push('restored action script executable bit');
|
|
223
234
|
}
|
|
224
235
|
}
|
|
225
236
|
if (!stampMatches && !binaryStable) {
|
|
@@ -429,10 +440,24 @@ async function resolveCodexBundleId(input) {
|
|
|
429
440
|
}
|
|
430
441
|
return null;
|
|
431
442
|
}
|
|
432
|
-
async function smokeSksMenuBarAction(actionScriptPath) {
|
|
443
|
+
export async function smokeSksMenuBarAction(actionScriptPath) {
|
|
433
444
|
if (!(await exists(actionScriptPath)))
|
|
434
|
-
return { ok: false, code: null, output: null, versionDetected: false };
|
|
435
|
-
|
|
445
|
+
return { ok: false, code: null, output: null, versionDetected: false, executable: false };
|
|
446
|
+
// The Swift app runs the script directly (which requires the executable bit), so the smoke
|
|
447
|
+
// check must invoke it the same way. Running it via `/bin/zsh <script>` — as this check used
|
|
448
|
+
// to — succeeds even when +x is missing, which let doctor/status report a healthy action
|
|
449
|
+
// target while the menu bar itself was showing "action script broken".
|
|
450
|
+
const executable = await fs.access(actionScriptPath, fs.constants.X_OK).then(() => true).catch(() => false);
|
|
451
|
+
if (!executable) {
|
|
452
|
+
return {
|
|
453
|
+
ok: false,
|
|
454
|
+
code: null,
|
|
455
|
+
output: 'action script is not executable (missing +x); the menu bar app cannot run it',
|
|
456
|
+
versionDetected: false,
|
|
457
|
+
executable: false
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
const result = await runProcess(actionScriptPath, ['version'], {
|
|
436
461
|
timeoutMs: 5_000,
|
|
437
462
|
maxOutputBytes: 16 * 1024
|
|
438
463
|
}).catch((err) => ({ code: 1, stdout: '', stderr: err?.message || String(err) }));
|
|
@@ -442,7 +467,8 @@ async function smokeSksMenuBarAction(actionScriptPath) {
|
|
|
442
467
|
ok: result.code === 0 && versionDetected,
|
|
443
468
|
code: result.code,
|
|
444
469
|
output: output ? output.slice(0, 700) : null,
|
|
445
|
-
versionDetected
|
|
470
|
+
versionDetected,
|
|
471
|
+
executable: true
|
|
446
472
|
};
|
|
447
473
|
}
|
|
448
474
|
async function isCodexAppRunningByBundleId(bundleId, env = process.env) {
|
|
@@ -485,6 +511,8 @@ export async function inspectSksMenuBarStatus(opts = {}) {
|
|
|
485
511
|
blockers.push('menubar_app_missing');
|
|
486
512
|
if (installed && launchd.checked && !launchd.ok)
|
|
487
513
|
blockers.push('launchd_not_running');
|
|
514
|
+
if (installed && !actionSmoke.executable)
|
|
515
|
+
blockers.push('action_script_not_executable');
|
|
488
516
|
if (installed && !actionSmoke.ok)
|
|
489
517
|
blockers.push('action_script_smoke_failed');
|
|
490
518
|
if (installed && signature.checked && !signature.ok)
|
|
@@ -509,6 +537,7 @@ export async function inspectSksMenuBarStatus(opts = {}) {
|
|
|
509
537
|
smoke_code: actionSmoke.code,
|
|
510
538
|
smoke_output: actionSmoke.output,
|
|
511
539
|
version_detected: actionSmoke.versionDetected,
|
|
540
|
+
executable: actionSmoke.executable,
|
|
512
541
|
ok: actionSmoke.ok
|
|
513
542
|
},
|
|
514
543
|
codex_sync: codexSync,
|
|
@@ -678,6 +707,14 @@ export function actionScriptSource(input) {
|
|
|
678
707
|
return `#!/bin/zsh
|
|
679
708
|
set -e
|
|
680
709
|
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$PATH"
|
|
710
|
+
# launchd starts this app with cwd=/. sks treats an unmarked cwd as the project
|
|
711
|
+
# root fallback, and / is neither writable nor a workspace, so give every
|
|
712
|
+
# menu-bar-spawned command a sane home-directory cwd instead.
|
|
713
|
+
cd "$HOME" 2>/dev/null || true
|
|
714
|
+
# Menu-bar actions operate on global state (~/.codex, keychain, launchd), never
|
|
715
|
+
# on a project, so the per-project update-migration gate must not fire here —
|
|
716
|
+
# it would otherwise treat $HOME as a project and run a migration doctor in it.
|
|
717
|
+
export SKS_UPDATE_MIGRATION_GATE_DISABLED=1
|
|
681
718
|
NODE_BIN=${shellQuote(input.nodeBin)}
|
|
682
719
|
SKS_ENTRY=${shellQuote(input.sksEntry)}
|
|
683
720
|
|
|
@@ -770,6 +807,9 @@ export function swiftMenuSource(input) {
|
|
|
770
807
|
|
|
771
808
|
func setIconVisible(_ visible: Bool) {
|
|
772
809
|
statusItem.isVisible = visible
|
|
810
|
+
if visible {
|
|
811
|
+
reassertControlCenterVisibility()
|
|
812
|
+
}
|
|
773
813
|
}
|
|
774
814
|
|
|
775
815
|
func isCodexRunning() -> Bool {
|
|
@@ -784,6 +824,9 @@ export function swiftMenuSource(input) {
|
|
|
784
824
|
|
|
785
825
|
func setIconVisible(_ visible: Bool) {
|
|
786
826
|
statusItem.isVisible = visible
|
|
827
|
+
if visible {
|
|
828
|
+
reassertControlCenterVisibility()
|
|
829
|
+
}
|
|
787
830
|
}
|
|
788
831
|
|
|
789
832
|
func isCodexRunning() -> Bool {
|
|
@@ -799,6 +842,32 @@ let menubarConfigPath = ${swiftString(input.configPath)}
|
|
|
799
842
|
let lastActionLogPath = ${swiftString(input.lastActionLogPath)}
|
|
800
843
|
let codexBundleId: String? = ${input.codexBundleId ? swiftString(input.codexBundleId) : 'nil'}
|
|
801
844
|
let packageVersion = ${swiftString(input.packageVersion)}
|
|
845
|
+
let menuBarLabel = ${swiftString(SKS_MENUBAR_LABEL)}
|
|
846
|
+
let controlCenterDomain = ${swiftString(CONTROL_CENTER_DOMAIN)}
|
|
847
|
+
|
|
848
|
+
/// macOS persists status-item visibility hints per-label in Control Center's
|
|
849
|
+
/// defaults domain (see installSksMenuBar's seedMenuBarPreferredPosition).
|
|
850
|
+
/// Toggling NSStatusItem.isVisible back to true inside a resident process is
|
|
851
|
+
/// not always sufficient to make Control Center re-render a previously
|
|
852
|
+
/// hidden item, so re-show must reassert the same Control Center defaults
|
|
853
|
+
/// the installer seeds, or the icon can stay invisible after a Codex
|
|
854
|
+
/// quit/relaunch cycle even though isVisible is technically true again.
|
|
855
|
+
func reassertControlCenterVisibility() {
|
|
856
|
+
let defaultsBin = "/usr/bin/defaults"
|
|
857
|
+
guard FileManager.default.isExecutableFile(atPath: defaultsBin) else { return }
|
|
858
|
+
let writes: [[String]] = [
|
|
859
|
+
["write", controlCenterDomain, "NSStatusItem Visible \\(menuBarLabel)", "-bool", "true"],
|
|
860
|
+
["write", controlCenterDomain, "NSStatusItem VisibleCC \\(menuBarLabel)", "-bool", "true"]
|
|
861
|
+
]
|
|
862
|
+
for args in writes {
|
|
863
|
+
let process = Process()
|
|
864
|
+
process.executableURL = URL(fileURLWithPath: defaultsBin)
|
|
865
|
+
process.arguments = args
|
|
866
|
+
process.standardOutput = FileHandle.nullDevice
|
|
867
|
+
process.standardError = FileHandle.nullDevice
|
|
868
|
+
try? process.run()
|
|
869
|
+
}
|
|
870
|
+
}
|
|
802
871
|
|
|
803
872
|
func shellQuote(_ value: String) -> String {
|
|
804
873
|
return "'" + value.replacingOccurrences(of: "'", with: "'\\\\''") + "'"
|
|
@@ -1316,7 +1385,11 @@ async function launchWithLaunchctl(input) {
|
|
|
1316
1385
|
...stdio
|
|
1317
1386
|
}).catch((err) => ({ code: 1, stdout: '', stderr: err?.message || String(err) }));
|
|
1318
1387
|
if (kickstart.code !== 0) {
|
|
1319
|
-
|
|
1388
|
+
// A kickstart timeout does not mean the relaunch failed — under heavy
|
|
1389
|
+
// system load (e.g. mid `npm install -g`) the app can take well past the
|
|
1390
|
+
// kickstart timeout to reach running state, so give the recheck a much
|
|
1391
|
+
// longer window before declaring the whole install blocked.
|
|
1392
|
+
const printedWork = waitForLaunchctlRunning(input.launchctl, service, 20);
|
|
1320
1393
|
const printed = input.quiet === true ? await printedWork : await withHeartbeat('launchctl SKS menu bar wait', printedWork, { warnAfterMs: 10_000 });
|
|
1321
1394
|
if (printed.running) {
|
|
1322
1395
|
return {
|
|
@@ -1364,9 +1437,9 @@ async function launchWithLaunchctl(input) {
|
|
|
1364
1437
|
error: String(bootstrap.stderr || bootstrap.stdout || '').trim() || 'launchctl_bootstrap_failed'
|
|
1365
1438
|
};
|
|
1366
1439
|
}
|
|
1367
|
-
async function waitForLaunchctlRunning(launchctl, service) {
|
|
1440
|
+
async function waitForLaunchctlRunning(launchctl, service, attempts = 6) {
|
|
1368
1441
|
let lastCode = null;
|
|
1369
|
-
for (let attempt = 0; attempt <
|
|
1442
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
1370
1443
|
const printed = await runProcess(launchctl, ['print', service], {
|
|
1371
1444
|
timeoutMs: 1_000,
|
|
1372
1445
|
maxOutputBytes: 32 * 1024
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { findLatestMission, loadMission } from '../mission.js';
|
|
2
|
+
import { findLatestMission, loadMission, missionDir } from '../mission.js';
|
|
3
3
|
import { readJson, readText, sksRoot, writeJsonAtomic } from '../fsx.js';
|
|
4
4
|
import { runNativeAgentOrchestrator } from '../agents/agent-orchestrator.js';
|
|
5
5
|
import { parseAgentCommandArgs } from '../agents/agent-command-surface.js';
|
|
@@ -23,6 +23,9 @@ export async function agentCommand(commandOrArgs = 'agent', maybeArgs = []) {
|
|
|
23
23
|
}
|
|
24
24
|
async function agentRun(parsed) {
|
|
25
25
|
const result = await runNativeAgentOrchestrator({ ...parsed, routeCommand: 'sks agent run', routeBlackboxKind: 'actual_agent_command' });
|
|
26
|
+
if (normalizeRouteName(parsed.route) === 'release-review' && result.mission_id) {
|
|
27
|
+
await writeReleaseReviewNativeAgentPlan(parsed, result);
|
|
28
|
+
}
|
|
26
29
|
return emit(parsed, result, () => {
|
|
27
30
|
console.log('Native agent mission: ' + result.mission_id);
|
|
28
31
|
console.log('Backend: ' + result.backend);
|
|
@@ -30,6 +33,37 @@ async function agentRun(parsed) {
|
|
|
30
33
|
console.log('Proof: ' + result.proof.status);
|
|
31
34
|
});
|
|
32
35
|
}
|
|
36
|
+
function normalizeRouteName(route = '') {
|
|
37
|
+
return String(route || '').replace(/^\$/, '').trim().toLowerCase();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* $Release-Review runs through the generic native agent orchestrator (sks agent run
|
|
41
|
+
* --route "$Release-Review"), which does not itself know it is a release-review run.
|
|
42
|
+
* Write the route-specific release-review-native-agent-plan.json summary artifact here
|
|
43
|
+
* so `route-release-review`'s fixture contract (plan file + agent proof evidence +
|
|
44
|
+
* agent effort policy) is genuinely satisfied by this command rather than only partially.
|
|
45
|
+
*/
|
|
46
|
+
async function writeReleaseReviewNativeAgentPlan(parsed, result) {
|
|
47
|
+
const root = await sksRoot();
|
|
48
|
+
const dir = missionDir(root, result.mission_id);
|
|
49
|
+
const plan = {
|
|
50
|
+
schema: 'sks.release-review-native-agent-plan.v1',
|
|
51
|
+
ok: Boolean(result.ok),
|
|
52
|
+
mission_id: result.mission_id,
|
|
53
|
+
route: '$Release-Review',
|
|
54
|
+
route_command: 'sks agent run',
|
|
55
|
+
backend: result.backend,
|
|
56
|
+
prompt: parsed.prompt,
|
|
57
|
+
roster: {
|
|
58
|
+
agent_count: result.roster?.agent_count ?? parsed.agents ?? null,
|
|
59
|
+
concurrency: result.roster?.concurrency ?? parsed.concurrency ?? null
|
|
60
|
+
},
|
|
61
|
+
proof_status: result.proof?.status || null,
|
|
62
|
+
agent_proof_evidence: 'agents/agent-proof-evidence.json',
|
|
63
|
+
agent_effort_policy: 'agents/agent-effort-policy.json'
|
|
64
|
+
};
|
|
65
|
+
await writeJsonAtomic(path.join(dir, 'release-review-native-agent-plan.json'), plan);
|
|
66
|
+
}
|
|
33
67
|
async function agentPlan(parsed) {
|
|
34
68
|
const root = await sksRoot();
|
|
35
69
|
const roster = buildAgentRoster({ agents: parsed.agents, concurrency: parsed.concurrency, prompt: parsed.prompt, readonly: parsed.readonly });
|
|
@@ -8,6 +8,7 @@ import { PACKAGE_VERSION, ensureDir, exists, nowIso, projectRoot, readJson, sksR
|
|
|
8
8
|
import { COMMAND_CATALOG, DOLLAR_COMMAND_ALIASES, DOLLAR_COMMANDS, USAGE_TOPICS, routePrompt, routeReasoning, reasoningInstruction } from '../routes.js';
|
|
9
9
|
import { initProject, normalizeInstallScope, sksCommandPrefix } from '../init.js';
|
|
10
10
|
import { buildFeatureRegistry, validateFeatureRegistry } from '../feature-registry.js';
|
|
11
|
+
import { runFeatureFixture } from '../feature-fixture-executor.js';
|
|
11
12
|
import { hooksExplainReport } from '../../cli/feature-commands.js';
|
|
12
13
|
import { writeSelftestRouteProof } from '../proof/selftest-proof-fixtures.js';
|
|
13
14
|
import { createMission } from '../mission.js';
|
|
@@ -279,6 +280,8 @@ export async function postinstallCommand(args = []) {
|
|
|
279
280
|
return postinstall({ bootstrap: bootstrapCommand, args });
|
|
280
281
|
}
|
|
281
282
|
export async function selftestCommand(args = []) {
|
|
283
|
+
if (flag(args, '--real'))
|
|
284
|
+
return selftestRealCommand(args);
|
|
282
285
|
process.env.CI = 'true';
|
|
283
286
|
const root = await projectRoot();
|
|
284
287
|
const tmp = tmpdir('sks-selftest-');
|
|
@@ -307,6 +310,62 @@ export async function selftestCommand(args = []) {
|
|
|
307
310
|
return printJson(result);
|
|
308
311
|
console.log('SKS selftest passed');
|
|
309
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* `sks selftest --real`: actually spawns every feature fixture whose kind is
|
|
315
|
+
* 'execute' or 'execute_and_validate_artifacts', derives real pass/fail status
|
|
316
|
+
* from the process exit code (and, for execute_and_validate_artifacts, from
|
|
317
|
+
* whether the declared expected_artifacts actually exist and match their
|
|
318
|
+
* declared schema), and writes a JSON report that explicitly lists fixtures
|
|
319
|
+
* skipped because they are mock/wiring_only kind rather than silently omitting
|
|
320
|
+
* them. This is additive: plain `sks selftest --mock` behavior is unchanged.
|
|
321
|
+
*/
|
|
322
|
+
export async function selftestRealCommand(args = []) {
|
|
323
|
+
process.env.CI = 'true';
|
|
324
|
+
const root = await projectRoot();
|
|
325
|
+
const registry = await buildFeatureRegistry({ root });
|
|
326
|
+
const executableKinds = new Set(['execute', 'execute_and_validate_artifacts']);
|
|
327
|
+
const results = [];
|
|
328
|
+
const skippedWiringOnly = [];
|
|
329
|
+
for (const feature of registry.features || []) {
|
|
330
|
+
const fx = feature.fixture || {};
|
|
331
|
+
if (executableKinds.has(fx.kind)) {
|
|
332
|
+
const run = await runFeatureFixture(feature, { root });
|
|
333
|
+
results.push(run);
|
|
334
|
+
}
|
|
335
|
+
else if (fx.kind === 'mock' || fx.kind === 'wiring_only' || fx.quality === 'wiring_only') {
|
|
336
|
+
skippedWiringOnly.push({ id: feature.id, kind: fx.kind, reason: fx.reason || 'mock_or_wiring_only_kind_not_executed' });
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
const failures = results.filter((row) => !row.ok);
|
|
340
|
+
const ok = failures.length === 0;
|
|
341
|
+
const report = {
|
|
342
|
+
schema: 'sks.selftest-real.v1',
|
|
343
|
+
ok,
|
|
344
|
+
version: PACKAGE_VERSION,
|
|
345
|
+
generated_at: nowIso(),
|
|
346
|
+
root,
|
|
347
|
+
checked: results.length,
|
|
348
|
+
passed: results.filter((row) => row.ok).length,
|
|
349
|
+
failed: failures.length,
|
|
350
|
+
results,
|
|
351
|
+
skipped_wiring_only: skippedWiringOnly,
|
|
352
|
+
skipped_wiring_only_count: skippedWiringOnly.length,
|
|
353
|
+
blockers: failures.flatMap((row) => row.blockers || [])
|
|
354
|
+
};
|
|
355
|
+
const reportPath = path.join(root, '.sneakoscope', 'reports', 'selftest-real-report.json');
|
|
356
|
+
await writeJsonAtomic(reportPath, report);
|
|
357
|
+
const output = { ...report, report_file: path.relative(root, reportPath) };
|
|
358
|
+
if (flag(args, '--json'))
|
|
359
|
+
return printJson(output);
|
|
360
|
+
console.log(`SKS selftest --real: ${ok ? 'passed' : 'blocked'} (checked=${results.length}, skipped_wiring_only=${skippedWiringOnly.length})`);
|
|
361
|
+
console.log(`Report: ${path.relative(root, reportPath)}`);
|
|
362
|
+
if (!ok) {
|
|
363
|
+
for (const blocker of report.blockers)
|
|
364
|
+
console.log(`- ${blocker}`);
|
|
365
|
+
process.exitCode = 1;
|
|
366
|
+
}
|
|
367
|
+
return output;
|
|
368
|
+
}
|
|
310
369
|
export async function reasoningCommand(args = []) {
|
|
311
370
|
const prompt = args.filter((arg) => !String(arg).startsWith('--')).join(' ').trim();
|
|
312
371
|
const route = routePrompt(prompt || '$SKS');
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import { findLatestMission, listSessionStates } from '../mission.js';
|
|
2
2
|
import { DOLLAR_SKILL_NAMES, RECOMMENDED_SKILLS } from '../routes.js';
|
|
3
3
|
export const flag = (args = [], name) => args.includes(name);
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
// Blindly dropping every "--"-prefixed argv token (the previous behavior of
|
|
5
|
+
// promptOf/positionalArgs) silently deletes any part of an unquoted work-order
|
|
6
|
+
// prompt that happens to contain a flag-lookalike phrase (e.g. "--files 로
|
|
7
|
+
// 확인해라"). Only strip tokens that are actually recognized boolean/global
|
|
8
|
+
// flags; anything else stays in the reconstructed prompt.
|
|
9
|
+
const KNOWN_BOOLEAN_FLAGS = new Set([
|
|
10
|
+
'--json', '--mock', '--execute', '--auto', '--visual', '--research', '--db',
|
|
11
|
+
'--legacy-goal-runtime', '--help', '-h'
|
|
12
|
+
]);
|
|
13
|
+
export function promptOf(args = [], knownFlags = KNOWN_BOOLEAN_FLAGS) {
|
|
14
|
+
return args.filter((x) => !knownFlags.has(String(x))).join(' ').trim();
|
|
6
15
|
}
|
|
7
16
|
export async function resolveMissionId(root, arg) {
|
|
8
17
|
if (arg && arg !== 'latest')
|
|
@@ -56,8 +65,9 @@ export function positionalArgs(args = []) {
|
|
|
56
65
|
i += 1;
|
|
57
66
|
continue;
|
|
58
67
|
}
|
|
59
|
-
if (
|
|
60
|
-
|
|
68
|
+
if (KNOWN_BOOLEAN_FLAGS.has(arg))
|
|
69
|
+
continue;
|
|
70
|
+
out.push(arg);
|
|
61
71
|
}
|
|
62
72
|
return out;
|
|
63
73
|
}
|
|
@@ -2,6 +2,7 @@ import path from 'node:path';
|
|
|
2
2
|
import { exists, readJson, sksRoot } from '../fsx.js';
|
|
3
3
|
import { initProject } from '../init.js';
|
|
4
4
|
import { createMission, loadMission, setCurrent, stateFile } from '../mission.js';
|
|
5
|
+
import { createAndWriteWorkOrderLedgerForPrompt, closeWorkOrderLedgerForRouteResult } from '../work-order-ledger.js';
|
|
5
6
|
import { GOAL_BRIDGE_ARTIFACT, GOAL_WORKFLOW_ARTIFACT, updateGoalWorkflow, writeGoalWorkflow } from '../goal-workflow.js';
|
|
6
7
|
import { flag, promptOf, resolveMissionId } from './command-utils.js';
|
|
7
8
|
import { compileGoalToLoopPlan } from '../loops/goal-to-loop-compat.js';
|
|
@@ -37,11 +38,13 @@ async function goalCreate(args) {
|
|
|
37
38
|
if (flag(args, '--legacy-goal-runtime') || process.env.SKS_LEGACY_GOAL_RUNTIME === '1')
|
|
38
39
|
return legacyGoalCreate(root, prompt, args);
|
|
39
40
|
const { id, dir, mission } = await createMission(root, { mode: 'goal', prompt });
|
|
41
|
+
await createAndWriteWorkOrderLedgerForPrompt(dir, { missionId: id, route: 'Goal', prompt });
|
|
40
42
|
const workflow = await writeGoalWorkflow(dir, mission, { action: 'create', prompt });
|
|
41
43
|
const plan = await compileGoalToLoopPlan({ root, missionId: id, goalText: prompt, legacyGoalOptions: { native_goal: workflow.native_goal } });
|
|
42
44
|
const result = await runLoopPlan({ root, plan, parallelism: 'balanced' });
|
|
43
45
|
const gate = await evaluateLocalGate({ root, missionId: id, gateFile: 'loop-graph-proof.json' });
|
|
44
46
|
const ok = result.ok === true && gate.ok === true;
|
|
47
|
+
await closeWorkOrderLedgerForRouteResult(dir, { ok, blockers: gate.blockers || [] });
|
|
45
48
|
await setCurrent(root, { mission_id: id, mode: 'GOAL', route: 'Goal', route_command: '$Goal', phase: ok ? 'GOAL_LOOP_COMPLETED' : 'GOAL_LOOP_BLOCKED', questions_allowed: true, implementation_allowed: true, native_goal: workflow.native_goal, stop_gate: 'loop-graph-proof.json', stop_gate_blockers: gate.blockers }, { replace: true });
|
|
46
49
|
if (!ok)
|
|
47
50
|
process.exitCode = 1;
|
|
@@ -13,6 +13,7 @@ import { sha256File, imageDimensions } from '../wiki-image/image-hash.js';
|
|
|
13
13
|
import { writeRouteCollaborationArtifacts } from '../agents/route-collaboration-ledger.js';
|
|
14
14
|
import { codexChromeExtensionStatus } from '../codex-app.js';
|
|
15
15
|
import { requireCodexImagegen } from '../imagegen/require-imagegen.js';
|
|
16
|
+
import { evaluateGate } from '../stop-gate/gate-evaluator.js';
|
|
16
17
|
const ONE_BY_ONE_PNG_BASE64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMB/axX7V8AAAAASUVORK5CYII=';
|
|
17
18
|
const IMAGE_UX_REVIEW_ARTIFACT_PATHS = {
|
|
18
19
|
policy: IMAGE_UX_REVIEW_POLICY_ARTIFACT,
|
|
@@ -358,9 +359,11 @@ async function statusImageUxReview(root, args = []) {
|
|
|
358
359
|
const gate = await readJson(path.join(dir, 'image-ux-review-gate.json'), null);
|
|
359
360
|
const issueLedger = await readJson(path.join(dir, IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT), null);
|
|
360
361
|
const generatedLedger = await readJson(path.join(dir, IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT), null);
|
|
361
|
-
const
|
|
362
|
+
const gateVerdict = await evaluateGate(root, missionId, 'image-ux-review-gate.json');
|
|
363
|
+
const result = { schema: 'sks.image-ux-review-status.v2', ok: true, mission_id: missionId, gate, gate_verdict: gateVerdict, issue_ledger: issueLedger, generated_review_ledger: generatedLedger };
|
|
362
364
|
if (flag(args, '--json'))
|
|
363
365
|
return printJson(result);
|
|
366
|
+
console.log(gateVerdict.verdict);
|
|
364
367
|
console.log(`Image UX Review mission: ${missionId}`);
|
|
365
368
|
console.log(`Gate: ${gate?.status || (gate?.passed ? 'passed' : gate ? 'present' : 'missing')}`);
|
|
366
369
|
if (gate?.verified_level)
|
|
@@ -26,6 +26,7 @@ import { repairZellijForSks } from '../zellij/zellij-self-heal.js';
|
|
|
26
26
|
import { buildMadGlmLaunchArtifact, buildMadGlmLaunchProfileNoWrite, resolveMadGlmLaunchKey, writeMadGlmCodexWrapper } from '../providers/glm/glm-mad-launch.js';
|
|
27
27
|
import { GLM_MAD_MODE } from '../providers/glm/glm-52-settings.js';
|
|
28
28
|
import { assertNonGlmMadRoute } from '../routes/model-mode-router.js';
|
|
29
|
+
import { evaluateGate } from '../stop-gate/gate-evaluator.js';
|
|
29
30
|
const MAD_SKS_DEFAULT_TTL_MS = 10 * 60 * 1000;
|
|
30
31
|
export async function madHighCommand(args = [], deps = {}) {
|
|
31
32
|
const subcommand = firstSubcommand(args);
|
|
@@ -1013,6 +1014,12 @@ async function madSksSubcommand(subcommand, args = []) {
|
|
|
1013
1014
|
if (subcommand === 'doctor' || subcommand === 'status') {
|
|
1014
1015
|
const protectedCore = resolveProtectedCore({ packageRoot: packageRoot(), targetRoot });
|
|
1015
1016
|
const before = await snapshotProtectedCore(packageRoot(), 'status');
|
|
1017
|
+
const statusMissionId = await findLatestMission(root);
|
|
1018
|
+
const gateVerdict = statusMissionId
|
|
1019
|
+
? await evaluateGate(root, statusMissionId, 'mad-sks-gate.json')
|
|
1020
|
+
: await evaluateGate(root, 'no-mission', 'mad-sks-gate.json');
|
|
1021
|
+
if (!json)
|
|
1022
|
+
console.log(gateVerdict.verdict);
|
|
1016
1023
|
return emit({
|
|
1017
1024
|
schema: subcommand === 'doctor' ? 'sks.mad-sks-doctor.v1' : 'sks.mad-sks-status.v1',
|
|
1018
1025
|
ok: true,
|
|
@@ -1022,7 +1029,9 @@ async function madSksSubcommand(subcommand, args = []) {
|
|
|
1022
1029
|
protected_core_snapshot: before,
|
|
1023
1030
|
protected_core_immutable: !protectedCore.engine_source_exception,
|
|
1024
1031
|
protected_core_write_allowed: protectedCore.engine_source_exception,
|
|
1025
|
-
permission_active: false
|
|
1032
|
+
permission_active: false,
|
|
1033
|
+
mission_id: statusMissionId,
|
|
1034
|
+
gate_verdict: gateVerdict
|
|
1026
1035
|
}, json);
|
|
1027
1036
|
}
|
|
1028
1037
|
if (subcommand === 'close' || subcommand === 'revoke') {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { ui as cliUi } from '../../cli/cli-theme.js';
|
|
3
3
|
import { createMission, findLatestMission, loadMission, setCurrent } from '../mission.js';
|
|
4
|
+
import { createAndWriteWorkOrderLedgerForPrompt, closeWorkOrderLedgerForRouteResult } from '../work-order-ledger.js';
|
|
4
5
|
import { nowIso, readJson, sksRoot, writeJsonAtomic } from '../fsx.js';
|
|
5
6
|
import { runNativeAgentOrchestrator } from '../agents/agent-orchestrator.js';
|
|
6
7
|
import { classifyOllamaWorkerSlice } from '../agents/agent-runner-ollama.js';
|
|
@@ -95,6 +96,7 @@ async function narutoRun(parsed) {
|
|
|
95
96
|
maxAgentCount: MAX_NARUTO_AGENT_COUNT
|
|
96
97
|
});
|
|
97
98
|
const mission = await createMission(root, { mode: 'naruto', prompt: parsed.prompt });
|
|
99
|
+
await createAndWriteWorkOrderLedgerForPrompt(mission.dir, { missionId: mission.id, route: 'Naruto', prompt: parsed.prompt });
|
|
98
100
|
await writeCodex0138CapabilityArtifacts(root, { missionId: mission.id }).catch(() => null);
|
|
99
101
|
await writeCodex0139CapabilityArtifacts(root, { missionId: mission.id }).catch(() => null);
|
|
100
102
|
const gitWorktreeCapability = writeCapable
|
|
@@ -280,13 +282,13 @@ async function narutoRun(parsed) {
|
|
|
280
282
|
rebalance_ready: rebalancePolicy.ok === true,
|
|
281
283
|
concurrency_governor_ready: true,
|
|
282
284
|
active_pool_simulated: activePool.ok === true,
|
|
283
|
-
verification_dag_ready:
|
|
284
|
-
gpt_final_pack_ready:
|
|
285
|
+
verification_dag_ready: Array.isArray(verificationDag?.tasks),
|
|
286
|
+
gpt_final_pack_ready: Boolean(gptFinalPack?.schema),
|
|
285
287
|
zellij_dashboard_ready: zellijDashboard.ok === true,
|
|
286
288
|
native_agent_proof: false,
|
|
287
289
|
final_arbiter_accepted: false,
|
|
288
290
|
session_cleanup: false,
|
|
289
|
-
blockers: [],
|
|
291
|
+
blockers: [...(workGraph.blockers || []), ...(allocationPolicy.blockers || [])],
|
|
290
292
|
updated_at: nowIso()
|
|
291
293
|
});
|
|
292
294
|
await setCurrent(root, {
|
|
@@ -417,9 +419,11 @@ async function narutoRun(parsed) {
|
|
|
417
419
|
const regressionProof = summarizeRegressionProof(workGraph, result);
|
|
418
420
|
await writeJsonAtomic(path.join(mission.dir, 'regression-proof-summary.json'), regressionProof);
|
|
419
421
|
const tddOk = !regressionProof.required || (regressionProof.regression_test_added && regressionProof.regression_test_failed_before_fix && regressionProof.regression_test_passed_after_fix);
|
|
422
|
+
const narutoGateFullPassed = result.ok === true && nativeProofOk && finalAccepted && parallelRuntimeOk && tddOk && workGraph.ok === true && allocationPolicy.ok === true;
|
|
423
|
+
const narutoGateFullBlockers = [...(result.proof?.blockers || []), ...(parallelRuntimeOk ? [] : ['naruto_parallel_runtime_proof_below_gate']), ...(tddOk ? [] : ['tdd_evidence_missing']), ...(workGraph.blockers || []), ...(allocationPolicy.blockers || [])];
|
|
420
424
|
await writeJsonAtomic(path.join(mission.dir, 'naruto-gate.json'), {
|
|
421
425
|
schema: 'sks.naruto-gate.v1',
|
|
422
|
-
passed:
|
|
426
|
+
passed: narutoGateFullPassed,
|
|
423
427
|
mission_id: mission.id,
|
|
424
428
|
clone_roster_built: true,
|
|
425
429
|
clone_count: roster.agent_count,
|
|
@@ -429,8 +433,8 @@ async function narutoRun(parsed) {
|
|
|
429
433
|
rebalance_ready: rebalancePolicy.ok === true,
|
|
430
434
|
concurrency_governor_ready: true,
|
|
431
435
|
active_pool_simulated: activePool.ok === true,
|
|
432
|
-
verification_dag_ready:
|
|
433
|
-
gpt_final_pack_ready:
|
|
436
|
+
verification_dag_ready: Array.isArray(verificationDag?.tasks),
|
|
437
|
+
gpt_final_pack_ready: Boolean(gptFinalPack?.schema),
|
|
434
438
|
zellij_dashboard_ready: zellijDashboard.ok === true,
|
|
435
439
|
native_agent_proof: nativeProofOk,
|
|
436
440
|
parallel_runtime_proof: parallelRuntimeOk,
|
|
@@ -440,9 +444,10 @@ async function narutoRun(parsed) {
|
|
|
440
444
|
regression_proof: 'regression-proof-summary.json',
|
|
441
445
|
final_arbiter_accepted: finalAccepted,
|
|
442
446
|
session_cleanup: result.proof?.all_sessions_closed === true || nativeProofOk,
|
|
443
|
-
blockers:
|
|
447
|
+
blockers: narutoGateFullBlockers,
|
|
444
448
|
updated_at: nowIso()
|
|
445
449
|
});
|
|
450
|
+
await closeWorkOrderLedgerForRouteResult(mission.dir, { ok: narutoGateFullPassed, blockers: narutoGateFullBlockers });
|
|
446
451
|
const clones = result.roster?.agent_count ?? roster.agent_count;
|
|
447
452
|
const localWorkerSummary = summarizeNarutoLocalWorkerResult(localWorker, result);
|
|
448
453
|
// Finalizer policy: when local LLM workers contributed patches, the GPT
|
|
@@ -337,6 +337,45 @@ function mockPptFixtureGate(gate = {}) {
|
|
|
337
337
|
blockers: ['ppt_fixture_mode_cannot_claim_real']
|
|
338
338
|
};
|
|
339
339
|
}
|
|
340
|
+
// The image-asset-ledger's `assets` array (see buildPptImageAssetLedger in ../ppt.ts) is the
|
|
341
|
+
// authoritative list of raster/bitmap image assets planned or generated for the deck; every
|
|
342
|
+
// entry in it is a raster PNG produced (or pending) via Codex App $imagegen/gpt-image-2. When the
|
|
343
|
+
// ledger omits `imagegen_evidence` outright, we must NOT assume the imagegen-required policy was
|
|
344
|
+
// satisfied. Instead derive a safe default from that asset list: any raster asset present forces
|
|
345
|
+
// the derived evidence to fail-closed (required + not passed) rather than silently pass.
|
|
346
|
+
function deriveImagegenEvidenceDefault(imageAssetLedger) {
|
|
347
|
+
if (imageAssetLedger?.imagegen_evidence)
|
|
348
|
+
return imageAssetLedger.imagegen_evidence;
|
|
349
|
+
const rasterAssets = Array.isArray(imageAssetLedger?.assets) ? imageAssetLedger.assets : [];
|
|
350
|
+
const rasterAssetCount = rasterAssets.length;
|
|
351
|
+
if (rasterAssetCount === 0) {
|
|
352
|
+
return {
|
|
353
|
+
schema: 'sks.ppt-imagegen-evidence.v1',
|
|
354
|
+
required: false,
|
|
355
|
+
passed: true,
|
|
356
|
+
blockers: [],
|
|
357
|
+
derived: true,
|
|
358
|
+
derivation_basis: { raster_asset_count: 0, source: 'derived_default_no_raster_assets' }
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
return {
|
|
362
|
+
schema: 'sks.ppt-imagegen-evidence.v1',
|
|
363
|
+
required: true,
|
|
364
|
+
passed: false,
|
|
365
|
+
required_count: imageAssetLedger?.required_count || rasterAssetCount,
|
|
366
|
+
generated_count: imageAssetLedger?.generated_count || 0,
|
|
367
|
+
generated_image_evidence: false,
|
|
368
|
+
assets: [],
|
|
369
|
+
blockers: ['imagegen_evidence_missing'],
|
|
370
|
+
derived: true,
|
|
371
|
+
derivation_basis: {
|
|
372
|
+
raster_asset_count: rasterAssetCount,
|
|
373
|
+
source: 'derived_default_raster_assets_present',
|
|
374
|
+
note: `imagegen_evidence section was absent from the image-asset-ledger while ${rasterAssetCount} raster asset(s) were present; failing closed instead of silently passing.`
|
|
375
|
+
},
|
|
376
|
+
passed_note: 'imagegen_evidence_missing: ledger omitted imagegen_evidence despite raster assets requiring Codex App imagegen verification'
|
|
377
|
+
};
|
|
378
|
+
}
|
|
340
379
|
export async function evaluatePptGateArtifacts(dir, baseGate = {}) {
|
|
341
380
|
const factLedger = await readJson(path.join(dir, PPT_FACT_LEDGER_ARTIFACT), null);
|
|
342
381
|
const imageAssetLedger = await readJson(path.join(dir, PPT_IMAGE_ASSET_LEDGER_ARTIFACT), null);
|
|
@@ -370,7 +409,7 @@ export async function evaluatePptGateArtifacts(dir, baseGate = {}) {
|
|
|
370
409
|
const renderReportPassed = renderReport?.passed === true;
|
|
371
410
|
const factLedgerPassed = factLedger?.passed === true && Number(factLedger.unsupported_critical_claims_count || 0) === 0;
|
|
372
411
|
const imageAssetLedgerPassed = imageAssetLedger?.passed === true;
|
|
373
|
-
const imagegenEvidence = imageAssetLedger
|
|
412
|
+
const imagegenEvidence = deriveImagegenEvidenceDefault(imageAssetLedger);
|
|
374
413
|
const imagegenEvidencePassed = imagegenEvidence?.required === true ? imagegenEvidence?.passed === true : true;
|
|
375
414
|
const reviewLedgerPassed = reviewLedger?.passed === true;
|
|
376
415
|
const iterationReportPassed = iterationReport?.passed === true;
|
|
@@ -14,6 +14,7 @@ import { maybeFinalizeRoute } from '../proof/auto-finalize.js';
|
|
|
14
14
|
import { runNativeAgentOrchestrator } from '../agents/agent-orchestrator.js';
|
|
15
15
|
import { flag, promptOf, readBoundedIntegerFlag, readFlagValue, readMaxCycles, resolveMissionId, safeReadTextFile } from './command-utils.js';
|
|
16
16
|
import { runCodexAppHandoff, qaLoopShouldRequestAppHandoff } from '../codex-app/codex-app-handoff.js';
|
|
17
|
+
import { evaluateGate } from '../stop-gate/gate-evaluator.js';
|
|
17
18
|
import { writeCodex0138CapabilityArtifacts } from '../codex-control/codex-0138-capability.js';
|
|
18
19
|
import { writeCodexAccountUsageArtifacts } from '../usage/codex-account-usage.js';
|
|
19
20
|
import { buildQaLoopBudgetPolicy, selectQaLoopEscalatedEffort } from '../qa-loop/qa-loop-budget-policy.js';
|
|
@@ -441,8 +442,10 @@ async function qaLoopStatus(args) {
|
|
|
441
442
|
const desktop = await readJson(path.join(dir, 'qa-loop', 'app-handoff.json'), null);
|
|
442
443
|
const desktopConfirmation = await readJson(path.join(dir, 'qa-loop', 'app-handoff-confirmation.json'), null);
|
|
443
444
|
const desktopReviewComplete = desktopConfirmation?.verdict === 'pass';
|
|
445
|
+
const gateVerdict = await evaluateGate(root, id, 'qa-gate.json');
|
|
444
446
|
if (flag(args, '--json'))
|
|
445
|
-
return console.log(JSON.stringify({ mission, state, qa: status, desktop_app_handoff: desktop, desktop_app_confirmation: desktopConfirmation, desktop_review_complete: desktopReviewComplete, native_agent_plan: nativeAgentPlan, agent_sessions: agentSessions?.sessions || null }, null, 2));
|
|
447
|
+
return console.log(JSON.stringify({ mission, state, qa: status, desktop_app_handoff: desktop, desktop_app_confirmation: desktopConfirmation, desktop_review_complete: desktopReviewComplete, native_agent_plan: nativeAgentPlan, agent_sessions: agentSessions?.sessions || null, gate_verdict: gateVerdict }, null, 2));
|
|
448
|
+
console.log(gateVerdict.verdict);
|
|
446
449
|
console.log('SKS QA-LOOP Status\n');
|
|
447
450
|
console.log(`Mission: ${id}`);
|
|
448
451
|
console.log(`Phase: ${state.phase || mission.phase}`);
|
|
@@ -27,7 +27,7 @@ export async function releaseCommand(args = []) {
|
|
|
27
27
|
await writeTextAtomic(stdoutPath, String(result.stdout || ''));
|
|
28
28
|
await writeTextAtomic(stderrPath, String(result.stderr || ''));
|
|
29
29
|
const readiness = await findReleaseReadinessReport(root);
|
|
30
|
-
const requiredSections = [
|
|
30
|
+
const requiredSections = [];
|
|
31
31
|
const missingSections = requiredSections.filter((section) => readiness.report?.[section] == null);
|
|
32
32
|
if (readiness.report)
|
|
33
33
|
await writeJsonAtomic(path.join(mission.dir, 'release-readiness-report.json'), readiness.report);
|
|
@@ -53,14 +53,14 @@ export async function releaseCommand(args = []) {
|
|
|
53
53
|
stdout_tail: tail(String(result.stdout || '')),
|
|
54
54
|
stderr_tail: tail(String(result.stderr || ''))
|
|
55
55
|
};
|
|
56
|
+
if (!report.ok)
|
|
57
|
+
process.exitCode = result.status || 1;
|
|
56
58
|
if (json)
|
|
57
59
|
return printJson(report);
|
|
58
60
|
if (result.stdout)
|
|
59
61
|
process.stdout.write(result.stdout);
|
|
60
62
|
if (result.stderr)
|
|
61
63
|
process.stderr.write(result.stderr);
|
|
62
|
-
if (!report.ok)
|
|
63
|
-
process.exitCode = result.status || 1;
|
|
64
64
|
return report;
|
|
65
65
|
}
|
|
66
66
|
async function findReleaseReadinessReport(root) {
|