@atlassian/chippy 0.0.0-stage → 0.0.1-beta.2

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 (133) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +276 -2
  3. package/bin/chippy.mjs +92 -0
  4. package/bin/pack.mjs +204 -0
  5. package/bin/skill-dir.mjs +15 -0
  6. package/lib/avalanche.js +2601 -0
  7. package/lib/avalanche.js.map +7 -0
  8. package/lib/bootstrap.d.ts +1 -0
  9. package/lib/bootstrap.js +70 -0
  10. package/lib/bootstrap.js.map +7 -0
  11. package/lib/browser.js +15 -0
  12. package/lib/browser.js.map +7 -0
  13. package/lib/extension/avalanche.es.js +5535 -0
  14. package/lib/extension/avalanche.es.js.map +1 -0
  15. package/lib/extension/chippy-page-bridge.js +1953 -0
  16. package/lib/extension/inspect-react.js +216 -0
  17. package/lib/extension/layout-lab.js +1453 -0
  18. package/lib/extension/panel-dom.js +90 -0
  19. package/lib/extension/resolve-probes.js +342 -0
  20. package/lib/mcp-skill/LICENSE +201 -0
  21. package/lib/mcp-skill/README.md +148 -0
  22. package/lib/mcp-skill/SKILL.md +335 -0
  23. package/lib/mcp-skill/browser-scripts/playwright-probes.js +368 -0
  24. package/lib/mcp-skill/browser-scripts/playwright-session.js +114 -0
  25. package/lib/mcp-skill/extension/armed-reload-startup.js +83 -0
  26. package/lib/mcp-skill/extension/avalanche.es.js +5535 -0
  27. package/lib/mcp-skill/extension/avalanche.es.js.map +1 -0
  28. package/lib/mcp-skill/extension/avalanche.iife.js +5536 -0
  29. package/lib/mcp-skill/extension/avalanche.iife.js.map +1 -0
  30. package/lib/mcp-skill/extension/background.js +325 -0
  31. package/lib/mcp-skill/extension/chippy-page-bridge.js +1953 -0
  32. package/lib/mcp-skill/extension/content-script.js +190 -0
  33. package/lib/mcp-skill/extension/devtools-hook-shim.js +85 -0
  34. package/lib/mcp-skill/extension/early-bootstrap.js +49 -0
  35. package/lib/mcp-skill/extension/icon-128.png +0 -0
  36. package/lib/mcp-skill/extension/icon-16.png +0 -0
  37. package/lib/mcp-skill/extension/icon-32.png +0 -0
  38. package/lib/mcp-skill/extension/icon-48.png +0 -0
  39. package/lib/mcp-skill/extension/inspect-react.js +216 -0
  40. package/lib/mcp-skill/extension/layout-lab.js +1453 -0
  41. package/lib/mcp-skill/extension/logo.svg +14 -0
  42. package/lib/mcp-skill/extension/manifest.json +69 -0
  43. package/lib/mcp-skill/extension/panel-dom.js +90 -0
  44. package/lib/mcp-skill/extension/panel-settings.js +173 -0
  45. package/lib/mcp-skill/extension/popup.html +41 -0
  46. package/lib/mcp-skill/extension/popup.js +80 -0
  47. package/lib/mcp-skill/extension/probe-descriptor.js +241 -0
  48. package/lib/mcp-skill/extension/resolve-probes.js +342 -0
  49. package/lib/mcp-skill/extension/startup-handoff.js +30 -0
  50. package/lib/mcp-skill/package.json +1 -0
  51. package/lib/mcp-skill/references/README.md +56 -0
  52. package/lib/mcp-skill/references/active-stimulus.md +85 -0
  53. package/lib/mcp-skill/references/avalanche-detector.md +731 -0
  54. package/lib/mcp-skill/references/bdd-router.md +310 -0
  55. package/lib/mcp-skill/references/browser-runtime-api.md +193 -0
  56. package/lib/mcp-skill/references/command-output-formats.md +1085 -0
  57. package/lib/mcp-skill/references/command-surface.md +183 -0
  58. package/lib/mcp-skill/references/cynefin.md +99 -0
  59. package/lib/mcp-skill/references/debug-hook.md +161 -0
  60. package/lib/mcp-skill/references/fixing-performance.md +490 -0
  61. package/lib/mcp-skill/references/hook-metrics.md +199 -0
  62. package/lib/mcp-skill/references/hooks-inspection.md +61 -0
  63. package/lib/mcp-skill/references/jira-taxonomy.md +81 -0
  64. package/lib/mcp-skill/references/mcp.md +122 -0
  65. package/lib/mcp-skill/references/mode-a-injection.md +102 -0
  66. package/lib/mcp-skill/references/operating-model.md +128 -0
  67. package/lib/mcp-skill/references/playwright-cli-setup.md +105 -0
  68. package/lib/mcp-skill/references/playwright-probes.md +205 -0
  69. package/lib/mcp-skill/references/procedures.md +545 -0
  70. package/lib/mcp-skill/references/recipes.txt +114 -0
  71. package/lib/mcp-skill/references/stability-render.md +428 -0
  72. package/lib/mcp-skill/references/test-mode-playbook.md +194 -0
  73. package/lib/mcp-skill/references/troubleshooting.md +59 -0
  74. package/lib/mcp-skill/scripts/avalanche-analyze.cjs +495 -0
  75. package/lib/mcp-skill/scripts/build-avalanche-command.js +34 -0
  76. package/lib/mcp-skill/scripts/build-fiber-state-command.js +19 -0
  77. package/lib/mcp-skill/scripts/chippy-helper-server.js +1768 -0
  78. package/lib/mcp-skill/scripts/chippy-mcp.cjs +273 -0
  79. package/lib/mcp-skill/scripts/chippy.sh +1252 -0
  80. package/lib/mcp-skill/scripts/format-probe-result.js +34 -0
  81. package/lib/mcp-skill/scripts/helper-access.js +158 -0
  82. package/lib/mcp-skill/scripts/helper-proxy.js +87 -0
  83. package/lib/mcp-skill/scripts/page-socket-trust.js +81 -0
  84. package/lib/mcp-skill/scripts/probe-annotation-channel.js +116 -0
  85. package/lib/mcp-skill/scripts/search-recipes.js +248 -0
  86. package/lib/mcp-skill/snippets/README.md +89 -0
  87. package/lib/mcp-skill/snippets/count-context-consumers.js +40 -0
  88. package/lib/mcp-skill/snippets/find-context-providers.js +38 -0
  89. package/lib/mcp-skill/snippets/find-legacy-context.js +47 -0
  90. package/lib/mcp-skill/snippets/find-prop.js +83 -0
  91. package/lib/mcp-skill/snippets/hook-metrics-sample.js +69 -0
  92. package/lib/mcp-skill/snippets/hook-metrics.js +60 -0
  93. package/lib/mcp-skill/snippets/hook-tracker.js +198 -0
  94. package/lib/mcp-skill/snippets/stability-nudge.js +101 -0
  95. package/lib/package.json +1 -0
  96. package/lib/plugin.js +119 -0
  97. package/lib/plugin.js.map +7 -0
  98. package/lib/storybook/avalanche-panel-model.js +79 -0
  99. package/lib/storybook/brand.jsx +6 -0
  100. package/lib/storybook/connection-fallback.jsx +47 -0
  101. package/lib/storybook/constants.js +15 -0
  102. package/lib/storybook/context-card.jsx +171 -0
  103. package/lib/storybook/manager-owner.js +42 -0
  104. package/lib/storybook/manager.jsx +425 -0
  105. package/lib/storybook/preset.js +7 -0
  106. package/lib/storybook/preview.js +298 -0
  107. package/lib/storybook/probe-annotation-editor.jsx +52 -0
  108. package/lib/storybook/probe-intent.js +23 -0
  109. package/lib/storybook/setup.js +104 -0
  110. package/lib/types/avalanche/commit-metrics-adapter.d.ts +76 -0
  111. package/lib/types/avalanche/commit-stats-indices.d.ts +41 -0
  112. package/lib/types/avalanche/commit-surface-ownership.d.ts +9 -0
  113. package/lib/types/avalanche/detector.d.ts +183 -0
  114. package/lib/types/avalanche/interaction-capture.d.ts +25 -0
  115. package/lib/types/avalanche/internals.d.ts +189 -0
  116. package/lib/types/avalanche/report-serializer.d.ts +283 -0
  117. package/lib/types/avalanche/reporter.d.ts +610 -0
  118. package/lib/types/chippy-bridge/fiber-source.d.ts +48 -0
  119. package/lib/types/chippy-bridge/react-renderer.d.ts +74 -0
  120. package/lib/types/chippy-bridge/recorded-source.d.ts +44 -0
  121. package/lib/types/chippy-bridge/source-map.d.ts +30 -0
  122. package/lib/types/debug-render/core.d.ts +78 -0
  123. package/lib/types/layout-lab/fiber-boundaries.d.ts +51 -0
  124. package/lib/types/layout-lab/fiber-walk.d.ts +131 -0
  125. package/lib/types/layout-lab/marker-context.d.ts +94 -0
  126. package/lib/types/package/avalanche.d.ts +6 -0
  127. package/lib/types/package/browser.d.ts +10 -0
  128. package/lib/types/package/plugin.d.ts +15 -0
  129. package/lib/types/plugins/atl/commit-metrics-adapter.d.ts +65 -0
  130. package/lib/types/plugins/atl/metrics-slot.d.ts +37 -0
  131. package/lib/types/plugins/index.d.ts +49 -0
  132. package/lib/types/plugins/jira/context-provider.d.ts +36 -0
  133. package/package.json +106 -4
@@ -0,0 +1,241 @@
1
+ /* eslint-disable */
2
+ (function installProbeDescriptor(global) {
3
+ const chippy = global.__CHIPPY = global.__CHIPPY || {};
4
+
5
+ function normalizeText(value, max = 200) {
6
+ return String(value || '').replace(/\s+/g, ' ').trim().slice(0, max) || null;
7
+ }
8
+
9
+ function stableAttrs(node) {
10
+ if (!node || node.nodeType !== 1) return {};
11
+ const attrs = {};
12
+ const names = ['data-testid', 'role', 'aria-label', 'name', 'id', 'title', 'href', 'type'];
13
+ for (const name of names) {
14
+ const value = node.getAttribute?.(name);
15
+ if (value) attrs[name] = value;
16
+ }
17
+ return attrs;
18
+ }
19
+
20
+ function siblingIndex(node) {
21
+ if (!node?.parentElement) return 0;
22
+ const tag = node.tagName;
23
+ let index = 0;
24
+ for (const sibling of node.parentElement.children) {
25
+ if (sibling === node) return index;
26
+ if (sibling.tagName === tag) index += 1;
27
+ }
28
+ return index;
29
+ }
30
+
31
+ function ownerNames(fiber, max = 8) {
32
+ const names = [];
33
+ let owner = fiber?._debugOwner || null;
34
+ while (owner && names.length < max) {
35
+ const name = chippy.getDisplayName?.(owner.type) || owner.type?.displayName || owner.type?.name || null;
36
+ if (name) names.push(name);
37
+ owner = owner._debugOwner || null;
38
+ }
39
+ return names;
40
+ }
41
+
42
+ function describeSource(source) {
43
+ if (!source?.fileName) return null;
44
+ return {
45
+ fileName: source.fileName,
46
+ lineNumber: Number(source.lineNumber || 0) || null,
47
+ columnNumber: Number(source.columnNumber || 0) || null,
48
+ };
49
+ }
50
+
51
+ function getNearestTestId(node) {
52
+ return chippy.getNearestTestIdParent?.(node) || null;
53
+ }
54
+
55
+ function captureProbeDescriptor(target) {
56
+ const node = target?.nodeType === 1 ? target : null;
57
+ if (!node) return { ok: false, error: 'Target element is required' };
58
+
59
+ const inspection = chippy.inspectNode ? chippy.inspectNode(node) : null;
60
+ const component = inspection?.component || null;
61
+ const fiber = chippy.getClosestFiber ? chippy.getClosestFiber(node) : null;
62
+ const source = component?.source || describeSource(fiber?._debugSource || fiber?.type?._debugSource || null);
63
+ const descriptor = {
64
+ dom: {
65
+ tag: node.tagName?.toLowerCase() || null,
66
+ attrs: stableAttrs(node),
67
+ text: normalizeText(node.innerText || node.textContent || ''),
68
+ nearestTestId: getNearestTestId(node),
69
+ siblingIndex: siblingIndex(node),
70
+ },
71
+ component: {
72
+ name: component?.name || chippy.getDisplayName?.(fiber?.type) || fiber?.type?.displayName || fiber?.type?.name || null,
73
+ source,
74
+ ownerNames: ownerNames(fiber),
75
+ },
76
+ };
77
+ const hasIdentity = !!(
78
+ descriptor.dom.nearestTestId
79
+ || descriptor.component.name
80
+ || descriptor.component.source?.fileName
81
+ || Object.keys(descriptor.dom.attrs).length
82
+ || descriptor.dom.text
83
+ );
84
+ if (!hasIdentity) {
85
+ return { ok: false, error: 'Target does not expose stable identity yet', descriptor };
86
+ }
87
+ return { ok: true, descriptor };
88
+ }
89
+
90
+ function sourceKey(source) {
91
+ if (!source?.fileName) return null;
92
+ return [source.fileName, source.lineNumber || '', source.columnNumber || ''].join(':');
93
+ }
94
+
95
+ function elementFromFiber(fiber) {
96
+ const node = fiber?.stateNode;
97
+ return node && node.nodeType === 1 ? node : null;
98
+ }
99
+
100
+ function candidateFromNode(node, providedFiber) {
101
+ const fiber = providedFiber || (chippy.getClosestFiber ? chippy.getClosestFiber(node) : null);
102
+ const inspection = chippy.inspectNode ? chippy.inspectNode(node) : null;
103
+ const component = inspection?.component || null;
104
+ return {
105
+ node,
106
+ dom: {
107
+ tag: node.tagName?.toLowerCase() || null,
108
+ attrs: stableAttrs(node),
109
+ text: normalizeText(node.innerText || node.textContent || ''),
110
+ nearestTestId: getNearestTestId(node),
111
+ siblingIndex: siblingIndex(node),
112
+ },
113
+ component: {
114
+ name: component?.name || chippy.getDisplayName?.(fiber?.type) || fiber?.type?.displayName || fiber?.type?.name || null,
115
+ source: component?.source || describeSource(fiber?._debugSource || fiber?.type?._debugSource || null),
116
+ ownerNames: ownerNames(fiber),
117
+ },
118
+ fiber,
119
+ };
120
+ }
121
+
122
+ function getFiberCandidates(descriptor, limit = 250) {
123
+ const findFibers = chippy.pageBridge?.helpers?.findFibers;
124
+ if (typeof findFibers !== 'function') return [];
125
+ const seen = new Set();
126
+ const fibers = findFibers((fiber) => {
127
+ const node = elementFromFiber(fiber);
128
+ if (!node) return false;
129
+ const name = chippy.getDisplayName?.(fiber?.type) || fiber?.type?.displayName || fiber?.type?.name || null;
130
+ const source = describeSource(fiber?._debugSource || fiber?.type?._debugSource || null);
131
+ const testId = getNearestTestId(node);
132
+ if (descriptor.component.source?.fileName && sourceKey(source) === sourceKey(descriptor.component.source)) return true;
133
+ if (descriptor.component.name && name === descriptor.component.name) return true;
134
+ if (descriptor.dom.nearestTestId && testId === descriptor.dom.nearestTestId) return true;
135
+ if (descriptor.dom.text) {
136
+ const text = normalizeText(node.innerText || node.textContent || '');
137
+ if (text && (text.includes(descriptor.dom.text) || descriptor.dom.text.includes(text))) return true;
138
+ }
139
+ return false;
140
+ }, { max: limit });
141
+ for (const fiber of fibers) {
142
+ const node = elementFromFiber(fiber);
143
+ if (!node || seen.has(node)) continue;
144
+ seen.add(node);
145
+ }
146
+ return Array.from(seen).map((node) => candidateFromNode(node));
147
+ }
148
+
149
+ function getDomCandidates(root) {
150
+ return Array.from(root.querySelectorAll('*')).map((node) => candidateFromNode(node));
151
+ }
152
+
153
+ function scoreCandidate(descriptor, candidate) {
154
+ const matches = {};
155
+ let strict = true;
156
+ let score = 0;
157
+
158
+ function match(name, condition, weight, strictField = false) {
159
+ matches[name] = !!condition;
160
+ if (condition) score += weight;
161
+ if (strictField && !condition) strict = false;
162
+ }
163
+
164
+ match('nearestTestId', !descriptor.dom.nearestTestId || descriptor.dom.nearestTestId === candidate.dom.nearestTestId, 5, !!descriptor.dom.nearestTestId);
165
+ match('componentName', !descriptor.component.name || descriptor.component.name === candidate.component.name, 4, !!descriptor.component.name);
166
+ match('componentSource', !sourceKey(descriptor.component.source) || sourceKey(descriptor.component.source) === sourceKey(candidate.component.source), 6, !!sourceKey(descriptor.component.source));
167
+ match('tag', !descriptor.dom.tag || descriptor.dom.tag === candidate.dom.tag, 2);
168
+
169
+ const textMatch = descriptor.dom.text && candidate.dom.text
170
+ ? candidate.dom.text.includes(descriptor.dom.text) || descriptor.dom.text.includes(candidate.dom.text)
171
+ : !descriptor.dom.text;
172
+ match('text', textMatch, 2);
173
+
174
+ const ownerNames = descriptor.component.ownerNames || [];
175
+ const candidateOwners = candidate.component.ownerNames || [];
176
+ const ownerOverlap = ownerNames.length === 0 || ownerNames.every((name) => candidateOwners.includes(name));
177
+ match('ownerNames', ownerOverlap, 3);
178
+
179
+ const attrEntries = Object.entries(descriptor.dom.attrs || {});
180
+ const attrsMatch = attrEntries.every(([name, value]) => candidate.dom.attrs?.[name] === value);
181
+ match('attrs', attrsMatch, Math.min(4, attrEntries.length || 1));
182
+
183
+ match('siblingIndex', descriptor.dom.siblingIndex == null || descriptor.dom.siblingIndex === candidate.dom.siblingIndex, 1);
184
+
185
+ return { strict, score, matches };
186
+ }
187
+
188
+ function resolveProbeDescriptor(descriptor, options = {}) {
189
+ if (!descriptor?.dom || !descriptor?.component) return { ok: false, error: 'Descriptor is required' };
190
+ const root = options.root || document;
191
+ const preferFiber = options.preferFiber !== false;
192
+ const candidates = [];
193
+ const seenKeys = new Set();
194
+ const pools = [];
195
+ if (preferFiber) pools.push({ kind: 'fiber', items: getFiberCandidates(descriptor, Number(options.fiberLimit || 250)) });
196
+ pools.push({ kind: 'dom', items: getDomCandidates(root) });
197
+
198
+ for (const pool of pools) {
199
+ for (const candidate of pool.items) {
200
+ const key = [candidate.component.name || '', sourceKey(candidate.component.source) || '', candidate.dom.nearestTestId || '', candidate.dom.tag || '', candidate.dom.text || '', candidate.dom.siblingIndex ?? ''].join('||');
201
+ if (seenKeys.has(key)) continue;
202
+ seenKeys.add(key);
203
+ const evaluation = scoreCandidate(descriptor, candidate);
204
+ if (!evaluation.strict && evaluation.score < 5) continue;
205
+ candidates.push({
206
+ score: evaluation.score + (pool.kind === 'fiber' ? 2 : 0),
207
+ strict: evaluation.strict,
208
+ matches: evaluation.matches,
209
+ dom: candidate.dom,
210
+ component: candidate.component,
211
+ node: candidate.node,
212
+ fiber: candidate.fiber,
213
+ candidateKind: pool.kind,
214
+ });
215
+ }
216
+ }
217
+ candidates.sort((a, b) => {
218
+ if (a.strict !== b.strict) return a.strict ? -1 : 1;
219
+ if (a.score !== b.score) return b.score - a.score;
220
+ if (a.candidateKind !== b.candidateKind) return a.candidateKind === 'fiber' ? -1 : 1;
221
+ return 0;
222
+ });
223
+ const best = candidates[0] || null;
224
+ return {
225
+ ok: !!best,
226
+ best,
227
+ candidates: candidates.slice(0, 5).map((candidate) => ({
228
+ score: candidate.score,
229
+ strict: candidate.strict,
230
+ matches: candidate.matches,
231
+ dom: candidate.dom,
232
+ component: candidate.component,
233
+ candidateKind: candidate.candidateKind,
234
+ })),
235
+ error: best ? null : 'No matching element found',
236
+ };
237
+ }
238
+
239
+ chippy.captureProbeDescriptor = captureProbeDescriptor;
240
+ chippy.resolveProbeDescriptor = resolveProbeDescriptor;
241
+ })(globalThis);
@@ -0,0 +1,342 @@
1
+ /* eslint-disable */
2
+ (function installResolveProbes(global) {
3
+ const chippy = global.__CHIPPY = global.__CHIPPY || {};
4
+ // Always reinstall — guards caused stale code to win after extension reload.
5
+
6
+ // ─── Source confidence ────────────────────────────────────────────────────
7
+ // In a dev build React <=18 exposes _debugSource; React 19 exposes
8
+ // _debugStack, which the bundled resolver turns into a source location.
9
+ // In a production build neither exists — we only have component function
10
+ // names from fiber.type, which may also be minified.
11
+ //
12
+ // Every resolved probe carries a `confidence` field:
13
+ // "dev" — React development source evidence is present
14
+ // "production" — no _debugSource, component names only (may be minified)
15
+ // "unknown" — could not determine anything useful
16
+
17
+ function sourceConfidence(source, owners) {
18
+ if (source?.fileName) return 'dev';
19
+ if (owners?.some((o) => o?.source?.fileName)) return 'dev';
20
+ return 'production';
21
+ }
22
+
23
+ function summarizeSource(source) {
24
+ if (!source) return null;
25
+ return {
26
+ fileName: source.fileName || null,
27
+ lineNumber: source.lineNumber || null,
28
+ columnNumber: source.columnNumber || null,
29
+ };
30
+ }
31
+
32
+ function summarizeChainEntry(entry) {
33
+ if (!entry) return null;
34
+ return {
35
+ name: entry.name || null,
36
+ key: entry.key ?? null,
37
+ source: summarizeSource(entry.source),
38
+ // props from layout-lab getOwnerChain/getReturnComponentChain are already key arrays
39
+ propNames: Array.isArray(entry.props)
40
+ ? entry.props.filter((k) => k !== 'children')
41
+ : (entry.props && typeof entry.props === 'object' ? listPropNames(entry.props) : []),
42
+ hookCount: entry.hookCount ?? null,
43
+ hookCountTruncated: entry.hookCountTruncated ?? false,
44
+ hookCountMax: entry.hookCountMax ?? null,
45
+ complexityNote: typeof entry.hookCount === 'number' && entry.hookCount >= 40
46
+ ? 'High enough hook count to inspect carefully before changing local logic.'
47
+ : null,
48
+ };
49
+ }
50
+
51
+ function summarizeProps(props) {
52
+ if (!props || typeof props !== 'object') return {};
53
+ return Object.entries(props)
54
+ .filter(([k]) => !['children', 'style', 'className'].includes(k))
55
+ .reduce((acc, [k, v]) => {
56
+ if (v == null) { acc[k] = v; return acc; }
57
+ if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') {
58
+ acc[k] = v; return acc;
59
+ }
60
+ if (Array.isArray(v)) { acc[k] = `[array:${v.length}]`; return acc; }
61
+ if (typeof v === 'function') { acc[k] = '[function]'; return acc; }
62
+ acc[k] = v?.id || v?.key || v?.name || '[object]';
63
+ return acc;
64
+ }, {});
65
+ }
66
+
67
+ // List all prop names (excluding children) for a props object — used for compact snapshot
68
+ function listPropNames(props) {
69
+ if (!props || typeof props !== 'object') return [];
70
+ return Object.keys(props).filter((k) => k !== 'children');
71
+ }
72
+
73
+ // ─── Component stack fallback for production ──────────────────────────────
74
+ // When _debugSource is absent we still try to provide a useful component
75
+ // stack by walking fiber.return. Names may be minified in production.
76
+
77
+ function getComponentStack(fiber) {
78
+ if (!fiber) return [];
79
+ const stack = [];
80
+ let current = fiber.return;
81
+ while (current) {
82
+ const name =
83
+ current.type?.displayName ||
84
+ current.type?.name ||
85
+ current.type?.render?.displayName ||
86
+ current.type?.render?.name ||
87
+ null;
88
+ if (name && typeof current.type !== 'string') {
89
+ stack.push({ name, key: current.key ?? null, source: null });
90
+ }
91
+ current = current.return;
92
+ }
93
+ return stack;
94
+ }
95
+
96
+ // ─── Full probe summary ───────────────────────────────────────────────────
97
+
98
+ function summarizeProbe(probe) {
99
+ const best = probe?.probe?.best;
100
+ return {
101
+ dom: {
102
+ tag: best?.exact?.domTag,
103
+ text: best?.exact?.domText,
104
+ fiberType: best?.exact?.fiberType,
105
+ source: best?.exact?.source,
106
+ },
107
+ component: {
108
+ name: best?.component?.name,
109
+ key: best?.component?.key ?? null,
110
+ props: best?.component?.props,
111
+ propNames: listPropNames(best?.component?.props),
112
+ hookCount: best?.component?.hookCount ?? null,
113
+ hookCountTruncated: best?.component?.hookCountTruncated ?? false,
114
+ hookCountMax: best?.component?.hookCountMax ?? null,
115
+ complexityNote: typeof best?.component?.hookCount === 'number' && best.component.hookCount >= 40
116
+ ? 'High enough hook count to inspect carefully before changing local logic.'
117
+ : null,
118
+ source: best?.component?.source,
119
+ owners: best?.component?.owners,
120
+ parents: best?.component?.parents,
121
+ },
122
+ };
123
+ }
124
+
125
+ // ─── Compact probe summary (used by bridge for publishing) ────────────────
126
+
127
+ function compactProbeSummary(probe) {
128
+ const best = probe?.probe?.best;
129
+
130
+ const componentSource = summarizeSource(best?.component?.source);
131
+ const owners = (best?.component?.owners || []).map(summarizeChainEntry);
132
+ const confidence = sourceConfidence(componentSource, owners);
133
+
134
+ // In production, owners from _debugOwner chain are absent.
135
+ // Fall back to the fiber return-chain component stack.
136
+ let resolvedOwners = owners;
137
+ let componentStack = null;
138
+ if (confidence === 'production') {
139
+ // Try to get the raw fiber for fallback stack
140
+ const rawFiber = probe?.probe?.rawFiber || null;
141
+ componentStack = getComponentStack(rawFiber);
142
+ }
143
+
144
+ // Matched CSS rules — rules from all sheets whose selector matches the target element
145
+ const targetEl = probe?.probe?._targetEl || null;
146
+ const inspectedTarget = targetEl ? chippy.inspectReact?.inspectNode?.(targetEl) : null;
147
+ const nearestTestId = inspectedTarget?.nearestTestIdParent?.testId || null;
148
+ const matchedStyles = [];
149
+ if (targetEl) {
150
+ try {
151
+ for (const sheet of document.styleSheets) {
152
+ let rules;
153
+ try { rules = sheet.cssRules; } catch { continue; }
154
+ for (const rule of rules) {
155
+ if (rule instanceof CSSStyleRule) {
156
+ try {
157
+ if (targetEl.matches(rule.selectorText)) {
158
+ matchedStyles.push({
159
+ selector: rule.selectorText,
160
+ declarations: rule.style.cssText,
161
+ source: sheet.href || 'inline',
162
+ });
163
+ }
164
+ } catch { /* invalid selector — skip */ }
165
+ }
166
+ }
167
+ }
168
+ } catch { /* sheet access failed */ }
169
+ }
170
+
171
+ // DOM parent chain — walk up to 10 parentElement levels from the target element
172
+ const domParents = [];
173
+ if (targetEl) {
174
+ let el = targetEl.parentElement;
175
+ while (el && domParents.length < 10) {
176
+ const cls = el.className && typeof el.className === 'string'
177
+ ? el.className.trim().split(/\s+/).slice(0, 3).join(' ')
178
+ : null;
179
+ domParents.push({
180
+ tag: el.tagName?.toLowerCase() || null,
181
+ id: el.id || null,
182
+ className: cls || null,
183
+ });
184
+ el = el.parentElement;
185
+ }
186
+ }
187
+
188
+ return {
189
+ nearestTestId,
190
+ confidence,
191
+ dom: {
192
+ tag: best?.exact?.domTag || null,
193
+ text: (best?.exact?.domText || '').slice(0, 80),
194
+ fiberType: best?.exact?.fiberType || null,
195
+ source: summarizeSource(best?.exact?.source),
196
+ parents: domParents,
197
+ matchedStyles,
198
+ },
199
+ component: {
200
+ name: best?.component?.name || null,
201
+ key: best?.component?.key ?? null,
202
+ propNames: listPropNames(best?.component?.props),
203
+ props: summarizeProps(best?.component?.props),
204
+ hookCount: best?.component?.hookCount ?? null,
205
+ hookCountTruncated: best?.component?.hookCountTruncated ?? false,
206
+ hookCountMax: best?.component?.hookCountMax ?? null,
207
+ complexityNote: typeof best?.component?.hookCount === 'number' && best.component.hookCount >= 40
208
+ ? 'High enough hook count to inspect carefully before changing local logic.'
209
+ : null,
210
+ source: componentSource,
211
+ owners: resolvedOwners,
212
+ // In production: source-free stack from fiber.return walk
213
+ componentStack: confidence !== 'dev' ? componentStack : null,
214
+ parents: (best?.component?.parents || []).map(summarizeChainEntry),
215
+ },
216
+ // Human-readable note about what is and isn't available
217
+ note: confidence === 'dev'
218
+ ? null
219
+ : confidence === 'production'
220
+ ? 'Production build: no React development source metadata is available. Component names may be minified. Source locations are not reliable. Use component stack and names only.'
221
+ : 'Could not determine build mode. Probe data may be incomplete.',
222
+ };
223
+ }
224
+
225
+ // ─── Cross-source correlation hint ───────────────────────────────────────
226
+ // When one probe is on a dev page and another is on a production page
227
+ // pointing at "around the same component", the agent can use this to
228
+ // try to match names across the two.
229
+
230
+
231
+ function componentIdentity(entry) {
232
+ if (!entry) return null;
233
+ const src = entry.source || null;
234
+ const srcKey = src && src.fileName ? `${src.fileName}:${src.lineNumber || 0}:${src.columnNumber || 0}` : '';
235
+ return `${entry.name || ''}|${entry.key ?? ''}|${srcKey}`;
236
+ }
237
+
238
+ function ancestryForSummary(summary) {
239
+ if (!summary?.component) return [];
240
+ const out = [];
241
+ out.push({
242
+ name: summary.component.name || null,
243
+ key: summary.component.key ?? null,
244
+ source: summary.component.source || null,
245
+ propNames: summary.component.propNames || [],
246
+ hookCount: summary.component.hookCount ?? null,
247
+ hookCountTruncated: summary.component.hookCountTruncated ?? false,
248
+ hookCountMax: summary.component.hookCountMax ?? null,
249
+ complexityNote: summary.component.complexityNote || null,
250
+ });
251
+ for (const parent of summary.component.parents || []) out.push(parent);
252
+ return out.filter(Boolean);
253
+ }
254
+
255
+ function findCommonParent(summaries) {
256
+ const ids = Object.keys(summaries).filter((id) => !id.startsWith('__') && summaries[id]?.component);
257
+ if (ids.length < 2) return null;
258
+ const chains = ids.map((id) => ancestryForSummary(summaries[id]));
259
+ if (chains.some((c) => c.length === 0)) return null;
260
+ const otherSets = chains.slice(1).map((chain) => new Set(chain.map(componentIdentity).filter(Boolean)));
261
+ for (const entry of chains[0]) {
262
+ const id = componentIdentity(entry);
263
+ if (!id) continue;
264
+ if (otherSets.every((set) => set.has(id))) {
265
+ return {
266
+ confidence: 'dev',
267
+ dom: null,
268
+ component: {
269
+ name: entry.name || null,
270
+ key: entry.key ?? null,
271
+ propNames: entry.propNames || [],
272
+ props: {},
273
+ hookCount: entry.hookCount ?? null,
274
+ hookCountTruncated: entry.hookCountTruncated ?? false,
275
+ hookCountMax: entry.hookCountMax ?? null,
276
+ complexityNote: entry.complexityNote || null,
277
+ source: entry.source || null,
278
+ owners: [],
279
+ componentStack: null,
280
+ parents: [],
281
+ },
282
+ note: `Nearest common parent for probes ${ids.join(', ')}.`,
283
+ };
284
+ }
285
+ }
286
+ return null;
287
+ }
288
+
289
+ function correlatePairs(summaries) {
290
+ const ids = Object.keys(summaries);
291
+ const devProbes = ids.filter((id) => summaries[id]?.confidence === 'dev');
292
+ const prodProbes = ids.filter((id) => summaries[id]?.confidence === 'production');
293
+ if (devProbes.length === 0 || prodProbes.length === 0) return null;
294
+
295
+ const hints = [];
296
+ for (const devId of devProbes) {
297
+ for (const prodId of prodProbes) {
298
+ const devName = summaries[devId]?.component?.name;
299
+ const prodName = summaries[prodId]?.component?.name;
300
+ hints.push({
301
+ devProbe: devId,
302
+ productionProbe: prodId,
303
+ devComponentName: devName || null,
304
+ productionComponentName: prodName || null,
305
+ note: devName && prodName && devName === prodName
306
+ ? `Component names match ("${devName}") — likely the same component.`
307
+ : devName && prodName
308
+ ? `Component names differ ("${devName}" vs "${prodName}") — may still be the same component if production is minified. Check component stack for structural similarity.`
309
+ : 'Could not compare — one or both probes have no component name.',
310
+ });
311
+ }
312
+ }
313
+ return hints.length > 0 ? hints : null;
314
+ }
315
+
316
+ // ─── Public API ───────────────────────────────────────────────────────────
317
+
318
+ function resolveProbes(...ids) {
319
+ const result = {};
320
+ for (const id of ids) {
321
+ const probe = chippy.layoutLab?.getMarkerProbe(id);
322
+ result[id] = probe ? summarizeProbe(probe) : null;
323
+ }
324
+ return result;
325
+ }
326
+
327
+ function resolveProbesCompact(...ids) {
328
+ const result = {};
329
+ for (const id of ids) {
330
+ const probe = chippy.layoutLab?.getMarkerProbe(id);
331
+ result[id] = probe ? compactProbeSummary(probe) : null;
332
+ }
333
+ const crossSourceHints = correlatePairs(result);
334
+ if (crossSourceHints) result.__crossSourceHints = crossSourceHints;
335
+ const commonParent = findCommonParent(result);
336
+ if (commonParent) result.__commonParent = commonParent;
337
+ return result;
338
+ }
339
+
340
+ chippy.resolveProbes = resolveProbes;
341
+ chippy.resolveProbesCompact = resolveProbesCompact;
342
+ })(window);
@@ -0,0 +1,30 @@
1
+ /* eslint-disable */
2
+ // Runs in MAIN at document_start, before avalanche.iife.js, on the one
3
+ // navigation that follows an armed reload. The page bridge stored the arm in
4
+ // sessionStorage just before reloading; reading it here is synchronous, so the
5
+ // startup config exists when Avalanche evaluates and the detector installs
6
+ // before React's first commit. The entry is removed on read and ignored when old.
7
+ (function chippyStartupHandoff(global) {
8
+ var KEY = '__chippyStartupHandoff';
9
+ var MAX_AGE_MS = 60000;
10
+ var handoff = null;
11
+ try {
12
+ var raw = global.sessionStorage.getItem(KEY);
13
+ if (raw == null) return;
14
+ global.sessionStorage.removeItem(KEY);
15
+ handoff = JSON.parse(raw);
16
+ } catch (_) {
17
+ return;
18
+ }
19
+ if (!handoff || !handoff.avalanche || !handoff.avalanche.autoStart) return;
20
+ if (!(Date.now() - Number(handoff.at) <= MAX_AGE_MS)) return;
21
+ if (global.__CHIPPY_STARTUP_CONFIG) return;
22
+ global.__CHIPPY_STARTUP_CONFIG = {
23
+ enabled: true,
24
+ helperBaseUrl: handoff.helperBaseUrl,
25
+ avalanche: handoff.avalanche,
26
+ plugins: Array.isArray(handoff.plugins) ? handoff.plugins : undefined,
27
+ contextUiEnabled: true,
28
+ startupSource: 'armed-reload-handoff',
29
+ };
30
+ })(typeof globalThis !== 'undefined' ? globalThis : window);
@@ -0,0 +1 @@
1
+ {"type":"commonjs"}
@@ -0,0 +1,56 @@
1
+ # Chippy reference guide
2
+
3
+ Choose the Chippy decision in front of you—position evidence, trace the page, investigate performance, automate a browser, parse a contract, or recover a failed path—then load only the owning reference. Each page owns one decision or contract.
4
+
5
+ ## Start with the work
6
+
7
+ | Need | Read | What it gives you |
8
+ |---|---|---|
9
+ | Connect through an MCP-capable host | [MCP](mcp.md) | Automatic helper startup, typed tools, lifecycle, and sandbox requirements |
10
+ | Position an uncertain case | [Cynefin and OODA](cynefin.md) | The permitted evidence move for Clear, Complicated, Complex, Chaotic, or unpositioned work |
11
+ | Define proof and edit gates | [Operating model](operating-model.md) | Observation contract, proof packet, confidence rule, and Apply gate |
12
+ | Select an exact workflow | [BDD router](bdd-router.md) | Situation-to-procedure routing with completion checks |
13
+ | Execute the selected workflow | [Procedures](procedures.md) | P-01 and later ceremonies, procedures, and plays |
14
+ | Use a mutating or synthetic action | [Active stimulus](active-stimulus.md) | Anchor contract, pollution boundary, and recovery rules |
15
+
16
+ ## Trace the page
17
+
18
+ | Need | Read | What it gives you |
19
+ |---|---|---|
20
+ | Interpret hook output | [Hooks inspection](hooks-inspection.md) | Hook tree reading, limits, and `useDebugValue` guidance |
21
+ | Automate probe handling | [Playwright probes](playwright-probes.md) | Probe lifecycle, measurement operations, and extension handoff |
22
+ | Work on minified Jira pages | [Jira taxonomy](jira-taxonomy.md) | Production-name patterns and bounded fallback signals |
23
+
24
+ ## Investigate performance
25
+
26
+ | Need | Read | What it gives you |
27
+ |---|---|---|
28
+ | Capture and interpret commits | [Avalanche detector](avalanche-detector.md) | Capture modes, schemas, causality reading, unmounts, Suspense, and large-capture analysis |
29
+ | Choose the fix layer | [Fixing performance](fixing-performance.md) | Source/boundary/collateral classification and targeted remediation |
30
+ | Grade one subtree | [Stability render](stability-render.md) | Deterministic nudge procedure and clean/leak/cascade/trasher verdicts |
31
+ | Identify one captured hook | [Debug-hook](debug-hook.md) | Hook primitive, custom-hook ownership, confidence, and remediation branch |
32
+ | Count dispatches or recomputations | [Hook metrics](hook-metrics.md) | `setStateCount`, hook tracker actions, histograms, and Avalanche joins |
33
+
34
+ These pages are siblings, not substitutes. Start with Avalanche for a real slow interaction or load, use stability-render for a known subtree, and use debug-hook only when a capture already produced `sourceId + hookIndex`.
35
+
36
+ ## Automate a browser
37
+
38
+ | Need | Read | What it gives you |
39
+ |---|---|---|
40
+ | Load Chippy into a Playwright-controlled page | [Playwright CLI setup](playwright-cli-setup.md) | The shortest current-document bootstrap and reload recovery recipe |
41
+ | Build a repeatable browser loop | [Test mode playbook](test-mode-playbook.md) | Sidecar and Playwright-controlled automation contracts |
42
+ | Load Chippy before page scripts, or record the initial load without the extension | [Mode A injection](mode-a-injection.md) | Init-script loader variants, pre-page start-up, boot-time limits, and failure signatures |
43
+
44
+ Use Playwright CLI setup for ad-hoc inspection. Use the test-mode playbook for scripted or CI workflows. Use Mode A injection when Chippy must stay across reloads or record the initial load in a Playwright browser.
45
+
46
+ ## Look up a contract
47
+
48
+ | Need | Read | What it gives you |
49
+ |---|---|---|
50
+ | Find the recipe for a task or symptom | Run `chippy.sh recipes <query...>` against [the grep-friendly corpus](recipes.txt) | Ranked, self-contained answers with completion signals, guardrails, and canonical deeper references |
51
+ | Select a shell command | [Command surface](command-surface.md) | Complete public command catalog and important option modes |
52
+ | Parse a response | [Command output formats](command-output-formats.md) | Exact envelopes, DTOs, field semantics, and examples |
53
+ | Work in the page runtime | [Browser runtime API](browser-runtime-api.md) | Public globals, bridge API, and helper-to-page commands |
54
+ | Recover a failed path | [Troubleshooting](troubleshooting.md) | Failure signature, failing layer, and one recovery action |
55
+
56
+ The shell wrapper's `help` output owns mechanical command signatures. Recipe search owns task-to-answer lookup, command surface explains selection and boundaries, and command output formats owns exact response shapes.