sneakoscope 8.3.2 → 8.6.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.
- package/README.md +1 -1
- package/config/architecture-map-policy.v1.json +168 -0
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/commands/bridge.js +19 -1
- package/dist/commands/doctor-profile.js +1 -1
- package/dist/commands/doctor.js +97 -16
- package/dist/config/skills-manifest.json +1 -1
- package/dist/core/align/align-route.js +8 -2
- package/dist/core/align/code-navigation-align.js +88 -32
- package/dist/core/architecture-map-pipeline.js +173 -0
- package/dist/core/architecture-map-review.js +180 -0
- package/dist/core/codex/agent-config-file-repair.js +17 -3
- package/dist/core/codex/codex-config-guard.js +3 -1
- package/dist/core/codex/codex-feature-flags.js +10 -0
- package/dist/core/codex/codex-project-config-policy.js +54 -30
- package/dist/core/codex/codex-startup-config-postcheck.js +5 -0
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +2 -1
- package/dist/core/codex-app/menubar/constants.js +1 -0
- package/dist/core/codex-hooks/codex-hook-state-writer.js +2 -0
- package/dist/core/codex-lb/codex-lb-env.js +25 -3
- package/dist/core/codex-lb/combined-catalog/builder.js +7 -2
- package/dist/core/codex-lb/combined-catalog/model-selection.js +71 -0
- package/dist/core/codex-lb/combined-catalog/normalize.js +20 -3
- package/dist/core/codex-lb/desktop-bridge/security.js +11 -8
- package/dist/core/codex-lb/desktop-controller-v3/catalog.js +75 -4
- package/dist/core/codex-lb/desktop-controller-v3.js +5 -1
- package/dist/core/codex-lb/request-route-resolver.js +21 -18
- package/dist/core/commands/triwiki-atlas-command.js +372 -0
- package/dist/core/commands/triwiki-command.js +12 -1
- package/dist/core/doctor/doctor-dirty-planner.js +16 -1
- package/dist/core/doctor/doctor-idempotence.js +11 -0
- package/dist/core/doctor/doctor-repair-postcheck.js +49 -0
- package/dist/core/doctor/doctor-transaction.js +47 -2
- package/dist/core/doctor/legacy-global-hook-cleanup.js +1 -1
- package/dist/core/fsx.js +14 -0
- package/dist/core/git-hygiene/git-policy.js +4 -0
- package/dist/core/git-hygiene/gitignore-writer.js +2 -0
- package/dist/core/gx-renderer.js +2 -1
- package/dist/core/init.js +40 -17
- package/dist/core/managed-paths.js +1 -0
- package/dist/core/perf/release-latency-slo.js +57 -11
- package/dist/core/pipeline-internals/pipeline-stage-builder.js +181 -0
- package/dist/core/pipeline-internals/runtime-core.js +18 -172
- package/dist/core/pipeline-internals/runtime-gates.js +12 -1
- package/dist/core/release/content-digest-cache.js +70 -0
- package/dist/core/release/gate-manifest.js +9 -0
- package/dist/core/release/main-push-guard.js +4 -3
- package/dist/core/release/package-dist-snapshot.js +18 -2
- package/dist/core/release/physical-release-gates.js +26 -3
- package/dist/core/release/release-authorization-snapshot.js +4 -0
- package/dist/core/release/release-gate-cache-v2.js +27 -3
- package/dist/core/release/release-gate-contract.js +2 -0
- package/dist/core/release/release-gate-dag.js +3 -0
- package/dist/core/release/release-inspection-memo.js +69 -0
- package/dist/core/release/release-pack-content-scanner.js +5 -0
- package/dist/core/release/release-pack-receipt.js +28 -18
- package/dist/core/release/release-pack-tarball.js +10 -0
- package/dist/core/release/release-upgrade-baseline.js +4 -0
- package/dist/core/routes.js +1 -1
- package/dist/core/safety/ssot-authority-inventory.js +114 -0
- package/dist/core/safety/ssot-guard.js +2 -44
- package/dist/core/subagents/agent-catalog.js +4 -2
- package/dist/core/subagents/official-subagent-config.js +92 -15
- package/dist/core/subagents/official-subagent-preparation.js +1 -0
- package/dist/core/triwiki/context-graph/architecture/baseline.js +81 -0
- package/dist/core/triwiki/context-graph/architecture/capsule.js +34 -0
- package/dist/core/triwiki/context-graph/architecture/contracts.js +67 -0
- package/dist/core/triwiki/context-graph/architecture/delta.js +149 -0
- package/dist/core/triwiki/context-graph/architecture/finding-factory.js +28 -0
- package/dist/core/triwiki/context-graph/architecture/findings.js +173 -0
- package/dist/core/triwiki/context-graph/architecture/fingerprint.js +75 -0
- package/dist/core/triwiki/context-graph/architecture/index.js +15 -0
- package/dist/core/triwiki/context-graph/architecture/input-bundle.js +66 -0
- package/dist/core/triwiki/context-graph/architecture/metrics.js +137 -0
- package/dist/core/triwiki/context-graph/architecture/policy.js +321 -0
- package/dist/core/triwiki/context-graph/architecture/slice.js +100 -0
- package/dist/core/triwiki/context-graph/architecture/ssot-analysis.js +59 -0
- package/dist/core/triwiki/context-graph/architecture/topology-overlay.js +106 -0
- package/dist/core/triwiki/context-graph/architecture/validation.js +145 -0
- package/dist/core/triwiki/context-graph/architecture/voxel-context.js +39 -0
- package/dist/core/triwiki/context-graph/extractors/index.js +4 -0
- package/dist/core/triwiki/context-graph/projections/index.js +2 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/architecture-delta.js +68 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/ast.js +49 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/change-impact.js +43 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/contracts.js +5 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/escape.js +45 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/ids.js +73 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/index.js +113 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/manifest.js +37 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/module-dependency.js +21 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/ownership-workstream.js +21 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/project-topology.js +13 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/public-surface.js +17 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/reduce.js +44 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/risk-domains.js +23 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/runtime-control.js +20 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/selection-ledger.js +94 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/serializer.js +81 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/ssot-provenance.js +17 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/verification-coverage.js +17 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/view-builder-common.js +1 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/view-builder.js +173 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/view-specs.js +33 -0
- package/dist/core/triwiki/context-graph/store/architecture-map-store.js +71 -0
- package/dist/core/triwiki/triwiki-proof-bank-index.js +26 -1
- package/dist/core/triwiki/triwiki-proof-bank.js +82 -2
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/OverviewViewController.swift +2 -2
- package/dist/native/sks-menubar/Sources/ProvidersModelExposure.swift +178 -0
- package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +10 -1
- package/dist/scripts/architecture-map-check.js +299 -0
- package/dist/scripts/lib/ensure-dist-fresh.js +48 -1
- package/dist/scripts/release-metadata-check.js +2 -0
- package/package.json +6 -3
- package/release-gates.v2.json +204 -0
- package/schemas/architecture-baseline.v1.schema.json +47 -0
- package/schemas/architecture-map-manifest.v1.schema.json +40 -0
- package/schemas/architecture-map-policy.v1.schema.json +147 -0
- package/schemas/architecture-review.v1.schema.json +46 -0
- package/schemas/mermaid-projection.v1.schema.json +47 -0
|
@@ -7,15 +7,40 @@ import { guardedRename, guardedRm, guardContextForRoute } from '../safety/mutati
|
|
|
7
7
|
import { createRequestedScopeContract } from '../safety/requested-scope-contract.js';
|
|
8
8
|
import { projectTriwikiToAgentsMdTransactional } from '../triwiki/agents-md-projector.js';
|
|
9
9
|
import { buildCodeNavigationContextPack, validateCodeNavigationContextPack } from '../triwiki/code-navigation-context-pack.js';
|
|
10
|
-
import { CODE_NAVIGATION_FATAL_SKIP_REASONS, CODE_NAVIGATION_LIMITS
|
|
10
|
+
import { CODE_NAVIGATION_FATAL_SKIP_REASONS, CODE_NAVIGATION_LIMITS } from '../triwiki/code-navigation-policy.js';
|
|
11
11
|
import { isCodePackProjectionBoundToSnapshot, projectCodePackFromGraph, validateCodePack } from '../triwiki/code-pack.js';
|
|
12
12
|
import { validateContextGraphSnapshot } from '../triwiki/context-graph/contracts.js';
|
|
13
13
|
import { compileContextGraph } from '../triwiki/context-graph/compiler/index.js';
|
|
14
14
|
import { readSourceHashes } from '../triwiki/context-graph/compiler/freshness.js';
|
|
15
|
-
import {
|
|
15
|
+
import { ARCHITECTURE_MAP_EXTRACTOR_IDS, architectureMapGraphExtractors } from '../triwiki/context-graph/extractors/index.js';
|
|
16
|
+
import { computeContextGraphCacheKey } from '../triwiki/context-graph/compiler/cache-key.js';
|
|
17
|
+
import { walkCodeInventory } from '../triwiki/context-graph/extractors/code/inventory.js';
|
|
18
|
+
import { codeInventoryInputHashes } from '../triwiki/code-navigation-policy.js';
|
|
16
19
|
import { clearContextGraphSnapshotCache } from '../triwiki/context-graph/query/snapshot-cache.js';
|
|
17
20
|
import { withTriWikiStateLock } from '../triwiki/triwiki-cleanup.js';
|
|
21
|
+
import { publishArchitectureMapToStage } from '../triwiki/context-graph/store/architecture-map-store.js';
|
|
18
22
|
import { ALIGN_GATE_ARTIFACT, ALIGN_LEDGER_ARTIFACT, ALIGN_OUTPUT_ARTIFACTS, ALIGN_PLAN_ARTIFACT, ALIGN_STAGING_ROOT_REL, buildInitialAlignLedger, refreshAlignGate } from './align-route.js';
|
|
23
|
+
const WIKI_PREFIX = '.sneakoscope/wiki/';
|
|
24
|
+
function isArchitectureMapOptionalSkip(skip) {
|
|
25
|
+
const rel = skip.path.replace(/\\/g, '/');
|
|
26
|
+
if (skip.reason !== 'unreadable' && skip.reason !== 'excluded')
|
|
27
|
+
return false;
|
|
28
|
+
if (rel === '.sneakoscope/wiki/context-pack.json')
|
|
29
|
+
return true;
|
|
30
|
+
if (rel.startsWith('.sneakoscope/triwiki/proof-bank'))
|
|
31
|
+
return true;
|
|
32
|
+
if (rel.startsWith('.sneakoscope/wiki/'))
|
|
33
|
+
return true;
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
function isAlignFatalSkip(skip) {
|
|
37
|
+
if (!CODE_NAVIGATION_FATAL_SKIP_REASONS.has(skip.reason))
|
|
38
|
+
return false;
|
|
39
|
+
return !isArchitectureMapOptionalSkip(skip);
|
|
40
|
+
}
|
|
41
|
+
if (new Set(ALIGN_OUTPUT_ARTIFACTS).size !== ALIGN_OUTPUT_ARTIFACTS.length) {
|
|
42
|
+
throw new Error('align_output_artifacts_not_unique');
|
|
43
|
+
}
|
|
19
44
|
const ALIGN_REPLACED_SURFACES = Object.freeze([
|
|
20
45
|
{ key: 'wiki', rel: '.sneakoscope/wiki' },
|
|
21
46
|
{ key: 'memory', rel: '.sneakoscope/memory' },
|
|
@@ -116,19 +141,19 @@ async function buildPack(root, snapshot, meta, generatedAt) {
|
|
|
116
141
|
observedSourceHashes: observed
|
|
117
142
|
}).pack;
|
|
118
143
|
}
|
|
119
|
-
function buildManifest(generatedAt,
|
|
144
|
+
function buildManifest(generatedAt, inventory, inventoryDigest, snapshot) {
|
|
120
145
|
return {
|
|
121
146
|
schema: 'sks.code-navigation-manifest.v1',
|
|
122
147
|
generated_at: generatedAt,
|
|
123
148
|
source_policy: 'repository_code_only',
|
|
124
149
|
exhaustive_graph: 'context-graph.json',
|
|
125
|
-
inventory_digest:
|
|
150
|
+
inventory_digest: inventoryDigest,
|
|
126
151
|
snapshot_hash: snapshot.snapshotHash,
|
|
127
|
-
source_file_count:
|
|
152
|
+
source_file_count: inventory.files.length,
|
|
128
153
|
symbol_count: snapshot.nodes.filter((node) => node.kind === 'symbol').length,
|
|
129
154
|
edge_count: snapshot.edgeCount,
|
|
130
155
|
extractors: snapshot.extractors.map(({ id, revision }) => ({ id, revision })),
|
|
131
|
-
source_files:
|
|
156
|
+
source_files: inventory.files.map((file) => ({
|
|
132
157
|
path: file.rel,
|
|
133
158
|
sha256: file.hash,
|
|
134
159
|
language: file.language,
|
|
@@ -141,13 +166,15 @@ function buildManifest(generatedAt, inspection, snapshot) {
|
|
|
141
166
|
}
|
|
142
167
|
async function validateStaging(input) {
|
|
143
168
|
for (const artifact of ALIGN_OUTPUT_ARTIFACTS) {
|
|
144
|
-
|
|
169
|
+
if (!artifact.startsWith(WIKI_PREFIX))
|
|
170
|
+
return false;
|
|
171
|
+
const stageFile = path.join(input.stageWiki, artifact.slice(WIKI_PREFIX.length));
|
|
145
172
|
const stat = await fsp.stat(stageFile).catch(() => null);
|
|
146
173
|
if (!stat?.isFile() || stat.size <= 0)
|
|
147
174
|
return false;
|
|
148
175
|
}
|
|
149
176
|
const meta = await readJson(path.join(input.stageWiki, 'context-graph.meta.json'), null);
|
|
150
|
-
if (meta?.snapshotHash !== input.snapshot.snapshotHash || meta?.cacheKeyParts?.sourcePolicy !== '
|
|
177
|
+
if (meta?.snapshotHash !== input.snapshot.snapshotHash || meta?.cacheKeyParts?.sourcePolicy !== 'workspace')
|
|
151
178
|
return false;
|
|
152
179
|
const manifest = await readJson(path.join(input.stageWiki, 'code-navigation-manifest.json'), null);
|
|
153
180
|
if (manifest?.inventory_digest !== input.manifest.inventory_digest || manifest?.source_file_count !== input.manifest.source_file_count)
|
|
@@ -174,21 +201,31 @@ async function runLocked(root, dir, missionId, plan, ledger, beforeFinalSourceCa
|
|
|
174
201
|
ledger.validation.absent_or_existing_input_supported = plan.requires_cleanup_receipt === false
|
|
175
202
|
&& plan.acceptance.absent_or_existing_triwiki_supported === true;
|
|
176
203
|
ledger.validation.prior_state_ignored = true;
|
|
177
|
-
const extractorIdentities =
|
|
178
|
-
const
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
.
|
|
204
|
+
const extractorIdentities = architectureMapGraphExtractors();
|
|
205
|
+
const inventory = walkCodeInventory(root, CODE_NAVIGATION_LIMITS);
|
|
206
|
+
const inputHashes = codeInventoryInputHashes(inventory);
|
|
207
|
+
const codeInventoryDigest = crypto
|
|
208
|
+
.createHash('sha256')
|
|
209
|
+
.update(Object.entries(inputHashes)
|
|
210
|
+
.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
|
|
211
|
+
.map(([relative, hash]) => `${relative}\0${hash}`)
|
|
212
|
+
.join('\n'))
|
|
213
|
+
.digest('hex');
|
|
214
|
+
const cacheKey = await computeContextGraphCacheKey({ root, extractors: extractorIdentities });
|
|
215
|
+
const fatalSkips = inventory.skipped.filter((skip) => isAlignFatalSkip(skip));
|
|
216
|
+
ledger.scan.inventory_digest = codeInventoryDigest;
|
|
217
|
+
ledger.scan.source_file_count = inventory.files.length;
|
|
218
|
+
ledger.scan.source_bytes = inventory.files.reduce((sum, file) => sum + file.bytes, 0);
|
|
219
|
+
ledger.scan.source_lines = inventory.files.reduce((sum, file) => sum + file.lines, 0);
|
|
220
|
+
ledger.scan.languages = countsBy(inventory.files, (file) => file.language);
|
|
221
|
+
ledger.scan.allowed_exclusions = inventory.skipped
|
|
222
|
+
.filter((skip) => !isAlignFatalSkip(skip))
|
|
187
223
|
.map((skip) => ({ path: skip.path, reason: skip.reason }));
|
|
188
|
-
ledger.scan.fatal_skips =
|
|
189
|
-
ledger.validation.source_inventory_complete =
|
|
190
|
-
if (
|
|
191
|
-
throw new Error(`code_navigation_fatal_skips:${
|
|
224
|
+
ledger.scan.fatal_skips = fatalSkips.map((skip) => ({ ...skip }));
|
|
225
|
+
ledger.validation.source_inventory_complete = fatalSkips.length === 0;
|
|
226
|
+
if (fatalSkips.length)
|
|
227
|
+
throw new Error(`code_navigation_fatal_skips:${fatalSkips.map((skip) => `${skip.reason}:${skip.path}`).join('|')}`);
|
|
228
|
+
const extractors = architectureMapGraphExtractors({ preparedInventory: inventory });
|
|
192
229
|
const generatedAt = nowIso();
|
|
193
230
|
const compiled = await compileContextGraph({
|
|
194
231
|
root,
|
|
@@ -197,12 +234,12 @@ async function runLocked(root, dir, missionId, plan, ledger, beforeFinalSourceCa
|
|
|
197
234
|
limits: CODE_NAVIGATION_LIMITS,
|
|
198
235
|
observedAt: generatedAt,
|
|
199
236
|
useFragmentCache: false,
|
|
200
|
-
cacheKey
|
|
237
|
+
cacheKey,
|
|
201
238
|
persistArtifacts: false,
|
|
202
239
|
useCompileLock: false
|
|
203
240
|
});
|
|
204
241
|
ledger.validation.graph_compile = compiled.ok;
|
|
205
|
-
const compileFatal = compiled.skipped.filter((skip) =>
|
|
242
|
+
const compileFatal = compiled.skipped.filter((skip) => isAlignFatalSkip(skip));
|
|
206
243
|
if (compileFatal.length)
|
|
207
244
|
ledger.scan.fatal_skips.push(...compileFatal.map((skip) => ({ ...skip })));
|
|
208
245
|
if (!compiled.ok || !compiled.snapshot || !compiled.meta || compileFatal.length) {
|
|
@@ -218,7 +255,7 @@ async function runLocked(root, dir, missionId, plan, ledger, beforeFinalSourceCa
|
|
|
218
255
|
ledger.graph.test_nodes = snapshot.nodes.filter((node) => node.kind === 'test').length;
|
|
219
256
|
ledger.graph.edge_count = snapshot.edgeCount;
|
|
220
257
|
ledger.graph.edges_by_type = countsBy(snapshot.edges, (edge) => edge.type);
|
|
221
|
-
const inventoryPaths =
|
|
258
|
+
const inventoryPaths = inventory.files.map((file) => file.rel).sort();
|
|
222
259
|
const fileNodePaths = snapshot.nodes.filter((node) => node.kind === 'file').map((node) => String(node.path || '')).filter(Boolean).sort();
|
|
223
260
|
const inventorySet = new Set(inventoryPaths);
|
|
224
261
|
const fileNodeSet = new Set(fileNodePaths);
|
|
@@ -229,12 +266,16 @@ async function runLocked(root, dir, missionId, plan, ledger, beforeFinalSourceCa
|
|
|
229
266
|
&& fileNodePaths.length === inventoryPaths.length;
|
|
230
267
|
if (!ledger.graph.exact_file_coverage)
|
|
231
268
|
throw new Error('code_navigation_exact_file_coverage_failed');
|
|
232
|
-
|
|
233
|
-
|
|
269
|
+
const expectedExtractors = [...ARCHITECTURE_MAP_EXTRACTOR_IDS];
|
|
270
|
+
if (ledger.graph.extractor_ids.length !== expectedExtractors.length
|
|
271
|
+
|| expectedExtractors.some((id, index) => ledger.graph.extractor_ids[index] !== id)) {
|
|
272
|
+
throw new Error(`code_navigation_architecture_map_extractors_mismatch:${ledger.graph.extractor_ids.join(',')}`);
|
|
273
|
+
}
|
|
234
274
|
const graphValidation = validateContextGraphSnapshot(snapshot);
|
|
235
275
|
ledger.validation.graph_schema = graphValidation.ok;
|
|
236
|
-
if (!graphValidation.ok || meta.cacheKeyParts.sourcePolicy !== '
|
|
276
|
+
if (!graphValidation.ok || meta.cacheKeyParts.sourcePolicy !== 'workspace') {
|
|
237
277
|
throw new Error('code_navigation_graph_validation_failed');
|
|
278
|
+
}
|
|
238
279
|
const pack = await buildPack(root, snapshot, meta, generatedAt);
|
|
239
280
|
const packValidation = await validateCodePack(pack, root);
|
|
240
281
|
ledger.validation.code_pack = packValidation.ok
|
|
@@ -255,7 +296,7 @@ async function runLocked(root, dir, missionId, plan, ledger, beforeFinalSourceCa
|
|
|
255
296
|
ledger.validation.context_pack = contextValidation.ok;
|
|
256
297
|
if (!contextValidation.ok)
|
|
257
298
|
throw new Error('code_navigation_context_pack_invalid');
|
|
258
|
-
const manifest = buildManifest(generatedAt,
|
|
299
|
+
const manifest = buildManifest(generatedAt, inventory, codeInventoryDigest, snapshot);
|
|
259
300
|
const stageRoot = path.join(stageBase, `${missionId}-${randomUUID().slice(0, 8)}`);
|
|
260
301
|
const stageWiki = path.join(stageRoot, 'new-wiki');
|
|
261
302
|
const previousRoot = path.join(stageRoot, 'previous');
|
|
@@ -271,6 +312,7 @@ async function runLocked(root, dir, missionId, plan, ledger, beforeFinalSourceCa
|
|
|
271
312
|
await writeJsonAtomic(path.join(stageWiki, 'code-navigation-manifest.json'), manifest);
|
|
272
313
|
await writeJsonAtomic(path.join(stageWiki, 'code-pack.json'), pack);
|
|
273
314
|
await writeJsonAtomic(path.join(stageWiki, 'context-pack.json'), contextPack);
|
|
315
|
+
await publishArchitectureMapToStage(stageWiki, snapshot, { root, missionId });
|
|
274
316
|
ledger.publication.staged = true;
|
|
275
317
|
ledger.validation.staged_readback = await validateStaging({ stageWiki, root, snapshot, pack, contextPack, manifest });
|
|
276
318
|
if (!ledger.validation.staged_readback)
|
|
@@ -308,9 +350,23 @@ async function runLocked(root, dir, missionId, plan, ledger, beforeFinalSourceCa
|
|
|
308
350
|
}
|
|
309
351
|
ledger.validation.artifact_hashes = Object.keys(ledger.publication.artifact_sha256).length === ALIGN_OUTPUT_ARTIFACTS.length;
|
|
310
352
|
await beforeFinalSourceCas?.();
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
353
|
+
const finalInventory = walkCodeInventory(root, CODE_NAVIGATION_LIMITS);
|
|
354
|
+
const finalFatal = finalInventory.skipped.filter((skip) => isAlignFatalSkip(skip));
|
|
355
|
+
const finalCacheKey = await computeContextGraphCacheKey({ root, extractors: extractorIdentities });
|
|
356
|
+
const finalCodeDigest = crypto
|
|
357
|
+
.createHash('sha256')
|
|
358
|
+
.update(Object.entries(codeInventoryInputHashes(finalInventory))
|
|
359
|
+
.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
|
|
360
|
+
.map(([relative, hash]) => `${relative}\0${hash}`)
|
|
361
|
+
.join('\n'))
|
|
362
|
+
.digest('hex');
|
|
363
|
+
const casStable = finalCacheKey.parts.tsconfigHash === cacheKey.parts.tsconfigHash
|
|
364
|
+
&& finalCacheKey.parts.commandManifestHash === cacheKey.parts.commandManifestHash
|
|
365
|
+
&& finalCacheKey.parts.gateManifestHash === cacheKey.parts.gateManifestHash
|
|
366
|
+
&& finalCacheKey.parts.schemaRevision === cacheKey.parts.schemaRevision
|
|
367
|
+
&& finalCacheKey.parts.proofIndexHash === cacheKey.parts.proofIndexHash
|
|
368
|
+
&& finalCodeDigest === codeInventoryDigest;
|
|
369
|
+
ledger.scan.source_cas_verified = finalFatal.length === 0 && casStable;
|
|
314
370
|
if (!ledger.scan.source_cas_verified)
|
|
315
371
|
throw new Error('code_navigation_source_changed_during_scan');
|
|
316
372
|
commitStarted = true;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import fsp from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { exists, nowIso, readJson, writeJsonAtomic } from './fsx.js';
|
|
4
|
+
import { ARCHITECTURE_CAPSULE_ARTIFACT, ARCHITECTURE_MAP_BASELINE_ARTIFACT, ARCHITECTURE_MAP_MANIFEST_ARTIFACT, ARCHITECTURE_MAP_REVIEW_ARTIFACT, bindArchitectureMapBaseline, buildAfterReview, sealBaseline, validateArchitectureMapBaselineArtifact, validateArchitectureMapReviewArtifact } from './architecture-map-review.js';
|
|
5
|
+
import { readContextGraphSnapshot } from './triwiki/context-graph/store/snapshot-store.js';
|
|
6
|
+
export { ARCHITECTURE_MAP_REVIEW_ARTIFACT };
|
|
7
|
+
export const ARCHITECTURE_MAP_BASELINE_STAGE = 'architecture_map_baseline';
|
|
8
|
+
export const ARCHITECTURE_MAP_REVIEW_STAGE = 'architecture_map_review';
|
|
9
|
+
const ARCHITECTURE_MAP_EXEMPT_ROUTES = new Set([
|
|
10
|
+
'Answer',
|
|
11
|
+
'DFix',
|
|
12
|
+
'Help',
|
|
13
|
+
'Wiki',
|
|
14
|
+
'Goal',
|
|
15
|
+
'GX',
|
|
16
|
+
'DB'
|
|
17
|
+
]);
|
|
18
|
+
export function routeNeedsArchitectureMap(route) {
|
|
19
|
+
const id = String(route?.id || '');
|
|
20
|
+
if (!id)
|
|
21
|
+
return false;
|
|
22
|
+
if (ARCHITECTURE_MAP_EXEMPT_ROUTES.has(id))
|
|
23
|
+
return false;
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
export function createArchitectureMapPlanBinding() {
|
|
27
|
+
return Object.freeze({
|
|
28
|
+
baseline_artifact: ARCHITECTURE_MAP_BASELINE_ARTIFACT,
|
|
29
|
+
review_artifact: ARCHITECTURE_MAP_REVIEW_ARTIFACT,
|
|
30
|
+
capsule_artifact: ARCHITECTURE_CAPSULE_ARTIFACT,
|
|
31
|
+
manifest_artifact: ARCHITECTURE_MAP_MANIFEST_ARTIFACT,
|
|
32
|
+
required: true,
|
|
33
|
+
seeded_at: nowIso()
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export function planStagesArchitectureMap(plan) {
|
|
37
|
+
return Array.isArray(plan?.stages)
|
|
38
|
+
&& plan.stages.some((stage) => String(stage?.id || '') === ARCHITECTURE_MAP_BASELINE_STAGE
|
|
39
|
+
&& !['skipped', 'not_applicable'].includes(String(stage?.status || '')));
|
|
40
|
+
}
|
|
41
|
+
export async function seedArchitectureMapPlanBinding(dir, plan) {
|
|
42
|
+
if (!planStagesArchitectureMap(plan))
|
|
43
|
+
return plan;
|
|
44
|
+
plan.architecture_map = createArchitectureMapPlanBinding();
|
|
45
|
+
await writeJsonAtomic(path.join(dir, 'pipeline-plan.json'), plan);
|
|
46
|
+
return plan;
|
|
47
|
+
}
|
|
48
|
+
export async function maybeSeedArchitectureMapForPlan(input) {
|
|
49
|
+
if (!input.plan?.architecture_map || !input.missionId) {
|
|
50
|
+
return { ok: false, reason: 'architecture_map_not_planned' };
|
|
51
|
+
}
|
|
52
|
+
return seedArchitectureMapBaselineArtifacts({
|
|
53
|
+
root: input.root,
|
|
54
|
+
dir: input.dir,
|
|
55
|
+
missionId: String(input.missionId),
|
|
56
|
+
routeId: String(input.plan.route?.id || input.plan.route?.command || 'SKS'),
|
|
57
|
+
taskProfile: String(input.taskProfile || input.plan.task_profile || '')
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
export async function seedArchitectureMapBaselineArtifacts(input) {
|
|
61
|
+
const baselinePath = path.join(input.dir, ARCHITECTURE_MAP_BASELINE_ARTIFACT);
|
|
62
|
+
if (await exists(baselinePath))
|
|
63
|
+
return { ok: true, baselinePath };
|
|
64
|
+
const load = await readContextGraphSnapshot(input.root);
|
|
65
|
+
if (load.status !== 'ok' || !load.snapshot) {
|
|
66
|
+
return { ok: false, reason: load.blocker || 'architecture_map_graph_unavailable' };
|
|
67
|
+
}
|
|
68
|
+
const sealed = sealBaseline({
|
|
69
|
+
missionId: input.missionId,
|
|
70
|
+
routeId: input.routeId,
|
|
71
|
+
snapshot: load.snapshot,
|
|
72
|
+
policyRoot: input.root,
|
|
73
|
+
capturedAt: nowIso(),
|
|
74
|
+
...(input.taskProfile ? { taskProfile: input.taskProfile } : {})
|
|
75
|
+
});
|
|
76
|
+
await fsp.mkdir(input.dir, { recursive: true });
|
|
77
|
+
await writeJsonAtomic(baselinePath, sealed.baseline);
|
|
78
|
+
await writeJsonAtomic(path.join(input.dir, ARCHITECTURE_MAP_MANIFEST_ARTIFACT), sealed.manifest);
|
|
79
|
+
await writeArchitectureMapCapsule(input.dir, sealed.capsule.text);
|
|
80
|
+
return { ok: true, baselinePath };
|
|
81
|
+
}
|
|
82
|
+
export async function ensureArchitectureMapReviewArtifacts(input) {
|
|
83
|
+
const reviewPath = path.join(input.dir, ARCHITECTURE_MAP_REVIEW_ARTIFACT);
|
|
84
|
+
if (await exists(reviewPath))
|
|
85
|
+
return { ok: true, reviewPath };
|
|
86
|
+
const baselinePath = path.join(input.dir, ARCHITECTURE_MAP_BASELINE_ARTIFACT);
|
|
87
|
+
if (!(await exists(baselinePath))) {
|
|
88
|
+
return { ok: false, reason: 'architecture_map_baseline_missing' };
|
|
89
|
+
}
|
|
90
|
+
const baseline = await readJson(baselinePath, null);
|
|
91
|
+
if (!baseline)
|
|
92
|
+
return { ok: false, reason: 'architecture_map_baseline_unreadable' };
|
|
93
|
+
const load = await readContextGraphSnapshot(input.root);
|
|
94
|
+
if (load.status !== 'ok' || !load.snapshot) {
|
|
95
|
+
return { ok: false, reason: load.blocker || 'architecture_map_graph_unavailable' };
|
|
96
|
+
}
|
|
97
|
+
const built = buildAfterReview({
|
|
98
|
+
missionId: input.missionId,
|
|
99
|
+
...(input.routeId ? { routeId: input.routeId } : {}),
|
|
100
|
+
baseline,
|
|
101
|
+
afterSnapshot: load.snapshot,
|
|
102
|
+
policyRoot: input.root,
|
|
103
|
+
generatedAt: nowIso()
|
|
104
|
+
});
|
|
105
|
+
await fsp.mkdir(input.dir, { recursive: true });
|
|
106
|
+
await writeJsonAtomic(reviewPath, built.review);
|
|
107
|
+
await writeJsonAtomic(path.join(input.dir, ARCHITECTURE_MAP_MANIFEST_ARTIFACT), built.manifest);
|
|
108
|
+
await writeArchitectureMapCapsule(input.dir, built.capsule.text);
|
|
109
|
+
await writeJsonAtomic(path.join(input.dir, 'architecture-map-delta.json'), built.delta);
|
|
110
|
+
return { ok: true, reviewPath };
|
|
111
|
+
}
|
|
112
|
+
export async function architectureMapGateStatus(root, state = {}) {
|
|
113
|
+
if (state?.architecture_map_required !== true)
|
|
114
|
+
return { ok: true, blockers: [] };
|
|
115
|
+
const missionId = state?.mission_id;
|
|
116
|
+
if (!missionId)
|
|
117
|
+
return { ok: false, blockers: ['architecture_map_mission_missing'] };
|
|
118
|
+
const dir = path.join(root, '.sneakoscope/missions', missionId);
|
|
119
|
+
const plan = await readJson(path.join(dir, 'pipeline-plan.json'), null);
|
|
120
|
+
if (!plan?.architecture_map)
|
|
121
|
+
return { ok: true, blockers: [], not_applicable: true };
|
|
122
|
+
if (await exists(path.join(dir, ARCHITECTURE_MAP_BASELINE_ARTIFACT))) {
|
|
123
|
+
await ensureArchitectureMapReviewArtifacts({
|
|
124
|
+
root,
|
|
125
|
+
dir,
|
|
126
|
+
missionId,
|
|
127
|
+
routeId: String(plan?.route?.id || state?.route || '')
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
const blockers = [];
|
|
131
|
+
const baselinePath = path.join(dir, ARCHITECTURE_MAP_BASELINE_ARTIFACT);
|
|
132
|
+
const reviewPath = path.join(dir, ARCHITECTURE_MAP_REVIEW_ARTIFACT);
|
|
133
|
+
if (!(await exists(baselinePath))) {
|
|
134
|
+
blockers.push('architecture_map_baseline_missing');
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
const baseline = await readJson(baselinePath, null);
|
|
138
|
+
const validation = validateArchitectureMapBaselineArtifact(baseline);
|
|
139
|
+
if (!validation.ok)
|
|
140
|
+
blockers.push('architecture_map_baseline_invalid', ...validation.blockers.map((b) => `baseline:${b}`));
|
|
141
|
+
else {
|
|
142
|
+
const binding = bindArchitectureMapBaseline({ baseline: baseline, missionId });
|
|
143
|
+
if (!binding.ok)
|
|
144
|
+
blockers.push(...binding.blockers.map((b) => `architecture_map_baseline_${b}`));
|
|
145
|
+
if (baseline && baseline.capturedBeforeMutation !== true)
|
|
146
|
+
blockers.push('architecture_map_baseline_late');
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (!(await exists(reviewPath))) {
|
|
150
|
+
blockers.push('architecture_map_review_missing');
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
const review = await readJson(reviewPath, null);
|
|
154
|
+
const validation = validateArchitectureMapReviewArtifact(review);
|
|
155
|
+
if (!validation.ok)
|
|
156
|
+
blockers.push('architecture_map_review_invalid', ...validation.blockers.map((b) => `review:${b}`));
|
|
157
|
+
else if (review?.verdict !== 'pass') {
|
|
158
|
+
blockers.push('architecture_map_review_blocked');
|
|
159
|
+
if (Array.isArray(review?.unaccountedChangedPaths) && review.unaccountedChangedPaths.length) {
|
|
160
|
+
blockers.push('architecture_map_unaccounted_changed_file');
|
|
161
|
+
}
|
|
162
|
+
for (const id of review?.blockingFindingIds || [])
|
|
163
|
+
blockers.push(`architecture_map_finding:${id}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return { ok: blockers.length === 0, blockers: [...new Set(blockers)] };
|
|
167
|
+
}
|
|
168
|
+
export async function writeArchitectureMapCapsule(dir, text) {
|
|
169
|
+
const file = path.join(dir, ARCHITECTURE_CAPSULE_ARTIFACT);
|
|
170
|
+
await fsp.mkdir(dir, { recursive: true });
|
|
171
|
+
await fsp.writeFile(file, text.endsWith('\n') ? text : `${text}\n`, 'utf8');
|
|
172
|
+
return file;
|
|
173
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { sha256 } from './fsx.js';
|
|
2
|
+
import { ARCHITECTURE_BASELINE_SCHEMA, ARCHITECTURE_MAP_MANIFEST_SCHEMA, ARCHITECTURE_REVIEW_SCHEMA } from './triwiki/context-graph/architecture/contracts.js';
|
|
3
|
+
import { buildArchitectureBaseline, verifyArchitectureBaselineSeal } from './triwiki/context-graph/architecture/baseline.js';
|
|
4
|
+
import { renderArchitectureCapsule } from './triwiki/context-graph/architecture/capsule.js';
|
|
5
|
+
import { buildArchitectureDelta, buildArchitectureReview } from './triwiki/context-graph/architecture/delta.js';
|
|
6
|
+
import { buildArchitectureInputBundle, policyContentHash } from './triwiki/context-graph/architecture/input-bundle.js';
|
|
7
|
+
import { loadArchitectureMapPolicy } from './triwiki/context-graph/architecture/policy.js';
|
|
8
|
+
import { buildArchitectureScope } from './triwiki/context-graph/architecture/slice.js';
|
|
9
|
+
import { validateArchitectureBaseline, validateArchitectureInputBundle, validateArchitectureMapManifest, validateArchitectureReview } from './triwiki/context-graph/architecture/validation.js';
|
|
10
|
+
import { buildArchitectureMapManifest, buildArchitectureMapViews } from './triwiki/context-graph/projections/mermaid/index.js';
|
|
11
|
+
export const ARCHITECTURE_MAP_BASELINE_ARTIFACT = 'architecture-map-baseline.json';
|
|
12
|
+
export const ARCHITECTURE_MAP_REVIEW_ARTIFACT = 'architecture-map-review.json';
|
|
13
|
+
export const ARCHITECTURE_CAPSULE_ARTIFACT = 'architecture-capsule.txt';
|
|
14
|
+
export const ARCHITECTURE_MAP_MANIFEST_ARTIFACT = 'architecture-map-manifest.json';
|
|
15
|
+
export const ARCHITECTURE_MAP_REVIEW_SCHEMA = ARCHITECTURE_REVIEW_SCHEMA;
|
|
16
|
+
export const ARCHITECTURE_MAP_BASELINE_SCHEMA = ARCHITECTURE_BASELINE_SCHEMA;
|
|
17
|
+
export const ARCHITECTURE_MAP_MANIFEST_SCHEMA_ID = ARCHITECTURE_MAP_MANIFEST_SCHEMA;
|
|
18
|
+
export function resolveArchitectureMapPolicy(input) {
|
|
19
|
+
if (input.policy)
|
|
20
|
+
return input.policy;
|
|
21
|
+
if (input.policyRoot)
|
|
22
|
+
return loadArchitectureMapPolicy(input.policyRoot);
|
|
23
|
+
throw new Error('architecture_map_policy_required');
|
|
24
|
+
}
|
|
25
|
+
export function sealBaseline(input) {
|
|
26
|
+
const policy = resolveArchitectureMapPolicy(input);
|
|
27
|
+
const rootId = input.rootId ?? 'workspace';
|
|
28
|
+
const profile = input.profile ?? 'global';
|
|
29
|
+
const bundle = buildArchitectureInputBundle({
|
|
30
|
+
rootId,
|
|
31
|
+
graph: input.snapshot,
|
|
32
|
+
policy,
|
|
33
|
+
profile
|
|
34
|
+
});
|
|
35
|
+
const views = buildArchitectureMapViews(input.snapshot, policy, {
|
|
36
|
+
rootId,
|
|
37
|
+
missionId: input.missionId
|
|
38
|
+
});
|
|
39
|
+
const viewHashes = Object.freeze(Object.fromEntries(views.views.map((view) => [view.viewId, view.projection.contentHash])));
|
|
40
|
+
const scope = buildArchitectureScope({
|
|
41
|
+
profile,
|
|
42
|
+
...(input.seedNodeIds ? { seedNodeIds: input.seedNodeIds } : {}),
|
|
43
|
+
...(input.seedPaths ? { seedPaths: input.seedPaths } : {})
|
|
44
|
+
});
|
|
45
|
+
const baseline = buildArchitectureBaseline({
|
|
46
|
+
missionId: input.missionId,
|
|
47
|
+
routeId: input.routeId,
|
|
48
|
+
taskProfile: input.taskProfile ?? profile,
|
|
49
|
+
capturedAt: input.capturedAt,
|
|
50
|
+
bundle,
|
|
51
|
+
policy,
|
|
52
|
+
scope,
|
|
53
|
+
viewHashes,
|
|
54
|
+
findings: views.findings,
|
|
55
|
+
metrics: views.metrics
|
|
56
|
+
});
|
|
57
|
+
const capsule = renderArchitectureCapsule({
|
|
58
|
+
profile,
|
|
59
|
+
missionId: input.missionId,
|
|
60
|
+
routeId: input.routeId,
|
|
61
|
+
baseline,
|
|
62
|
+
metrics: views.metrics,
|
|
63
|
+
findings: views.findings,
|
|
64
|
+
...(input.seedPaths ? { seedPaths: input.seedPaths } : {}),
|
|
65
|
+
mapArtifact: ARCHITECTURE_MAP_MANIFEST_ARTIFACT
|
|
66
|
+
});
|
|
67
|
+
return Object.freeze({
|
|
68
|
+
baseline,
|
|
69
|
+
bundle,
|
|
70
|
+
manifest: views.manifest,
|
|
71
|
+
capsule,
|
|
72
|
+
policy
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
export function buildAfterReview(input) {
|
|
76
|
+
const policy = resolveArchitectureMapPolicy(input);
|
|
77
|
+
const rootId = input.rootId ?? 'workspace';
|
|
78
|
+
const profile = input.profile ?? input.baseline.scope.profile;
|
|
79
|
+
const afterBundle = buildArchitectureInputBundle({
|
|
80
|
+
rootId,
|
|
81
|
+
graph: input.afterSnapshot,
|
|
82
|
+
policy,
|
|
83
|
+
profile,
|
|
84
|
+
policyHash: policyContentHash(policy)
|
|
85
|
+
});
|
|
86
|
+
const views = buildArchitectureMapViews(input.afterSnapshot, policy, {
|
|
87
|
+
rootId,
|
|
88
|
+
missionId: input.missionId
|
|
89
|
+
});
|
|
90
|
+
const projectionAccounting = Object.freeze(Object.fromEntries(views.views.map((view) => [
|
|
91
|
+
view.viewId,
|
|
92
|
+
{
|
|
93
|
+
selectedNodeCount: view.projection.accounting.selectedNodeIds.length,
|
|
94
|
+
emittedNodeCount: view.projection.accounting.emittedNodeIds.length,
|
|
95
|
+
omittedNodeCount: view.projection.accounting.omittedNodes.length,
|
|
96
|
+
selectedEdgeCount: view.projection.accounting.selectedEdgeIds.length,
|
|
97
|
+
emittedEdgeCount: view.projection.accounting.emittedEdgeIds.length,
|
|
98
|
+
omittedEdgeCount: view.projection.accounting.omittedEdges.length,
|
|
99
|
+
accountingRatio: view.projection.accounting.selectedNodeIds.length === 0
|
|
100
|
+
? 1
|
|
101
|
+
: view.projection.accounting.emittedNodeIds.length /
|
|
102
|
+
view.projection.accounting.selectedNodeIds.length
|
|
103
|
+
}
|
|
104
|
+
])));
|
|
105
|
+
const review = buildArchitectureReview({
|
|
106
|
+
missionId: input.missionId,
|
|
107
|
+
baseline: input.baseline,
|
|
108
|
+
afterBundle,
|
|
109
|
+
policy,
|
|
110
|
+
generatedAt: input.generatedAt,
|
|
111
|
+
projectionAccounting,
|
|
112
|
+
...(input.accountedChangedPaths
|
|
113
|
+
? { accountedChangedPaths: input.accountedChangedPaths }
|
|
114
|
+
: {})
|
|
115
|
+
});
|
|
116
|
+
const delta = buildArchitectureDelta({
|
|
117
|
+
missionId: input.missionId,
|
|
118
|
+
baseline: input.baseline,
|
|
119
|
+
afterBundle,
|
|
120
|
+
policy
|
|
121
|
+
});
|
|
122
|
+
const capsule = renderArchitectureCapsule({
|
|
123
|
+
profile,
|
|
124
|
+
missionId: input.missionId,
|
|
125
|
+
...(input.routeId ? { routeId: input.routeId } : {}),
|
|
126
|
+
baseline: input.baseline,
|
|
127
|
+
metrics: views.metrics,
|
|
128
|
+
findings: views.findings,
|
|
129
|
+
mapArtifact: 'architecture-map-delta.mmd'
|
|
130
|
+
});
|
|
131
|
+
return Object.freeze({
|
|
132
|
+
review,
|
|
133
|
+
delta,
|
|
134
|
+
afterBundle,
|
|
135
|
+
manifest: views.manifest,
|
|
136
|
+
capsule,
|
|
137
|
+
policy
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
export function validateArchitectureMapBaselineArtifact(value) {
|
|
141
|
+
return validateArchitectureBaseline(value);
|
|
142
|
+
}
|
|
143
|
+
export function validateArchitectureMapReviewArtifact(value) {
|
|
144
|
+
return validateArchitectureReview(value);
|
|
145
|
+
}
|
|
146
|
+
export function validateArchitectureMapManifestArtifact(value) {
|
|
147
|
+
return validateArchitectureMapManifest(value);
|
|
148
|
+
}
|
|
149
|
+
export function validateArchitectureMapInputBundleArtifact(value) {
|
|
150
|
+
return validateArchitectureInputBundle(value);
|
|
151
|
+
}
|
|
152
|
+
export function bindArchitectureMapBaseline(input) {
|
|
153
|
+
const validation = validateArchitectureBaseline(input.baseline);
|
|
154
|
+
const blockers = [...validation.blockers];
|
|
155
|
+
if (input.baseline.missionId !== input.missionId)
|
|
156
|
+
blockers.push('mission_mismatch');
|
|
157
|
+
if (!verifyArchitectureBaselineSeal(input.baseline))
|
|
158
|
+
blockers.push('seal_mismatch');
|
|
159
|
+
if (blockers.length)
|
|
160
|
+
return { ok: false, blockers, binding: null };
|
|
161
|
+
return {
|
|
162
|
+
ok: true,
|
|
163
|
+
blockers: [],
|
|
164
|
+
binding: {
|
|
165
|
+
missionId: input.baseline.missionId,
|
|
166
|
+
seal: input.baseline.seal,
|
|
167
|
+
canonicalPayloadHash: input.baseline.canonicalPayloadHash,
|
|
168
|
+
policyHash: input.baseline.policyHash,
|
|
169
|
+
graphHash: input.baseline.graphHash,
|
|
170
|
+
worktreeFingerprintHash: input.baseline.worktreeFingerprintHash
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
export function architectureMapReviewDigest(review) {
|
|
175
|
+
return sha256(JSON.stringify(review ?? null));
|
|
176
|
+
}
|
|
177
|
+
export function architectureMapBaselineDigest(baseline) {
|
|
178
|
+
return sha256(JSON.stringify(baseline ?? null));
|
|
179
|
+
}
|
|
180
|
+
export { buildArchitectureMapManifest, buildArchitectureMapViews };
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { nowIso, readJson, writeJsonAtomic } from '../fsx.js';
|
|
4
|
-
import { backupInvalidToml, inspectOfficialSubagentToml,
|
|
4
|
+
import { SKS_MANAGED_CODEX_CONFIG_MARKER, backupInvalidToml, inspectOfficialSubagentToml, mergeOfficialSubagentConfigResult, officialSubagentConfigOwnershipProof, officialSubagentConfigWarnings, readInheritedOfficialSubagentConfigText } from '../subagents/official-subagent-config.js';
|
|
5
5
|
import { writeCodexConfigGuarded } from './codex-config-guard.js';
|
|
6
|
+
function unmanagedConfigOperatorAction(configPath) {
|
|
7
|
+
return `Refused to modify ${configPath}: no SKS ownership proof. `
|
|
8
|
+
+ `If SKS owns this file, add the line \`${SKS_MANAGED_CODEX_CONFIG_MARKER}\` as its first line `
|
|
9
|
+
+ 'and re-run `sks doctor --fix`. Otherwise run `sks setup` to regenerate a managed config.';
|
|
10
|
+
}
|
|
6
11
|
export async function repairAgentConfigFileReferences(input) {
|
|
7
12
|
const root = path.resolve(input.root);
|
|
8
13
|
const configPath = path.join(root, '.codex', 'config.toml');
|
|
@@ -37,6 +42,10 @@ export async function repairAgentConfigFileReferences(input) {
|
|
|
37
42
|
...(!ownershipProof.owned ? ['user_owned_file_without_sks_marker'] : [])
|
|
38
43
|
],
|
|
39
44
|
warnings: [],
|
|
45
|
+
operator_actions: [
|
|
46
|
+
`${configPath} is not valid TOML; SKS preserved it and wrote a backup. Fix the syntax, then re-run \`sks doctor --fix\`.`,
|
|
47
|
+
...(!ownershipProof.owned ? [unmanagedConfigOperatorAction(configPath)] : [])
|
|
48
|
+
],
|
|
40
49
|
ownership_proof: ownershipProof
|
|
41
50
|
});
|
|
42
51
|
}
|
|
@@ -55,6 +64,7 @@ export async function repairAgentConfigFileReferences(input) {
|
|
|
55
64
|
manual_required: true,
|
|
56
65
|
blockers: ['user_owned_file_without_sks_marker'],
|
|
57
66
|
warnings: [],
|
|
67
|
+
operator_actions: [unmanagedConfigOperatorAction(configPath)],
|
|
58
68
|
ownership_proof: ownershipProof
|
|
59
69
|
});
|
|
60
70
|
}
|
|
@@ -62,13 +72,14 @@ export async function repairAgentConfigFileReferences(input) {
|
|
|
62
72
|
...(input.home ? { home: input.home } : {}),
|
|
63
73
|
...(input.codexHome ? { codexHome: input.codexHome } : {})
|
|
64
74
|
});
|
|
65
|
-
const
|
|
75
|
+
const mergeResult = mergeOfficialSubagentConfigResult(original, {
|
|
66
76
|
sksOwned: ownershipProof.owned,
|
|
67
77
|
inheritedText
|
|
68
78
|
});
|
|
79
|
+
const merged = mergeResult.text;
|
|
69
80
|
const validation = inspectOfficialSubagentToml(merged);
|
|
70
81
|
const warnings = officialSubagentConfigWarnings(merged, inheritedText);
|
|
71
|
-
const blockers = [];
|
|
82
|
+
const blockers = [...mergeResult.blockers];
|
|
72
83
|
let changed = merged !== original;
|
|
73
84
|
let writeSucceeded = input.apply !== true;
|
|
74
85
|
let backupPath = null;
|
|
@@ -104,6 +115,9 @@ export async function repairAgentConfigFileReferences(input) {
|
|
|
104
115
|
manual_required: blockers.length > 0,
|
|
105
116
|
blockers,
|
|
106
117
|
warnings,
|
|
118
|
+
operator_actions: blockers.map((blocker) => blocker === 'user_owned_file_without_sks_marker'
|
|
119
|
+
? unmanagedConfigOperatorAction(configPath)
|
|
120
|
+
: `${configPath}: ${blocker}. Re-run \`sks doctor --fix\` after resolving it, or run \`sks setup\` to regenerate a managed config.`),
|
|
107
121
|
ownership_proof: ownershipProof
|
|
108
122
|
};
|
|
109
123
|
return writeReport(input.reportPath, root, report);
|
|
@@ -4,6 +4,7 @@ import { randomBytes } from 'node:crypto';
|
|
|
4
4
|
import { appendJsonl, ensureDir, nowIso, readText, sha256, writeTextAtomic } from '../fsx.js';
|
|
5
5
|
import { diffCodexAppUiSnapshots, snapshotCodexAppUiState } from '../codex-app/codex-app-ui-state-snapshot.js';
|
|
6
6
|
import { cleanupCodexConfigBackups, validateCodexConfigRoundTrip } from './codex-config-toml.js';
|
|
7
|
+
import { hasManagedAgentsConfigFingerprint } from '../subagents/official-subagent-config.js';
|
|
7
8
|
import { escapeRegExp } from '../text/regex.js';
|
|
8
9
|
import { withFileLock } from '../locks/file-lock.js';
|
|
9
10
|
export const SKS_MANAGED_CODEX_CONFIG_MARKER = '# SKS-MANAGED-CODEX-CONFIG';
|
|
@@ -576,7 +577,8 @@ export function isProjectCodexConfig(root, configPath) {
|
|
|
576
577
|
export function hasSksManagedCodexConfigMarker(text) {
|
|
577
578
|
const source = String(text || '');
|
|
578
579
|
return hasExplicitSksManagedCodexConfigMarker(source)
|
|
579
|
-
|| /(?:SKS managed|Sneakoscope|sneakoscope|sks_|agents\.native_agent|agents\.implementation_worker
|
|
580
|
+
|| /(?:SKS managed|Sneakoscope|sneakoscope|sks_|agents\.native_agent|agents\.implementation_worker)/i.test(source)
|
|
581
|
+
|| hasManagedAgentsConfigFingerprint(source)
|
|
580
582
|
|| /^\s*model_provider\s*=\s*["']codex-lb["']\s*(?:#.*)?$/mi.test(source)
|
|
581
583
|
|| /^\s*default_profile\s*=\s*["']sks-fast-high["']\s*(?:#.*)?$/mi.test(source)
|
|
582
584
|
|| /^\s*\[(?:user\.fast_mode|model_providers\.(?:"codex-lb"|codex-lb)|profiles\.(?:"sks-fast-high"|sks-fast-high))\]\s*(?:#.*)?$/mi.test(source);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const MANAGED_CODEX_FEATURE_FLAGS = Object.freeze(['hooks', 'fast_mode', 'apps']);
|
|
2
|
+
export const REMOVED_CODEX_FEATURE_FLAGS = Object.freeze([
|
|
3
|
+
'fast_mode_ui',
|
|
4
|
+
'codex_hooks',
|
|
5
|
+
'remote_control',
|
|
6
|
+
'codex_git_commit',
|
|
7
|
+
'plugin_hooks',
|
|
8
|
+
'js_repl',
|
|
9
|
+
'multi_agent_mode'
|
|
10
|
+
]);
|