🌳
pt0/deployF/testsF/testMemoAI.mts
1import { readLedger, getGitShaFull, type ActionResult } from './actionResultsAI.mts'
9import type { Ptenv } from '../../sharedF/ptenvF.mts'
16export type SkipResult = { skip: boolean, reason?: string, lastRecord?: ActionResult }
18export const shouldSkipTests = async ({ep, ptenv}: {
19 ep: string
20 ptenv: Ptenv
21}): Promise<SkipResult> => {
23 const epRel = toPtRelPath(ep)
24 const last = readLedger().findLast(r =>
25 r.ep === epRel && r.action === 'runtests' && r.ptenv === ptenv && r.success
26 )
27 if (!last?.gitSha) return { skip: false }
29 const currentSha = getGitShaFull()
30 const lastSha = last.gitSha
32 // Tier 1: same commit (fast path — no tree computation needed)
33 if (currentSha === lastSha) {
34 if (isVeryVerbose) betLog({tier: 'memo:tier1', skip: true})
35 return { skip: true, reason: `unchanged at ${currentSha}`, lastRecord: last }
36 }
38 // Tier 1.5: reuse lastSha's cached import tree to check for diffs before recomputing.
39 // Safe because any file added to the tree between shas implies a modifying edit to a
40 // file already in the old tree (the one that gained the new import) → gitDiff catches it.
41 const lastTree = await readMemoTempfileIfExist<string[]>({cacheKeyA: [lastSha, epRel, 'fullTree-v1']})
42 if (lastTree?.length) {
43 const lastAllPaths = [...lastTree, ...globalDependPaths]
44 const uncommittedInLastTree = getUncommittedInPathSet(lastAllPaths)
45 if (!uncommittedInLastTree.length) {
46 try {
47 if (gitDiffQuietInPaths(lastSha, currentSha, lastAllPaths)) {
48 if (isVeryVerbose) betLog({tier: 'memo:tier1.5', skip: true})
49 return { skip: true, reason: `no import-tree changes since ${lastSha}`, lastRecord: last }
50 }
51 } catch { // catch:userapproved
52 }
53 }
54 }
56 const importPaths = await betDurMs('shouldSkipTests:getFullEpImportTree', () => getFullEpImportTree(epRel))
57 const allPaths = [...importPaths, ...globalDependPaths]
59 const uncommittedInCodeTree = getUncommittedInPathSet(allPaths)
60 if (uncommittedInCodeTree.length) {
61 if (isVeryVerbose) betLog({tier: 'memo:uncommitted', skip: false})
62 return { skip: false }
63 }
65 // Tier 2: different commit but no import-tree diff
66 try {
67 if (gitDiffQuietInPaths(lastSha, currentSha, allPaths)) {
68 if (isVeryVerbose) betLog({tier: 'memo:tier2', skip: true})
69 return { skip: true, reason: `no import-tree changes since ${lastSha}`, lastRecord: last }
70 }
71 } catch { // catch:userapproved
72 // old SHA pruned from git, etc. — don't skip
73 }
75 if (isVeryVerbose) betLog({tier: 'memo:miss', skip: false})
76 return { skip: false }
79export const printSkipMsg = (ptenv: Ptenv, reason: string, lastRecord?: ActionResult) => {
80 console.log(`${chalkGreen('runtests')} ${chalkGray(`ptenv=${ptenv}`)} ${chalkGreen('SKIP')} ${chalkGray(`(${reason})`)}`)
81 if (lastRecord?.suites) {
82 for (const [name, sr] of Object.entries(lastRecord.suites)) {
83 const status = sr.passed ? chalkGreen('PASS') : chalkRed('FAIL')
84 console.log(` ${status} testsuite=${name} ${sr.message} ${chalkGray('(memoized)')}`)
85 }
86 }
87 const action = getAction()
88 const cliSchema = action && availActionsCtx.getStore()?.[action]?.cliSchema
89 if (cliSchema) console.log(chalkGray(cliHelpText('', cliSchema).trim()))