nexarch 0.13.2 → 0.13.3

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.
@@ -613,8 +613,9 @@ function injectInitProjectReportingContract(path) {
613
613
  "**One exception: a possible duplicate.** If init-project returns `status: \"review_required\"`",
614
614
  "or prints `STOP — POSSIBLE DUPLICATE`, an application already in the workspace looks like",
615
615
  "one it would register, and it wrote nothing. Tell the human which one you think matches and",
616
- "why, wait for their answer, then re-run init-project with the command for their choice",
617
- "(`decision.commands` in the output). Only then continue with enrichment.",
616
+ "why, wait for their answer, then run the command for their choice yourself",
617
+ "(`decision.commands` in the output) and continue. If it is the same system, enrich the",
618
+ "existing application — nothing needs merging.",
618
619
  "",
619
620
  "Stop early only when a step genuinely cannot run — missing cloud credentials, a root",
620
621
  "module that has not been initialised, or an environment you should not touch without",
@@ -1315,7 +1315,8 @@ export function enrichmentStatusFor(params) {
1315
1315
  }
1316
1316
  export const REVIEW_REQUIRED_NOTE = "STOP. Nothing was written. Existing applications in this workspace resemble what this scan would register"
1317
1317
  + " (see similarApplications). Tell the human, say which one you think is the same system and why, and wait for"
1318
- + " their answer. Then re-run init-project with the command in `decision.commands` that matches their choice.";
1318
+ + " their answer. Then run the command in `decision.commands` for their choice yourself and carry on. If it is the"
1319
+ + " same system, enrich the existing application: nothing needs merging.";
1319
1320
  /**
1320
1321
  * The possible-duplicate warning for the text directive, first thing in it.
1321
1322
  *
@@ -1338,6 +1339,7 @@ export function possibleDuplicateDirective(matches, isMonorepo) {
1338
1339
  ? " same system npx nexarch@latest init-project --dir . --map-deployable <deployable>=<entityRef>"
1339
1340
  : " same application npx nexarch@latest init-project --dir . --application-ref <entityRef>",
1340
1341
  " all different npx nexarch@latest init-project --dir . --create-application",
1342
+ "Run the command for their answer yourself. If it is the same system, enrich the existing application — nothing needs merging.",
1341
1343
  ];
1342
1344
  }
1343
1345
  /**
@@ -1684,6 +1686,11 @@ export async function initProject(args) {
1684
1686
  let stopForReview = null;
1685
1687
  // Names of applications already in the workspace, by ref, for mapped deployables.
1686
1688
  const existingApplicationNames = new Map();
1689
+ // Refs of those that are still proposals: a mapped deployable's leftover
1690
+ // duplicate from an earlier scan is folded away only if nobody reviewed it.
1691
+ const proposedApplicationRefs = new Set();
1692
+ // What happened to each leftover duplicate, for the output.
1693
+ const foldedDuplicates = [];
1687
1694
  if (projectConstruct && isMonorepo && (applicationRefOverride || forceCreateApplication) && !asJson) {
1688
1695
  console.log("\nNote: monorepo detected — no root application is created (ADR 8a); --application-ref/--create-application apply only to single-package repositories.");
1689
1696
  }
@@ -1780,8 +1787,11 @@ export async function initProject(args) {
1780
1787
  const listed = parseToolText(appsRaw).entities ?? [];
1781
1788
  for (const entity of listed) {
1782
1789
  const ref = entity.entityRef ?? entity.externalKey;
1783
- if (ref)
1784
- existingApplicationNames.set(ref, entity.name);
1790
+ if (!ref)
1791
+ continue;
1792
+ existingApplicationNames.set(ref, entity.name);
1793
+ if (String(entity.attributes?.workflow_state ?? "").toLowerCase() === "proposed")
1794
+ proposedApplicationRefs.add(ref);
1785
1795
  }
1786
1796
  // A mapped deployable writes no stub of its own, so a mapping to an
1787
1797
  // application that is not there would leave its relationships pointing at
@@ -2172,6 +2182,42 @@ export async function initProject(args) {
2172
2182
  // ability to resolve via tier 2; the scan itself already succeeded.
2173
2183
  }
2174
2184
  }
2185
+ // "There should be nothing to merge" (Paul, 16 September). When the human has
2186
+ // confirmed a deployable is an existing application, an earlier scan may
2187
+ // already have registered that deployable as a proposal of its own -- the
2188
+ // 0.13.1 behaviour, or any run before the duplicate check stopped the scan.
2189
+ // Fold it into the existing application here, so the agent carries on
2190
+ // enriching the real one and nobody has to press Merge. Only unreviewed
2191
+ // proposals: the gateway refuses anything a human has activated.
2192
+ for (const sp of subPackages) {
2193
+ if (sp.mappedBy !== "flag" || !sp.mappedFrom || !proposedApplicationRefs.has(sp.mappedFrom))
2194
+ continue;
2195
+ try {
2196
+ const raw = await callMcpTool("nexarch_merge_proposed_application", {
2197
+ proposedApplicationRef: sp.mappedFrom,
2198
+ intoApplicationRef: sp.externalKey,
2199
+ reason: `The human confirmed the ${sp.relativePath} deployable is ${existingApplicationNames.get(sp.externalKey) ?? sp.externalKey}.`,
2200
+ companyId: creds.companyId,
2201
+ }, { companyId: creds.companyId });
2202
+ const result = parseToolText(raw);
2203
+ foldedDuplicates.push({
2204
+ proposedApplicationRef: sp.mappedFrom,
2205
+ intoApplicationRef: sp.externalKey,
2206
+ folded: true,
2207
+ detail: `folded in; ${result.relationshipsRepointed ?? 0} relationship(s) moved to ${sp.externalKey}`,
2208
+ });
2209
+ }
2210
+ catch (error) {
2211
+ // An older gateway without the tool, or a credential without the scope.
2212
+ // Say so plainly: the duplicate is still there and the human should know.
2213
+ foldedDuplicates.push({
2214
+ proposedApplicationRef: sp.mappedFrom,
2215
+ intoApplicationRef: sp.externalKey,
2216
+ folded: false,
2217
+ detail: error instanceof Error ? error.message : String(error),
2218
+ });
2219
+ }
2220
+ }
2175
2221
  // Remember an explicit --map-deployable, so the next scan does not ask again.
2176
2222
  // A merge registers the same alias; this is the same decision made up front.
2177
2223
  for (const sp of subPackages) {
@@ -2249,8 +2295,7 @@ export async function initProject(args) {
2249
2295
  if (similarApplications.length > 0 && isMonorepo) {
2250
2296
  // --application-ref does not apply to a monorepo (the CLI says so above),
2251
2297
  // so the single-repo remedy would hand the agent a command that does
2252
- // nothing. The resolution here is a merge, which a human does on the
2253
- // application page.
2298
+ // nothing. The resolution is --map-deployable, which the agent runs.
2254
2299
  pendingSteps.push({
2255
2300
  step: stepNum++,
2256
2301
  action: "review_similar_applications",
@@ -2258,8 +2303,9 @@ export async function initProject(args) {
2258
2303
  + ` workspace resemble${similarApplications.length === 1 ? "s" : ""} this repository: ${matchList}.`
2259
2304
  + " One of the deployables just registered may be the same system — for example a web app registered"
2260
2305
  + " earlier from its public website. Tell the human which deployable you think matches and why, before"
2261
- + " enriching anything. The new deployables are PROPOSED, so nothing is broken yet; if one is a"
2262
- + " duplicate, the human merges the two with \"Merge with…\" on the application's page.",
2306
+ + " enriching anything. If the human confirms one is the same system, re-run with"
2307
+ + " --map-deployable <deployable>=<entityRef> yourself and enrich the existing application: the scan"
2308
+ + " folds this proposal into it, so there is nothing for anyone to merge.",
2263
2309
  notes: [
2264
2310
  "Do not report the registration as complete without mentioning these.",
2265
2311
  "The match is on the repository, not a specific deployable: say which one you believe it is, and why.",
@@ -2532,7 +2578,8 @@ export async function initProject(args) {
2532
2578
  // "Confirm type, then update" is right for a new skeleton and wrong
2533
2579
  // here: this is an application a human already registered and named.
2534
2580
  lines.push(` MAPPED : this deployable is the existing application ${sp.externalKey}${sp.mappedBy === "alias" ? " (from an earlier merge)" : ""}.`);
2535
- lines.push(" Add what the code shows; do not rename it or replace its description.");
2581
+ lines.push(" It is this deployable's application: enrich it — name, description, type and");
2582
+ lines.push(" functions — from the code, as you would a new one.");
2536
2583
  }
2537
2584
  lines.push(` inferred : ${sp.entityType} / ${sp.subtype} (confidence ${conf.toFixed(2)})`);
2538
2585
  const unresolvedDeps = sp.depSpecs.map((d) => d.name).filter((d) => !resolvedByInput.has(d));
@@ -2666,6 +2713,7 @@ export async function initProject(args) {
2666
2713
  resolved: resolvedItems.length,
2667
2714
  unresolved: unresolvedItems.length,
2668
2715
  similarApplications,
2716
+ ...(foldedDuplicates.length > 0 ? { foldedDuplicates } : {}),
2669
2717
  entityErrors: entitiesResult.errors ?? [],
2670
2718
  relationshipErrors: relsResult?.errors ?? [],
2671
2719
  enrichmentRequired,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexarch",
3
- "version": "0.13.2",
3
+ "version": "0.13.3",
4
4
  "description": "Your architecture workspace for AI delivery.",
5
5
  "keywords": [
6
6
  "nexarch",