archgraph-argo 0.10.37 → 0.10.39

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.
@@ -155,7 +155,7 @@ const TOOLS = [
155
155
  },
156
156
  {
157
157
  name: 'updateArchitectureElement',
158
- description: 'Use for one global element metadata patch. Does not change view membership. Element id and type are immutable; remove and re-add to change them.',
158
+ description: 'Use for one global element metadata patch. Does not change view membership. Element id and type are immutable; remove and re-add to change them. patch.attributes is a targeted merge by attribute name: each entry {name, value?, description?} upserts that attribute, {name, op:"remove"} deletes it, and attributes NOT mentioned are preserved — updating one attribute never wipes out the others.',
159
159
  inputSchema: {
160
160
  type: 'object',
161
161
  required: ['id', 'patch'],
@@ -1,4 +1,5 @@
1
1
  const fs = require('node:fs');
2
+ const os = require('node:os');
2
3
  const path = require('node:path');
3
4
  const { fileURLToPath } = require('node:url');
4
5
 
@@ -109,6 +110,15 @@ function getArgoEnvPath() {
109
110
  return path.resolve(process.env.ARGO_ENV_FILE);
110
111
  }
111
112
 
113
+ // Prefer the global clean env (<user-home>/.argo/.env) when present, so a
114
+ // workspace-local env file (which may carry non-whitelisted keys, e.g.
115
+ // repo argo/.env with DEEPSEEK_* that fails the secret-ACL) never shadows the
116
+ // sanctioned global configuration for semantic/embedding lifecycle.
117
+ const homeEnvPath = path.join(os.homedir(), '.argo', '.env');
118
+ if (fs.existsSync(homeEnvPath)) {
119
+ return homeEnvPath;
120
+ }
121
+
112
122
  const globalPath = path.join(getArgoRoot(), '.env');
113
123
  if (fs.existsSync(globalPath)) {
114
124
  return globalPath;
@@ -248,7 +248,7 @@ const TOOLS = [
248
248
  },
249
249
  {
250
250
  name: 'updateArchitectureElement',
251
- description: 'Use for one global element metadata patch. Does not change view membership. Element id and type are immutable; remove and re-add to change them. Set dryRun to preview without writing.',
251
+ description: 'Use for one global element metadata patch. Does not change view membership. Element id and type are immutable; remove and re-add to change them. patch.attributes is a targeted merge by attribute name: each entry {name, value?, description?} upserts that attribute, {name, op:"remove"} deletes it, and attributes NOT mentioned are preserved — updating one attribute never wipes out the others. Set dryRun to preview without writing.',
252
252
  inputSchema: {
253
253
  type: 'object',
254
254
  required: ['id', 'patch'],
@@ -1031,6 +1031,50 @@ function buildNativeSubgraph(document, includedElementIds, includedRelationshipI
1031
1031
  return { elements, relationships, views };
1032
1032
  }
1033
1033
 
1034
+ /**
1035
+ * Merge a targeted attribute patch into the element's existing attributes.
1036
+ *
1037
+ * Each patch entry is an object with a non-empty `name`:
1038
+ * - `{ name, op: 'remove' }` -> remove that attribute;
1039
+ * - `{ name, value }` / `{ name, value, description }` -> upsert (add or update in place).
1040
+ *
1041
+ * Attributes NOT mentioned in the patch are preserved. This avoids the
1042
+ * full-array replacement footgun where updating one attribute would wipe out the
1043
+ * rest (e.g. a stale deliveryStatus being re-written when registering a commit
1044
+ * attribute).
1045
+ */
1046
+ function mergeAttributesPatch(existing, patchEntries) {
1047
+ const result = Array.isArray(existing)
1048
+ ? existing.map(attr => ({ ...attr }))
1049
+ : [];
1050
+ if (!Array.isArray(patchEntries)) {
1051
+ throw new Error('patch.attributes must be an array of { name, ... } entries');
1052
+ }
1053
+ for (const entry of patchEntries) {
1054
+ if (!entry || typeof entry !== 'object' || typeof entry.name !== 'string' || entry.name.trim() === '') {
1055
+ throw new Error('patch.attributes entries must have a non-empty string name');
1056
+ }
1057
+ if (entry.op === 'remove') {
1058
+ const index = result.findIndex(attr => attr.name === entry.name);
1059
+ if (index >= 0) {
1060
+ result.splice(index, 1);
1061
+ }
1062
+ continue;
1063
+ }
1064
+ const index = result.findIndex(attr => attr.name === entry.name);
1065
+ if (index >= 0) {
1066
+ if (Object.prototype.hasOwnProperty.call(entry, 'value')) result[index].value = entry.value;
1067
+ if (Object.prototype.hasOwnProperty.call(entry, 'description')) result[index].description = entry.description;
1068
+ } else {
1069
+ const newAttribute = { name: entry.name };
1070
+ if (Object.prototype.hasOwnProperty.call(entry, 'value')) newAttribute.value = entry.value;
1071
+ if (Object.prototype.hasOwnProperty.call(entry, 'description')) newAttribute.description = entry.description;
1072
+ result.push(newAttribute);
1073
+ }
1074
+ }
1075
+ return result;
1076
+ }
1077
+
1034
1078
  function applyMutations(document, mutations) {
1035
1079
  const nextDocument = clone(document);
1036
1080
  const touchedElementIds = new Set();
@@ -1082,7 +1126,17 @@ function applyMutations(document, mutations) {
1082
1126
  requirePatchDoesNotChangeElementIdentityOrType(mutation.id, mutation.patch);
1083
1127
  const patchesSubdiagramViews = Object.prototype.hasOwnProperty.call(mutation.patch, 'subdiagram_views');
1084
1128
  const patchesName = Object.prototype.hasOwnProperty.call(mutation.patch, 'name');
1085
- Object.assign(element, clone(mutation.patch));
1129
+ const patch = clone(mutation.patch);
1130
+ if (Object.prototype.hasOwnProperty.call(patch, 'attributes')) {
1131
+ // Targeted attribute merge by name (upsert / op:'remove'), preserving every
1132
+ // attribute not mentioned in the patch — never a full-array replacement,
1133
+ // so updating one attribute cannot wipe out the others.
1134
+ patch.attributes = mergeAttributesPatch(
1135
+ Array.isArray(element.attributes) ? element.attributes : [],
1136
+ patch.attributes,
1137
+ );
1138
+ }
1139
+ Object.assign(element, patch);
1086
1140
  if (patchesSubdiagramViews || patchesName) {
1087
1141
  reconcileSubdiagramViewsForElement(nextDocument, element);
1088
1142
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archgraph-argo",
3
- "version": "0.10.37",
3
+ "version": "0.10.39",
4
4
  "description": "Deploy the ArchGraph ARGO toolchain, skills, and rules (schema, scripts, argo-init skill, global rule) with one command.",
5
5
  "license": "MIT",
6
6
  "bin": {