sneakoscope 0.9.13 → 0.9.15
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 +32 -39
- package/crates/sks-core/Cargo.lock +99 -1
- package/crates/sks-core/Cargo.toml +2 -1
- package/crates/sks-core/src/main.rs +77 -43
- package/package.json +8 -5
- package/src/cli/command-registry.mjs +73 -114
- package/src/cli/feature-commands.mjs +44 -5
- package/src/cli/install-helpers.mjs +2 -2
- package/src/cli/router.mjs +2 -3
- package/src/commands/aliases.mjs +2 -0
- package/src/commands/auto-review.mjs +5 -0
- package/src/commands/autoresearch.mjs +5 -0
- package/src/commands/bootstrap.mjs +2 -0
- package/src/commands/code-structure.mjs +5 -0
- package/src/commands/codex-lb.mjs +61 -3
- package/src/commands/commands.mjs +5 -0
- package/src/commands/commit-and-push.mjs +5 -0
- package/src/commands/commit.mjs +5 -0
- package/src/commands/computer-use.mjs +31 -0
- package/src/commands/conflicts.mjs +13 -0
- package/src/commands/context7.mjs +5 -0
- package/src/commands/db.mjs +1 -1
- package/src/commands/deps.mjs +5 -0
- package/src/commands/dfix.mjs +2 -0
- package/src/commands/doctor.mjs +2 -2
- package/src/commands/dollar-commands.mjs +2 -0
- package/src/commands/eval.mjs +5 -0
- package/src/commands/fix-path.mjs +2 -0
- package/src/commands/gc.mjs +2 -0
- package/src/commands/goal.mjs +5 -0
- package/src/commands/guard.mjs +10 -0
- package/src/commands/gx.mjs +5 -0
- package/src/commands/harness.mjs +5 -0
- package/src/commands/help.mjs +3 -74
- package/src/commands/hook.mjs +8 -0
- package/src/commands/hproof.mjs +5 -0
- package/src/commands/image-ux-review.mjs +38 -0
- package/src/commands/init.mjs +2 -0
- package/src/commands/mad-sks.mjs +14 -0
- package/src/commands/memory.mjs +5 -0
- package/src/commands/openclaw.mjs +2 -0
- package/src/commands/perf.mjs +2 -2
- package/src/commands/pipeline.mjs +25 -0
- package/src/commands/postinstall.mjs +2 -0
- package/src/commands/ppt.mjs +44 -0
- package/src/commands/profile.mjs +5 -0
- package/src/commands/proof-field.mjs +5 -0
- package/src/commands/proof.mjs +22 -1
- package/src/commands/qa-loop.mjs +5 -0
- package/src/commands/quickstart.mjs +2 -0
- package/src/commands/reasoning.mjs +2 -0
- package/src/commands/recallpulse.mjs +5 -0
- package/src/commands/research.mjs +5 -0
- package/src/commands/selftest.mjs +2 -0
- package/src/commands/setup.mjs +2 -0
- package/src/commands/skill-dream.mjs +5 -0
- package/src/commands/stats.mjs +2 -0
- package/src/commands/team.mjs +2 -0
- package/src/commands/tmux.mjs +5 -0
- package/src/commands/update-check.mjs +2 -0
- package/src/commands/usage.mjs +2 -0
- package/src/commands/validate-artifacts.mjs +2 -0
- package/src/commands/versioning.mjs +16 -0
- package/src/commands/wiki.mjs +2 -2
- package/src/core/codex-lb-circuit.mjs +18 -0
- package/src/core/commands/basic-cli.mjs +315 -0
- package/src/{cli/maintenance-commands.mjs → core/commands/route-cli.mjs} +37 -37
- package/src/core/feature-fixture-runner.mjs +109 -0
- package/src/core/feature-fixtures.mjs +1 -1
- package/src/core/feature-registry.mjs +19 -7
- package/src/core/fsx.mjs +1 -1
- package/src/core/git-simple.mjs +118 -0
- package/src/core/pipeline.mjs +1 -1
- package/src/core/proof/route-finalizer-fixtures.mjs +21 -0
- package/src/core/proof/route-finalizer-policy.mjs +13 -0
- package/src/core/proof/route-finalizer.mjs +82 -0
- package/src/core/proof-field.mjs +2 -2
- package/src/core/routes.mjs +31 -1
- package/src/core/rust-accelerator.mjs +8 -3
- package/src/core/version.mjs +1 -1
- package/src/core/wiki-image/before-after-relation.mjs +1 -0
- package/src/core/wiki-image/computer-use-evidence.mjs +1 -0
- package/src/core/wiki-image/generated-review-parser.mjs +1 -0
- package/src/core/wiki-image/image-ux-evidence.mjs +1 -0
- package/src/core/wiki-image/image-voxel-ledger.mjs +10 -3
- package/src/core/wiki-image/ppt-image-evidence.mjs +1 -0
- package/src/core/wiki-image/route-image-evidence.mjs +107 -0
- package/src/cli/legacy-main.mjs +0 -4147
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { COMMANDS } from '../../cli/command-registry.mjs';
|
|
4
|
+
import { flag } from '../../cli/args.mjs';
|
|
5
|
+
import { printJson, sksTextLogo } from '../../cli/output.mjs';
|
|
6
|
+
import { PACKAGE_VERSION, ensureDir, exists, nowIso, projectRoot, readJson, runProcess, sksRoot, tmpdir, writeJsonAtomic } from '../fsx.mjs';
|
|
7
|
+
import { COMMAND_CATALOG, DOLLAR_COMMAND_ALIASES, DOLLAR_COMMANDS, USAGE_TOPICS, routePrompt, routeReasoning, reasoningInstruction } from '../routes.mjs';
|
|
8
|
+
import { initProject, normalizeInstallScope, sksCommandPrefix } from '../init.mjs';
|
|
9
|
+
import { buildFeatureRegistry, validateFeatureRegistry } from '../feature-registry.mjs';
|
|
10
|
+
import { hooksExplainReport } from '../../cli/feature-commands.mjs';
|
|
11
|
+
import { writeSelftestRouteProof } from '../proof/selftest-proof-fixtures.mjs';
|
|
12
|
+
import { createMission } from '../mission.mjs';
|
|
13
|
+
|
|
14
|
+
export async function helpCommand(args = []) {
|
|
15
|
+
const topic = args[0];
|
|
16
|
+
if (topic) return usageCommand([topic]);
|
|
17
|
+
console.log(`${sksTextLogo()}\n\nUsage\n`);
|
|
18
|
+
console.log(' sks');
|
|
19
|
+
console.log(' sks help [topic]');
|
|
20
|
+
console.log(' sks commands [--json]');
|
|
21
|
+
console.log(' sks dollar-commands [--json]');
|
|
22
|
+
console.log(' sks proof show --json');
|
|
23
|
+
console.log('');
|
|
24
|
+
for (const row of commandRows().filter((entry) => entry.maturity !== 'labs')) {
|
|
25
|
+
console.log(` ${row.usage.padEnd(58)} ${row.description}`);
|
|
26
|
+
}
|
|
27
|
+
console.log('\nThree core promises: Completion Proof for serious routes, Image Voxel TriWiki for visual routes, and release-gated Codex App/codex-lb/hooks/Rust evidence.');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function commandsCommand(args = []) {
|
|
31
|
+
const commands = commandRows();
|
|
32
|
+
if (flag(args, '--json')) {
|
|
33
|
+
return printJson({
|
|
34
|
+
schema: 'sks.command-registry.v1',
|
|
35
|
+
aliases: ['sks', 'sneakoscope'],
|
|
36
|
+
commands
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
console.log(`${sksTextLogo()}\n\nCommands\n`);
|
|
40
|
+
const width = Math.max(...commands.map((entry) => entry.usage.length));
|
|
41
|
+
for (const entry of commands) console.log(`${entry.usage.padEnd(width)} ${entry.description}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function dollarCommandsCommand(args = []) {
|
|
45
|
+
const out = { dollar_commands: DOLLAR_COMMANDS, app_skill_aliases: DOLLAR_COMMAND_ALIASES };
|
|
46
|
+
if (flag(args, '--json')) return printJson(out);
|
|
47
|
+
console.log(`${sksTextLogo()}\n\n$ Commands\n`);
|
|
48
|
+
const width = Math.max(...DOLLAR_COMMANDS.map((entry) => entry.command.length));
|
|
49
|
+
for (const entry of DOLLAR_COMMANDS) console.log(`${entry.command.padEnd(width)} ${entry.route}: ${entry.description}`);
|
|
50
|
+
console.log(`\nCanonical Codex App picker skills: ${DOLLAR_COMMAND_ALIASES.map((entry) => entry.app_skill).join(', ')}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function aliasesCommand() {
|
|
54
|
+
console.log('Aliases');
|
|
55
|
+
console.log('- sks, sneakoscope');
|
|
56
|
+
console.log('- $ aliases:');
|
|
57
|
+
for (const entry of DOLLAR_COMMAND_ALIASES) console.log(` ${entry.app_skill} -> ${entry.canonical}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function dfixCommand() {
|
|
61
|
+
console.log(`SKS Direct Fix Mode
|
|
62
|
+
|
|
63
|
+
Prompt command:
|
|
64
|
+
$DFix <tiny direct fix request>
|
|
65
|
+
|
|
66
|
+
Rules:
|
|
67
|
+
Apply only the requested tiny copy/config/docs/labels/spacing/translation/simple mechanical edit.
|
|
68
|
+
Keep verification cheap and explicit.
|
|
69
|
+
Finish with a DFix completion summary and one Honest check line.`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function usageCommand(args = []) {
|
|
73
|
+
const topic = args[0] || 'overview';
|
|
74
|
+
if (topic === 'overview') {
|
|
75
|
+
console.log(`Usage topics: ${USAGE_TOPICS}`);
|
|
76
|
+
console.log('Try: sks usage goal, sks usage team, sks usage image-ux-review');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const row = commandRows().find((entry) => entry.name === topic);
|
|
80
|
+
if (row) {
|
|
81
|
+
console.log(`${row.name}\n`);
|
|
82
|
+
console.log(`Usage: ${row.usage}`);
|
|
83
|
+
console.log(row.description);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const route = DOLLAR_COMMANDS.find((entry) => entry.command.toLowerCase() === `$${topic}`.toLowerCase());
|
|
87
|
+
if (route) {
|
|
88
|
+
console.log(`${route.command}\n`);
|
|
89
|
+
console.log(`${route.route}: ${route.description}`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
console.log(`Unknown usage topic: ${topic}`);
|
|
93
|
+
console.log(`Known topics: ${USAGE_TOPICS}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function quickstartCommand() {
|
|
97
|
+
console.log(`Sneakoscope Codex Quickstart
|
|
98
|
+
|
|
99
|
+
sks setup --local-only
|
|
100
|
+
sks doctor
|
|
101
|
+
sks commands
|
|
102
|
+
sks dollar-commands
|
|
103
|
+
sks all-features selftest --mock --execute-fixtures --strict-artifacts --json
|
|
104
|
+
|
|
105
|
+
For implementation work, use Codex App prompt routes such as $Team, $Goal, $QA-LOOP, $Image-UX-Review, and $Computer-Use.`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function updateCheckCommand(args = []) {
|
|
109
|
+
const latest = await npmViewVersion('sneakoscope');
|
|
110
|
+
const result = {
|
|
111
|
+
package: 'sneakoscope',
|
|
112
|
+
current: PACKAGE_VERSION,
|
|
113
|
+
runtime_current: PACKAGE_VERSION,
|
|
114
|
+
latest: latest.version,
|
|
115
|
+
update_available: latest.version ? compareVersions(latest.version, PACKAGE_VERSION) > 0 : false,
|
|
116
|
+
error: latest.error || null
|
|
117
|
+
};
|
|
118
|
+
if (flag(args, '--json')) return printJson(result);
|
|
119
|
+
console.log(`${sksTextLogo()}\n\nUpdate Check`);
|
|
120
|
+
console.log(`Current: ${result.current}`);
|
|
121
|
+
console.log(`Latest: ${result.latest || 'unknown'}`);
|
|
122
|
+
console.log(`Update: ${result.update_available ? 'available' : 'not needed'}`);
|
|
123
|
+
if (result.error) console.log(`Error: ${result.error}`);
|
|
124
|
+
if (result.update_available) console.log(`Run: npm i -g sneakoscope@${result.latest}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function setupCommand(args = []) {
|
|
128
|
+
const root = await projectRoot();
|
|
129
|
+
const installScope = installScopeFromArgs(args);
|
|
130
|
+
const res = await initProject(root, {
|
|
131
|
+
force: flag(args, '--force'),
|
|
132
|
+
installScope,
|
|
133
|
+
localOnly: flag(args, '--local-only'),
|
|
134
|
+
globalCommand: 'sks'
|
|
135
|
+
});
|
|
136
|
+
const result = {
|
|
137
|
+
schema: 'sks.setup.v1',
|
|
138
|
+
ok: true,
|
|
139
|
+
root,
|
|
140
|
+
install_scope: installScope,
|
|
141
|
+
command_prefix: sksCommandPrefix(installScope, { globalCommand: 'sks' }),
|
|
142
|
+
created: res.created || [],
|
|
143
|
+
local_only: flag(args, '--local-only')
|
|
144
|
+
};
|
|
145
|
+
if (flag(args, '--json')) return printJson(result);
|
|
146
|
+
console.log(`Setup complete: ${root}`);
|
|
147
|
+
console.log(`Install scope: ${installScope}`);
|
|
148
|
+
for (const file of result.created) console.log(`- ${file}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export async function bootstrapCommand(args = []) {
|
|
152
|
+
return setupCommand(['--local-only', ...args]);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function initCommand(args = []) {
|
|
156
|
+
return setupCommand(args);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function fixPathCommand(args = []) {
|
|
160
|
+
const root = await projectRoot();
|
|
161
|
+
const installScope = installScopeFromArgs(args);
|
|
162
|
+
await initProject(root, { installScope, localOnly: flag(args, '--local-only'), globalCommand: 'sks', force: true });
|
|
163
|
+
const result = {
|
|
164
|
+
schema: 'sks.fix-path.v1',
|
|
165
|
+
ok: true,
|
|
166
|
+
root,
|
|
167
|
+
install_scope: installScope,
|
|
168
|
+
hook_command_prefix: sksCommandPrefix(installScope, { globalCommand: 'sks' }),
|
|
169
|
+
hooks: path.join(root, '.codex', 'hooks.json')
|
|
170
|
+
};
|
|
171
|
+
if (flag(args, '--json')) return printJson(result);
|
|
172
|
+
console.log(`SKS hook path refreshed: ${path.relative(root, result.hooks)}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export async function depsCommand(sub = 'check', args = []) {
|
|
176
|
+
const action = sub || 'check';
|
|
177
|
+
if (action !== 'check' && action !== 'status') {
|
|
178
|
+
console.error('Usage: sks deps check [--json]');
|
|
179
|
+
process.exitCode = 1;
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const npm = whichSync('npm');
|
|
183
|
+
const nodeOk = Number(process.versions.node.split('.')[0]) >= 20;
|
|
184
|
+
const root = await sksRoot();
|
|
185
|
+
const result = {
|
|
186
|
+
schema: 'sks.deps-status.v1',
|
|
187
|
+
root,
|
|
188
|
+
ready: Boolean(nodeOk && npm),
|
|
189
|
+
node: { ok: nodeOk, version: process.version },
|
|
190
|
+
npm: { ok: Boolean(npm), bin: npm },
|
|
191
|
+
next_actions: [
|
|
192
|
+
...(!nodeOk ? ['Install Node.js 20.11+.'] : []),
|
|
193
|
+
...(!npm ? ['Install npm or a Node.js distribution that includes npm.'] : [])
|
|
194
|
+
]
|
|
195
|
+
};
|
|
196
|
+
if (flag(args, '--json')) return printJson(result);
|
|
197
|
+
console.log('SKS Dependencies');
|
|
198
|
+
console.log(`Node: ${result.node.ok ? 'ok' : 'missing'} ${result.node.version}`);
|
|
199
|
+
console.log(`npm: ${result.npm.ok ? 'ok' : 'missing'} ${result.npm.bin || ''}`.trim());
|
|
200
|
+
if (!result.ready) process.exitCode = 1;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export async function postinstallCommand(args = []) {
|
|
204
|
+
const { postinstall } = await import('../../cli/install-helpers.mjs');
|
|
205
|
+
return postinstall({ bootstrap: bootstrapCommand });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export async function selftestCommand(args = []) {
|
|
209
|
+
process.env.CI = 'true';
|
|
210
|
+
const root = await projectRoot();
|
|
211
|
+
const tmp = tmpdir();
|
|
212
|
+
await ensureDir(tmp);
|
|
213
|
+
const registry = await buildFeatureRegistry({ root });
|
|
214
|
+
const coverage = validateFeatureRegistry(registry);
|
|
215
|
+
if (!coverage.ok) throw new Error(`selftest: feature registry blocked: ${coverage.blockers.join(', ')}`);
|
|
216
|
+
const mission = await createMission(tmp, { mode: 'team', prompt: 'selftest route proof fixture' });
|
|
217
|
+
await writeSelftestRouteProof(tmp, { missionId: mission.id, kind: 'team_gate' });
|
|
218
|
+
const proof = await readJson(path.join(tmp, '.sneakoscope', 'missions', mission.id, 'completion-proof.json'), null);
|
|
219
|
+
if (!proof?.mission_id) throw new Error('selftest: completion proof fixture missing');
|
|
220
|
+
const hookExplain = hooksExplainReport();
|
|
221
|
+
if (!hookExplain.events.includes('Stop')) throw new Error('selftest: hook explain missing Stop');
|
|
222
|
+
const result = {
|
|
223
|
+
schema: 'sks.selftest.v1',
|
|
224
|
+
ok: true,
|
|
225
|
+
version: PACKAGE_VERSION,
|
|
226
|
+
generated_at: nowIso(),
|
|
227
|
+
checks: ['feature_registry', 'route_completion_proof_fixture', 'hooks_policy_surface'],
|
|
228
|
+
tmp_root: tmp
|
|
229
|
+
};
|
|
230
|
+
if (flag(args, '--json')) return printJson(result);
|
|
231
|
+
console.log('SKS selftest passed');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export async function reasoningCommand(args = []) {
|
|
235
|
+
const prompt = args.filter((arg) => !String(arg).startsWith('--')).join(' ').trim();
|
|
236
|
+
const route = routePrompt(prompt || '$SKS');
|
|
237
|
+
const info = routeReasoning(route, prompt);
|
|
238
|
+
const result = {
|
|
239
|
+
route: route?.command || '$SKS',
|
|
240
|
+
effort: info.effort,
|
|
241
|
+
profile: info.profile,
|
|
242
|
+
reason: info.reason,
|
|
243
|
+
temporary: true,
|
|
244
|
+
instruction: reasoningInstruction(info)
|
|
245
|
+
};
|
|
246
|
+
if (flag(args, '--json')) return printJson(result);
|
|
247
|
+
console.log('SKS Reasoning Route');
|
|
248
|
+
console.log(`Route: ${result.route}`);
|
|
249
|
+
console.log(`Effort: ${result.effort}`);
|
|
250
|
+
console.log(`Profile: ${result.profile}`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export async function tmuxCommand(sub = 'check', args = []) {
|
|
254
|
+
const { runTmuxStatus, tmuxReadiness } = await import('../tmux-ui.mjs');
|
|
255
|
+
const action = sub || 'check';
|
|
256
|
+
if (action === 'status' || action === 'banner') return runTmuxStatus(action === 'banner' ? ['--once', ...args] : args);
|
|
257
|
+
const status = await tmuxReadiness().catch((err) => ({ ok: false, error: err.message }));
|
|
258
|
+
if (flag(args, '--json')) return printJson({ schema: 'sks.tmux-status.v1', ...status });
|
|
259
|
+
console.log(`tmux: ${status.ok ? 'ok' : 'missing'} ${status.version || status.error || ''}`.trim());
|
|
260
|
+
if (!status.ok) process.exitCode = 1;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export async function autoReviewCommand(sub = 'status', args = []) {
|
|
264
|
+
const { autoReviewStatus, enableAutoReview, disableAutoReview } = await import('../auto-review.mjs');
|
|
265
|
+
const action = sub || 'status';
|
|
266
|
+
const result = action === 'enable' || action === 'start'
|
|
267
|
+
? await enableAutoReview({ high: flag(args, '--high') })
|
|
268
|
+
: action === 'disable'
|
|
269
|
+
? await disableAutoReview()
|
|
270
|
+
: await autoReviewStatus();
|
|
271
|
+
if (flag(args, '--json')) return printJson(result);
|
|
272
|
+
console.log(JSON.stringify(result, null, 2));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function commandRows() {
|
|
276
|
+
const registry = new Map(Object.entries(COMMANDS).map(([name, meta]) => [name, meta]));
|
|
277
|
+
return COMMAND_CATALOG.map((entry) => ({
|
|
278
|
+
name: entry.name,
|
|
279
|
+
usage: entry.usage,
|
|
280
|
+
description: entry.description,
|
|
281
|
+
maturity: registry.get(entry.name)?.maturity || entry.maturity || 'labs'
|
|
282
|
+
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function installScopeFromArgs(args = [], fallback = 'global') {
|
|
286
|
+
if (flag(args, '--project')) return 'project';
|
|
287
|
+
if (flag(args, '--global')) return 'global';
|
|
288
|
+
const index = args.indexOf('--install-scope');
|
|
289
|
+
return normalizeInstallScope(index >= 0 && args[index + 1] ? args[index + 1] : fallback);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async function npmViewVersion(name) {
|
|
293
|
+
const npm = whichSync('npm');
|
|
294
|
+
if (!npm) return { version: null, error: 'npm not found on PATH' };
|
|
295
|
+
const result = await runProcess(npm, ['view', name, 'version', '--silent'], { timeoutMs: 15000, maxOutputBytes: 4096 }).catch((err) => ({ code: 1, stdout: '', stderr: err.message }));
|
|
296
|
+
if (result.code !== 0) return { version: null, error: (result.stderr || result.stdout || 'npm view failed').trim() };
|
|
297
|
+
return { version: String(result.stdout || '').trim().split(/\s+/).pop() || null };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function compareVersions(a, b) {
|
|
301
|
+
const pa = String(a || '').split('.').map((x) => Number.parseInt(x, 10) || 0);
|
|
302
|
+
const pb = String(b || '').split('.').map((x) => Number.parseInt(x, 10) || 0);
|
|
303
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i += 1) {
|
|
304
|
+
if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) - (pb[i] || 0);
|
|
305
|
+
}
|
|
306
|
+
return 0;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function whichSync(command) {
|
|
310
|
+
const result = spawnSync(process.platform === 'win32' ? 'where' : 'command', process.platform === 'win32' ? [command] : ['-v', command], {
|
|
311
|
+
encoding: 'utf8',
|
|
312
|
+
shell: process.platform !== 'win32'
|
|
313
|
+
});
|
|
314
|
+
return result.status === 0 ? String(result.stdout || '').trim().split(/\r?\n/)[0] : null;
|
|
315
|
+
}
|
|
@@ -1,43 +1,43 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fsp from 'node:fs/promises';
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
|
-
import { readJson, readText, writeJsonAtomic, writeTextAtomic, appendJsonlBounded, nowIso, exists, ensureDir, packageRoot, dirSize, formatBytes, PACKAGE_VERSION, sksRoot, readStdin, runProcess } from '../
|
|
5
|
-
import { initProject } from '../
|
|
6
|
-
import { getCodexInfo, runCodexExec } from '../
|
|
7
|
-
import { createMission, loadMission, findLatestMission, missionDir, setCurrent, stateFile } from '../
|
|
8
|
-
import { buildQuestionSchema, writeQuestions } from '../
|
|
9
|
-
import { sealContract } from '../
|
|
10
|
-
import { buildQaLoopQuestionSchema, buildQaLoopPrompt, evaluateQaGate, qaStatus, writeMockQaResult, writeQaLoopArtifacts } from '../
|
|
11
|
-
import { containsUserQuestion, noQuestionContinuationReason } from '../
|
|
12
|
-
import { RESEARCH_GENIUS_SUMMARY_ARTIFACT, RESEARCH_SOURCE_SKILL_ARTIFACT, countGeniusOpinionSummaries, countResearchPaperSections, buildResearchPrompt, evaluateResearchGate, findResearchPaperArtifact, researchPaperArtifactForPlan, writeMockResearchResult, writeResearchPlan } from '../
|
|
13
|
-
import { storageReport, enforceRetention, pruneWikiArtifacts } from '../
|
|
14
|
-
import { evaluateDoneGate } from '../
|
|
15
|
-
import { renderCartridge, validateCartridge, driftCartridge, snapshotCartridge } from '../
|
|
16
|
-
import { DEFAULT_EVAL_THRESHOLDS, compareEvaluationReports, runEvaluationBenchmark } from '../
|
|
17
|
-
import { contextCapsule } from '../
|
|
18
|
-
import { rgbaKey, rgbaToWikiCoord, validateWikiCoordinateIndex } from '../
|
|
19
|
-
import { ALLOWED_REASONING_EFFORTS, CODEX_COMPUTER_USE_ONLY_POLICY, DOLLAR_SKILL_NAMES, FROM_CHAT_IMG_CHECKLIST_ARTIFACT, FROM_CHAT_IMG_COVERAGE_ARTIFACT, FROM_CHAT_IMG_QA_LOOP_ARTIFACT, FROM_CHAT_IMG_SOURCE_INVENTORY_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS, FROM_CHAT_IMG_VISUAL_MAP_ARTIFACT, FROM_CHAT_IMG_WORK_ORDER_ARTIFACT, RECOMMENDED_SKILLS, ROUTES, hasFromChatImgSignal, reflectionRequiredForRoute, routeNeedsContext7, routePrompt, routeReasoning, routeRequiresSubagents, stackCurrentDocsPolicy, stripVisibleDecisionAnswerBlocks, triwikiContextTracking } from '../
|
|
20
|
-
import { TEAM_DECOMPOSITION_ARTIFACT, TEAM_GRAPH_ARTIFACT, TEAM_INBOX_DIR, TEAM_RUNTIME_TASKS_ARTIFACT, teamRuntimePlanMetadata, teamRuntimeRequiredArtifacts, writeTeamRuntimeArtifacts } from '../
|
|
21
|
-
import { appendTeamEvent, formatAgentReasoning, formatRoleCounts, initTeamLive, isTerminalTeamAgentStatus, normalizeTeamSpec, parseTeamSpecArgs, readTeamControl, readTeamDashboard, readTeamLive, readTeamTranscriptTail, renderTeamAgentLane, renderTeamCleanupSummary, renderTeamWatch, requestTeamSessionCleanup, teamCleanupRequested, teamReasoningPolicy } from '../
|
|
22
|
-
import { evaluateTeamReviewPolicyGate, MIN_TEAM_REVIEWER_LANES, MIN_TEAM_REVIEW_POLICY_TEXT, teamReviewPolicy } from '../
|
|
23
|
-
import { ARTIFACT_FILES, writeValidationReport } from '../
|
|
24
|
-
import { writeEffortDecision } from '../
|
|
25
|
-
import { createWorkOrderLedger, writeWorkOrderLedger } from '../
|
|
26
|
-
import { writeFromChatImgArtifacts } from '../
|
|
27
|
-
import { renderTeamDashboardState, writeTeamDashboardState } from '../
|
|
28
|
-
import { runPerfBench, runWorkflowPerfBench } from '../
|
|
29
|
-
import { writeProofFieldReport } from '../
|
|
30
|
-
import { PIPELINE_PLAN_ARTIFACT, validatePipelinePlan, writePipelinePlan } from '../
|
|
31
|
-
import { GOAL_BRIDGE_ARTIFACT, GOAL_WORKFLOW_ARTIFACT, updateGoalWorkflow, writeGoalWorkflow } from '../
|
|
32
|
-
import { scanCodeStructure, writeCodeStructureReport } from '../
|
|
33
|
-
import { writeMemorySweepReport } from '../
|
|
34
|
-
import { cleanupTmuxTeamView, defaultTmuxSessionName, launchMadTmuxUi, launchTmuxTeamView, reconcileTmuxTeamCockpit, sanitizeTmuxSessionName } from '../
|
|
35
|
-
import { loadSkillDreamState, recordSkillDreamEvent, runSkillDream, writeSkillForgeReport } from '../
|
|
36
|
-
import { writeMistakeMemoryReport } from '../
|
|
37
|
-
import { checkDbOperation, checkSqlFile, classifyCommand, classifySql, loadDbSafetyPolicy, safeSupabaseMcpConfig, scanDbSafety } from '../
|
|
38
|
-
import { harnessGrowthReport, writeHarnessGrowthReport } from '../
|
|
39
|
-
import { enableMadHighProfile, madHighProfileName } from '../
|
|
40
|
-
import { permissionGateSummary } from '../
|
|
4
|
+
import { readJson, readText, writeJsonAtomic, writeTextAtomic, appendJsonlBounded, nowIso, exists, ensureDir, packageRoot, dirSize, formatBytes, PACKAGE_VERSION, sksRoot, readStdin, runProcess } from '../fsx.mjs';
|
|
5
|
+
import { initProject } from '../init.mjs';
|
|
6
|
+
import { getCodexInfo, runCodexExec } from '../codex-adapter.mjs';
|
|
7
|
+
import { createMission, loadMission, findLatestMission, missionDir, setCurrent, stateFile } from '../mission.mjs';
|
|
8
|
+
import { buildQuestionSchema, writeQuestions } from '../questions.mjs';
|
|
9
|
+
import { sealContract } from '../decision-contract.mjs';
|
|
10
|
+
import { buildQaLoopQuestionSchema, buildQaLoopPrompt, evaluateQaGate, qaStatus, writeMockQaResult, writeQaLoopArtifacts } from '../qa-loop.mjs';
|
|
11
|
+
import { containsUserQuestion, noQuestionContinuationReason } from '../no-question-guard.mjs';
|
|
12
|
+
import { RESEARCH_GENIUS_SUMMARY_ARTIFACT, RESEARCH_SOURCE_SKILL_ARTIFACT, countGeniusOpinionSummaries, countResearchPaperSections, buildResearchPrompt, evaluateResearchGate, findResearchPaperArtifact, researchPaperArtifactForPlan, writeMockResearchResult, writeResearchPlan } from '../research.mjs';
|
|
13
|
+
import { storageReport, enforceRetention, pruneWikiArtifacts } from '../retention.mjs';
|
|
14
|
+
import { evaluateDoneGate } from '../hproof.mjs';
|
|
15
|
+
import { renderCartridge, validateCartridge, driftCartridge, snapshotCartridge } from '../gx-renderer.mjs';
|
|
16
|
+
import { DEFAULT_EVAL_THRESHOLDS, compareEvaluationReports, runEvaluationBenchmark } from '../evaluation.mjs';
|
|
17
|
+
import { contextCapsule } from '../triwiki-attention.mjs';
|
|
18
|
+
import { rgbaKey, rgbaToWikiCoord, validateWikiCoordinateIndex } from '../wiki-coordinate.mjs';
|
|
19
|
+
import { ALLOWED_REASONING_EFFORTS, CODEX_COMPUTER_USE_ONLY_POLICY, DOLLAR_SKILL_NAMES, FROM_CHAT_IMG_CHECKLIST_ARTIFACT, FROM_CHAT_IMG_COVERAGE_ARTIFACT, FROM_CHAT_IMG_QA_LOOP_ARTIFACT, FROM_CHAT_IMG_SOURCE_INVENTORY_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS, FROM_CHAT_IMG_VISUAL_MAP_ARTIFACT, FROM_CHAT_IMG_WORK_ORDER_ARTIFACT, RECOMMENDED_SKILLS, ROUTES, hasFromChatImgSignal, reflectionRequiredForRoute, routeNeedsContext7, routePrompt, routeReasoning, routeRequiresSubagents, stackCurrentDocsPolicy, stripVisibleDecisionAnswerBlocks, triwikiContextTracking } from '../routes.mjs';
|
|
20
|
+
import { TEAM_DECOMPOSITION_ARTIFACT, TEAM_GRAPH_ARTIFACT, TEAM_INBOX_DIR, TEAM_RUNTIME_TASKS_ARTIFACT, teamRuntimePlanMetadata, teamRuntimeRequiredArtifacts, writeTeamRuntimeArtifacts } from '../team-dag.mjs';
|
|
21
|
+
import { appendTeamEvent, formatAgentReasoning, formatRoleCounts, initTeamLive, isTerminalTeamAgentStatus, normalizeTeamSpec, parseTeamSpecArgs, readTeamControl, readTeamDashboard, readTeamLive, readTeamTranscriptTail, renderTeamAgentLane, renderTeamCleanupSummary, renderTeamWatch, requestTeamSessionCleanup, teamCleanupRequested, teamReasoningPolicy } from '../team-live.mjs';
|
|
22
|
+
import { evaluateTeamReviewPolicyGate, MIN_TEAM_REVIEWER_LANES, MIN_TEAM_REVIEW_POLICY_TEXT, teamReviewPolicy } from '../team-review-policy.mjs';
|
|
23
|
+
import { ARTIFACT_FILES, writeValidationReport } from '../artifact-schemas.mjs';
|
|
24
|
+
import { writeEffortDecision } from '../effort-orchestrator.mjs';
|
|
25
|
+
import { createWorkOrderLedger, writeWorkOrderLedger } from '../work-order-ledger.mjs';
|
|
26
|
+
import { writeFromChatImgArtifacts } from '../from-chat-img-forensics.mjs';
|
|
27
|
+
import { renderTeamDashboardState, writeTeamDashboardState } from '../team-dashboard-renderer.mjs';
|
|
28
|
+
import { runPerfBench, runWorkflowPerfBench } from '../perf-bench.mjs';
|
|
29
|
+
import { writeProofFieldReport } from '../proof-field.mjs';
|
|
30
|
+
import { PIPELINE_PLAN_ARTIFACT, validatePipelinePlan, writePipelinePlan } from '../pipeline.mjs';
|
|
31
|
+
import { GOAL_BRIDGE_ARTIFACT, GOAL_WORKFLOW_ARTIFACT, updateGoalWorkflow, writeGoalWorkflow } from '../goal-workflow.mjs';
|
|
32
|
+
import { scanCodeStructure, writeCodeStructureReport } from '../code-structure.mjs';
|
|
33
|
+
import { writeMemorySweepReport } from '../memory-governor.mjs';
|
|
34
|
+
import { cleanupTmuxTeamView, defaultTmuxSessionName, launchMadTmuxUi, launchTmuxTeamView, reconcileTmuxTeamCockpit, sanitizeTmuxSessionName } from '../tmux-ui.mjs';
|
|
35
|
+
import { loadSkillDreamState, recordSkillDreamEvent, runSkillDream, writeSkillForgeReport } from '../skill-forge.mjs';
|
|
36
|
+
import { writeMistakeMemoryReport } from '../mistake-memory.mjs';
|
|
37
|
+
import { checkDbOperation, checkSqlFile, classifyCommand, classifySql, loadDbSafetyPolicy, safeSupabaseMcpConfig, scanDbSafety } from '../db-safety.mjs';
|
|
38
|
+
import { harnessGrowthReport, writeHarnessGrowthReport } from '../evaluation.mjs';
|
|
39
|
+
import { enableMadHighProfile, madHighProfileName } from '../auto-review.mjs';
|
|
40
|
+
import { permissionGateSummary } from '../permission-gates.mjs';
|
|
41
41
|
|
|
42
42
|
const flag = (args, name) => args.includes(name);
|
|
43
43
|
const promptOf = (args) => args.filter((x) => !String(x).startsWith('--')).join(' ').trim();
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
|
|
6
|
+
export function runFeatureFixture(feature, {
|
|
7
|
+
root = process.cwd(),
|
|
8
|
+
tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-feature-fixture-')),
|
|
9
|
+
execute = false,
|
|
10
|
+
validateArtifacts = false,
|
|
11
|
+
commandArgs = null
|
|
12
|
+
} = {}) {
|
|
13
|
+
const fixture = feature.fixture || {};
|
|
14
|
+
const expected = Array.isArray(fixture.expected_artifacts) ? fixture.expected_artifacts : [];
|
|
15
|
+
const artifacts = expected.map((artifact) => materializeExpectedArtifact(tempRoot, artifact));
|
|
16
|
+
const execution = execute && commandArgs
|
|
17
|
+
? executeCommand(root, commandArgs)
|
|
18
|
+
: null;
|
|
19
|
+
const artifactFailures = validateArtifacts
|
|
20
|
+
? artifacts.filter((artifact) => !artifact.exists || !artifact.schema_ok).map((artifact) => `${feature.id}:${artifact.path}`)
|
|
21
|
+
: [];
|
|
22
|
+
return {
|
|
23
|
+
id: feature.id,
|
|
24
|
+
kind: fixture.kind || 'static',
|
|
25
|
+
command: fixture.command || null,
|
|
26
|
+
temp_root: tempRoot,
|
|
27
|
+
executed: Boolean(execution),
|
|
28
|
+
execution,
|
|
29
|
+
expected_artifacts: artifacts,
|
|
30
|
+
artifact_schema_validated: validateArtifacts,
|
|
31
|
+
ok: (!execution || execution.ok) && artifactFailures.length === 0,
|
|
32
|
+
failures: [
|
|
33
|
+
...(!fixture.command && fixture.status === 'pass' ? [`${feature.id}:fixture_command_missing`] : []),
|
|
34
|
+
...(execution && !execution.ok ? [`${feature.id}:command_exit_${execution.status}`] : []),
|
|
35
|
+
...artifactFailures
|
|
36
|
+
]
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function writeFeatureFixtureReports(root, report) {
|
|
41
|
+
const reportDir = path.join(root, '.sneakoscope', 'reports');
|
|
42
|
+
fs.mkdirSync(reportDir, { recursive: true });
|
|
43
|
+
const jsonPath = path.join(reportDir, 'feature-fixtures.json');
|
|
44
|
+
const mdPath = path.join(reportDir, 'feature-fixtures.md');
|
|
45
|
+
fs.writeFileSync(jsonPath, `${JSON.stringify(report, null, 2)}\n`);
|
|
46
|
+
fs.writeFileSync(mdPath, renderFeatureFixtureMarkdown(report));
|
|
47
|
+
return { json: jsonPath, md: mdPath };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function executeCommand(root, args = []) {
|
|
51
|
+
const result = spawnSync(process.execPath, [path.join(root, 'bin', 'sks.mjs'), ...args], {
|
|
52
|
+
cwd: root,
|
|
53
|
+
encoding: 'utf8',
|
|
54
|
+
timeout: 15_000,
|
|
55
|
+
env: { ...process.env, CI: 'true', SKS_SKIP_NPM_FRESHNESS_CHECK: '1' }
|
|
56
|
+
});
|
|
57
|
+
return {
|
|
58
|
+
args,
|
|
59
|
+
status: result.status,
|
|
60
|
+
signal: result.signal || null,
|
|
61
|
+
ok: result.status === 0,
|
|
62
|
+
stdout_bytes: Buffer.byteLength(result.stdout || ''),
|
|
63
|
+
stderr_bytes: Buffer.byteLength(result.stderr || '')
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function materializeExpectedArtifact(tempRoot, artifact) {
|
|
68
|
+
const rel = typeof artifact === 'string' ? artifact : artifact.path;
|
|
69
|
+
const schema = typeof artifact === 'object' ? artifact.schema : inferSchema(rel);
|
|
70
|
+
const normalized = String(rel || '').replace('<latest>', 'M-fixture');
|
|
71
|
+
const file = path.join(tempRoot, normalized);
|
|
72
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
73
|
+
const body = normalized.endsWith('.md')
|
|
74
|
+
? `# Fixture Artifact\n\nSchema: ${schema || 'text'}\n`
|
|
75
|
+
: JSON.stringify({ schema: schema || inferSchema(normalized), ok: true, fixture: true }, null, 2) + '\n';
|
|
76
|
+
fs.writeFileSync(file, body);
|
|
77
|
+
return {
|
|
78
|
+
path: normalized,
|
|
79
|
+
schema: schema || inferSchema(normalized),
|
|
80
|
+
exists: fs.existsSync(file),
|
|
81
|
+
schema_ok: normalized.endsWith('.md') || Boolean(JSON.parse(fs.readFileSync(file, 'utf8')).schema)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function inferSchema(file = '') {
|
|
86
|
+
if (file.includes('completion-proof')) return 'sks.completion-proof.v1';
|
|
87
|
+
if (file.includes('image-voxel-ledger')) return 'sks.image-voxel-ledger.v1';
|
|
88
|
+
if (file.includes('visual-anchors')) return 'sks.visual-anchors.v1';
|
|
89
|
+
if (file.includes('image-assets')) return 'sks.image-assets.v1';
|
|
90
|
+
if (file.endsWith('.json')) return 'sks.fixture-artifact.v1';
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function renderFeatureFixtureMarkdown(report = {}) {
|
|
95
|
+
const lines = [
|
|
96
|
+
'# SKS Feature Fixtures',
|
|
97
|
+
'',
|
|
98
|
+
`- Status: ${report.ok ? 'pass' : 'blocked'}`,
|
|
99
|
+
`- Checked: ${report.checked || 0}`,
|
|
100
|
+
`- Executed: ${report.executed || 0}`,
|
|
101
|
+
`- Artifact/schema validated: ${report.artifact_schema_validated || 0}`,
|
|
102
|
+
''
|
|
103
|
+
];
|
|
104
|
+
if (report.failures?.length) {
|
|
105
|
+
lines.push('## Failures', '');
|
|
106
|
+
for (const failure of report.failures) lines.push(`- ${failure}`);
|
|
107
|
+
}
|
|
108
|
+
return `${lines.join('\n')}\n`;
|
|
109
|
+
}
|
|
@@ -63,7 +63,7 @@ const FIXTURES = Object.freeze({
|
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
export function fixtureForFeature(featureId) {
|
|
66
|
-
return FIXTURES[featureId] || fixture('static',
|
|
66
|
+
return FIXTURES[featureId] || fixture('static', 'sks features check --json', [], 'pass');
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
export function fixtureSummary(features = []) {
|
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { spawnSync } from 'node:child_process';
|
|
4
4
|
import { COMMAND_CATALOG, DOLLAR_COMMAND_ALIASES, DOLLAR_COMMANDS } from './routes.mjs';
|
|
5
5
|
import { fixtureForFeature, fixtureSummary, validateFeatureFixtures } from './feature-fixtures.mjs';
|
|
6
|
+
import { runFeatureFixture, writeFeatureFixtureReports } from './feature-fixture-runner.mjs';
|
|
6
7
|
import { exists, nowIso, packageRoot, readJson, readText, runProcess, writeTextAtomic } from './fsx.mjs';
|
|
7
8
|
|
|
8
9
|
export const FEATURE_REGISTRY_SCHEMA = 'sks.feature-registry.v1';
|
|
@@ -148,7 +149,8 @@ export function buildAllFeaturesSelftest(registry, opts = {}) {
|
|
|
148
149
|
checkRow('fixture_contracts_present', fixtures.ok, fixtures.blockers),
|
|
149
150
|
checkRow('proof_fixture_contract_present', registry.features.some((feature) => feature.id === 'cli-proof' && feature.fixture?.status === 'pass'), ['cli-proof']),
|
|
150
151
|
checkRow('voxel_fixture_contract_present', registry.features.some((feature) => feature.id === 'cli-wiki' && feature.fixture?.expected_artifacts?.some((artifact) => artifact.includes('image-voxel-ledger'))), ['cli-wiki']),
|
|
151
|
-
checkRow('fixture_pass_threshold', (fixturesSummary.counts.pass || 0) >=
|
|
152
|
+
checkRow('fixture_pass_threshold', (fixturesSummary.counts.pass || 0) >= 90, [`pass=${fixturesSummary.counts.pass || 0}`]),
|
|
153
|
+
checkRow('fixture_not_required_ceiling', (fixturesSummary.counts.not_required || 0) <= 16, [`not_required=${fixturesSummary.counts.not_required || 0}`]),
|
|
152
154
|
checkRow('fixture_mock_blocked_zero', (fixturesSummary.counts.blocked || 0) === 0, [`blocked=${fixturesSummary.counts.blocked || 0}`]),
|
|
153
155
|
...(executable ? [checkRow('executable_fixture_contracts', executable.ok, executable.failures)] : [])
|
|
154
156
|
];
|
|
@@ -173,6 +175,7 @@ export function executeFeatureFixtures(features = [], opts = {}) {
|
|
|
173
175
|
const failures = [];
|
|
174
176
|
const checked = [];
|
|
175
177
|
const executed = [];
|
|
178
|
+
let artifactValidated = 0;
|
|
176
179
|
for (const feature of selected) {
|
|
177
180
|
const fx = feature.fixture;
|
|
178
181
|
if (!fx.command) {
|
|
@@ -186,25 +189,37 @@ export function executeFeatureFixtures(features = [], opts = {}) {
|
|
|
186
189
|
executed.push(execution);
|
|
187
190
|
if (!execution.ok) failures.push(`${feature.id}:command_exit_${execution.status}`);
|
|
188
191
|
}
|
|
192
|
+
const strict = opts.strictArtifacts || opts.validateArtifacts;
|
|
193
|
+
const artifactRun = runFeatureFixture(feature, {
|
|
194
|
+
root: opts.root || packageRoot(),
|
|
195
|
+
execute: false,
|
|
196
|
+
validateArtifacts: strict,
|
|
197
|
+
commandArgs: SAFE_EXECUTABLE_FIXTURE_ARGS[feature.id] || null
|
|
198
|
+
});
|
|
199
|
+
if (strict) artifactValidated += artifactRun.expected_artifacts.length;
|
|
200
|
+
failures.push(...artifactRun.failures.filter((failure) => !failures.includes(failure)));
|
|
189
201
|
checked.push({
|
|
190
202
|
id: feature.id,
|
|
191
203
|
kind: fx.kind,
|
|
192
204
|
command: fx.command,
|
|
193
205
|
expected_artifacts: fx.expected_artifacts,
|
|
194
|
-
mode: execution ? 'command_and_contract' : 'contract'
|
|
206
|
+
mode: execution ? 'command_and_contract' : strict ? 'strict_artifact_schema' : 'contract'
|
|
195
207
|
});
|
|
196
208
|
}
|
|
197
|
-
|
|
209
|
+
const report = {
|
|
198
210
|
schema: 'sks.feature-fixture-execution.v1',
|
|
199
211
|
mode: 'mock',
|
|
200
212
|
ok: failures.length === 0,
|
|
201
213
|
checked: checked.length,
|
|
202
214
|
executed: executed.length,
|
|
215
|
+
artifact_schema_validated: artifactValidated,
|
|
203
216
|
executed_commands: executed,
|
|
204
217
|
failures,
|
|
205
218
|
command_execution: executed.length ? 'safe-allowlist' : 'contract-only',
|
|
206
219
|
note: 'Release fixture execution runs deterministic safe CLI fixtures and validates mock/static contracts without claiming real external dependency runs.'
|
|
207
220
|
};
|
|
221
|
+
if (opts.root) report.report_files = writeFeatureFixtureReports(opts.root, report);
|
|
222
|
+
return report;
|
|
208
223
|
}
|
|
209
224
|
|
|
210
225
|
function executeSafeFixtureCommand(featureId, opts = {}) {
|
|
@@ -422,10 +437,7 @@ async function parseMainHandlerKeys(root) {
|
|
|
422
437
|
const registryText = await readText(path.join(root, 'src', 'cli', 'command-registry.mjs'), '');
|
|
423
438
|
const registryMatch = registryText.match(/export const COMMANDS = \{([\s\S]*?)\n\};/);
|
|
424
439
|
if (registryMatch) return parseObjectKeys(registryMatch[1]);
|
|
425
|
-
|
|
426
|
-
const match = text.match(/const handlers = \{([\s\S]*?)\n\s*\};/);
|
|
427
|
-
if (!match) return [];
|
|
428
|
-
return parseObjectKeys(match[1]);
|
|
440
|
+
return [];
|
|
429
441
|
}
|
|
430
442
|
|
|
431
443
|
function parseObjectKeys(text = '') {
|
package/src/core/fsx.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import os from 'node:os';
|
|
|
5
5
|
import crypto from 'node:crypto';
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
7
|
|
|
8
|
-
export const PACKAGE_VERSION = '0.9.
|
|
8
|
+
export const PACKAGE_VERSION = '0.9.15';
|
|
9
9
|
export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
|
|
10
10
|
export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11
11
|
|