knosky 0.5.0 → 0.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/CHANGELOG.md +21 -0
- package/README.md +25 -1
- package/SECURITY.md +14 -0
- package/action/post-comment.mjs +89 -0
- package/action.yml +62 -0
- package/core/benchmark-results.mjs +225 -0
- package/core/bundle.mjs +7 -1
- package/core/comparison.mjs +189 -0
- package/core/constants.mjs +13 -0
- package/core/cross-repo.mjs +111 -0
- package/core/escalate.mjs +68 -0
- package/core/freshness.mjs +194 -0
- package/core/fs-indexer.mjs +6 -0
- package/core/key-store.mjs +348 -0
- package/core/ledger.mjs +141 -0
- package/core/lod.mjs +155 -0
- package/core/multi-model-benchmark.mjs +405 -0
- package/core/onboarding.mjs +223 -0
- package/core/protocol-spec.mjs +460 -0
- package/core/route.mjs +5 -0
- package/core/schema.mjs +26 -1
- package/package.json +3 -1
- package/renderer/city.template.html +129 -20
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
// KnoSky machine-readable protocol spec (SAT-462).
|
|
2
|
+
// Exports authoritative JSON Schema (Draft 2020-12) objects for every protocol artifact.
|
|
3
|
+
// Pure ESM, no dependencies — the objects are plain JSON-serializable data.
|
|
4
|
+
//
|
|
5
|
+
// Four schemas are published:
|
|
6
|
+
// ROUTE_SCHEMA — the `route` artifact written by kc_route / kcRoute()
|
|
7
|
+
// INTENT_MANIFEST_SCHEMA — the `intent-manifest` artifact written by kc_bundle / kcBundle()
|
|
8
|
+
// CONFIG_SCHEMA — the `.knosky/config.yml` file (after YAML → JS object parse)
|
|
9
|
+
// CITY_SCHEMA — the city-data v2 envelope (core/CONTRACT.md)
|
|
10
|
+
//
|
|
11
|
+
// These complement, and must stay consistent with, the runtime validators in:
|
|
12
|
+
// core/schema.mjs (route + intent-manifest)
|
|
13
|
+
// core/config.mjs (config)
|
|
14
|
+
// core/contract.mjs (city)
|
|
15
|
+
//
|
|
16
|
+
// Protocol version constants (mirrors runtime constants in schema.mjs / config.mjs).
|
|
17
|
+
export const PROTOCOL_VERSION = '1.0'; // knosky_protocol field value
|
|
18
|
+
export const CITY_SCHEMA_VERSION = '2.0'; // schema_version field value
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Reusable sub-schemas
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
/** A path entry inside route[] / alternates[]: either a bare string or an object with a `path` field. */
|
|
25
|
+
const PATH_ENTRY = {
|
|
26
|
+
oneOf: [
|
|
27
|
+
{ type: 'string', minLength: 1 },
|
|
28
|
+
{
|
|
29
|
+
type: 'object',
|
|
30
|
+
required: ['path'],
|
|
31
|
+
properties: {
|
|
32
|
+
path: { type: 'string', minLength: 1 },
|
|
33
|
+
},
|
|
34
|
+
additionalProperties: true,
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** A KnoSky secret-scan result object embedded in the intent-manifest. */
|
|
40
|
+
const SECRET_SCAN_RESULT = {
|
|
41
|
+
type: 'object',
|
|
42
|
+
required: ['status'],
|
|
43
|
+
properties: {
|
|
44
|
+
status: { type: 'string', enum: ['clean', 'blocked'] },
|
|
45
|
+
detail: { type: 'string' },
|
|
46
|
+
},
|
|
47
|
+
additionalProperties: true,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** A provenance object embedded in every city node. */
|
|
51
|
+
const PROVENANCE = {
|
|
52
|
+
type: 'object',
|
|
53
|
+
required: ['store', 'ref'],
|
|
54
|
+
properties: {
|
|
55
|
+
store: { type: 'string', minLength: 1 },
|
|
56
|
+
ref: { type: 'string', minLength: 1 },
|
|
57
|
+
source_rev: { type: 'string' },
|
|
58
|
+
fetched_at: { type: 'string' },
|
|
59
|
+
},
|
|
60
|
+
additionalProperties: true,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// ROUTE_SCHEMA
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* JSON Schema (Draft 2020-12) for the KnoSky `route` artifact.
|
|
69
|
+
* Produced by kc_route / kcRoute(). Must agree with validateRouteDoc() in core/schema.mjs.
|
|
70
|
+
*
|
|
71
|
+
* @type {object}
|
|
72
|
+
*/
|
|
73
|
+
export const ROUTE_SCHEMA = {
|
|
74
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
75
|
+
$id: 'https://knosky.com/schemas/route.json',
|
|
76
|
+
title: 'KnoSky Route',
|
|
77
|
+
description:
|
|
78
|
+
'Advisory navigation route produced by kc_route. ' +
|
|
79
|
+
'Structural metadata only — never code analysis. ' +
|
|
80
|
+
'Always advisory: the `advisory` field is invariantly true.',
|
|
81
|
+
type: 'object',
|
|
82
|
+
required: [
|
|
83
|
+
'knosky_protocol',
|
|
84
|
+
'artifact_type',
|
|
85
|
+
'advisory',
|
|
86
|
+
'generated_at',
|
|
87
|
+
'destination',
|
|
88
|
+
'route',
|
|
89
|
+
'alternates',
|
|
90
|
+
'caveats',
|
|
91
|
+
'confidence',
|
|
92
|
+
],
|
|
93
|
+
additionalProperties: true,
|
|
94
|
+
properties: {
|
|
95
|
+
knosky_protocol: {
|
|
96
|
+
type: 'string',
|
|
97
|
+
const: '1.0',
|
|
98
|
+
description: 'Protocol version. Always "1.0" for this schema.',
|
|
99
|
+
},
|
|
100
|
+
artifact_type: {
|
|
101
|
+
type: 'string',
|
|
102
|
+
const: 'route',
|
|
103
|
+
description: 'Discriminator field. Always "route" for this artifact.',
|
|
104
|
+
},
|
|
105
|
+
advisory: {
|
|
106
|
+
type: 'boolean',
|
|
107
|
+
const: true,
|
|
108
|
+
description: 'Invariantly true — routes are advisory, never authoritative.',
|
|
109
|
+
},
|
|
110
|
+
generated_at: {
|
|
111
|
+
type: 'string',
|
|
112
|
+
format: 'date-time',
|
|
113
|
+
description: 'ISO-8601 timestamp at which this document was generated.',
|
|
114
|
+
},
|
|
115
|
+
source_rev: {
|
|
116
|
+
type: ['string', 'null'],
|
|
117
|
+
description: 'VCS revision (e.g. git commit SHA) of the city index used. Null when unknown.',
|
|
118
|
+
},
|
|
119
|
+
destination: {
|
|
120
|
+
type: 'string',
|
|
121
|
+
description: 'The navigation target string supplied by the caller.',
|
|
122
|
+
},
|
|
123
|
+
route: {
|
|
124
|
+
type: 'array',
|
|
125
|
+
items: PATH_ENTRY,
|
|
126
|
+
description: 'Ordered primary waypoints. Each entry is a path string or { path, id, reason, score }.',
|
|
127
|
+
},
|
|
128
|
+
alternates: {
|
|
129
|
+
type: 'array',
|
|
130
|
+
items: PATH_ENTRY,
|
|
131
|
+
description: 'Alternate waypoints beyond the primary route limit.',
|
|
132
|
+
},
|
|
133
|
+
caveats: {
|
|
134
|
+
type: 'array',
|
|
135
|
+
items: { type: 'string' },
|
|
136
|
+
description:
|
|
137
|
+
'Advisory notes. Always contains at least the mandatory advisory caveat. ' +
|
|
138
|
+
'May include churn, coverage, and staleness warnings.',
|
|
139
|
+
},
|
|
140
|
+
confidence: {
|
|
141
|
+
type: 'number',
|
|
142
|
+
minimum: 0,
|
|
143
|
+
maximum: 1,
|
|
144
|
+
description: 'Estimated confidence of the route [0..1]. 0 = no signal; ~0.95 = direct file match.',
|
|
145
|
+
},
|
|
146
|
+
tests: {
|
|
147
|
+
type: 'array',
|
|
148
|
+
items: { type: 'object' },
|
|
149
|
+
description: 'Test files in the candidate neighbourhood (informational).',
|
|
150
|
+
},
|
|
151
|
+
docs: {
|
|
152
|
+
type: 'array',
|
|
153
|
+
items: { type: 'object' },
|
|
154
|
+
description: 'Documentation files in the candidate neighbourhood (informational).',
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
// INTENT_MANIFEST_SCHEMA
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* JSON Schema (Draft 2020-12) for the KnoSky `intent-manifest` artifact.
|
|
165
|
+
* Produced by kc_bundle / kcBundle(). Must agree with validateIntentManifest() in core/schema.mjs.
|
|
166
|
+
*
|
|
167
|
+
* @type {object}
|
|
168
|
+
*/
|
|
169
|
+
export const INTENT_MANIFEST_SCHEMA = {
|
|
170
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
171
|
+
$id: 'https://knosky.com/schemas/intent-manifest.json',
|
|
172
|
+
title: 'KnoSky Intent Manifest',
|
|
173
|
+
description:
|
|
174
|
+
'Shareable, verifiable file-set manifest produced by kc_bundle. ' +
|
|
175
|
+
'Each path entry carries a SHA-256 digest. ' +
|
|
176
|
+
'The secret_scan field signals whether the manifest was produced by a clean or blocked scan.',
|
|
177
|
+
type: 'object',
|
|
178
|
+
required: [
|
|
179
|
+
'knosky_protocol',
|
|
180
|
+
'artifact_type',
|
|
181
|
+
'advisory',
|
|
182
|
+
'generated_at',
|
|
183
|
+
'paths',
|
|
184
|
+
'edges',
|
|
185
|
+
'secret_scan',
|
|
186
|
+
],
|
|
187
|
+
additionalProperties: true,
|
|
188
|
+
properties: {
|
|
189
|
+
knosky_protocol: {
|
|
190
|
+
type: 'string',
|
|
191
|
+
const: '1.0',
|
|
192
|
+
description: 'Protocol version. Always "1.0" for this schema.',
|
|
193
|
+
},
|
|
194
|
+
artifact_type: {
|
|
195
|
+
type: 'string',
|
|
196
|
+
const: 'intent-manifest',
|
|
197
|
+
description: 'Discriminator field. Always "intent-manifest" for this artifact.',
|
|
198
|
+
},
|
|
199
|
+
advisory: {
|
|
200
|
+
type: 'boolean',
|
|
201
|
+
const: true,
|
|
202
|
+
description: 'Invariantly true.',
|
|
203
|
+
},
|
|
204
|
+
generated_at: {
|
|
205
|
+
type: 'string',
|
|
206
|
+
format: 'date-time',
|
|
207
|
+
description: 'ISO-8601 timestamp at which this document was generated.',
|
|
208
|
+
},
|
|
209
|
+
paths: {
|
|
210
|
+
type: 'array',
|
|
211
|
+
items: {
|
|
212
|
+
type: 'object',
|
|
213
|
+
required: ['path', 'sha256'],
|
|
214
|
+
properties: {
|
|
215
|
+
path: {
|
|
216
|
+
type: 'string',
|
|
217
|
+
minLength: 1,
|
|
218
|
+
description: 'Repo-relative path. Must not be absolute or contain ".." segments.',
|
|
219
|
+
},
|
|
220
|
+
sha256: {
|
|
221
|
+
type: 'string',
|
|
222
|
+
description: 'Hex-encoded SHA-256 digest of the file at bundle time. Empty string when unreadable.',
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
additionalProperties: true,
|
|
226
|
+
},
|
|
227
|
+
description: 'Ordered list of files covered by this manifest.',
|
|
228
|
+
},
|
|
229
|
+
edges: {
|
|
230
|
+
type: 'array',
|
|
231
|
+
items: { type: 'object' },
|
|
232
|
+
description: 'Dependency edges among the covered files.',
|
|
233
|
+
},
|
|
234
|
+
expiry: {
|
|
235
|
+
type: ['string', 'null'],
|
|
236
|
+
description: 'ISO-8601 expiry timestamp, or null for no expiry.',
|
|
237
|
+
},
|
|
238
|
+
secret_scan: {
|
|
239
|
+
...SECRET_SCAN_RESULT,
|
|
240
|
+
description:
|
|
241
|
+
'Result of the fail-closed secret scan run at bundle time. ' +
|
|
242
|
+
'"clean" means no secret-like values were detected; ' +
|
|
243
|
+
'"blocked" means the scan found a pattern match and the bundle should not be shared.',
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
// CONFIG_SCHEMA
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* JSON Schema (Draft 2020-12) for a KnoSky project config object
|
|
254
|
+
* (`.knosky/config.yml`, parsed to a JS object by loadConfig()).
|
|
255
|
+
* Must agree with validateConfig() in core/config.mjs.
|
|
256
|
+
*
|
|
257
|
+
* @type {object}
|
|
258
|
+
*/
|
|
259
|
+
export const CONFIG_SCHEMA = {
|
|
260
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
261
|
+
$id: 'https://knosky.com/schemas/config.json',
|
|
262
|
+
title: 'KnoSky Project Config',
|
|
263
|
+
description:
|
|
264
|
+
'KnoSky project configuration. Read from `.knosky/config.yml`; ' +
|
|
265
|
+
'missing fields default to the values shown in `default`. ' +
|
|
266
|
+
'Unknown keys are rejected by validateConfig().',
|
|
267
|
+
type: 'object',
|
|
268
|
+
required: [
|
|
269
|
+
'knosky_protocol',
|
|
270
|
+
'telemetry',
|
|
271
|
+
'absolute_paths',
|
|
272
|
+
'fail_on_secret',
|
|
273
|
+
'allow_excerpts',
|
|
274
|
+
'max_excerpt_chars',
|
|
275
|
+
'categories',
|
|
276
|
+
'ignore',
|
|
277
|
+
],
|
|
278
|
+
additionalProperties: false,
|
|
279
|
+
properties: {
|
|
280
|
+
knosky_protocol: {
|
|
281
|
+
type: 'string',
|
|
282
|
+
const: '1.0',
|
|
283
|
+
default: '1.0',
|
|
284
|
+
description: 'Protocol version. Must be "1.0".',
|
|
285
|
+
},
|
|
286
|
+
telemetry: {
|
|
287
|
+
type: 'boolean',
|
|
288
|
+
default: false,
|
|
289
|
+
description: 'Whether to emit telemetry. Defaults to false (never sends data out).',
|
|
290
|
+
},
|
|
291
|
+
absolute_paths: {
|
|
292
|
+
type: 'boolean',
|
|
293
|
+
default: false,
|
|
294
|
+
description: 'Embed absolute filesystem paths in the city output. Defaults to false (basename only).',
|
|
295
|
+
},
|
|
296
|
+
fail_on_secret: {
|
|
297
|
+
type: 'boolean',
|
|
298
|
+
default: true,
|
|
299
|
+
description: 'Abort the build if a secret-like value is detected. Defaults to true (fail-closed).',
|
|
300
|
+
},
|
|
301
|
+
allow_excerpts: {
|
|
302
|
+
type: 'boolean',
|
|
303
|
+
default: false,
|
|
304
|
+
description: 'Include file-content excerpts in the index. Defaults to false.',
|
|
305
|
+
},
|
|
306
|
+
max_excerpt_chars: {
|
|
307
|
+
type: 'integer',
|
|
308
|
+
minimum: 0,
|
|
309
|
+
default: 0,
|
|
310
|
+
description: 'Maximum characters per excerpt. 0 = no excerpts even when allow_excerpts is true.',
|
|
311
|
+
},
|
|
312
|
+
categories: {
|
|
313
|
+
type: 'array',
|
|
314
|
+
items: { type: 'string' },
|
|
315
|
+
default: [],
|
|
316
|
+
description: 'Explicit category (district) labels. Empty array = derived automatically.',
|
|
317
|
+
},
|
|
318
|
+
ignore: {
|
|
319
|
+
type: 'array',
|
|
320
|
+
items: { type: 'string' },
|
|
321
|
+
default: [],
|
|
322
|
+
description: 'Additional glob-style ignore patterns (appended to the built-in defaults and .gitignore).',
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
// ---------------------------------------------------------------------------
|
|
328
|
+
// CITY_SCHEMA
|
|
329
|
+
// ---------------------------------------------------------------------------
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* JSON Schema (Draft 2020-12) for a KnoSky city-data v2 envelope.
|
|
333
|
+
* Produced by the fs-indexer and consumed by the renderer / MCP server.
|
|
334
|
+
* Must agree with validateCity() + NODE_FIELD_ALLOWLIST in core/contract.mjs.
|
|
335
|
+
*
|
|
336
|
+
* @type {object}
|
|
337
|
+
*/
|
|
338
|
+
export const CITY_SCHEMA = {
|
|
339
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
340
|
+
$id: 'https://knosky.com/schemas/city.json',
|
|
341
|
+
title: 'KnoSky City Data (v2)',
|
|
342
|
+
description:
|
|
343
|
+
'N-category city-data envelope (contract v2). ' +
|
|
344
|
+
'Contains pointers and projections only — never full file bodies (decision D-146). ' +
|
|
345
|
+
'See core/CONTRACT.md for narrative spec.',
|
|
346
|
+
type: 'object',
|
|
347
|
+
required: ['schema_version', 'generated_at', 'categories', 'nodes'],
|
|
348
|
+
additionalProperties: true,
|
|
349
|
+
properties: {
|
|
350
|
+
schema_version: {
|
|
351
|
+
type: 'string',
|
|
352
|
+
const: '2.0',
|
|
353
|
+
description: 'City contract version. Always "2.0" for this schema.',
|
|
354
|
+
},
|
|
355
|
+
generated_at: {
|
|
356
|
+
type: 'string',
|
|
357
|
+
format: 'date-time',
|
|
358
|
+
description: 'ISO-8601 timestamp at which the city was built.',
|
|
359
|
+
},
|
|
360
|
+
source: {
|
|
361
|
+
type: 'object',
|
|
362
|
+
required: ['kind', 'ref'],
|
|
363
|
+
properties: {
|
|
364
|
+
kind: { type: 'string', enum: ['fs', 'github', 'board', 'legacy'] },
|
|
365
|
+
ref: { type: 'string' },
|
|
366
|
+
rev: { type: 'string' },
|
|
367
|
+
},
|
|
368
|
+
additionalProperties: false,
|
|
369
|
+
description: 'Where the city was built from.',
|
|
370
|
+
},
|
|
371
|
+
categories: {
|
|
372
|
+
type: 'array',
|
|
373
|
+
items: {
|
|
374
|
+
type: 'object',
|
|
375
|
+
required: ['id', 'label', 'order'],
|
|
376
|
+
properties: {
|
|
377
|
+
id: { type: 'string', minLength: 1 },
|
|
378
|
+
label: { type: 'string', minLength: 1 },
|
|
379
|
+
color: { type: 'string' },
|
|
380
|
+
order: { type: 'integer', minimum: 0 },
|
|
381
|
+
},
|
|
382
|
+
additionalProperties: false,
|
|
383
|
+
},
|
|
384
|
+
description: 'N-category manifest. Replaces the hardcoded 4-district v1 model.',
|
|
385
|
+
},
|
|
386
|
+
node_count: {
|
|
387
|
+
type: 'integer',
|
|
388
|
+
minimum: 0,
|
|
389
|
+
description: 'Expected number of nodes. Must equal nodes.length when present.',
|
|
390
|
+
},
|
|
391
|
+
nodes: {
|
|
392
|
+
type: 'array',
|
|
393
|
+
items: {
|
|
394
|
+
type: 'object',
|
|
395
|
+
required: ['id', 'kind', 'title', 'category', 'links', 'provenance'],
|
|
396
|
+
// additionalProperties:false enforces the serialization allowlist from contract.mjs.
|
|
397
|
+
additionalProperties: false,
|
|
398
|
+
properties: {
|
|
399
|
+
id: {
|
|
400
|
+
type: 'string',
|
|
401
|
+
minLength: 1,
|
|
402
|
+
description: 'Stable, source-derived node identifier.',
|
|
403
|
+
},
|
|
404
|
+
kind: {
|
|
405
|
+
type: 'string',
|
|
406
|
+
description: 'Node kind: decision, spec, file, dir, doc, …',
|
|
407
|
+
},
|
|
408
|
+
title: {
|
|
409
|
+
type: 'string',
|
|
410
|
+
description: 'Short projection title (scrubbed; never a full file body).',
|
|
411
|
+
},
|
|
412
|
+
summary: {
|
|
413
|
+
type: 'string',
|
|
414
|
+
maxLength: 200,
|
|
415
|
+
description:
|
|
416
|
+
'Short excerpt projection (≤ 200 chars, scrubbed; never a full file body).',
|
|
417
|
+
},
|
|
418
|
+
category: {
|
|
419
|
+
type: 'string',
|
|
420
|
+
description: 'Category id. Must match an entry in the top-level categories[] manifest.',
|
|
421
|
+
},
|
|
422
|
+
status: { type: 'string' },
|
|
423
|
+
fact_date: { type: 'string' },
|
|
424
|
+
tags: {
|
|
425
|
+
type: 'array',
|
|
426
|
+
items: { type: 'string' },
|
|
427
|
+
},
|
|
428
|
+
headings: {
|
|
429
|
+
type: 'array',
|
|
430
|
+
items: { type: 'string' },
|
|
431
|
+
},
|
|
432
|
+
links: {
|
|
433
|
+
type: 'array',
|
|
434
|
+
items: { type: 'string' },
|
|
435
|
+
description: 'Edges to other node ids.',
|
|
436
|
+
},
|
|
437
|
+
churn: {
|
|
438
|
+
description: 'Recent-change signal. Object with `c` (commit count) or a plain number.',
|
|
439
|
+
},
|
|
440
|
+
provenance: {
|
|
441
|
+
...PROVENANCE,
|
|
442
|
+
description: 'Back-pointer to the live source. Fields: store (required), ref (required).',
|
|
443
|
+
},
|
|
444
|
+
visibility: {
|
|
445
|
+
type: 'string',
|
|
446
|
+
enum: ['internal', 'public'],
|
|
447
|
+
},
|
|
448
|
+
sensitive: {
|
|
449
|
+
type: 'boolean',
|
|
450
|
+
description: 'Scrub/flag marker. True when a sensitive-term match was found.',
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
},
|
|
454
|
+
description:
|
|
455
|
+
'All nodes in the city. Node fields are strictly limited to the serialization allowlist ' +
|
|
456
|
+
'(id, kind, title, summary, category, status, fact_date, tags, headings, links, churn, ' +
|
|
457
|
+
'provenance, visibility, sensitive).',
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
};
|
package/core/route.mjs
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { getRelated } from './retrieve.mjs';
|
|
4
4
|
import { makeRouteDoc, validateRouteDoc } from './schema.mjs';
|
|
5
5
|
import { parseDestination } from './destination.mjs';
|
|
6
|
+
import { extractLedgerSeq } from './freshness.mjs';
|
|
6
7
|
|
|
7
8
|
// ---------------------------------------------------------------------------
|
|
8
9
|
// Internal helpers
|
|
@@ -233,6 +234,9 @@ export function kcRoute(ctx, destination, { limit = 8, overlays } = {}) {
|
|
|
233
234
|
caveats.push('route is based on rev ' + source_rev + ' and may be stale');
|
|
234
235
|
}
|
|
235
236
|
|
|
237
|
+
// Ledger-anchored freshness (SAT-444): read the monotone commit count from the city.
|
|
238
|
+
const ledger_seq = extractLedgerSeq(ctx.city);
|
|
239
|
+
|
|
236
240
|
// Slice into route + alternates
|
|
237
241
|
const routeEntries = scored.slice(0, clampedLimit);
|
|
238
242
|
const alternateEntries = scored.slice(clampedLimit, clampedLimit + 5);
|
|
@@ -283,6 +287,7 @@ export function kcRoute(ctx, destination, { limit = 8, overlays } = {}) {
|
|
|
283
287
|
caveats,
|
|
284
288
|
confidence,
|
|
285
289
|
source_rev,
|
|
290
|
+
ledger_seq,
|
|
286
291
|
});
|
|
287
292
|
|
|
288
293
|
// Attach tests + docs (extra fields beyond the base envelope — schema.validateRouteDoc does not reject them)
|
package/core/schema.mjs
CHANGED
|
@@ -86,6 +86,9 @@ function checkAbsolutePaths(entries, fieldName, errors) {
|
|
|
86
86
|
* @param {string[]} [opts.caveats] Advisory notes.
|
|
87
87
|
* @param {number} [opts.confidence] Confidence score [0..1].
|
|
88
88
|
* @param {string|null} [opts.source_rev] VCS revision that produced this doc.
|
|
89
|
+
* @param {number|null} [opts.ledger_seq] Ledger sequence number (SAT-444) —
|
|
90
|
+
* monotone commit count from the city that produced this route doc.
|
|
91
|
+
* null means the city predates ledger-anchored freshness.
|
|
89
92
|
* @returns {object}
|
|
90
93
|
*/
|
|
91
94
|
export function makeRouteDoc({
|
|
@@ -95,6 +98,7 @@ export function makeRouteDoc({
|
|
|
95
98
|
caveats = [],
|
|
96
99
|
confidence = 0,
|
|
97
100
|
source_rev = null,
|
|
101
|
+
ledger_seq = null,
|
|
98
102
|
} = {}) {
|
|
99
103
|
return {
|
|
100
104
|
knosky_protocol: PROTOCOL_VERSION,
|
|
@@ -102,6 +106,7 @@ export function makeRouteDoc({
|
|
|
102
106
|
advisory: true,
|
|
103
107
|
generated_at: new Date().toISOString(),
|
|
104
108
|
source_rev,
|
|
109
|
+
ledger_seq,
|
|
105
110
|
destination,
|
|
106
111
|
route,
|
|
107
112
|
alternates,
|
|
@@ -142,10 +147,18 @@ export function validateRouteDoc(doc) {
|
|
|
142
147
|
}
|
|
143
148
|
}
|
|
144
149
|
|
|
145
|
-
|
|
150
|
+
// Number.isFinite rejects NaN and ±Infinity; the range check then enforces [0, 1].
|
|
151
|
+
if (!Number.isFinite(doc.confidence) || doc.confidence < 0 || doc.confidence > 1) {
|
|
146
152
|
errors.push(`confidence must be a number in [0, 1], got: ${JSON.stringify(doc.confidence)}`);
|
|
147
153
|
}
|
|
148
154
|
|
|
155
|
+
// ledger_seq: must be null or a non-negative integer (SAT-444)
|
|
156
|
+
if (doc.ledger_seq !== undefined && doc.ledger_seq !== null) {
|
|
157
|
+
if (typeof doc.ledger_seq !== 'number' || !Number.isInteger(doc.ledger_seq) || doc.ledger_seq < 0) {
|
|
158
|
+
errors.push(`ledger_seq must be null or a non-negative integer, got: ${JSON.stringify(doc.ledger_seq)}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
149
162
|
// Absolute-path invariant applies to route[] and alternates[]
|
|
150
163
|
if (Array.isArray(doc.route)) {
|
|
151
164
|
checkAbsolutePaths(doc.route, 'route', errors);
|
|
@@ -169,6 +182,9 @@ export function validateRouteDoc(doc) {
|
|
|
169
182
|
* @param {unknown[]} [opts.edges] Dependency edges.
|
|
170
183
|
* @param {string|null} [opts.expiry] ISO-8601 expiry timestamp or null.
|
|
171
184
|
* @param {object} opts.secret_scan REQUIRED secret-scan result object.
|
|
185
|
+
* @param {number|null} [opts.ledger_seq] Ledger sequence number (SAT-444) —
|
|
186
|
+
* monotone commit count from the city that produced this manifest.
|
|
187
|
+
* null means the city predates ledger-anchored freshness.
|
|
172
188
|
* @returns {object}
|
|
173
189
|
*/
|
|
174
190
|
export function makeIntentManifest({
|
|
@@ -176,6 +192,7 @@ export function makeIntentManifest({
|
|
|
176
192
|
edges = [],
|
|
177
193
|
expiry = null,
|
|
178
194
|
secret_scan,
|
|
195
|
+
ledger_seq = null,
|
|
179
196
|
} = {}) {
|
|
180
197
|
return {
|
|
181
198
|
knosky_protocol: PROTOCOL_VERSION,
|
|
@@ -185,6 +202,7 @@ export function makeIntentManifest({
|
|
|
185
202
|
paths,
|
|
186
203
|
edges,
|
|
187
204
|
expiry,
|
|
205
|
+
ledger_seq,
|
|
188
206
|
secret_scan,
|
|
189
207
|
};
|
|
190
208
|
}
|
|
@@ -237,6 +255,13 @@ export function validateIntentManifest(doc) {
|
|
|
237
255
|
checkAbsolutePaths(doc.paths, 'paths', errors);
|
|
238
256
|
}
|
|
239
257
|
|
|
258
|
+
// ledger_seq: must be null or a non-negative integer (SAT-444)
|
|
259
|
+
if (doc.ledger_seq !== undefined && doc.ledger_seq !== null) {
|
|
260
|
+
if (typeof doc.ledger_seq !== 'number' || !Number.isInteger(doc.ledger_seq) || doc.ledger_seq < 0) {
|
|
261
|
+
errors.push(`ledger_seq must be null or a non-negative integer, got: ${JSON.stringify(doc.ledger_seq)}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
240
265
|
// secret_scan must be present and have a valid status
|
|
241
266
|
if (!doc.secret_scan || typeof doc.secret_scan !== 'object') {
|
|
242
267
|
errors.push('secret_scan is required and must be an object');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "knosky",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Turn any repo or docs folder into an explorable city, plus a local MCP that grounds your AI in your own source with citations. Local-first, free, $0 tokens.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
"bin",
|
|
11
11
|
"core",
|
|
12
12
|
"renderer",
|
|
13
|
+
"action",
|
|
14
|
+
"action.yml",
|
|
13
15
|
"mcp/server.mjs",
|
|
14
16
|
"README.md",
|
|
15
17
|
"LICENSE.md",
|