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.
- package/README.md +369 -0
- package/agents/plan-requirement.md +32 -0
- package/dist/campfire/configItem.js +201 -0
- package/dist/campfire/configItemDescription.js +379 -0
- package/dist/campfire/deriveFromBundle.js +1013 -0
- package/dist/cli.js +465 -0
- package/dist/core/betaFeedback.js +493 -0
- package/dist/core/chapterInstructions.js +473 -0
- package/dist/core/claudeAssets.js +61 -0
- package/dist/core/gate.js +201 -0
- package/dist/core/ids.js +9 -0
- package/dist/core/install.js +60 -0
- package/dist/core/operatorQa.js +462 -0
- package/dist/core/orgCallMeter.js +178 -0
- package/dist/core/parcels.js +1235 -0
- package/dist/core/paths.js +105 -0
- package/dist/core/phases.js +225 -0
- package/dist/core/planFile.js +251 -0
- package/dist/core/planFragments.js +119 -0
- package/dist/core/readsLog.js +398 -0
- package/dist/core/registration.js +24 -0
- package/dist/core/result.js +38 -0
- package/dist/core/resume.js +233 -0
- package/dist/core/revisionChain.js +351 -0
- package/dist/core/richText.js +61 -0
- package/dist/core/semanticHash.js +89 -0
- package/dist/core/sessionOrg.js +60 -0
- package/dist/core/sfQuery.js +19 -0
- package/dist/core/spawn.js +182 -0
- package/dist/core/supervisorContract.js +230 -0
- package/dist/core/telemetry.js +1297 -0
- package/dist/core/telemetryContext.js +154 -0
- package/dist/core/telemetrySpool.js +244 -0
- package/dist/core/transcriptUpload.js +516 -0
- package/dist/core/update.js +363 -0
- package/dist/core/viewer.js +117 -0
- package/dist/core/workspace.js +92 -0
- package/dist/core/writeGrants.js +93 -0
- package/dist/core/writebackManifest.js +92 -0
- package/dist/direct/applyLoop.js +2516 -0
- package/dist/direct/batchReport.js +510 -0
- package/dist/direct/comparatorNoise.js +75 -0
- package/dist/direct/containers.js +85 -0
- package/dist/direct/contamination.js +221 -0
- package/dist/direct/crud.js +56 -0
- package/dist/direct/destructive.js +168 -0
- package/dist/direct/knownTokens.js +94 -0
- package/dist/direct/lint.js +382 -0
- package/dist/direct/metadataSchema.generated.json +1 -0
- package/dist/direct/metadataSchema.js +179 -0
- package/dist/direct/operatorPage.js +349 -0
- package/dist/direct/orgRepo.js +1531 -0
- package/dist/direct/orgRepoSpawnCount.js +21 -0
- package/dist/direct/plannedScope.js +119 -0
- package/dist/direct/preImageTargets.js +63 -0
- package/dist/direct/refVerify.js +191 -0
- package/dist/direct/registry.js +129 -0
- package/dist/direct/removals.js +262 -0
- package/dist/direct/rollback.js +1513 -0
- package/dist/direct/rollbackRun.js +1360 -0
- package/dist/direct/sessionProjection.js +126 -0
- package/dist/direct/snapshotComparability.js +165 -0
- package/dist/direct/sourceTree.js +113 -0
- package/dist/direct/unstatedChanges.js +106 -0
- package/dist/direct/waveAssembly.js +556 -0
- package/dist/direct/waveCounts.js +19 -0
- package/dist/direct/waveDerivation.js +152 -0
- package/dist/direct/waveGrade.js +185 -0
- package/dist/index.js +90 -0
- package/dist/orgread/customFieldSnapshot.js +48 -0
- package/dist/orgread/fieldMetadata.js +178 -0
- package/dist/orgread/genericSnapshot.js +61 -0
- package/dist/orgread/layoutSnapshot.js +151 -0
- package/dist/orgread/salesforceOrgReader.js +184 -0
- package/dist/orgread/snapshotTypes.js +9 -0
- package/dist/projection/bridge.js +26 -0
- package/dist/projection/diff.js +141 -0
- package/dist/projection/mapmd.js +213 -0
- package/dist/projection/nameAmbiguity.js +106 -0
- package/dist/projection/precedent.js +136 -0
- package/dist/projection/project.js +214 -0
- package/dist/projection/scope.js +110 -0
- package/dist/projection/types.js +5 -0
- package/dist/tools/analyze.js +322 -0
- package/dist/tools/auth.js +915 -0
- package/dist/tools/campfire.js +1742 -0
- package/dist/tools/closeBatch.js +154 -0
- package/dist/tools/closeText.js +35 -0
- package/dist/tools/direct.js +1613 -0
- package/dist/tools/feedback.js +318 -0
- package/dist/tools/operator.js +287 -0
- package/dist/tools/org.js +739 -0
- package/dist/tools/phase.js +249 -0
- package/dist/tools/projection.js +172 -0
- package/dist/tools/session.js +313 -0
- package/dist/tools/update.js +82 -0
- package/dist/tools/waves.js +793 -0
- package/package.json +48 -0
- package/reference/campfire-configuration-items-guide.md +710 -0
- package/reference/deployability-cribs.md +119 -0
- package/reference/operator-voice.md +141 -0
- package/reference/org-read-cribs.md +34 -0
- package/reference/scoped-dml-interim.md +68 -0
- package/reference/supervisor-playbook.md +313 -0
- package/reference/templates.md +233 -0
- package/skills/in2-delivery/SKILL.md +322 -0
- package/skills/in2-delivery/references/campfire-configuration-items-guide.md +710 -0
- package/skills/in2-delivery/references/deployability-cribs.md +119 -0
- package/skills/in2-delivery/references/operator-voice.md +141 -0
- package/skills/in2-delivery/references/org-read-cribs.md +34 -0
- package/skills/in2-delivery/references/scoped-dml-interim.md +68 -0
- package/skills/in2-delivery/references/templates.md +233 -0
- package/skills/plan-requirement/SKILL.md +188 -0
- package/viewer/index.html +899 -0
- 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
|
+
}
|