hcifootprint 0.2.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.
- package/LICENSE +20 -0
- package/README.md +524 -0
- package/dist/atom/types.d.ts +618 -0
- package/dist/atom/types.d.ts.map +1 -0
- package/dist/atom/types.js +2 -0
- package/dist/atom/types.js.map +1 -0
- package/dist/graph/builder.d.ts +31 -0
- package/dist/graph/builder.d.ts.map +1 -0
- package/dist/graph/builder.js +165 -0
- package/dist/graph/builder.js.map +1 -0
- package/dist/graph/guards.d.ts +31 -0
- package/dist/graph/guards.d.ts.map +1 -0
- package/dist/graph/guards.js +94 -0
- package/dist/graph/guards.js.map +1 -0
- package/dist/graph/skill-deps.d.ts +20 -0
- package/dist/graph/skill-deps.d.ts.map +1 -0
- package/dist/graph/skill-deps.js +15 -0
- package/dist/graph/skill-deps.js.map +1 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp.d.ts +16 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +15 -0
- package/dist/mcp.js.map +1 -0
- package/dist/presence/presence.d.ts +50 -0
- package/dist/presence/presence.d.ts.map +1 -0
- package/dist/presence/presence.js +126 -0
- package/dist/presence/presence.js.map +1 -0
- package/dist/registry/registry.d.ts +53 -0
- package/dist/registry/registry.d.ts.map +1 -0
- package/dist/registry/registry.js +76 -0
- package/dist/registry/registry.js.map +1 -0
- package/dist/serve/mcp-server.d.ts +40 -0
- package/dist/serve/mcp-server.d.ts.map +1 -0
- package/dist/serve/mcp-server.js +72 -0
- package/dist/serve/mcp-server.js.map +1 -0
- package/dist/serve/mcp.d.ts +12 -0
- package/dist/serve/mcp.d.ts.map +1 -0
- package/dist/serve/mcp.js +62 -0
- package/dist/serve/mcp.js.map +1 -0
- package/dist/serve/modes.d.ts +66 -0
- package/dist/serve/modes.d.ts.map +1 -0
- package/dist/serve/modes.js +347 -0
- package/dist/serve/modes.js.map +1 -0
- package/dist/testing/harness.d.ts +156 -0
- package/dist/testing/harness.d.ts.map +1 -0
- package/dist/testing/harness.js +416 -0
- package/dist/testing/harness.js.map +1 -0
- package/dist/testing/index.d.ts +35 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +32 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/lint.d.ts +20 -0
- package/dist/testing/lint.d.ts.map +1 -0
- package/dist/testing/lint.js +18 -0
- package/dist/testing/lint.js.map +1 -0
- package/dist/testing/model/check.d.ts +30 -0
- package/dist/testing/model/check.d.ts.map +1 -0
- package/dist/testing/model/check.js +89 -0
- package/dist/testing/model/check.js.map +1 -0
- package/dist/testing/model/lint.d.ts +54 -0
- package/dist/testing/model/lint.d.ts.map +1 -0
- package/dist/testing/model/lint.js +235 -0
- package/dist/testing/model/lint.js.map +1 -0
- package/dist/testing/model/satisfiable.d.ts +25 -0
- package/dist/testing/model/satisfiable.d.ts.map +1 -0
- package/dist/testing/model/satisfiable.js +101 -0
- package/dist/testing/model/satisfiable.js.map +1 -0
- package/dist/traverse/nav-session.d.ts +108 -0
- package/dist/traverse/nav-session.d.ts.map +1 -0
- package/dist/traverse/nav-session.js +637 -0
- package/dist/traverse/nav-session.js.map +1 -0
- package/dist/traverse/session.d.ts +239 -0
- package/dist/traverse/session.d.ts.map +1 -0
- package/dist/traverse/session.js +1305 -0
- package/dist/traverse/session.js.map +1 -0
- package/dist/tree/appmap.d.ts +8 -0
- package/dist/tree/appmap.d.ts.map +1 -0
- package/dist/tree/appmap.js +259 -0
- package/dist/tree/appmap.js.map +1 -0
- package/dist/tree/types.d.ts +151 -0
- package/dist/tree/types.d.ts.map +1 -0
- package/dist/tree/types.js +2 -0
- package/dist/tree/types.js.map +1 -0
- package/package.json +90 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* testApp — a headless workflow runner for a navigation graph, over the REAL
|
|
3
|
+
* session (never a parallel simulation). It is "Playwright for your interaction
|
|
4
|
+
* logic, minus the browser": you write mock handlers (one per action, returning
|
|
5
|
+
* a state change), then drive the graph two ways — `user.*` (like a human
|
|
6
|
+
* clicking) and `agent.*` (like the LLM picking tools, through the real Mode B
|
|
7
|
+
* serving path). Every assertion reads real session output, so a green test
|
|
8
|
+
* exercises the same fire → updateState → commit code your users run.
|
|
9
|
+
*
|
|
10
|
+
* What it catches (the point of the feature): behavioral drift between the
|
|
11
|
+
* graph and the app. When a mock reports a delta that does NOT cover the
|
|
12
|
+
* action's declared `effect.writes`, the session's own honesty marker
|
|
13
|
+
* (effectVerified) flips false — the graph drifted from the handler — and
|
|
14
|
+
* `report()` surfaces it. Report-by-default; pass `strict: true` to fail the
|
|
15
|
+
* test the instant drift appears.
|
|
16
|
+
*
|
|
17
|
+
* Honest boundary: this tests interaction LOGIC above the binding. It does NOT
|
|
18
|
+
* verify pixels, the DOM, or that a binding's locator resolves to a real
|
|
19
|
+
* element — that stays Playwright's job. And a mock is a simulation: if it
|
|
20
|
+
* diverges from the real handler, the test is green while prod is broken. Use
|
|
21
|
+
* `testApp({ session })` (bring-your-own wired session) for full fidelity;
|
|
22
|
+
* effectVerified catches declaration-vs-delta drift even in mock mode.
|
|
23
|
+
*
|
|
24
|
+
* Layer note: imports the real Session (via graph.createSession) + the Mode B
|
|
25
|
+
* port. Kept OUT of the lint path so a pure CI lint stays engine-free.
|
|
26
|
+
*/
|
|
27
|
+
import { skillsAsTools } from '../serve/modes.js';
|
|
28
|
+
const SETTLE_CAP = 60;
|
|
29
|
+
export function testApp(graphOrOptions, maybeOptions) {
|
|
30
|
+
const byo = !('spec' in graphOrOptions);
|
|
31
|
+
const opts = byo
|
|
32
|
+
? graphOrOptions
|
|
33
|
+
: maybeOptions ?? {};
|
|
34
|
+
const graph = byo ? undefined : graphOrOptions;
|
|
35
|
+
const warnings = [];
|
|
36
|
+
const warn = (message) => {
|
|
37
|
+
warnings.push(message);
|
|
38
|
+
opts.onWarn?.(message);
|
|
39
|
+
};
|
|
40
|
+
// Controllable clock — starts at 0 so time is fully deterministic.
|
|
41
|
+
let clockMs = 0;
|
|
42
|
+
const clock = {
|
|
43
|
+
now: () => clockMs,
|
|
44
|
+
set: (ms) => {
|
|
45
|
+
clockMs = ms;
|
|
46
|
+
},
|
|
47
|
+
advance: (ms) => {
|
|
48
|
+
clockMs += ms;
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
const resolvers = opts.resolvers ?? {};
|
|
52
|
+
const affordances = byo ? undefined : graph.spec.affordances;
|
|
53
|
+
const nodes = byo ? undefined : graph.nodes;
|
|
54
|
+
const toolNodes = byo ? undefined : graph.toolNodes;
|
|
55
|
+
const session = byo
|
|
56
|
+
? opts.session
|
|
57
|
+
: graph.createSession({
|
|
58
|
+
...(opts.node !== undefined ? { node: opts.node } : {}),
|
|
59
|
+
...(opts.initialState !== undefined ? { state: opts.initialState } : {}),
|
|
60
|
+
...(opts.redactedKeys !== undefined ? { redactedKeys: opts.redactedKeys } : {}),
|
|
61
|
+
...(opts.dormantGraceMs !== undefined ? { dormantGraceMs: opts.dormantGraceMs } : {}),
|
|
62
|
+
now: clock.now,
|
|
63
|
+
onWarn: warn,
|
|
64
|
+
});
|
|
65
|
+
const port = skillsAsTools(session, { source: 'agent' });
|
|
66
|
+
const graphId = session.graphId;
|
|
67
|
+
const sanitize = (name) => name.replace(/[^A-Za-z0-9_.-]/g, '_');
|
|
68
|
+
// --- resolver → handler wrapper -------------------------------------------
|
|
69
|
+
function affordanceById(id) {
|
|
70
|
+
if (!affordances)
|
|
71
|
+
return null;
|
|
72
|
+
if (affordances[id])
|
|
73
|
+
return { id, declaresWrites: (affordances[id].effect?.writes?.length ?? 0) > 0 };
|
|
74
|
+
// bare/leaf: resolve to the single qualified id that ends with it
|
|
75
|
+
const matches = Object.keys(affordances).filter((qid) => qid === id || qid.endsWith(`.${id}`));
|
|
76
|
+
if (matches.length === 1) {
|
|
77
|
+
const qid = matches[0];
|
|
78
|
+
return { id: qid, declaresWrites: (affordances[qid].effect?.writes?.length ?? 0) > 0 };
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
function wrap(affId) {
|
|
83
|
+
const declaresWrites = (affordances?.[affId]?.effect?.writes?.length ?? 0) > 0;
|
|
84
|
+
return (payload) => {
|
|
85
|
+
const resolver = resolvers[affId] ?? resolvers[leafOf(affId)];
|
|
86
|
+
const out = resolver ? resolver(payload, { state: session.state() }) : undefined;
|
|
87
|
+
const patch = out?.patch && Object.keys(out.patch).length > 0 ? out.patch : undefined;
|
|
88
|
+
// A write-declaring action MUST settle its pending — even with no patch,
|
|
89
|
+
// which honestly yields effectVerified:false (drift), surfaced not hidden.
|
|
90
|
+
if (declaresWrites) {
|
|
91
|
+
session.updateState((patch ?? {}));
|
|
92
|
+
}
|
|
93
|
+
else if (patch) {
|
|
94
|
+
// A no-writes action changed state it never declared: report it as an
|
|
95
|
+
// explicit stimulus (NOT an anonymous delta), so effect-signature
|
|
96
|
+
// inference can't misattribute it to another action's writes and fake a
|
|
97
|
+
// step as inferred-done. Surface the graph gap instead of hiding it.
|
|
98
|
+
warn(`hcifootprint/testing: action '${affId}' declares no writes but its resolver returned a state ` +
|
|
99
|
+
`patch (${Object.keys(patch).join(', ')}) — recorded as an external stimulus. Add writes: [...] ` +
|
|
100
|
+
`to this action in the graph if it really changes state.`);
|
|
101
|
+
session.updateState(patch, { stimulus: 'unknown', principal: 'system' });
|
|
102
|
+
}
|
|
103
|
+
if (out?.goTo)
|
|
104
|
+
session.sync(out.goTo, { stimulus: 'navigation' });
|
|
105
|
+
return out?.produced;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function leafOf(id) {
|
|
109
|
+
return id.includes('.') ? id.slice(id.lastIndexOf('.') + 1) : id;
|
|
110
|
+
}
|
|
111
|
+
// --- auto-mount (page + area descendants; modals/tabs need open()) ---------
|
|
112
|
+
let mountedPage = null;
|
|
113
|
+
const autoHandles = [];
|
|
114
|
+
const openHandles = new Map();
|
|
115
|
+
function autoMountable(path) {
|
|
116
|
+
if (!nodes)
|
|
117
|
+
return false;
|
|
118
|
+
for (let cursor = nodes[path]; cursor; cursor = cursor.parent ? nodes[cursor.parent] : undefined) {
|
|
119
|
+
if (cursor.kind === 'modal' || cursor.kind === 'tab')
|
|
120
|
+
return false;
|
|
121
|
+
if (!cursor.parent)
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
function handlersForNode(nodePath) {
|
|
127
|
+
const handlers = {};
|
|
128
|
+
if (!affordances || !toolNodes)
|
|
129
|
+
return handlers;
|
|
130
|
+
for (const aff of Object.values(affordances)) {
|
|
131
|
+
if (!(toolNodes[aff.id] ?? []).includes(nodePath))
|
|
132
|
+
continue;
|
|
133
|
+
const leaf = aff.id.startsWith(`${nodePath}.`) ? aff.id.slice(nodePath.length + 1) : aff.id;
|
|
134
|
+
handlers[leaf] = wrap(aff.id);
|
|
135
|
+
}
|
|
136
|
+
return handlers;
|
|
137
|
+
}
|
|
138
|
+
function remountForCurrentPage() {
|
|
139
|
+
if (byo || !nodes)
|
|
140
|
+
return;
|
|
141
|
+
for (const handle of autoHandles.splice(0))
|
|
142
|
+
handle.unregister();
|
|
143
|
+
const page = session.node;
|
|
144
|
+
for (const node of Object.values(nodes)) {
|
|
145
|
+
if (node.page !== page)
|
|
146
|
+
continue;
|
|
147
|
+
if (node.kind !== 'page' && node.kind !== 'area')
|
|
148
|
+
continue;
|
|
149
|
+
if (!autoMountable(node.path))
|
|
150
|
+
continue;
|
|
151
|
+
const handlers = handlersForNode(node.path);
|
|
152
|
+
if (Object.keys(handlers).length > 0) {
|
|
153
|
+
autoHandles.push(session.registerToolGroup(node.path, { handlers }));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
mountedPage = page;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Keep the mount current with the cursor BEFORE a drive fires — a prior
|
|
160
|
+
* back()/stimulus/nav may have moved the page while the old page's tools are
|
|
161
|
+
* still mounted. settle() re-syncs AFTER; this covers the before.
|
|
162
|
+
*/
|
|
163
|
+
function syncMount() {
|
|
164
|
+
if (!byo && mountedPage !== session.node)
|
|
165
|
+
remountForCurrentPage();
|
|
166
|
+
}
|
|
167
|
+
// --- settle: drain microtasks + keep mounts synced with the cursor ---------
|
|
168
|
+
const sig = () => `${session.stateVersion}:${session.structureVersion}:${session.pending().length}:${session.node}`;
|
|
169
|
+
async function drainMicrotasks() {
|
|
170
|
+
for (let k = 0; k < 8; k++)
|
|
171
|
+
await Promise.resolve();
|
|
172
|
+
}
|
|
173
|
+
async function settle() {
|
|
174
|
+
for (let i = 0; i < SETTLE_CAP; i++) {
|
|
175
|
+
if (!byo && mountedPage !== session.node)
|
|
176
|
+
remountForCurrentPage();
|
|
177
|
+
const before = sig();
|
|
178
|
+
await drainMicrotasks();
|
|
179
|
+
const after = sig();
|
|
180
|
+
const mounted = byo || mountedPage === session.node;
|
|
181
|
+
if (before === after && mounted && session.pending().length === 0) {
|
|
182
|
+
if (opts.strict)
|
|
183
|
+
assertNoDrift();
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
throw new Error(`hcifootprint/testing: the app did not settle after ${SETTLE_CAP} rounds — an async mock never ` +
|
|
188
|
+
`resolved, or a resolver keeps re-firing. Resolvers must be synchronous; observe pending windows ` +
|
|
189
|
+
`with user.fireRaw instead.`);
|
|
190
|
+
}
|
|
191
|
+
function assertNoDrift() {
|
|
192
|
+
const drift = collectDrift();
|
|
193
|
+
if (drift.length > 0) {
|
|
194
|
+
const first = drift[0];
|
|
195
|
+
throw new Error(`hcifootprint/testing: declared-effect drift on “${first.affordanceId}” — it claims to write ` +
|
|
196
|
+
`${first.declaredWrites.map((k) => `“${k}”`).join(', ')} but the mock's delta did not cover that. ` +
|
|
197
|
+
`The graph drifted from the handler. (strict mode)`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// --- drift report ----------------------------------------------------------
|
|
201
|
+
function collectDrift() {
|
|
202
|
+
const out = [];
|
|
203
|
+
for (const t of session.transitions()) {
|
|
204
|
+
if (t.effectVerified === false && t.cause.kind === 'fired' && t.cause.affordanceId) {
|
|
205
|
+
out.push({
|
|
206
|
+
transitionId: t.id,
|
|
207
|
+
affordanceId: t.cause.affordanceId,
|
|
208
|
+
declaredWrites: [...(affordances?.[t.cause.affordanceId]?.effect?.writes ?? [])],
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return out;
|
|
213
|
+
}
|
|
214
|
+
function report() {
|
|
215
|
+
const effectDrift = collectDrift();
|
|
216
|
+
const unevaluatedGuards = [];
|
|
217
|
+
for (const t of session.transitions()) {
|
|
218
|
+
if (t.guardUnevaluated && t.guardUnevaluated.length > 0 && t.cause.affordanceId) {
|
|
219
|
+
unevaluatedGuards.push({
|
|
220
|
+
transitionId: t.id,
|
|
221
|
+
affordanceId: t.cause.affordanceId,
|
|
222
|
+
keys: [...t.guardUnevaluated],
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return { ok: effectDrift.length === 0, effectDrift, unevaluatedGuards, gaps: session.gaps() };
|
|
227
|
+
}
|
|
228
|
+
// --- helpers for assertions -----------------------------------------------
|
|
229
|
+
function resolveId(id) {
|
|
230
|
+
const resolved = affordanceById(id);
|
|
231
|
+
return resolved ? resolved.id : id;
|
|
232
|
+
}
|
|
233
|
+
function deepEqual(a, b) {
|
|
234
|
+
if (a === b)
|
|
235
|
+
return true;
|
|
236
|
+
if (typeof a !== typeof b || a === null || b === null || typeof a !== 'object')
|
|
237
|
+
return false;
|
|
238
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
239
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length)
|
|
240
|
+
return false;
|
|
241
|
+
return a.every((item, i) => deepEqual(item, b[i]));
|
|
242
|
+
}
|
|
243
|
+
const ak = Object.keys(a);
|
|
244
|
+
const bk = Object.keys(b);
|
|
245
|
+
if (ak.length !== bk.length)
|
|
246
|
+
return false;
|
|
247
|
+
return ak.every((k) => deepEqual(a[k], b[k]));
|
|
248
|
+
}
|
|
249
|
+
// --- initial mount ---------------------------------------------------------
|
|
250
|
+
if (!byo)
|
|
251
|
+
remountForCurrentPage();
|
|
252
|
+
// --- assembled surface -----------------------------------------------------
|
|
253
|
+
const app = {
|
|
254
|
+
session,
|
|
255
|
+
get node() {
|
|
256
|
+
return session.node;
|
|
257
|
+
},
|
|
258
|
+
get version() {
|
|
259
|
+
return session.version;
|
|
260
|
+
},
|
|
261
|
+
state: () => session.state(),
|
|
262
|
+
warnings: () => [...warnings],
|
|
263
|
+
clock,
|
|
264
|
+
advanceTime: (ms) => clock.advance(ms),
|
|
265
|
+
user: {
|
|
266
|
+
async fire(affordanceId, fireOpts) {
|
|
267
|
+
syncMount();
|
|
268
|
+
const result = session.fire(resolveId(affordanceId), {
|
|
269
|
+
source: 'user',
|
|
270
|
+
...(fireOpts?.payload !== undefined ? { payload: fireOpts.payload } : {}),
|
|
271
|
+
...(fireOpts?.instance !== undefined ? { instance: fireOpts.instance } : {}),
|
|
272
|
+
});
|
|
273
|
+
await settle();
|
|
274
|
+
if (!result.ok) {
|
|
275
|
+
throw new Error(`hcifootprint/testing: user.fire('${affordanceId}') was refused — ${result.reason}. ` +
|
|
276
|
+
`Use user.tryFire to inspect a rejection without throwing.`);
|
|
277
|
+
}
|
|
278
|
+
return result.transition;
|
|
279
|
+
},
|
|
280
|
+
async tryFire(affordanceId, fireOpts) {
|
|
281
|
+
syncMount();
|
|
282
|
+
const result = session.fire(resolveId(affordanceId), {
|
|
283
|
+
source: 'user',
|
|
284
|
+
...(fireOpts?.payload !== undefined ? { payload: fireOpts.payload } : {}),
|
|
285
|
+
...(fireOpts?.instance !== undefined ? { instance: fireOpts.instance } : {}),
|
|
286
|
+
});
|
|
287
|
+
await settle();
|
|
288
|
+
return result;
|
|
289
|
+
},
|
|
290
|
+
fireRaw(affordanceId, fireOpts) {
|
|
291
|
+
syncMount();
|
|
292
|
+
return session.fire(resolveId(affordanceId), {
|
|
293
|
+
source: 'user',
|
|
294
|
+
...(fireOpts?.payload !== undefined ? { payload: fireOpts.payload } : {}),
|
|
295
|
+
...(fireOpts?.instance !== undefined ? { instance: fireOpts.instance } : {}),
|
|
296
|
+
});
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
agent: {
|
|
300
|
+
tools: () => port.tools(),
|
|
301
|
+
async whatsHere() {
|
|
302
|
+
syncMount();
|
|
303
|
+
const result = port.call(sanitize(`${graphId}.whats_here`));
|
|
304
|
+
await settle();
|
|
305
|
+
return result;
|
|
306
|
+
},
|
|
307
|
+
async skill(skillId, args) {
|
|
308
|
+
syncMount();
|
|
309
|
+
const result = port.call(sanitize(`${graphId}.skill.${skillId}`), args);
|
|
310
|
+
await settle();
|
|
311
|
+
return foldProduced(result);
|
|
312
|
+
},
|
|
313
|
+
async do(action, args) {
|
|
314
|
+
syncMount();
|
|
315
|
+
const result = port.call(sanitize(`${graphId}.do_action`), { action, ...(args ?? {}) });
|
|
316
|
+
await settle();
|
|
317
|
+
return foldProduced(result);
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
stimulus(patch, stimulusOpts) {
|
|
321
|
+
session.updateState(patch, { stimulus: stimulusOpts?.stimulus ?? 'push' });
|
|
322
|
+
},
|
|
323
|
+
back(page) {
|
|
324
|
+
session.sync(page, { stimulus: 'navigation', principal: 'user' });
|
|
325
|
+
},
|
|
326
|
+
open(path, openOpts) {
|
|
327
|
+
// Re-opening the same node replaces the prior mount — release it first so
|
|
328
|
+
// its registration + presence handle can never be orphaned.
|
|
329
|
+
openHandles.get(path)?.unregister();
|
|
330
|
+
const handlers = handlersForNode(path);
|
|
331
|
+
const handle = session.registerToolGroup(path, {
|
|
332
|
+
handlers,
|
|
333
|
+
visible: true,
|
|
334
|
+
...(openOpts?.instance !== undefined ? { instance: openOpts.instance } : {}),
|
|
335
|
+
});
|
|
336
|
+
session.show(path);
|
|
337
|
+
openHandles.set(path, handle);
|
|
338
|
+
return handle;
|
|
339
|
+
},
|
|
340
|
+
close(path) {
|
|
341
|
+
const handle = openHandles.get(path);
|
|
342
|
+
if (handle) {
|
|
343
|
+
handle.unregister();
|
|
344
|
+
openHandles.delete(path);
|
|
345
|
+
}
|
|
346
|
+
session.setVisible(path, false);
|
|
347
|
+
},
|
|
348
|
+
settled: settle,
|
|
349
|
+
report,
|
|
350
|
+
expectOn(page) {
|
|
351
|
+
if (session.node !== page) {
|
|
352
|
+
throw new Error(`hcifootprint/testing: expected to be on “${page}”, but on “${session.node}”.`);
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
expectState(partial) {
|
|
356
|
+
const state = session.state();
|
|
357
|
+
for (const [key, value] of Object.entries(partial)) {
|
|
358
|
+
if (!deepEqual(state[key], value)) {
|
|
359
|
+
throw new Error(`hcifootprint/testing: expected state “${key}” to be ${JSON.stringify(value)}, ` +
|
|
360
|
+
`but it is ${JSON.stringify(state[key])}.`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
expectAvailable(affordanceId) {
|
|
365
|
+
const id = resolveId(affordanceId);
|
|
366
|
+
const ids = session.available().edges.map((e) => e.affordanceId);
|
|
367
|
+
if (!ids.includes(id)) {
|
|
368
|
+
throw new Error(`hcifootprint/testing: expected “${affordanceId}” to be available on “${session.node}”, ` +
|
|
369
|
+
`but available: ${ids.length > 0 ? ids.join(', ') : '(none)'}.`);
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
expectRejected(result, reason) {
|
|
373
|
+
if (result.ok) {
|
|
374
|
+
throw new Error(`hcifootprint/testing: expected a rejection, but the fire succeeded.`);
|
|
375
|
+
}
|
|
376
|
+
if (reason !== undefined && result.reason !== reason) {
|
|
377
|
+
throw new Error(`hcifootprint/testing: expected rejection “${String(reason)}”, but got “${result.reason}”.`);
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
expectSkillCompleted(skillId) {
|
|
381
|
+
const done = session.frames().some((f) => f.skillId === skillId && f.status === 'completed');
|
|
382
|
+
if (!done) {
|
|
383
|
+
const seen = session
|
|
384
|
+
.frames()
|
|
385
|
+
.filter((f) => f.skillId === skillId)
|
|
386
|
+
.map((f) => f.status);
|
|
387
|
+
throw new Error(`hcifootprint/testing: expected skill “${skillId}” to have completed, ` +
|
|
388
|
+
`but its frames were: ${seen.length > 0 ? seen.join(', ') : '(never opened)'}.`);
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
expectClean(cleanOpts) {
|
|
392
|
+
const r = report();
|
|
393
|
+
if (r.effectDrift.length > 0) {
|
|
394
|
+
const f = r.effectDrift[0];
|
|
395
|
+
throw new Error(`hcifootprint/testing: declared-effect drift on “${f.affordanceId}” (declares ` +
|
|
396
|
+
`${f.declaredWrites.map((k) => `“${k}”`).join(', ')}, but the delta did not cover it) — ` +
|
|
397
|
+
`${r.effectDrift.length} total. The graph drifted from the handler.`);
|
|
398
|
+
}
|
|
399
|
+
if (cleanOpts?.includeGaps && r.gaps.length > 0) {
|
|
400
|
+
throw new Error(`hcifootprint/testing: ${r.gaps.length} gap(s) recorded (refused fires / reported unmet demand).`);
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
};
|
|
404
|
+
/** Fold the handler's produced data into an agent ServeResult (mirrors the MCP server). */
|
|
405
|
+
function foldProduced(result) {
|
|
406
|
+
const transitionId = result['transitionId'];
|
|
407
|
+
if (typeof transitionId === 'string') {
|
|
408
|
+
const produced = session.producedFor(transitionId);
|
|
409
|
+
if (produced !== undefined)
|
|
410
|
+
return { ...result, data: produced };
|
|
411
|
+
}
|
|
412
|
+
return result;
|
|
413
|
+
}
|
|
414
|
+
return app;
|
|
415
|
+
}
|
|
416
|
+
//# sourceMappingURL=harness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harness.js","sourceRoot":"","sources":["../../src/testing/harness.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hcifootprint/testing — a headless workflow tester for your navigation graph.
|
|
3
|
+
*
|
|
4
|
+
* The graph is a second artifact you maintain alongside the real app, so it
|
|
5
|
+
* drifts as the app changes. This subpath catches that drift before production,
|
|
6
|
+
* in two layers:
|
|
7
|
+
*
|
|
8
|
+
* lintGraph(graph) — STATIC. No app, no run, no test code. The cheap CI
|
|
9
|
+
* gate: flags stale logic provable from the graph alone
|
|
10
|
+
* (a control gated on state nothing produces, a guard
|
|
11
|
+
* that can never be true, a skill that can never finish,
|
|
12
|
+
* a page nothing can reach).
|
|
13
|
+
*
|
|
14
|
+
* testApp(graph) — DYNAMIC. "Playwright for your interaction logic, minus
|
|
15
|
+
* the browser." Write mock handlers, drive the REAL
|
|
16
|
+
* session as a user or as the agent, and let the honesty
|
|
17
|
+
* markers (effectVerified) surface behavioral drift — a
|
|
18
|
+
* handler no longer doing what the graph declares.
|
|
19
|
+
*
|
|
20
|
+
* Zero new dependencies; tree-shakeable; imports the real Session (never a
|
|
21
|
+
* parallel simulation) and never the MCP SDK. Honest boundary: this tests
|
|
22
|
+
* interaction LOGIC above the binding — not pixels, not the DOM. It complements
|
|
23
|
+
* Playwright; it does not replace it.
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* import { testApp, lintGraph } from 'hcifootprint/testing';
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export { lintGraph, formatFindings, expectNoStaleLogic } from './model/lint.js';
|
|
30
|
+
export type { LintFinding, LintOptions, LintCode, LintSeverity } from './model/lint.js';
|
|
31
|
+
export { checkGraph } from './model/check.js';
|
|
32
|
+
export type { GraphHealth, SkillHealth, DriftType } from './model/check.js';
|
|
33
|
+
export { testApp } from './harness.js';
|
|
34
|
+
export type { TestApp, TestAppOptions, TestClock, DriftReport, Resolver, ResolverContext, ResolverOutcome, } from './harness.js';
|
|
35
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAChF,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACxF,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,YAAY,EACV,OAAO,EACP,cAAc,EACd,SAAS,EACT,WAAW,EACX,QAAQ,EACR,eAAe,EACf,eAAe,GAChB,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hcifootprint/testing — a headless workflow tester for your navigation graph.
|
|
3
|
+
*
|
|
4
|
+
* The graph is a second artifact you maintain alongside the real app, so it
|
|
5
|
+
* drifts as the app changes. This subpath catches that drift before production,
|
|
6
|
+
* in two layers:
|
|
7
|
+
*
|
|
8
|
+
* lintGraph(graph) — STATIC. No app, no run, no test code. The cheap CI
|
|
9
|
+
* gate: flags stale logic provable from the graph alone
|
|
10
|
+
* (a control gated on state nothing produces, a guard
|
|
11
|
+
* that can never be true, a skill that can never finish,
|
|
12
|
+
* a page nothing can reach).
|
|
13
|
+
*
|
|
14
|
+
* testApp(graph) — DYNAMIC. "Playwright for your interaction logic, minus
|
|
15
|
+
* the browser." Write mock handlers, drive the REAL
|
|
16
|
+
* session as a user or as the agent, and let the honesty
|
|
17
|
+
* markers (effectVerified) surface behavioral drift — a
|
|
18
|
+
* handler no longer doing what the graph declares.
|
|
19
|
+
*
|
|
20
|
+
* Zero new dependencies; tree-shakeable; imports the real Session (never a
|
|
21
|
+
* parallel simulation) and never the MCP SDK. Honest boundary: this tests
|
|
22
|
+
* interaction LOGIC above the binding — not pixels, not the DOM. It complements
|
|
23
|
+
* Playwright; it does not replace it.
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* import { testApp, lintGraph } from 'hcifootprint/testing';
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export { lintGraph, formatFindings, expectNoStaleLogic } from './model/lint.js';
|
|
30
|
+
export { checkGraph } from './model/check.js';
|
|
31
|
+
export { testApp } from './harness.js';
|
|
32
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEhF,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hcifootprint/testing/lint — the STATIC linter, in isolation.
|
|
3
|
+
*
|
|
4
|
+
* Import from here (not hcifootprint/testing) for a guaranteed engine-free CI
|
|
5
|
+
* lint: this entry pulls in ONLY the pure model (compiled-graph types + the
|
|
6
|
+
* guard helpers), never the Session/footprint engine. So a plain `node` or
|
|
7
|
+
* `tsx` lint script — no bundler, no tree-shaking — still loads nothing heavy.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { lintGraph, expectNoStaleLogic } from 'hcifootprint/testing/lint';
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* (The full hcifootprint/testing barrel re-exports these too, alongside the
|
|
14
|
+
* driver — reach for it when you also want testApp.)
|
|
15
|
+
*/
|
|
16
|
+
export { lintGraph, formatFindings, expectNoStaleLogic } from './model/lint.js';
|
|
17
|
+
export type { LintFinding, LintOptions, LintCode, LintSeverity } from './model/lint.js';
|
|
18
|
+
export { checkGraph } from './model/check.js';
|
|
19
|
+
export type { GraphHealth, SkillHealth, DriftType } from './model/check.js';
|
|
20
|
+
//# sourceMappingURL=lint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../../src/testing/lint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAChF,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACxF,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hcifootprint/testing/lint — the STATIC linter, in isolation.
|
|
3
|
+
*
|
|
4
|
+
* Import from here (not hcifootprint/testing) for a guaranteed engine-free CI
|
|
5
|
+
* lint: this entry pulls in ONLY the pure model (compiled-graph types + the
|
|
6
|
+
* guard helpers), never the Session/footprint engine. So a plain `node` or
|
|
7
|
+
* `tsx` lint script — no bundler, no tree-shaking — still loads nothing heavy.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { lintGraph, expectNoStaleLogic } from 'hcifootprint/testing/lint';
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* (The full hcifootprint/testing barrel re-exports these too, alongside the
|
|
14
|
+
* driver — reach for it when you also want testApp.)
|
|
15
|
+
*/
|
|
16
|
+
export { lintGraph, formatFindings, expectNoStaleLogic } from './model/lint.js';
|
|
17
|
+
export { checkGraph } from './model/check.js';
|
|
18
|
+
//# sourceMappingURL=lint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lint.js","sourceRoot":"","sources":["../../src/testing/lint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEhF,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { LintFinding, LintOptions } from './lint.js';
|
|
2
|
+
import type { NavigationGraph } from '../../tree/types.js';
|
|
3
|
+
/** The human-recognisable drift buckets a finding falls into. */
|
|
4
|
+
export type DriftType = 'control' | 'page' | 'flow' | 'note';
|
|
5
|
+
export interface SkillHealth {
|
|
6
|
+
id: string;
|
|
7
|
+
/** No error-severity finding blocks this skill from completing. */
|
|
8
|
+
feasible: boolean;
|
|
9
|
+
/** State keys that block it (from the flow findings), when not feasible. */
|
|
10
|
+
blockedOn: string[];
|
|
11
|
+
}
|
|
12
|
+
export interface GraphHealth {
|
|
13
|
+
/** True when there are no error-severity findings — the release-readiness signal. */
|
|
14
|
+
ok: boolean;
|
|
15
|
+
errors: number;
|
|
16
|
+
warnings: number;
|
|
17
|
+
/** The full lint output (errors, warnings, and advisory notes). */
|
|
18
|
+
findings: LintFinding[];
|
|
19
|
+
/** Findings grouped by drift type — control (buttons/inputs), page, flow, note. */
|
|
20
|
+
byType: Record<DriftType, LintFinding[]>;
|
|
21
|
+
/** Per-skill feasibility rollup. */
|
|
22
|
+
skills: SkillHealth[];
|
|
23
|
+
/** Pages nothing can navigate to. */
|
|
24
|
+
unreachablePages: string[];
|
|
25
|
+
/** A ready-to-print, plain-language report. Empty-ish (a ✓ line) when healthy. */
|
|
26
|
+
summary: string;
|
|
27
|
+
}
|
|
28
|
+
/** One-call health check: lint + group by drift type + per-skill rollup + a printable summary. */
|
|
29
|
+
export declare function checkGraph(graph: NavigationGraph, opts?: LintOptions): GraphHealth;
|
|
30
|
+
//# sourceMappingURL=check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../../src/testing/model/check.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,iEAAiE;AACjE,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAE7D,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,mEAAmE;IACnE,QAAQ,EAAE,OAAO,CAAC;IAClB,4EAA4E;IAC5E,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,qFAAqF;IACrF,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE,WAAW,EAAE,CAAC,CAAC;IACzC,oCAAoC;IACpC,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,qCAAqC;IACrC,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,kFAAkF;IAClF,OAAO,EAAE,MAAM,CAAC;CACjB;AAoBD,kGAAkG;AAClG,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,WAAW,CAiClF"}
|