nexarch 0.9.7 → 0.9.9
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/init-agent.js +35 -0
- package/dist/commands/init-project.js +5 -0
- package/dist/commands/update-entity.js +56 -107
- package/dist/index.js +12 -12
- package/package.json +1 -1
|
@@ -444,6 +444,33 @@ function injectTrustAttestationUnavailableBlock(path, reason) {
|
|
|
444
444
|
}
|
|
445
445
|
writeFileSync(path, replaced !== existing ? replaced : `${existing}${existing.endsWith("\n") ? "" : "\n"}${managed}\n`, "utf8");
|
|
446
446
|
}
|
|
447
|
+
function injectInitProjectReportingContract(path) {
|
|
448
|
+
const section = [
|
|
449
|
+
"## Nexarch Init-Project Reporting Contract",
|
|
450
|
+
"",
|
|
451
|
+
"When you run `npx nexarch@latest init-project`, treat it as skeleton registration only.",
|
|
452
|
+
"Do not claim full completion until enrichment commands are actually executed.",
|
|
453
|
+
"",
|
|
454
|
+
"Required final status fields in your reply after init-project:",
|
|
455
|
+
'- `registration_status`: `"skeleton_only"` or `"enriched"`',
|
|
456
|
+
'- `enrichment_completed`: `true` or `false`',
|
|
457
|
+
"",
|
|
458
|
+
"Hard rule:",
|
|
459
|
+
'- If `enrichment_completed=false`, you MUST include: `Enrichment pending: run update-entity/enrichment steps.`',
|
|
460
|
+
'- If `enrichment_completed=false`, NEVER say: "fully registered", "fully done", or equivalent.',
|
|
461
|
+
'- For enrichment, use explicit per-entity `update-entity` runs (no bulk entity-update shortcut).',
|
|
462
|
+
"",
|
|
463
|
+
"You may set `registration_status=\"enriched\"` only after executing enrichment actions (e.g. update-entity for project/sub-packages, aliases, and any chosen gap wiring), with evidence-based per-entity descriptions/subtypes.",
|
|
464
|
+
"",
|
|
465
|
+
].join("\n");
|
|
466
|
+
const existing = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
467
|
+
const managed = wrapManagedSection("init-project-reporting-contract", section.trim());
|
|
468
|
+
let replaced = replaceManagedSection(existing, "init-project-reporting-contract", section.trim());
|
|
469
|
+
if (replaced === existing) {
|
|
470
|
+
replaced = replaceInjectedSection(existing, "## Nexarch Init-Project Reporting Contract", managed);
|
|
471
|
+
}
|
|
472
|
+
writeFileSync(path, replaced !== existing ? replaced : `${existing}${existing.endsWith("\n") ? "" : "\n"}${managed}\n`, "utf8");
|
|
473
|
+
}
|
|
447
474
|
function injectGenericAgentConfig(registry) {
|
|
448
475
|
const templateByCode = new Map(registry.instructionTemplates.map((t) => [t.code, t]));
|
|
449
476
|
const genericTargets = [...registry.instructionTargets]
|
|
@@ -1114,6 +1141,7 @@ export async function initAgent(args) {
|
|
|
1114
1141
|
else {
|
|
1115
1142
|
injectTrustAttestationUnavailableBlock(r.path, trustAttestation.reason ?? "unknown");
|
|
1116
1143
|
}
|
|
1144
|
+
injectInitProjectReportingContract(r.path);
|
|
1117
1145
|
}
|
|
1118
1146
|
catch {
|
|
1119
1147
|
// non-fatal
|
|
@@ -1222,6 +1250,13 @@ export async function initAgent(args) {
|
|
|
1222
1250
|
consentPrompt: "I detected a likely project in this directory. Do you want me to run init-project now?",
|
|
1223
1251
|
actionIfApproved: "run_init_project",
|
|
1224
1252
|
command: "npx nexarch@latest init-project --dir . --json",
|
|
1253
|
+
reportingContract: {
|
|
1254
|
+
requiredFields: ["registration_status", "enrichment_completed"],
|
|
1255
|
+
allowedRegistrationStatus: ["skeleton_only", "enriched"],
|
|
1256
|
+
forbiddenWhenEnrichmentPending: ["fully registered", "fully done"],
|
|
1257
|
+
mandatoryLineWhenEnrichmentPending: "Enrichment pending: run update-entity/enrichment steps.",
|
|
1258
|
+
enrichmentMode: "per_entity_updates_only",
|
|
1259
|
+
},
|
|
1225
1260
|
}
|
|
1226
1261
|
: {
|
|
1227
1262
|
detected: false,
|
|
@@ -1667,6 +1667,11 @@ STEP 1 — Read the project README / docs and build your understanding of:
|
|
|
1667
1667
|
|
|
1668
1668
|
STEP 2 — Enrich the project entity. Run this command with the description you've written:
|
|
1669
1669
|
|
|
1670
|
+
ENRICHMENT QUALITY RULE:
|
|
1671
|
+
• Do enrichment as explicit per-entity updates.
|
|
1672
|
+
• Do NOT shortcut with bulk entity updates for semantic enrichment.
|
|
1673
|
+
• The goal is accurate, evidence-based descriptions/subtypes per entity, not just write throughput.
|
|
1674
|
+
|
|
1670
1675
|
npx nexarch update-entity \\
|
|
1671
1676
|
--key "${projectExternalKey}" \\
|
|
1672
1677
|
--entity-type "${entityTypeOverride}"${entityTypeOverride === "application" ? " \\\n --subtype \"app_custom_built\" \\\n --icon \"<curated icon>\"" : ""} \\
|
|
@@ -22,124 +22,52 @@ function parseAttributesInput(jsonText, filePath) {
|
|
|
22
22
|
if (!jsonText && !filePath)
|
|
23
23
|
return null;
|
|
24
24
|
if (jsonText && filePath) {
|
|
25
|
-
|
|
25
|
+
console.error("error: use only one of --attributes-json or --attributes-file");
|
|
26
|
+
process.exit(1);
|
|
26
27
|
}
|
|
27
28
|
let raw = jsonText;
|
|
28
29
|
if (filePath) {
|
|
29
30
|
if (!existsSync(filePath)) {
|
|
30
|
-
|
|
31
|
+
console.error(`error: --attributes-file not found: ${filePath}`);
|
|
32
|
+
process.exit(1);
|
|
31
33
|
}
|
|
32
34
|
raw = readFileSync(filePath, "utf8");
|
|
33
35
|
}
|
|
34
36
|
try {
|
|
35
37
|
const parsed = JSON.parse(raw ?? "{}");
|
|
36
38
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
37
|
-
|
|
39
|
+
console.error("error: attributes input must be a JSON object");
|
|
40
|
+
process.exit(1);
|
|
38
41
|
}
|
|
39
42
|
return parsed;
|
|
40
43
|
}
|
|
41
44
|
catch (error) {
|
|
42
|
-
|
|
45
|
+
console.error(`error: invalid attributes JSON (${error instanceof Error ? error.message : "parse failed"})`);
|
|
46
|
+
process.exit(1);
|
|
43
47
|
}
|
|
44
48
|
}
|
|
45
|
-
function parseEntityBatch(args) {
|
|
46
|
-
const entitiesFile = parseOptionValue(args, "--entities-file");
|
|
47
|
-
const entitiesJson = parseOptionValue(args, "--entities-json");
|
|
48
|
-
if (!entitiesFile && !entitiesJson)
|
|
49
|
-
return null;
|
|
50
|
-
if (entitiesFile && entitiesJson)
|
|
51
|
-
throw new Error("Use only one of --entities-file or --entities-json");
|
|
52
|
-
let raw = entitiesJson;
|
|
53
|
-
if (entitiesFile) {
|
|
54
|
-
if (!existsSync(entitiesFile))
|
|
55
|
-
throw new Error(`--entities-file not found: ${entitiesFile}`);
|
|
56
|
-
raw = readFileSync(entitiesFile, "utf8");
|
|
57
|
-
}
|
|
58
|
-
let parsed;
|
|
59
|
-
try {
|
|
60
|
-
parsed = JSON.parse(raw ?? "[]");
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
throw new Error(entitiesFile ? "--entities-file must contain valid JSON array" : "--entities-json must be valid JSON array");
|
|
64
|
-
}
|
|
65
|
-
if (!Array.isArray(parsed) || parsed.length === 0) {
|
|
66
|
-
throw new Error("Entities input must be a non-empty JSON array");
|
|
67
|
-
}
|
|
68
|
-
return parsed.map((item, index) => {
|
|
69
|
-
const v = (item ?? {});
|
|
70
|
-
const externalKey = String(v.externalKey ?? v.key ?? "").trim();
|
|
71
|
-
if (!externalKey)
|
|
72
|
-
throw new Error(`Entity #${index + 1} is missing externalKey/key`);
|
|
73
|
-
const entityTypeCode = String(v.entityTypeCode ?? v.entity_type_code ?? "application").trim() || "application";
|
|
74
|
-
const entitySubtypeCode = String(v.entitySubtypeCode ?? v.entity_subtype_code ?? "").trim();
|
|
75
|
-
const out = {
|
|
76
|
-
externalKey,
|
|
77
|
-
entityTypeCode,
|
|
78
|
-
confidence: 1,
|
|
79
|
-
};
|
|
80
|
-
if (typeof v.name === "string" && v.name.trim())
|
|
81
|
-
out.name = v.name.trim();
|
|
82
|
-
if (typeof v.description === "string" && v.description.trim())
|
|
83
|
-
out.description = v.description.trim();
|
|
84
|
-
if (entitySubtypeCode)
|
|
85
|
-
out.entitySubtypeCode = entitySubtypeCode;
|
|
86
|
-
const attrs = {
|
|
87
|
-
...(v.attributes && typeof v.attributes === "object" && !Array.isArray(v.attributes) ? v.attributes : {}),
|
|
88
|
-
};
|
|
89
|
-
if (typeof v.icon === "string" && v.icon.trim()) {
|
|
90
|
-
attrs.application_icon = { provider: "lucide", name: v.icon.trim() };
|
|
91
|
-
}
|
|
92
|
-
if (Object.keys(attrs).length > 0)
|
|
93
|
-
out.attributes = attrs;
|
|
94
|
-
if (!out.name && !out.description && !out.attributes) {
|
|
95
|
-
throw new Error(`Entity #${index + 1} must include at least one of name, description, icon, or attributes`);
|
|
96
|
-
}
|
|
97
|
-
return out;
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
49
|
export async function updateEntity(args) {
|
|
101
50
|
const asJson = parseFlag(args, "--json");
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
entities = batchEntities;
|
|
51
|
+
if (parseOptionValue(args, "--entities-json") || parseOptionValue(args, "--entities-file")) {
|
|
52
|
+
console.error("error: batch entity updates were removed. Use explicit per-entity update-entity commands for enrichment quality.");
|
|
53
|
+
process.exit(1);
|
|
106
54
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
externalKey,
|
|
124
|
-
entityTypeCode,
|
|
125
|
-
confidence: 1,
|
|
126
|
-
};
|
|
127
|
-
if (name)
|
|
128
|
-
entity.name = name;
|
|
129
|
-
if (description)
|
|
130
|
-
entity.description = description;
|
|
131
|
-
if (entitySubtypeCode)
|
|
132
|
-
entity.entitySubtypeCode = entitySubtypeCode;
|
|
133
|
-
const attributes = {
|
|
134
|
-
...(explicitAttributes ?? {}),
|
|
135
|
-
};
|
|
136
|
-
if (iconName) {
|
|
137
|
-
attributes.application_icon = { provider: "lucide", name: iconName };
|
|
138
|
-
}
|
|
139
|
-
if (Object.keys(attributes).length > 0) {
|
|
140
|
-
entity.attributes = attributes;
|
|
141
|
-
}
|
|
142
|
-
entities = [entity];
|
|
55
|
+
const externalKey = parseOptionValue(args, "--key");
|
|
56
|
+
const name = parseOptionValue(args, "--name");
|
|
57
|
+
const description = parseOptionValue(args, "--description");
|
|
58
|
+
const entityTypeCode = parseOptionValue(args, "--entity-type") ?? "application";
|
|
59
|
+
const entitySubtypeCode = parseOptionValue(args, "--subtype");
|
|
60
|
+
const iconName = parseOptionValue(args, "--icon");
|
|
61
|
+
const attributesJson = parseOptionValue(args, "--attributes-json");
|
|
62
|
+
const attributesFile = parseOptionValue(args, "--attributes-file");
|
|
63
|
+
if (!externalKey) {
|
|
64
|
+
console.error("error: --key <externalKey> is required");
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
const explicitAttributes = parseAttributesInput(attributesJson, attributesFile);
|
|
68
|
+
if (!name && !description && !iconName && !explicitAttributes) {
|
|
69
|
+
console.error("error: provide at least one of --name, --description, --icon, --attributes-json, or --attributes-file");
|
|
70
|
+
process.exit(1);
|
|
143
71
|
}
|
|
144
72
|
const creds = requireCredentials();
|
|
145
73
|
const mcpOpts = { companyId: creds.companyId };
|
|
@@ -150,7 +78,7 @@ export async function updateEntity(args) {
|
|
|
150
78
|
const agentContext = {
|
|
151
79
|
agentId: "nexarch-cli:update-entity",
|
|
152
80
|
agentRunId: `update-entity-${Date.now()}`,
|
|
153
|
-
repoRef:
|
|
81
|
+
repoRef: externalKey,
|
|
154
82
|
observedAt: nowIso,
|
|
155
83
|
source: "nexarch-cli",
|
|
156
84
|
model: "n/a",
|
|
@@ -159,7 +87,31 @@ export async function updateEntity(args) {
|
|
|
159
87
|
const policyContext = policyBundleHash
|
|
160
88
|
? { policyBundleHash, alignmentSummary: { score: 1, violations: [], waivers: [] } }
|
|
161
89
|
: undefined;
|
|
162
|
-
const
|
|
90
|
+
const entity = {
|
|
91
|
+
externalKey,
|
|
92
|
+
entityTypeCode,
|
|
93
|
+
confidence: 1,
|
|
94
|
+
};
|
|
95
|
+
if (name)
|
|
96
|
+
entity.name = name;
|
|
97
|
+
if (description)
|
|
98
|
+
entity.description = description;
|
|
99
|
+
if (entitySubtypeCode)
|
|
100
|
+
entity.entitySubtypeCode = entitySubtypeCode;
|
|
101
|
+
const attributes = {
|
|
102
|
+
...(explicitAttributes ?? {}),
|
|
103
|
+
};
|
|
104
|
+
// Convenience sugar; still generic because user can fully control payload via --attributes-json/file.
|
|
105
|
+
if (iconName) {
|
|
106
|
+
attributes.application_icon = {
|
|
107
|
+
provider: "lucide",
|
|
108
|
+
name: iconName,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
if (Object.keys(attributes).length > 0) {
|
|
112
|
+
entity.attributes = attributes;
|
|
113
|
+
}
|
|
114
|
+
const raw = await callMcpTool("nexarch_upsert_entities", { entities: [entity], agentContext, policyContext }, mcpOpts);
|
|
163
115
|
const result = parseToolText(raw);
|
|
164
116
|
if (asJson) {
|
|
165
117
|
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
@@ -170,17 +122,14 @@ export async function updateEntity(args) {
|
|
|
170
122
|
const succeeded = result.summary?.succeeded ?? 0;
|
|
171
123
|
const failed = result.summary?.failed ?? 0;
|
|
172
124
|
if (failed > 0) {
|
|
173
|
-
console.error(`Failed to update
|
|
125
|
+
console.error(`Failed to update entity: ${externalKey}`);
|
|
174
126
|
for (const err of result.errors ?? []) {
|
|
175
127
|
console.error(` ${err.externalKey}: ${err.error} — ${err.message}`);
|
|
176
128
|
}
|
|
177
129
|
process.exitCode = 1;
|
|
178
130
|
return;
|
|
179
131
|
}
|
|
180
|
-
if (
|
|
181
|
-
console.log(`Updated ${succeeded} entity: ${
|
|
182
|
-
}
|
|
183
|
-
else {
|
|
184
|
-
console.log(`Updated ${succeeded}/${entities.length} entities.`);
|
|
132
|
+
if (!asJson) {
|
|
133
|
+
console.log(`Updated ${succeeded} entity: ${externalKey}`);
|
|
185
134
|
}
|
|
186
135
|
}
|
package/dist/index.js
CHANGED
|
@@ -99,18 +99,18 @@ Usage:
|
|
|
99
99
|
--dry-run preview without writing
|
|
100
100
|
--json
|
|
101
101
|
nexarch update-entity
|
|
102
|
-
Update
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
102
|
+
Update the name and/or description of an existing graph entity.
|
|
103
|
+
Use this after init-project to enrich the entity with meaningful
|
|
104
|
+
content from the project README or docs.
|
|
105
|
+
Options: --key <externalKey> (required)
|
|
106
|
+
--name <name>
|
|
107
|
+
--description <text>
|
|
108
|
+
--entity-type <code> (default: application)
|
|
109
|
+
--subtype <code>
|
|
110
|
+
--icon <lucide-name> (convenience; sets attributes.application_icon)
|
|
111
|
+
--attributes-json '<json object>'
|
|
112
|
+
--attributes-file <path.json>
|
|
113
|
+
--json
|
|
114
114
|
nexarch add-relationship
|
|
115
115
|
Add relationships between existing graph entities (single or batch).
|
|
116
116
|
Single options: --from <externalKey>
|