taphound 0.2.0-dev.11 → 0.2.0-dev.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/assets/skills/taphound-journey-generator/SKILL.md +7 -0
- package/assets/skills/taphound-journey-generator/scripts/envelope.mjs +18 -1
- package/assets/skills/taphound-verify-change/references/preserve.md +15 -7
- package/assets/skills/taphound-verify-change/scripts/ui-refactor.mjs +1 -1
- package/dist/adapters/appium/appium-ui-snapshot-provider.d.ts +8 -0
- package/dist/adapters/appium/appium-ui-snapshot-provider.js +123 -54
- package/dist/adapters/filesystem/diagnostics-journal.d.ts +17 -0
- package/dist/adapters/filesystem/diagnostics-journal.js +130 -0
- package/dist/application/diagnostics/diagnostics-exporter.d.ts +29 -0
- package/dist/application/diagnostics/diagnostics-exporter.js +283 -0
- package/dist/application/diagnostics/ui-capture-telemetry.d.ts +16 -0
- package/dist/application/diagnostics/ui-capture-telemetry.js +81 -0
- package/dist/application/report/report-writer.d.ts +5 -0
- package/dist/application/report/report-writer.js +16 -0
- package/dist/application/runtime/verify-runtime.js +23 -1
- package/dist/application/ui/observed-ui-snapshot-provider.d.ts +15 -0
- package/dist/application/ui/observed-ui-snapshot-provider.js +66 -0
- package/dist/application/ui/ui-stability-probe.d.ts +6 -0
- package/dist/application/ui/ui-stability-probe.js +21 -5
- package/dist/application/wait/idle-waiter.js +19 -4
- package/dist/cli/commands/diagnose.d.ts +3 -0
- package/dist/cli/commands/diagnose.js +84 -0
- package/dist/cli/commands/verify.js +55 -3
- package/dist/cli/dependencies.d.ts +11 -0
- package/dist/cli/dependencies.js +58 -5
- package/dist/cli/diagnostics-recorder.d.ts +31 -0
- package/dist/cli/diagnostics-recorder.js +82 -0
- package/dist/cli/main.d.ts +1 -1
- package/dist/cli/main.js +58 -1
- package/dist/cli/program.js +3 -1
- package/dist/domain/contract.d.ts +6 -6
- package/dist/domain/diagnostics.d.ts +1396 -0
- package/dist/domain/diagnostics.js +193 -0
- package/dist/domain/failure.js +1 -1
- package/dist/domain/report.d.ts +3 -3
- package/dist/domain/verify-receipt.d.ts +17 -0
- package/dist/domain/verify-receipt.js +17 -0
- package/dist/domain/workspace.d.ts +2 -0
- package/dist/domain/workspace.js +2 -0
- package/dist/ports/diagnostics.d.ts +15 -0
- package/dist/ports/diagnostics.js +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { FAILURE_CODES } from "./failure.js";
|
|
3
|
+
import { RuntimeBackendChoiceSchema } from "./runtime.js";
|
|
4
|
+
import { UiBackendIdSchema, UiBackendSelectionSchema } from "./ui-backend.js";
|
|
5
|
+
import { UiCacheTelemetrySchema } from "./ui-cache.js";
|
|
6
|
+
/**
|
|
7
|
+
* Diagnostics exist so a user can attach evidence to TapHound feedback
|
|
8
|
+
* without leaking their project. Every schema here is an allowlist: a field
|
|
9
|
+
* that is not declared cannot reach the journal or an exported bundle, and
|
|
10
|
+
* no field carries a path, package, Activity, Journey name, locator value,
|
|
11
|
+
* device serial, or free-text message.
|
|
12
|
+
*/
|
|
13
|
+
export const DIAGNOSTICS_EVENTS_FILE = "events.jsonl";
|
|
14
|
+
export const DIAGNOSTICS_EVENTS_ROTATED_FILE = "events.1.jsonl";
|
|
15
|
+
export const DIAGNOSTICS_SALT_FILE = "salt";
|
|
16
|
+
export const DIAGNOSTICS_DISABLE_ENV_VAR = "TAPHOUND_DIAGNOSTICS";
|
|
17
|
+
/** Upper bounds (exclusive) of the capture latency buckets; the last bucket is open. */
|
|
18
|
+
export const UI_CAPTURE_LATENCY_BUCKETS_MS = [250, 500, 1000, 2000, 5000];
|
|
19
|
+
export const UI_CAPTURE_FAILURE_KINDS = [
|
|
20
|
+
"timeout",
|
|
21
|
+
"cancelled",
|
|
22
|
+
"http4xx",
|
|
23
|
+
"http5xx",
|
|
24
|
+
"error"
|
|
25
|
+
];
|
|
26
|
+
const CountSchema = z.number().int().nonnegative();
|
|
27
|
+
const DurationSchema = z.number().nonnegative();
|
|
28
|
+
const VersionSchema = z.string().regex(/^[\w.+-]{1,64}$/);
|
|
29
|
+
const TokenSchema = z.string().regex(/^[a-zA-Z][a-zA-Z\d]{0,31}$/);
|
|
30
|
+
export const DiagnosticsHostSchema = z.strictObject({
|
|
31
|
+
platform: TokenSchema,
|
|
32
|
+
arch: TokenSchema,
|
|
33
|
+
node: VersionSchema
|
|
34
|
+
});
|
|
35
|
+
export const UiBackendTelemetrySchema = z.strictObject({
|
|
36
|
+
backend: UiBackendIdSchema,
|
|
37
|
+
captures: CountSchema,
|
|
38
|
+
failures: z.strictObject({
|
|
39
|
+
timeout: CountSchema,
|
|
40
|
+
cancelled: CountSchema,
|
|
41
|
+
http4xx: CountSchema,
|
|
42
|
+
http5xx: CountSchema,
|
|
43
|
+
error: CountSchema
|
|
44
|
+
}),
|
|
45
|
+
totalMs: DurationSchema,
|
|
46
|
+
maxMs: DurationSchema,
|
|
47
|
+
latencyBuckets: z.array(CountSchema).length(UI_CAPTURE_LATENCY_BUCKETS_MS.length + 1),
|
|
48
|
+
sessionRecoveries: CountSchema,
|
|
49
|
+
sessionRecoveryFailures: CountSchema
|
|
50
|
+
});
|
|
51
|
+
/** One journal line: a finished CLI invocation. */
|
|
52
|
+
export const CommandEventSchema = z.strictObject({
|
|
53
|
+
version: z.literal(1),
|
|
54
|
+
kind: z.literal("command"),
|
|
55
|
+
at: z.iso.datetime(),
|
|
56
|
+
taphoundVersion: VersionSchema,
|
|
57
|
+
host: DiagnosticsHostSchema,
|
|
58
|
+
command: z.string().regex(/^[a-z][a-z-]*(?: [a-z][a-z-]*){0,3}$/),
|
|
59
|
+
flags: z.array(TokenSchema),
|
|
60
|
+
durationMs: DurationSchema,
|
|
61
|
+
exitCode: z.number().int(),
|
|
62
|
+
status: TokenSchema.optional(),
|
|
63
|
+
failureCode: z.enum(FAILURE_CODES).optional(),
|
|
64
|
+
runId: z.string().regex(/^[\w.-]{1,128}$/).optional(),
|
|
65
|
+
ui: z.array(UiBackendTelemetrySchema)
|
|
66
|
+
});
|
|
67
|
+
const RunAliasSchema = z.string().regex(/^run#\d+$/);
|
|
68
|
+
const ActivityAliasSchema = z.string().regex(/^activity#\d+$/);
|
|
69
|
+
const DeviceAliasSchema = z.string().regex(/^device#\d+$/);
|
|
70
|
+
const StepResultSchema = z.enum(["passed", "failed", "notRun", "manualRequired"]);
|
|
71
|
+
const ActivityCheckSummarySchema = z.strictObject({
|
|
72
|
+
status: StepResultSchema,
|
|
73
|
+
expected: ActivityAliasSchema,
|
|
74
|
+
actual: ActivityAliasSchema.optional()
|
|
75
|
+
});
|
|
76
|
+
export const StepSummarySchema = z.strictObject({
|
|
77
|
+
index: CountSchema,
|
|
78
|
+
action: z.enum([
|
|
79
|
+
"click",
|
|
80
|
+
"longClick",
|
|
81
|
+
"inputText",
|
|
82
|
+
"swipe",
|
|
83
|
+
"scrollTo",
|
|
84
|
+
"back",
|
|
85
|
+
"wait",
|
|
86
|
+
"bridge"
|
|
87
|
+
]),
|
|
88
|
+
status: StepResultSchema,
|
|
89
|
+
device: DeviceAliasSchema.optional(),
|
|
90
|
+
durationMs: DurationSchema,
|
|
91
|
+
locator: z.strictObject({
|
|
92
|
+
status: z.enum(["found", "failed", "notRun"]),
|
|
93
|
+
matchedBy: z.enum(["resourceId", "text", "contentDescription", "anchor"]).optional(),
|
|
94
|
+
requestedFields: z.array(z.enum(["resourceId", "text", "contentDescription", "index", "within"])),
|
|
95
|
+
locatorId: z.string().regex(/^[a-f\d]{16}$/).optional(),
|
|
96
|
+
anchorStatus: z.enum(["resolved", "locatorFallback", "failed"]).optional(),
|
|
97
|
+
fallbackUsed: z.boolean()
|
|
98
|
+
}).optional(),
|
|
99
|
+
idle: z.strictObject({
|
|
100
|
+
status: z.enum(["stable", "timeout", "cancelled", "notRun"]),
|
|
101
|
+
polls: CountSchema,
|
|
102
|
+
durationMs: DurationSchema.optional(),
|
|
103
|
+
samplingDurationMs: DurationSchema.optional(),
|
|
104
|
+
strategy: z.enum(["hybrid", "layoutDiff", "frameStats", "structural"]).optional(),
|
|
105
|
+
backend: z.enum(["uiautomator", "androidCli", "gfxFrameStats", "mobileMcp"]).optional(),
|
|
106
|
+
fallbackUsed: z.boolean().optional(),
|
|
107
|
+
frameActivityDetected: z.boolean().optional(),
|
|
108
|
+
lastDiffCount: CountSchema.optional()
|
|
109
|
+
}).optional(),
|
|
110
|
+
activity: z.strictObject({
|
|
111
|
+
before: ActivityCheckSummarySchema,
|
|
112
|
+
after: ActivityCheckSummarySchema
|
|
113
|
+
}).optional(),
|
|
114
|
+
expectation: z.strictObject({
|
|
115
|
+
type: z.enum(["activity", "element", "logcat", "logcatEvent"]),
|
|
116
|
+
status: StepResultSchema,
|
|
117
|
+
code: z.enum(FAILURE_CODES).optional()
|
|
118
|
+
}).optional(),
|
|
119
|
+
scroll: z.strictObject({
|
|
120
|
+
swipesUsed: CountSchema,
|
|
121
|
+
maxSwipes: CountSchema
|
|
122
|
+
}).optional()
|
|
123
|
+
});
|
|
124
|
+
const LayerStatusSchema = StepResultSchema;
|
|
125
|
+
export const RunSummarySchema = z.strictObject({
|
|
126
|
+
run: RunAliasSchema,
|
|
127
|
+
status: z.enum(["passed", "failed", "error", "manualRequired"]),
|
|
128
|
+
durationMs: DurationSchema,
|
|
129
|
+
journey: z.string().regex(/^journey#\d+$/),
|
|
130
|
+
devices: z.array(z.strictObject({
|
|
131
|
+
device: DeviceAliasSchema,
|
|
132
|
+
uiBackend: z.strictObject({
|
|
133
|
+
id: UiBackendIdSchema,
|
|
134
|
+
adapterVersion: VersionSchema,
|
|
135
|
+
engineVersion: VersionSchema.optional()
|
|
136
|
+
}).optional(),
|
|
137
|
+
uiCache: UiCacheTelemetrySchema.optional()
|
|
138
|
+
})),
|
|
139
|
+
tools: z.partialRecord(z.enum(["node", "adb", "android"]), VersionSchema),
|
|
140
|
+
layers: z.strictObject({
|
|
141
|
+
run: LayerStatusSchema,
|
|
142
|
+
structural: LayerStatusSchema,
|
|
143
|
+
activityCheckpoint: LayerStatusSchema,
|
|
144
|
+
explicitExpect: LayerStatusSchema,
|
|
145
|
+
collection: LayerStatusSchema
|
|
146
|
+
}),
|
|
147
|
+
primaryFailure: z.strictObject({
|
|
148
|
+
code: z.enum(FAILURE_CODES),
|
|
149
|
+
phase: TokenSchema,
|
|
150
|
+
stepIndex: CountSchema.optional()
|
|
151
|
+
}).optional(),
|
|
152
|
+
secondaryErrorCodes: z.array(z.enum(FAILURE_CODES)),
|
|
153
|
+
fallbackUsed: z.boolean(),
|
|
154
|
+
logcatEvidence: z.array(z.strictObject({
|
|
155
|
+
device: DeviceAliasSchema,
|
|
156
|
+
droppedLines: CountSchema,
|
|
157
|
+
droppedBytes: CountSchema
|
|
158
|
+
})),
|
|
159
|
+
steps: z.array(StepSummarySchema)
|
|
160
|
+
});
|
|
161
|
+
export const DiagnosticsConfigSummarySchema = z.strictObject({
|
|
162
|
+
idle: z.strictObject({
|
|
163
|
+
strategy: z.enum(["hybrid", "layoutDiff", "frameStats", "structural"]),
|
|
164
|
+
pollIntervalMs: CountSchema,
|
|
165
|
+
stablePolls: CountSchema,
|
|
166
|
+
timeoutMs: CountSchema,
|
|
167
|
+
ignoreCursorBlink: z.boolean().optional(),
|
|
168
|
+
ignoreLayoutDrift: z.boolean().optional(),
|
|
169
|
+
deviceProfiles: CountSchema
|
|
170
|
+
}),
|
|
171
|
+
ui: z.strictObject({
|
|
172
|
+
backend: UiBackendSelectionSchema,
|
|
173
|
+
snapshotTimeoutMs: CountSchema.optional(),
|
|
174
|
+
cacheEnabled: z.boolean().optional()
|
|
175
|
+
}).optional(),
|
|
176
|
+
runtime: z.strictObject({
|
|
177
|
+
backend: RuntimeBackendChoiceSchema
|
|
178
|
+
}).optional()
|
|
179
|
+
});
|
|
180
|
+
export const DiagnosticsBundleSchema = z.strictObject({
|
|
181
|
+
version: z.literal(1),
|
|
182
|
+
generatedAt: z.iso.datetime(),
|
|
183
|
+
taphoundVersion: VersionSchema,
|
|
184
|
+
host: DiagnosticsHostSchema,
|
|
185
|
+
config: DiagnosticsConfigSummarySchema.optional(),
|
|
186
|
+
journal: z.strictObject({
|
|
187
|
+
events: z.array(CommandEventSchema.extend({
|
|
188
|
+
runId: RunAliasSchema.optional()
|
|
189
|
+
})),
|
|
190
|
+
skippedLines: CountSchema
|
|
191
|
+
}),
|
|
192
|
+
runs: z.array(RunSummarySchema)
|
|
193
|
+
});
|
package/dist/domain/failure.js
CHANGED
package/dist/domain/report.d.ts
CHANGED
|
@@ -150,8 +150,8 @@ export declare const StepReportSchema: z.ZodObject<{
|
|
|
150
150
|
idle: z.ZodOptional<z.ZodObject<{
|
|
151
151
|
status: z.ZodEnum<{
|
|
152
152
|
cancelled: "cancelled";
|
|
153
|
-
notRun: "notRun";
|
|
154
153
|
timeout: "timeout";
|
|
154
|
+
notRun: "notRun";
|
|
155
155
|
stable: "stable";
|
|
156
156
|
}>;
|
|
157
157
|
polls: z.ZodNumber;
|
|
@@ -661,8 +661,8 @@ export declare const TapHoundReportV4Schema: z.ZodObject<{
|
|
|
661
661
|
idle: z.ZodOptional<z.ZodObject<{
|
|
662
662
|
status: z.ZodEnum<{
|
|
663
663
|
cancelled: "cancelled";
|
|
664
|
-
notRun: "notRun";
|
|
665
664
|
timeout: "timeout";
|
|
665
|
+
notRun: "notRun";
|
|
666
666
|
stable: "stable";
|
|
667
667
|
}>;
|
|
668
668
|
polls: z.ZodNumber;
|
|
@@ -1288,8 +1288,8 @@ export declare const TapHoundReportSchema: z.ZodObject<{
|
|
|
1288
1288
|
idle: z.ZodOptional<z.ZodObject<{
|
|
1289
1289
|
status: z.ZodEnum<{
|
|
1290
1290
|
cancelled: "cancelled";
|
|
1291
|
-
notRun: "notRun";
|
|
1292
1291
|
timeout: "timeout";
|
|
1292
|
+
notRun: "notRun";
|
|
1293
1293
|
stable: "stable";
|
|
1294
1294
|
}>;
|
|
1295
1295
|
polls: z.ZodNumber;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const VERIFY_RECEIPT_FILE = "receipt.json";
|
|
3
|
+
/**
|
|
4
|
+
* A process receipt that `verify --journey` writes beside the report it
|
|
5
|
+
* published. `argv` is the normalized invocation TapHound actually ran:
|
|
6
|
+
* absolute project and Journey paths and the selected device serial, so a
|
|
7
|
+
* Workflow checker never has to trust a hand-written record.
|
|
8
|
+
*/
|
|
9
|
+
export declare const VerifyReceiptSchema: z.ZodObject<{
|
|
10
|
+
version: z.ZodLiteral<1>;
|
|
11
|
+
argv: z.ZodArray<z.ZodString>;
|
|
12
|
+
exitCode: z.ZodNumber;
|
|
13
|
+
journeySha256: z.ZodString;
|
|
14
|
+
reportPath: z.ZodString;
|
|
15
|
+
reportSha256: z.ZodString;
|
|
16
|
+
}, z.core.$strict>;
|
|
17
|
+
export type VerifyReceipt = z.infer<typeof VerifyReceiptSchema>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const VERIFY_RECEIPT_FILE = "receipt.json";
|
|
3
|
+
const DigestSchema = z.string().regex(/^[a-f\d]{64}$/);
|
|
4
|
+
/**
|
|
5
|
+
* A process receipt that `verify --journey` writes beside the report it
|
|
6
|
+
* published. `argv` is the normalized invocation TapHound actually ran:
|
|
7
|
+
* absolute project and Journey paths and the selected device serial, so a
|
|
8
|
+
* Workflow checker never has to trust a hand-written record.
|
|
9
|
+
*/
|
|
10
|
+
export const VerifyReceiptSchema = z.strictObject({
|
|
11
|
+
version: z.literal(1),
|
|
12
|
+
argv: z.array(z.string().min(1)).min(1),
|
|
13
|
+
exitCode: z.number().int().min(0).max(4),
|
|
14
|
+
journeySha256: DigestSchema,
|
|
15
|
+
reportPath: z.string().min(1),
|
|
16
|
+
reportSha256: DigestSchema
|
|
17
|
+
});
|
|
@@ -17,6 +17,8 @@ export declare const GENERATIONS_DIR = ".taphound/build/generations";
|
|
|
17
17
|
export declare const JOBS_DIR = ".taphound/build/jobs";
|
|
18
18
|
export declare const DEFAULT_ARTIFACTS_DIR = ".taphound/build/runs";
|
|
19
19
|
export declare const WORKFLOWS_DIR = ".taphound/build/workflows";
|
|
20
|
+
export declare const DIAGNOSTICS_LOG_DIR = ".taphound/build/log";
|
|
21
|
+
export declare const DIAGNOSTICS_EXPORT_DIR = ".taphound/build/diagnostics";
|
|
20
22
|
export declare function workflowManifestPath(caseId: string): string;
|
|
21
23
|
export declare const BUILD_IGNORE_FILE = ".taphound/.gitignore";
|
|
22
24
|
export declare const BUILD_IGNORE_CONTENT = "build/\n";
|
package/dist/domain/workspace.js
CHANGED
|
@@ -19,6 +19,8 @@ export const GENERATIONS_DIR = `${BUILD_DIR}/generations`;
|
|
|
19
19
|
export const JOBS_DIR = `${BUILD_DIR}/jobs`;
|
|
20
20
|
export const DEFAULT_ARTIFACTS_DIR = `${BUILD_DIR}/runs`;
|
|
21
21
|
export const WORKFLOWS_DIR = `${BUILD_DIR}/workflows`;
|
|
22
|
+
export const DIAGNOSTICS_LOG_DIR = `${BUILD_DIR}/log`;
|
|
23
|
+
export const DIAGNOSTICS_EXPORT_DIR = `${BUILD_DIR}/diagnostics`;
|
|
22
24
|
export function workflowManifestPath(caseId) {
|
|
23
25
|
if (!/^[a-z][a-z0-9-]{0,63}$/.test(caseId)) {
|
|
24
26
|
throw new Error("Workflow Case id must be a safe lowercase path component");
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CommandEvent } from "../domain/diagnostics.js";
|
|
2
|
+
import type { UiBackendId } from "../domain/ui-backend.js";
|
|
3
|
+
/** Receives UI backend timing facts; never error text. */
|
|
4
|
+
export interface UiCaptureObserver {
|
|
5
|
+
captured: (backend: UiBackendId, durationMs: number, error?: unknown) => void;
|
|
6
|
+
sessionRecovered: (backend: UiBackendId, succeeded: boolean) => void;
|
|
7
|
+
}
|
|
8
|
+
/** The local, Git-ignored diagnostics journal of one host project. */
|
|
9
|
+
export interface DiagnosticsJournal {
|
|
10
|
+
append: (projectRoot: string, event: CommandEvent) => Promise<void>;
|
|
11
|
+
/** Raw journal lines, oldest first, including the rotated file. */
|
|
12
|
+
readLines: (projectRoot: string) => Promise<readonly string[]>;
|
|
13
|
+
/** A per-project random salt that never leaves the host. */
|
|
14
|
+
salt: (projectRoot: string) => Promise<Buffer>;
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "taphound",
|
|
3
|
-
"version": "0.2.0-dev.
|
|
3
|
+
"version": "0.2.0-dev.12",
|
|
4
4
|
"description": "Deterministic app journey recording and verification",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"type": "module",
|
|
27
27
|
"bin": {
|
|
28
|
-
"taphound": "
|
|
28
|
+
"taphound": "dist/cli/main.js"
|
|
29
29
|
},
|
|
30
30
|
"files": [
|
|
31
31
|
"dist",
|