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.
@@ -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
- throw new Error("Use only one of --attributes-json or --attributes-file");
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
- throw new Error(`--attributes-file not found: ${filePath}`);
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
- throw new Error("Attributes input must be a JSON object");
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
- throw new Error(`Invalid attributes JSON (${error instanceof Error ? error.message : "parse failed"})`);
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
- const batchEntities = parseEntityBatch(args);
103
- let entities;
104
- if (batchEntities) {
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
- else {
108
- const externalKey = parseOptionValue(args, "--key");
109
- const name = parseOptionValue(args, "--name");
110
- const description = parseOptionValue(args, "--description");
111
- const entityTypeCode = parseOptionValue(args, "--entity-type") ?? "application";
112
- const entitySubtypeCode = parseOptionValue(args, "--subtype");
113
- const iconName = parseOptionValue(args, "--icon");
114
- const attributesJson = parseOptionValue(args, "--attributes-json");
115
- const attributesFile = parseOptionValue(args, "--attributes-file");
116
- if (!externalKey)
117
- throw new Error("--key <externalKey> is required (or use --entities-file/--entities-json)");
118
- const explicitAttributes = parseAttributesInput(attributesJson, attributesFile);
119
- if (!name && !description && !iconName && !explicitAttributes) {
120
- throw new Error("Provide at least one of --name, --description, --icon, --attributes-json, or --attributes-file");
121
- }
122
- const entity = {
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: String(entities[0]?.externalKey ?? "batch"),
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 raw = await callMcpTool("nexarch_upsert_entities", { entities, agentContext, policyContext }, mcpOpts);
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 entities (${failed}/${entities.length}).`);
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 (entities.length === 1) {
181
- console.log(`Updated ${succeeded} entity: ${String(entities[0].externalKey)}`);
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 existing graph entities (single or batch).
103
- Single options: --key <externalKey>
104
- --name <name>
105
- --description <text>
106
- --entity-type <code> (default: application)
107
- --subtype <code>
108
- --icon <lucide-name> (sets attributes.application_icon)
109
- --attributes-json '<json object>'
110
- --attributes-file <path.json>
111
- Batch options: --entities-json '<json array>'
112
- --entities-file <path.json>
113
- --json
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>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexarch",
3
- "version": "0.9.7",
3
+ "version": "0.9.9",
4
4
  "description": "Your architecture workspace for AI delivery.",
5
5
  "keywords": [
6
6
  "nexarch",