sneakoscope 7.3.0 → 7.5.0

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 (151) hide show
  1. package/README.md +117 -5
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/dist/cli/codex-app-command.js +1 -1
  5. package/dist/cli/command-manifest-lite.js +2 -1
  6. package/dist/cli/install-helpers-codex-lb-selftest.js +2 -1
  7. package/dist/cli/install-helpers-install-support.js +6 -16
  8. package/dist/cli/install-helpers.js +59 -50
  9. package/dist/cli/router.js +9 -1
  10. package/dist/config/skills-manifest.json +50 -71
  11. package/dist/core/codex-app.js +5 -5
  12. package/dist/core/codex-native/core-skill-manifest.js +9 -6
  13. package/dist/core/commands/image-ux-review-command.js +243 -50
  14. package/dist/core/commands/naruto-command.js +245 -8
  15. package/dist/core/commands/ppt-command.js +2 -2
  16. package/dist/core/commands/telegram-command.js +123 -10
  17. package/dist/core/commands/triwiki-command.js +11 -1
  18. package/dist/core/commands/triwiki-graph-command.js +160 -0
  19. package/dist/core/commands/wiki-command.js +100 -20
  20. package/dist/core/errors/message.js +3 -0
  21. package/dist/core/feature-registry.js +2 -2
  22. package/dist/core/fsx.js +7 -3
  23. package/dist/core/hooks-runtime/codex-commit-hooks-selftest.js +82 -0
  24. package/dist/core/hooks-runtime/context-graph-freshness-preflight.js +113 -0
  25. package/dist/core/hooks-runtime/official-subagent-lifecycle.js +1 -1
  26. package/dist/core/hooks-runtime/tool-output-quarantine.js +1 -1
  27. package/dist/core/hooks-runtime.js +28 -86
  28. package/dist/core/image-ux-review/callout-extraction.js +6 -0
  29. package/dist/core/image-ux-review/imagegen-adapter.js +54 -18
  30. package/dist/core/image-ux-review/real-callout-extractor.js +1 -0
  31. package/dist/core/image-ux-review.js +182 -7
  32. package/dist/core/imagegen/codex-lb-imagegen-target.js +22 -7
  33. package/dist/core/imagegen/imagegen-capability.js +11 -4
  34. package/dist/core/imagegen/require-imagegen.js +29 -5
  35. package/dist/core/init/skills.js +22 -10
  36. package/dist/core/install/installed-package-smoke.js +157 -4
  37. package/dist/core/managed-path-safety.js +8 -10
  38. package/dist/core/mcp-config/backup.js +2 -4
  39. package/dist/core/mcp-config/config-reader.js +2 -4
  40. package/dist/core/mcp-config/scope.js +3 -5
  41. package/dist/core/naruto/context-graph-advisor-scope.js +242 -0
  42. package/dist/core/naruto/context-graph-advisor.js +0 -0
  43. package/dist/core/pipeline-internals/runtime-core.js +44 -0
  44. package/dist/core/policy/latest-version-guidance.js +113 -0
  45. package/dist/core/ppt.js +8 -8
  46. package/dist/core/release/gate-manifest.js +32 -1
  47. package/dist/core/release/npm-stage-contract.js +21 -0
  48. package/dist/core/release/package-size-budget.js +2 -2
  49. package/dist/core/release/release-gate-contract.js +5 -0
  50. package/dist/core/release/stage-publish.js +27 -0
  51. package/dist/core/responses-stream.js +65 -19
  52. package/dist/core/routes/design-policy.js +7 -2
  53. package/dist/core/routes/dollar-manifest-lite.js +0 -4
  54. package/dist/core/routes/evidence.js +1 -1
  55. package/dist/core/routes.js +12 -8
  56. package/dist/core/safety/command-contract/contracts.js +2 -0
  57. package/dist/core/safety/command-contract/types.js +1 -1
  58. package/dist/core/search/context-graph-seeds.js +205 -0
  59. package/dist/core/search/context.js +0 -0
  60. package/dist/core/search/provider.js +2 -2
  61. package/dist/core/subagents/naruto-help-contract.js +2 -1
  62. package/dist/core/subagents/naruto-host-credentials.js +161 -0
  63. package/dist/core/subagents/official-subagent-preparation.js +4 -1
  64. package/dist/core/subagents/official-subagent-prompt.js +44 -2
  65. package/dist/core/subagents/official-subagent-runner.js +26 -8
  66. package/dist/core/subagents/triwiki-attention.js +135 -123
  67. package/dist/core/telegram/bot-api.js +59 -4
  68. package/dist/core/telegram/config.js +3 -2
  69. package/dist/core/telegram/hub.js +3 -2
  70. package/dist/core/telegram/owner-lock.js +4 -6
  71. package/dist/core/telegram/setup.js +223 -73
  72. package/dist/core/triwiki/code-pack.js +12 -120
  73. package/dist/core/triwiki/context-graph/compiler/cache-key.js +277 -0
  74. package/dist/core/triwiki/context-graph/compiler/extract.js +104 -0
  75. package/dist/core/triwiki/context-graph/compiler/freshness.js +108 -0
  76. package/dist/core/triwiki/context-graph/compiler/index.js +237 -0
  77. package/dist/core/triwiki/context-graph/compiler/merge.js +0 -0
  78. package/dist/core/triwiki/context-graph/compiler/serialize.js +115 -0
  79. package/dist/core/triwiki/context-graph/contracts.js +236 -0
  80. package/dist/core/triwiki/context-graph/extractors/code/builders.js +150 -0
  81. package/dist/core/triwiki/context-graph/extractors/code/declarations.js +161 -0
  82. package/dist/core/triwiki/context-graph/extractors/code/index.js +265 -0
  83. package/dist/core/triwiki/context-graph/extractors/code/inventory.js +237 -0
  84. package/dist/core/triwiki/context-graph/extractors/code/module-graph.js +252 -0
  85. package/dist/core/triwiki/context-graph/extractors/code/modules.js +79 -0
  86. package/dist/core/triwiki/context-graph/extractors/code/references.js +184 -0
  87. package/dist/core/triwiki/context-graph/extractors/code/selection.js +100 -0
  88. package/dist/core/triwiki/context-graph/extractors/code/ts-config.js +161 -0
  89. package/dist/core/triwiki/context-graph/extractors/code/types.js +18 -0
  90. package/dist/core/triwiki/context-graph/extractors/evidence/claims.js +289 -0
  91. package/dist/core/triwiki/context-graph/extractors/evidence/index.js +54 -0
  92. package/dist/core/triwiki/context-graph/extractors/evidence/proof-index.js +213 -0
  93. package/dist/core/triwiki/context-graph/extractors/evidence/proofs.js +266 -0
  94. package/dist/core/triwiki/context-graph/extractors/evidence/redaction.js +177 -0
  95. package/dist/core/triwiki/context-graph/extractors/evidence/shared.js +0 -0
  96. package/dist/core/triwiki/context-graph/extractors/evidence/sources.js +154 -0
  97. package/dist/core/triwiki/context-graph/extractors/index.js +7 -0
  98. package/dist/core/triwiki/context-graph/extractors/topology/commands.js +207 -0
  99. package/dist/core/triwiki/context-graph/extractors/topology/gate-edges.js +201 -0
  100. package/dist/core/triwiki/context-graph/extractors/topology/gates.js +182 -0
  101. package/dist/core/triwiki/context-graph/extractors/topology/globs.js +124 -0
  102. package/dist/core/triwiki/context-graph/extractors/topology/index.js +92 -0
  103. package/dist/core/triwiki/context-graph/extractors/topology/routes.js +135 -0
  104. package/dist/core/triwiki/context-graph/extractors/topology/shared.js +215 -0
  105. package/dist/core/triwiki/context-graph/graph-index.js +143 -0
  106. package/dist/core/triwiki/context-graph/ids.js +0 -0
  107. package/dist/core/triwiki/context-graph/lint/index.js +0 -0
  108. package/dist/core/triwiki/context-graph/lint/rules.js +232 -0
  109. package/dist/core/triwiki/context-graph/lint/warnings.js +93 -0
  110. package/dist/core/triwiki/context-graph/paths.js +116 -0
  111. package/dist/core/triwiki/context-graph/profiles.js +78 -0
  112. package/dist/core/triwiki/context-graph/projections/anchors.js +91 -0
  113. package/dist/core/triwiki/context-graph/projections/attention.js +58 -0
  114. package/dist/core/triwiki/context-graph/projections/code-pack-workspace.js +62 -0
  115. package/dist/core/triwiki/context-graph/projections/code-pack.js +144 -0
  116. package/dist/core/triwiki/context-graph/projections/index.js +7 -0
  117. package/dist/core/triwiki/context-graph/projections/module-view.js +140 -0
  118. package/dist/core/triwiki/context-graph/projections/node-summary.js +118 -0
  119. package/dist/core/triwiki/context-graph/projections/pack-contract.js +6 -0
  120. package/dist/core/triwiki/context-graph/query/explain.js +0 -0
  121. package/dist/core/triwiki/context-graph/query/index.js +178 -0
  122. package/dist/core/triwiki/context-graph/query/load.js +116 -0
  123. package/dist/core/triwiki/context-graph/query/pack.js +117 -0
  124. package/dist/core/triwiki/context-graph/query/rank.js +142 -0
  125. package/dist/core/triwiki/context-graph/query/ranking-config.js +64 -0
  126. package/dist/core/triwiki/context-graph/query/seeds.js +241 -0
  127. package/dist/core/triwiki/context-graph/query/snapshot-cache.js +72 -0
  128. package/dist/core/triwiki/context-graph/query/traverse.js +154 -0
  129. package/dist/core/triwiki/context-graph/query-types.js +29 -0
  130. package/dist/core/triwiki/context-graph/store/compile-lock.js +21 -0
  131. package/dist/core/triwiki/context-graph/store/event-log.js +48 -0
  132. package/dist/core/triwiki/context-graph/store/fragment-cache.js +85 -0
  133. package/dist/core/triwiki/context-graph/store/graph-status.js +85 -0
  134. package/dist/core/triwiki/context-graph/store/snapshot-store.js +155 -0
  135. package/dist/core/triwiki/triwiki-proof-bank-index-repair.js +115 -0
  136. package/dist/core/triwiki/triwiki-proof-bank-index-store.js +290 -0
  137. package/dist/core/triwiki/triwiki-proof-bank-index.js +126 -0
  138. package/dist/core/triwiki/triwiki-proof-bank.js +7 -1
  139. package/dist/core/verification/context-graph-affected.js +271 -0
  140. package/dist/core/version.js +1 -1
  141. package/dist/scripts/context-graph-check.js +191 -0
  142. package/dist/scripts/helper-dedup-check.js +0 -1
  143. package/dist/scripts/installed-package-smoke-check.js +4 -1
  144. package/dist/scripts/latest-version-guidance-check.js +74 -0
  145. package/dist/scripts/release-metadata-check.js +20 -3
  146. package/dist/scripts/ux-review-imagegen-blackbox-check.js +25 -1
  147. package/dist/scripts/ux-review-run-wires-imagegen-check.js +9 -1
  148. package/package.json +5 -3
  149. package/release-gates.v2.json +174 -0
  150. package/schemas/triwiki/context-graph.schema.json +119 -0
  151. package/dist/core/triwiki/code-index-scanner.js +0 -309
@@ -0,0 +1,160 @@
1
+ import { flag, readOption } from '../../cli/args.js';
2
+ import { CONTEXT_GRAPH_REPAIR_COMMAND } from '../triwiki/context-graph/contracts.js';
3
+ import { contextGraphFreshnessPreflight } from '../hooks-runtime/context-graph-freshness-preflight.js';
4
+ import { contextGraphExtractors } from '../triwiki/context-graph/extractors/index.js';
5
+ import { runContextGraphLint } from '../triwiki/context-graph/lint/index.js';
6
+ import { contextGraphQueryProfile } from '../triwiki/context-graph/profiles.js';
7
+ import { queryContextGraph } from '../triwiki/context-graph/query/index.js';
8
+ import { contextGraphStatus } from '../triwiki/context-graph/store/graph-status.js';
9
+ import { readContextGraphMeta, readContextGraphSnapshot } from '../triwiki/context-graph/store/snapshot-store.js';
10
+ export const TRIWIKI_GRAPH_SUBCOMMANDS = ['graph-status', 'graph-lint', 'graph-query'];
11
+ export function isTriWikiGraphSubcommand(value) {
12
+ return typeof value === 'string' && TRIWIKI_GRAPH_SUBCOMMANDS.includes(value);
13
+ }
14
+ function issueLine(issue) {
15
+ const where = issue.nodeId ?? issue.edgeId ?? issue.path ?? '';
16
+ return `${issue.severity}: ${issue.code}${where ? ` ${where}` : ''} — ${issue.message}`;
17
+ }
18
+ async function graphStatusFast(root) {
19
+ const preflight = await contextGraphFreshnessPreflight(root);
20
+ return {
21
+ result: { ...preflight, mode: 'fast', ok: preflight.usable },
22
+ ok: preflight.usable,
23
+ lines: [
24
+ `Context graph: ${preflight.status} (${preflight.node_count} nodes, ${preflight.edge_count} edges, ${preflight.coverage})`,
25
+ ...(preflight.reasons.length ? [`Stale reasons: ${preflight.reasons.join(', ')}`] : []),
26
+ ...(preflight.unverified_reasons.length ? [`Not evaluated: ${preflight.unverified_reasons.join(', ')}`] : []),
27
+ ...(preflight.usable ? [] : [`Repair: ${preflight.repair_command}`])
28
+ ]
29
+ };
30
+ }
31
+ async function graphStatus(root) {
32
+ const status = await contextGraphStatus(root, { extractors: contextGraphExtractors() });
33
+ const meta = await readContextGraphMeta(root);
34
+ const result = {
35
+ schema: 'sks.triwiki-graph-status.v1',
36
+ ok: status.status === 'fresh',
37
+ status: status.status,
38
+ snapshot_hash: status.snapshotHash,
39
+ generated_at: status.generatedAt,
40
+ stale_reasons: status.reasons,
41
+ error_code: status.errorCode,
42
+ node_count: status.nodeCount,
43
+ edge_count: status.edgeCount,
44
+ lint: meta.status === 'ok' && meta.meta ? meta.meta.lint : null,
45
+ repair_command: status.repairCommand
46
+ };
47
+ const lines = [
48
+ `Context graph: ${status.status} (${status.nodeCount} nodes, ${status.edgeCount} edges)`,
49
+ ...(status.snapshotHash ? [`Snapshot: ${status.snapshotHash.slice(0, 16)}`] : []),
50
+ ...(status.reasons.length ? [`Stale reasons: ${status.reasons.join(', ')}`] : []),
51
+ ...(status.status === 'fresh' ? [] : [`Repair: ${status.repairCommand}`])
52
+ ];
53
+ return { result, ok: result.ok, lines };
54
+ }
55
+ async function graphLint(root) {
56
+ const snapshotLoad = await readContextGraphSnapshot(root);
57
+ if (snapshotLoad.status !== 'ok' || !snapshotLoad.snapshot) {
58
+ const errorCode = snapshotLoad.status === 'missing' ? 'context_graph_missing' : 'context_graph_corrupt';
59
+ return {
60
+ result: {
61
+ schema: 'sks.triwiki-graph-lint.v1',
62
+ ok: false,
63
+ error_code: errorCode,
64
+ errors: [],
65
+ warnings: [],
66
+ repair_command: CONTEXT_GRAPH_REPAIR_COMMAND
67
+ },
68
+ ok: false,
69
+ lines: [`Context graph lint: ${errorCode}`, `Repair: ${CONTEXT_GRAPH_REPAIR_COMMAND}`]
70
+ };
71
+ }
72
+ const meta = await readContextGraphMeta(root);
73
+ const lint = runContextGraphLint({
74
+ root,
75
+ snapshot: snapshotLoad.snapshot,
76
+ meta: meta.status === 'ok' ? meta.meta : null
77
+ });
78
+ const result = {
79
+ schema: 'sks.triwiki-graph-lint.v1',
80
+ ok: lint.ok,
81
+ snapshot_hash: snapshotLoad.snapshot.snapshotHash,
82
+ error_count: lint.errorCount,
83
+ warning_count: lint.warningCount,
84
+ errors: lint.errors.map(issueLine),
85
+ warnings: lint.warnings.slice(0, 40).map(issueLine),
86
+ repair_command: CONTEXT_GRAPH_REPAIR_COMMAND
87
+ };
88
+ return {
89
+ result,
90
+ ok: lint.ok,
91
+ lines: [
92
+ `Context graph lint: ${lint.ok ? 'ok' : 'blocked'} (${lint.errorCount} errors, ${lint.warningCount} warnings)`,
93
+ ...lint.errors.slice(0, 20).map((issue) => `- ${issueLine(issue)}`)
94
+ ]
95
+ };
96
+ }
97
+ async function graphQuery(root, args) {
98
+ const query = args.find((arg) => !arg.startsWith('-')) ?? '';
99
+ const profile = contextGraphQueryProfile(readOption(args, '--profile', 'implementation')).name;
100
+ const budgetRaw = readOption(args, '--token-budget', '');
101
+ const parsedBudget = budgetRaw ? Number.parseInt(String(budgetRaw), 10) : Number.NaN;
102
+ if (!query) {
103
+ return {
104
+ result: { schema: 'sks.triwiki-graph-query.v1', ok: false, errors: ['missing_query'] },
105
+ ok: false,
106
+ lines: ['Usage: sks triwiki graph-query "<query>" [--profile implementation|review|planning|answer] [--json]']
107
+ };
108
+ }
109
+ const answer = await queryContextGraph({
110
+ root,
111
+ query,
112
+ profile,
113
+ ...(Number.isFinite(parsedBudget) ? { tokenBudget: parsedBudget } : {})
114
+ });
115
+ const result = {
116
+ schema: 'sks.triwiki-graph-query.v1',
117
+ ok: answer.ok,
118
+ profile: answer.profile,
119
+ snapshot_hash: answer.snapshotHash,
120
+ snapshot_freshness: answer.snapshotFreshness,
121
+ seed_count: answer.seedCount,
122
+ visited_nodes: answer.visitedNodes,
123
+ selected: answer.selected.map((node) => ({
124
+ id: node.nodeId,
125
+ kind: node.kind,
126
+ path: node.path ?? null,
127
+ score: node.score,
128
+ trust: node.trust,
129
+ freshness: node.freshness,
130
+ token_cost: node.tokenCost,
131
+ reason_path: node.reasonPath,
132
+ provenance: node.provenance
133
+ })),
134
+ provenance_coverage: answer.provenanceCoverage,
135
+ stale_excluded: answer.staleExcluded,
136
+ invalidated_excluded: answer.invalidatedExcluded,
137
+ token_cost: answer.tokenCost,
138
+ token_budget: answer.tokenBudget,
139
+ truncated: answer.truncated,
140
+ timeout: answer.timeout,
141
+ omission_reasons: answer.omissionReasons,
142
+ warnings: answer.warnings,
143
+ errors: answer.errors,
144
+ repair_command: CONTEXT_GRAPH_REPAIR_COMMAND
145
+ };
146
+ const lines = answer.ok
147
+ ? [
148
+ `Context graph query (${answer.profile}): ${answer.selected.length} nodes, ${answer.tokenCost}/${answer.tokenBudget} tokens`,
149
+ ...answer.selected.slice(0, 12).map((node) => `- ${node.nodeId} [${node.reasonPath.join(' ')}]`)
150
+ ]
151
+ : [`Context graph query blocked: ${answer.errors.join(', ')}`, `Repair: ${CONTEXT_GRAPH_REPAIR_COMMAND}`];
152
+ return { result, ok: answer.ok, lines };
153
+ }
154
+ export async function triwikiGraphCommand(root, sub, args) {
155
+ if (sub === 'graph-status')
156
+ return flag(args, '--fast') ? graphStatusFast(root) : graphStatus(root);
157
+ if (sub === 'graph-lint')
158
+ return graphLint(root);
159
+ return graphQuery(root, args.filter((arg) => arg !== 'graph-query' && !flag([arg], '--json')));
160
+ }
@@ -20,8 +20,15 @@ import { wrongnessContextForRoute } from '../triwiki-wrongness/wrongness-retriev
20
20
  import { readCombinedWrongnessRecords } from '../triwiki-wrongness/wrongness-ledger.js';
21
21
  import { recordImageWrongnessFromValidation } from '../triwiki-wrongness/image-wrongness.js';
22
22
  import { publishSharedMemory, rebuildSharedIndexes, sharedMemorySummary, validateSharedMemory } from '../git-hygiene/shared-memory-publish.js';
23
- import { scanCodebaseIndex } from '../triwiki/code-index-scanner.js';
24
- import { buildCodePack, validateCodePack, writeCodePackAtomic } from '../triwiki/code-pack.js';
23
+ import { codePackPath, isCodePackProjectionBoundToSnapshot, validateCodePack, writeCodePackAtomic } from '../triwiki/code-pack.js';
24
+ import { repairTriWikiProofIndex } from '../triwiki/triwiki-proof-bank-index.js';
25
+ import { runContextGraphLint } from '../triwiki/context-graph/lint/index.js';
26
+ import { contextGraphStatus } from '../triwiki/context-graph/store/graph-status.js';
27
+ import { readContextGraphMeta, readContextGraphSnapshot } from '../triwiki/context-graph/store/snapshot-store.js';
28
+ import { compileContextGraph } from '../triwiki/context-graph/compiler/index.js';
29
+ import { contextGraphExtractors } from '../triwiki/context-graph/extractors/index.js';
30
+ import { buildWorkspaceCodePack } from '../triwiki/context-graph/projections/code-pack-workspace.js';
31
+ import { CONTEXT_GRAPH_REPAIR_COMMAND } from '../triwiki/context-graph/contracts.js';
25
32
  import { inspectCodePackHeadFreshness } from '../triwiki/code-pack-head-freshness.js';
26
33
  import { sealTriWikiContextPack, validateTriWikiContextPackProvenance } from '../triwiki-provenance.js';
27
34
  import { flag, positionalArgs, readFlagValue, readOption, resolveMissionId } from './command-utils.js';
@@ -205,46 +212,119 @@ export async function wikiCommand(sub, args = []) {
205
212
  const pack = await readJson(path.resolve(target));
206
213
  const { result, trustAnchors } = wikiValidationResult(pack, root);
207
214
  const codePack = await codePackFreshness(root);
208
- process.exitCode = result.ok ? 0 : 2;
209
- if (flag(args, '--json'))
210
- return console.log(JSON.stringify({ ...result, code_pack: codePack }, null, 2));
215
+ const graph = await wikiValidateContextGraph(root);
216
+ const ok = result.ok && graph.ok;
217
+ process.exitCode = ok ? 0 : 2;
218
+ if (flag(args, '--json')) {
219
+ return console.log(JSON.stringify({ ...result, ok, code_pack: codePack, context_graph: graph }, null, 2));
220
+ }
211
221
  console.log(`Wiki coordinate index: ${result.ok ? 'ok' : 'failed'}`);
212
222
  console.log(`Anchors checked: ${result.checked}`);
213
223
  console.log(`Trust anchors: ${trustAnchors}/${result.checked}`);
214
224
  for (const issue of result.issues)
215
225
  console.log(`- ${issue.severity}: ${issue.id}${issue.anchor ? ` ${issue.anchor}` : ''}`);
216
- console.log(`Code pack: ${codePack.status}${codePack.status === 'stale' ? ' — run `sks wiki refresh --code`' : ''}`);
226
+ console.log(`Code pack: ${codePack.status}${codePack.status === 'stale' ? ` — run \`${CONTEXT_GRAPH_REPAIR_COMMAND}\`` : ''}`);
227
+ console.log(`Context graph: ${graph.status}${graph.ok ? '' : ` — run \`${CONTEXT_GRAPH_REPAIR_COMMAND}\``}`);
228
+ for (const issue of graph.issues)
229
+ console.log(`- ${issue}`);
217
230
  return;
218
231
  }
219
232
  console.error('Usage: sks wiki coords|pack|refresh|publish|rebuild-index|rebuild-summary|validate|validate-shared|wrongness|image-ingest|anchor-add|relation-add|image-validate|image-summary');
220
233
  process.exitCode = 1;
221
234
  }
235
+ async function wikiValidateContextGraph(root) {
236
+ const status = await contextGraphStatus(root, { extractors: contextGraphExtractors() });
237
+ if (status.status !== 'fresh') {
238
+ return {
239
+ ok: false,
240
+ status: status.status,
241
+ snapshot_hash: status.snapshotHash,
242
+ issues: [status.errorCode ?? 'context_graph_not_fresh', ...status.reasons]
243
+ };
244
+ }
245
+ const snapshotLoad = await readContextGraphSnapshot(root);
246
+ const metaLoad = await readContextGraphMeta(root);
247
+ if (snapshotLoad.status !== 'ok' || !snapshotLoad.snapshot || metaLoad.status !== 'ok' || !metaLoad.meta) {
248
+ return { ok: false, status: 'corrupt', snapshot_hash: status.snapshotHash, issues: ['context_graph_snapshot_meta_unreadable'] };
249
+ }
250
+ const lint = runContextGraphLint({ root, snapshot: snapshotLoad.snapshot, meta: metaLoad.meta });
251
+ const issues = lint.errors.slice(0, 20).map((issue) => `${issue.code}: ${issue.message}`);
252
+ const pack = await readJson(codePackPath(root), null).catch(() => null);
253
+ const digest = typeof pack?.index_digest === 'string' ? pack.index_digest : '';
254
+ const boundToSnapshot = Boolean(pack
255
+ && Array.isArray(pack.entries)
256
+ && isCodePackProjectionBoundToSnapshot(snapshotLoad.snapshot.snapshotHash, pack));
257
+ if (digest && !boundToSnapshot)
258
+ issues.push('code_pack_not_bound_to_current_snapshot');
259
+ return {
260
+ ok: lint.ok && (!digest || boundToSnapshot),
261
+ status: 'fresh',
262
+ snapshot_hash: snapshotLoad.snapshot.snapshotHash,
263
+ issues
264
+ };
265
+ }
222
266
  async function wikiRefreshCode(args = []) {
223
267
  const root = await sksRoot();
224
- const index = await scanCodebaseIndex(root);
225
268
  const tokenBudgetRaw = readFlagValue(args, '--token-budget', null);
226
- const tokenBudget = tokenBudgetRaw ? Number.parseInt(String(tokenBudgetRaw), 10) : undefined;
227
- const pack = tokenBudget && Number.isFinite(tokenBudget) ? buildCodePack(root, index, tokenBudget) : buildCodePack(root, index);
228
- const validation = await validateCodePack(pack, root);
229
- const written = validation.ok ? await writeCodePackAtomic(root, pack) : null;
269
+ const parsedBudget = tokenBudgetRaw ? Number.parseInt(String(tokenBudgetRaw), 10) : Number.NaN;
270
+ const tokenBudget = Number.isFinite(parsedBudget) ? parsedBudget : undefined;
271
+ const proofIndex = repairTriWikiProofIndex(root);
272
+ const compiled = await compileContextGraph({
273
+ root,
274
+ extractors: contextGraphExtractors(),
275
+ useFragmentCache: false
276
+ });
277
+ const lintErrors = compiled.issues.filter((issue) => issue.severity === 'error');
278
+ if (!compiled.ok || !compiled.snapshot) {
279
+ const blocked = {
280
+ schema: 'sks.wiki-refresh-code.v1',
281
+ ok: false,
282
+ reason: compiled.reason ?? 'context_graph_compile_failed',
283
+ blockers: compiled.blockers ?? [],
284
+ issues: lintErrors.slice(0, 20).map((issue) => `${issue.code}: ${issue.message}`),
285
+ repair_command: CONTEXT_GRAPH_REPAIR_COMMAND,
286
+ written: null
287
+ };
288
+ process.exitCode = 2;
289
+ process.stderr.write(`wiki-refresh-code: root=${root} graph=blocked reason=${blocked.reason} blockers=${JSON.stringify(blocked.blockers)}\n`);
290
+ if (flag(args, '--json'))
291
+ return console.log(JSON.stringify(blocked, null, 2));
292
+ console.log('Sneakoscope TriWiki Context Graph Refresh');
293
+ console.log(`Context graph: blocked (${blocked.reason})`);
294
+ for (const issue of blocked.issues)
295
+ console.log(`- ${issue}`);
296
+ return;
297
+ }
298
+ const projected = await buildWorkspaceCodePack(root, {
299
+ ...(tokenBudget === undefined ? {} : { tokenBudget }),
300
+ status: { status: 'fresh', snapshotHash: compiled.snapshotHash }
301
+ });
302
+ const pack = projected.pack;
303
+ const validation = pack ? await validateCodePack(pack, root) : { ok: false, issues: projected.errors };
304
+ const written = pack && validation.ok ? await writeCodePackAtomic(root, pack) : null;
230
305
  const result = {
231
306
  schema: 'sks.wiki-refresh-code.v1',
232
307
  ok: validation.ok,
233
308
  issues: validation.issues,
234
- modules: index.modules.length,
235
- truncated: index.truncated,
236
- scanned_file_count: index.scanned_file_count,
237
- entries: pack.entries.length,
238
- token_cost: pack.total_token_cost,
239
- token_budget: pack.token_budget,
309
+ snapshot_hash: compiled.snapshotHash,
310
+ nodes: compiled.snapshot.nodeCount,
311
+ edges: compiled.snapshot.edgeCount,
312
+ graph_warnings: compiled.issues.filter((issue) => issue.severity === 'warning').length,
313
+ incremental: Boolean(compiled.incremental),
314
+ proof_index: { ok: proofIndex.ok, indexed: proofIndex.indexed_count, previous_status: proofIndex.previous_status },
315
+ compile_ms: compiled.durationMs,
316
+ entries: pack ? pack.entries.length : 0,
317
+ token_cost: pack ? pack.total_token_cost : 0,
318
+ token_budget: pack ? pack.token_budget : 0,
240
319
  written: written ? written.path : null
241
320
  };
242
321
  process.exitCode = validation.ok ? 0 : 2;
243
- process.stderr.write(`wiki-refresh-code: root=${root} modules=${index.modules.length} entries=${pack.entries.length} token_cost=${pack.total_token_cost}/${pack.token_budget} issues=${JSON.stringify(validation.issues)}\n`);
322
+ process.stderr.write(`wiki-refresh-code: root=${root} nodes=${result.nodes} edges=${result.edges} entries=${result.entries} token_cost=${result.token_cost}/${result.token_budget} issues=${JSON.stringify(validation.issues)}\n`);
244
323
  if (flag(args, '--json'))
245
324
  return console.log(JSON.stringify(result, null, 2));
246
- console.log('Sneakoscope TriWiki Code Pack Refresh');
247
- console.log(`Code pack refresh: ${validation.ok ? 'ok' : 'blocked'} (${pack.entries.length} entries from ${index.modules.length} modules${index.truncated ? ', truncated' : ''})`);
325
+ console.log('Sneakoscope TriWiki Context Graph Refresh');
326
+ console.log(`Context graph: ok (${result.nodes} nodes, ${result.edges} edges, snapshot ${String(result.snapshot_hash).slice(0, 12)})`);
327
+ console.log(`Code pack projection: ${validation.ok ? 'ok' : 'blocked'} (${result.entries} entries, ${result.token_cost}/${result.token_budget} tokens)`);
248
328
  for (const issue of validation.issues)
249
329
  console.log(`- ${issue}`);
250
330
  }
@@ -1,3 +1,6 @@
1
1
  export function messageOf(error) {
2
2
  return error instanceof Error ? error.message : String(error);
3
3
  }
4
+ export function errorCodeOf(error) {
5
+ return error && typeof error === 'object' && 'code' in error ? String(error.code) : '';
6
+ }
@@ -735,8 +735,8 @@ function imagegenWiringFeatures() {
735
735
  cli_command_names: ['image-ux-review'],
736
736
  handler_keys: ['image-ux-review', 'ux-review'],
737
737
  dollar_commands: ['$Image-UX-Review', '$UX-Review'],
738
- app_skill_aliases: ['$image-ux-review', '$ux-review'],
739
- skills: ['image-ux-review', 'ux-review', 'imagegen']
738
+ app_skill_aliases: ['$image-ux-review'],
739
+ skills: ['image-ux-review', 'imagegen']
740
740
  }
741
741
  }),
742
742
  baseFeature({
package/dist/core/fsx.js CHANGED
@@ -227,18 +227,22 @@ export async function writeReceiptRotated(p, data, opts = {}) {
227
227
  .slice(keep);
228
228
  await Promise.all(removable.map((row) => fsp.rm(row.file, { force: true }).catch(() => undefined)));
229
229
  }
230
- export async function writeBinaryAtomic(p, data) {
230
+ export async function writeBinaryAtomic(p, data, options = {}) {
231
231
  await ensureDir(path.dirname(p));
232
232
  try {
233
- if ((await fsp.readFile(p)).equals(data))
233
+ if (options.mode === undefined && (await fsp.readFile(p)).equals(data))
234
234
  return;
235
235
  }
236
236
  catch { }
237
237
  const tmp = `${p}.${process.pid}.${randomId(6)}.tmp`;
238
238
  try {
239
- const handle = await fsp.open(tmp, 'w');
239
+ const handle = options.mode === undefined
240
+ ? await fsp.open(tmp, 'w')
241
+ : await fsp.open(tmp, 'w', options.mode);
240
242
  try {
241
243
  await handle.writeFile(data);
244
+ if (options.mode !== undefined)
245
+ await handle.chmod(options.mode);
242
246
  await handle.sync().catch(() => { });
243
247
  }
244
248
  finally {
@@ -0,0 +1,82 @@
1
+ import path from 'node:path';
2
+ import { packageRoot, runProcess, tmpdir } from '../fsx.js';
3
+ export async function selftestCodexCommitHooks() {
4
+ const root = tmpdir();
5
+ const hookBin = path.join(packageRoot(), 'dist', 'bin', 'sks.js');
6
+ const env = { SKS_DISABLE_UPDATE_CHECK: '1' };
7
+ const setup = await runProcess(process.execPath, [hookBin, 'setup', '--install-scope', 'project'], { cwd: root, env, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
8
+ if (setup.code !== 0)
9
+ throw new Error(`selftest failed: commit setup ${setup.code}: ${setup.stderr}`);
10
+ const runHook = (name, payload) => runProcess(process.execPath, [hookBin, 'hook', name], { cwd: root, input: JSON.stringify({ cwd: root, ...payload }), env, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
11
+ const id = 'commit-selftest';
12
+ const hook = await runHook('user-prompt-submit', { conversation_id: id, action: 'codex_git_commit', prompt: 'Generate a git commit message for the staged diff.' });
13
+ if (hook.code !== 0)
14
+ throw new Error(`selftest failed: commit hook ${hook.code}: ${hook.stderr}`);
15
+ const hookJson = JSON.parse(hook.stdout);
16
+ if (hookJson.decision === 'block' || hookJson.hookSpecificOutput?.additionalContext || !String(hookJson.systemMessage || '').includes('git action'))
17
+ throw new Error('selftest failed: commit route bypass');
18
+ const stop = await runHook('stop', { conversation_id: id, last_assistant_message: 'Fix Codex App commit message hook bypass' });
19
+ if (stop.code !== 0)
20
+ throw new Error(`selftest failed: commit stop ${stop.code}: ${stop.stderr}`);
21
+ const stopJson = JSON.parse(stop.stdout);
22
+ if (stopJson.decision === 'block' || !String(stopJson.systemMessage || '').includes('accepted without route finalization'))
23
+ throw new Error('selftest failed: commit stop bypass');
24
+ const commitPushId = 'commit-push-selftest';
25
+ const appCommitPushHook = await runHook('user-prompt-submit', { conversation_id: commitPushId, action: 'Codex App Git Actions Commit and Push', prompt: 'Commit and push changes.' });
26
+ if (appCommitPushHook.code !== 0)
27
+ throw new Error(`selftest failed: app commit-push hook ${appCommitPushHook.code}: ${appCommitPushHook.stderr}`);
28
+ const appCommitPushJson = JSON.parse(appCommitPushHook.stdout);
29
+ if (appCommitPushJson.decision === 'block' || appCommitPushJson.hookSpecificOutput?.additionalContext || !String(appCommitPushJson.systemMessage || '').includes('git action'))
30
+ throw new Error('selftest failed: app commit-push route bypass');
31
+ const appCommitPushStop = await runHook('stop', { conversation_id: commitPushId, last_assistant_message: 'Commit and push complete.' });
32
+ if (appCommitPushStop.code !== 0)
33
+ throw new Error(`selftest failed: app commit-push stop ${appCommitPushStop.code}: ${appCommitPushStop.stderr}`);
34
+ if (JSON.parse(appCommitPushStop.stdout).decision === 'block')
35
+ throw new Error('selftest failed: app commit-push stop bypass');
36
+ const appPushId = 'app-push-selftest';
37
+ const appPushHook = await runHook('user-prompt-submit', { conversation_id: appPushId, metadata: { source: 'codex_app', action: 'Git Actions Push' }, prompt: 'Push changes.' });
38
+ if (appPushHook.code !== 0)
39
+ throw new Error(`selftest failed: app push hook ${appPushHook.code}: ${appPushHook.stderr}`);
40
+ const appPushJson = JSON.parse(appPushHook.stdout);
41
+ if (appPushJson.decision === 'block' || appPushJson.hookSpecificOutput?.additionalContext || !String(appPushJson.systemMessage || '').includes('git action'))
42
+ throw new Error('selftest failed: app push metadata route bypass');
43
+ const appPushStop = await runHook('stop', { conversation_id: appPushId, metadata: { source: 'codex_app', action: 'Git Actions Push' }, last_assistant_message: 'Done.' });
44
+ if (appPushStop.code !== 0)
45
+ throw new Error(`selftest failed: app push stop ${appPushStop.code}: ${appPushStop.stderr}`);
46
+ if (JSON.parse(appPushStop.stdout).decision === 'block')
47
+ throw new Error('selftest failed: app push metadata stop bypass');
48
+ const metadataLightId = 'metadata-light-commit-push-selftest';
49
+ const metadataLightHook = await runHook('user-prompt-submit', { conversation_id: metadataLightId, prompt: 'Commit and push changes.' });
50
+ if (metadataLightHook.code !== 0)
51
+ throw new Error(`selftest failed: metadata-light commit-push hook ${metadataLightHook.code}: ${metadataLightHook.stderr}`);
52
+ const metadataLightJson = JSON.parse(metadataLightHook.stdout);
53
+ if (metadataLightJson.decision === 'block' || metadataLightJson.hookSpecificOutput?.additionalContext || !String(metadataLightJson.systemMessage || '').includes('git action'))
54
+ throw new Error('selftest failed: metadata-light app commit-push route bypass');
55
+ const metadataLightStop = await runHook('stop', { conversation_id: metadataLightId, last_assistant_message: 'Commit and push complete.' });
56
+ if (metadataLightStop.code !== 0)
57
+ throw new Error(`selftest failed: metadata-light commit-push stop ${metadataLightStop.code}: ${metadataLightStop.stderr}`);
58
+ if (JSON.parse(metadataLightStop.stdout).decision === 'block')
59
+ throw new Error('selftest failed: metadata-light commit-push stop bypass');
60
+ const settingsHook = await runHook('user-prompt-submit', { model: 'future-codex-catalog-model', metadata: { source: 'codex_app_settings', feature: 'speed profile' } });
61
+ if (settingsHook.code !== 0)
62
+ throw new Error(`selftest failed: settings hook ${settingsHook.code}: ${settingsHook.stderr}`);
63
+ const settingsJson = JSON.parse(settingsHook.stdout);
64
+ if (settingsJson.decision === 'block' || settingsJson.hookSpecificOutput?.additionalContext || !String(settingsJson.systemMessage || '').includes('settings/profile event ignored'))
65
+ throw new Error('selftest failed: settings/profile event should not route or block');
66
+ const userHook = await runHook('user-prompt-submit', { prompt: '[커밋 메시지를 생성하지 못했습니다.] 코덱스 앱에서 이 버그 수정해줘' });
67
+ if (userHook.code !== 0)
68
+ throw new Error(`selftest failed: user commit hook ${userHook.code}: ${userHook.stderr}`);
69
+ if (!JSON.parse(userHook.stdout).hookSpecificOutput?.additionalContext?.includes('$Naruto route prepared'))
70
+ throw new Error('selftest failed: user prompt route');
71
+ const userCommitPushHook = await runHook('user-prompt-submit', { prompt: '배포하게 커밋하고 푸쉬해줘' });
72
+ if (userCommitPushHook.code !== 0)
73
+ throw new Error(`selftest failed: user commit-push hook ${userCommitPushHook.code}: ${userCommitPushHook.stderr}`);
74
+ const userCommitPushJson = JSON.parse(userCommitPushHook.stdout);
75
+ if (userCommitPushJson.decision === 'block' || userCommitPushJson.hookSpecificOutput?.additionalContext || !String(userCommitPushJson.systemMessage || '').includes('git action'))
76
+ throw new Error('selftest failed: user commit-push prompt should bypass route');
77
+ const userCommitPushStop = await runHook('stop', { last_assistant_message: 'Commit and push complete.' });
78
+ if (userCommitPushStop.code !== 0)
79
+ throw new Error(`selftest failed: user commit-push stop ${userCommitPushStop.code}: ${userCommitPushStop.stderr}`);
80
+ if (JSON.parse(userCommitPushStop.stdout).decision === 'block')
81
+ throw new Error('selftest failed: user commit-push stop bypass');
82
+ }
@@ -0,0 +1,113 @@
1
+ import { CONTEXT_GRAPH_REPAIR_COMMAND } from '../triwiki/context-graph/contracts.js';
2
+ import { contextGraphCacheKey } from '../triwiki/context-graph/compiler/cache-key.js';
3
+ import { contextGraphStatus } from '../triwiki/context-graph/store/graph-status.js';
4
+ import { readContextGraphMeta } from '../triwiki/context-graph/store/snapshot-store.js';
5
+ import { contextGraphExtractors } from '../triwiki/context-graph/extractors/index.js';
6
+ export const CONTEXT_GRAPH_FRESHNESS_PREFLIGHT_SCHEMA = 'sks.context-graph-freshness-preflight.v1';
7
+ const CACHE_KEY_DERIVED_REASONS = [
8
+ 'head_changed',
9
+ 'dirty_fingerprint_changed',
10
+ 'schema_revision_changed',
11
+ 'tsconfig_changed',
12
+ 'command_manifest_changed',
13
+ 'gate_manifest_changed',
14
+ 'proof_index_changed',
15
+ 'wiki_context_changed',
16
+ 'git_state_unknown',
17
+ 'cache_key_changed'
18
+ ];
19
+ const PLACEHOLDER_CACHE_KEY_PARTS = {
20
+ workspaceIdentity: 'unevaluated',
21
+ head: null,
22
+ gitState: 'unknown',
23
+ trackedDirtyFingerprint: 'unevaluated',
24
+ untrackedFingerprint: 'unevaluated',
25
+ schemaRevision: 'unevaluated',
26
+ tsconfigHash: 'unevaluated',
27
+ commandManifestHash: 'unevaluated',
28
+ gateManifestHash: 'unevaluated',
29
+ proofIndexHash: 'unevaluated',
30
+ wikiContextHash: 'unevaluated'
31
+ };
32
+ export async function contextGraphFreshnessPreflight(root, options = {}) {
33
+ const verifySources = options.verifySources !== false;
34
+ const extractors = options.extractors ?? contextGraphExtractors();
35
+ const supplied = options.cacheKey ?? null;
36
+ const metaLoad = await readContextGraphMeta(root);
37
+ const meta = metaLoad.status === 'ok' ? metaLoad.meta : null;
38
+ const cacheKey = supplied ?? neutralCacheKey(meta?.cacheKeyParts ?? PLACEHOLDER_CACHE_KEY_PARTS);
39
+ const recordedSources = meta ? Object.keys(meta.inputHashes ?? {}).length : 0;
40
+ const status = await contextGraphStatus(root, {
41
+ extractors,
42
+ cacheKey,
43
+ verifySources,
44
+ ...(options.maxVerifiedSources === undefined ? {} : { maxVerifiedSources: options.maxVerifiedSources })
45
+ });
46
+ const sourcesVerified = verifySources
47
+ && (options.maxVerifiedSources === undefined || options.maxVerifiedSources >= recordedSources);
48
+ const unverified = collectUnverifiedReasons(status.reasons, Boolean(supplied), sourcesVerified);
49
+ return {
50
+ schema: CONTEXT_GRAPH_FRESHNESS_PREFLIGHT_SCHEMA,
51
+ usable: status.status === 'fresh',
52
+ status: status.status,
53
+ reasons: status.reasons,
54
+ unverified_reasons: unverified,
55
+ coverage: supplied ? 'full' : sourcesVerified ? 'artifacts_and_sources' : 'artifacts_only',
56
+ snapshot_hash: status.snapshotHash,
57
+ generated_at: status.generatedAt,
58
+ node_count: status.nodeCount,
59
+ edge_count: status.edgeCount,
60
+ error_code: status.errorCode,
61
+ repair_command: CONTEXT_GRAPH_REPAIR_COMMAND
62
+ };
63
+ }
64
+ export async function contextGraphFreshnessNote(root, options = {}) {
65
+ const budgetMs = Math.max(1, options.budgetMs ?? 750);
66
+ const preflight = await raceWithTimeout(contextGraphFreshnessPreflight(root, options), budgetMs).catch(() => null);
67
+ if (!preflight)
68
+ return null;
69
+ return contextGraphFreshnessNoteFor(preflight);
70
+ }
71
+ export function contextGraphFreshnessNoteFor(preflight) {
72
+ if (preflight.status === 'fresh')
73
+ return null;
74
+ if (preflight.status === 'missing') {
75
+ return `SKS note: the codebase context graph has not been built (${preflight.error_code}). Run \`${preflight.repair_command}\` before relying on graph-cited context.`;
76
+ }
77
+ if (preflight.status === 'corrupt') {
78
+ return `SKS note: the codebase context graph is unusable (${preflight.error_code}). Run \`${preflight.repair_command}\` to rebuild it; no older generation is substituted.`;
79
+ }
80
+ const reasons = preflight.reasons.join(', ');
81
+ return `SKS note: the codebase context graph is stale (${preflight.error_code}: ${reasons}). Run \`${preflight.repair_command}\` to refresh graph-cited context.`;
82
+ }
83
+ function neutralCacheKey(parts) {
84
+ return { key: contextGraphCacheKey(parts), parts, reusable: true, reasons: [], dirtyPaths: [] };
85
+ }
86
+ function collectUnverifiedReasons(fired, cacheKeySupplied, sourcesVerified) {
87
+ const firedSet = new Set(fired);
88
+ const out = [];
89
+ if (!cacheKeySupplied) {
90
+ for (const reason of CACHE_KEY_DERIVED_REASONS) {
91
+ if (!firedSet.has(reason))
92
+ out.push(reason);
93
+ }
94
+ }
95
+ if (!sourcesVerified && !firedSet.has('source_hash_mismatch'))
96
+ out.push('source_hash_mismatch');
97
+ return out;
98
+ }
99
+ async function raceWithTimeout(work, ms) {
100
+ let timer = null;
101
+ const timeout = new Promise((resolve) => {
102
+ timer = setTimeout(() => resolve(null), ms);
103
+ if (timer.unref)
104
+ timer.unref();
105
+ });
106
+ try {
107
+ return await Promise.race([work, timeout]);
108
+ }
109
+ finally {
110
+ if (timer)
111
+ clearTimeout(timer);
112
+ }
113
+ }
@@ -350,7 +350,7 @@ async function rebuildHostCapabilityEvidenceForFinalization(input) {
350
350
  blockers: bound.blockers
351
351
  };
352
352
  }
353
- function completeNarutoTerminalBundle(input) {
353
+ export function completeNarutoTerminalBundle(input) {
354
354
  const runId = String(input.workflowRunId || '').trim();
355
355
  const parent = normalizeSubagentParentSummary(input.parentSummary);
356
356
  return Boolean(runId
@@ -87,6 +87,6 @@ export function interruptedToolOutputRecoveryBlockReason(input = {}) {
87
87
  `SKS blocked same-thread continuation because custom tool call ${callId} has no correlated output (active mission: ${missionId}).`,
88
88
  'The current Codex conversation state may be structurally invalid, so additional context cannot repair the pending Responses request.',
89
89
  'Treat the call result as unknown and do not replay a possibly mutating action.',
90
- 'Upgrade the selected codex-lb to 1.21.0-beta.3 or later, or explicitly switch with `sks codex-lb use-oauth`, then open a fresh Codex thread and continue the persisted mission after inspecting side effects.'
90
+ 'Upgrade the selected codex-lb to the official latest stable release (`sks codex-lb status --json` reports whether the deployment satisfies tool-heavy continuation), or explicitly switch with `sks codex-lb use-oauth`, then open a fresh Codex thread and continue the persisted mission after inspecting side effects.'
91
91
  ].join(' ');
92
92
  }