sneakoscope 0.9.13 → 0.9.14
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 +30 -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,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.14';
|
|
9
9
|
export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
|
|
10
10
|
export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11
11
|
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { runProcess, projectRoot, isGitRepo } from './fsx.mjs';
|
|
2
|
+
import { redactSecrets } from './secret-redaction.mjs';
|
|
3
|
+
|
|
4
|
+
const TRAILER = 'Co-authored-by: Codex <noreply@openai.com>';
|
|
5
|
+
|
|
6
|
+
export async function simpleGitCommitCommand(args = [], opts = {}) {
|
|
7
|
+
const root = await projectRoot();
|
|
8
|
+
const json = args.includes('--json');
|
|
9
|
+
const message = argValue(args, '--message') || argValue(args, '-m') || null;
|
|
10
|
+
const result = await simpleGitCommit(root, { message, push: Boolean(opts.push) });
|
|
11
|
+
if (json) console.log(JSON.stringify(result, null, 2));
|
|
12
|
+
else printSimpleGitCommit(result);
|
|
13
|
+
if (!result.ok) process.exitCode = 1;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function simpleGitCommit(root, { message = null, push = false } = {}) {
|
|
17
|
+
if (!await isGitRepo(root)) return { schema: 'sks.simple-git.v1', ok: false, reason: 'not_git_repo', root };
|
|
18
|
+
const before = await git(root, ['status', '--short']);
|
|
19
|
+
const changed = statusLines(before.stdout);
|
|
20
|
+
if (!changed.length) return { schema: 'sks.simple-git.v1', ok: false, reason: 'no_changes', root };
|
|
21
|
+
const add = await git(root, ['add', '-A']);
|
|
22
|
+
if (add.code !== 0) return failure(root, 'git_add_failed', before, add);
|
|
23
|
+
const stagedCheck = await git(root, ['diff', '--cached', '--quiet']);
|
|
24
|
+
if (stagedCheck.code === 0) return { schema: 'sks.simple-git.v1', ok: false, reason: 'no_staged_changes', root, changed };
|
|
25
|
+
const commitMessage = ensureCodexTrailer(message || buildCommitMessage(changed));
|
|
26
|
+
const commit = await git(root, ['commit', '-m', commitTitle(commitMessage), '-m', commitBody(commitMessage)]);
|
|
27
|
+
if (commit.code !== 0) return failure(root, 'git_commit_failed', before, commit);
|
|
28
|
+
const hash = await git(root, ['rev-parse', '--short', 'HEAD']);
|
|
29
|
+
let pushResult = null;
|
|
30
|
+
if (push) {
|
|
31
|
+
pushResult = await git(root, ['push']);
|
|
32
|
+
if (pushResult.code !== 0) return failure(root, 'git_push_failed', before, pushResult, { commit: commitSummary(commit), hash: hash.stdout.trim() });
|
|
33
|
+
}
|
|
34
|
+
return redactSecrets({
|
|
35
|
+
schema: 'sks.simple-git.v1',
|
|
36
|
+
ok: true,
|
|
37
|
+
root,
|
|
38
|
+
action: push ? 'commit-and-push' : 'commit',
|
|
39
|
+
changed,
|
|
40
|
+
commit: commitSummary(commit),
|
|
41
|
+
hash: hash.stdout.trim(),
|
|
42
|
+
pushed: Boolean(push && pushResult?.code === 0),
|
|
43
|
+
push: pushResult ? { ok: pushResult.code === 0, stdout: pushResult.stdout.trim(), stderr: pushResult.stderr.trim() } : null
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function failure(root, reason, before, failed, extra = {}) {
|
|
48
|
+
return redactSecrets({
|
|
49
|
+
schema: 'sks.simple-git.v1',
|
|
50
|
+
ok: false,
|
|
51
|
+
root,
|
|
52
|
+
reason,
|
|
53
|
+
changed: statusLines(before?.stdout || ''),
|
|
54
|
+
command: { code: failed.code, stdout: failed.stdout.trim(), stderr: failed.stderr.trim() },
|
|
55
|
+
...extra
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function git(root, args) {
|
|
60
|
+
return runProcess('git', args, { cwd: root, timeoutMs: 120000, maxOutputBytes: 256 * 1024 });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function statusLines(text = '') {
|
|
64
|
+
return String(text || '').split(/\r?\n/).map((line) => line.trimEnd()).filter(Boolean);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function buildCommitMessage(changed = []) {
|
|
68
|
+
const counts = {};
|
|
69
|
+
for (const line of changed) {
|
|
70
|
+
const status = line.slice(0, 2).trim() || 'changed';
|
|
71
|
+
counts[status] = (counts[status] || 0) + 1;
|
|
72
|
+
}
|
|
73
|
+
const summary = Object.entries(counts).map(([status, count]) => `${status}:${count}`).join(', ') || 'changes';
|
|
74
|
+
const files = changed.slice(0, 12).map((line) => `- ${line}`).join('\n');
|
|
75
|
+
const more = changed.length > 12 ? `\n- ...and ${changed.length - 12} more` : '';
|
|
76
|
+
return `chore: update project changes\n\nSummary: ${summary}\n\nChanged files:\n${files}${more}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function ensureCodexTrailer(message = '') {
|
|
80
|
+
const withoutDuplicate = String(message || '')
|
|
81
|
+
.split(/\r?\n/)
|
|
82
|
+
.filter((line) => line.trim() !== TRAILER)
|
|
83
|
+
.join('\n')
|
|
84
|
+
.trimEnd();
|
|
85
|
+
return `${withoutDuplicate}\n\n${TRAILER}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function commitTitle(message = '') {
|
|
89
|
+
return String(message || '').split(/\r?\n/)[0].trim() || 'chore: update project changes';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function commitBody(message = '') {
|
|
93
|
+
return String(message || '').split(/\r?\n/).slice(1).join('\n').trim();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function commitSummary(result) {
|
|
97
|
+
const line = String(result.stdout || '').split(/\r?\n/).find((row) => /^\[[^\]]+\s+[0-9a-f]+\]/.test(row.trim()));
|
|
98
|
+
return line?.trim() || String(result.stdout || '').split(/\r?\n/).find(Boolean)?.trim() || 'commit created';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function argValue(args = [], name) {
|
|
102
|
+
const index = args.indexOf(name);
|
|
103
|
+
if (index >= 0 && args[index + 1] && !String(args[index + 1]).startsWith('--')) return args[index + 1];
|
|
104
|
+
const prefix = `${name}=`;
|
|
105
|
+
const hit = args.find((arg) => String(arg).startsWith(prefix));
|
|
106
|
+
return hit ? String(hit).slice(prefix.length) : null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function printSimpleGitCommit(result) {
|
|
110
|
+
if (!result.ok) {
|
|
111
|
+
console.error(`Git ${result.action || 'commit'} failed: ${result.reason}`);
|
|
112
|
+
if (result.command?.stderr) console.error(result.command.stderr);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
console.log(`Git ${result.action}: ${result.hash}`);
|
|
116
|
+
console.log(result.commit);
|
|
117
|
+
if (result.pushed) console.log('Push: ok');
|
|
118
|
+
}
|
package/src/core/pipeline.mjs
CHANGED
|
@@ -1415,7 +1415,7 @@ async function complianceBlock(root, state = {}, reason = '', detail = {}) {
|
|
|
1415
1415
|
};
|
|
1416
1416
|
await writeJsonAtomic(guardPath, record);
|
|
1417
1417
|
await appendJsonl(path.join(dir, 'events.jsonl'), { ts: nowIso(), type: 'pipeline.compliance_loop_guard', gate: record.gate, repeat_count: count, limit, tripped: record.tripped, missing: record.missing });
|
|
1418
|
-
if (!record.tripped) return { decision: 'block', reason };
|
|
1418
|
+
if (!record.tripped) return { decision: 'block', reason, gate: detail.gate || state.stop_gate || null, missing: Array.isArray(detail.missing) ? detail.missing : [] };
|
|
1419
1419
|
await writeJsonAtomic(path.join(dir, HARD_BLOCKER_ARTIFACT), {
|
|
1420
1420
|
passed: true,
|
|
1421
1421
|
created_at: nowIso(),
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const ROUTE_FINALIZER_FIXTURE_ROUTES = Object.freeze([
|
|
2
|
+
'$Team',
|
|
3
|
+
'$DFix',
|
|
4
|
+
'$QA-LOOP',
|
|
5
|
+
'$Research',
|
|
6
|
+
'$AutoResearch',
|
|
7
|
+
'$PPT',
|
|
8
|
+
'$Image-UX-Review',
|
|
9
|
+
'$UX-Review',
|
|
10
|
+
'$From-Chat-IMG',
|
|
11
|
+
'$Computer-Use',
|
|
12
|
+
'$CU',
|
|
13
|
+
'$DB',
|
|
14
|
+
'$Wiki',
|
|
15
|
+
'$GX',
|
|
16
|
+
'$Goal',
|
|
17
|
+
'$MAD-SKS',
|
|
18
|
+
'hproof',
|
|
19
|
+
'proof-field',
|
|
20
|
+
'recallpulse'
|
|
21
|
+
]);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { normalizeProofRoute, routeRequiresCompletionProof, routeRequiresImageVoxelAnchors } from './route-proof-policy.mjs';
|
|
2
|
+
|
|
3
|
+
export function routeFinalizerPolicy(route, opts = {}) {
|
|
4
|
+
const normalized = normalizeProofRoute(route);
|
|
5
|
+
const visual = routeRequiresImageVoxelAnchors(normalized, opts);
|
|
6
|
+
return {
|
|
7
|
+
route: normalized,
|
|
8
|
+
requires_completion_proof: routeRequiresCompletionProof(normalized),
|
|
9
|
+
requires_image_voxel_anchors: visual,
|
|
10
|
+
requires_before_after_relation: Boolean(opts.fixClaim || opts.requireRelation || opts.beforeAfterClaim),
|
|
11
|
+
strict: Boolean(opts.strict)
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { collectProofEvidence } from './evidence-collector.mjs';
|
|
2
|
+
import { writeRouteCompletionProof } from './route-adapter.mjs';
|
|
3
|
+
import { routeFinalizerPolicy } from './route-finalizer-policy.mjs';
|
|
4
|
+
import { ensureRouteImageEvidence } from '../wiki-image/route-image-evidence.mjs';
|
|
5
|
+
|
|
6
|
+
export async function finalizeRouteWithProof(root, {
|
|
7
|
+
missionId,
|
|
8
|
+
route,
|
|
9
|
+
gateFile = null,
|
|
10
|
+
gate = null,
|
|
11
|
+
artifacts = [],
|
|
12
|
+
visualEvidence = null,
|
|
13
|
+
dbEvidence = null,
|
|
14
|
+
testEvidence = null,
|
|
15
|
+
commandEvidence = null,
|
|
16
|
+
claims = [],
|
|
17
|
+
unverified = [],
|
|
18
|
+
blockers = [],
|
|
19
|
+
statusHint = 'verified_partial',
|
|
20
|
+
strict = false,
|
|
21
|
+
mock = false,
|
|
22
|
+
fixClaim = false,
|
|
23
|
+
requireRelation = false
|
|
24
|
+
} = {}) {
|
|
25
|
+
const policy = routeFinalizerPolicy(route, { strict, fixClaim, requireRelation });
|
|
26
|
+
const localBlockers = [...blockers];
|
|
27
|
+
let imageEvidence = visualEvidence;
|
|
28
|
+
if (policy.requires_image_voxel_anchors) {
|
|
29
|
+
imageEvidence = await ensureRouteImageEvidence(root, {
|
|
30
|
+
missionId,
|
|
31
|
+
route: policy.route,
|
|
32
|
+
mock,
|
|
33
|
+
requireRelation: policy.requires_before_after_relation,
|
|
34
|
+
source: 'route-finalizer'
|
|
35
|
+
});
|
|
36
|
+
if (!imageEvidence.ok) {
|
|
37
|
+
localBlockers.push(...(imageEvidence.issues?.length ? imageEvidence.issues : ['image_voxel_anchors_missing']));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const collected = await collectProofEvidence(root);
|
|
41
|
+
const status = localBlockers.length
|
|
42
|
+
? (strict ? 'blocked' : statusHint === 'verified' ? 'verified_partial' : statusHint)
|
|
43
|
+
: statusHint;
|
|
44
|
+
const evidence = {
|
|
45
|
+
...collected,
|
|
46
|
+
...(dbEvidence ? { db: dbEvidence } : {}),
|
|
47
|
+
...(testEvidence ? { tests: testEvidence } : {}),
|
|
48
|
+
...(commandEvidence ? { commands: commandEvidence } : {}),
|
|
49
|
+
...(imageEvidence?.ledger ? { image_voxels: {
|
|
50
|
+
schema: 'sks.image-voxel-summary.v1',
|
|
51
|
+
status: imageEvidence.status || 'verified_partial',
|
|
52
|
+
ok: imageEvidence.ok,
|
|
53
|
+
images: imageEvidence.ledger.images?.length || 0,
|
|
54
|
+
anchors: imageEvidence.ledger.anchors?.length || 0,
|
|
55
|
+
anchor_count: imageEvidence.ledger.anchors?.length || 0,
|
|
56
|
+
relations: imageEvidence.ledger.relations?.length || 0,
|
|
57
|
+
mock: Boolean(imageEvidence.mock)
|
|
58
|
+
} } : {}),
|
|
59
|
+
route_gate: gate || (gateFile ? { source: gateFile } : null)
|
|
60
|
+
};
|
|
61
|
+
return writeRouteCompletionProof(root, {
|
|
62
|
+
missionId,
|
|
63
|
+
route: policy.route,
|
|
64
|
+
status,
|
|
65
|
+
gate: evidence.route_gate,
|
|
66
|
+
artifacts,
|
|
67
|
+
evidence,
|
|
68
|
+
claims,
|
|
69
|
+
unverified: [
|
|
70
|
+
...unverified,
|
|
71
|
+
...(imageEvidence?.mock ? ['Image voxel evidence is mock fixture evidence and does not claim a real visual run.'] : [])
|
|
72
|
+
],
|
|
73
|
+
blockers: localBlockers,
|
|
74
|
+
summary: {
|
|
75
|
+
files_changed: collected.files?.length || 0,
|
|
76
|
+
commands_run: evidence.commands?.length || 0,
|
|
77
|
+
tests_passed: Array.isArray(testEvidence) ? testEvidence.filter((row) => row.ok).length : 0,
|
|
78
|
+
tests_failed: Array.isArray(testEvidence) ? testEvidence.filter((row) => row.ok === false).length : 0,
|
|
79
|
+
manual_review_required: status !== 'verified'
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
package/src/core/proof-field.mjs
CHANGED
|
@@ -48,7 +48,7 @@ export const PROOF_CONE_DEFINITIONS = Object.freeze([
|
|
|
48
48
|
{
|
|
49
49
|
id: 'cli_runtime',
|
|
50
50
|
surfaces: ['cli', 'commands', 'runtime'],
|
|
51
|
-
match: [/src\/cli|bin\/sks|
|
|
51
|
+
match: [/src\/cli|src\/commands|bin\/sks|fsx|codex-adapter/i],
|
|
52
52
|
verification: ['npm run packcheck', 'node ./bin/sks.mjs commands --json', 'node ./bin/sks.mjs proof-field scan --json'],
|
|
53
53
|
negative_work: ['browser_ui_e2e']
|
|
54
54
|
},
|
|
@@ -163,7 +163,7 @@ export function validateProofFieldReport(report = {}) {
|
|
|
163
163
|
export async function proofFieldFixture() {
|
|
164
164
|
const report = await buildProofField(process.cwd(), {
|
|
165
165
|
intent: 'small CLI help surface update',
|
|
166
|
-
changedFiles: ['src/
|
|
166
|
+
changedFiles: ['src/commands/help.mjs', 'src/core/routes.mjs']
|
|
167
167
|
});
|
|
168
168
|
return {
|
|
169
169
|
report,
|
package/src/core/routes.mjs
CHANGED
|
@@ -28,7 +28,7 @@ export const FROM_CHAT_IMG_CHECKLIST_ARTIFACT = 'from-chat-img-checklist.md';
|
|
|
28
28
|
export const FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT = 'from-chat-img-temp-triwiki.json';
|
|
29
29
|
export const FROM_CHAT_IMG_QA_LOOP_ARTIFACT = 'from-chat-img-qa-loop.json';
|
|
30
30
|
export const FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS = 5;
|
|
31
|
-
export const USAGE_TOPICS = 'install|setup|bootstrap|root|deps|tmux|auto-review|team|qa-loop|ppt|image-ux-review|goal|research|db|codex-app|hooks|features|all-features|openclaw|dfix|design|imagegen|dollar|context7|pipeline|reasoning|guard|conflicts|versioning|eval|harness|hproof|gx|wiki|code-structure|proof-field|skill-dream';
|
|
31
|
+
export const USAGE_TOPICS = 'install|setup|bootstrap|root|deps|tmux|auto-review|team|qa-loop|ppt|image-ux-review|goal|research|db|codex-app|hooks|features|all-features|openclaw|dfix|commit|commit-and-push|design|imagegen|dollar|context7|pipeline|reasoning|guard|conflicts|versioning|eval|harness|hproof|gx|wiki|code-structure|proof-field|skill-dream';
|
|
32
32
|
export const CODEX_COMPUTER_USE_EVIDENCE_SOURCE = 'codex_computer_use';
|
|
33
33
|
export const CODEX_IMAGEGEN_EVIDENCE_SOURCE = 'codex_app_imagegen_gpt_image_2';
|
|
34
34
|
export const CODEX_APP_IMAGE_GENERATION_DOC_URL = 'https://developers.openai.com/codex/app/features#image-generation';
|
|
@@ -405,6 +405,34 @@ export const ROUTES = [
|
|
|
405
405
|
cliEntrypoint: 'sks goal create|pause|resume|clear|status',
|
|
406
406
|
examples: ['$Goal persist this migration workflow with native /goal continuation']
|
|
407
407
|
},
|
|
408
|
+
{
|
|
409
|
+
id: 'Commit',
|
|
410
|
+
command: '$Commit',
|
|
411
|
+
mode: 'COMMIT',
|
|
412
|
+
route: 'simple git commit',
|
|
413
|
+
description: 'Summarize current git changes, stage them, and create one commit without the full SKS pipeline.',
|
|
414
|
+
requiredSkills: ['honest-mode'],
|
|
415
|
+
lifecycle: ['git_status_summary', 'git_add_all', 'git_commit', 'short_result'],
|
|
416
|
+
context7Policy: 'not_required',
|
|
417
|
+
reasoningPolicy: 'low',
|
|
418
|
+
stopGate: 'none',
|
|
419
|
+
cliEntrypoint: 'sks commit [--message "msg"] [--json]',
|
|
420
|
+
examples: ['$Commit 이번 작업 커밋해줘']
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
id: 'CommitAndPush',
|
|
424
|
+
command: '$Commit-And-Push',
|
|
425
|
+
mode: 'COMMIT_AND_PUSH',
|
|
426
|
+
route: 'simple git commit and push',
|
|
427
|
+
description: 'Summarize current git changes, stage them, create one commit, then run git push without the full SKS pipeline.',
|
|
428
|
+
requiredSkills: ['honest-mode'],
|
|
429
|
+
lifecycle: ['git_status_summary', 'git_add_all', 'git_commit', 'git_push', 'short_result'],
|
|
430
|
+
context7Policy: 'not_required',
|
|
431
|
+
reasoningPolicy: 'low',
|
|
432
|
+
stopGate: 'none',
|
|
433
|
+
cliEntrypoint: 'sks commit-and-push [--message "msg"] [--json]',
|
|
434
|
+
examples: ['$Commit-And-Push 커밋하고 바로 푸쉬해줘']
|
|
435
|
+
},
|
|
408
436
|
{
|
|
409
437
|
id: 'Research',
|
|
410
438
|
command: '$Research',
|
|
@@ -540,6 +568,8 @@ export const COMMAND_CATALOG = [
|
|
|
540
568
|
{ name: 'mad', usage: 'sks --mad [--high]', description: 'Open a one-shot tmux Codex CLI workspace with the SKS MAD full-access auto-review profile.' },
|
|
541
569
|
{ name: 'auto-review', usage: 'sks auto-review status|enable|start [--high] | sks --Auto-review --high', description: 'Enable Codex automatic approval review and launch SKS tmux with the auto-review profile.' },
|
|
542
570
|
{ name: 'dollar-commands', usage: 'sks dollar-commands [--json]', description: 'List Codex App $ commands such as $DFix and $Team.' },
|
|
571
|
+
{ name: 'commit', usage: 'sks commit [--message "msg"] [--json]', description: 'Stage current changes, summarize them, and create a simple git commit without the full SKS pipeline.' },
|
|
572
|
+
{ name: 'commit-and-push', usage: 'sks commit-and-push [--message "msg"] [--json]', description: 'Stage current changes, create a simple git commit, and push without the full SKS pipeline.' },
|
|
543
573
|
{ name: 'dfix', usage: 'sks dfix', description: 'Explain $DFix ultralight direct-fix mode.' },
|
|
544
574
|
{ name: 'qa-loop', usage: 'sks qa-loop prepare|answer|run|status ...', description: 'Dogfood UI/API as human proxy with safety gates, safe fixes, rechecks, Codex Computer Use-only UI evidence, report.' },
|
|
545
575
|
{ name: 'ppt', usage: 'sks ppt build|status <mission-id|latest> [--json]', description: 'Build or inspect $PPT HTML/PDF artifacts from a sealed presentation decision contract.' },
|
|
@@ -26,9 +26,14 @@ export async function rustImageHash(file) {
|
|
|
26
26
|
return runRustOrFallback('image-hash', [file], async () => ({ ok: true, engine: 'js', path: file, sha256: await sha256File(file) }));
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export async function rustVoxelValidate(file) {
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
export async function rustVoxelValidate(file, opts = {}) {
|
|
30
|
+
const args = [file, ...(opts.requireAnchors ? ['--require-anchors'] : []), ...(opts.requireRelations ? ['--require-relations'] : [])];
|
|
31
|
+
return runRustOrFallback('voxel-validate', args, async () => {
|
|
32
|
+
const validation = validateImageVoxelLedger(await readImageVoxelLedger(packageRoot(), file), {
|
|
33
|
+
requireAnchors: opts.requireAnchors,
|
|
34
|
+
requireRelations: opts.requireRelations,
|
|
35
|
+
route: opts.route
|
|
36
|
+
});
|
|
32
37
|
return { ok: validation.ok, engine: 'js', schema: 'sks.image-voxel-ledger.v1', images: validation.summary.images, anchors: validation.summary.anchors, issues: validation.issues };
|
|
33
38
|
});
|
|
34
39
|
}
|
package/src/core/version.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '0.9.
|
|
1
|
+
export const PACKAGE_VERSION = '0.9.14';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { addImageRelation } from './image-voxel-ledger.mjs';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ensureRouteImageEvidence } from './route-image-evidence.mjs';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ensureRouteImageEvidence } from './route-image-evidence.mjs';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ensureRouteImageEvidence } from './route-image-evidence.mjs';
|
|
@@ -30,6 +30,13 @@ export async function readImageVoxelLedger(root = packageRoot(), file = wikiImag
|
|
|
30
30
|
return readJson(file);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
async function readScopedImageVoxelLedger(root = packageRoot(), missionId = null) {
|
|
34
|
+
if (!missionId) return readImageVoxelLedger(root);
|
|
35
|
+
const file = missionImageLedgerPath(root, missionId);
|
|
36
|
+
if (!await exists(file)) return emptyImageVoxelLedger({ mission_id: missionId });
|
|
37
|
+
return readImageVoxelLedger(root, file);
|
|
38
|
+
}
|
|
39
|
+
|
|
33
40
|
export async function writeImageVoxelLedger(root = packageRoot(), ledger = emptyImageVoxelLedger()) {
|
|
34
41
|
await ensureDir(path.dirname(wikiImageLedgerPath(root)));
|
|
35
42
|
const normalized = { ...emptyImageVoxelLedger(), ...ledger, generated_at: nowIso() };
|
|
@@ -65,7 +72,7 @@ export async function ingestImage(root = packageRoot(), imagePath, opts = {}) {
|
|
|
65
72
|
const absolute = path.resolve(root, imagePath);
|
|
66
73
|
const dims = await imageDimensions(absolute);
|
|
67
74
|
const sha256 = await sha256File(absolute);
|
|
68
|
-
const ledger = await
|
|
75
|
+
const ledger = await readScopedImageVoxelLedger(root, opts.missionId || null);
|
|
69
76
|
const rel = path.relative(root, absolute).split(path.sep).join('/');
|
|
70
77
|
const id = opts.id || stableImageId(rel, sha256);
|
|
71
78
|
const image = {
|
|
@@ -100,7 +107,7 @@ export async function imageVoxelSummary(root = packageRoot(), ledgerFile = wikiI
|
|
|
100
107
|
}
|
|
101
108
|
|
|
102
109
|
export async function addVisualAnchor(root = packageRoot(), input = {}) {
|
|
103
|
-
const ledger = await
|
|
110
|
+
const ledger = await readScopedImageVoxelLedger(root, input.missionId || null);
|
|
104
111
|
const image = (ledger.images || []).find((entry) => entry.id === input.imageId);
|
|
105
112
|
const anchor = createVisualAnchor({
|
|
106
113
|
id: input.id || stableAnchorId(input.imageId, input.label, ledger.anchors?.length || 0),
|
|
@@ -120,7 +127,7 @@ export async function addVisualAnchor(root = packageRoot(), input = {}) {
|
|
|
120
127
|
}
|
|
121
128
|
|
|
122
129
|
export async function addImageRelation(root = packageRoot(), input = {}) {
|
|
123
|
-
const ledger = await
|
|
130
|
+
const ledger = await readScopedImageVoxelLedger(root, input.missionId || null);
|
|
124
131
|
const relation = createImageRelation({
|
|
125
132
|
type: input.type || 'before_after',
|
|
126
133
|
beforeImageId: input.beforeImageId,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ensureRouteImageEvidence } from './route-image-evidence.mjs';
|