@inflexa-ai/harness 0.28.1 → 0.29.0

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 (83) hide show
  1. package/dist/app/data-profile-orientation.d.ts +52 -27
  2. package/dist/app/data-profile-orientation.js +331 -92
  3. package/dist/app/data-profile-view.d.ts +70 -0
  4. package/dist/app/data-profile-view.js +123 -0
  5. package/dist/app/synthesize-run.js +1 -1
  6. package/dist/contracts/data-profile.d.ts +276 -25
  7. package/dist/contracts/index.d.ts +3 -1
  8. package/dist/contracts/index.js +1 -0
  9. package/dist/contracts/profile-vocabulary.d.ts +103 -0
  10. package/dist/contracts/profile-vocabulary.js +449 -0
  11. package/dist/contracts/target-dossier.d.ts +21 -21
  12. package/dist/execution/input-signature.d.ts +16 -3
  13. package/dist/execution/input-signature.js +0 -0
  14. package/dist/execution/post-step-pipeline.js +1 -1
  15. package/dist/execution/staged-input.d.ts +4 -4
  16. package/dist/index.d.ts +5 -1
  17. package/dist/index.js +7 -0
  18. package/dist/input-scan/clustering.d.ts +63 -0
  19. package/dist/input-scan/clustering.js +208 -0
  20. package/dist/input-scan/companions.d.ts +30 -0
  21. package/dist/input-scan/companions.js +138 -0
  22. package/dist/input-scan/decoder/container_readout.py +171 -0
  23. package/dist/input-scan/detect-sets.d.ts +24 -0
  24. package/dist/input-scan/detect-sets.js +279 -0
  25. package/dist/input-scan/enrich.d.ts +62 -19
  26. package/dist/input-scan/enrich.js +132 -187
  27. package/dist/input-scan/formats.d.ts +13 -5
  28. package/dist/input-scan/formats.js +13 -5
  29. package/dist/input-scan/index.d.ts +10 -4
  30. package/dist/input-scan/index.js +7 -4
  31. package/dist/input-scan/markers.d.ts +21 -0
  32. package/dist/input-scan/markers.js +89 -0
  33. package/dist/input-scan/menu.d.ts +127 -0
  34. package/dist/input-scan/menu.js +0 -0
  35. package/dist/input-scan/quarantine.d.ts +17 -0
  36. package/dist/input-scan/quarantine.js +69 -0
  37. package/dist/input-scan/readout-budget.d.ts +32 -0
  38. package/dist/input-scan/readout-budget.js +61 -0
  39. package/dist/input-scan/readout.d.ts +33 -0
  40. package/dist/input-scan/readout.js +336 -0
  41. package/dist/input-scan/scan.d.ts +5 -4
  42. package/dist/input-scan/scan.js +5 -5
  43. package/dist/input-scan/set-types.d.ts +209 -0
  44. package/dist/input-scan/set-types.js +15 -0
  45. package/dist/input-scan/templates.d.ts +93 -0
  46. package/dist/input-scan/templates.js +369 -0
  47. package/dist/input-scan/tokens.d.ts +94 -0
  48. package/dist/input-scan/tokens.js +267 -0
  49. package/dist/input-scan/tuning.d.ts +55 -0
  50. package/dist/input-scan/tuning.js +55 -0
  51. package/dist/input-scan/types.d.ts +2 -2
  52. package/dist/prompts/conversation.d.ts +1 -1
  53. package/dist/prompts/conversation.js +8 -6
  54. package/dist/prompts/report-session.d.ts +1 -1
  55. package/dist/prompts/report-session.js +3 -2
  56. package/dist/prompts/sandbox/data-profiler.d.ts +10 -1
  57. package/dist/prompts/sandbox/data-profiler.js +197 -53
  58. package/dist/prompts/sandbox-standards.d.ts +1 -1
  59. package/dist/prompts/sandbox-standards.js +10 -7
  60. package/dist/schemas/data-profile-schemas.d.ts +943 -80
  61. package/dist/schemas/data-profile-schemas.js +300 -115
  62. package/dist/state/data-profile.d.ts +1 -1
  63. package/dist/state/index.d.ts +1 -1
  64. package/dist/state/vector-store.d.ts +13 -0
  65. package/dist/state/vector-store.js +8 -0
  66. package/dist/tasks/data-profile-absorb.d.ts +78 -0
  67. package/dist/tasks/data-profile-absorb.js +285 -0
  68. package/dist/tasks/data-profile-index.d.ts +34 -25
  69. package/dist/tasks/data-profile-index.js +160 -59
  70. package/dist/tasks/data-profile-resolve.d.ts +72 -0
  71. package/dist/tasks/data-profile-resolve.js +639 -0
  72. package/dist/tasks/data-profile.d.ts +16 -5
  73. package/dist/tasks/data-profile.js +270 -131
  74. package/dist/tools/research/inspect-data-profile.d.ts +48 -22
  75. package/dist/tools/research/inspect-data-profile.js +73 -54
  76. package/dist/tools/workspace/scan-inputs.d.ts +4 -4
  77. package/dist/tools/workspace/scan-inputs.js +13 -14
  78. package/dist/tools/workspace/workspace-search.d.ts +11 -2
  79. package/dist/tools/workspace/workspace-search.js +44 -16
  80. package/dist/workflows/target-assessment/phase4-assemble.d.ts +21 -21
  81. package/package.json +5 -3
  82. package/dist/input-scan/coverage.d.ts +0 -22
  83. package/dist/input-scan/coverage.js +0 -85
@@ -0,0 +1,279 @@
1
+ /**
2
+ * The detected-set pipeline: quarantine → markers → sibling clustering → template
3
+ * mining → assembly.
4
+ *
5
+ * Pure over paths, sizes, and the format already detected — no read, no container, no
6
+ * model. Each stage narrows what the next one has to explain: junk leaves before
7
+ * structure is observed, a marker-claimed subtree never reaches inference, and a
8
+ * clustered directory arrives at mining as one template with its entity already a
9
+ * slot.
10
+ *
11
+ * Assembly carries variance rather than splitting on it. Members that differ only in
12
+ * compression are one set whose wrapper census says so, and a token appearing in both
13
+ * a directory segment and the stem is cross-checked and reported as ONE identity slot,
14
+ * because reporting it twice would invent a second thing that varies.
15
+ */
16
+ import { assessCompleteness, attachCompanions } from "./companions.js";
17
+ import { buildTree, clusterChildren, computeSignatures } from "./clustering.js";
18
+ import { applyMarkers } from "./markers.js";
19
+ import { quarantine } from "./quarantine.js";
20
+ import { selectReadouts } from "./readout-budget.js";
21
+ import { catchAll, describeGroup, describeSlot, mineContext, slotToken } from "./templates.js";
22
+ import { IDENTITY_CROSS_CHECK_RATIO, MAX_EXAMPLE_PATHS, MAX_LEFTOVER_SAMPLE, MAX_SETS_PER_CONTEXT, MIN_IDENTITY_TOKEN_LENGTH } from "./tuning.js";
23
+ function fileCountOf(members) {
24
+ return members.reduce((total, member) => total + 1 + member.companions.length, 0);
25
+ }
26
+ function bytesOf(members) {
27
+ return members.reduce((total, member) => total + member.size + member.companions.reduce((sum, companion) => sum + companion.size, 0), 0);
28
+ }
29
+ function formatCensus(members) {
30
+ const counts = new Map();
31
+ for (const member of members)
32
+ counts.set(member.format, (counts.get(member.format) ?? 0) + 1);
33
+ return [...counts.entries()].map(([format, count]) => ({ format, count })).sort((a, b) => b.count - a.count || a.format.localeCompare(b.format, "en"));
34
+ }
35
+ function wrapperCensus(members) {
36
+ const counts = new Map();
37
+ for (const member of members) {
38
+ const wrapper = member.wrapper ?? "none";
39
+ counts.set(wrapper, (counts.get(wrapper) ?? 0) + 1);
40
+ }
41
+ return [...counts.entries()].map(([wrapper, count]) => ({ wrapper, count })).sort((a, b) => b.count - a.count || a.wrapper.localeCompare(b.wrapper, "en"));
42
+ }
43
+ function renderTemplate(segments, slots) {
44
+ return segments.map((segment) => (segment.kind === "literal" ? segment.text : slotToken(slots[segment.slot]))).join("");
45
+ }
46
+ /** The token value at a slot's position, with the literal affixes the template shows stripped off. */
47
+ function coreAt(item, slot) {
48
+ const raw = item.tokens[slot.index]?.value ?? "";
49
+ const end = slot.suffix ? raw.length - slot.suffix.length : raw.length;
50
+ return raw.slice(slot.prefix?.length ?? 0, end);
51
+ }
52
+ /**
53
+ * Link a name slot to the directory slot it repeats.
54
+ *
55
+ * Containment rather than equality: affix recovery can take a character off the token,
56
+ * and a directory segment may carry decoration its stem twin lacks. Containment on its
57
+ * own is weak evidence, so it counts only where the contained token is long enough to
58
+ * be an identifier, or where the two positions have the same cardinality — a token
59
+ * that appears inside a segment AND varies with it one-for-one is that segment's
60
+ * identity, while one that merely appears inside it is a coincidence of naming.
61
+ */
62
+ function crossCheckIdentity(group, nameSlots, directorySlots, directoryValues) {
63
+ if (group.kind !== "tokens" && group.kind !== "family")
64
+ return;
65
+ for (const slot of nameSlots) {
66
+ if (slot.distinctValues < 2)
67
+ continue;
68
+ for (let k = 0; k < directoryValues.length; k++) {
69
+ const values = directoryValues[k];
70
+ const paired = directorySlots[k].distinctValues === slot.distinctValues;
71
+ let matches = 0;
72
+ for (let i = 0; i < group.items.length; i++) {
73
+ const core = coreAt(group.items[i], slot);
74
+ const segment = values[i] ?? "";
75
+ const alike = core === segment ||
76
+ (segment.includes(core) && (paired || core.length >= MIN_IDENTITY_TOKEN_LENGTH)) ||
77
+ (core.includes(segment) && (paired || segment.length >= MIN_IDENTITY_TOKEN_LENGTH));
78
+ if (alike)
79
+ matches++;
80
+ }
81
+ if (matches / group.items.length < IDENTITY_CROSS_CHECK_RATIO)
82
+ continue;
83
+ slot.sameAs = k;
84
+ slot.crossCheckMismatches = group.items.length - matches;
85
+ break;
86
+ }
87
+ }
88
+ }
89
+ function assembleMinedSet(group, path) {
90
+ const members = group.items.map((item) => item.file);
91
+ const { slots: nameSlots, segments: nameSegments } = describeGroup(group);
92
+ const directorySlots = [];
93
+ const directorySegments = [];
94
+ const directoryValues = [];
95
+ let variablePosition = 0;
96
+ for (const segment of path) {
97
+ if (segment.kind === "literal") {
98
+ directorySegments.push({ kind: "literal", text: `${segment.name}/` });
99
+ continue;
100
+ }
101
+ const at = variablePosition++;
102
+ const values = members.map((member) => member.varValues[at] ?? "");
103
+ const distinct = new Set(values);
104
+ if (distinct.size === 1) {
105
+ directorySegments.push({ kind: "literal", text: `${[...distinct][0]}/` });
106
+ continue;
107
+ }
108
+ const slot = describeSlot(values, "directory", segment.index);
109
+ if (slot.prefix)
110
+ directorySegments.push({ kind: "literal", text: slot.prefix });
111
+ directorySegments.push({ kind: "slot", slot: directorySlots.length });
112
+ if (slot.suffix)
113
+ directorySegments.push({ kind: "literal", text: slot.suffix });
114
+ directorySegments.push({ kind: "literal", text: "/" });
115
+ directorySlots.push(slot);
116
+ directoryValues.push(values);
117
+ }
118
+ crossCheckIdentity(group, nameSlots, directorySlots, directoryValues);
119
+ const slots = [...directorySlots, ...nameSlots];
120
+ const segments = [
121
+ ...directorySegments,
122
+ ...nameSegments.map((segment) => (segment.kind === "slot" ? { kind: "slot", slot: segment.slot + directorySlots.length } : segment)),
123
+ ];
124
+ const origin = group.kind === "tokens" ? "mined" : group.kind;
125
+ return { origin, pathTemplate: renderTemplate(segments, slots), segments, slots, members };
126
+ }
127
+ function finalizeSet(draft, index) {
128
+ const id = `set-${index + 1}`;
129
+ const slotIds = draft.slots.map((_, position) => `${id}.slot-${position + 1}`);
130
+ const slots = draft.slots.map((slot, position) => ({
131
+ id: slotIds[position],
132
+ location: slot.location,
133
+ index: slot.index,
134
+ tokenClass: slot.tokenClass,
135
+ ...(slot.width !== undefined ? { width: slot.width } : {}),
136
+ ...(slot.minLength !== undefined ? { minLength: slot.minLength, maxLength: slot.maxLength } : {}),
137
+ ...(slot.skeleton !== undefined ? { skeleton: slot.skeleton } : {}),
138
+ ...(slot.prefix !== undefined ? { prefix: slot.prefix } : {}),
139
+ ...(slot.suffix !== undefined ? { suffix: slot.suffix } : {}),
140
+ distinctValues: slot.distinctValues,
141
+ sampleValues: slot.sampleValues,
142
+ ...(slot.sameAs !== undefined ? { sameAsSlot: slotIds[slot.sameAs], crossCheckMismatches: slot.crossCheckMismatches ?? 0 } : {}),
143
+ }));
144
+ const segments = draft.segments.map((segment) => segment.kind === "literal" ? { kind: "literal", text: segment.text } : { kind: "slot", slotId: slotIds[segment.slot] });
145
+ const { members, completeness } = assessCompleteness(draft.members);
146
+ return {
147
+ set: {
148
+ id,
149
+ origin: draft.origin,
150
+ ...(draft.marker ? { marker: draft.marker } : {}),
151
+ pathTemplate: draft.pathTemplate,
152
+ segments,
153
+ memberCount: members.length,
154
+ fileCount: fileCountOf(members),
155
+ totalBytes: bytesOf(members),
156
+ formats: formatCensus(members),
157
+ wrappers: wrapperCensus(members),
158
+ slots,
159
+ completeness,
160
+ examplePaths: members.slice(0, MAX_EXAMPLE_PATHS).map((member) => member.path),
161
+ members,
162
+ },
163
+ values: draft.slots.map((slot, position) => [slotIds[position], slot.values]),
164
+ memberValues: draft.slots.map((slot, position) => [slotIds[position], slot.memberValues]),
165
+ };
166
+ }
167
+ /**
168
+ * Sample the leftovers across formats first, then by size.
169
+ *
170
+ * A metadata sheet, a README, and a paper are each one file of their own format, so a
171
+ * size-ordered sample would bury all three under whichever large files failed to
172
+ * group. Covering formats first is what makes the notable singletons visible.
173
+ */
174
+ function sampleLeftovers(members) {
175
+ const bySize = [...members].sort((a, b) => b.size - a.size || a.path.localeCompare(b.path, "en"));
176
+ const picked = [];
177
+ const seen = new Set();
178
+ for (const member of bySize) {
179
+ if (picked.length >= MAX_LEFTOVER_SAMPLE)
180
+ break;
181
+ if (seen.has(member.format))
182
+ continue;
183
+ seen.add(member.format);
184
+ picked.push(member);
185
+ }
186
+ for (const member of bySize) {
187
+ if (picked.length >= MAX_LEFTOVER_SAMPLE)
188
+ break;
189
+ if (!picked.includes(member))
190
+ picked.push(member);
191
+ }
192
+ return picked.map((member) => ({ path: member.path, size: member.size, format: member.format }));
193
+ }
194
+ /** Observe the detected sets of a scanned tree. Pure — no I/O, no container, no model. */
195
+ export function detectSets(files, options = {}) {
196
+ const { kept, summary } = quarantine(files);
197
+ const { units, unclaimed } = applyMarkers(attachCompanions(kept));
198
+ const drafts = [];
199
+ for (const unit of units) {
200
+ const template = `${unit.root ? `${unit.root}/` : ""}**`;
201
+ drafts.push({
202
+ origin: "marker",
203
+ marker: unit.label,
204
+ pathTemplate: template,
205
+ segments: [{ kind: "literal", text: template }],
206
+ slots: [],
207
+ members: unit.members,
208
+ });
209
+ }
210
+ const leftovers = [];
211
+ const emit = (context, path) => {
212
+ const hasDirectorySlot = path.some((segment) => segment.kind === "variable");
213
+ const mined = mineContext(context);
214
+ let sets = mined.sets;
215
+ let residue = mined.residue;
216
+ // The long tail of tiny sets inside a repeated directory shape is noise: the
217
+ // repetition is the structure, and the catch-all is where it belongs.
218
+ if (hasDirectorySlot && sets.length > MAX_SETS_PER_CONTEXT) {
219
+ const ordered = [...sets].sort((a, b) => b.items.length - a.items.length);
220
+ sets = ordered.slice(0, MAX_SETS_PER_CONTEXT);
221
+ residue = [...residue, ...ordered.slice(MAX_SETS_PER_CONTEXT).flatMap((group) => group.items)];
222
+ }
223
+ const caught = catchAll(residue, hasDirectorySlot);
224
+ for (const group of [...sets, ...caught.sets])
225
+ drafts.push(assembleMinedSet(group, path));
226
+ leftovers.push(...caught.rest.map((item) => item.file));
227
+ };
228
+ const descend = (entries, path) => {
229
+ const context = [];
230
+ for (const entry of entries)
231
+ for (const file of entry.node.files)
232
+ context.push({ ...file, varValues: entry.varValues });
233
+ if (context.length > 0)
234
+ emit(context, path);
235
+ const children = entries.flatMap((entry) => [...entry.node.children.values()].map((node) => ({ node, varValues: entry.varValues })));
236
+ if (children.length === 0)
237
+ return;
238
+ for (const cluster of clusterChildren(children, options.contentSimilarity)) {
239
+ if (cluster.names.length === 1) {
240
+ descend(cluster.entries, [...path, { kind: "literal", name: cluster.names[0] }]);
241
+ continue;
242
+ }
243
+ const next = cluster.entries.map((entry) => ({ node: entry.node, varValues: [...entry.varValues, entry.node.name] }));
244
+ descend(next, [...path, { kind: "variable", index: path.length }]);
245
+ }
246
+ };
247
+ descend([{ node: computeSignatures(buildTree(unclaimed)), varValues: [] }], []);
248
+ const ordered = drafts.sort((a, b) => b.members.length - a.members.length || bytesOf(b.members) - bytesOf(a.members) || a.pathTemplate.localeCompare(b.pathTemplate, "en"));
249
+ const slotValues = new Map();
250
+ const memberSlotValues = new Map();
251
+ const sets = [];
252
+ for (const [index, draft] of ordered.entries()) {
253
+ const finalized = finalizeSet(draft, index);
254
+ sets.push(finalized.set);
255
+ for (const [slotId, values] of finalized.values)
256
+ slotValues.set(slotId, values);
257
+ for (const [slotId, values] of finalized.memberValues)
258
+ memberSlotValues.set(slotId, values);
259
+ }
260
+ const coveredFiles = sets.reduce((total, set) => total + set.fileCount, 0);
261
+ const leftoverFiles = {
262
+ memberCount: leftovers.length,
263
+ fileCount: fileCountOf(leftovers),
264
+ totalBytes: bytesOf(leftovers),
265
+ sample: sampleLeftovers(leftovers),
266
+ };
267
+ return {
268
+ fileCount: files.length,
269
+ keptFileCount: kept.length,
270
+ coverage: kept.length === 0 ? 1 : coveredFiles / kept.length,
271
+ quarantine: summary,
272
+ sets,
273
+ leftovers: leftoverFiles,
274
+ leftoverMembers: leftovers,
275
+ readout: selectReadouts(sets, leftovers),
276
+ slotValues,
277
+ memberSlotValues,
278
+ };
279
+ }
@@ -1,30 +1,66 @@
1
1
  /**
2
- * Per-shape header readout — the one part of the scan that needs the sandbox.
2
+ * Per-shape header readout.
3
3
  *
4
- * A bounded prefix read compared against a magic table runs on the host; a DECODER
5
- * over user-supplied bytes does not. zlib, HDF5, and PDF parsers in the long-lived
6
- * multi-tenant harness process are exactly the exposure the ephemeral sandbox exists
7
- * to contain, so every decode below runs there.
4
+ * Split by what the readout needs. A header that sits at the front of the file —
5
+ * magic bytes, a `##` preamble, a delimited header row, a compressed prefix — is
6
+ * reachable from a bounded read, so it runs in this process over the workspace read
7
+ * seam (see `readout.ts`) and costs no sandbox round trip.
8
8
  *
9
- * Cost is O(shapes), not O(files): shapes are observed from names and sizes alone, so
10
- * enrichment decodes a bounded number of members per shape — roughly four files for
11
- * the 3513-file tree that motivated this capability. One exec covers every shape, so
12
- * even the round trip does not scale with the tree.
9
+ * A container that indexes its contents from a footer cannot be read that way, and
10
+ * running a container parser over user-supplied bytes in a long-lived multi-tenant
11
+ * process is the exposure the ephemeral sandbox exists to contain. Those readouts run
12
+ * from `decoder/container_readout.py`, a package asset delivered on the command line
13
+ * through the existing `runSandboxExec` path.
13
14
  *
14
- * Nothing ships in `sandbox-base` for this: the decoder is a Python program passed on
15
- * the command line through the existing `runSandboxExec` path, so the capability
16
- * releases on the harness's own path rather than on the image's manual one.
15
+ * Cost is O(shapes), not O(files): shapes are observed from names and sizes alone, so
16
+ * enrichment reads a bounded number of members per shape whatever the tree's size.
17
17
  */
18
+ import type { AgentSession } from "../auth/types.js";
18
19
  import type { EmitFn } from "../tools/define-tool.js";
19
20
  import type { SandboxClient } from "../sandbox/client.js";
20
21
  import type { SandboxRef } from "../sandbox/types.js";
21
- import type { FileShape } from "./types.js";
22
+ import type { WorkspaceFilesystem } from "../workspace/filesystem.js";
23
+ import type { FileShape, HeaderReadout } from "./types.js";
22
24
  /** Members decoded per shape. One is enough to characterise a set whose names already match. */
23
25
  export declare const MEMBERS_DECODED_PER_SHAPE = 1;
26
+ /**
27
+ * Prefix reads in flight at once. Each is a bounded read over the workspace seam; running
28
+ * them one at a time makes a menu of sixty leftovers sixty serial round trips, and running
29
+ * them all at once hands the seam an unbounded burst.
30
+ */
31
+ export declare const READOUT_CONCURRENCY = 8;
32
+ /**
33
+ * The formats whose header lives behind a whole-container parse: Parquet's schema is
34
+ * in its footer, the HDF5 families index objects across the file, and PDF and DOCX
35
+ * both resolve their contents through a trailer. Everything else reads from a prefix.
36
+ */
37
+ export declare const SANDBOX_CONTAINER_FORMATS: ReadonlySet<string>;
38
+ /** One file to read a header from, and what the scan already knows about its bytes. */
39
+ export interface ReadoutTargetSpec {
40
+ readonly path: string;
41
+ readonly format: string;
42
+ readonly wrapper?: string;
43
+ }
44
+ export interface ReadHeadersArgs {
45
+ readonly targets: readonly ReadoutTargetSpec[];
46
+ readonly session: AgentSession;
47
+ readonly fs: WorkspaceFilesystem;
48
+ /** Omit the sandbox to skip container readouts; every other format still reads. */
49
+ readonly sandboxClient?: SandboxClient;
50
+ readonly sandbox?: SandboxRef;
51
+ /** Absolute in-sandbox path of the analysis root (`/{analysisId}`). */
52
+ readonly mountRoot: string;
53
+ readonly execId: string;
54
+ readonly deadlineMs: number;
55
+ readonly emit: EmitFn;
56
+ }
24
57
  export interface EnrichShapesArgs {
25
58
  readonly shapes: readonly FileShape[];
26
- readonly sandboxClient: SandboxClient;
27
- readonly sandbox: SandboxRef;
59
+ readonly session: AgentSession;
60
+ readonly fs: WorkspaceFilesystem;
61
+ /** Omit the sandbox to skip container readouts; every other format still reads. */
62
+ readonly sandboxClient?: SandboxClient;
63
+ readonly sandbox?: SandboxRef;
28
64
  /** Absolute in-sandbox path of the analysis root (`/{analysisId}`). */
29
65
  readonly mountRoot: string;
30
66
  readonly execId: string;
@@ -32,10 +68,17 @@ export interface EnrichShapesArgs {
32
68
  readonly emit: EmitFn;
33
69
  }
34
70
  /**
35
- * Decode one member per shape and attach the readout.
71
+ * Read a header from each named file, keyed by path.
72
+ *
73
+ * A target whose readout failed carries an `unavailable` note rather than vanishing: the
74
+ * readout is enrichment, and a menu without it still carries every structural observation
75
+ * the agent's grouping rests on.
76
+ */
77
+ export declare function readHeaders(args: ReadHeadersArgs): Promise<Map<string, HeaderReadout>>;
78
+ /**
79
+ * Read one member per shape and attach the readout.
36
80
  *
37
- * A shape whose decode failed keeps its `unavailable` note rather than losing the
38
- * shape: the readout is enrichment, and a manifest without it still carries every
39
- * structural observation the agent's grouping rests on.
81
+ * A shape whose readout failed keeps its `unavailable` note rather than losing the
82
+ * shape, for the same reason.
40
83
  */
41
84
  export declare function enrichShapes(args: EnrichShapesArgs): Promise<FileShape[]>;