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.
Files changed (88) hide show
  1. package/README.md +30 -39
  2. package/crates/sks-core/Cargo.lock +99 -1
  3. package/crates/sks-core/Cargo.toml +2 -1
  4. package/crates/sks-core/src/main.rs +77 -43
  5. package/package.json +8 -5
  6. package/src/cli/command-registry.mjs +73 -114
  7. package/src/cli/feature-commands.mjs +44 -5
  8. package/src/cli/install-helpers.mjs +2 -2
  9. package/src/cli/router.mjs +2 -3
  10. package/src/commands/aliases.mjs +2 -0
  11. package/src/commands/auto-review.mjs +5 -0
  12. package/src/commands/autoresearch.mjs +5 -0
  13. package/src/commands/bootstrap.mjs +2 -0
  14. package/src/commands/code-structure.mjs +5 -0
  15. package/src/commands/codex-lb.mjs +61 -3
  16. package/src/commands/commands.mjs +5 -0
  17. package/src/commands/commit-and-push.mjs +5 -0
  18. package/src/commands/commit.mjs +5 -0
  19. package/src/commands/computer-use.mjs +31 -0
  20. package/src/commands/conflicts.mjs +13 -0
  21. package/src/commands/context7.mjs +5 -0
  22. package/src/commands/db.mjs +1 -1
  23. package/src/commands/deps.mjs +5 -0
  24. package/src/commands/dfix.mjs +2 -0
  25. package/src/commands/doctor.mjs +2 -2
  26. package/src/commands/dollar-commands.mjs +2 -0
  27. package/src/commands/eval.mjs +5 -0
  28. package/src/commands/fix-path.mjs +2 -0
  29. package/src/commands/gc.mjs +2 -0
  30. package/src/commands/goal.mjs +5 -0
  31. package/src/commands/guard.mjs +10 -0
  32. package/src/commands/gx.mjs +5 -0
  33. package/src/commands/harness.mjs +5 -0
  34. package/src/commands/help.mjs +3 -74
  35. package/src/commands/hook.mjs +8 -0
  36. package/src/commands/hproof.mjs +5 -0
  37. package/src/commands/image-ux-review.mjs +38 -0
  38. package/src/commands/init.mjs +2 -0
  39. package/src/commands/mad-sks.mjs +14 -0
  40. package/src/commands/memory.mjs +5 -0
  41. package/src/commands/openclaw.mjs +2 -0
  42. package/src/commands/perf.mjs +2 -2
  43. package/src/commands/pipeline.mjs +25 -0
  44. package/src/commands/postinstall.mjs +2 -0
  45. package/src/commands/ppt.mjs +44 -0
  46. package/src/commands/profile.mjs +5 -0
  47. package/src/commands/proof-field.mjs +5 -0
  48. package/src/commands/proof.mjs +22 -1
  49. package/src/commands/qa-loop.mjs +5 -0
  50. package/src/commands/quickstart.mjs +2 -0
  51. package/src/commands/reasoning.mjs +2 -0
  52. package/src/commands/recallpulse.mjs +5 -0
  53. package/src/commands/research.mjs +5 -0
  54. package/src/commands/selftest.mjs +2 -0
  55. package/src/commands/setup.mjs +2 -0
  56. package/src/commands/skill-dream.mjs +5 -0
  57. package/src/commands/stats.mjs +2 -0
  58. package/src/commands/team.mjs +2 -0
  59. package/src/commands/tmux.mjs +5 -0
  60. package/src/commands/update-check.mjs +2 -0
  61. package/src/commands/usage.mjs +2 -0
  62. package/src/commands/validate-artifacts.mjs +2 -0
  63. package/src/commands/versioning.mjs +16 -0
  64. package/src/commands/wiki.mjs +2 -2
  65. package/src/core/codex-lb-circuit.mjs +18 -0
  66. package/src/core/commands/basic-cli.mjs +315 -0
  67. package/src/{cli/maintenance-commands.mjs → core/commands/route-cli.mjs} +37 -37
  68. package/src/core/feature-fixture-runner.mjs +109 -0
  69. package/src/core/feature-fixtures.mjs +1 -1
  70. package/src/core/feature-registry.mjs +19 -7
  71. package/src/core/fsx.mjs +1 -1
  72. package/src/core/git-simple.mjs +118 -0
  73. package/src/core/pipeline.mjs +1 -1
  74. package/src/core/proof/route-finalizer-fixtures.mjs +21 -0
  75. package/src/core/proof/route-finalizer-policy.mjs +13 -0
  76. package/src/core/proof/route-finalizer.mjs +82 -0
  77. package/src/core/proof-field.mjs +2 -2
  78. package/src/core/routes.mjs +31 -1
  79. package/src/core/rust-accelerator.mjs +8 -3
  80. package/src/core/version.mjs +1 -1
  81. package/src/core/wiki-image/before-after-relation.mjs +1 -0
  82. package/src/core/wiki-image/computer-use-evidence.mjs +1 -0
  83. package/src/core/wiki-image/generated-review-parser.mjs +1 -0
  84. package/src/core/wiki-image/image-ux-evidence.mjs +1 -0
  85. package/src/core/wiki-image/image-voxel-ledger.mjs +10 -3
  86. package/src/core/wiki-image/ppt-image-evidence.mjs +1 -0
  87. package/src/core/wiki-image/route-image-evidence.mjs +107 -0
  88. 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', null, [], 'not_required');
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) >= 45, [`pass=${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
- return {
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
- const text = await readText(path.join(root, 'src', 'cli', 'legacy-main.mjs'), '');
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.13';
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
+ }
@@ -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
+ }
@@ -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|maintenance-commands|fsx|codex-adapter/i],
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/cli/maintenance-commands.mjs', 'src/core/routes.mjs']
166
+ changedFiles: ['src/commands/help.mjs', 'src/core/routes.mjs']
167
167
  });
168
168
  return {
169
169
  report,
@@ -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
- return runRustOrFallback('voxel-validate', [file], async () => {
31
- const validation = validateImageVoxelLedger(await readImageVoxelLedger(packageRoot(), file));
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
  }
@@ -1 +1 @@
1
- export const PACKAGE_VERSION = '0.9.13';
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 readImageVoxelLedger(root);
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 readImageVoxelLedger(root);
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 readImageVoxelLedger(root);
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';