in2-agent-server 0.20260821.185429

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.
Files changed (115) hide show
  1. package/README.md +369 -0
  2. package/agents/plan-requirement.md +32 -0
  3. package/dist/campfire/configItem.js +201 -0
  4. package/dist/campfire/configItemDescription.js +379 -0
  5. package/dist/campfire/deriveFromBundle.js +1013 -0
  6. package/dist/cli.js +465 -0
  7. package/dist/core/betaFeedback.js +493 -0
  8. package/dist/core/chapterInstructions.js +473 -0
  9. package/dist/core/claudeAssets.js +61 -0
  10. package/dist/core/gate.js +201 -0
  11. package/dist/core/ids.js +9 -0
  12. package/dist/core/install.js +60 -0
  13. package/dist/core/operatorQa.js +462 -0
  14. package/dist/core/orgCallMeter.js +178 -0
  15. package/dist/core/parcels.js +1235 -0
  16. package/dist/core/paths.js +105 -0
  17. package/dist/core/phases.js +225 -0
  18. package/dist/core/planFile.js +251 -0
  19. package/dist/core/planFragments.js +119 -0
  20. package/dist/core/readsLog.js +398 -0
  21. package/dist/core/registration.js +24 -0
  22. package/dist/core/result.js +38 -0
  23. package/dist/core/resume.js +233 -0
  24. package/dist/core/revisionChain.js +351 -0
  25. package/dist/core/richText.js +61 -0
  26. package/dist/core/semanticHash.js +89 -0
  27. package/dist/core/sessionOrg.js +60 -0
  28. package/dist/core/sfQuery.js +19 -0
  29. package/dist/core/spawn.js +182 -0
  30. package/dist/core/supervisorContract.js +230 -0
  31. package/dist/core/telemetry.js +1297 -0
  32. package/dist/core/telemetryContext.js +154 -0
  33. package/dist/core/telemetrySpool.js +244 -0
  34. package/dist/core/transcriptUpload.js +516 -0
  35. package/dist/core/update.js +363 -0
  36. package/dist/core/viewer.js +117 -0
  37. package/dist/core/workspace.js +92 -0
  38. package/dist/core/writeGrants.js +93 -0
  39. package/dist/core/writebackManifest.js +92 -0
  40. package/dist/direct/applyLoop.js +2516 -0
  41. package/dist/direct/batchReport.js +510 -0
  42. package/dist/direct/comparatorNoise.js +75 -0
  43. package/dist/direct/containers.js +85 -0
  44. package/dist/direct/contamination.js +221 -0
  45. package/dist/direct/crud.js +56 -0
  46. package/dist/direct/destructive.js +168 -0
  47. package/dist/direct/knownTokens.js +94 -0
  48. package/dist/direct/lint.js +382 -0
  49. package/dist/direct/metadataSchema.generated.json +1 -0
  50. package/dist/direct/metadataSchema.js +179 -0
  51. package/dist/direct/operatorPage.js +349 -0
  52. package/dist/direct/orgRepo.js +1531 -0
  53. package/dist/direct/orgRepoSpawnCount.js +21 -0
  54. package/dist/direct/plannedScope.js +119 -0
  55. package/dist/direct/preImageTargets.js +63 -0
  56. package/dist/direct/refVerify.js +191 -0
  57. package/dist/direct/registry.js +129 -0
  58. package/dist/direct/removals.js +262 -0
  59. package/dist/direct/rollback.js +1513 -0
  60. package/dist/direct/rollbackRun.js +1360 -0
  61. package/dist/direct/sessionProjection.js +126 -0
  62. package/dist/direct/snapshotComparability.js +165 -0
  63. package/dist/direct/sourceTree.js +113 -0
  64. package/dist/direct/unstatedChanges.js +106 -0
  65. package/dist/direct/waveAssembly.js +556 -0
  66. package/dist/direct/waveCounts.js +19 -0
  67. package/dist/direct/waveDerivation.js +152 -0
  68. package/dist/direct/waveGrade.js +185 -0
  69. package/dist/index.js +90 -0
  70. package/dist/orgread/customFieldSnapshot.js +48 -0
  71. package/dist/orgread/fieldMetadata.js +178 -0
  72. package/dist/orgread/genericSnapshot.js +61 -0
  73. package/dist/orgread/layoutSnapshot.js +151 -0
  74. package/dist/orgread/salesforceOrgReader.js +184 -0
  75. package/dist/orgread/snapshotTypes.js +9 -0
  76. package/dist/projection/bridge.js +26 -0
  77. package/dist/projection/diff.js +141 -0
  78. package/dist/projection/mapmd.js +213 -0
  79. package/dist/projection/nameAmbiguity.js +106 -0
  80. package/dist/projection/precedent.js +136 -0
  81. package/dist/projection/project.js +214 -0
  82. package/dist/projection/scope.js +110 -0
  83. package/dist/projection/types.js +5 -0
  84. package/dist/tools/analyze.js +322 -0
  85. package/dist/tools/auth.js +915 -0
  86. package/dist/tools/campfire.js +1742 -0
  87. package/dist/tools/closeBatch.js +154 -0
  88. package/dist/tools/closeText.js +35 -0
  89. package/dist/tools/direct.js +1613 -0
  90. package/dist/tools/feedback.js +318 -0
  91. package/dist/tools/operator.js +287 -0
  92. package/dist/tools/org.js +739 -0
  93. package/dist/tools/phase.js +249 -0
  94. package/dist/tools/projection.js +172 -0
  95. package/dist/tools/session.js +313 -0
  96. package/dist/tools/update.js +82 -0
  97. package/dist/tools/waves.js +793 -0
  98. package/package.json +48 -0
  99. package/reference/campfire-configuration-items-guide.md +710 -0
  100. package/reference/deployability-cribs.md +119 -0
  101. package/reference/operator-voice.md +141 -0
  102. package/reference/org-read-cribs.md +34 -0
  103. package/reference/scoped-dml-interim.md +68 -0
  104. package/reference/supervisor-playbook.md +313 -0
  105. package/reference/templates.md +233 -0
  106. package/skills/in2-delivery/SKILL.md +322 -0
  107. package/skills/in2-delivery/references/campfire-configuration-items-guide.md +710 -0
  108. package/skills/in2-delivery/references/deployability-cribs.md +119 -0
  109. package/skills/in2-delivery/references/operator-voice.md +141 -0
  110. package/skills/in2-delivery/references/org-read-cribs.md +34 -0
  111. package/skills/in2-delivery/references/scoped-dml-interim.md +68 -0
  112. package/skills/in2-delivery/references/templates.md +233 -0
  113. package/skills/plan-requirement/SKILL.md +188 -0
  114. package/viewer/index.html +899 -0
  115. package/viewer/server.mjs +433 -0
@@ -0,0 +1,262 @@
1
+ import { canonJson } from "../projection/diff.js";
2
+ const isObj = (v) => Boolean(v) && typeof v === "object" && !Array.isArray(v);
3
+ const gone = (v) => v === undefined || v === null;
4
+ // Pure reorder is not a removal: the org alphabetizes many collections on read-back.
5
+ function sameMultiset(a, b) {
6
+ const key = (arr) => arr.map(canonJson).sort().join(" ");
7
+ return key(a) === key(b);
8
+ }
9
+ // Elements of a metadata collection are identified by a NAME, not by their position. Salesforce
10
+ // reorders freely on read-back, and an insertion shifts every later index — so positional matching
11
+ // compares unrelated things and reports the difference as a loss.
12
+ //
13
+ // Live (CF77): inserting an "Intake Details" section at index 1 of a Layout made this report
14
+ // `layoutSections[2].layoutColumns 3 → 2 items` as CONTENT REMOVED. Nothing was removed; index 2
15
+ // was "Custom Links" before and "System Information" after. The operator had to hand-diff the
16
+ // pre and post images to prove the alarm was empty — on the one check whose whole job is to be
17
+ // trustworthy about silent loss.
18
+ //
19
+ // Matching by identity also makes the check STRONGER, not just quieter: a section genuinely
20
+ // deleted from the middle used to be masked by the same shift.
21
+ const NAME_INDIVIDUALLY_UP_TO = 6;
22
+ const IDENTITY_KEYS = ["fullName", "name", "label", "field", "relatedList", "apiName"];
23
+ // PARTIALLY keyed is the normal case, not the exception, and requiring every element to carry the
24
+ // key threw away the whole mechanism whenever one did not. Live: a Layout's "Custom Links" section
25
+ // has NO label in the org's pre-image (the org materializes one only on the way back out), so a
26
+ // single unlabeled section sent the entire comparison back to positional matching and reproduced
27
+ // the exact index-shift false alarm identity matching was added to remove.
28
+ /**
29
+ * Which keys across a set of containers hold a COLLECTION rather than a scalar setting?
30
+ *
31
+ * Not answerable per value: the Metadata API collapses a single-element collection to a BARE
32
+ * OBJECT on read-back (the same quirk compareIntent normalizes, and that findRemovals already
33
+ * coerces for on the after-side), and generic types get no array normalization on the way in. So a
34
+ * Layout column holding exactly ONE layoutItem arrives as `layoutItems: {…}`, indistinguishable by
35
+ * shape from a scalar-valued setting.
36
+ *
37
+ * Two signals, unioned, and computed over BOTH sides together so the two merges cannot classify
38
+ * the same key differently — which would compare an array against an object and report nothing:
39
+ * - it is an array on at least one container anywhere, or
40
+ * - it appears on more than one container (a per-container SETTING would too, but multiplying a
41
+ * repeated scalar into a list only ever makes this check noisier, never blinder).
42
+ */
43
+ function collectionKeysOf(containers) {
44
+ const keys = new Set();
45
+ const seen = new Map();
46
+ for (const el of containers) {
47
+ if (!isObj(el))
48
+ continue;
49
+ for (const [k, v] of Object.entries(el)) {
50
+ if (Array.isArray(v))
51
+ keys.add(k);
52
+ seen.set(k, (seen.get(k) ?? 0) + 1);
53
+ }
54
+ }
55
+ for (const [k, n] of seen)
56
+ if (n > 1)
57
+ keys.add(k);
58
+ return keys;
59
+ }
60
+ /**
61
+ * Collapse a list of anonymous container objects into one, concatenating their collections.
62
+ *
63
+ * Used only where the containers themselves have no identity, so which container a child sits in
64
+ * carries no meaning the removals check should assert on — only whether the child still EXISTS
65
+ * somewhere in the collection.
66
+ *
67
+ * `collectionKeys` must come from BOTH sides via collectionKeysOf. An earlier version decided
68
+ * per value with `Array.isArray`, which silently DROPPED any collection the org had collapsed to a
69
+ * bare object — turning a real full-file-replace loss into a clean DIFF_VERIFIED. That is the exact
70
+ * 6.5.4 failure this module was written to close, so the classification is deliberately shared.
71
+ */
72
+ function mergeContainers(list, collectionKeys) {
73
+ const merged = {};
74
+ for (const el of list) {
75
+ if (!isObj(el))
76
+ continue;
77
+ for (const [k, v] of Object.entries(el)) {
78
+ if (collectionKeys.has(k)) {
79
+ const prior = merged[k];
80
+ const priorArr = prior === undefined ? [] : Array.isArray(prior) ? prior : [prior];
81
+ merged[k] = [...priorArr, ...(Array.isArray(v) ? v : [v])];
82
+ }
83
+ else if (merged[k] === undefined) {
84
+ merged[k] = v;
85
+ }
86
+ }
87
+ }
88
+ return merged;
89
+ }
90
+ function identityKey(before, after) {
91
+ for (const key of IDENTITY_KEYS) {
92
+ const read = (arr) => {
93
+ const vals = arr.filter(isObj).map((el) => el[key]).filter((v) => typeof v === "string" && v !== "");
94
+ // An EMPTY side cannot supply a key but must not veto one either — a wholesale wipe is the
95
+ // case where naming each casualty matters most, and the names are all on the other side.
96
+ if (arr.length === 0)
97
+ return true;
98
+ // Otherwise usable when the values that ARE present are unambiguous. One keyless element no
99
+ // longer disqualifies the rest — those are handled separately and conservatively below.
100
+ return vals.length > 0 && new Set(vals).size === vals.length;
101
+ };
102
+ if (read(before) && read(after))
103
+ return key;
104
+ }
105
+ return null;
106
+ }
107
+ export function findRemovals(pre, post, intent) {
108
+ if (!pre || !post)
109
+ return []; // a create has no pre-image — nothing can be lost
110
+ const out = [];
111
+ const statedRoots = new Set(Object.keys(intent ?? {}));
112
+ const walk = (before, after, path, root) => {
113
+ const stated = statedRoots.has(root);
114
+ if (Array.isArray(before)) {
115
+ const afterArr = Array.isArray(after) ? after : gone(after) ? [] : [after];
116
+ // A pure reorder is never a removal, whatever the shape.
117
+ if (sameMultiset(before, afterArr))
118
+ return;
119
+ const key = identityKey(before, afterArr);
120
+ if (key) {
121
+ // Identity-matched: report only elements that genuinely disappeared, and compare each
122
+ // survivor against ITS OWN counterpart rather than whatever now sits at its old index.
123
+ const byId = new Map();
124
+ for (const el of afterArr) {
125
+ if (!isObj(el))
126
+ continue;
127
+ const id = el[key];
128
+ if (typeof id === "string" && id !== "")
129
+ byId.set(id, el);
130
+ }
131
+ const missing = [];
132
+ for (const el of before) {
133
+ const id = isObj(el) ? el[key] : undefined;
134
+ if (typeof id !== "string" || id === "") {
135
+ // No identity, so there is no honest way to say WHICH element in `after` this became.
136
+ // Counting it as missing is how the false alarm was manufactured; the only defensible
137
+ // evidence for a keyless element is the anonymous population shrinking, checked below.
138
+ continue;
139
+ }
140
+ const match = byId.get(id);
141
+ if (match === undefined)
142
+ missing.push(id);
143
+ else
144
+ walk(el, match, `${path}[${key}=${id}]`, root);
145
+ }
146
+ // Compare KEYLESS against KEYLESS, not against the whole collection. Gating on the total
147
+ // shrinking conflates two populations: every named casualty is already reported
148
+ // individually below, so a collection that lost one named element and no anonymous ones
149
+ // fired this line as well — double-reporting, and blocking DIFF_VERIFIED on the strength of
150
+ // a count nobody can act on.
151
+ //
152
+ // It also manufactured a fresh false positive once columns merge (CF63 w4's shape): a
153
+ // Layout's pooled layoutItems legitimately contain `emptySpace` padding cells, which carry
154
+ // no `field` and so land here. A field moving column while a spacer disappeared read as
155
+ // content loss. Padding is not content — a cell that states nothing but its own emptiness
156
+ // cannot be something the org lost.
157
+ const anonymous = (list) => list.filter((el) => {
158
+ const id = isObj(el) ? el[key] : undefined;
159
+ if (typeof id === "string" && id !== "")
160
+ return false;
161
+ return !(isObj(el) && el.emptySpace !== undefined);
162
+ }).length;
163
+ const keylessBefore = anonymous(before);
164
+ const keylessAfter = anonymous(afterArr);
165
+ if (keylessBefore > keylessAfter) {
166
+ out.push({ path, detail: `${keylessBefore} → ${keylessAfter} unnamed item(s)`, stated });
167
+ }
168
+ // Name them individually — that is the whole point, and "2 → 0 item(s)" used to send the
169
+ // reader to the pre-image to find out WHAT died. Past a handful it inverts: a wholesale
170
+ // wipe (a Profile losing 400 fieldPermissions) would bury every other finding, so collapse.
171
+ if (missing.length > NAME_INDIVIDUALLY_UP_TO) {
172
+ out.push({ path, detail: `${before.length} → ${afterArr.length} item(s), including ${missing.slice(0, 3).join(", ")}…`, stated });
173
+ }
174
+ else {
175
+ for (const id of missing)
176
+ out.push({ path: `${path}[${key}=${id}]`, detail: "present → absent", stated });
177
+ }
178
+ return;
179
+ }
180
+ // ANONYMOUS CONTAINERS. The elements are objects but carry no identity of their own, and they
181
+ // hold the collections that DO — `Layout.layoutColumns` is the canonical case: a column has no
182
+ // name, only `layoutItems`. Position is not identity here either (the same reason stated at
183
+ // the top of this file), so comparing column 0 to column 0 reports a field that merely MOVED
184
+ // COLUMN as a loss.
185
+ //
186
+ // Live-caught CF63 w4: the supervisor moved CF63_Due_Date__c and CF63_Requester_Email__c
187
+ // between columns of the "Intake Details" section. This reported both as CONTENT REMOVED
188
+ // while compareIntent — in the SAME response — correctly scored them `orderOnly: 2`. Two
189
+ // subsystems disagreeing about the same two fields, and the wrong one is the one that blocks
190
+ // DIFF_VERIFIED. It cost a hand-read of the post-deploy layout to prove nothing was lost.
191
+ //
192
+ // Merging the containers and comparing the contents is also STRICTLY STRONGER than per-index:
193
+ // a field deleted outright is absent from EVERY column, so it still reports — while a field
194
+ // that simply moved no longer does.
195
+ // The gate asks collectionKeysOf rather than testing for an array directly, for the same
196
+ // single-element-collapse reason: a section whose columns each hold exactly ONE layoutItem
197
+ // has no array anywhere, and an Array.isArray gate would drop it back to positional matching
198
+ // — reproducing the very bug this branch exists to fix, on the narrowest layouts.
199
+ const collectionKeys = collectionKeysOf([...before, ...afterArr]);
200
+ const anonymousContainers = before.length > 0 && before.every(isObj) && afterArr.every(isObj) && collectionKeys.size > 0;
201
+ if (anonymousContainers) {
202
+ walk(mergeContainers(before, collectionKeys), mergeContainers(afterArr, collectionKeys), path, root);
203
+ return;
204
+ }
205
+ // No usable identity (scalars, or a key that repeats): fall back to counting, then to
206
+ // positional comparison — the best available answer for a collection with nothing to key on.
207
+ if (afterArr.length < before.length) {
208
+ out.push({ path, detail: `${before.length} → ${afterArr.length} item(s)`, stated });
209
+ return;
210
+ }
211
+ for (let i = 0; i < before.length; i += 1)
212
+ walk(before[i], afterArr[i], `${path}[${i}]`, root);
213
+ return;
214
+ }
215
+ if (isObj(before)) {
216
+ if (!isObj(after)) {
217
+ if (gone(after))
218
+ out.push({ path, detail: "present → absent", stated });
219
+ return;
220
+ }
221
+ for (const [k, v] of Object.entries(before))
222
+ walk(v, after[k], `${path}.${k}`, root);
223
+ return;
224
+ }
225
+ // Scalar leaf: only a disappearance counts. A CHANGED value is ordinary drift and is
226
+ // already covered by the intent check + three-way diff; re-reporting it here would drown
227
+ // the signal this exists for.
228
+ if (!gone(before) && before !== "" && gone(after)) {
229
+ out.push({ path, detail: `${JSON.stringify(before).slice(0, 48)} → absent`, stated });
230
+ }
231
+ };
232
+ for (const [k, v] of Object.entries(pre))
233
+ walk(v, post[k], `metadata.${k}`, k);
234
+ return out;
235
+ }
236
+ export function renderRemovals(component, removals) {
237
+ const shown = removals.slice(0, 8).map((r) => `${r.path} (${r.detail})${r.stated ? "" : " [UNSTATED]"}`);
238
+ const more = removals.length > shown.length ? ` …and ${removals.length - shown.length} more` : "";
239
+ return `${component}: ${shown.join(", ")}${more}`;
240
+ }
241
+ /**
242
+ * Drop the two removal classes that are artifacts of comparing an ORG READ-BACK against PARSED
243
+ * SOURCE XML — the pre-flight comparison only. The post-deploy check compares two read-backs, where
244
+ * neither class can arise.
245
+ *
246
+ * Measured on CF78: 5 of 8 pre-flight findings were one of these. On the w4 RE-APPLY — after the
247
+ * supervisor had correctly carried every real facet forward — the check fired twice and BOTH were
248
+ * false, which is what forced an ack of the whole rule. Acks are per-rule, so that ack would also
249
+ * have waved through a genuine drop introduced in the same wave. A check that is usually wrong does
250
+ * not merely waste attention; it actively disarms itself.
251
+ *
252
+ * fullName — path-derived in source format, so it is NEVER in an authored file. It fired on every
253
+ * pre-flight of every existing component: a 100% false-positive rate by construction.
254
+ * default-valued scalars — `false` and `""` are what the platform stores when an element is
255
+ * omitted, so omitting them changes nothing. compareIntent already treats this exact
256
+ * class as agreement; this brings the removals check into line.
257
+ */
258
+ export function isSourceFormatArtifact(r) {
259
+ if (r.path === "metadata.fullName")
260
+ return true;
261
+ return /^(false|"")\s*→ absent$/.test(r.detail);
262
+ }