sneakoscope 8.3.2 → 8.4.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 +33 -1
- 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 +172 -0
- package/dist/core/architecture-map-review.js +180 -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-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-controller-v3/catalog.js +75 -4
- package/dist/core/codex-lb/desktop-controller-v3.js +5 -1
- package/dist/core/commands/triwiki-atlas-command.js +372 -0
- package/dist/core/commands/triwiki-command.js +12 -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/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 +13 -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/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
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { readText } from '../../fsx.js';
|
|
1
|
+
import { readText, writeJsonAtomic } from '../../fsx.js';
|
|
2
|
+
import { readTopLevelTomlString } from '../../codex-app/codex-model-catalog.js';
|
|
3
|
+
import { BRIDGE_MODEL_SELECTION_SCHEMA, MAX_SELECTED_OPENROUTER_MODELS, bridgeAvailableModelsPath, pruneSelection, readBridgeModelSelectionState, writeBridgeModelSelection } from '../combined-catalog/model-selection.js';
|
|
2
4
|
import { listOpenRouterModels } from '../../providers/openrouter/openrouter-account.js';
|
|
3
5
|
import { buildCombinedBridgeCatalog, readActiveCombinedBridgeCatalog, stageCombinedBridgeCatalog } from '../combined-catalog.js';
|
|
4
6
|
import { codexLbEnvPath, codexLbMetadataPath, loadCodexLbEnv, readCodexLbModelCatalog } from '../codex-lb-env.js';
|
|
@@ -40,7 +42,21 @@ export async function syncCatalogInternal(options) {
|
|
|
40
42
|
const catalogs = await fetchProviderCatalogs(initialRegistry, rawCredentials, paths, options);
|
|
41
43
|
const credentials = await resolveValidatedCredentials(options, paths);
|
|
42
44
|
const registry = await resolveBridgeProviderRegistry({ home: paths.home, credentials, storedRegistry });
|
|
43
|
-
const
|
|
45
|
+
const selectionState = await readBridgeModelSelectionState(paths.home, nowIso(options));
|
|
46
|
+
const selection = selectionState.stored
|
|
47
|
+
? selectionState.selection
|
|
48
|
+
: seedSelectionFromConfiguredModel(selectionState.selection, await readText(paths.configPath, ''));
|
|
49
|
+
const build = buildCombinedBridgeCatalog(registry, { catalogs, created_at: nowIso(options), selection });
|
|
50
|
+
if (!selectionState.stored)
|
|
51
|
+
await writeBridgeModelSelection(paths.home, selection).catch(() => undefined);
|
|
52
|
+
await writeJsonAtomic(bridgeAvailableModelsPath(paths.home), {
|
|
53
|
+
schema: 'sks.bridge-available-models.v1',
|
|
54
|
+
updated_at: nowIso(options),
|
|
55
|
+
openrouter: build.available_openrouter_models
|
|
56
|
+
}, { mode: 0o600 }).catch(() => undefined);
|
|
57
|
+
const pruned = pruneSelection(selection, build.available_openrouter_models);
|
|
58
|
+
if (pruned !== selection)
|
|
59
|
+
await writeBridgeModelSelection(paths.home, pruned).catch(() => undefined);
|
|
44
60
|
const staging = await stageCombinedBridgeCatalog({
|
|
45
61
|
build,
|
|
46
62
|
catalogPath: paths.catalogPath,
|
|
@@ -200,11 +216,12 @@ async function fetchProviderCatalogs(registry, credentials, paths, options) {
|
|
|
200
216
|
home: paths.home,
|
|
201
217
|
validationPath: paths.validationPath
|
|
202
218
|
});
|
|
219
|
+
const codexLbModels = result.model_rows.length > 0 ? result.model_rows : result.models;
|
|
203
220
|
return [providerId, {
|
|
204
221
|
provider_id: providerId,
|
|
205
222
|
state: result.ok ? 'verified' : 'failed',
|
|
206
|
-
generation: result.ok ? sha256Stable({ provider_id: providerId, models:
|
|
207
|
-
models: { models:
|
|
223
|
+
generation: result.ok ? sha256Stable({ provider_id: providerId, models: codexLbModels }) : null,
|
|
224
|
+
models: { models: codexLbModels },
|
|
208
225
|
checked_at: checkedAt,
|
|
209
226
|
expires_at: expiresAt,
|
|
210
227
|
blockers: result.blockers,
|
|
@@ -255,3 +272,57 @@ function validationFailureState(blockers) {
|
|
|
255
272
|
return blockers.some((blocker) => /(?:401|403|auth|rejected|invalid_key|unauthorized)/i.test(blocker))
|
|
256
273
|
? 'rejected' : 'unavailable';
|
|
257
274
|
}
|
|
275
|
+
export async function listSelectableModels(options) {
|
|
276
|
+
const paths = controllerPaths(options);
|
|
277
|
+
const selection = (await readBridgeModelSelectionState(paths.home, nowIso(options))).selection;
|
|
278
|
+
const available = await readText(bridgeAvailableModelsPath(paths.home), '');
|
|
279
|
+
let rows = [];
|
|
280
|
+
try {
|
|
281
|
+
const parsed = available ? JSON.parse(available) : null;
|
|
282
|
+
if (parsed && Array.isArray(parsed.openrouter))
|
|
283
|
+
rows = parsed.openrouter;
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
rows = [];
|
|
287
|
+
}
|
|
288
|
+
const selected = new Set(selection.openrouter.public_ids);
|
|
289
|
+
const status = await desktopBridgeStatusV3(options);
|
|
290
|
+
return commandResult('models.list', true, status, {
|
|
291
|
+
codex_lb_exposure: 'all',
|
|
292
|
+
openrouter: {
|
|
293
|
+
mode: 'selected',
|
|
294
|
+
selected_count: selection.openrouter.public_ids.length,
|
|
295
|
+
max_selected: MAX_SELECTED_OPENROUTER_MODELS,
|
|
296
|
+
available_count: rows.length,
|
|
297
|
+
models: rows.map((row) => ({
|
|
298
|
+
public_id: String(row?.public_id || ''),
|
|
299
|
+
display_name: String(row?.display_name || row?.public_id || ''),
|
|
300
|
+
selected: selected.has(String(row?.public_id || ''))
|
|
301
|
+
})).filter((row) => row.public_id)
|
|
302
|
+
}
|
|
303
|
+
}, rows.length === 0 ? ['bridge_available_models_unknown'] : [], options);
|
|
304
|
+
}
|
|
305
|
+
export async function selectExposedModels(publicIds, options) {
|
|
306
|
+
const paths = controllerPaths(options);
|
|
307
|
+
const requested = [...new Set(publicIds.map((id) => String(id || '').trim()).filter(Boolean))].sort();
|
|
308
|
+
if (requested.length > MAX_SELECTED_OPENROUTER_MODELS) {
|
|
309
|
+
throw new Error('bridge_model_selection_limit_exceeded');
|
|
310
|
+
}
|
|
311
|
+
await writeBridgeModelSelection(paths.home, {
|
|
312
|
+
schema: BRIDGE_MODEL_SELECTION_SCHEMA,
|
|
313
|
+
updated_at: nowIso(options),
|
|
314
|
+
openrouter: { mode: 'selected', public_ids: requested }
|
|
315
|
+
});
|
|
316
|
+
const sync = await syncCatalogInternal(options);
|
|
317
|
+
const status = await desktopBridgeStatusV3(options);
|
|
318
|
+
const activation = sync.activation && typeof sync.activation === 'object'
|
|
319
|
+
? sync.activation
|
|
320
|
+
: {};
|
|
321
|
+
return commandResult('models.select', sync.ok === true, status, { selected_count: requested.length, catalog_sync: sync }, sync.ok === true ? [] : stringArray(activation.blockers), options);
|
|
322
|
+
}
|
|
323
|
+
function seedSelectionFromConfiguredModel(selection, configText) {
|
|
324
|
+
const configured = readTopLevelTomlString(configText, 'model');
|
|
325
|
+
if (!configured || !configured.includes('/'))
|
|
326
|
+
return selection;
|
|
327
|
+
return { ...selection, openrouter: { mode: 'selected', public_ids: [configured] } };
|
|
328
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { syncCatalog } from './desktop-controller-v3/catalog.js';
|
|
1
|
+
import { listSelectableModels, selectExposedModels, syncCatalog } from './desktop-controller-v3/catalog.js';
|
|
2
2
|
import { explainRoute, ensureDesktopBridge, repairDesktopBridge, rollbackDesktopBridge, setDefaultProvider, unmanageDesktopBridge } from './desktop-controller-v3/lifecycle-commands.js';
|
|
3
3
|
import { configureProvider, removeCredential, setProviderState, validateProvider } from './desktop-controller-v3/provider-commands.js';
|
|
4
4
|
import { commandResult, safeCode } from './desktop-controller-v3/shared.js';
|
|
@@ -40,6 +40,10 @@ export async function executeDesktopBridgeCommandV3(request, options = {}) {
|
|
|
40
40
|
if (request.operation === 'provider.remove-credential') {
|
|
41
41
|
return removeCredential(request.provider_id, options);
|
|
42
42
|
}
|
|
43
|
+
if (request.operation === 'models.list')
|
|
44
|
+
return listSelectableModels(options);
|
|
45
|
+
if (request.operation === 'models.select')
|
|
46
|
+
return selectExposedModels(request.public_ids, options);
|
|
43
47
|
if (request.operation === 'catalog.sync')
|
|
44
48
|
return syncCatalog(options);
|
|
45
49
|
if (request.operation === 'catalog.status') {
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { flag, readOption } from '../../cli/args.js';
|
|
4
|
+
import { ARCHITECTURE_MAP_MANIFEST_SCHEMA, ARCHITECTURE_MAP_VIEW_IDS, GLOBAL_ARCHITECTURE_MAP_VIEW_IDS } from '../triwiki/context-graph/architecture/contracts.js';
|
|
5
|
+
import { ARCHITECTURE_MAP_DIR_REL } from '../triwiki/context-graph/store/architecture-map-store.js';
|
|
6
|
+
export const TRIWIKI_ATLAS_SUBCOMMANDS = [
|
|
7
|
+
'atlas-status',
|
|
8
|
+
'atlas-lint',
|
|
9
|
+
'atlas-list',
|
|
10
|
+
'atlas-show',
|
|
11
|
+
'atlas-why'
|
|
12
|
+
];
|
|
13
|
+
const REPAIR_COMMAND = 'sks align run';
|
|
14
|
+
const VIEW_ID_SET = new Set(ARCHITECTURE_MAP_VIEW_IDS);
|
|
15
|
+
const GLOBAL_VIEW_ID_SET = new Set(GLOBAL_ARCHITECTURE_MAP_VIEW_IDS);
|
|
16
|
+
export function isTriWikiAtlasSubcommand(value) {
|
|
17
|
+
return typeof value === 'string' && TRIWIKI_ATLAS_SUBCOMMANDS.includes(value);
|
|
18
|
+
}
|
|
19
|
+
function mapRoot(root) {
|
|
20
|
+
return path.join(root, ...ARCHITECTURE_MAP_DIR_REL.split('/'));
|
|
21
|
+
}
|
|
22
|
+
function missionMapRoot(root, missionId) {
|
|
23
|
+
return path.join(root, '.sneakoscope', 'missions', missionId, 'architecture-map');
|
|
24
|
+
}
|
|
25
|
+
function resolveMapDir(root, args) {
|
|
26
|
+
const missionId = readOption(args, '--mission', '') || null;
|
|
27
|
+
if (missionId) {
|
|
28
|
+
return { dir: missionMapRoot(root, missionId), mode: 'mission', missionId };
|
|
29
|
+
}
|
|
30
|
+
return { dir: mapRoot(root), mode: 'global', missionId: null };
|
|
31
|
+
}
|
|
32
|
+
function readJsonFile(file) {
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function loadManifest(dir) {
|
|
41
|
+
const file = path.join(dir, 'manifest.json');
|
|
42
|
+
if (!fs.existsSync(file))
|
|
43
|
+
return { ok: false, manifest: null, path: file };
|
|
44
|
+
const manifest = readJsonFile(file);
|
|
45
|
+
return { ok: Boolean(manifest && manifest.schema === ARCHITECTURE_MAP_MANIFEST_SCHEMA), manifest, path: file };
|
|
46
|
+
}
|
|
47
|
+
function loadFindings(dir) {
|
|
48
|
+
const file = path.join(dir, 'findings.json');
|
|
49
|
+
const doc = readJsonFile(file);
|
|
50
|
+
return Array.isArray(doc?.findings) ? doc.findings : [];
|
|
51
|
+
}
|
|
52
|
+
function countBySeverity(findings) {
|
|
53
|
+
let blocking = 0;
|
|
54
|
+
let warning = 0;
|
|
55
|
+
let info = 0;
|
|
56
|
+
for (const finding of findings) {
|
|
57
|
+
if (finding.severity === 'blocking')
|
|
58
|
+
blocking += 1;
|
|
59
|
+
else if (finding.severity === 'warning')
|
|
60
|
+
warning += 1;
|
|
61
|
+
else
|
|
62
|
+
info += 1;
|
|
63
|
+
}
|
|
64
|
+
return { blocking, warning, info };
|
|
65
|
+
}
|
|
66
|
+
function graphFreshness(root, manifest) {
|
|
67
|
+
if (!manifest)
|
|
68
|
+
return 'missing';
|
|
69
|
+
const graphPath = path.join(root, '.sneakoscope', 'wiki', 'context-graph.json');
|
|
70
|
+
if (!fs.existsSync(graphPath))
|
|
71
|
+
return 'missing';
|
|
72
|
+
const graph = readJsonFile(graphPath);
|
|
73
|
+
const expected = String(graph?.snapshotHash || '');
|
|
74
|
+
const observed = String(manifest.graphHash || manifest.sourceBinding?.graphHash || '');
|
|
75
|
+
if (!expected || !observed)
|
|
76
|
+
return 'missing';
|
|
77
|
+
return expected === observed ? 'fresh' : 'stale';
|
|
78
|
+
}
|
|
79
|
+
async function atlasStatus(root, args) {
|
|
80
|
+
const resolved = resolveMapDir(root, args);
|
|
81
|
+
const loaded = loadManifest(resolved.dir);
|
|
82
|
+
const findings = loaded.manifest ? loadFindings(resolved.dir) : [];
|
|
83
|
+
const severity = countBySeverity(findings);
|
|
84
|
+
const freshness = graphFreshness(root, loaded.manifest);
|
|
85
|
+
const ok = loaded.ok && freshness === 'fresh' && severity.blocking === 0;
|
|
86
|
+
const result = {
|
|
87
|
+
schema: 'sks.triwiki-atlas-status.v1',
|
|
88
|
+
ok,
|
|
89
|
+
mode: resolved.mode,
|
|
90
|
+
mission_id: resolved.missionId,
|
|
91
|
+
manifest_schema: loaded.manifest?.schema ?? null,
|
|
92
|
+
graph_hash: loaded.manifest?.graphHash ?? null,
|
|
93
|
+
policy_hash: loaded.manifest?.policyHash ?? null,
|
|
94
|
+
generated_from: loaded.manifest?.canonicalHash ?? null,
|
|
95
|
+
freshness,
|
|
96
|
+
view_count: loaded.manifest?.views?.length ?? 0,
|
|
97
|
+
blocking_count: severity.blocking,
|
|
98
|
+
warning_count: severity.warning,
|
|
99
|
+
projection_accounting: loaded.manifest?.projectionAccounting ?? null,
|
|
100
|
+
next_action: ok ? null : REPAIR_COMMAND,
|
|
101
|
+
repair_command: REPAIR_COMMAND
|
|
102
|
+
};
|
|
103
|
+
const lines = [
|
|
104
|
+
`Architecture map (${resolved.mode}): ${ok ? 'ok' : 'blocked'}`,
|
|
105
|
+
`Freshness: ${freshness}`,
|
|
106
|
+
`Views: ${result.view_count}; blocking=${severity.blocking} warning=${severity.warning}`,
|
|
107
|
+
...(ok ? [] : [`Repair: ${REPAIR_COMMAND}`])
|
|
108
|
+
];
|
|
109
|
+
return { result, ok, lines };
|
|
110
|
+
}
|
|
111
|
+
async function atlasLint(root, args) {
|
|
112
|
+
const resolved = resolveMapDir(root, args);
|
|
113
|
+
const loaded = loadManifest(resolved.dir);
|
|
114
|
+
const errors = [];
|
|
115
|
+
if (!loaded.manifest)
|
|
116
|
+
errors.push('manifest_missing');
|
|
117
|
+
else {
|
|
118
|
+
if (loaded.manifest.schema !== ARCHITECTURE_MAP_MANIFEST_SCHEMA)
|
|
119
|
+
errors.push('schema');
|
|
120
|
+
if (!loaded.manifest.graphHash)
|
|
121
|
+
errors.push('graph_hash');
|
|
122
|
+
if (!loaded.manifest.policyHash)
|
|
123
|
+
errors.push('policy_hash');
|
|
124
|
+
if (!Array.isArray(loaded.manifest.views) || loaded.manifest.views.length === 0)
|
|
125
|
+
errors.push('views');
|
|
126
|
+
if (!loaded.manifest.projectionAccounting)
|
|
127
|
+
errors.push('projection_accounting');
|
|
128
|
+
for (const view of loaded.manifest.views ?? []) {
|
|
129
|
+
if (!VIEW_ID_SET.has(view.viewId))
|
|
130
|
+
errors.push(`unknown_view:${view.viewId}`);
|
|
131
|
+
const viewPath = path.join(resolved.dir, 'views', `${view.viewId}.mmd`);
|
|
132
|
+
if (!fs.existsSync(viewPath)) {
|
|
133
|
+
errors.push(`missing_view_file:${view.viewId}`);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const text = fs.readFileSync(viewPath, 'utf8');
|
|
137
|
+
if (!text.startsWith('%% GENERATED BY SKS'))
|
|
138
|
+
errors.push(`header:${view.viewId}`);
|
|
139
|
+
if (!/\bflowchart (TD|LR)\b/.test(text))
|
|
140
|
+
errors.push(`grammar:${view.viewId}`);
|
|
141
|
+
if (/\/Users\/|\/home\/|C:\\\\/.test(text))
|
|
142
|
+
errors.push(`absolute_path:${view.viewId}`);
|
|
143
|
+
const rel = path.relative(resolved.dir, viewPath);
|
|
144
|
+
if (rel.startsWith('..') || path.isAbsolute(rel))
|
|
145
|
+
errors.push(`path_escape:${view.viewId}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const freshness = graphFreshness(root, loaded.manifest);
|
|
149
|
+
if (freshness !== 'fresh')
|
|
150
|
+
errors.push(`freshness_${freshness}`);
|
|
151
|
+
const ok = errors.length === 0;
|
|
152
|
+
const result = {
|
|
153
|
+
schema: 'sks.triwiki-atlas-lint.v1',
|
|
154
|
+
ok,
|
|
155
|
+
mode: resolved.mode,
|
|
156
|
+
mission_id: resolved.missionId,
|
|
157
|
+
error_count: errors.length,
|
|
158
|
+
errors,
|
|
159
|
+
repair_command: REPAIR_COMMAND
|
|
160
|
+
};
|
|
161
|
+
return {
|
|
162
|
+
result,
|
|
163
|
+
ok,
|
|
164
|
+
lines: [
|
|
165
|
+
`Architecture map lint: ${ok ? 'ok' : 'blocked'} (${errors.length} errors)`,
|
|
166
|
+
...errors.slice(0, 20).map((error) => `- ${error}`),
|
|
167
|
+
...(ok ? [] : [`Repair: ${REPAIR_COMMAND}`])
|
|
168
|
+
]
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
async function atlasList(root, args) {
|
|
172
|
+
const resolved = resolveMapDir(root, args);
|
|
173
|
+
const loaded = loadManifest(resolved.dir);
|
|
174
|
+
if (!loaded.manifest) {
|
|
175
|
+
return {
|
|
176
|
+
result: {
|
|
177
|
+
schema: 'sks.triwiki-atlas-list.v1',
|
|
178
|
+
ok: false,
|
|
179
|
+
error_code: 'manifest_missing',
|
|
180
|
+
repair_command: REPAIR_COMMAND
|
|
181
|
+
},
|
|
182
|
+
ok: false,
|
|
183
|
+
lines: ['Architecture map list: manifest_missing', `Repair: ${REPAIR_COMMAND}`]
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
const views = (loaded.manifest.views ?? []).map((view) => {
|
|
187
|
+
const rel = `${ARCHITECTURE_MAP_DIR_REL}/views/${view.viewId}.mmd`;
|
|
188
|
+
const abs = path.join(resolved.dir, 'views', `${view.viewId}.mmd`);
|
|
189
|
+
let direction = null;
|
|
190
|
+
let nodeCount = 0;
|
|
191
|
+
let edgeCount = 0;
|
|
192
|
+
if (fs.existsSync(abs)) {
|
|
193
|
+
const text = fs.readFileSync(abs, 'utf8');
|
|
194
|
+
direction = text.match(/\bflowchart (TD|LR)\b/)?.[1] ?? null;
|
|
195
|
+
nodeCount = [...text.matchAll(/^\s*[A-Za-z0-9_]+\[/gm)].length;
|
|
196
|
+
edgeCount = [...text.matchAll(/-->/g)].length;
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
view_id: view.viewId,
|
|
200
|
+
path: rel,
|
|
201
|
+
direction,
|
|
202
|
+
node_count: nodeCount,
|
|
203
|
+
edge_count: edgeCount,
|
|
204
|
+
byte_count: view.byteLength,
|
|
205
|
+
hash: view.contentHash
|
|
206
|
+
};
|
|
207
|
+
});
|
|
208
|
+
const result = {
|
|
209
|
+
schema: 'sks.triwiki-atlas-list.v1',
|
|
210
|
+
ok: true,
|
|
211
|
+
mode: resolved.mode,
|
|
212
|
+
mission_id: resolved.missionId,
|
|
213
|
+
views
|
|
214
|
+
};
|
|
215
|
+
return {
|
|
216
|
+
result,
|
|
217
|
+
ok: true,
|
|
218
|
+
lines: views.map((view) => `${view.view_id} ${view.direction ?? '?'} nodes=${view.node_count} edges=${view.edge_count} bytes=${view.byte_count} ${view.hash.slice(0, 12)}`)
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
async function atlasShow(root, args) {
|
|
222
|
+
const viewId = args.find((arg) => !arg.startsWith('-')) ?? '';
|
|
223
|
+
if (!viewId || !VIEW_ID_SET.has(viewId)) {
|
|
224
|
+
return {
|
|
225
|
+
result: {
|
|
226
|
+
schema: 'sks.triwiki-atlas-show.v1',
|
|
227
|
+
ok: false,
|
|
228
|
+
error_code: 'invalid_view_id',
|
|
229
|
+
allowed: [...ARCHITECTURE_MAP_VIEW_IDS]
|
|
230
|
+
},
|
|
231
|
+
ok: false,
|
|
232
|
+
lines: [
|
|
233
|
+
'Usage: sks triwiki atlas-show <view-id> [--mission <id>] [--format mermaid|json]',
|
|
234
|
+
`Allowed: ${ARCHITECTURE_MAP_VIEW_IDS.join(', ')}`
|
|
235
|
+
]
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
if (!GLOBAL_VIEW_ID_SET.has(viewId) && !readOption(args, '--mission', '')) {
|
|
239
|
+
return {
|
|
240
|
+
result: {
|
|
241
|
+
schema: 'sks.triwiki-atlas-show.v1',
|
|
242
|
+
ok: false,
|
|
243
|
+
error_code: 'mission_view_requires_mission',
|
|
244
|
+
view_id: viewId
|
|
245
|
+
},
|
|
246
|
+
ok: false,
|
|
247
|
+
lines: [`View ${viewId} is mission-scoped; pass --mission <id>`]
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
const format = readOption(args, '--format', 'mermaid') || 'mermaid';
|
|
251
|
+
const resolved = resolveMapDir(root, args);
|
|
252
|
+
const viewPath = path.join(resolved.dir, 'views', `${viewId}.mmd`);
|
|
253
|
+
const confined = path.relative(resolved.dir, viewPath);
|
|
254
|
+
if (confined.startsWith('..') || path.isAbsolute(confined)) {
|
|
255
|
+
return {
|
|
256
|
+
result: { schema: 'sks.triwiki-atlas-show.v1', ok: false, error_code: 'path_escape' },
|
|
257
|
+
ok: false,
|
|
258
|
+
lines: ['Architecture map show blocked: path_escape']
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
if (!fs.existsSync(viewPath)) {
|
|
262
|
+
return {
|
|
263
|
+
result: {
|
|
264
|
+
schema: 'sks.triwiki-atlas-show.v1',
|
|
265
|
+
ok: false,
|
|
266
|
+
error_code: 'view_missing',
|
|
267
|
+
view_id: viewId,
|
|
268
|
+
repair_command: REPAIR_COMMAND
|
|
269
|
+
},
|
|
270
|
+
ok: false,
|
|
271
|
+
lines: [`Architecture map show: view_missing (${viewId})`, `Repair: ${REPAIR_COMMAND}`]
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
const text = fs.readFileSync(viewPath, 'utf8');
|
|
275
|
+
if (format === 'json') {
|
|
276
|
+
const result = {
|
|
277
|
+
schema: 'sks.triwiki-atlas-show.v1',
|
|
278
|
+
ok: true,
|
|
279
|
+
view_id: viewId,
|
|
280
|
+
format: 'json',
|
|
281
|
+
path: `${ARCHITECTURE_MAP_DIR_REL}/views/${viewId}.mmd`,
|
|
282
|
+
text
|
|
283
|
+
};
|
|
284
|
+
return { result, ok: true, lines: [JSON.stringify(result, null, 2)] };
|
|
285
|
+
}
|
|
286
|
+
return {
|
|
287
|
+
result: {
|
|
288
|
+
schema: 'sks.triwiki-atlas-show.v1',
|
|
289
|
+
ok: true,
|
|
290
|
+
view_id: viewId,
|
|
291
|
+
format: 'mermaid',
|
|
292
|
+
path: `${ARCHITECTURE_MAP_DIR_REL}/views/${viewId}.mmd`,
|
|
293
|
+
text
|
|
294
|
+
},
|
|
295
|
+
ok: true,
|
|
296
|
+
lines: [text.trimEnd()]
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
async function atlasWhy(root, args) {
|
|
300
|
+
const findingId = args.find((arg) => !arg.startsWith('-')) ?? '';
|
|
301
|
+
const missionId = readOption(args, '--mission', '');
|
|
302
|
+
if (!findingId || !missionId) {
|
|
303
|
+
return {
|
|
304
|
+
result: { schema: 'sks.triwiki-atlas-why.v1', ok: false, error_code: 'usage' },
|
|
305
|
+
ok: false,
|
|
306
|
+
lines: ['Usage: sks triwiki atlas-why <finding-id> --mission <id> [--json]']
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
const resolved = resolveMapDir(root, [`--mission`, missionId]);
|
|
310
|
+
const findings = loadFindings(resolved.dir);
|
|
311
|
+
const finding = findings.find((entry) => entry.id === findingId) ?? null;
|
|
312
|
+
if (!finding) {
|
|
313
|
+
return {
|
|
314
|
+
result: {
|
|
315
|
+
schema: 'sks.triwiki-atlas-why.v1',
|
|
316
|
+
ok: false,
|
|
317
|
+
error_code: 'finding_not_found',
|
|
318
|
+
finding_id: findingId,
|
|
319
|
+
mission_id: missionId
|
|
320
|
+
},
|
|
321
|
+
ok: false,
|
|
322
|
+
lines: [`Architecture map why: finding_not_found (${findingId})`]
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
const result = {
|
|
326
|
+
schema: 'sks.triwiki-atlas-why.v1',
|
|
327
|
+
ok: true,
|
|
328
|
+
finding_id: finding.id,
|
|
329
|
+
code: finding.code ?? null,
|
|
330
|
+
severity: finding.severity ?? null,
|
|
331
|
+
subject: finding.subjectIds ?? [],
|
|
332
|
+
evidence_path: finding.evidenceIds ?? [],
|
|
333
|
+
before: null,
|
|
334
|
+
after: null,
|
|
335
|
+
policy_rule: finding.ruleId ?? null,
|
|
336
|
+
exception: finding.disposition ?? null,
|
|
337
|
+
inspect_nodes: finding.subjectIds ?? [],
|
|
338
|
+
message: finding.message ?? null
|
|
339
|
+
};
|
|
340
|
+
return {
|
|
341
|
+
result,
|
|
342
|
+
ok: true,
|
|
343
|
+
lines: [
|
|
344
|
+
`${finding.id}: ${finding.code ?? 'unknown'} (${finding.severity ?? 'info'})`,
|
|
345
|
+
...(finding.subjectIds?.length ? [`Subject: ${finding.subjectIds.join(', ')}`] : []),
|
|
346
|
+
...(finding.message ? [finding.message] : [])
|
|
347
|
+
]
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
export async function triwikiAtlasCommand(root, sub, args) {
|
|
351
|
+
let answer;
|
|
352
|
+
if (sub === 'atlas-status')
|
|
353
|
+
answer = await atlasStatus(root, args);
|
|
354
|
+
else if (sub === 'atlas-lint')
|
|
355
|
+
answer = await atlasLint(root, args);
|
|
356
|
+
else if (sub === 'atlas-list')
|
|
357
|
+
answer = await atlasList(root, args);
|
|
358
|
+
else if (sub === 'atlas-show')
|
|
359
|
+
answer = await atlasShow(root, args);
|
|
360
|
+
else
|
|
361
|
+
answer = await atlasWhy(root, args);
|
|
362
|
+
const usageFailed = typeof answer.result === 'object' &&
|
|
363
|
+
answer.result !== null &&
|
|
364
|
+
'error_code' in answer.result &&
|
|
365
|
+
answer.result.error_code === 'usage';
|
|
366
|
+
const invalidView = typeof answer.result === 'object' &&
|
|
367
|
+
answer.result !== null &&
|
|
368
|
+
'error_code' in answer.result &&
|
|
369
|
+
answer.result.error_code === 'invalid_view_id';
|
|
370
|
+
const exitCode = answer.ok ? 0 : usageFailed || invalidView ? 2 : 1;
|
|
371
|
+
return { ...answer, exitCode };
|
|
372
|
+
}
|
|
@@ -5,6 +5,7 @@ import { computeTriWikiAffectedGraph } from '../triwiki/triwiki-affected-graph.j
|
|
|
5
5
|
import { buildTriWikiGateImpactMap } from '../triwiki/triwiki-gate-impact-map.js';
|
|
6
6
|
import { DEFAULT_TRIWIKI_MODULE_CARDS } from '../triwiki/triwiki-module-card.js';
|
|
7
7
|
import { summarizeTriWikiProofBank } from '../triwiki/triwiki-proof-bank.js';
|
|
8
|
+
import { isTriWikiAtlasSubcommand, triwikiAtlasCommand } from './triwiki-atlas-command.js';
|
|
8
9
|
import { isTriWikiGraphSubcommand, triwikiGraphCommand } from './triwiki-graph-command.js';
|
|
9
10
|
export async function triwikiCommand(args = []) {
|
|
10
11
|
const root = await projectRoot();
|
|
@@ -20,6 +21,16 @@ export async function triwikiCommand(args = []) {
|
|
|
20
21
|
console.log(line);
|
|
21
22
|
return answer.result;
|
|
22
23
|
}
|
|
24
|
+
if (isTriWikiAtlasSubcommand(sub)) {
|
|
25
|
+
const answer = await triwikiAtlasCommand(root, sub, args.slice(1));
|
|
26
|
+
process.exitCode = answer.exitCode;
|
|
27
|
+
if (json)
|
|
28
|
+
return printJson(answer.result);
|
|
29
|
+
const stream = answer.ok ? console.log : console.error;
|
|
30
|
+
for (const line of answer.lines)
|
|
31
|
+
stream(line);
|
|
32
|
+
return answer.result;
|
|
33
|
+
}
|
|
23
34
|
if (sub === 'index') {
|
|
24
35
|
result = {
|
|
25
36
|
schema: 'sks.triwiki-index.v1',
|
|
@@ -36,7 +47,7 @@ export async function triwikiCommand(args = []) {
|
|
|
36
47
|
result = summarizeTriWikiProofBank(root);
|
|
37
48
|
}
|
|
38
49
|
else {
|
|
39
|
-
console.error('Usage: sks triwiki index|affected|proof-bank|graph-status|graph-lint|graph-query [--json]');
|
|
50
|
+
console.error('Usage: sks triwiki index|affected|proof-bank|graph-status|graph-lint|graph-query|atlas-status|atlas-lint|atlas-list|atlas-show|atlas-why [--json]');
|
|
40
51
|
process.exitCode = 1;
|
|
41
52
|
return null;
|
|
42
53
|
}
|
package/dist/core/fsx.js
CHANGED
|
@@ -65,6 +65,20 @@ export async function canonicalFilesystemPath(p) {
|
|
|
65
65
|
const resolved = path.resolve(p);
|
|
66
66
|
return fsp.realpath(resolved).catch(() => resolved);
|
|
67
67
|
}
|
|
68
|
+
export function canonicalFilesystemPathSync(value) {
|
|
69
|
+
const resolved = path.resolve(value);
|
|
70
|
+
try {
|
|
71
|
+
return fs.realpathSync.native(resolved);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
if (process.platform === 'darwin' && resolved.startsWith('/var/'))
|
|
75
|
+
return `/private${resolved}`;
|
|
76
|
+
return resolved;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export function sameFilesystemPathSync(left, right) {
|
|
80
|
+
return Boolean(left && right && canonicalFilesystemPathSync(left) === canonicalFilesystemPathSync(right));
|
|
81
|
+
}
|
|
68
82
|
export async function sameFilesystemPath(left, right) {
|
|
69
83
|
const [leftCanonical, rightCanonical] = await Promise.all([
|
|
70
84
|
canonicalFilesystemPath(left),
|
|
@@ -17,6 +17,8 @@ export const SHARED_MEMORY_TRACK = [
|
|
|
17
17
|
export const GENERATED_INDEX_IGNORE = [
|
|
18
18
|
'.sneakoscope/wiki/indexes/**/*.json',
|
|
19
19
|
'.sneakoscope/wiki/context-packs/**/*.json',
|
|
20
|
+
'.sneakoscope/wiki/architecture-map/**',
|
|
21
|
+
'.sneakoscope/wiki/architecture-map/**/*.mmd',
|
|
20
22
|
'.sneakoscope/wiki/tmp/**'
|
|
21
23
|
];
|
|
22
24
|
export const LOCAL_RUNTIME_IGNORE = [
|
|
@@ -53,6 +55,8 @@ export const LOCAL_RUNTIME_IGNORE = [
|
|
|
53
55
|
'.sneakoscope/wiki/image-wrongness-index.json',
|
|
54
56
|
'.sneakoscope/wiki/visual-anchors.json',
|
|
55
57
|
'.sneakoscope/wiki/last-sweep-report.json',
|
|
58
|
+
'.sneakoscope/wiki/architecture-map/**',
|
|
59
|
+
'.sneakoscope/wiki/architecture-map/**/*.mmd',
|
|
56
60
|
'.sneakoscope/**/*.log'
|
|
57
61
|
];
|
|
58
62
|
export const SHARED_MEMORY_DIRS = [
|
|
@@ -32,6 +32,8 @@ export const GITIGNORE_BLOCK = `# BEGIN ${GIT_HYGIENE_BLOCK}
|
|
|
32
32
|
# Generated TriWiki indexes and transient context packs
|
|
33
33
|
.sneakoscope/wiki/indexes/
|
|
34
34
|
.sneakoscope/wiki/context-packs/
|
|
35
|
+
.sneakoscope/wiki/architecture-map/
|
|
36
|
+
.sneakoscope/wiki/architecture-map/**
|
|
35
37
|
.sneakoscope/wiki/tmp/
|
|
36
38
|
.sneakoscope/wiki/context-pack.json
|
|
37
39
|
.sneakoscope/wiki/image-assets.json
|
package/dist/core/gx-renderer.js
CHANGED
|
@@ -78,7 +78,8 @@ export function normalizeVGraph(vgraph = {}) {
|
|
|
78
78
|
edges,
|
|
79
79
|
invariants: Array.isArray(vgraph.invariants) ? vgraph.invariants : [],
|
|
80
80
|
tests: Array.isArray(vgraph.tests) ? vgraph.tests : [],
|
|
81
|
-
risks: Array.isArray(vgraph.risks) ? vgraph.risks : []
|
|
81
|
+
risks: Array.isArray(vgraph.risks) ? vgraph.risks : [],
|
|
82
|
+
...(vgraph.atlas && typeof vgraph.atlas === 'object' && !Array.isArray(vgraph.atlas) ? { atlas: vgraph.atlas } : {})
|
|
82
83
|
};
|
|
83
84
|
}
|
|
84
85
|
export function vgraphHash(vgraph = {}) {
|
|
@@ -29,6 +29,7 @@ export function defaultManagedPaths() {
|
|
|
29
29
|
row('.sneakoscope/wiki/summaries', 'tracked shared markdown summaries', false, 'shared_memory', 'tracked'),
|
|
30
30
|
row('.sneakoscope/wiki/indexes', 'ignored regenerated shared-memory indexes', true, 'generated_index', 'ignored'),
|
|
31
31
|
row('.sneakoscope/wiki/context-packs', 'ignored regenerated context-pack snapshots', true, 'generated_index', 'ignored'),
|
|
32
|
+
row('.sneakoscope/wiki/architecture-map', 'ignored regenerated Architecture Map views', true, 'generated_index', 'ignored'),
|
|
32
33
|
row('.sneakoscope/missions', 'local runtime mission state and proof artifacts', true, 'local_runtime', 'ignored'),
|
|
33
34
|
row('.sneakoscope/reports', 'local runtime reports', true, 'local_runtime', 'ignored'),
|
|
34
35
|
row('.sneakoscope/tmp', 'local temporary runtime files', true, 'local_runtime', 'ignored'),
|