@disclosureos/cli 1.0.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 +17 -0
- package/LICENSE +21 -0
- package/README.md +60 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1358 -0
- package/package.json +70 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1358 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
5
|
+
import { dirname, join } from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
|
|
8
|
+
// src/utils/args.ts
|
|
9
|
+
var SHORT_FLAG_MAP = {
|
|
10
|
+
o: "out",
|
|
11
|
+
h: "help",
|
|
12
|
+
v: "version",
|
|
13
|
+
r: "recursive",
|
|
14
|
+
s: "strict",
|
|
15
|
+
j: "json"
|
|
16
|
+
};
|
|
17
|
+
var VALUE_FLAGS = /* @__PURE__ */ new Set(["out", "category", "id"]);
|
|
18
|
+
function assignFlag(key, args2, index, flags) {
|
|
19
|
+
if (VALUE_FLAGS.has(key)) {
|
|
20
|
+
const next = args2[index + 1];
|
|
21
|
+
if (next !== void 0 && !next.startsWith("-")) {
|
|
22
|
+
flags[key] = next;
|
|
23
|
+
return index + 1;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
flags[key] = true;
|
|
27
|
+
return index;
|
|
28
|
+
}
|
|
29
|
+
function parseArgs(argv) {
|
|
30
|
+
const args2 = argv.slice(2);
|
|
31
|
+
const positional = [];
|
|
32
|
+
const flags = {};
|
|
33
|
+
let command = "help";
|
|
34
|
+
let subcommand = "";
|
|
35
|
+
let positionsFilled = 0;
|
|
36
|
+
for (let i = 0; i < args2.length; i++) {
|
|
37
|
+
const arg = args2[i];
|
|
38
|
+
if (arg.startsWith("--")) {
|
|
39
|
+
i = assignFlag(arg.slice(2), args2, i, flags);
|
|
40
|
+
} else if (arg.startsWith("-") && arg.length === 2) {
|
|
41
|
+
const longKey = SHORT_FLAG_MAP[arg[1]] ?? arg[1];
|
|
42
|
+
i = assignFlag(longKey, args2, i, flags);
|
|
43
|
+
} else if (positionsFilled === 0) {
|
|
44
|
+
command = arg;
|
|
45
|
+
positionsFilled++;
|
|
46
|
+
} else if (positionsFilled === 1) {
|
|
47
|
+
subcommand = arg;
|
|
48
|
+
positionsFilled++;
|
|
49
|
+
} else {
|
|
50
|
+
positional.push(arg);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return { command, subcommand, positional, flags };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/output/templates.ts
|
|
57
|
+
function uuid() {
|
|
58
|
+
return globalThis.crypto.randomUUID();
|
|
59
|
+
}
|
|
60
|
+
function buildObservation(full) {
|
|
61
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
62
|
+
const minimal = {
|
|
63
|
+
id: uuid(),
|
|
64
|
+
temporal: {
|
|
65
|
+
date: "2024-01-01",
|
|
66
|
+
dateCertainty: "exact",
|
|
67
|
+
timeOfDay: "night"
|
|
68
|
+
},
|
|
69
|
+
location: {
|
|
70
|
+
id: uuid(),
|
|
71
|
+
name: "Untitled Observation",
|
|
72
|
+
country: "Unknown",
|
|
73
|
+
latitude: 0,
|
|
74
|
+
longitude: 0,
|
|
75
|
+
siteType: "unknown",
|
|
76
|
+
terrain: "unknown"
|
|
77
|
+
},
|
|
78
|
+
status: "draft",
|
|
79
|
+
createdAt: now,
|
|
80
|
+
updatedAt: now,
|
|
81
|
+
summary: ""
|
|
82
|
+
};
|
|
83
|
+
if (!full) return minimal;
|
|
84
|
+
return {
|
|
85
|
+
...minimal,
|
|
86
|
+
temporal: {
|
|
87
|
+
date: "2024-01-01",
|
|
88
|
+
dateCertainty: "exact",
|
|
89
|
+
timeOfDay: "night",
|
|
90
|
+
durationSeconds: 0
|
|
91
|
+
},
|
|
92
|
+
location: {
|
|
93
|
+
id: uuid(),
|
|
94
|
+
name: "Untitled Observation",
|
|
95
|
+
country: "Unknown",
|
|
96
|
+
latitude: 0,
|
|
97
|
+
longitude: 0,
|
|
98
|
+
siteType: "unknown",
|
|
99
|
+
terrain: "unknown",
|
|
100
|
+
elevationMeters: 0,
|
|
101
|
+
coordinatesApproximate: false,
|
|
102
|
+
locationSensitivity: "none"
|
|
103
|
+
},
|
|
104
|
+
description: "",
|
|
105
|
+
objectCharacteristics: {
|
|
106
|
+
shape: "unknown",
|
|
107
|
+
color: "",
|
|
108
|
+
size: "",
|
|
109
|
+
luminosity: "",
|
|
110
|
+
sound: "",
|
|
111
|
+
numberObserved: 1
|
|
112
|
+
},
|
|
113
|
+
eventType: "",
|
|
114
|
+
sourceData: { sources: [] },
|
|
115
|
+
investigation: {
|
|
116
|
+
investigatingBody: "",
|
|
117
|
+
caseNumber: "",
|
|
118
|
+
conclusion: "",
|
|
119
|
+
confidence: "unassessed"
|
|
120
|
+
},
|
|
121
|
+
movement: { speed: "", maneuvers: [] },
|
|
122
|
+
witnesses: { count: 0, categories: [] },
|
|
123
|
+
sensorEvidence: { sensors: [], multiSensorCorrelation: false },
|
|
124
|
+
environment: { weather: "", visibility: "" },
|
|
125
|
+
media: [],
|
|
126
|
+
relations: { edges: [] },
|
|
127
|
+
dataSourceId: "",
|
|
128
|
+
schemaVersion: "1.0.0"
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
var ASSESSMENT_TEMPLATE = {
|
|
132
|
+
technology: {
|
|
133
|
+
antigravity_lift: [
|
|
134
|
+
{ level: "not_indicated", rationale: "", evidenceRefs: [] }
|
|
135
|
+
],
|
|
136
|
+
instantaneous_acceleration: [{ level: "not_indicated", rationale: "" }],
|
|
137
|
+
hypersonic_no_signatures: [{ level: "not_indicated", rationale: "" }],
|
|
138
|
+
low_observability: [{ level: "not_indicated", rationale: "" }],
|
|
139
|
+
transmedium_travel: [{ level: "not_indicated", rationale: "" }],
|
|
140
|
+
biological_effects: [{ level: "not_indicated", rationale: "" }]
|
|
141
|
+
},
|
|
142
|
+
biologics: {
|
|
143
|
+
molecular_complexity: [{ level: "not_indicated", rationale: "" }],
|
|
144
|
+
isotopic_provenance: [{ level: "not_indicated", rationale: "" }],
|
|
145
|
+
non_standard_biochemistry: [{ level: "not_indicated", rationale: "" }],
|
|
146
|
+
non_phylogenetic_genetics: [{ level: "not_indicated", rationale: "" }],
|
|
147
|
+
anomalous_morphology: [{ level: "not_indicated", rationale: "" }],
|
|
148
|
+
anomalous_bio_interaction: [{ level: "not_indicated", rationale: "" }]
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
var CLASSIFICATION_TEMPLATE = [
|
|
152
|
+
{
|
|
153
|
+
primaryHypothesis: "1.1.1",
|
|
154
|
+
confidence: 0.5,
|
|
155
|
+
alternativeHypotheses: [{ nodeId: "1.1.3", confidence: 0.2 }],
|
|
156
|
+
rationale: "",
|
|
157
|
+
evaluatedBy: "",
|
|
158
|
+
evidenceRefs: []
|
|
159
|
+
}
|
|
160
|
+
];
|
|
161
|
+
function getTemplate(name) {
|
|
162
|
+
const data = name === "observation" ? buildObservation(false) : name === "observation-full" ? buildObservation(true) : name === "assessment" ? ASSESSMENT_TEMPLATE : CLASSIFICATION_TEMPLATE;
|
|
163
|
+
return JSON.stringify(data, null, 2);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// src/utils/fs.ts
|
|
167
|
+
import { readFileSync, writeFileSync, readdirSync, statSync, existsSync } from "fs";
|
|
168
|
+
import { resolve, extname } from "path";
|
|
169
|
+
function readJSON(filePath) {
|
|
170
|
+
const content = readFileSync(resolve(filePath), "utf-8");
|
|
171
|
+
return JSON.parse(content);
|
|
172
|
+
}
|
|
173
|
+
function writeOutput(filePath, data) {
|
|
174
|
+
const content = typeof data === "string" ? data : JSON.stringify(data, null, 2);
|
|
175
|
+
writeFileSync(resolve(filePath), content + "\n", "utf-8");
|
|
176
|
+
}
|
|
177
|
+
function findJSONFiles(dirPath, recursive) {
|
|
178
|
+
const resolved = resolve(dirPath);
|
|
179
|
+
if (!existsSync(resolved)) return [];
|
|
180
|
+
const stat = statSync(resolved);
|
|
181
|
+
if (stat.isFile()) {
|
|
182
|
+
return extname(resolved) === ".json" ? [resolved] : [];
|
|
183
|
+
}
|
|
184
|
+
const results = [];
|
|
185
|
+
const entries = readdirSync(resolved, { withFileTypes: true });
|
|
186
|
+
for (const entry of entries) {
|
|
187
|
+
const entryPath = resolve(resolved, entry.name);
|
|
188
|
+
if (entry.isFile() && extname(entry.name) === ".json") {
|
|
189
|
+
results.push(entryPath);
|
|
190
|
+
} else if (entry.isDirectory() && recursive) {
|
|
191
|
+
results.push(...findJSONFiles(entryPath, true));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return results;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/output/format.ts
|
|
198
|
+
import pc from "picocolors";
|
|
199
|
+
var BRAND = pc.bold(pc.cyan("DisclosureOS"));
|
|
200
|
+
function heading(text) {
|
|
201
|
+
return `
|
|
202
|
+
${pc.bold(pc.white(text))}
|
|
203
|
+
${"\u2500".repeat(text.length)}`;
|
|
204
|
+
}
|
|
205
|
+
function success(text) {
|
|
206
|
+
return pc.green(`\u2713 ${text}`);
|
|
207
|
+
}
|
|
208
|
+
function error(text) {
|
|
209
|
+
return pc.red(`\u2717 ${text}`);
|
|
210
|
+
}
|
|
211
|
+
function warn(text) {
|
|
212
|
+
return pc.yellow(`\u26A0 ${text}`);
|
|
213
|
+
}
|
|
214
|
+
function dim(text) {
|
|
215
|
+
return pc.dim(text);
|
|
216
|
+
}
|
|
217
|
+
function label(key, value) {
|
|
218
|
+
return ` ${pc.bold(key)}: ${value}`;
|
|
219
|
+
}
|
|
220
|
+
function bullet(text) {
|
|
221
|
+
return ` ${pc.dim("\u2022")} ${text}`;
|
|
222
|
+
}
|
|
223
|
+
function table(rows, gutter = 2) {
|
|
224
|
+
const maxKey = Math.max(...rows.map(([k]) => k.length));
|
|
225
|
+
return rows.map(([k, v]) => ` ${pc.bold(k.padEnd(maxKey + gutter))}${v}`).join("\n");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/commands/scaffold.ts
|
|
229
|
+
var VALID_TEMPLATES = ["observation", "observation-full", "assessment", "classification"];
|
|
230
|
+
function scaffold(args2) {
|
|
231
|
+
if (args2.flags["help"]) {
|
|
232
|
+
printUsage();
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const templateName = resolveTemplateName(args2);
|
|
236
|
+
if (!templateName) {
|
|
237
|
+
printUsage();
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const asTypeScript = !!args2.flags["ts"];
|
|
241
|
+
const content = asTypeScript ? getTypeScriptTemplate(templateName) : getTemplate(templateName);
|
|
242
|
+
const outputPath = args2.flags["out"];
|
|
243
|
+
if (outputPath && typeof outputPath === "string") {
|
|
244
|
+
writeOutput(outputPath, content);
|
|
245
|
+
console.log(success(`Wrote ${templateName} template to ${outputPath}`));
|
|
246
|
+
} else {
|
|
247
|
+
console.log(content);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function resolveTemplateName(args2) {
|
|
251
|
+
const sub = args2.subcommand;
|
|
252
|
+
if (!sub) return null;
|
|
253
|
+
if (sub === "observation") {
|
|
254
|
+
return args2.flags["full"] ? "observation-full" : "observation";
|
|
255
|
+
}
|
|
256
|
+
if (VALID_TEMPLATES.includes(sub)) {
|
|
257
|
+
return sub;
|
|
258
|
+
}
|
|
259
|
+
console.log(error(`Unknown template: "${sub}"`));
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
function getTypeScriptTemplate(name) {
|
|
263
|
+
switch (name) {
|
|
264
|
+
case "observation":
|
|
265
|
+
return TS_OBSERVATION;
|
|
266
|
+
case "observation-full":
|
|
267
|
+
return TS_OBSERVATION_FULL;
|
|
268
|
+
case "assessment":
|
|
269
|
+
return TS_ASSESSMENT;
|
|
270
|
+
case "classification":
|
|
271
|
+
return TS_CLASSIFICATION;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
var TS_OBSERVATION = `import { createObservation } from '@disclosureos/records/factories';
|
|
275
|
+
|
|
276
|
+
// createObservation auto-generates id, location.id, and timestamps.
|
|
277
|
+
export const observation = createObservation(
|
|
278
|
+
{
|
|
279
|
+
temporal: {
|
|
280
|
+
date: '2024-01-01',
|
|
281
|
+
dateCertainty: 'exact',
|
|
282
|
+
timeOfDay: 'night',
|
|
283
|
+
},
|
|
284
|
+
location: {
|
|
285
|
+
name: '',
|
|
286
|
+
country: '',
|
|
287
|
+
latitude: 0,
|
|
288
|
+
longitude: 0,
|
|
289
|
+
siteType: 'unknown',
|
|
290
|
+
},
|
|
291
|
+
summary: '',
|
|
292
|
+
},
|
|
293
|
+
{ status: 'draft' },
|
|
294
|
+
);
|
|
295
|
+
`;
|
|
296
|
+
var TS_OBSERVATION_FULL = `import { createObservation } from '@disclosureos/records/factories';
|
|
297
|
+
|
|
298
|
+
// createObservation auto-generates id, location.id, and timestamps.
|
|
299
|
+
export const observation = createObservation(
|
|
300
|
+
{
|
|
301
|
+
temporal: {
|
|
302
|
+
date: '2024-01-01',
|
|
303
|
+
dateCertainty: 'exact',
|
|
304
|
+
timeOfDay: 'night',
|
|
305
|
+
durationSeconds: 0,
|
|
306
|
+
},
|
|
307
|
+
location: {
|
|
308
|
+
name: '',
|
|
309
|
+
country: '',
|
|
310
|
+
latitude: 0,
|
|
311
|
+
longitude: 0,
|
|
312
|
+
siteType: 'unknown',
|
|
313
|
+
terrain: 'unknown',
|
|
314
|
+
},
|
|
315
|
+
summary: '',
|
|
316
|
+
description: '',
|
|
317
|
+
objectCharacteristics: {
|
|
318
|
+
shape: 'unknown',
|
|
319
|
+
color: '',
|
|
320
|
+
numberObserved: 1,
|
|
321
|
+
},
|
|
322
|
+
eventType: '',
|
|
323
|
+
sourceData: { sources: [] },
|
|
324
|
+
investigation: {
|
|
325
|
+
investigatingBody: '',
|
|
326
|
+
conclusion: '',
|
|
327
|
+
confidence: 'unassessed',
|
|
328
|
+
},
|
|
329
|
+
movement: { maneuvers: [] },
|
|
330
|
+
witnesses: { count: 0, categories: [] },
|
|
331
|
+
sensorEvidence: { sensors: [], multiSensorCorrelation: false },
|
|
332
|
+
environment: { weather: '', visibility: '' },
|
|
333
|
+
media: [],
|
|
334
|
+
schemaVersion: '1.0.0',
|
|
335
|
+
},
|
|
336
|
+
{ status: 'draft' },
|
|
337
|
+
);
|
|
338
|
+
`;
|
|
339
|
+
var TS_ASSESSMENT = `import type { ObservableAssessmentMap } from '@disclosureos/observables';
|
|
340
|
+
import { createObservableClaim } from '@disclosureos/observables';
|
|
341
|
+
|
|
342
|
+
// Each observable maps to a *list* of claims, so competing verdicts coexist.
|
|
343
|
+
// Pass evidenceRefs (e.g. ['sensor:<id>']) to link a claim to in-record evidence.
|
|
344
|
+
export const assessment: ObservableAssessmentMap = {
|
|
345
|
+
technology: {
|
|
346
|
+
antigravity_lift: [createObservableClaim('not_indicated')],
|
|
347
|
+
instantaneous_acceleration: [createObservableClaim('not_indicated')],
|
|
348
|
+
hypersonic_no_signatures: [createObservableClaim('not_indicated')],
|
|
349
|
+
low_observability: [createObservableClaim('not_indicated')],
|
|
350
|
+
transmedium_travel: [createObservableClaim('not_indicated')],
|
|
351
|
+
biological_effects: [createObservableClaim('not_indicated')],
|
|
352
|
+
},
|
|
353
|
+
biologics: {
|
|
354
|
+
molecular_complexity: [createObservableClaim('not_indicated')],
|
|
355
|
+
isotopic_provenance: [createObservableClaim('not_indicated')],
|
|
356
|
+
non_standard_biochemistry: [createObservableClaim('not_indicated')],
|
|
357
|
+
non_phylogenetic_genetics: [createObservableClaim('not_indicated')],
|
|
358
|
+
anomalous_morphology: [createObservableClaim('not_indicated')],
|
|
359
|
+
anomalous_bio_interaction: [createObservableClaim('not_indicated')],
|
|
360
|
+
},
|
|
361
|
+
};
|
|
362
|
+
`;
|
|
363
|
+
var TS_CLASSIFICATION = `import { createOriginClaim } from '@disclosureos/origins';
|
|
364
|
+
import type { OriginClassificationSlot } from '@disclosureos/origins';
|
|
365
|
+
|
|
366
|
+
// The origin slot is a *list* of competing classifications (one per evaluator).
|
|
367
|
+
// Replace '1.1.1' with your primary OCS node ID; createOriginClaim validates
|
|
368
|
+
// node IDs and confidence at runtime.
|
|
369
|
+
export const classification: OriginClassificationSlot = [
|
|
370
|
+
createOriginClaim('1.1.1', 0.5, {
|
|
371
|
+
alternativeHypotheses: [{ nodeId: '1.1.3', confidence: 0.2 }],
|
|
372
|
+
rationale: '',
|
|
373
|
+
}),
|
|
374
|
+
];
|
|
375
|
+
`;
|
|
376
|
+
function printUsage() {
|
|
377
|
+
console.log(heading(`${BRAND} scaffold`));
|
|
378
|
+
console.log(`
|
|
379
|
+
Generate typed templates for DisclosureOS data structures.
|
|
380
|
+
`);
|
|
381
|
+
console.log(`${dim("Usage:")}`);
|
|
382
|
+
console.log(` disclosureos scaffold <template> [options]
|
|
383
|
+
`);
|
|
384
|
+
console.log(`${dim("Templates:")}`);
|
|
385
|
+
console.log(` observation Minimal Observation (required fields only)`);
|
|
386
|
+
console.log(` observation --full Complete Observation with all optional fields`);
|
|
387
|
+
console.log(` assessment Observable assessment map (tech + biologics)`);
|
|
388
|
+
console.log(` classification Origin classification assignment
|
|
389
|
+
`);
|
|
390
|
+
console.log(`${dim("Options:")}`);
|
|
391
|
+
console.log(` --out, -o <path> Write to file instead of stdout`);
|
|
392
|
+
console.log(` --ts Output as TypeScript (with typed imports)`);
|
|
393
|
+
console.log(` --help, -h Show this help message
|
|
394
|
+
`);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// src/commands/validate.ts
|
|
398
|
+
import { parseEvidenceRef } from "@disclosureos/records/shared";
|
|
399
|
+
import { parseEnrichedObservation } from "@disclosureos/schema";
|
|
400
|
+
function validate(args2) {
|
|
401
|
+
if (args2.flags["help"]) {
|
|
402
|
+
printUsage2();
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const targets = [args2.subcommand, ...args2.positional].filter((t) => !!t);
|
|
406
|
+
if (targets.length === 0) {
|
|
407
|
+
printUsage2();
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
const recursive = !!args2.flags["recursive"];
|
|
411
|
+
const strict = !!args2.flags["strict"];
|
|
412
|
+
const jsonMode = !!args2.flags["json"];
|
|
413
|
+
const seen = /* @__PURE__ */ new Set();
|
|
414
|
+
const files = [];
|
|
415
|
+
const emptyTargets = [];
|
|
416
|
+
for (const target of targets) {
|
|
417
|
+
const found = findJSONFiles(target, recursive);
|
|
418
|
+
if (found.length === 0) {
|
|
419
|
+
emptyTargets.push(target);
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
for (const f of found) {
|
|
423
|
+
if (!seen.has(f)) {
|
|
424
|
+
seen.add(f);
|
|
425
|
+
files.push(f);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (jsonMode) {
|
|
430
|
+
runJsonMode(files, emptyTargets, strict);
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
for (const t of emptyTargets) {
|
|
434
|
+
console.log(error(`No JSON files found at: ${t}`));
|
|
435
|
+
}
|
|
436
|
+
if (files.length === 0) {
|
|
437
|
+
process.exit(1);
|
|
438
|
+
}
|
|
439
|
+
let totalErrors = 0;
|
|
440
|
+
let totalWarnings = 0;
|
|
441
|
+
for (const file of files) {
|
|
442
|
+
const { errors: fileErrors, warnings } = validateFile(file, strict);
|
|
443
|
+
totalErrors += fileErrors;
|
|
444
|
+
totalWarnings += warnings;
|
|
445
|
+
}
|
|
446
|
+
console.log("");
|
|
447
|
+
if (totalErrors === 0 && emptyTargets.length === 0) {
|
|
448
|
+
const warnSuffix = totalWarnings > 0 ? ` (${totalWarnings} warning${totalWarnings > 1 ? "s" : ""})` : "";
|
|
449
|
+
console.log(success(`All ${files.length} file(s) valid${warnSuffix}`));
|
|
450
|
+
} else {
|
|
451
|
+
if (totalErrors > 0) {
|
|
452
|
+
console.log(error(`${totalErrors} error(s) across ${files.length} file(s)`));
|
|
453
|
+
}
|
|
454
|
+
if (emptyTargets.length > 0) {
|
|
455
|
+
console.log(error(`${emptyTargets.length} path(s) matched no JSON file`));
|
|
456
|
+
}
|
|
457
|
+
process.exit(1);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
function runJsonMode(files, emptyTargets, strict) {
|
|
461
|
+
const fileResults = [];
|
|
462
|
+
for (const file of files) {
|
|
463
|
+
fileResults.push(validateFileStructured(file, strict));
|
|
464
|
+
}
|
|
465
|
+
const totalErrors = fileResults.reduce((sum, r) => sum + r.errors.length, 0);
|
|
466
|
+
const totalWarnings = fileResults.reduce((sum, r) => sum + r.warnings.length, 0);
|
|
467
|
+
const output = {
|
|
468
|
+
files: fileResults,
|
|
469
|
+
emptyTargets,
|
|
470
|
+
totals: {
|
|
471
|
+
files: files.length,
|
|
472
|
+
errors: totalErrors,
|
|
473
|
+
warnings: totalWarnings,
|
|
474
|
+
emptyTargets: emptyTargets.length
|
|
475
|
+
},
|
|
476
|
+
valid: totalErrors === 0 && emptyTargets.length === 0
|
|
477
|
+
};
|
|
478
|
+
console.log(JSON.stringify(output, null, 2));
|
|
479
|
+
if (!output.valid) {
|
|
480
|
+
process.exit(1);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
function validateFileStructured(filePath, strict) {
|
|
484
|
+
let data;
|
|
485
|
+
try {
|
|
486
|
+
data = readJSON(filePath);
|
|
487
|
+
} catch {
|
|
488
|
+
return {
|
|
489
|
+
file: filePath,
|
|
490
|
+
valid: false,
|
|
491
|
+
errors: [{ path: "", message: "Failed to parse JSON" }],
|
|
492
|
+
warnings: []
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
if (!data || typeof data !== "object") {
|
|
496
|
+
return {
|
|
497
|
+
file: filePath,
|
|
498
|
+
valid: false,
|
|
499
|
+
errors: [{ path: "", message: "Not a valid JSON object" }],
|
|
500
|
+
warnings: []
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
const obs = data;
|
|
504
|
+
const structuralErrors = parseEnrichedObservation(obs).issues;
|
|
505
|
+
const semanticErrors = validateSemantics(obs);
|
|
506
|
+
const warnings = [...validateEvidenceRefs(obs), ...strict ? validateStrict(obs) : []];
|
|
507
|
+
const allErrors = [...structuralErrors, ...semanticErrors];
|
|
508
|
+
return {
|
|
509
|
+
file: filePath,
|
|
510
|
+
valid: allErrors.length === 0,
|
|
511
|
+
errors: allErrors.map((e) => ({ path: e.path, message: e.message })),
|
|
512
|
+
warnings: warnings.map((w) => ({ path: w.path, message: w.message }))
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
function validateFile(filePath, strict) {
|
|
516
|
+
let data;
|
|
517
|
+
try {
|
|
518
|
+
data = readJSON(filePath);
|
|
519
|
+
} catch {
|
|
520
|
+
console.log(error(`Failed to parse: ${filePath}`));
|
|
521
|
+
return { errors: 1, warnings: 0 };
|
|
522
|
+
}
|
|
523
|
+
if (!data || typeof data !== "object") {
|
|
524
|
+
console.log(error(`${filePath}: not a valid JSON object`));
|
|
525
|
+
return { errors: 1, warnings: 0 };
|
|
526
|
+
}
|
|
527
|
+
const obs = data;
|
|
528
|
+
const structuralErrors = parseEnrichedObservation(obs).issues;
|
|
529
|
+
const semanticErrors = validateSemantics(obs);
|
|
530
|
+
const warnings = [...validateEvidenceRefs(obs), ...strict ? validateStrict(obs) : []];
|
|
531
|
+
const allErrors = [...structuralErrors, ...semanticErrors];
|
|
532
|
+
if (allErrors.length > 0 || warnings.length > 0) {
|
|
533
|
+
console.log(`
|
|
534
|
+
${dim(filePath)}`);
|
|
535
|
+
for (const e of allErrors) {
|
|
536
|
+
console.log(` ${error(`${e.path}: ${e.message}`)}`);
|
|
537
|
+
}
|
|
538
|
+
for (const w of warnings) {
|
|
539
|
+
console.log(` ${warn(`${w.path}: ${w.message}`)}`);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return { errors: allErrors.length, warnings: warnings.length };
|
|
543
|
+
}
|
|
544
|
+
var EVIDENCE_ID_KEY = {
|
|
545
|
+
media: "id",
|
|
546
|
+
sensor: "id",
|
|
547
|
+
physical: "id",
|
|
548
|
+
testimony: "statementId"
|
|
549
|
+
};
|
|
550
|
+
function readId(item, key) {
|
|
551
|
+
if (item && typeof item === "object") {
|
|
552
|
+
const value = item[key];
|
|
553
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
554
|
+
}
|
|
555
|
+
return void 0;
|
|
556
|
+
}
|
|
557
|
+
function addIds(set, items, key) {
|
|
558
|
+
if (!Array.isArray(items)) return;
|
|
559
|
+
for (const item of items) {
|
|
560
|
+
const id = readId(item, key);
|
|
561
|
+
if (id) set.add(id);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
function collectEvidenceIds(obs) {
|
|
565
|
+
const ids = {
|
|
566
|
+
media: /* @__PURE__ */ new Set(),
|
|
567
|
+
sensor: /* @__PURE__ */ new Set(),
|
|
568
|
+
physical: /* @__PURE__ */ new Set(),
|
|
569
|
+
testimony: /* @__PURE__ */ new Set()
|
|
570
|
+
};
|
|
571
|
+
addIds(ids.media, obs["media"], "id");
|
|
572
|
+
const featured = readId(obs["featuredMedia"], "id");
|
|
573
|
+
if (featured) ids.media.add(featured);
|
|
574
|
+
const sensorEvidence = obs["sensorEvidence"];
|
|
575
|
+
if (sensorEvidence && typeof sensorEvidence === "object") {
|
|
576
|
+
addIds(ids.sensor, sensorEvidence["sensors"], "id");
|
|
577
|
+
}
|
|
578
|
+
addIds(ids.physical, obs["physicalEvidence"], "id");
|
|
579
|
+
addIds(ids.testimony, obs["testimony"], "statementId");
|
|
580
|
+
return ids;
|
|
581
|
+
}
|
|
582
|
+
function collectClaimRefs(obs) {
|
|
583
|
+
const out = [];
|
|
584
|
+
const pushClaim = (path, claim) => {
|
|
585
|
+
if (!claim || typeof claim !== "object") return;
|
|
586
|
+
const refs = claim["evidenceRefs"];
|
|
587
|
+
if (Array.isArray(refs)) {
|
|
588
|
+
out.push({ path, refs: refs.filter((r) => typeof r === "string") });
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
const assessments = obs["observableAssessments"];
|
|
592
|
+
if (assessments && typeof assessments === "object") {
|
|
593
|
+
for (const domain of ["technology", "biologics"]) {
|
|
594
|
+
const group = assessments[domain];
|
|
595
|
+
if (!group || typeof group !== "object") continue;
|
|
596
|
+
for (const [observableId, claims] of Object.entries(group)) {
|
|
597
|
+
if (Array.isArray(claims)) {
|
|
598
|
+
claims.forEach((c, i) => pushClaim(`observableAssessments.${domain}.${observableId}[${i}]`, c));
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
if (Array.isArray(obs["origin"])) {
|
|
604
|
+
obs["origin"].forEach((c, i) => pushClaim(`origin[${i}]`, c));
|
|
605
|
+
}
|
|
606
|
+
return out;
|
|
607
|
+
}
|
|
608
|
+
function validateEvidenceRefs(obs) {
|
|
609
|
+
const warnings = [];
|
|
610
|
+
const available = collectEvidenceIds(obs);
|
|
611
|
+
for (const { path, refs } of collectClaimRefs(obs)) {
|
|
612
|
+
refs.forEach((ref, i) => {
|
|
613
|
+
const refPath = `${path}.evidenceRefs[${i}]`;
|
|
614
|
+
const parsed = parseEvidenceRef(ref);
|
|
615
|
+
if (!parsed) {
|
|
616
|
+
warnings.push({ path: refPath, message: `malformed evidence ref "${ref}" (expected "<kind>:<id>")` });
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
const pool = available[parsed.kind];
|
|
620
|
+
const key = EVIDENCE_ID_KEY[parsed.kind] ?? "id";
|
|
621
|
+
if (!pool || !pool.has(parsed.id)) {
|
|
622
|
+
warnings.push({
|
|
623
|
+
path: refPath,
|
|
624
|
+
message: `dangling evidence ref "${ref}" \u2014 no ${parsed.kind} with ${key} "${parsed.id}" in record`
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
return warnings;
|
|
630
|
+
}
|
|
631
|
+
function validateStrict(obs) {
|
|
632
|
+
const warnings = [];
|
|
633
|
+
const recommended = ["summary", "description", "objectCharacteristics", "sourceData", "witnesses"];
|
|
634
|
+
for (const field of recommended) {
|
|
635
|
+
if (obs[field] == null) {
|
|
636
|
+
warnings.push({ path: field, message: `recommended field missing (strict mode)` });
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return warnings;
|
|
640
|
+
}
|
|
641
|
+
function validateSemantics(obs) {
|
|
642
|
+
const errors = [];
|
|
643
|
+
const numericField = (parent, key, path, { min }) => {
|
|
644
|
+
if (parent == null || typeof parent !== "object") return;
|
|
645
|
+
const value = parent[key];
|
|
646
|
+
if (typeof value === "number" && value < min) {
|
|
647
|
+
errors.push({ path, message: `${key} cannot be less than ${min}` });
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
numericField(obs["temporal"], "durationSeconds", "temporal.durationSeconds", { min: 0 });
|
|
651
|
+
numericField(obs["movement"], "speedKmh", "movement.speedKmh", { min: 0 });
|
|
652
|
+
numericField(obs["movement"], "altitudeMeters", "movement.altitudeMeters", { min: 0 });
|
|
653
|
+
numericField(obs["witnesses"], "count", "witnesses.count", { min: 0 });
|
|
654
|
+
numericField(obs["objectCharacteristics"], "numberObserved", "objectCharacteristics.numberObserved", { min: 0 });
|
|
655
|
+
return errors;
|
|
656
|
+
}
|
|
657
|
+
function printUsage2() {
|
|
658
|
+
console.log(heading(`${BRAND} validate`));
|
|
659
|
+
console.log(`
|
|
660
|
+
Validate observation JSON files against the full schema.
|
|
661
|
+
`);
|
|
662
|
+
console.log(`${dim("Usage:")}`);
|
|
663
|
+
console.log(` disclosureos validate <path...> [options]
|
|
664
|
+
`);
|
|
665
|
+
console.log(`${dim("Paths:")}`);
|
|
666
|
+
console.log(` Accepts one or more files, directories, or shell globs. Results are`);
|
|
667
|
+
console.log(` deduped, and any path matching no JSON fails the run (never silently skipped).
|
|
668
|
+
`);
|
|
669
|
+
console.log(`${dim("Options:")}`);
|
|
670
|
+
console.log(` --recursive, -r Validate all JSON files in directories recursively`);
|
|
671
|
+
console.log(` --strict, -s Warn on missing recommended fields`);
|
|
672
|
+
console.log(` --json, -j Output results as structured JSON (for CI and scripts)`);
|
|
673
|
+
console.log(` --help, -h Show this help message
|
|
674
|
+
`);
|
|
675
|
+
console.log(`${dim("Validation checks:")}`);
|
|
676
|
+
console.log(` \u2022 Required fields (id, temporal, location, status, createdAt, updatedAt)`);
|
|
677
|
+
console.log(` \u2022 Temporal type correctness (ISO date, certainty, granularity, range, relative)`);
|
|
678
|
+
console.log(` \u2022 Observable assessment levels and IDs (if present)`);
|
|
679
|
+
console.log(` \u2022 OCS node IDs in origin classifications (if present)`);
|
|
680
|
+
console.log(` \u2022 Unknown top-level keys rejected (third-party data belongs under "extensions")`);
|
|
681
|
+
console.log(` \u2022 Evidence refs on claims resolve to in-record evidence (warns on dangling)`);
|
|
682
|
+
console.log(` \u2022 Semantic sanity (non-negative duration, speed, counts)
|
|
683
|
+
`);
|
|
684
|
+
console.log(`${dim("Examples:")}`);
|
|
685
|
+
console.log(` disclosureos validate ./data/nimitz.json`);
|
|
686
|
+
console.log(` disclosureos validate ./data/ --recursive`);
|
|
687
|
+
console.log(` disclosureos validate ./data/*.json`);
|
|
688
|
+
console.log(` disclosureos validate ./data/*.json --json`);
|
|
689
|
+
console.log(` disclosureos validate ./a.json ./b.json --strict
|
|
690
|
+
`);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// src/commands/completeness.ts
|
|
694
|
+
import { parseEnrichedObservation as parseEnrichedObservation2 } from "@disclosureos/schema";
|
|
695
|
+
import { getCompleteness } from "@disclosureos/scoring";
|
|
696
|
+
function completeness(args2) {
|
|
697
|
+
if (args2.flags["help"]) {
|
|
698
|
+
printUsage3();
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
const targets = [args2.subcommand, ...args2.positional].filter((t) => !!t);
|
|
702
|
+
if (targets.length === 0) {
|
|
703
|
+
printUsage3();
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
const recursive = !!args2.flags["recursive"];
|
|
707
|
+
const jsonMode = !!args2.flags["json"];
|
|
708
|
+
const seen = /* @__PURE__ */ new Set();
|
|
709
|
+
const files = [];
|
|
710
|
+
const emptyTargets = [];
|
|
711
|
+
for (const target of targets) {
|
|
712
|
+
const found = findJSONFiles(target, recursive);
|
|
713
|
+
if (found.length === 0) {
|
|
714
|
+
emptyTargets.push(target);
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
717
|
+
for (const f of found) {
|
|
718
|
+
if (!seen.has(f)) {
|
|
719
|
+
seen.add(f);
|
|
720
|
+
files.push(f);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
if (jsonMode) {
|
|
725
|
+
runJsonMode2(files, emptyTargets);
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
for (const t of emptyTargets) {
|
|
729
|
+
console.log(error(`No JSON files found at: ${t}`));
|
|
730
|
+
}
|
|
731
|
+
if (files.length === 0) {
|
|
732
|
+
process.exit(1);
|
|
733
|
+
}
|
|
734
|
+
let hasErrors = false;
|
|
735
|
+
const percentages = [];
|
|
736
|
+
const reqPercentages = [];
|
|
737
|
+
for (const file of files) {
|
|
738
|
+
const result = scoreFile(file);
|
|
739
|
+
if (!result.valid) {
|
|
740
|
+
hasErrors = true;
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
percentages.push(result.percentage);
|
|
744
|
+
reqPercentages.push(result.requiredPercentage);
|
|
745
|
+
}
|
|
746
|
+
console.log("");
|
|
747
|
+
if (hasErrors || emptyTargets.length > 0) {
|
|
748
|
+
console.log(error("Some files could not be scored (see errors above)."));
|
|
749
|
+
process.exit(1);
|
|
750
|
+
}
|
|
751
|
+
const mean = avg(percentages);
|
|
752
|
+
const reqMean = avg(reqPercentages);
|
|
753
|
+
console.log(
|
|
754
|
+
success(
|
|
755
|
+
`${files.length} file(s) scored \u2014 mean completeness ${mean}%, required fields ${reqMean}%`
|
|
756
|
+
)
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
function avg(values) {
|
|
760
|
+
return values.length > 0 ? Math.round(values.reduce((a, b) => a + b, 0) / values.length) : 0;
|
|
761
|
+
}
|
|
762
|
+
function scoreFile(filePath) {
|
|
763
|
+
let data;
|
|
764
|
+
try {
|
|
765
|
+
data = readJSON(filePath);
|
|
766
|
+
} catch {
|
|
767
|
+
console.log(error(`Failed to parse: ${filePath}`));
|
|
768
|
+
return emptyResult(filePath, "Failed to parse JSON");
|
|
769
|
+
}
|
|
770
|
+
if (!data || typeof data !== "object") {
|
|
771
|
+
console.log(error(`${filePath}: not a valid JSON object`));
|
|
772
|
+
return emptyResult(filePath, "Not a valid JSON object");
|
|
773
|
+
}
|
|
774
|
+
const parsed = parseEnrichedObservation2(data);
|
|
775
|
+
if (!parsed.success) {
|
|
776
|
+
console.log(error(`${filePath}: invalid observation (${parsed.issues.length} issue${parsed.issues.length !== 1 ? "s" : ""})`));
|
|
777
|
+
for (const issue of parsed.issues.slice(0, 5)) {
|
|
778
|
+
console.log(` ${dim(`${issue.path}: ${issue.message}`)}`);
|
|
779
|
+
}
|
|
780
|
+
return emptyResult(filePath, "Invalid observation");
|
|
781
|
+
}
|
|
782
|
+
const comp = getCompleteness(data);
|
|
783
|
+
console.log(`
|
|
784
|
+
${dim(filePath)}`);
|
|
785
|
+
console.log(` Completeness: ${comp.percentage}% (${comp.present}/${comp.total} fields)`);
|
|
786
|
+
console.log(` Required: ${comp.requiredPercentage}% (${comp.requiredPresent}/${comp.requiredTotal})`);
|
|
787
|
+
if (comp.missing.length > 0) {
|
|
788
|
+
const shown = comp.missing.slice(0, 10);
|
|
789
|
+
console.log(` Missing (top ${shown.length}):`);
|
|
790
|
+
for (const path of shown) {
|
|
791
|
+
console.log(` ${dim("\u2022")} ${path}`);
|
|
792
|
+
}
|
|
793
|
+
if (comp.missing.length > shown.length) {
|
|
794
|
+
console.log(` ${dim(`\u2026 and ${comp.missing.length - shown.length} more`)}`);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
return {
|
|
798
|
+
file: filePath,
|
|
799
|
+
valid: true,
|
|
800
|
+
percentage: comp.percentage,
|
|
801
|
+
requiredPercentage: comp.requiredPercentage,
|
|
802
|
+
present: comp.present,
|
|
803
|
+
total: comp.total,
|
|
804
|
+
missing: comp.missing
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
function emptyResult(file, errorMsg) {
|
|
808
|
+
return {
|
|
809
|
+
file,
|
|
810
|
+
valid: false,
|
|
811
|
+
percentage: 0,
|
|
812
|
+
requiredPercentage: 0,
|
|
813
|
+
present: 0,
|
|
814
|
+
total: 0,
|
|
815
|
+
missing: [],
|
|
816
|
+
error: errorMsg
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
function runJsonMode2(files, emptyTargets) {
|
|
820
|
+
const fileResults = [];
|
|
821
|
+
for (const file of files) {
|
|
822
|
+
fileResults.push(scoreFileQuiet(file));
|
|
823
|
+
}
|
|
824
|
+
const validResults = fileResults.filter((r) => r.valid);
|
|
825
|
+
const meanPercentage = avg(validResults.map((r) => r.percentage));
|
|
826
|
+
const meanRequired = avg(validResults.map((r) => r.requiredPercentage));
|
|
827
|
+
const allValid = fileResults.every((r) => r.valid) && emptyTargets.length === 0;
|
|
828
|
+
const output = {
|
|
829
|
+
files: fileResults,
|
|
830
|
+
emptyTargets,
|
|
831
|
+
totals: {
|
|
832
|
+
files: files.length,
|
|
833
|
+
meanPercentage,
|
|
834
|
+
meanRequiredPercentage: meanRequired,
|
|
835
|
+
emptyTargets: emptyTargets.length
|
|
836
|
+
},
|
|
837
|
+
valid: allValid
|
|
838
|
+
};
|
|
839
|
+
console.log(JSON.stringify(output, null, 2));
|
|
840
|
+
if (!allValid) {
|
|
841
|
+
process.exit(1);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
function scoreFileQuiet(filePath) {
|
|
845
|
+
let data;
|
|
846
|
+
try {
|
|
847
|
+
data = readJSON(filePath);
|
|
848
|
+
} catch {
|
|
849
|
+
return emptyResult(filePath, "Failed to parse JSON");
|
|
850
|
+
}
|
|
851
|
+
if (!data || typeof data !== "object") {
|
|
852
|
+
return emptyResult(filePath, "Not a valid JSON object");
|
|
853
|
+
}
|
|
854
|
+
const parsed = parseEnrichedObservation2(data);
|
|
855
|
+
if (!parsed.success) {
|
|
856
|
+
return emptyResult(filePath, `Invalid observation (${parsed.issues.length} issue${parsed.issues.length !== 1 ? "s" : ""})`);
|
|
857
|
+
}
|
|
858
|
+
const comp = getCompleteness(data);
|
|
859
|
+
return {
|
|
860
|
+
file: filePath,
|
|
861
|
+
valid: true,
|
|
862
|
+
percentage: comp.percentage,
|
|
863
|
+
requiredPercentage: comp.requiredPercentage,
|
|
864
|
+
present: comp.present,
|
|
865
|
+
total: comp.total,
|
|
866
|
+
missing: comp.missing
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
function printUsage3() {
|
|
870
|
+
console.log(heading(`${BRAND} completeness`));
|
|
871
|
+
console.log(`
|
|
872
|
+
Measure how fully observation JSON files populate the records schema.
|
|
873
|
+
`);
|
|
874
|
+
console.log(`${dim("Usage:")}`);
|
|
875
|
+
console.log(` disclosureos completeness <path...> [options]
|
|
876
|
+
`);
|
|
877
|
+
console.log(`${dim("Paths:")}`);
|
|
878
|
+
console.log(` Accepts one or more files, directories, or shell globs.
|
|
879
|
+
`);
|
|
880
|
+
console.log(`${dim("Options:")}`);
|
|
881
|
+
console.log(` --recursive, -r Score all JSON files in directories recursively`);
|
|
882
|
+
console.log(` --json, -j Output results as structured JSON (for CI and scripts)`);
|
|
883
|
+
console.log(` --help, -h Show this help message
|
|
884
|
+
`);
|
|
885
|
+
console.log(`${dim("Scoring:")}`);
|
|
886
|
+
console.log(` Files must be structurally valid observations before completeness is measured.`);
|
|
887
|
+
console.log(` Invalid JSON or invalid observations exit non-zero without a score.
|
|
888
|
+
`);
|
|
889
|
+
console.log(`${dim("Output:")}`);
|
|
890
|
+
console.log(` \u2022 Completeness percentage (0\u2013100) \u2014 how many schema fields are populated`);
|
|
891
|
+
console.log(` \u2022 Required field percentage \u2014 coverage of required fields only`);
|
|
892
|
+
console.log(` \u2022 Top missing field paths \u2014 the next fields to target
|
|
893
|
+
`);
|
|
894
|
+
console.log(`${dim("Examples:")}`);
|
|
895
|
+
console.log(` disclosureos completeness ./data/nimitz.json`);
|
|
896
|
+
console.log(` disclosureos completeness ./data/ --recursive`);
|
|
897
|
+
console.log(` disclosureos completeness ./out/*.json --json
|
|
898
|
+
`);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// src/commands/registry.ts
|
|
902
|
+
import { deriveFieldPaths } from "@disclosureos/scoring";
|
|
903
|
+
import {
|
|
904
|
+
ALL_OBSERVABLES,
|
|
905
|
+
TECHNOLOGY_OBSERVABLES,
|
|
906
|
+
BIOLOGICS_OBSERVABLES,
|
|
907
|
+
ASSESSMENT_LEVELS
|
|
908
|
+
} from "@disclosureos/observables";
|
|
909
|
+
import {
|
|
910
|
+
OCS_TAXONOMY,
|
|
911
|
+
getNode,
|
|
912
|
+
getChildren,
|
|
913
|
+
getTestableNodes,
|
|
914
|
+
ORIGIN_DOMAINS,
|
|
915
|
+
ORIGIN_DOMAIN_LABELS
|
|
916
|
+
} from "@disclosureos/origins";
|
|
917
|
+
function registry(args2) {
|
|
918
|
+
if (args2.flags["help"]) {
|
|
919
|
+
printUsage4();
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
const sub = args2.subcommand;
|
|
923
|
+
switch (sub) {
|
|
924
|
+
case "fields":
|
|
925
|
+
registryFields(args2);
|
|
926
|
+
break;
|
|
927
|
+
case "observables":
|
|
928
|
+
registryObservables(args2);
|
|
929
|
+
break;
|
|
930
|
+
case "origins":
|
|
931
|
+
registryOrigins(args2);
|
|
932
|
+
break;
|
|
933
|
+
default:
|
|
934
|
+
printUsage4();
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
function registryFields(args2) {
|
|
938
|
+
const group = args2.flags["category"];
|
|
939
|
+
const paths = deriveFieldPaths();
|
|
940
|
+
const groups = /* @__PURE__ */ new Map();
|
|
941
|
+
for (const field of paths) {
|
|
942
|
+
const top = field.path.split(".")[0] ?? field.path;
|
|
943
|
+
const list = groups.get(top) ?? [];
|
|
944
|
+
list.push(field);
|
|
945
|
+
groups.set(top, list);
|
|
946
|
+
}
|
|
947
|
+
console.log(heading("Observation Field Registry (schema-derived)"));
|
|
948
|
+
if (group) {
|
|
949
|
+
const fields = groups.get(group);
|
|
950
|
+
if (!fields || fields.length === 0) {
|
|
951
|
+
console.log(`
|
|
952
|
+
No fields found for group "${group}"`);
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
console.log(`
|
|
956
|
+
Group: ${group}
|
|
957
|
+
`);
|
|
958
|
+
const rows = fields.map((f) => [f.path, f.required ? "required" : "optional"]);
|
|
959
|
+
console.log(table(rows));
|
|
960
|
+
} else {
|
|
961
|
+
for (const groupName of [...groups.keys()].sort()) {
|
|
962
|
+
const fields = groups.get(groupName);
|
|
963
|
+
console.log(`
|
|
964
|
+
${groupName} (${fields.length} fields)`);
|
|
965
|
+
for (const f of fields) {
|
|
966
|
+
console.log(bullet(`${f.path}${f.required ? " [required]" : ""}`));
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
console.log(`
|
|
971
|
+
${dim(`Total: ${paths.length} schema-derived field paths`)}
|
|
972
|
+
`);
|
|
973
|
+
}
|
|
974
|
+
function registryObservables(args2) {
|
|
975
|
+
const biologicsOnly = !!args2.flags["biologics"];
|
|
976
|
+
const techOnly = !!args2.flags["technology"];
|
|
977
|
+
console.log(heading("Observable Registry"));
|
|
978
|
+
if (!biologicsOnly) {
|
|
979
|
+
console.log(`
|
|
980
|
+
Technology Observables (AATIP-derived)
|
|
981
|
+
`);
|
|
982
|
+
const techObs = Object.values(TECHNOLOGY_OBSERVABLES);
|
|
983
|
+
for (const obs of techObs) {
|
|
984
|
+
console.log(` ${obs.code} ${obs.label}`);
|
|
985
|
+
if (obs.description) {
|
|
986
|
+
console.log(` ${dim(obs.description)}`);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
if (!techOnly) {
|
|
991
|
+
console.log(`
|
|
992
|
+
Biologics Observables (NHI Detection)
|
|
993
|
+
`);
|
|
994
|
+
const bioObs = Object.values(BIOLOGICS_OBSERVABLES);
|
|
995
|
+
for (const obs of bioObs) {
|
|
996
|
+
console.log(` ${obs.code} ${obs.label}`);
|
|
997
|
+
if (obs.description) {
|
|
998
|
+
console.log(` ${dim(obs.description)}`);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
console.log(`
|
|
1003
|
+
${dim(`Assessment Levels: ${ASSESSMENT_LEVELS.join(" \u2192 ")}`)}`);
|
|
1004
|
+
console.log(` ${dim(`Total: ${ALL_OBSERVABLES.length} observables`)}
|
|
1005
|
+
`);
|
|
1006
|
+
}
|
|
1007
|
+
function registryOrigins(args2) {
|
|
1008
|
+
const targetId = args2.flags["id"];
|
|
1009
|
+
const testableOnly = !!args2.flags["testable"];
|
|
1010
|
+
console.log(heading("Origin Classification System (OCS)"));
|
|
1011
|
+
if (testableOnly) {
|
|
1012
|
+
const testable = getTestableNodes();
|
|
1013
|
+
console.log(`
|
|
1014
|
+
Scientifically Testable Hypotheses (${testable.length}):
|
|
1015
|
+
`);
|
|
1016
|
+
for (const node of testable) {
|
|
1017
|
+
console.log(` [${node.id}] ${node.label}`);
|
|
1018
|
+
}
|
|
1019
|
+
console.log("");
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
if (targetId) {
|
|
1023
|
+
const node = getNode(targetId);
|
|
1024
|
+
if (!node) {
|
|
1025
|
+
console.log(`
|
|
1026
|
+
Unknown OCS node: "${targetId}"
|
|
1027
|
+
`);
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
console.log(`
|
|
1031
|
+
${label("ID", node.id)}`);
|
|
1032
|
+
console.log(label("Label", node.label));
|
|
1033
|
+
console.log(label("Domain", node.domain));
|
|
1034
|
+
console.log(label("Depth", String(node.depth)));
|
|
1035
|
+
console.log(label("Testable", node.scientificallyTestable ? "Yes" : "No"));
|
|
1036
|
+
if (node.description) {
|
|
1037
|
+
console.log(label("Description", node.description));
|
|
1038
|
+
}
|
|
1039
|
+
if (node.aliases && node.aliases.length > 0) {
|
|
1040
|
+
console.log(label("Aliases", node.aliases.join(", ")));
|
|
1041
|
+
}
|
|
1042
|
+
const children = getChildren(targetId);
|
|
1043
|
+
if (children.length > 0) {
|
|
1044
|
+
console.log(`
|
|
1045
|
+
Children (${children.length}):`);
|
|
1046
|
+
for (const child of children) {
|
|
1047
|
+
console.log(bullet(`[${child.id}] ${child.label}`));
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
console.log("");
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
console.log(`
|
|
1054
|
+
3 Domains, ${OCS_TAXONOMY.length} total nodes
|
|
1055
|
+
`);
|
|
1056
|
+
for (const domain of ORIGIN_DOMAINS) {
|
|
1057
|
+
const domainLabel = ORIGIN_DOMAIN_LABELS[domain];
|
|
1058
|
+
const domainNode = OCS_TAXONOMY.find((n) => n.depth === 0 && n.domain === domain);
|
|
1059
|
+
if (domainNode) {
|
|
1060
|
+
console.log(` [${domainNode.id}] ${domainLabel}`);
|
|
1061
|
+
const subdomains = getChildren(domainNode.id);
|
|
1062
|
+
for (const sub of subdomains) {
|
|
1063
|
+
console.log(` [${sub.id}] ${sub.label}`);
|
|
1064
|
+
const leaves = getChildren(sub.id);
|
|
1065
|
+
for (const leaf of leaves) {
|
|
1066
|
+
console.log(` ${dim(`[${leaf.id}] ${leaf.label}`)}`);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
console.log("");
|
|
1072
|
+
}
|
|
1073
|
+
function printUsage4() {
|
|
1074
|
+
console.log(heading(`${BRAND} registry`));
|
|
1075
|
+
console.log(`
|
|
1076
|
+
Introspect DisclosureOS field, observable, and origin registries.
|
|
1077
|
+
`);
|
|
1078
|
+
console.log(`${dim("Usage:")}`);
|
|
1079
|
+
console.log(` disclosureos registry <subcommand> [options]
|
|
1080
|
+
`);
|
|
1081
|
+
console.log(`${dim("Subcommands:")}`);
|
|
1082
|
+
console.log(` fields List observation field definitions`);
|
|
1083
|
+
console.log(` fields --category <cat> Filter by category`);
|
|
1084
|
+
console.log(` observables List all 12 observables`);
|
|
1085
|
+
console.log(` observables --biologics Biologics only`);
|
|
1086
|
+
console.log(` observables --technology Technology only`);
|
|
1087
|
+
console.log(` origins Show OCS taxonomy overview`);
|
|
1088
|
+
console.log(` origins --id <nodeId> Show subtree for specific node`);
|
|
1089
|
+
console.log(` origins --testable Only scientifically testable nodes
|
|
1090
|
+
`);
|
|
1091
|
+
console.log(`${dim("Options:")}`);
|
|
1092
|
+
console.log(` --help, -h Show this help message
|
|
1093
|
+
`);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// src/commands/info.ts
|
|
1097
|
+
import { deriveFieldPaths as deriveFieldPaths2 } from "@disclosureos/scoring";
|
|
1098
|
+
import {
|
|
1099
|
+
ALL_OBSERVABLES as ALL_OBSERVABLES2,
|
|
1100
|
+
ALL_OBSERVABLE_IDS,
|
|
1101
|
+
ASSESSMENT_LEVELS as ASSESSMENT_LEVELS2
|
|
1102
|
+
} from "@disclosureos/observables";
|
|
1103
|
+
import {
|
|
1104
|
+
OCS_TAXONOMY as OCS_TAXONOMY2,
|
|
1105
|
+
getNode as getNode2,
|
|
1106
|
+
getChildren as getChildren2,
|
|
1107
|
+
getAncestors,
|
|
1108
|
+
ORIGIN_DOMAINS as ORIGIN_DOMAINS2
|
|
1109
|
+
} from "@disclosureos/origins";
|
|
1110
|
+
var FIELD_PATH_COUNT = deriveFieldPaths2().length;
|
|
1111
|
+
function info(args2) {
|
|
1112
|
+
if (args2.flags["help"]) {
|
|
1113
|
+
printUsage5();
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
const sub = args2.subcommand;
|
|
1117
|
+
switch (sub) {
|
|
1118
|
+
case "":
|
|
1119
|
+
case void 0:
|
|
1120
|
+
infoOverview();
|
|
1121
|
+
break;
|
|
1122
|
+
case "observation":
|
|
1123
|
+
infoObservation();
|
|
1124
|
+
break;
|
|
1125
|
+
case "observable":
|
|
1126
|
+
infoObservable(args2.positional[0]);
|
|
1127
|
+
break;
|
|
1128
|
+
case "origin":
|
|
1129
|
+
infoOrigin(args2.positional[0]);
|
|
1130
|
+
break;
|
|
1131
|
+
default:
|
|
1132
|
+
infoOverview();
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
function infoOverview() {
|
|
1136
|
+
console.log(heading(`${BRAND} Ecosystem`));
|
|
1137
|
+
console.log(`
|
|
1138
|
+
A structured data framework for UAP research.
|
|
1139
|
+
`);
|
|
1140
|
+
console.log(table([
|
|
1141
|
+
["@disclosureos/records", `Data dictionary \u2014 ${FIELD_PATH_COUNT} schema fields, Observation type`],
|
|
1142
|
+
["@disclosureos/observables", `Detection criteria \u2014 ${ALL_OBSERVABLE_IDS.length} observables`],
|
|
1143
|
+
["@disclosureos/origins", `Classification taxonomy \u2014 ${OCS_TAXONOMY2.length} OCS nodes`],
|
|
1144
|
+
["@disclosureos/scoring", "Reference completeness + compellingness scoring"],
|
|
1145
|
+
["@disclosureos/cli", "Developer tooling (this package)"]
|
|
1146
|
+
]));
|
|
1147
|
+
console.log(`
|
|
1148
|
+
${dim(" Commands: scaffold, validate, registry, info")}
|
|
1149
|
+
`);
|
|
1150
|
+
}
|
|
1151
|
+
function infoObservation() {
|
|
1152
|
+
console.log(heading("Observation Type"));
|
|
1153
|
+
console.log(`
|
|
1154
|
+
The core record type for UAP/UAI research data.
|
|
1155
|
+
`);
|
|
1156
|
+
console.log(` ${dim("Required Fields:")}`);
|
|
1157
|
+
console.log(bullet("id \u2014 Unique identifier"));
|
|
1158
|
+
console.log(bullet("temporal \u2014 Date, time, duration"));
|
|
1159
|
+
console.log(bullet("location \u2014 Coordinates, site type"));
|
|
1160
|
+
console.log(bullet("status \u2014 Publication status (draft/review/published/archived/retracted)"));
|
|
1161
|
+
console.log(bullet("createdAt \u2014 ISO timestamp"));
|
|
1162
|
+
console.log(bullet("updatedAt \u2014 ISO timestamp"));
|
|
1163
|
+
console.log(`
|
|
1164
|
+
${dim("Extension Slots:")}`);
|
|
1165
|
+
console.log(bullet("observableAssessments \u2014 Technology + Biologics assessment levels"));
|
|
1166
|
+
console.log(bullet("origin \u2014 OCS hypothesis assignment"));
|
|
1167
|
+
console.log(`
|
|
1168
|
+
${dim("Records-owned slots: provenance, identifiers, testimony, physicalEvidence, documents")}`);
|
|
1169
|
+
console.log(` ${dim(`Total schema-derived field paths: ${FIELD_PATH_COUNT}`)}
|
|
1170
|
+
`);
|
|
1171
|
+
}
|
|
1172
|
+
function infoObservable(id) {
|
|
1173
|
+
if (!id) {
|
|
1174
|
+
console.log(heading("Observables"));
|
|
1175
|
+
console.log(`
|
|
1176
|
+
Use: disclosureos info observable <code>
|
|
1177
|
+
`);
|
|
1178
|
+
console.log(` ${dim("Available codes:")}`);
|
|
1179
|
+
for (const obs2 of ALL_OBSERVABLES2) {
|
|
1180
|
+
console.log(bullet(`${obs2.code} \u2014 ${obs2.label}`));
|
|
1181
|
+
}
|
|
1182
|
+
console.log("");
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
const obs = ALL_OBSERVABLES2.find((o) => o.code === id || o.id === id);
|
|
1186
|
+
if (!obs) {
|
|
1187
|
+
console.log(`
|
|
1188
|
+
Unknown observable: "${id}"`);
|
|
1189
|
+
console.log(` ${dim(`Available: ${ALL_OBSERVABLES2.map((o) => o.code).join(", ")}`)}
|
|
1190
|
+
`);
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
console.log(heading(`Observable: ${obs.label}`));
|
|
1194
|
+
console.log(`
|
|
1195
|
+
${label("Code", obs.code)}`);
|
|
1196
|
+
console.log(label("ID", obs.id));
|
|
1197
|
+
console.log(label("Category", obs.category));
|
|
1198
|
+
if (obs.description) {
|
|
1199
|
+
console.log(label("Description", obs.description));
|
|
1200
|
+
}
|
|
1201
|
+
console.log(`
|
|
1202
|
+
${dim("Assessment Levels:")}`);
|
|
1203
|
+
for (const level of ASSESSMENT_LEVELS2) {
|
|
1204
|
+
console.log(bullet(level));
|
|
1205
|
+
}
|
|
1206
|
+
console.log("");
|
|
1207
|
+
}
|
|
1208
|
+
function infoOrigin(id) {
|
|
1209
|
+
if (!id) {
|
|
1210
|
+
console.log(heading("Origin Classification System"));
|
|
1211
|
+
console.log(`
|
|
1212
|
+
Use: disclosureos info origin <nodeId>
|
|
1213
|
+
`);
|
|
1214
|
+
console.log(` ${dim("Domains:")}`);
|
|
1215
|
+
for (const domain of ORIGIN_DOMAINS2) {
|
|
1216
|
+
const domainNode = OCS_TAXONOMY2.find((n) => n.depth === 0 && n.domain === domain);
|
|
1217
|
+
if (domainNode) {
|
|
1218
|
+
console.log(bullet(`[${domainNode.id}] \u2014 ${domainNode.label}`));
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
console.log("");
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
const node = getNode2(id);
|
|
1225
|
+
if (!node) {
|
|
1226
|
+
console.log(`
|
|
1227
|
+
Unknown OCS node: "${id}"
|
|
1228
|
+
`);
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
console.log(heading(`Origin: ${node.label}`));
|
|
1232
|
+
console.log(`
|
|
1233
|
+
${label("ID", node.id)}`);
|
|
1234
|
+
console.log(label("Label", node.label));
|
|
1235
|
+
console.log(label("Domain", node.domain));
|
|
1236
|
+
console.log(label("Depth", String(node.depth)));
|
|
1237
|
+
console.log(label("Testable", node.scientificallyTestable ? "Yes" : "No"));
|
|
1238
|
+
if (node.description) {
|
|
1239
|
+
console.log(label("Description", node.description));
|
|
1240
|
+
}
|
|
1241
|
+
if (node.aliases && node.aliases.length > 0) {
|
|
1242
|
+
console.log(label("Aliases", node.aliases.join(", ")));
|
|
1243
|
+
}
|
|
1244
|
+
const ancestors = getAncestors(id);
|
|
1245
|
+
if (ancestors.length > 0) {
|
|
1246
|
+
console.log(`
|
|
1247
|
+
${dim("Path:")}`);
|
|
1248
|
+
console.log(` ${ancestors.map((a) => a.label).join(" \u2192 ")} \u2192 ${node.label}`);
|
|
1249
|
+
}
|
|
1250
|
+
const children = getChildren2(id);
|
|
1251
|
+
if (children.length > 0) {
|
|
1252
|
+
console.log(`
|
|
1253
|
+
${dim(`Children (${children.length}):`)}`);
|
|
1254
|
+
for (const child of children) {
|
|
1255
|
+
console.log(bullet(`[${child.id}] ${child.label}`));
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
console.log("");
|
|
1259
|
+
}
|
|
1260
|
+
function printUsage5() {
|
|
1261
|
+
console.log(heading(`${BRAND} info`));
|
|
1262
|
+
console.log(`
|
|
1263
|
+
Quick reference for DisclosureOS types and definitions.
|
|
1264
|
+
`);
|
|
1265
|
+
console.log(`${dim("Usage:")}`);
|
|
1266
|
+
console.log(` disclosureos info [topic] [id]
|
|
1267
|
+
`);
|
|
1268
|
+
console.log(`${dim("Topics:")}`);
|
|
1269
|
+
console.log(` (none) Ecosystem overview`);
|
|
1270
|
+
console.log(` observation Observation type summary`);
|
|
1271
|
+
console.log(` observable <code> Observable detail (e.g. TO-3, BO-1)`);
|
|
1272
|
+
console.log(` origin <nodeId> OCS node detail (e.g. 1.1.3)
|
|
1273
|
+
`);
|
|
1274
|
+
console.log(`${dim("Options:")}`);
|
|
1275
|
+
console.log(` --help, -h Show this help message
|
|
1276
|
+
`);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
// src/index.ts
|
|
1280
|
+
var CLI_VERSION = readCliVersion();
|
|
1281
|
+
var args = parseArgs(process.argv);
|
|
1282
|
+
if (args.flags["version"]) {
|
|
1283
|
+
printVersion();
|
|
1284
|
+
} else if (args.flags["help"] && !args.command) {
|
|
1285
|
+
printHelp();
|
|
1286
|
+
} else {
|
|
1287
|
+
switch (args.command) {
|
|
1288
|
+
case "scaffold":
|
|
1289
|
+
scaffold(args);
|
|
1290
|
+
break;
|
|
1291
|
+
case "validate":
|
|
1292
|
+
validate(args);
|
|
1293
|
+
break;
|
|
1294
|
+
case "completeness":
|
|
1295
|
+
completeness(args);
|
|
1296
|
+
break;
|
|
1297
|
+
case "registry":
|
|
1298
|
+
registry(args);
|
|
1299
|
+
break;
|
|
1300
|
+
case "info":
|
|
1301
|
+
info(args);
|
|
1302
|
+
break;
|
|
1303
|
+
case "help":
|
|
1304
|
+
case "":
|
|
1305
|
+
printHelp();
|
|
1306
|
+
break;
|
|
1307
|
+
case "version":
|
|
1308
|
+
printVersion();
|
|
1309
|
+
break;
|
|
1310
|
+
default:
|
|
1311
|
+
console.log(`Unknown command: "${args.command}"
|
|
1312
|
+
`);
|
|
1313
|
+
printHelp();
|
|
1314
|
+
process.exit(1);
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
function printHelp() {
|
|
1318
|
+
console.log(heading(`${BRAND} CLI v${CLI_VERSION}`));
|
|
1319
|
+
console.log(`
|
|
1320
|
+
Developer tools for the DisclosureOS ecosystem.
|
|
1321
|
+
`);
|
|
1322
|
+
console.log(`${dim("Commands:")}`);
|
|
1323
|
+
console.log(` scaffold Generate typed data structure templates`);
|
|
1324
|
+
console.log(` validate Validate observation JSON files`);
|
|
1325
|
+
console.log(` completeness Measure how fully observations populate the schema`);
|
|
1326
|
+
console.log(` registry Introspect field, observable, and origin registries`);
|
|
1327
|
+
console.log(` info Quick reference for types and definitions`);
|
|
1328
|
+
console.log(` help Show this help message`);
|
|
1329
|
+
console.log(` version Print version
|
|
1330
|
+
`);
|
|
1331
|
+
console.log(`${dim("Global Options:")}`);
|
|
1332
|
+
console.log(` --help, -h Show help (for any command)`);
|
|
1333
|
+
console.log(` --version, -v Print version
|
|
1334
|
+
`);
|
|
1335
|
+
console.log(`${dim("Examples:")}`);
|
|
1336
|
+
console.log(` disclosureos scaffold observation --full`);
|
|
1337
|
+
console.log(` disclosureos validate ./data/ --recursive`);
|
|
1338
|
+
console.log(` disclosureos completeness ./out/ --recursive`);
|
|
1339
|
+
console.log(` disclosureos registry origins --id 1.1.3`);
|
|
1340
|
+
console.log(` disclosureos info observable TO-3
|
|
1341
|
+
`);
|
|
1342
|
+
}
|
|
1343
|
+
function printVersion() {
|
|
1344
|
+
console.log(CLI_VERSION);
|
|
1345
|
+
}
|
|
1346
|
+
function readCliVersion() {
|
|
1347
|
+
try {
|
|
1348
|
+
const packagePath = join(
|
|
1349
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
1350
|
+
"..",
|
|
1351
|
+
"package.json"
|
|
1352
|
+
);
|
|
1353
|
+
const parsed = JSON.parse(readFileSync2(packagePath, "utf8"));
|
|
1354
|
+
return typeof parsed.version === "string" ? parsed.version : "1.0.0";
|
|
1355
|
+
} catch {
|
|
1356
|
+
return "1.0.0";
|
|
1357
|
+
}
|
|
1358
|
+
}
|