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.
Files changed (44) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/assets/skills/taphound-journey-generator/SKILL.md +7 -0
  4. package/assets/skills/taphound-journey-generator/scripts/envelope.mjs +18 -1
  5. package/assets/skills/taphound-verify-change/references/preserve.md +15 -7
  6. package/assets/skills/taphound-verify-change/scripts/ui-refactor.mjs +1 -1
  7. package/dist/adapters/appium/appium-ui-snapshot-provider.d.ts +8 -0
  8. package/dist/adapters/appium/appium-ui-snapshot-provider.js +123 -54
  9. package/dist/adapters/filesystem/diagnostics-journal.d.ts +17 -0
  10. package/dist/adapters/filesystem/diagnostics-journal.js +130 -0
  11. package/dist/application/diagnostics/diagnostics-exporter.d.ts +29 -0
  12. package/dist/application/diagnostics/diagnostics-exporter.js +283 -0
  13. package/dist/application/diagnostics/ui-capture-telemetry.d.ts +16 -0
  14. package/dist/application/diagnostics/ui-capture-telemetry.js +81 -0
  15. package/dist/application/report/report-writer.d.ts +5 -0
  16. package/dist/application/report/report-writer.js +16 -0
  17. package/dist/application/runtime/verify-runtime.js +23 -1
  18. package/dist/application/ui/observed-ui-snapshot-provider.d.ts +15 -0
  19. package/dist/application/ui/observed-ui-snapshot-provider.js +66 -0
  20. package/dist/application/ui/ui-stability-probe.d.ts +6 -0
  21. package/dist/application/ui/ui-stability-probe.js +21 -5
  22. package/dist/application/wait/idle-waiter.js +19 -4
  23. package/dist/cli/commands/diagnose.d.ts +3 -0
  24. package/dist/cli/commands/diagnose.js +84 -0
  25. package/dist/cli/commands/verify.js +55 -3
  26. package/dist/cli/dependencies.d.ts +11 -0
  27. package/dist/cli/dependencies.js +58 -5
  28. package/dist/cli/diagnostics-recorder.d.ts +31 -0
  29. package/dist/cli/diagnostics-recorder.js +82 -0
  30. package/dist/cli/main.d.ts +1 -1
  31. package/dist/cli/main.js +58 -1
  32. package/dist/cli/program.js +3 -1
  33. package/dist/domain/contract.d.ts +6 -6
  34. package/dist/domain/diagnostics.d.ts +1396 -0
  35. package/dist/domain/diagnostics.js +193 -0
  36. package/dist/domain/failure.js +1 -1
  37. package/dist/domain/report.d.ts +3 -3
  38. package/dist/domain/verify-receipt.d.ts +17 -0
  39. package/dist/domain/verify-receipt.js +17 -0
  40. package/dist/domain/workspace.d.ts +2 -0
  41. package/dist/domain/workspace.js +2 -0
  42. package/dist/ports/diagnostics.d.ts +15 -0
  43. package/dist/ports/diagnostics.js +1 -0
  44. 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
+ });
@@ -81,7 +81,7 @@ const EXIT_CODES = {
81
81
  DEVICE_UNAVAILABLE: 3,
82
82
  UI_BACKEND_UNAVAILABLE: 3,
83
83
  RUNTIME_CAPABILITY_MISSING: 3,
84
- UI_SNAPSHOT_FAILED: 1,
84
+ UI_SNAPSHOT_FAILED: 3,
85
85
  UI_SNAPSHOT_INVALID: 1,
86
86
  APP_NOT_INSTALLED: 3,
87
87
  APP_LAUNCH_FAILED: 1,
@@ -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";
@@ -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.11",
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": "./dist/cli/main.js"
28
+ "taphound": "dist/cli/main.js"
29
29
  },
30
30
  "files": [
31
31
  "dist",