airgen-cli 0.16.0 → 0.17.1
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/dist/commands/lint.js +63 -19
- package/dist/uht-client.d.ts +12 -0
- package/dist/uht-client.js +31 -0
- package/package.json +1 -1
package/dist/commands/lint.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
2
|
-
import { UhtClient } from "../uht-client.js";
|
|
2
|
+
import { UhtClient, decodeHexTraits } from "../uht-client.js";
|
|
3
3
|
import { isJsonMode } from "../output.js";
|
|
4
4
|
const TRAIT_CHECKS = [
|
|
5
5
|
// Physical Layer (1-8)
|
|
@@ -693,6 +693,7 @@ export function registerLintCommands(program, client) {
|
|
|
693
693
|
.option("--threshold <n>", "Jaccard similarity threshold (0.0-1.0)", "0.6")
|
|
694
694
|
.option("--spray-threshold <n>", "Outgoing trace link count for spray pattern detection", "8")
|
|
695
695
|
.option("--min-severity <level>", "Only show findings at this severity or above: low, medium, high", "low")
|
|
696
|
+
.option("--substrate-namespace <ns>", "Use existing Substrate entity classifications instead of re-classifying (e.g. SE, SE:naval)")
|
|
696
697
|
.action(async (tenant, project, opts) => {
|
|
697
698
|
const uht = new UhtClient();
|
|
698
699
|
if (!uht.isConfigured) {
|
|
@@ -721,26 +722,69 @@ export function registerLintCommands(program, client) {
|
|
|
721
722
|
const conceptRefs = extractConcepts(requirements);
|
|
722
723
|
const top = topConcepts(conceptRefs, maxConcepts);
|
|
723
724
|
console.error(` ${conceptRefs.size} unique concepts found, classifying top ${top.length}.`);
|
|
724
|
-
// Step 3: Classify concepts
|
|
725
|
-
|
|
726
|
-
|
|
725
|
+
// Step 3: Classify concepts — use Substrate namespace if provided, else fresh classify
|
|
726
|
+
const ns = opts.substrateNamespace;
|
|
727
|
+
console.error(ns
|
|
728
|
+
? `Looking up concepts in Substrate namespace "${ns}"...`
|
|
729
|
+
: "Classifying concepts via UHT...");
|
|
727
730
|
const concepts = [];
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
const
|
|
731
|
-
|
|
732
|
-
const
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
731
|
+
if (ns) {
|
|
732
|
+
// Namespace mode: look up pre-classified entities
|
|
733
|
+
const lookupResults = await parallelMap(top, async ([name]) => uht.lookupEntity(name, ns), 5);
|
|
734
|
+
for (let i = 0; i < lookupResults.length; i++) {
|
|
735
|
+
const [name, refs] = top[i];
|
|
736
|
+
const entity = lookupResults[i].result;
|
|
737
|
+
if (entity) {
|
|
738
|
+
const traitNames = decodeHexTraits(entity.hex_code);
|
|
739
|
+
concepts.push({
|
|
740
|
+
name: entity.name, // Use canonical name from Substrate
|
|
741
|
+
hexCode: entity.hex_code,
|
|
742
|
+
traits: traitNames,
|
|
743
|
+
traitSet: new Set(traitNames),
|
|
744
|
+
reqs: refs,
|
|
745
|
+
});
|
|
746
|
+
console.error(` ✓ ${name} → ${entity.name} (${entity.hex_code}, ${traitNames.length} traits)`);
|
|
747
|
+
}
|
|
748
|
+
else {
|
|
749
|
+
// Fall back to fresh classification
|
|
750
|
+
try {
|
|
751
|
+
const result = await uht.classify(name);
|
|
752
|
+
const traitNames = result.traits.map(t => t.name).filter(Boolean);
|
|
753
|
+
concepts.push({
|
|
754
|
+
name,
|
|
755
|
+
hexCode: result.hex_code,
|
|
756
|
+
traits: traitNames,
|
|
757
|
+
traitSet: new Set(traitNames),
|
|
758
|
+
reqs: refs,
|
|
759
|
+
});
|
|
760
|
+
console.error(` ~ ${name} → ${result.hex_code} (${traitNames.length} traits, not in namespace — classified fresh)`);
|
|
761
|
+
}
|
|
762
|
+
catch (err) {
|
|
763
|
+
console.error(` ✗ ${name}: ${err.message}`);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
741
766
|
}
|
|
742
|
-
|
|
743
|
-
|
|
767
|
+
}
|
|
768
|
+
else {
|
|
769
|
+
// Fresh classification mode (original behavior)
|
|
770
|
+
const classifyResults = await parallelMap(top, async ([name]) => uht.classify(name), 5);
|
|
771
|
+
for (let i = 0; i < classifyResults.length; i++) {
|
|
772
|
+
const entry = classifyResults[i];
|
|
773
|
+
const [name, refs] = top[i];
|
|
774
|
+
if (entry.result) {
|
|
775
|
+
const traitNames = entry.result.traits.map(t => t.name).filter(Boolean);
|
|
776
|
+
concepts.push({
|
|
777
|
+
name,
|
|
778
|
+
hexCode: entry.result.hex_code,
|
|
779
|
+
traits: traitNames,
|
|
780
|
+
traitSet: new Set(traitNames),
|
|
781
|
+
reqs: refs,
|
|
782
|
+
});
|
|
783
|
+
console.error(` ✓ ${name} → ${entry.result.hex_code} (${traitNames.length} traits)`);
|
|
784
|
+
}
|
|
785
|
+
else if (entry.error) {
|
|
786
|
+
console.error(` ✗ ${name}: ${entry.error.message}`);
|
|
787
|
+
}
|
|
744
788
|
}
|
|
745
789
|
}
|
|
746
790
|
// Step 4: Cross-compare concepts in batches
|
package/dist/uht-client.d.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* Talks to the UHT Substrate factory API for entity classification and comparison.
|
|
5
5
|
* Token resolution: UHT_TOKEN env → UHT_API_KEY env → ~/.config/uht-substrate/config.json
|
|
6
6
|
*/
|
|
7
|
+
/** Decode an 8-char hex code into trait names by reading the 32-bit field. */
|
|
8
|
+
export declare function decodeHexTraits(hexCode: string): string[];
|
|
7
9
|
export interface UhtClassification {
|
|
8
10
|
entity: string;
|
|
9
11
|
hex_code: string;
|
|
@@ -12,6 +14,13 @@ export interface UhtClassification {
|
|
|
12
14
|
justification: string;
|
|
13
15
|
}>;
|
|
14
16
|
}
|
|
17
|
+
export interface UhtEntityRecord {
|
|
18
|
+
uuid: string;
|
|
19
|
+
name: string;
|
|
20
|
+
hex_code: string;
|
|
21
|
+
description: string | null;
|
|
22
|
+
source: string;
|
|
23
|
+
}
|
|
15
24
|
export interface UhtComparison {
|
|
16
25
|
candidate: string;
|
|
17
26
|
hex_code: string;
|
|
@@ -42,4 +51,7 @@ export declare class UhtClient {
|
|
|
42
51
|
private request;
|
|
43
52
|
classify(entity: string): Promise<UhtClassification>;
|
|
44
53
|
batchCompare(entity: string, candidates: string[]): Promise<UhtBatchResult>;
|
|
54
|
+
/** Look up an entity by name in a Substrate namespace. Returns null if not found.
|
|
55
|
+
* Only returns exact name matches (case-insensitive) — no fuzzy fallback. */
|
|
56
|
+
lookupEntity(name: string, namespace: string): Promise<UhtEntityRecord | null>;
|
|
45
57
|
}
|
package/dist/uht-client.js
CHANGED
|
@@ -8,6 +8,30 @@ import { readFileSync } from "node:fs";
|
|
|
8
8
|
import { join } from "node:path";
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
10
|
const DEFAULT_UHT_URL = "https://substrate.universalhex.org/api";
|
|
11
|
+
// All 32 UHT v2 canonical trait names, indexed by bit position (1-based)
|
|
12
|
+
const TRAIT_NAMES = {
|
|
13
|
+
1: "Physical Object", 2: "Synthetic", 3: "Biological/Biomimetic", 4: "Powered",
|
|
14
|
+
5: "Structural", 6: "Observable", 7: "Physical Medium", 8: "Active",
|
|
15
|
+
9: "Intentionally Designed", 10: "Outputs Effect", 11: "Processes Signals/Logic", 12: "State-Transforming",
|
|
16
|
+
13: "Human-Interactive", 14: "System-integrated", 15: "Functionally Autonomous", 16: "System-Essential",
|
|
17
|
+
17: "Symbolic", 18: "Signalling", 19: "Rule-governed", 20: "Compositional",
|
|
18
|
+
21: "Normative", 22: "Meta", 23: "Temporal", 24: "Digital/Virtual",
|
|
19
|
+
25: "Social Construct", 26: "Institutionally Defined", 27: "Identity-Linked", 28: "Regulated",
|
|
20
|
+
29: "Economically Significant", 30: "Politicised", 31: "Ritualised", 32: "Ethically Significant",
|
|
21
|
+
};
|
|
22
|
+
/** Decode an 8-char hex code into trait names by reading the 32-bit field. */
|
|
23
|
+
export function decodeHexTraits(hexCode) {
|
|
24
|
+
const binary = parseInt(hexCode, 16).toString(2).padStart(32, "0");
|
|
25
|
+
const traits = [];
|
|
26
|
+
for (let i = 0; i < 32; i++) {
|
|
27
|
+
if (binary[i] === "1") {
|
|
28
|
+
const name = TRAIT_NAMES[i + 1];
|
|
29
|
+
if (name)
|
|
30
|
+
traits.push(name);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return traits;
|
|
34
|
+
}
|
|
11
35
|
function loadUhtConfigToken() {
|
|
12
36
|
try {
|
|
13
37
|
const configPath = join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "uht-substrate", "config.json");
|
|
@@ -52,4 +76,11 @@ export class UhtClient {
|
|
|
52
76
|
async batchCompare(entity, candidates) {
|
|
53
77
|
return this.request("POST", "/batch-compare", { entity, candidates });
|
|
54
78
|
}
|
|
79
|
+
/** Look up an entity by name in a Substrate namespace. Returns null if not found.
|
|
80
|
+
* Only returns exact name matches (case-insensitive) — no fuzzy fallback. */
|
|
81
|
+
async lookupEntity(name, namespace) {
|
|
82
|
+
const data = await this.request("GET", `/entities?namespace=${encodeURIComponent(namespace)}&name=${encodeURIComponent(name)}&limit=10`);
|
|
83
|
+
// Exact match only — the API does substring filtering, so we must check
|
|
84
|
+
return data.entities.find(e => e.name.toLowerCase() === name.toLowerCase()) ?? null;
|
|
85
|
+
}
|
|
55
86
|
}
|