tryassay 0.15.1 → 0.17.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/dist/runtime/__tests__/check-loop.test.d.ts +16 -0
- package/dist/runtime/__tests__/check-loop.test.js +437 -0
- package/dist/runtime/__tests__/check-loop.test.js.map +1 -0
- package/dist/runtime/__tests__/integration-verifier.test.d.ts +9 -0
- package/dist/runtime/__tests__/integration-verifier.test.js +120 -0
- package/dist/runtime/__tests__/integration-verifier.test.js.map +1 -0
- package/dist/runtime/app-create-orchestrator.js +42 -5
- package/dist/runtime/app-create-orchestrator.js.map +1 -1
- package/dist/runtime/check-definitions.d.ts +36 -0
- package/dist/runtime/check-definitions.js +238 -0
- package/dist/runtime/check-definitions.js.map +1 -0
- package/dist/runtime/check-loop.d.ts +27 -0
- package/dist/runtime/check-loop.js +66 -0
- package/dist/runtime/check-loop.js.map +1 -0
- package/dist/runtime/check-store.d.ts +24 -0
- package/dist/runtime/check-store.js +81 -0
- package/dist/runtime/check-store.js.map +1 -0
- package/dist/runtime/failure-classifier.d.ts +40 -0
- package/dist/runtime/failure-classifier.js +184 -0
- package/dist/runtime/failure-classifier.js.map +1 -0
- package/dist/runtime/fs-helpers.d.ts +20 -0
- package/dist/runtime/fs-helpers.js +122 -0
- package/dist/runtime/fs-helpers.js.map +1 -0
- package/dist/runtime/integration-verifier.d.ts +42 -0
- package/dist/runtime/integration-verifier.js +307 -0
- package/dist/runtime/integration-verifier.js.map +1 -0
- package/dist/runtime/types.d.ts +63 -0
- package/package.json +1 -1
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CheckLoop — orchestrates the failure-to-check loop.
|
|
3
|
+
*
|
|
4
|
+
* When integration verification finds failures:
|
|
5
|
+
* 1. Filter for FAIL checks only
|
|
6
|
+
* 2. For each failure, call FailureClassifier to propose a check definition
|
|
7
|
+
* 3. Save proposals to CheckStore with status 'proposed'
|
|
8
|
+
* 4. Return summary of proposals
|
|
9
|
+
*
|
|
10
|
+
* Called by AppCreateOrchestrator after integration verification.
|
|
11
|
+
*/
|
|
12
|
+
export class CheckLoop {
|
|
13
|
+
store;
|
|
14
|
+
classifier;
|
|
15
|
+
constructor(opts) {
|
|
16
|
+
this.store = opts.store;
|
|
17
|
+
this.classifier = opts.classifier;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Process integration verification failures into check proposals.
|
|
21
|
+
* Returns a summary of what was proposed, skipped, or errored.
|
|
22
|
+
*/
|
|
23
|
+
async onFailure(result, projectPath) {
|
|
24
|
+
const proposedChecks = [];
|
|
25
|
+
let duplicatesSkipped = 0;
|
|
26
|
+
let classificationErrors = 0;
|
|
27
|
+
// Only process failures
|
|
28
|
+
const failures = result.checks.filter(c => c.verdict === 'FAIL');
|
|
29
|
+
if (failures.length === 0) {
|
|
30
|
+
return { proposedChecks: [], duplicatesSkipped: 0, classificationErrors: 0 };
|
|
31
|
+
}
|
|
32
|
+
// Load existing checks to avoid duplicates
|
|
33
|
+
const existingChecks = await this.store.load();
|
|
34
|
+
// Deduplicate failures by type+evidence to avoid redundant proposals
|
|
35
|
+
// (e.g., 4 missing IPC handlers should produce 1 cross-reference check, not 4)
|
|
36
|
+
const seenTypes = new Set();
|
|
37
|
+
const uniqueFailures = failures.filter(f => {
|
|
38
|
+
const key = f.type;
|
|
39
|
+
if (seenTypes.has(key))
|
|
40
|
+
return false;
|
|
41
|
+
seenTypes.add(key);
|
|
42
|
+
return true;
|
|
43
|
+
});
|
|
44
|
+
for (const failure of uniqueFailures) {
|
|
45
|
+
try {
|
|
46
|
+
const proposal = await this.classifier.classify(failure, projectPath, [...existingChecks, ...proposedChecks]);
|
|
47
|
+
if (proposal === null) {
|
|
48
|
+
duplicatesSkipped++;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
// Save to store with 'proposed' status
|
|
52
|
+
await this.store.save(proposal);
|
|
53
|
+
proposedChecks.push(proposal);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
classificationErrors++;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
proposedChecks,
|
|
61
|
+
duplicatesSkipped,
|
|
62
|
+
classificationErrors,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=check-loop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-loop.js","sourceRoot":"","sources":["../../src/runtime/check-loop.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAUH,MAAM,OAAO,SAAS;IACH,KAAK,CAAa;IAClB,UAAU,CAAoB;IAE/C,YAAY,IAGX;QACC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACxB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;IACpC,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,SAAS,CACb,MAAqC,EACrC,WAAmB;QAEnB,MAAM,cAAc,GAAiC,EAAE,CAAC;QACxD,IAAI,iBAAiB,GAAG,CAAC,CAAC;QAC1B,IAAI,oBAAoB,GAAG,CAAC,CAAC;QAE7B,wBAAwB;QACxB,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,MAAM,CAAC,CAAC;QACjE,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE,iBAAiB,EAAE,CAAC,EAAE,oBAAoB,EAAE,CAAC,EAAE,CAAC;QAC/E,CAAC;QAED,2CAA2C;QAC3C,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QAE/C,qEAAqE;QACrE,+EAA+E;QAC/E,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;QACpC,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;YACzC,MAAM,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC;YACnB,IAAI,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,OAAO,KAAK,CAAC;YACrC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACnB,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;QAEH,KAAK,MAAM,OAAO,IAAI,cAAc,EAAE,CAAC;YACrC,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,QAAQ,CAC7C,OAAO,EACP,WAAW,EACX,CAAC,GAAG,cAAc,EAAE,GAAG,cAAc,CAAC,CACvC,CAAC;gBAEF,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;oBACtB,iBAAiB,EAAE,CAAC;oBACpB,SAAS;gBACX,CAAC;gBAED,uCAAuC;gBACvC,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gBAChC,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAChC,CAAC;YAAC,MAAM,CAAC;gBACP,oBAAoB,EAAE,CAAC;YACzB,CAAC;QACH,CAAC;QAED,OAAO;YACL,cAAc;YACd,iBAAiB;YACjB,oBAAoB;SACrB,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CheckStore — JSON file persistence for integration check definitions.
|
|
3
|
+
*
|
|
4
|
+
* Stores check definitions in a JSON file (~/.assay/integration-checks.json).
|
|
5
|
+
* Supports filtering by status and framework.
|
|
6
|
+
*/
|
|
7
|
+
import type { IntegrationCheckDefinition } from './types.js';
|
|
8
|
+
export declare class CheckStore {
|
|
9
|
+
private readonly storePath;
|
|
10
|
+
private cache;
|
|
11
|
+
constructor(storePath?: string);
|
|
12
|
+
/** Load all check definitions from disk. */
|
|
13
|
+
load(): Promise<IntegrationCheckDefinition[]>;
|
|
14
|
+
/** Get active checks, optionally filtered by framework. */
|
|
15
|
+
getActive(framework?: string): Promise<IntegrationCheckDefinition[]>;
|
|
16
|
+
/** Get checks filtered by status. */
|
|
17
|
+
getByStatus(status: IntegrationCheckDefinition['status']): Promise<IntegrationCheckDefinition[]>;
|
|
18
|
+
/** Save a new check definition. */
|
|
19
|
+
save(definition: IntegrationCheckDefinition): Promise<void>;
|
|
20
|
+
/** Update the status of a check definition by ID. */
|
|
21
|
+
updateStatus(id: string, status: IntegrationCheckDefinition['status']): Promise<void>;
|
|
22
|
+
/** Persist current cache to disk. */
|
|
23
|
+
private persist;
|
|
24
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CheckStore — JSON file persistence for integration check definitions.
|
|
3
|
+
*
|
|
4
|
+
* Stores check definitions in a JSON file (~/.assay/integration-checks.json).
|
|
5
|
+
* Supports filtering by status and framework.
|
|
6
|
+
*/
|
|
7
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
8
|
+
import { dirname } from 'node:path';
|
|
9
|
+
import { homedir } from 'node:os';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
export class CheckStore {
|
|
12
|
+
storePath;
|
|
13
|
+
cache = null;
|
|
14
|
+
constructor(storePath) {
|
|
15
|
+
this.storePath = storePath ?? join(homedir(), '.assay', 'integration-checks.json');
|
|
16
|
+
}
|
|
17
|
+
/** Load all check definitions from disk. */
|
|
18
|
+
async load() {
|
|
19
|
+
if (this.cache)
|
|
20
|
+
return this.cache;
|
|
21
|
+
try {
|
|
22
|
+
const raw = await readFile(this.storePath, 'utf-8');
|
|
23
|
+
this.cache = JSON.parse(raw);
|
|
24
|
+
return this.cache;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
this.cache = [];
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Get active checks, optionally filtered by framework. */
|
|
32
|
+
async getActive(framework) {
|
|
33
|
+
const all = await this.load();
|
|
34
|
+
return all.filter(d => {
|
|
35
|
+
if (d.status !== 'active')
|
|
36
|
+
return false;
|
|
37
|
+
if (framework && d.frameworks.length > 0) {
|
|
38
|
+
return d.frameworks.includes(framework);
|
|
39
|
+
}
|
|
40
|
+
return true;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/** Get checks filtered by status. */
|
|
44
|
+
async getByStatus(status) {
|
|
45
|
+
const all = await this.load();
|
|
46
|
+
return all.filter(d => d.status === status);
|
|
47
|
+
}
|
|
48
|
+
/** Save a new check definition. */
|
|
49
|
+
async save(definition) {
|
|
50
|
+
const all = await this.load();
|
|
51
|
+
// Replace if same ID exists, otherwise append
|
|
52
|
+
const idx = all.findIndex(d => d.id === definition.id);
|
|
53
|
+
if (idx >= 0) {
|
|
54
|
+
all[idx] = definition;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
all.push(definition);
|
|
58
|
+
}
|
|
59
|
+
this.cache = all;
|
|
60
|
+
await this.persist();
|
|
61
|
+
}
|
|
62
|
+
/** Update the status of a check definition by ID. */
|
|
63
|
+
async updateStatus(id, status) {
|
|
64
|
+
const all = await this.load();
|
|
65
|
+
const def = all.find(d => d.id === id);
|
|
66
|
+
if (!def)
|
|
67
|
+
return;
|
|
68
|
+
// Reconstruct with new status (readonly interface requires full replacement)
|
|
69
|
+
const updated = { ...def, status };
|
|
70
|
+
const idx = all.indexOf(def);
|
|
71
|
+
all[idx] = updated;
|
|
72
|
+
this.cache = all;
|
|
73
|
+
await this.persist();
|
|
74
|
+
}
|
|
75
|
+
/** Persist current cache to disk. */
|
|
76
|
+
async persist() {
|
|
77
|
+
await mkdir(dirname(this.storePath), { recursive: true });
|
|
78
|
+
await writeFile(this.storePath, JSON.stringify(this.cache, null, 2), 'utf-8');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=check-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-store.js","sourceRoot":"","sources":["../../src/runtime/check-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FailureClassifier — converts integration failures into check proposals.
|
|
3
|
+
*
|
|
4
|
+
* Uses a single LLM call per failure to propose a deterministic check definition.
|
|
5
|
+
* Validates the proposed strategy (regex compiles, globs are valid) before returning.
|
|
6
|
+
* Returns null if the failure duplicates an existing check.
|
|
7
|
+
*/
|
|
8
|
+
import type { IntegrationCheck, IntegrationCheckDefinition } from './types.js';
|
|
9
|
+
export declare class FailureClassifier {
|
|
10
|
+
private readonly model;
|
|
11
|
+
constructor(opts?: {
|
|
12
|
+
model?: string;
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* Classify a single integration failure into a check proposal.
|
|
16
|
+
* Returns null if it duplicates an existing check or classification fails.
|
|
17
|
+
*/
|
|
18
|
+
classify(failure: IntegrationCheck, projectPath: string, existingChecks: IntegrationCheckDefinition[]): Promise<IntegrationCheckDefinition | null>;
|
|
19
|
+
/**
|
|
20
|
+
* Check if a failure is already covered by an existing check definition.
|
|
21
|
+
* Compares by check type — if an existing definition targets the same
|
|
22
|
+
* failure type, we consider it a duplicate.
|
|
23
|
+
*/
|
|
24
|
+
private isDuplicate;
|
|
25
|
+
/**
|
|
26
|
+
* Propose a check strategy based on the failure.
|
|
27
|
+
* This is the single LLM call — it asks Claude to produce a CheckStrategy JSON.
|
|
28
|
+
*/
|
|
29
|
+
private proposeStrategy;
|
|
30
|
+
/**
|
|
31
|
+
* Heuristic-based classification — deterministic, no LLM needed.
|
|
32
|
+
* Covers the known failure patterns; returns null for unknown patterns
|
|
33
|
+
* (which can later be handled by an LLM call).
|
|
34
|
+
*/
|
|
35
|
+
private heuristicClassify;
|
|
36
|
+
/** Validate that a proposed strategy has valid patterns. */
|
|
37
|
+
private validateStrategy;
|
|
38
|
+
/** Derive an evidence template from the failure's evidence string. */
|
|
39
|
+
private deriveEvidenceTemplate;
|
|
40
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FailureClassifier — converts integration failures into check proposals.
|
|
3
|
+
*
|
|
4
|
+
* Uses a single LLM call per failure to propose a deterministic check definition.
|
|
5
|
+
* Validates the proposed strategy (regex compiles, globs are valid) before returning.
|
|
6
|
+
* Returns null if the failure duplicates an existing check.
|
|
7
|
+
*/
|
|
8
|
+
import { readFile } from 'node:fs/promises';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { randomUUID } from 'node:crypto';
|
|
11
|
+
export class FailureClassifier {
|
|
12
|
+
model;
|
|
13
|
+
constructor(opts) {
|
|
14
|
+
this.model = opts?.model ?? 'claude-sonnet-4-20250514';
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Classify a single integration failure into a check proposal.
|
|
18
|
+
* Returns null if it duplicates an existing check or classification fails.
|
|
19
|
+
*/
|
|
20
|
+
async classify(failure, projectPath, existingChecks) {
|
|
21
|
+
// Skip if an existing check already covers this pattern
|
|
22
|
+
if (this.isDuplicate(failure, existingChecks)) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
// Read the file referenced in the failure for context
|
|
26
|
+
let fileContent = '';
|
|
27
|
+
try {
|
|
28
|
+
const filePath = join(projectPath, failure.file.split(',')[0].trim());
|
|
29
|
+
fileContent = await readFile(filePath, 'utf-8');
|
|
30
|
+
// Truncate to avoid huge context
|
|
31
|
+
if (fileContent.length > 4000) {
|
|
32
|
+
fileContent = fileContent.slice(0, 4000) + '\n... (truncated)';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// File might not be readable
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const strategy = await this.proposeStrategy(failure, fileContent);
|
|
40
|
+
if (!strategy)
|
|
41
|
+
return null;
|
|
42
|
+
// Validate the proposed strategy
|
|
43
|
+
if (!this.validateStrategy(strategy))
|
|
44
|
+
return null;
|
|
45
|
+
const definition = {
|
|
46
|
+
id: `check_${randomUUID().slice(0, 8)}`,
|
|
47
|
+
name: `Auto: ${failure.type} — ${failure.evidence.slice(0, 60)}`,
|
|
48
|
+
type: failure.type,
|
|
49
|
+
strategy,
|
|
50
|
+
severity: failure.severity,
|
|
51
|
+
frameworks: [],
|
|
52
|
+
status: 'proposed',
|
|
53
|
+
createdAt: new Date().toISOString(),
|
|
54
|
+
evidenceTemplate: this.deriveEvidenceTemplate(failure),
|
|
55
|
+
};
|
|
56
|
+
return definition;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Check if a failure is already covered by an existing check definition.
|
|
64
|
+
* Compares by check type — if an existing definition targets the same
|
|
65
|
+
* failure type, we consider it a duplicate.
|
|
66
|
+
*/
|
|
67
|
+
isDuplicate(failure, existingChecks) {
|
|
68
|
+
for (const existing of existingChecks) {
|
|
69
|
+
// If an existing check covers the same type, it's a duplicate.
|
|
70
|
+
// Different failure instances of the same type (e.g., 4 missing IPC handlers)
|
|
71
|
+
// should all be caught by a single cross-reference check.
|
|
72
|
+
if (existing.type === failure.type) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Propose a check strategy based on the failure.
|
|
80
|
+
* This is the single LLM call — it asks Claude to produce a CheckStrategy JSON.
|
|
81
|
+
*/
|
|
82
|
+
async proposeStrategy(failure, fileContent) {
|
|
83
|
+
// Use heuristic classification instead of LLM call for determinism.
|
|
84
|
+
// This keeps the classifier fast and avoids API dependencies.
|
|
85
|
+
// A future version can use LLM for novel failure patterns.
|
|
86
|
+
return this.heuristicClassify(failure, fileContent);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Heuristic-based classification — deterministic, no LLM needed.
|
|
90
|
+
* Covers the known failure patterns; returns null for unknown patterns
|
|
91
|
+
* (which can later be handled by an LLM call).
|
|
92
|
+
*/
|
|
93
|
+
heuristicClassify(failure, _fileContent) {
|
|
94
|
+
const evidence = failure.evidence.toLowerCase();
|
|
95
|
+
// IPC handler coverage failures → cross_reference strategy
|
|
96
|
+
if (failure.type === 'ipc_handler_coverage' || evidence.includes('no handler')) {
|
|
97
|
+
const channelMatch = failure.evidence.match(/Channel '([^']+)'/);
|
|
98
|
+
if (channelMatch) {
|
|
99
|
+
return {
|
|
100
|
+
type: 'cross_reference',
|
|
101
|
+
sourceGlob: '**/preload/**/*.{ts,tsx,js,jsx}',
|
|
102
|
+
sourcePattern: "ipcRenderer\\.invoke\\(\\s*['\"`]([^'\"`]+)['\"`]",
|
|
103
|
+
targetGlob: '**/main/**/*.{ts,tsx,js,jsx}',
|
|
104
|
+
targetPattern: "ipcMain\\.handle\\(\\s*['\"`]{{item}}['\"`]",
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Routing provider failures → pattern_presence strategy
|
|
109
|
+
if (failure.type === 'routing_provider' || evidence.includes('router')) {
|
|
110
|
+
return {
|
|
111
|
+
type: 'pattern_presence',
|
|
112
|
+
fileGlob: '**/*.{tsx,jsx}',
|
|
113
|
+
pattern: '\\b(BrowserRouter|HashRouter|MemoryRouter|RouterProvider|createBrowserRouter)\\b',
|
|
114
|
+
requireIn: 'any',
|
|
115
|
+
contextGlob: '**/*.{tsx,jsx}',
|
|
116
|
+
contextPattern: '\\b(useNavigate|useParams|useLocation|useSearchParams)\\b',
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
// Unreachable page failures → import_reachability strategy
|
|
120
|
+
if (failure.type === 'unreachable_page' || evidence.includes('unreachable') || evidence.includes('not imported')) {
|
|
121
|
+
return {
|
|
122
|
+
type: 'import_reachability',
|
|
123
|
+
pageGlob: '**/pages/**/*.{tsx,jsx}',
|
|
124
|
+
entryPattern: 'createRoot|ReactDOM\\.render',
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
// Stub function failures → pattern_absence strategy
|
|
128
|
+
if (failure.type === 'stub_function' || evidence.includes('stub')) {
|
|
129
|
+
return {
|
|
130
|
+
type: 'pattern_absence',
|
|
131
|
+
fileGlob: '**/preload/**/*.{ts,tsx,js,jsx}',
|
|
132
|
+
pattern: 'async\\s*\\([^)]*\\)\\s*(?::\\s*Promise<[^>]+>)?\\s*=>\\s*\\{\\s*return\\s+\\{[^}]*\\}\\s*;?\\s*\\}',
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
/** Validate that a proposed strategy has valid patterns. */
|
|
138
|
+
validateStrategy(strategy) {
|
|
139
|
+
try {
|
|
140
|
+
switch (strategy.type) {
|
|
141
|
+
case 'pattern_presence':
|
|
142
|
+
new RegExp(strategy.pattern);
|
|
143
|
+
if (strategy.contextPattern)
|
|
144
|
+
new RegExp(strategy.contextPattern);
|
|
145
|
+
return true;
|
|
146
|
+
case 'pattern_absence':
|
|
147
|
+
new RegExp(strategy.pattern);
|
|
148
|
+
return true;
|
|
149
|
+
case 'cross_reference':
|
|
150
|
+
new RegExp(strategy.sourcePattern);
|
|
151
|
+
// targetPattern may contain {{item}} placeholder — validate the static parts
|
|
152
|
+
new RegExp(strategy.targetPattern.replace('{{item}}', 'test'));
|
|
153
|
+
return true;
|
|
154
|
+
case 'import_reachability':
|
|
155
|
+
new RegExp(strategy.entryPattern);
|
|
156
|
+
return true;
|
|
157
|
+
default:
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return false; // Invalid regex
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/** Derive an evidence template from the failure's evidence string. */
|
|
166
|
+
deriveEvidenceTemplate(failure) {
|
|
167
|
+
// Use the failure's existing evidence pattern as a template
|
|
168
|
+
const evidence = failure.evidence;
|
|
169
|
+
if (evidence.includes('Channel')) {
|
|
170
|
+
return "Channel '{match}' invoked in {sourceFile} has {found} handler.";
|
|
171
|
+
}
|
|
172
|
+
if (evidence.includes('router') || evidence.includes('Router')) {
|
|
173
|
+
return "Router provider check: {match} in {count} file(s).";
|
|
174
|
+
}
|
|
175
|
+
if (evidence.includes('unreachable') || evidence.includes('NOT imported')) {
|
|
176
|
+
return "Page '{match}' reachability: {reachable}.";
|
|
177
|
+
}
|
|
178
|
+
if (evidence.includes('stub')) {
|
|
179
|
+
return "Stub function found: {match} in {sourceFile}.";
|
|
180
|
+
}
|
|
181
|
+
return "Check '{match}': {found}.";
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=failure-classifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failure-classifier.js","sourceRoot":"","sources":["../../src/runtime/failure-classifier.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared file-scanning utilities for IntegrationVerifier and CheckExecutor.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from IntegrationVerifier to enable reuse by dynamic check strategies.
|
|
5
|
+
* Zero LLM calls. Deterministic. Uses only node:fs/promises and node:path.
|
|
6
|
+
*/
|
|
7
|
+
/** Recursively find files matching a regex pattern against filenames. */
|
|
8
|
+
export declare function findFiles(dir: string, pattern: RegExp, excludeDirs?: readonly string[]): Promise<string[]>;
|
|
9
|
+
/** Trace imports transitively from a file, populating a visited set. */
|
|
10
|
+
export declare function traceImports(file: string, visited: Set<string>, allFiles: string[]): Promise<void>;
|
|
11
|
+
/** Resolve a relative import path to an actual file. */
|
|
12
|
+
export declare function resolveImport(fromFile: string, importPath: string, allFiles: string[]): string | null;
|
|
13
|
+
/** Find the matching closing brace for an opening brace at the given index. */
|
|
14
|
+
export declare function findMatchingBrace(content: string, openIdx: number): number;
|
|
15
|
+
/**
|
|
16
|
+
* Find files matching a simple glob-like pattern.
|
|
17
|
+
* Supports: ** (any depth), * (any filename chars), {a,b,c} (alternatives),
|
|
18
|
+
* and literal path segments.
|
|
19
|
+
*/
|
|
20
|
+
export declare function matchesGlob(filePath: string, glob: string): boolean;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared file-scanning utilities for IntegrationVerifier and CheckExecutor.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from IntegrationVerifier to enable reuse by dynamic check strategies.
|
|
5
|
+
* Zero LLM calls. Deterministic. Uses only node:fs/promises and node:path.
|
|
6
|
+
*/
|
|
7
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
/** Recursively find files matching a regex pattern against filenames. */
|
|
10
|
+
export async function findFiles(dir, pattern, excludeDirs = ['node_modules', 'dist', '.git', 'out', 'build', '.next']) {
|
|
11
|
+
const excludeSet = new Set(excludeDirs);
|
|
12
|
+
const results = [];
|
|
13
|
+
try {
|
|
14
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
const fullPath = join(dir, entry.name);
|
|
17
|
+
if (entry.isDirectory()) {
|
|
18
|
+
if (excludeSet.has(entry.name))
|
|
19
|
+
continue;
|
|
20
|
+
results.push(...await findFiles(fullPath, pattern, excludeDirs));
|
|
21
|
+
}
|
|
22
|
+
else if (pattern.test(entry.name)) {
|
|
23
|
+
results.push(fullPath);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Directory doesn't exist or can't be read
|
|
29
|
+
}
|
|
30
|
+
return results;
|
|
31
|
+
}
|
|
32
|
+
/** Trace imports transitively from a file, populating a visited set. */
|
|
33
|
+
export async function traceImports(file, visited, allFiles) {
|
|
34
|
+
if (visited.has(file))
|
|
35
|
+
return;
|
|
36
|
+
visited.add(file);
|
|
37
|
+
let content;
|
|
38
|
+
try {
|
|
39
|
+
content = await readFile(file, 'utf-8');
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// Match import statements: import ... from './path' or import './path'
|
|
45
|
+
const importPattern = /(?:import\s+(?:[\s\S]*?)\s+from\s+|import\s+)['"]([^'"]+)['"]/g;
|
|
46
|
+
// Also match dynamic imports: import('./path')
|
|
47
|
+
const dynamicImportPattern = /import\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
48
|
+
const importPaths = [];
|
|
49
|
+
let match;
|
|
50
|
+
while ((match = importPattern.exec(content)) !== null) {
|
|
51
|
+
importPaths.push(match[1]);
|
|
52
|
+
}
|
|
53
|
+
while ((match = dynamicImportPattern.exec(content)) !== null) {
|
|
54
|
+
importPaths.push(match[1]);
|
|
55
|
+
}
|
|
56
|
+
for (const importPath of importPaths) {
|
|
57
|
+
if (!importPath.startsWith('.'))
|
|
58
|
+
continue; // Skip node_modules imports
|
|
59
|
+
const resolved = resolveImport(file, importPath, allFiles);
|
|
60
|
+
if (resolved) {
|
|
61
|
+
await traceImports(resolved, visited, allFiles);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** Resolve a relative import path to an actual file. */
|
|
66
|
+
export function resolveImport(fromFile, importPath, allFiles) {
|
|
67
|
+
const dir = join(fromFile, '..');
|
|
68
|
+
const base = join(dir, importPath);
|
|
69
|
+
// Try exact match, then with extensions
|
|
70
|
+
const extensions = ['.ts', '.tsx', '.js', '.jsx', '/index.ts', '/index.tsx', '/index.js', '/index.jsx'];
|
|
71
|
+
const candidates = [base, ...extensions.map(ext => base + ext)];
|
|
72
|
+
for (const candidate of candidates) {
|
|
73
|
+
if (allFiles.includes(candidate)) {
|
|
74
|
+
return candidate;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
/** Find the matching closing brace for an opening brace at the given index. */
|
|
80
|
+
export function findMatchingBrace(content, openIdx) {
|
|
81
|
+
let depth = 0;
|
|
82
|
+
for (let i = openIdx; i < content.length; i++) {
|
|
83
|
+
if (content[i] === '{')
|
|
84
|
+
depth++;
|
|
85
|
+
else if (content[i] === '}') {
|
|
86
|
+
depth--;
|
|
87
|
+
if (depth === 0)
|
|
88
|
+
return i;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return -1;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Find files matching a simple glob-like pattern.
|
|
95
|
+
* Supports: ** (any depth), * (any filename chars), {a,b,c} (alternatives),
|
|
96
|
+
* and literal path segments.
|
|
97
|
+
*/
|
|
98
|
+
export function matchesGlob(filePath, glob) {
|
|
99
|
+
// Expand brace patterns like {ts,tsx,js,jsx} into alternation groups
|
|
100
|
+
let expanded = glob;
|
|
101
|
+
const bracePattern = /\{([^}]+)\}/g;
|
|
102
|
+
expanded = expanded.replace(bracePattern, (_match, group) => {
|
|
103
|
+
const alternatives = group.split(',').map((s) => s.trim());
|
|
104
|
+
return `(${alternatives.join('|')})`;
|
|
105
|
+
});
|
|
106
|
+
// Convert glob to regex
|
|
107
|
+
// Key: **/ means "zero or more directories" (optional prefix with /)
|
|
108
|
+
// /** means "anything below" (optional suffix with /)
|
|
109
|
+
// ** alone means "any characters"
|
|
110
|
+
const regexStr = expanded
|
|
111
|
+
.replace(/\./g, '\\.')
|
|
112
|
+
.replace(/\*\*\//g, '{{GLOBDIR}}')
|
|
113
|
+
.replace(/\/\*\*/g, '{{GLOBEND}}')
|
|
114
|
+
.replace(/\*\*/g, '{{DOUBLESTAR}}')
|
|
115
|
+
.replace(/\*/g, '[^/]*')
|
|
116
|
+
.replace(/\{\{GLOBDIR\}\}/g, '(.*/)?')
|
|
117
|
+
.replace(/\{\{GLOBEND\}\}/g, '(/.*)?')
|
|
118
|
+
.replace(/\{\{DOUBLESTAR\}\}/g, '.*');
|
|
119
|
+
const regex = new RegExp(`^${regexStr}$`);
|
|
120
|
+
return regex.test(filePath);
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=fs-helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs-helpers.js","sourceRoot":"","sources":["../../src/runtime/fs-helpers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IntegrationVerifier — deterministic wiring checks for generated applications.
|
|
3
|
+
*
|
|
4
|
+
* Catches four classes of integration failure that compile clean but don't work:
|
|
5
|
+
* 1. Missing Router provider (pages use react-router hooks but no Router wraps them)
|
|
6
|
+
* 2. Missing IPC handlers (preload invokes channels with no main-process handler)
|
|
7
|
+
* 3. Stub functions (async functions that return literal objects without doing real work)
|
|
8
|
+
* 4. Unreachable pages (page files exist but are never imported from the entry point)
|
|
9
|
+
*
|
|
10
|
+
* Zero LLM calls. Deterministic. Uses only node:fs/promises, node:path, and regex.
|
|
11
|
+
*/
|
|
12
|
+
import type { IntegrationCheck, IntegrationVerificationResult } from './types.js';
|
|
13
|
+
export declare class IntegrationVerifier {
|
|
14
|
+
private readonly projectPath;
|
|
15
|
+
private readonly framework;
|
|
16
|
+
constructor(projectPath: string, framework: string);
|
|
17
|
+
verify(): Promise<IntegrationVerificationResult>;
|
|
18
|
+
/**
|
|
19
|
+
* Scan all .tsx files for react-router hook usage.
|
|
20
|
+
* If found, verify a Router provider exists in the entry point chain.
|
|
21
|
+
*/
|
|
22
|
+
checkRoutingProvider(): Promise<IntegrationCheck[]>;
|
|
23
|
+
/**
|
|
24
|
+
* Electron only. Scan preload files for ipcRenderer.invoke calls.
|
|
25
|
+
* Scan main/ipc files for ipcMain.handle calls.
|
|
26
|
+
* Report any channel invoked but not handled.
|
|
27
|
+
*/
|
|
28
|
+
checkIpcHandlerCoverage(): Promise<IntegrationCheck[]>;
|
|
29
|
+
/**
|
|
30
|
+
* Find async functions in preload whose body is ONLY a return of a literal object
|
|
31
|
+
* without ipcRenderer.invoke, await, .query(), or fetch().
|
|
32
|
+
*/
|
|
33
|
+
checkStubFunctions(): Promise<IntegrationCheck[]>;
|
|
34
|
+
/**
|
|
35
|
+
* Collect page files from pages/ directories.
|
|
36
|
+
* Build import graph from entry point.
|
|
37
|
+
* Report any page file not transitively imported.
|
|
38
|
+
*/
|
|
39
|
+
checkUnreachablePages(): Promise<IntegrationCheck[]>;
|
|
40
|
+
/** Check if the project has preload files (indicating Electron). */
|
|
41
|
+
private hasPreloadFiles;
|
|
42
|
+
}
|