@skyramp/testbot-core 0.13.0-v3.181 → 0.13.0-v3.182

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.
@@ -11,11 +11,17 @@
11
11
  * statusCode: 201, language: "python", framework: "pytest", outputDir: "tests/api", output: "test_products.py" });
12
12
  * // several requests: a trace built from the steps, then an integration test from it
13
13
  * const trace = await api.trace([
14
- * { method: "POST", path: "/api/v1/products", request: { name: "Lamp" }, status: 201, response: { id: 7, name: "Lamp" } },
14
+ * { method: "POST", path: "/api/v1/products", request: { name: "Lamp" }, unique: ["name"], status: 201, response: { id: 7, name: "Lamp" } },
15
15
  * { method: "GET", path: "/api/v1/products/{{1.id}}", status: 200, response: { id: 7, name: "Lamp" } },
16
16
  * ], { baseUrl: "http://localhost:8000", authHeader: "Authorization", authScheme: "Bearer" });
17
17
  * await api.generate({ kind: "integration", trace, language: "python", framework: "pytest", outputDir: "tests/api", output: "test_product_flow.py" });
18
18
  *
19
+ * A generated test's data is its own across runs. `unique` (a step's, or a contract test's) names the request-body
20
+ * fields the API keeps unique (a name, slug, SKU or email): the test sends a run-unique value for each, so a rerun or
21
+ * a parallel run does not collide (409) with what an earlier one left. An integration test deletes what it created
22
+ * when it ends, failed or not. A contract test of a create does too when given `cleanupPath`, the item route of what
23
+ * it creates ("/api/v1/products/{product_id}"), with the id in `responseData`.
24
+ *
19
25
  * `{{N.field}}` in a step's path, query or request is the value of `field` (dotted) in step N's response (1-based). The
20
26
  * trace then carries the real value, and the generator chains it: the test reads it from that step's response.
21
27
  * `schema` (a spec path or URL) may stand in for requestData/responseData in a contract or fuzz test.
@@ -80,6 +86,8 @@ function resolveRefs(value, responses) {
80
86
  }
81
87
 
82
88
  const asJson = (v) => (v === undefined ? undefined : typeof v === "string" ? v : JSON.stringify(v));
89
+ // The MCP's path and query params are "key=value,key2=value2"; an object ({ product_id: 1 }) is written that way.
90
+ const asParams = (v) => (v && typeof v === "object" ? Object.entries(v).map(([k, x]) => `${k}=${x}`).join(",") : String(v));
83
91
  const parsed = (v) => {
84
92
  if (typeof v !== "string") return v;
85
93
  try { return JSON.parse(v); } catch { return v; }
@@ -111,6 +119,7 @@ function traceEntries(steps, opts, toEntry) {
111
119
  queryParams: asJson(query),
112
120
  responseBody: asJson(s.response),
113
121
  statusCode: s.status === undefined ? undefined : Number(s.status),
122
+ ...(Array.isArray(s.unique) && s.unique.length ? { uniqueFields: s.unique.map(String) } : {}),
114
123
  authHeader: opts.authHeader,
115
124
  authScheme: opts.authScheme,
116
125
  });
@@ -210,8 +219,13 @@ async function generate(opts) {
210
219
  ...(opts.authHeader !== undefined ? { authHeader: opts.authHeader } : {}),
211
220
  ...(opts.authScheme !== undefined ? { authScheme: opts.authScheme } : {}),
212
221
  ...(opts.chainingKey ? { chainingKey: opts.chainingKey } : {}),
213
- ...(opts.pathParams ? { pathParams: opts.pathParams } : {}),
214
- ...(opts.queryParams ? { queryParams: opts.queryParams } : {}),
222
+ ...(opts.pathParams ? { pathParams: asParams(opts.pathParams) } : {}),
223
+ ...(opts.queryParams ? { queryParams: asParams(opts.queryParams) } : {}),
224
+ // The test's data stays its own: an integration test cleans up what it created; a contract test sends unique
225
+ // values for the fields it names and deletes its create through cleanupPath.
226
+ ...(opts.kind === "integration" ? { emitIntegrationTeardown: true } : {}),
227
+ ...(opts.kind === "contract" && Array.isArray(opts.unique) && opts.unique.length ? { uniqueFields: opts.unique.map(String) } : {}),
228
+ ...(opts.kind === "contract" && opts.cleanupPath ? { cleanupPath: String(opts.cleanupPath) } : {}),
215
229
  language: opts.language,
216
230
  ...(opts.framework ? { framework: opts.framework } : {}),
217
231
  outputDir,
package/dist/VERSION.json CHANGED
@@ -1 +1 @@
1
- {"version":"0.13.0-v3.181","commit":"301a6fa10fd353e0dd1a5c52f8f0d3bac25d952e"}
1
+ {"version":"0.13.0-v3.182","commit":"2ad5799177beca32d06133f0f68bc16d3f0bdf0e"}
package/dist/main.js CHANGED
@@ -934,11 +934,11 @@ ${a.body.slice(0,6e3)}
934
934
  `)||`exit ${n.status??n.code??"?"}`}};function Bkr(t){let e=[],r="",n;for(let s=0;s<t.length;s++){let i=t[s];if(n)i===n&&(n=void 0);else if(i==='"'||i==="'")n=i;else if(i==="&"&&t[s+1]==="&"){e.push(r.trim()),r="",s++;continue}r+=i}return[...e,r.trim()].filter(Boolean)}var _kr=/<[^<>\s][^<>]*>/;function Dkr(t,e,r=Fbt){if(!t.tests.length||!t.lint)return[];let n=[],s=t.lint.command.trim();if(!s)return t.lint.clean||n.push("lint.clean is false and no lint command is recorded \u2014 run the repo's linter on your test files, fix what it reports, and record the exact command you ran"),n;if(_kr.test(s))return[`lint command '${s}' names a placeholder, not files: record the exact command you ran, with the file paths`];let i=e;for(let o of Bkr(s)){let a=/^cd\s+("[^"]+"|'[^']+'|\S+)$/.exec(o);if(a){if(i=NY.resolve(i,a[1].replace(/^["']|["']$/g,"")),NY.relative(e,i).startsWith(".."))return[`lint command '${s}' runs outside the repository \u2014 record the exact command you ran, from the repository`];continue}let{steps:c}=Nd(o);if(!c)break;let l=c.reduce((u,d)=>u??r(d,i),void 0);if(l!==void 0)return[`the repo's linter still reports problems in your test files \u2014 '${o}' failed:
935
935
  ${l}
936
936
  Fix them in the test files, not the lint config, run it on your files again, and record it`]}return t.lint.clean||n.push("lint.clean is false: fix what the linter reports in your test files, run it on your files again, and record clean: true"),n}function Ubt(t,e=Fbt){return{name:kkr,verify(r,n,s,i){let o=t(r,n.stdout);if(!o)return{name:this.name,passed:!0};let a=Dkr(o,r.inputs.repositoryPath,e);return a.length?i&&!i.retryLeft?{name:this.name,passed:!0,details:`kept the tests, lint not clean: ${a[0]}`}:{...ve(this.name,a),repairable:!0}:{name:this.name,passed:!0}}}}var ZEe="write.json",ewe=["adapt.json","generate.json","generate-ui.json","finalize.json",hg,LM],Hbt="write-no-secret-leak",zbt="write-checkout-intact",Gbt="write-dependencies",p0=t=>Pr.join(t.outputsDir,"write-scratch"),Hr=t=>Pr.join(p0(t),ZEe);function jbt(t){if(Ps(t))return nf(t);let e=se(t,"plan.json")?.journeys;if(!Array.isArray(e)||!e.length)return[];let r=new Set((qr(t)?.generate??[]).map(n=>n.id));return e.filter(n=>n&&typeof n.id=="string"&&Array.isArray(n.steps)&&r.has(n.id))}function Okr(t,e={}){if(!t.length)return[];let r=e.noApp?["Each is one in-process test of the whole flow: it drives the UI the way the repo's in-process tests do, checks after","each step what the user then sees, and asserts every part of the outcome at the end. Mark each step so a failure","names it (a labelled assertion, or the runner's own step or subtest), and let a failed check go on to the next step","where the test still allows it.","A journey no in-process test can walk here goes under notWritten, with what it needs that is missing: it is","reported as not covered, never left out."]:["Each is one test of the whole flow, of its kind. A browser journey runs against the real app: it opens the entry route","and does every step through the UI in order (no URL jump to a later step, no stub of the app's own network). An","in-process journey drives the UI the way the repo's in-process tests do. Either checks after each step what the user then sees, and","asserts every part of the outcome at the end, after a reload where it must persist. Where a step lands on another page (a","redirect after a save, a detail or confirmation page), check on that page what it shows of what the user entered or saved","(the values, totals and counts), not only its address. Mark each step so a failure names it (in Playwright, test.step),","and let a failed check go on to the next step where the page still allows it, so one defect does not hide the rest.","A journey whose test cannot run here (a browser, package or service it needs cannot be set up) goes under notWritten,","with what is missing: it is reported as not covered, never left out."];return["User journeys:",...t.map(n=>[` - ${n.id} ${n.name} \u2192 ${n.testFile}`,` entry: ${n.entry}`,` steps: ${oT(n)}`,...n.finishes?.trim()?[` finishes: ${n.finishes.trim()}`]:[],` outcome: ${n.outcome}`].join(`
937
- `)),...r,'List each under "journeys" in your result (its file and test title, and where it broke when it did) and put its id in',`that file's "covers".`,""]}function Mkr(t){return["","Record each ui case's browser test with the Skyramp walk helper instead of writing it from scratch: a Node script that","calls `require(process.env.SKYRAMP_WALK).walk(...)` and walks the case once against the running app (usage at the top",`of ${t.helper}). It writes a Skyramp Playwright test that checks the request body of each action marked with`,"`w.expectRequest()`. That test is your draft: examine its assertions and strengthen them, so each step is checked","to have worked and the end checks every part of the case's expected outcome. Then fit it to the repo (its page objects,","step markers, location and naming), keeping the request checks, and run it the way the repo runs its browser tests.","Each browser test you add in Testbot's own test package, or in a repo package whose manifest already declares","@skyramp/skyramp (skyramp in Python), keeps the Skyramp page wrapper: `page = newSkyrampPlaywrightPage(page)`","(new_skyramp_playwright_page in Python), in the test or in a fixture or page object it imports, even where you fit it to","the repo's own fixtures or write a case by hand after its walk failed. In a repo package that does not declare it,","write the test the repo's way, with its own Playwright and no Skyramp import: don't add to the repo's dependencies.","An existing test you update keeps the repo's way.","The walk script is scratch: keep it out of the repo. When a step fails, the helper prints the step, the page URL and an","accessibility snapshot of the page: fix the selector from that snapshot and walk again."]}function Qkr(t,e){if(!t.use)return[];let r=t.placement==="test-package"&&t.runner?`in the Testbot test package for ${t.runner} (below), named its way (${JO[t.runner]}); the package declares the Skyramp SDK`:`beside the tests in ${t.dir}, written the way they are; where that test environment does not declare the Skyramp SDK, add it to its manifest and list it in dependenciesAdded`;return["",`Backend API cases with Skyramp (${t.reason}): ${t.cases.join(", ")}. A case that belongs in an existing test file`,"goes there, in that file's style, with no draft. A new test file starts from a Skyramp draft instead of from scratch: one","generation per new file, for the requests its cases make, and none for a file you will not keep. In a scratch Node script,",`\`const api = require(process.env.SKYRAMP_API)\` (usage at the top of ${e.helper}), language "${t.language}",`,`framework "${t.framework}", outputDir where the test goes, output a new file name:`,' - one request: `api.generate({ kind: "contract", baseUrl, endpoint, method, requestData, responseData, statusCode, ... })`,'," always with its method (only that method's test is generated), the request and response from the case's steps (or the"," app's OpenAPI spec as `schema`);"," - several requests in order: `api.trace(steps, { baseUrl, authHeader, authScheme })` from the case's steps (each step's method,",' path, request, status and response; an id an earlier step returned as {{N.field}}), then `api.generate({ kind: "integration", trace, ... })`.',`Write it ${r}. The draft checks only the status and the response's shape. Fix it so it runs (auth, seed data,`,"parent resources, ids the app gives), then strengthen its assertions to the case: each field and value of its expected outcome,",`and the error cases its plan names. Keep Skyramp's client and request code, keep the requirement ids in "functions", name it the repo's way.`,"In your result give its skyramp.draft: the draft path the helper returned (each, if you merged several into one file). If the helper fails for a case, fix the call once from its","error; then write the test by hand and give skyramp.handWritten (the call and its error) \u2014 by hand only after the helper failed.","The script and the trace are scratch: keep them out of the repo."]}function Lkr(t){let r=xt(t.inputs).reduce((i,o)=>{let a=o.path===t.inputs.repositoryPath?"":`${Pr.basename(Pr.resolve(o.path))}/`,c;try{c=hAt(o.path,a)}catch{return i}return{apiTests:[...i.apiTests,...c.apiTests],skyrampTests:[...i.skyrampTests,...c.skyrampTests],backendLanguage:i.backendLanguage??c.backendLanguage}},{apiTests:[],skyrampTests:[]}),n=(t.inputs.requirementItems??[]).flatMap(i=>{if(!("path"in i))return[];try{return[na.readFileSync(i.path,"utf-8")]}catch{return[]}}),s=[...Wm(t.inputs).map(i=>i.body),...t.inputs.instructions??[],...n];return fAt({facts:r,cases:qr(t)?.generate??[],requests:s})}function kY(t){return Ps(t)?!1:(qr(t)?.generate??[]).some(e=>e.type==="ui")}function Fkr(t){let e=_T(t);if(e){for(let n of[...ewe,hl])na.rmSync(Pr.join(t.outputsDir,n),{force:!0});return{ok:!1,reasons:[e]}}let r=qr(t);return r?r.generate.length===0&&r.maintenance.length===0&&!Lbe(ZT(t))?{ok:!1,reasons:["the plan has no test cases and no maintenance"]}:{ok:!0,reasons:[]}:{ok:!1,reasons:["no test plan (recommend.json) to write tests from"]}}function Ukr(t){let e=LAe(t),r=[];if(Ps(t))for(let{repo:b,job:E}of twe(t))J3({outputsDir:t.outputsDir,provision:!!t.inputs.provisionRuntime,run:pg,notes:r,deadlineMs:t.deadlineMs},b,E);let n=Z3(t);for(let b of[...ewe,"write.json",TT,hl])na.rmSync(Pr.join(t.outputsDir,b),{force:!0});let s=kY(t),i=(qr(t)?.generate??[]).some(b=>b.type!=="ui")||Lbe(ZT(t)),o=t.inputs.skyrampHelpers!==!1,{walk:a,note:c}=s&&o?Nbt(p0(t),t.inputs.skyrampMcp):{},l=o?Ps(t)?{use:!1,rule:"d",cases:[],reason:"no app runs on the unit path"}:Lkr(t):{use:!1,rule:"off",cases:[],reason:"the Skyramp helpers are switched off for this run"},u,d;if(l.use){let b=Bbt(p0(t),t.inputs.skyrampMcp,{sdk:a?.env.SKYRAMP_WALK_SDK});u=b.api,d=b.note,u||(l={...l,use:!1,cases:[],reason:`${l.reason}; not used: ${b.note}`})}co(t).info(`[write] Skyramp API tests: ${l.use?"yes":"no"} (rule ${l.rule}) \u2014 ${l.reason}`),nEe(Pr.join(t.outputsDir,LM),{schemaVersion:1,decision:l,generations:[],tests:[]});let p=l.use&&l.placement==="test-package"?l.runner:void 0,{packages:m,notes:f}=sgt(t,{ui:s,backend:i,...p?{skyramp:p}:{}});Vbt(t,m);let h=[...r,...f,...sx(t,"backend",[],{declaredOnly:!0,packages:m}),...s?sx(t,"ui",[],{declaredOnly:!0,packages:m}):[]];return c&&h.push(c),d&&h.push(d),{checkpoint:Pr.join(p0(t),ZEe),secretGateBaseline:e,checkoutHeads:n,provisionNotes:h,sharedHelpers:$kr(t),testPackages:m,dependencyBaseline:LAe(t),skyrampWalk:a,skyrampApi:{decision:l,...u?{helper:u}:{}}}}function Vbt(t,e){let r=Pr.join(t.outputsDir,hl);e.length===0?na.rmSync(r,{force:!0}):na.writeFileSync(r,JSON.stringify({schemaVersion:1,packages:[...e]},null,2)+`
937
+ `)),...r,'List each under "journeys" in your result (its file and test title, and where it broke when it did) and put its id in',`that file's "covers".`,""]}function Mkr(t){return["","Record each ui case's browser test with the Skyramp walk helper instead of writing it from scratch: a Node script that","calls `require(process.env.SKYRAMP_WALK).walk(...)` and walks the case once against the running app (usage at the top",`of ${t.helper}). It writes a Skyramp Playwright test that checks the request body of each action marked with`,"`w.expectRequest()`. That test is your draft: examine its assertions and strengthen them, so each step is checked","to have worked and the end checks every part of the case's expected outcome. Then fit it to the repo (its page objects,","step markers, location and naming), keeping the request checks, and run it the way the repo runs its browser tests.","Each browser test you add in Testbot's own test package, or in a repo package whose manifest already declares","@skyramp/skyramp (skyramp in Python), keeps the Skyramp page wrapper: `page = newSkyrampPlaywrightPage(page)`","(new_skyramp_playwright_page in Python), in the test or in a fixture or page object it imports, even where you fit it to","the repo's own fixtures or write a case by hand after its walk failed. In a repo package that does not declare it,","write the test the repo's way, with its own Playwright and no Skyramp import: don't add to the repo's dependencies.","An existing test you update keeps the repo's way.","The walk script is scratch: keep it out of the repo. When a step fails, the helper prints the step, the page URL and an","accessibility snapshot of the page: fix the selector from that snapshot and walk again."]}function Qkr(t,e){if(!t.use)return[];let r=t.placement==="test-package"&&t.runner?`in the Testbot test package for ${t.runner} (below), named its way (${JO[t.runner]}); the package declares the Skyramp SDK`:`beside the tests in ${t.dir}, written the way they are; where that test environment does not declare the Skyramp SDK, add it to its manifest and list it in dependenciesAdded`;return["",`Backend API cases with Skyramp (${t.reason}): ${t.cases.join(", ")}. A case that belongs in an existing test file`,"goes there, in that file's style, with no draft. A new test file starts from a Skyramp draft instead of from scratch: one","generation per new file, for the requests its cases make, and none for a file you will not keep. In a scratch Node script,",`\`const api = require(process.env.SKYRAMP_API)\` (usage at the top of ${e.helper}), language "${t.language}",`,`framework "${t.framework}", outputDir where the test goes, output a new file name:`,' - one request: `api.generate({ kind: "contract", baseUrl, endpoint, method, requestData, responseData, statusCode, ... })`,'," always with its method (only that method's test is generated), the request and response from the case's steps (or the"," app's OpenAPI spec as `schema`);"," - several requests in order: `api.trace(steps, { baseUrl, authHeader, authScheme })` from the case's steps (each step's method,",' path, request, status and response; an id an earlier step returned as {{N.field}}), then `api.generate({ kind: "integration", trace, ... })`.'," Name the fields the API keeps unique, a step's `unique: [\"name\"]` or a contract test's `unique`, and give a contract",' test of a create `cleanupPath`, the route it is deleted through ("/api/v1/products/{product_id}"), with the id in its'," responseData. Keep the draft's unique values and cleanup when you fix it.",`Write it ${r}. The draft checks only the status and the response's shape. Fix it so it runs (auth, seed data,`,"parent resources, ids the app gives), then strengthen its assertions to the case: each field and value of its expected outcome,",`and the error cases its plan names. Keep Skyramp's client and request code, keep the requirement ids in "functions", name it the repo's way.`,"In your result give its skyramp.draft: the draft path the helper returned (each, if you merged several into one file). If the helper fails for a case, fix the call once from its","error; then write the test by hand and give skyramp.handWritten (the call and its error) \u2014 by hand only after the helper failed.","The script and the trace are scratch: keep them out of the repo."]}function Lkr(t){let r=xt(t.inputs).reduce((i,o)=>{let a=o.path===t.inputs.repositoryPath?"":`${Pr.basename(Pr.resolve(o.path))}/`,c;try{c=hAt(o.path,a)}catch{return i}return{apiTests:[...i.apiTests,...c.apiTests],skyrampTests:[...i.skyrampTests,...c.skyrampTests],backendLanguage:i.backendLanguage??c.backendLanguage}},{apiTests:[],skyrampTests:[]}),n=(t.inputs.requirementItems??[]).flatMap(i=>{if(!("path"in i))return[];try{return[na.readFileSync(i.path,"utf-8")]}catch{return[]}}),s=[...Wm(t.inputs).map(i=>i.body),...t.inputs.instructions??[],...n];return fAt({facts:r,cases:qr(t)?.generate??[],requests:s})}function kY(t){return Ps(t)?!1:(qr(t)?.generate??[]).some(e=>e.type==="ui")}function Fkr(t){let e=_T(t);if(e){for(let n of[...ewe,hl])na.rmSync(Pr.join(t.outputsDir,n),{force:!0});return{ok:!1,reasons:[e]}}let r=qr(t);return r?r.generate.length===0&&r.maintenance.length===0&&!Lbe(ZT(t))?{ok:!1,reasons:["the plan has no test cases and no maintenance"]}:{ok:!0,reasons:[]}:{ok:!1,reasons:["no test plan (recommend.json) to write tests from"]}}function Ukr(t){let e=LAe(t),r=[];if(Ps(t))for(let{repo:b,job:E}of twe(t))J3({outputsDir:t.outputsDir,provision:!!t.inputs.provisionRuntime,run:pg,notes:r,deadlineMs:t.deadlineMs},b,E);let n=Z3(t);for(let b of[...ewe,"write.json",TT,hl])na.rmSync(Pr.join(t.outputsDir,b),{force:!0});let s=kY(t),i=(qr(t)?.generate??[]).some(b=>b.type!=="ui")||Lbe(ZT(t)),o=t.inputs.skyrampHelpers!==!1,{walk:a,note:c}=s&&o?Nbt(p0(t),t.inputs.skyrampMcp):{},l=o?Ps(t)?{use:!1,rule:"d",cases:[],reason:"no app runs on the unit path"}:Lkr(t):{use:!1,rule:"off",cases:[],reason:"the Skyramp helpers are switched off for this run"},u,d;if(l.use){let b=Bbt(p0(t),t.inputs.skyrampMcp,{sdk:a?.env.SKYRAMP_WALK_SDK});u=b.api,d=b.note,u||(l={...l,use:!1,cases:[],reason:`${l.reason}; not used: ${b.note}`})}co(t).info(`[write] Skyramp API tests: ${l.use?"yes":"no"} (rule ${l.rule}) \u2014 ${l.reason}`),nEe(Pr.join(t.outputsDir,LM),{schemaVersion:1,decision:l,generations:[],tests:[]});let p=l.use&&l.placement==="test-package"?l.runner:void 0,{packages:m,notes:f}=sgt(t,{ui:s,backend:i,...p?{skyramp:p}:{}});Vbt(t,m);let h=[...r,...f,...sx(t,"backend",[],{declaredOnly:!0,packages:m}),...s?sx(t,"ui",[],{declaredOnly:!0,packages:m}):[]];return c&&h.push(c),d&&h.push(d),{checkpoint:Pr.join(p0(t),ZEe),secretGateBaseline:e,checkoutHeads:n,provisionNotes:h,sharedHelpers:$kr(t),testPackages:m,dependencyBaseline:LAe(t),skyrampWalk:a,skyrampApi:{decision:l,...u?{helper:u}:{}}}}function Vbt(t,e){let r=Pr.join(t.outputsDir,hl);e.length===0?na.rmSync(r,{force:!0}):na.writeFileSync(r,JSON.stringify({schemaVersion:1,packages:[...e]},null,2)+`
938
938
  `)}function $kr(t){try{return Sbt(xt(t.inputs).flatMap(e=>{let r=e.path===t.inputs.repositoryPath?"":Dt(t.inputs,e.slug);return wbt(e.path).map(n=>({label:r,module:n}))}))}catch{return[]}}function qkr(t){let e=il(t.inputs)?["It is a shared environment this policy protects: write and run no test that changes state there (a create, edit or delete, a form that submits, a setup that seeds); list any such test in notWritten instead."]:[];if(t.inputs.appRunning){let o=oW(se(t,`${Ii}.json`));return[t.inputs.appRunning.trim(),...$bt(t),...e,...o?[o]:[]]}let r=se(t,"deploy.json"),n=El(t.inputs.repositoryPath),s=n.setupScript?`start it with ${Pr.relative(t.inputs.repositoryPath,n.setupScript)} and check it answers`:"start it the way the repo documents";if(PW(r)){let o=t.prepared?.appUrls?.urls??[],a=o.length?QEe(o):Object.entries(r.baseUrls??{}).map(([l,u])=>`${l} ${u}`).join(", "),c=r.health?.url??r.health?.command;return[`The app is running${a?`: ${a}`:""}.${c?` Health check: ${c}`:""}`,...$bt(t,o.length>0),`If it stops answering, ${s}.`]}return[`The app may not be running.${r?.bringupError?.output?` Planning could not start it: ${r.bringupError.output.slice(0,300)}`:""} Check, and if it isn't up, ${s}; leave it running.`,"If you can't get it running, still write the tests and record them as not run (exit null)."]}function $bt(t,e=!1){let r=t.prepared?.appUrls;if(!r?.urls.length)return[];let n=(i,o)=>r.env[i]!==void 0&&!r.urls.some(a=>a.envVar===i)?[`$${i} is ${o}`]:[],s=[...n(OEe,"the app"),...n(MEe,"the pages")];return[`${e?"":`Its addresses are in ${QEe(r.urls)}. `}A test reads each address from its variable, with the address as the default${s.length?` (${s.join("; ")})`:""}.`]}function Hkr(t){let e=Fbe(t);return["No app is running, and none is started for this run: Validation Triage decided the change is proven in-process","(targeted_unit). Every test runs in one process, the way the repo's own in-process tests run: no network or running","app, so nothing that calls a live service, the Skyramp client and its runtime mocks included. Mock outbound calls","(HTTP, queues, other services) inside the test with the repo's own fixtures and mocking tools. Run the tests the way","the repo's CI runs them (its runner, config and environment), selected as step 4 says. Each new test file is named the way its runner","collects tests, as the repo's own tests are. Maintain every stale existing test that can run in-process. An existing","test that needs the running app goes under notWritten, by its path, with what it needs that this run does not have:","it is reported as not covered.",'Give each test file in a framework other than pytest, jest, vitest and node --test a "run": native-run re-runs your tests',"by it, and each declared run is run before you finish, as native-run will run it, so one that cannot start or reports","none of its file's tests comes back to you. A run native-run would refuse to start comes back to you on every attempt.","The plan's runs for existing tests are run then too: for an existing test file","whose run cannot start or reports none of its tests, list it under tests as unchanged, with a run that does.",`${Hy}`,...hgt(t.outputsDir),...Xkr(t),...e.length?["The Target puts new unit tests in these directories, over the repo's own layout: put each new test under its repository's directory, not beside the existing tests:",...e.map(r=>` - ${Dt(t.inputs,r.slug)}${r.dir}/`)]:[]]}function zkr(t){return[...t.behaviors.length?['Behaviors that need a new in-process test (give each id in "covers"):',...t.behaviors.map(e=>` - ${e.id}: ${e.statement}
939
939
  missing evidence to obtain: ${e.covers||"(unstated)"}
940
940
  where the change is: ${e.where}`),"Every behavior above gets an in-process test, except that a test of what the change could break stays only if it","fails on the change (step 1).",""]:[],...t.defects.length?["For each defect suspected above, write a test that reaches it and expects the correct behaviour, so it fails on this","change and passes once the defect is fixed, and give its id in covers; a defect no in-process test can reach goes","under notWritten, with why.",""]:[],...t.browserJourneys.length?["These user journeys need a browser and the running app, which this run does not have: no in-process test can walk","them, so each goes under notWritten by its id, with what it needs that is missing:",...t.browserJourneys.map(e=>` - ${e.id} ${e.name}`),""]:[]]}function Gkr(t,e=[]){let r="";try{let n=wl(t,e),s=[...new Set(n.flatMap(i=>i.secretEnv))].filter(i=>e.includes(i));r=Qht(cg(n),s).trim()}catch{return[]}return r?["Some variables the repo's CI sets would clash with your own model access, so their values are in your shell",`renamed, under a TESTBOT_CI_ prefix. Run a test that needs CI's values with them mapped back: \`${r} <test command>\`.`]:[]}function jkr(t){let e=t.prepared,r=t.inputs.repositoryPath,n=dn(t),s=n?.repository.currentBranch??"HEAD",i=n?.repository.baseBranch??t.inputs.baseBranch??"origin/main",o=qr(t),a=se(t,"analyze.json"),c=new Map((a?.behaviors??[]).map(I=>[I.id,I.statement])),l=(a?.findings??[]).filter(I=>I.status!=="refuted"&&I.status!=="latent"),u=(a?.findings??[]).filter(I=>I.status==="latent"),d=se(t,"plan.json"),p=new Map((d?.cases??[]).map(I=>[I.id,I.steps])),m=Zo(t.inputs.requirementItems),f=new Map((d?.requirements??[]).flatMap(I=>I.reference&&I.criterion?[[I.id,` (${Id(I.reference,I.criterion,m)})`]]:[])),b=kY(t)&&Vh(t),E=Ps(t),A=ZT(t),x=jbt(t),C=new Set(x.map(I=>I.id)),R=o.generate.filter(I=>!C.has(I.id)).map(I=>{let N=[I.from_behavior&&`${I.from_behavior}: ${c.get(I.from_behavior)??""}`.trim(),I.from_finding&&`defect ${I.from_finding}`].filter(Boolean).join("; ");return[` - ${I.id} [${I.type}] ${I.target}${N?` \u2014 ${N}`:""}`,` do: ${p.get(I.id)??I.rationale}`,` expect: ${I.expected_observable}`].join(`
941
- `)});return["Write functional tests for this PR and run them. The plan below came from reading the PR; turn it into tests in the","repo, run them, and tell me what they show about the change.","",`The repo is at ${r}, checked out on branch \`${s}\`. You can see the change with \`git diff ${i}...HEAD\`.`,...NEe(t),...wu(t.inputs),"",...Byt(t.inputs),...Dyt(t.inputs),...a?.requirements?.length?["Requirements:",...a.requirements.map(I=>` - ${I.id}${f.get(I.id)??""}: ${I.statement}`),""]:[],...l.length?["Defects suspected while reading the change:",...l.map(I=>` - ${I.id} (${I.severity}) ${I.file}${I.line?`:${I.line}`:""} \u2014 ${I.claim}`),""]:[],...u.length?["Defects the base already has, in code the change left as it was: not this change's. Write no test that fails on them;","if your tests show one, report it as existing (its id as the issue's finding):",...u.map(I=>` - ${I.id} (${I.severity}) ${I.file}${I.line?`:${I.line}`:""} \u2014 ${I.claim}`),""]:[],"Test plan. Each case's expected outcome is the one the plan sets, not whatever the app does:",...R.length?R:[" (no new cases)"],"",...NW(d?.questions),...o.maintenance.length?["","Existing tests to maintain:",...o.maintenance.map(I=>` - ${I.action.toUpperCase()} ${Py(t.inputs,I.repository,I.test)} \u2014 ${I.evidence}`)]:[],"",...E?zkr(A):[],...Okr(x,{noApp:E}),...E?Hkr(t):qkr(t),...b?["For the ui cases you have the Skyramp browser tools (mcp__skyramp__browser_*): look at the real page before you pick locators."]:[],...e.skyrampWalk?Mkr(e.skyrampWalk):[],...e.skyrampApi?.helper?Qkr(e.skyrampApi.decision,e.skyrampApi.helper):[],...e.provisionNotes.length?[`Setup notes: ${e.provisionNotes.join("; ")}`]:[],...e.testPackages?.length?["",...opt(e.testPackages,{skyrampVersion:E?void 0:Vkr(t,e),nodeTests:(E||o.generate.some(I=>I.type!=="ui"))&&vbe(r)})]:[],...e.sharedHelpers?.length?["",...e.sharedHelpers]:[],...Gkr(r,t.providedEnvKeys??[]),"","1. Write each test the way the repo already tests that code (the same kind of test, framework, directories, naming,",...E?[" helpers and fixtures), unless that kind cannot check its case in one process; then use the in-process kind that can,"," and where none can, list the case under notWritten with what it needs."]:[" helpers and fixtures), unless that kind of test cannot check its case; then use the kind that can."]," Reuse the repo's helpers, page objects and fixtures, and put yours where someone who knows this repo would look for"," them: never copy a helper the repo already has \u2014 import it. A helper only one test file uses stays in that file,"," unless the repo keeps that kind in its own module (a page object per page, say). A helper two or more test files use"," \u2014 yours, or one you take from another test file \u2014 is defined once, where the repo keeps shared helpers for those"," tests: usually the support module they already import, or a new module beside it when that one no longer fits (say"," the repo splits its helpers by area, or the module has grown large); where there is none yet, beside the tests that",` use it. Name a new module for what it holds, not "utils" or "Skyramp". Change the repo's own helpers only as far as`," that needs. Use every name you import."," Where the repo's browser tests reach its pages through page objects,"," so do yours: locate a page's elements in its page object, the way its existing members locate theirs, and add a page"," object, where the repo keeps them, for a page that has none. Find an element by its role and name, label or test id;"," when a locator matches more than one element, narrow it to the one you mean (inside its row, dialog or region, or by"," its text) rather than taking .first(), .last() or .nth(). Pick by position only where the order is what the test checks.",...E?[" Write in-process tests the way the repo's own unit and in-process tests are written. The repo's existing tests"]:[" Don't add unit tests (a function or module called directly, its collaborators mocked). The repo's existing tests"]," are for maintenance only: update the ones the change makes wrong \u2014 the maintenance list is where to start, step 4"," finds the rest \u2014 above all any that assert as correct what the plan says is a defect, rather than writing a new"," one beside it."," Keep each requirement's check on its own, so one failure can't hide another: give it its own test or assertion,"," and stub or seed any data its setup needs that a different defect could break (a list the page loads, a shared"," helper that rejects the response before your assertion reads it). A test that shows a defect asserts only the"," outcome its case expects \u2014 one defect per test \u2014 so it passes once that defect is fixed."," For each element or field the change adds, assert that it is there (shown, or present in the response); clicking"," or filling it is not an assertion. For each it removes or renames, assert that it is gone: not shown, a count of 0,"," the field missing, a 404, the old option no longer offered."," Where a case's steps give the exact requests, send them as they are rather than working them out again, and keep"," those tests together in one file."," The plan is where to start, not the limit: if you notice something else the change should do, or could break,"," while you write and run these, test that too, filed under the plan requirement it guards. A test of what it could"," break that passes stays only when it is the one test of its requirement; remove one that another of your tests",' already covers, and list its case under notWritten with a reason that starts "Passing:" and says what it checked \u2014'," never a journey's test, which stays whether it"," passes or fails. Where nothing says what should happen, only decide it for what"," the change does; how older code treats odd input is not this PR's to test. One case for each kind of input is"," enough: add another only where it could fail while the first passes.","2. Run the tests you wrote together, once, in the foreground, with a short per-test timeout so a hung test fails",` fast; wait for the run to finish (one Bash call may run up to ${B3} minutes), and don't end the session with a test`," run still going: the session is non-interactive and ends when you stop, so nothing would report its result.","3. For each failure, work out whose fault it is, and settle it outside the test before you call it a defect. The"," outcome the test expects must be one the PR, spec or code requires, not one you chose: an exact status where only a"," refusal is required, a message or format nothing states, a value read from a column picked by position. Where only"," part of it is required, assert only that part. A failure before the step that checks the case (sign-in, setup,"," seeding, a helper, an element not found, a timeout) is the test's until you have seen the app do it wrong: look at"," the page or send the request yourself, and find the line of application code that does it."," If the test checks what its case requires and the app does something else, that's a defect in the change: keep the"," test as written, mark the case and its file as a defect, and report the bug with that line. Never change an expected"," outcome to match what the app does. If the test itself is wrong (selector, setup, data, import, timing, an outcome"," nothing requires), fix it and re-run only the tests that failed (e.g. pytest --lf, playwright test --last-failed, or"," by name); a failing case you could not fix is marked defect: false, never a defect. Give each failing case the",` runner's failure as it printed it ("failure"), and each case you keep as a defect its "cause", the application line,`,` and "requires", the words of the PR description or the plan's requirement that state what its failing check expects,`," quoted. When no words state it, the check asserts more than is required: assert only what is, and re-run it."," Don't re-run the whole set at"," the end: the results you already have stand.","4. Then run the repo's existing tests that reach the code the change touches, the way its CI runs them (its command"," and environment), selected as narrowly as still covers that code: the whole suite only when it runs in a few"," minutes. For each one that fails, find out whether this change made it fail; when you can't tell, run it on the"," base branch in a scratch copy outside the"," checkout (e.g. git worktree add under /tmp). One that asserts behaviour the change deliberately replaced is stale:"," update it, or delete it where the behaviour is gone, and re-run it; give staleBecause for it \u2014 the requirement the"," change deliberately alters and the changed line of application code (path:line) that alters it. One that catches"," something the change breaks is a defect: leave it failing and report it. Leave alone failures the change did not cause.","5. If the repo has a linter or formatter for test code, run it on your files, and record the exact command you ran:"," Testbot re-runs it, and sends back what it still reports. The application's manifests and"," lockfiles are its owner's: a test in the repo's own framework uses only what the repo already declares, and you"," never add, upgrade or remove a package in the application's own manifests or lockfiles for Testbot's tests.",E?" For the app's code, reach it the way the repo's in-process tests do (its test client, fixtures and helpers).":" For calls to the app, use the client and helpers the repo's existing tests use for that service."," A browser test goes in the repo's own browser suite where it has one (Selenium, Cypress, Capybara), with its page objects."," Tooling the repo doesn't declare (Playwright where it has none) goes in the Testbot test package"," above, never in the application's manifests; with no such package, write the test with what the repo has."," The one exception is a test that must run inside the application's existing test environment and cannot"," without a package it lacks: add it to the manifest of the checkout the test is in, keep the manifest's"," constraints, upgrade nothing else, update that manifest's lockfile with its own package manager (the updated lockfile"," must name the added package), run the existing tests of that environment, and list it in dependenciesAdded"," with the reason and the manifest's path relative to its checkout; for another checkout of the change,"," name that checkout as the entry's repository (its owner/repo).","",'In your result, give each file you added or updated its "functions": each test function in it as the runner names',"it, with the plan case ids it checks, so that every case it covers is checked by a function you name. Give each","issue its plan finding id and the requirement or clause ids it violates. Name each test for what it checks: no plan","id in a test's name or title, except a label the description or spec itself gives the requirement (AC-3).","","Apart from that scratch copy, stay inside the checkout and leave it on the commit it is on,","with the change's files in place: run or read the base in the scratch copy (or with git show <commit>:<file>), never","by checking the checkout out, resetting or stashing it. Don't commit or push, and leave the tests in the working tree.",`Once you've run the tests the first time, write your result as JSON (below) to ${e.checkpoint}, and keep it current`,"as you go: that file is what's","read. End with a one-line final message \u2014 don't repeat the JSON in it. The report is read by the PR author: describe","their code, not this process.","",Sn(es)].join(`
941
+ `)});return["Write functional tests for this PR and run them. The plan below came from reading the PR; turn it into tests in the","repo, run them, and tell me what they show about the change.","",`The repo is at ${r}, checked out on branch \`${s}\`. You can see the change with \`git diff ${i}...HEAD\`.`,...NEe(t),...wu(t.inputs),"",...Byt(t.inputs),...Dyt(t.inputs),...a?.requirements?.length?["Requirements:",...a.requirements.map(I=>` - ${I.id}${f.get(I.id)??""}: ${I.statement}`),""]:[],...l.length?["Defects suspected while reading the change:",...l.map(I=>` - ${I.id} (${I.severity}) ${I.file}${I.line?`:${I.line}`:""} \u2014 ${I.claim}`),""]:[],...u.length?["Defects the base already has, in code the change left as it was: not this change's. Write no test that fails on them;","if your tests show one, report it as existing (its id as the issue's finding):",...u.map(I=>` - ${I.id} (${I.severity}) ${I.file}${I.line?`:${I.line}`:""} \u2014 ${I.claim}`),""]:[],"Test plan. Each case's expected outcome is the one the plan sets, not whatever the app does:",...R.length?R:[" (no new cases)"],"",...NW(d?.questions),...o.maintenance.length?["","Existing tests to maintain:",...o.maintenance.map(I=>` - ${I.action.toUpperCase()} ${Py(t.inputs,I.repository,I.test)} \u2014 ${I.evidence}`)]:[],"",...E?zkr(A):[],...Okr(x,{noApp:E}),...E?Hkr(t):qkr(t),...b?["For the ui cases you have the Skyramp browser tools (mcp__skyramp__browser_*): look at the real page before you pick locators."]:[],...e.skyrampWalk?Mkr(e.skyrampWalk):[],...e.skyrampApi?.helper?Qkr(e.skyrampApi.decision,e.skyrampApi.helper):[],...e.provisionNotes.length?[`Setup notes: ${e.provisionNotes.join("; ")}`]:[],...e.testPackages?.length?["",...opt(e.testPackages,{skyrampVersion:E?void 0:Vkr(t,e),nodeTests:(E||o.generate.some(I=>I.type!=="ui"))&&vbe(r)})]:[],...e.sharedHelpers?.length?["",...e.sharedHelpers]:[],...Gkr(r,t.providedEnvKeys??[]),"","1. Write each test the way the repo already tests that code (the same kind of test, framework, directories, naming,",...E?[" helpers and fixtures), unless that kind cannot check its case in one process; then use the in-process kind that can,"," and where none can, list the case under notWritten with what it needs."]:[" helpers and fixtures), unless that kind of test cannot check its case; then use the kind that can."]," Reuse the repo's helpers, page objects and fixtures, and put yours where someone who knows this repo would look for"," them: never copy a helper the repo already has \u2014 import it. A helper only one test file uses stays in that file,"," unless the repo keeps that kind in its own module (a page object per page, say). A helper two or more test files use"," \u2014 yours, or one you take from another test file \u2014 is defined once, where the repo keeps shared helpers for those"," tests: usually the support module they already import, or a new module beside it when that one no longer fits (say"," the repo splits its helpers by area, or the module has grown large); where there is none yet, beside the tests that",` use it. Name a new module for what it holds, not "utils" or "Skyramp". Change the repo's own helpers only as far as`," that needs. Use every name you import."," Where the repo's browser tests reach its pages through page objects,"," so do yours: locate a page's elements in its page object, the way its existing members locate theirs, and add a page"," object, where the repo keeps them, for a page that has none. Find an element by its role and name, label or test id;"," when a locator matches more than one element, narrow it to the one you mean (inside its row, dialog or region, or by"," its text) rather than taking .first(), .last() or .nth(). Pick by position only where the order is what the test checks.",...E?[" Write in-process tests the way the repo's own unit and in-process tests are written. The repo's existing tests"]:[" Don't add unit tests (a function or module called directly, its collaborators mocked). The repo's existing tests"]," are for maintenance only: update the ones the change makes wrong \u2014 the maintenance list is where to start, step 4"," finds the rest \u2014 above all any that assert as correct what the plan says is a defect, rather than writing a new"," one beside it."," Keep each requirement's check on its own, so one failure can't hide another: give it its own test or assertion,"," and stub or seed any data its setup needs that a different defect could break (a list the page loads, a shared"," helper that rejects the response before your assertion reads it). A test that shows a defect asserts only the"," outcome its case expects \u2014 one defect per test \u2014 so it passes once that defect is fixed."," For each element or field the change adds, assert that it is there (shown, or present in the response); clicking"," or filling it is not an assertion. For each it removes or renames, assert that it is gone: not shown, a count of 0,"," the field missing, a 404, the old option no longer offered."," Where a case's steps give the exact requests, send them as they are rather than working them out again, and keep"," those tests together in one file.",...E?[]:[" A test runs against a fresh app, and again against one it already ran on. It makes the data it needs in its own"," setup (a file to share, a record to edit) rather than relying on what exists only because this session made it;"," what the app's setup recipe creates is there in CI too. A value the app keeps unique (a name, slug, SKU, email) is"," made unique to the run, the one change to a case's exact requests, and the test finds and asserts by the value it"," sent. When it ends, failed or not, it deletes what it made, through the API where it can."]," The plan is where to start, not the limit: if you notice something else the change should do, or could break,"," while you write and run these, test that too, filed under the plan requirement it guards. A test of what it could"," break that passes stays only when it is the one test of its requirement; remove one that another of your tests",' already covers, and list its case under notWritten with a reason that starts "Passing:" and says what it checked \u2014'," never a journey's test, which stays whether it"," passes or fails. Where nothing says what should happen, only decide it for what"," the change does; how older code treats odd input is not this PR's to test. One case for each kind of input is"," enough: add another only where it could fail while the first passes.","2. Run the tests you wrote together, once, in the foreground, with a short per-test timeout so a hung test fails",` fast; wait for the run to finish (one Bash call may run up to ${B3} minutes), and don't end the session with a test`," run still going: the session is non-interactive and ends when you stop, so nothing would report its result.","3. For each failure, work out whose fault it is, and settle it outside the test before you call it a defect. The"," outcome the test expects must be one the PR, spec or code requires, not one you chose: an exact status where only a"," refusal is required, a message or format nothing states, a value read from a column picked by position. Where only"," part of it is required, assert only that part. A failure before the step that checks the case (sign-in, setup,"," seeding, a helper, an element not found, a timeout) is the test's until you have seen the app do it wrong: look at"," the page or send the request yourself, and find the line of application code that does it."," If the test checks what its case requires and the app does something else, that's a defect in the change: keep the"," test as written, mark the case and its file as a defect, and report the bug with that line. Never change an expected"," outcome to match what the app does. If the test itself is wrong (selector, setup, data, import, timing, an outcome"," nothing requires), fix it and re-run only the tests that failed (e.g. pytest --lf, playwright test --last-failed, or"," by name); a failing case you could not fix is marked defect: false, never a defect. Give each failing case the",` runner's failure as it printed it ("failure"), and each case you keep as a defect its "cause", the application line,`,` and "requires", the words of the PR description or the plan's requirement that state what its failing check expects,`," quoted. When no words state it, the check asserts more than is required: assert only what is, and re-run it."," Don't re-run the whole set at"," the end: the results you already have stand.","4. Then run the repo's existing tests that reach the code the change touches, the way its CI runs them (its command"," and environment), selected as narrowly as still covers that code: the whole suite only when it runs in a few"," minutes. For each one that fails, find out whether this change made it fail; when you can't tell, run it on the"," base branch in a scratch copy outside the"," checkout (e.g. git worktree add under /tmp). One that asserts behaviour the change deliberately replaced is stale:"," update it, or delete it where the behaviour is gone, and re-run it; give staleBecause for it \u2014 the requirement the"," change deliberately alters and the changed line of application code (path:line) that alters it. One that catches"," something the change breaks is a defect: leave it failing and report it. Leave alone failures the change did not cause.","5. If the repo has a linter or formatter for test code, run it on your files, and record the exact command you ran:"," Testbot re-runs it, and sends back what it still reports. The application's manifests and"," lockfiles are its owner's: a test in the repo's own framework uses only what the repo already declares, and you"," never add, upgrade or remove a package in the application's own manifests or lockfiles for Testbot's tests.",E?" For the app's code, reach it the way the repo's in-process tests do (its test client, fixtures and helpers).":" For calls to the app, use the client and helpers the repo's existing tests use for that service."," A browser test goes in the repo's own browser suite where it has one (Selenium, Cypress, Capybara), with its page objects."," Tooling the repo doesn't declare (Playwright where it has none) goes in the Testbot test package"," above, never in the application's manifests; with no such package, write the test with what the repo has."," The one exception is a test that must run inside the application's existing test environment and cannot"," without a package it lacks: add it to the manifest of the checkout the test is in, keep the manifest's"," constraints, upgrade nothing else, update that manifest's lockfile with its own package manager (the updated lockfile"," must name the added package), run the existing tests of that environment, and list it in dependenciesAdded"," with the reason and the manifest's path relative to its checkout; for another checkout of the change,"," name that checkout as the entry's repository (its owner/repo).","",'In your result, give each file you added or updated its "functions": each test function in it as the runner names',"it, with the plan case ids it checks, so that every case it covers is checked by a function you name. Give each","issue its plan finding id and the requirement or clause ids it violates. Name each test for what it checks: no plan","id in a test's name or title, except a label the description or spec itself gives the requirement (AC-3).","","Apart from that scratch copy, stay inside the checkout and leave it on the commit it is on,","with the change's files in place: run or read the base in the scratch copy (or with git show <commit>:<file>), never","by checking the checkout out, resetting or stashing it. Don't commit or push, and leave the tests in the working tree.",`Once you've run the tests the first time, write your result as JSON (below) to ${e.checkpoint}, and keep it current`,"as you go: that file is what's","read. End with a one-line final message \u2014 don't repeat the JSON in it. The report is read by the PR author: describe","their code, not this process.","",Sn(es)].join(`
942
942
  `)}function Vkr(t,e){let r=e.skyrampApi?.helper?.env.SKYRAMP_API_SDK;return r&&e.skyrampApi?.decision.placement==="test-package"?RY(r):Vh(t)?bT(t.inputs.skyrampMcp):void 0}function YM(t,e,r){let n=Pr.join(t.outputsDir,e);return na.writeFileSync(n,JSON.stringify(Ar(r,t.secretValues??[]),null,2)+`
943
943
  `),Pr.relative(t.runDir,n)}function Wkr(t,e){return HAe(e.dependenciesAdded??[],VS(t.inputs.repositoryPath,t.inputs.relatedRepos??[])).added}function Wbt(t,e){let r=t.prepared?.dependencyBaseline??new Set;return[...nY(t,e,r),...CAt(t)]}var Ykr={name:Gbt,verify(t,e){let{value:r}=xn(es,e.stdout,Hr(t),t.secretValues);if(!r)return{name:this.name,passed:!0};let n=Wbt(t,Wkr(t,r));return n.length?{...ve(this.name,n),repairable:!0}:{name:this.name,passed:!0}}};function Jkr(t,e){let r=se(t,hl);if(!r)return[];let n=e.tests.map(i=>i.file),s=r.packages.filter(i=>{try{let o=ipt(t.inputs.repositoryPath,i,n);return o&&co(t).info(`[write] no test went in the new test package ${i.dir}: removed it`),!o}catch{return!0}});return Vbt(t,s),s}function Kkr(t,e){let{write:r,settled:n}=ebt(e);n.length&&co(t).warn(`[write] failed before any check, with no application line \u2014 not a defect: ${n.join(", ")}`);let{write:s,settled:i}=sbt(r,eEt(t));i.length&&co(t).warn(`[write] quoted no requirement for what its failing check expects \u2014 not a defect: ${i.join(", ")}`);let o=Ps(t)?Ogt(s,ZT(t).browserJourneys):s;Jkr(t,o);let a=Ldt(o,t.inputs.repositoryPath,t.inputs.relatedRepos??[]),c=Wbt(t,a.finalize.dependencies.added);return c.length&&(a.finalize.ready=!1,a.finalize.blockers=[...a.finalize.blockers,...c.slice(0,4)]),a.dropped.length&&co(t).warn(`[write] not on disk, left out of adapt.json: ${a.dropped.join(", ")}`),a.ambiguous.length&&co(t).warn(`[write] in both the repo under test and a related checkout, kept the repo under test's: ${a.ambiguous.join(", ")}`),{artifacts:{write:YM(t,"write.json",o),adapt:YM(t,"adapt.json",a.adapt),generate:YM(t,"generate.json",a.generate),"generate-ui":YM(t,"generate-ui.json",a.generateUi),finalize:YM(t,"finalize.json",a.finalize)},placed:a.placed}}async function Ybt(t,e){let{artifacts:r,placed:n}=Kkr(t,e);if(Ps(t))return r;let s=new Map(n.map(o=>[o.written,o])),i=await Ayt({write:e,outputsDir:t.outputsDir,resolve:o=>{let a=s.get(o),c=a&&Pr.resolve(a.root,a.file);return a&&c&&na.existsSync(c)&&na.statSync(c).isFile()?{abs:c,root:Pr.resolve(a.root),file:a.file,...a.repository?{repository:a.repository}:{}}:void 0},env:{...Jbt(t),...t.sutAuthEnv??{}},...t.deadlineMs!==void 0?{deadlineMs:t.deadlineMs}:{},...t.signal?{signal:t.signal}:{}}).catch(o=>{co(t).warn(`[write] recording the browser tests' videos failed: ${o instanceof Error?o.message:String(o)}`)});return i?.skipped.length&&co(t).info(`[write] no video for ${i.skipped.map(o=>`${o.fileName} (${o.reason})`).join(", ")}`),{...r,...i?{"ui-videos":Pr.relative(t.runDir,Pr.join(t.outputsDir,hg))}:{}}}function Jbt(t){let e={};try{let n=Ps(t)?twe(t):[],s=BM(n.flatMap(({job:i})=>i.toolSteps),process.env,void 0,PM(pg,t.inputs.repositoryPath));e={...n.length?fgt(t.outputsDir):{},...s.env}}catch{}let r=t.prepared?.appUrls?.env??{};try{return{...Q3(cg(wl(t.inputs.repositoryPath))),...e,...r,...ag}}catch{return{...e,...r,...ag}}}function twe(t){let e=new Map;for(let{repo:r,run:n}of qbe(t)){let s=W3(WT(r),n.cwd,n.argv);s&&e.set(`${r}\0${s.workflow}\0${s.job}`,{repo:r,job:s})}return[...e.values()]}function Xkr(t){let e=BM(twe(t).flatMap(({job:n})=>n.toolSteps),process.env,void 0,PM(pg,t.inputs.repositoryPath)),r=e.missing.map(n=>`${n.tool} ${n.version}`);return[...e.notes.length?[`The CI test job's tool versions are first on PATH for you and for native-run (${e.notes.map(n=>n.replace(/^provisioning: /,"")).join("; ")}).`]:[],...r.length?[`The CI test job sets up ${r.join(", ")}, which this runner does not have: a test that needs it cannot run here, and native-run reports it as blocked by the environment. That is not a defect: change no code or test for it.`]:[]]}var Kbt="write-stale-updates",Zkr={name:Kbt,verify(t,e){let{value:r}=xn(es,e.stdout,Hr(t),t.secretValues);if(!r)return{name:this.name,passed:!0};let n=VS(t.inputs.repositoryPath,t.inputs.relatedRepos??[]),s=E=>{let A=n(E);return eg(A)?Py(t.inputs,A.repository,A.rel):E},o=[{path:t.inputs.repositoryPath},...t.inputs.relatedRepos??[]].flatMap(({path:E,slug:A})=>{try{return(0,qbt.execFileSync)("git",["-C",E,"diff","--name-only","--diff-filter=d","HEAD"],{encoding:"utf-8"}).split(`
944
944
  `).filter(x=>x&&Ky(x)&&!x.startsWith(".skyramp/")).map(x=>Py(t.inputs,A,x))}catch{return[]}}),a=r.tests.filter(E=>E.change!=="added"&&E.change!=="deleted").map(E=>({file:s(E.file),changed:E.change!=="unchanged",ran:E.result.exit!==null})),c=Bpt(o,a),l=r.tests.filter(E=>E.change==="updated");if(!l.length)return na.writeFileSync(Pr.join(t.outputsDir,TT),`[]
package/dist/post.js CHANGED
@@ -715,11 +715,11 @@ ${a.body.slice(0,6e3)}
715
715
  `)||`exit ${n.status??n.code??"?"}`}};function yLt(e){let t=[],r="",n;for(let s=0;s<e.length;s++){let i=e[s];if(n)i===n&&(n=void 0);else if(i==='"'||i==="'")n=i;else if(i==="&"&&e[s+1]==="&"){t.push(r.trim()),r="",s++;continue}r+=i}return[...t,r.trim()].filter(Boolean)}var bLt=/<[^<>\s][^<>]*>/;function ELt(e,t,r=q1e){if(!e.tests.length||!e.lint)return[];let n=[],s=e.lint.command.trim();if(!s)return e.lint.clean||n.push("lint.clean is false and no lint command is recorded \u2014 run the repo's linter on your test files, fix what it reports, and record the exact command you ran"),n;if(bLt.test(s))return[`lint command '${s}' names a placeholder, not files: record the exact command you ran, with the file paths`];let i=t;for(let o of yLt(s)){let a=/^cd\s+("[^"]+"|'[^']+'|\S+)$/.exec(o);if(a){if(i=tF.resolve(i,a[1].replace(/^["']|["']$/g,"")),tF.relative(t,i).startsWith(".."))return[`lint command '${s}' runs outside the repository \u2014 record the exact command you ran, from the repository`];continue}let{steps:c}=wc(o);if(!c)break;let l=c.reduce((u,d)=>u??r(d,i),void 0);if(l!==void 0)return[`the repo's linter still reports problems in your test files \u2014 '${o}' failed:
716
716
  ${l}
717
717
  Fix them in the test files, not the lint config, run it on your files again, and record it`]}return e.lint.clean||n.push("lint.clean is false: fix what the linter reports in your test files, run it on your files again, and record clean: true"),n}function G1e(e,t=q1e){return{name:gLt,verify(r,n,s,i){let o=e(r,n.stdout);if(!o)return{name:this.name,passed:!0};let a=ELt(o,r.inputs.repositoryPath,t);return a.length?i&&!i.retryLeft?{name:this.name,passed:!0,details:`kept the tests, lint not clean: ${a[0]}`}:{...ce(this.name,a),repairable:!0}:{name:this.name,passed:!0}}}}var X9="write.json",Z9=["adapt.json","generate.json","generate-ui.json","finalize.json",$d,ER],z1e="write-no-secret-leak",V1e="write-checkout-intact",W1e="write-dependencies",ww=e=>kt.join(e.outputsDir,"write-scratch"),Lt=e=>kt.join(ww(e),X9);function J1e(e){if(hn(e))return ou(e);let t=Z(e,"plan.json")?.journeys;if(!Array.isArray(t)||!t.length)return[];let r=new Set((Ft(e)?.generate??[]).map(n=>n.id));return t.filter(n=>n&&typeof n.id=="string"&&Array.isArray(n.steps)&&r.has(n.id))}function wLt(e,t={}){if(!e.length)return[];let r=t.noApp?["Each is one in-process test of the whole flow: it drives the UI the way the repo's in-process tests do, checks after","each step what the user then sees, and asserts every part of the outcome at the end. Mark each step so a failure","names it (a labelled assertion, or the runner's own step or subtest), and let a failed check go on to the next step","where the test still allows it.","A journey no in-process test can walk here goes under notWritten, with what it needs that is missing: it is","reported as not covered, never left out."]:["Each is one test of the whole flow, of its kind. A browser journey runs against the real app: it opens the entry route","and does every step through the UI in order (no URL jump to a later step, no stub of the app's own network). An","in-process journey drives the UI the way the repo's in-process tests do. Either checks after each step what the user then sees, and","asserts every part of the outcome at the end, after a reload where it must persist. Where a step lands on another page (a","redirect after a save, a detail or confirmation page), check on that page what it shows of what the user entered or saved","(the values, totals and counts), not only its address. Mark each step so a failure names it (in Playwright, test.step),","and let a failed check go on to the next step where the page still allows it, so one defect does not hide the rest.","A journey whose test cannot run here (a browser, package or service it needs cannot be set up) goes under notWritten,","with what is missing: it is reported as not covered, never left out."];return["User journeys:",...e.map(n=>[` - ${n.id} ${n.name} \u2192 ${n.testFile}`,` entry: ${n.entry}`,` steps: ${yE(n)}`,...n.finishes?.trim()?[` finishes: ${n.finishes.trim()}`]:[],` outcome: ${n.outcome}`].join(`
718
- `)),...r,'List each under "journeys" in your result (its file and test title, and where it broke when it did) and put its id in',`that file's "covers".`,""]}function SLt(e){return["","Record each ui case's browser test with the Skyramp walk helper instead of writing it from scratch: a Node script that","calls `require(process.env.SKYRAMP_WALK).walk(...)` and walks the case once against the running app (usage at the top",`of ${e.helper}). It writes a Skyramp Playwright test that checks the request body of each action marked with`,"`w.expectRequest()`. That test is your draft: examine its assertions and strengthen them, so each step is checked","to have worked and the end checks every part of the case's expected outcome. Then fit it to the repo (its page objects,","step markers, location and naming), keeping the request checks, and run it the way the repo runs its browser tests.","Each browser test you add in Testbot's own test package, or in a repo package whose manifest already declares","@skyramp/skyramp (skyramp in Python), keeps the Skyramp page wrapper: `page = newSkyrampPlaywrightPage(page)`","(new_skyramp_playwright_page in Python), in the test or in a fixture or page object it imports, even where you fit it to","the repo's own fixtures or write a case by hand after its walk failed. In a repo package that does not declare it,","write the test the repo's way, with its own Playwright and no Skyramp import: don't add to the repo's dependencies.","An existing test you update keeps the repo's way.","The walk script is scratch: keep it out of the repo. When a step fails, the helper prints the step, the page URL and an","accessibility snapshot of the page: fix the selector from that snapshot and walk again."]}function CLt(e,t){if(!e.use)return[];let r=e.placement==="test-package"&&e.runner?`in the Testbot test package for ${e.runner} (below), named its way (${Fx[e.runner]}); the package declares the Skyramp SDK`:`beside the tests in ${e.dir}, written the way they are; where that test environment does not declare the Skyramp SDK, add it to its manifest and list it in dependenciesAdded`;return["",`Backend API cases with Skyramp (${e.reason}): ${e.cases.join(", ")}. A case that belongs in an existing test file`,"goes there, in that file's style, with no draft. A new test file starts from a Skyramp draft instead of from scratch: one","generation per new file, for the requests its cases make, and none for a file you will not keep. In a scratch Node script,",`\`const api = require(process.env.SKYRAMP_API)\` (usage at the top of ${t.helper}), language "${e.language}",`,`framework "${e.framework}", outputDir where the test goes, output a new file name:`,' - one request: `api.generate({ kind: "contract", baseUrl, endpoint, method, requestData, responseData, statusCode, ... })`,'," always with its method (only that method's test is generated), the request and response from the case's steps (or the"," app's OpenAPI spec as `schema`);"," - several requests in order: `api.trace(steps, { baseUrl, authHeader, authScheme })` from the case's steps (each step's method,",' path, request, status and response; an id an earlier step returned as {{N.field}}), then `api.generate({ kind: "integration", trace, ... })`.',`Write it ${r}. The draft checks only the status and the response's shape. Fix it so it runs (auth, seed data,`,"parent resources, ids the app gives), then strengthen its assertions to the case: each field and value of its expected outcome,",`and the error cases its plan names. Keep Skyramp's client and request code, keep the requirement ids in "functions", name it the repo's way.`,"In your result give its skyramp.draft: the draft path the helper returned (each, if you merged several into one file). If the helper fails for a case, fix the call once from its","error; then write the test by hand and give skyramp.handWritten (the call and its error) \u2014 by hand only after the helper failed.","The script and the trace are scratch: keep them out of the repo."]}function vLt(e){let r=Ue(e.inputs).reduce((i,o)=>{let a=o.path===e.inputs.repositoryPath?"":`${kt.basename(kt.resolve(o.path))}/`,c;try{c=pLe(o.path,a)}catch{return i}return{apiTests:[...i.apiTests,...c.apiTests],skyrampTests:[...i.skyrampTests,...c.skyrampTests],backendLanguage:i.backendLanguage??c.backendLanguage}},{apiTests:[],skyrampTests:[]}),n=(e.inputs.requirementItems??[]).flatMap(i=>{if(!("path"in i))return[];try{return[js.readFileSync(i.path,"utf-8")]}catch{return[]}}),s=[...Kl(e.inputs).map(i=>i.body),...e.inputs.instructions??[],...n];return dLe({facts:r,cases:Ft(e)?.generate??[],requests:s})}function rF(e){return hn(e)?!1:(Ft(e)?.generate??[]).some(t=>t.type==="ui")}function ILt(e){let t=HE(e);if(t){for(let n of[...Z9,_o])js.rmSync(kt.join(e.outputsDir,n),{force:!0});return{ok:!1,reasons:[t]}}let r=Ft(e);return r?r.generate.length===0&&r.maintenance.length===0&&!D6(cw(e))?{ok:!1,reasons:["the plan has no test cases and no maintenance"]}:{ok:!0,reasons:[]}:{ok:!1,reasons:["no test plan (recommend.json) to write tests from"]}}function xLt(e){let t=OK(e),r=[];if(hn(e))for(let{repo:S,job:E}of eX(e))IM({outputsDir:e.outputsDir,provision:!!e.inputs.provisionRuntime,run:Ld,notes:r,deadlineMs:e.deadlineMs},S,E);let n=kM(e);for(let S of[...Z9,"write.json",UE,_o])js.rmSync(kt.join(e.outputsDir,S),{force:!0});let s=rF(e),i=(Ft(e)?.generate??[]).some(S=>S.type!=="ui")||D6(cw(e)),o=e.inputs.skyrampHelpers!==!1,{walk:a,note:c}=s&&o?_1e(ww(e),e.inputs.skyrampMcp):{},l=o?hn(e)?{use:!1,rule:"d",cases:[],reason:"no app runs on the unit path"}:vLt(e):{use:!1,rule:"off",cases:[],reason:"the Skyramp helpers are switched off for this run"},u,d;if(l.use){let S=D1e(ww(e),e.inputs.skyrampMcp,{sdk:a?.env.SKYRAMP_WALK_SDK});u=S.api,d=S.note,u||(l={...l,use:!1,cases:[],reason:`${l.reason}; not used: ${S.note}`})}as(e).info(`[write] Skyramp API tests: ${l.use?"yes":"no"} (rule ${l.rule}) \u2014 ${l.reason}`),e9(kt.join(e.outputsDir,ER),{schemaVersion:1,decision:l,generations:[],tests:[]});let p=l.use&&l.placement==="test-package"?l.runner:void 0,{packages:f,notes:h}=nFe(e,{ui:s,backend:i,...p?{skyramp:p}:{}});Y1e(e,f);let A=[...r,...h,...kg(e,"backend",[],{declaredOnly:!0,packages:f}),...s?kg(e,"ui",[],{declaredOnly:!0,packages:f}):[]];return c&&A.push(c),d&&A.push(d),{checkpoint:kt.join(ww(e),X9),secretGateBaseline:t,checkoutHeads:n,provisionNotes:A,sharedHelpers:RLt(e),testPackages:f,dependencyBaseline:OK(e),skyrampWalk:a,skyrampApi:{decision:l,...u?{helper:u}:{}}}}function Y1e(e,t){let r=kt.join(e.outputsDir,_o);t.length===0?js.rmSync(r,{force:!0}):js.writeFileSync(r,JSON.stringify({schemaVersion:1,packages:[...t]},null,2)+`
718
+ `)),...r,'List each under "journeys" in your result (its file and test title, and where it broke when it did) and put its id in',`that file's "covers".`,""]}function SLt(e){return["","Record each ui case's browser test with the Skyramp walk helper instead of writing it from scratch: a Node script that","calls `require(process.env.SKYRAMP_WALK).walk(...)` and walks the case once against the running app (usage at the top",`of ${e.helper}). It writes a Skyramp Playwright test that checks the request body of each action marked with`,"`w.expectRequest()`. That test is your draft: examine its assertions and strengthen them, so each step is checked","to have worked and the end checks every part of the case's expected outcome. Then fit it to the repo (its page objects,","step markers, location and naming), keeping the request checks, and run it the way the repo runs its browser tests.","Each browser test you add in Testbot's own test package, or in a repo package whose manifest already declares","@skyramp/skyramp (skyramp in Python), keeps the Skyramp page wrapper: `page = newSkyrampPlaywrightPage(page)`","(new_skyramp_playwright_page in Python), in the test or in a fixture or page object it imports, even where you fit it to","the repo's own fixtures or write a case by hand after its walk failed. In a repo package that does not declare it,","write the test the repo's way, with its own Playwright and no Skyramp import: don't add to the repo's dependencies.","An existing test you update keeps the repo's way.","The walk script is scratch: keep it out of the repo. When a step fails, the helper prints the step, the page URL and an","accessibility snapshot of the page: fix the selector from that snapshot and walk again."]}function CLt(e,t){if(!e.use)return[];let r=e.placement==="test-package"&&e.runner?`in the Testbot test package for ${e.runner} (below), named its way (${Fx[e.runner]}); the package declares the Skyramp SDK`:`beside the tests in ${e.dir}, written the way they are; where that test environment does not declare the Skyramp SDK, add it to its manifest and list it in dependenciesAdded`;return["",`Backend API cases with Skyramp (${e.reason}): ${e.cases.join(", ")}. A case that belongs in an existing test file`,"goes there, in that file's style, with no draft. A new test file starts from a Skyramp draft instead of from scratch: one","generation per new file, for the requests its cases make, and none for a file you will not keep. In a scratch Node script,",`\`const api = require(process.env.SKYRAMP_API)\` (usage at the top of ${t.helper}), language "${e.language}",`,`framework "${e.framework}", outputDir where the test goes, output a new file name:`,' - one request: `api.generate({ kind: "contract", baseUrl, endpoint, method, requestData, responseData, statusCode, ... })`,'," always with its method (only that method's test is generated), the request and response from the case's steps (or the"," app's OpenAPI spec as `schema`);"," - several requests in order: `api.trace(steps, { baseUrl, authHeader, authScheme })` from the case's steps (each step's method,",' path, request, status and response; an id an earlier step returned as {{N.field}}), then `api.generate({ kind: "integration", trace, ... })`.'," Name the fields the API keeps unique, a step's `unique: [\"name\"]` or a contract test's `unique`, and give a contract",' test of a create `cleanupPath`, the route it is deleted through ("/api/v1/products/{product_id}"), with the id in its'," responseData. Keep the draft's unique values and cleanup when you fix it.",`Write it ${r}. The draft checks only the status and the response's shape. Fix it so it runs (auth, seed data,`,"parent resources, ids the app gives), then strengthen its assertions to the case: each field and value of its expected outcome,",`and the error cases its plan names. Keep Skyramp's client and request code, keep the requirement ids in "functions", name it the repo's way.`,"In your result give its skyramp.draft: the draft path the helper returned (each, if you merged several into one file). If the helper fails for a case, fix the call once from its","error; then write the test by hand and give skyramp.handWritten (the call and its error) \u2014 by hand only after the helper failed.","The script and the trace are scratch: keep them out of the repo."]}function vLt(e){let r=Ue(e.inputs).reduce((i,o)=>{let a=o.path===e.inputs.repositoryPath?"":`${kt.basename(kt.resolve(o.path))}/`,c;try{c=pLe(o.path,a)}catch{return i}return{apiTests:[...i.apiTests,...c.apiTests],skyrampTests:[...i.skyrampTests,...c.skyrampTests],backendLanguage:i.backendLanguage??c.backendLanguage}},{apiTests:[],skyrampTests:[]}),n=(e.inputs.requirementItems??[]).flatMap(i=>{if(!("path"in i))return[];try{return[js.readFileSync(i.path,"utf-8")]}catch{return[]}}),s=[...Kl(e.inputs).map(i=>i.body),...e.inputs.instructions??[],...n];return dLe({facts:r,cases:Ft(e)?.generate??[],requests:s})}function rF(e){return hn(e)?!1:(Ft(e)?.generate??[]).some(t=>t.type==="ui")}function ILt(e){let t=HE(e);if(t){for(let n of[...Z9,_o])js.rmSync(kt.join(e.outputsDir,n),{force:!0});return{ok:!1,reasons:[t]}}let r=Ft(e);return r?r.generate.length===0&&r.maintenance.length===0&&!D6(cw(e))?{ok:!1,reasons:["the plan has no test cases and no maintenance"]}:{ok:!0,reasons:[]}:{ok:!1,reasons:["no test plan (recommend.json) to write tests from"]}}function xLt(e){let t=OK(e),r=[];if(hn(e))for(let{repo:S,job:E}of eX(e))IM({outputsDir:e.outputsDir,provision:!!e.inputs.provisionRuntime,run:Ld,notes:r,deadlineMs:e.deadlineMs},S,E);let n=kM(e);for(let S of[...Z9,"write.json",UE,_o])js.rmSync(kt.join(e.outputsDir,S),{force:!0});let s=rF(e),i=(Ft(e)?.generate??[]).some(S=>S.type!=="ui")||D6(cw(e)),o=e.inputs.skyrampHelpers!==!1,{walk:a,note:c}=s&&o?_1e(ww(e),e.inputs.skyrampMcp):{},l=o?hn(e)?{use:!1,rule:"d",cases:[],reason:"no app runs on the unit path"}:vLt(e):{use:!1,rule:"off",cases:[],reason:"the Skyramp helpers are switched off for this run"},u,d;if(l.use){let S=D1e(ww(e),e.inputs.skyrampMcp,{sdk:a?.env.SKYRAMP_WALK_SDK});u=S.api,d=S.note,u||(l={...l,use:!1,cases:[],reason:`${l.reason}; not used: ${S.note}`})}as(e).info(`[write] Skyramp API tests: ${l.use?"yes":"no"} (rule ${l.rule}) \u2014 ${l.reason}`),e9(kt.join(e.outputsDir,ER),{schemaVersion:1,decision:l,generations:[],tests:[]});let p=l.use&&l.placement==="test-package"?l.runner:void 0,{packages:f,notes:h}=nFe(e,{ui:s,backend:i,...p?{skyramp:p}:{}});Y1e(e,f);let A=[...r,...h,...kg(e,"backend",[],{declaredOnly:!0,packages:f}),...s?kg(e,"ui",[],{declaredOnly:!0,packages:f}):[]];return c&&A.push(c),d&&A.push(d),{checkpoint:kt.join(ww(e),X9),secretGateBaseline:t,checkoutHeads:n,provisionNotes:A,sharedHelpers:RLt(e),testPackages:f,dependencyBaseline:OK(e),skyrampWalk:a,skyrampApi:{decision:l,...u?{helper:u}:{}}}}function Y1e(e,t){let r=kt.join(e.outputsDir,_o);t.length===0?js.rmSync(r,{force:!0}):js.writeFileSync(r,JSON.stringify({schemaVersion:1,packages:[...t]},null,2)+`
719
719
  `)}function RLt(e){try{return x1e(Ue(e.inputs).flatMap(t=>{let r=t.path===e.inputs.repositoryPath?"":Ke(e.inputs,t.slug);return I1e(t.path).map(n=>({label:r,module:n}))}))}catch{return[]}}function kLt(e){let t=Co(e.inputs)?["It is a shared environment this policy protects: write and run no test that changes state there (a create, edit or delete, a form that submits, a setup that seeds); list any such test in notWritten instead."]:[];if(e.inputs.appRunning){let o=PD(Z(e,`${Qn}.json`));return[e.inputs.appRunning.trim(),...j1e(e),...t,...o?[o]:[]]}let r=Z(e,"deploy.json"),n=Oo(e.inputs.repositoryPath),s=n.setupScript?`start it with ${kt.relative(e.inputs.repositoryPath,n.setupScript)} and check it answers`:"start it the way the repo documents";if(rO(r)){let o=e.prepared?.appUrls?.urls??[],a=o.length?O9(o):Object.entries(r.baseUrls??{}).map(([l,u])=>`${l} ${u}`).join(", "),c=r.health?.url??r.health?.command;return[`The app is running${a?`: ${a}`:""}.${c?` Health check: ${c}`:""}`,...j1e(e,o.length>0),`If it stops answering, ${s}.`]}return[`The app may not be running.${r?.bringupError?.output?` Planning could not start it: ${r.bringupError.output.slice(0,300)}`:""} Check, and if it isn't up, ${s}; leave it running.`,"If you can't get it running, still write the tests and record them as not run (exit null)."]}function j1e(e,t=!1){let r=e.prepared?.appUrls;if(!r?.urls.length)return[];let n=(i,o)=>r.env[i]!==void 0&&!r.urls.some(a=>a.envVar===i)?[`$${i} is ${o}`]:[],s=[...n(Q9,"the app"),...n(D9,"the pages")];return[`${t?"":`Its addresses are in ${O9(r.urls)}. `}A test reads each address from its variable, with the address as the default${s.length?` (${s.join("; ")})`:""}.`]}function TLt(e){let t=O6(e);return["No app is running, and none is started for this run: Validation Triage decided the change is proven in-process","(targeted_unit). Every test runs in one process, the way the repo's own in-process tests run: no network or running","app, so nothing that calls a live service, the Skyramp client and its runtime mocks included. Mock outbound calls","(HTTP, queues, other services) inside the test with the repo's own fixtures and mocking tools. Run the tests the way","the repo's CI runs them (its runner, config and environment), selected as step 4 says. Each new test file is named the way its runner","collects tests, as the repo's own tests are. Maintain every stale existing test that can run in-process. An existing","test that needs the running app goes under notWritten, by its path, with what it needs that this run does not have:","it is reported as not covered.",'Give each test file in a framework other than pytest, jest, vitest and node --test a "run": native-run re-runs your tests',"by it, and each declared run is run before you finish, as native-run will run it, so one that cannot start or reports","none of its file's tests comes back to you. A run native-run would refuse to start comes back to you on every attempt.","The plan's runs for existing tests are run then too: for an existing test file","whose run cannot start or reports none of its tests, list it under tests as unchanged, with a run that does.",`${Tf}`,...hFe(e.outputsDir),...FLt(e),...t.length?["The Target puts new unit tests in these directories, over the repo's own layout: put each new test under its repository's directory, not beside the existing tests:",...t.map(r=>` - ${Ke(e.inputs,r.slug)}${r.dir}/`)]:[]]}function BLt(e){return[...e.behaviors.length?['Behaviors that need a new in-process test (give each id in "covers"):',...e.behaviors.map(t=>` - ${t.id}: ${t.statement}
720
720
  missing evidence to obtain: ${t.covers||"(unstated)"}
721
721
  where the change is: ${t.where}`),"Every behavior above gets an in-process test, except that a test of what the change could break stays only if it","fails on the change (step 1).",""]:[],...e.defects.length?["For each defect suspected above, write a test that reaches it and expects the correct behaviour, so it fails on this","change and passes once the defect is fixed, and give its id in covers; a defect no in-process test can reach goes","under notWritten, with why.",""]:[],...e.browserJourneys.length?["These user journeys need a browser and the running app, which this run does not have: no in-process test can walk","them, so each goes under notWritten by its id, with what it needs that is missing:",...e.browserJourneys.map(t=>` - ${t.id} ${t.name}`),""]:[]]}function NLt(e,t=[]){let r="";try{let n=Mo(e,t),s=[...new Set(n.flatMap(i=>i.secretEnv))].filter(i=>t.includes(i));r=OMe(Dd(n),s).trim()}catch{return[]}return r?["Some variables the repo's CI sets would clash with your own model access, so their values are in your shell",`renamed, under a TESTBOT_CI_ prefix. Run a test that needs CI's values with them mapped back: \`${r} <test command>\`.`]:[]}function PLt(e){let t=e.prepared,r=e.inputs.repositoryPath,n=rr(e),s=n?.repository.currentBranch??"HEAD",i=n?.repository.baseBranch??e.inputs.baseBranch??"origin/main",o=Ft(e),a=Z(e,"analyze.json"),c=new Map((a?.behaviors??[]).map(x=>[x.id,x.statement])),l=(a?.findings??[]).filter(x=>x.status!=="refuted"&&x.status!=="latent"),u=(a?.findings??[]).filter(x=>x.status==="latent"),d=Z(e,"plan.json"),p=new Map((d?.cases??[]).map(x=>[x.id,x.steps])),f=$s(e.inputs.requirementItems),h=new Map((d?.requirements??[]).flatMap(x=>x.reference&&x.criterion?[[x.id,` (${Ec(x.reference,x.criterion,f)})`]]:[])),S=rF(e)&&Sd(e),E=hn(e),y=cw(e),C=J1e(e),I=new Set(C.map(x=>x.id)),k=o.generate.filter(x=>!I.has(x.id)).map(x=>{let R=[x.from_behavior&&`${x.from_behavior}: ${c.get(x.from_behavior)??""}`.trim(),x.from_finding&&`defect ${x.from_finding}`].filter(Boolean).join("; ");return[` - ${x.id} [${x.type}] ${x.target}${R?` \u2014 ${R}`:""}`,` do: ${p.get(x.id)??x.rationale}`,` expect: ${x.expected_observable}`].join(`
722
- `)});return["Write functional tests for this PR and run them. The plan below came from reading the PR; turn it into tests in the","repo, run them, and tell me what they show about the change.","",`The repo is at ${r}, checked out on branch \`${s}\`. You can see the change with \`git diff ${i}...HEAD\`.`,...k9(e),...va(e.inputs),"",...DUe(e.inputs),...MUe(e.inputs),...a?.requirements?.length?["Requirements:",...a.requirements.map(x=>` - ${x.id}${h.get(x.id)??""}: ${x.statement}`),""]:[],...l.length?["Defects suspected while reading the change:",...l.map(x=>` - ${x.id} (${x.severity}) ${x.file}${x.line?`:${x.line}`:""} \u2014 ${x.claim}`),""]:[],...u.length?["Defects the base already has, in code the change left as it was: not this change's. Write no test that fails on them;","if your tests show one, report it as existing (its id as the issue's finding):",...u.map(x=>` - ${x.id} (${x.severity}) ${x.file}${x.line?`:${x.line}`:""} \u2014 ${x.claim}`),""]:[],"Test plan. Each case's expected outcome is the one the plan sets, not whatever the app does:",...k.length?k:[" (no new cases)"],"",...eO(d?.questions),...o.maintenance.length?["","Existing tests to maintain:",...o.maintenance.map(x=>` - ${x.action.toUpperCase()} ${Af(e.inputs,x.repository,x.test)} \u2014 ${x.evidence}`)]:[],"",...E?BLt(y):[],...wLt(C,{noApp:E}),...E?TLt(e):kLt(e),...S?["For the ui cases you have the Skyramp browser tools (mcp__skyramp__browser_*): look at the real page before you pick locators."]:[],...t.skyrampWalk?SLt(t.skyrampWalk):[],...t.skyrampApi?.helper?CLt(t.skyrampApi.decision,t.skyrampApi.helper):[],...t.provisionNotes.length?[`Setup notes: ${t.provisionNotes.join("; ")}`]:[],...t.testPackages?.length?["",...CQe(t.testPackages,{skyrampVersion:E?void 0:_Lt(e,t),nodeTests:(E||o.generate.some(x=>x.type!=="ui"))&&S6(r)})]:[],...t.sharedHelpers?.length?["",...t.sharedHelpers]:[],...NLt(r,e.providedEnvKeys??[]),"","1. Write each test the way the repo already tests that code (the same kind of test, framework, directories, naming,",...E?[" helpers and fixtures), unless that kind cannot check its case in one process; then use the in-process kind that can,"," and where none can, list the case under notWritten with what it needs."]:[" helpers and fixtures), unless that kind of test cannot check its case; then use the kind that can."]," Reuse the repo's helpers, page objects and fixtures, and put yours where someone who knows this repo would look for"," them: never copy a helper the repo already has \u2014 import it. A helper only one test file uses stays in that file,"," unless the repo keeps that kind in its own module (a page object per page, say). A helper two or more test files use"," \u2014 yours, or one you take from another test file \u2014 is defined once, where the repo keeps shared helpers for those"," tests: usually the support module they already import, or a new module beside it when that one no longer fits (say"," the repo splits its helpers by area, or the module has grown large); where there is none yet, beside the tests that",` use it. Name a new module for what it holds, not "utils" or "Skyramp". Change the repo's own helpers only as far as`," that needs. Use every name you import."," Where the repo's browser tests reach its pages through page objects,"," so do yours: locate a page's elements in its page object, the way its existing members locate theirs, and add a page"," object, where the repo keeps them, for a page that has none. Find an element by its role and name, label or test id;"," when a locator matches more than one element, narrow it to the one you mean (inside its row, dialog or region, or by"," its text) rather than taking .first(), .last() or .nth(). Pick by position only where the order is what the test checks.",...E?[" Write in-process tests the way the repo's own unit and in-process tests are written. The repo's existing tests"]:[" Don't add unit tests (a function or module called directly, its collaborators mocked). The repo's existing tests"]," are for maintenance only: update the ones the change makes wrong \u2014 the maintenance list is where to start, step 4"," finds the rest \u2014 above all any that assert as correct what the plan says is a defect, rather than writing a new"," one beside it."," Keep each requirement's check on its own, so one failure can't hide another: give it its own test or assertion,"," and stub or seed any data its setup needs that a different defect could break (a list the page loads, a shared"," helper that rejects the response before your assertion reads it). A test that shows a defect asserts only the"," outcome its case expects \u2014 one defect per test \u2014 so it passes once that defect is fixed."," For each element or field the change adds, assert that it is there (shown, or present in the response); clicking"," or filling it is not an assertion. For each it removes or renames, assert that it is gone: not shown, a count of 0,"," the field missing, a 404, the old option no longer offered."," Where a case's steps give the exact requests, send them as they are rather than working them out again, and keep"," those tests together in one file."," The plan is where to start, not the limit: if you notice something else the change should do, or could break,"," while you write and run these, test that too, filed under the plan requirement it guards. A test of what it could"," break that passes stays only when it is the one test of its requirement; remove one that another of your tests",' already covers, and list its case under notWritten with a reason that starts "Passing:" and says what it checked \u2014'," never a journey's test, which stays whether it"," passes or fails. Where nothing says what should happen, only decide it for what"," the change does; how older code treats odd input is not this PR's to test. One case for each kind of input is"," enough: add another only where it could fail while the first passes.","2. Run the tests you wrote together, once, in the foreground, with a short per-test timeout so a hung test fails",` fast; wait for the run to finish (one Bash call may run up to ${aM} minutes), and don't end the session with a test`," run still going: the session is non-interactive and ends when you stop, so nothing would report its result.","3. For each failure, work out whose fault it is, and settle it outside the test before you call it a defect. The"," outcome the test expects must be one the PR, spec or code requires, not one you chose: an exact status where only a"," refusal is required, a message or format nothing states, a value read from a column picked by position. Where only"," part of it is required, assert only that part. A failure before the step that checks the case (sign-in, setup,"," seeding, a helper, an element not found, a timeout) is the test's until you have seen the app do it wrong: look at"," the page or send the request yourself, and find the line of application code that does it."," If the test checks what its case requires and the app does something else, that's a defect in the change: keep the"," test as written, mark the case and its file as a defect, and report the bug with that line. Never change an expected"," outcome to match what the app does. If the test itself is wrong (selector, setup, data, import, timing, an outcome"," nothing requires), fix it and re-run only the tests that failed (e.g. pytest --lf, playwright test --last-failed, or"," by name); a failing case you could not fix is marked defect: false, never a defect. Give each failing case the",` runner's failure as it printed it ("failure"), and each case you keep as a defect its "cause", the application line,`,` and "requires", the words of the PR description or the plan's requirement that state what its failing check expects,`," quoted. When no words state it, the check asserts more than is required: assert only what is, and re-run it."," Don't re-run the whole set at"," the end: the results you already have stand.","4. Then run the repo's existing tests that reach the code the change touches, the way its CI runs them (its command"," and environment), selected as narrowly as still covers that code: the whole suite only when it runs in a few"," minutes. For each one that fails, find out whether this change made it fail; when you can't tell, run it on the"," base branch in a scratch copy outside the"," checkout (e.g. git worktree add under /tmp). One that asserts behaviour the change deliberately replaced is stale:"," update it, or delete it where the behaviour is gone, and re-run it; give staleBecause for it \u2014 the requirement the"," change deliberately alters and the changed line of application code (path:line) that alters it. One that catches"," something the change breaks is a defect: leave it failing and report it. Leave alone failures the change did not cause.","5. If the repo has a linter or formatter for test code, run it on your files, and record the exact command you ran:"," Testbot re-runs it, and sends back what it still reports. The application's manifests and"," lockfiles are its owner's: a test in the repo's own framework uses only what the repo already declares, and you"," never add, upgrade or remove a package in the application's own manifests or lockfiles for Testbot's tests.",E?" For the app's code, reach it the way the repo's in-process tests do (its test client, fixtures and helpers).":" For calls to the app, use the client and helpers the repo's existing tests use for that service."," A browser test goes in the repo's own browser suite where it has one (Selenium, Cypress, Capybara), with its page objects."," Tooling the repo doesn't declare (Playwright where it has none) goes in the Testbot test package"," above, never in the application's manifests; with no such package, write the test with what the repo has."," The one exception is a test that must run inside the application's existing test environment and cannot"," without a package it lacks: add it to the manifest of the checkout the test is in, keep the manifest's"," constraints, upgrade nothing else, update that manifest's lockfile with its own package manager (the updated lockfile"," must name the added package), run the existing tests of that environment, and list it in dependenciesAdded"," with the reason and the manifest's path relative to its checkout; for another checkout of the change,"," name that checkout as the entry's repository (its owner/repo).","",'In your result, give each file you added or updated its "functions": each test function in it as the runner names',"it, with the plan case ids it checks, so that every case it covers is checked by a function you name. Give each","issue its plan finding id and the requirement or clause ids it violates. Name each test for what it checks: no plan","id in a test's name or title, except a label the description or spec itself gives the requirement (AC-3).","","Apart from that scratch copy, stay inside the checkout and leave it on the commit it is on,","with the change's files in place: run or read the base in the scratch copy (or with git show <commit>:<file>), never","by checking the checkout out, resetting or stashing it. Don't commit or push, and leave the tests in the working tree.",`Once you've run the tests the first time, write your result as JSON (below) to ${t.checkpoint}, and keep it current`,"as you go: that file is what's","read. End with a one-line final message \u2014 don't repeat the JSON in it. The report is read by the PR author: describe","their code, not this process.","",fr(Pr)].join(`
722
+ `)});return["Write functional tests for this PR and run them. The plan below came from reading the PR; turn it into tests in the","repo, run them, and tell me what they show about the change.","",`The repo is at ${r}, checked out on branch \`${s}\`. You can see the change with \`git diff ${i}...HEAD\`.`,...k9(e),...va(e.inputs),"",...DUe(e.inputs),...MUe(e.inputs),...a?.requirements?.length?["Requirements:",...a.requirements.map(x=>` - ${x.id}${h.get(x.id)??""}: ${x.statement}`),""]:[],...l.length?["Defects suspected while reading the change:",...l.map(x=>` - ${x.id} (${x.severity}) ${x.file}${x.line?`:${x.line}`:""} \u2014 ${x.claim}`),""]:[],...u.length?["Defects the base already has, in code the change left as it was: not this change's. Write no test that fails on them;","if your tests show one, report it as existing (its id as the issue's finding):",...u.map(x=>` - ${x.id} (${x.severity}) ${x.file}${x.line?`:${x.line}`:""} \u2014 ${x.claim}`),""]:[],"Test plan. Each case's expected outcome is the one the plan sets, not whatever the app does:",...k.length?k:[" (no new cases)"],"",...eO(d?.questions),...o.maintenance.length?["","Existing tests to maintain:",...o.maintenance.map(x=>` - ${x.action.toUpperCase()} ${Af(e.inputs,x.repository,x.test)} \u2014 ${x.evidence}`)]:[],"",...E?BLt(y):[],...wLt(C,{noApp:E}),...E?TLt(e):kLt(e),...S?["For the ui cases you have the Skyramp browser tools (mcp__skyramp__browser_*): look at the real page before you pick locators."]:[],...t.skyrampWalk?SLt(t.skyrampWalk):[],...t.skyrampApi?.helper?CLt(t.skyrampApi.decision,t.skyrampApi.helper):[],...t.provisionNotes.length?[`Setup notes: ${t.provisionNotes.join("; ")}`]:[],...t.testPackages?.length?["",...CQe(t.testPackages,{skyrampVersion:E?void 0:_Lt(e,t),nodeTests:(E||o.generate.some(x=>x.type!=="ui"))&&S6(r)})]:[],...t.sharedHelpers?.length?["",...t.sharedHelpers]:[],...NLt(r,e.providedEnvKeys??[]),"","1. Write each test the way the repo already tests that code (the same kind of test, framework, directories, naming,",...E?[" helpers and fixtures), unless that kind cannot check its case in one process; then use the in-process kind that can,"," and where none can, list the case under notWritten with what it needs."]:[" helpers and fixtures), unless that kind of test cannot check its case; then use the kind that can."]," Reuse the repo's helpers, page objects and fixtures, and put yours where someone who knows this repo would look for"," them: never copy a helper the repo already has \u2014 import it. A helper only one test file uses stays in that file,"," unless the repo keeps that kind in its own module (a page object per page, say). A helper two or more test files use"," \u2014 yours, or one you take from another test file \u2014 is defined once, where the repo keeps shared helpers for those"," tests: usually the support module they already import, or a new module beside it when that one no longer fits (say"," the repo splits its helpers by area, or the module has grown large); where there is none yet, beside the tests that",` use it. Name a new module for what it holds, not "utils" or "Skyramp". Change the repo's own helpers only as far as`," that needs. Use every name you import."," Where the repo's browser tests reach its pages through page objects,"," so do yours: locate a page's elements in its page object, the way its existing members locate theirs, and add a page"," object, where the repo keeps them, for a page that has none. Find an element by its role and name, label or test id;"," when a locator matches more than one element, narrow it to the one you mean (inside its row, dialog or region, or by"," its text) rather than taking .first(), .last() or .nth(). Pick by position only where the order is what the test checks.",...E?[" Write in-process tests the way the repo's own unit and in-process tests are written. The repo's existing tests"]:[" Don't add unit tests (a function or module called directly, its collaborators mocked). The repo's existing tests"]," are for maintenance only: update the ones the change makes wrong \u2014 the maintenance list is where to start, step 4"," finds the rest \u2014 above all any that assert as correct what the plan says is a defect, rather than writing a new"," one beside it."," Keep each requirement's check on its own, so one failure can't hide another: give it its own test or assertion,"," and stub or seed any data its setup needs that a different defect could break (a list the page loads, a shared"," helper that rejects the response before your assertion reads it). A test that shows a defect asserts only the"," outcome its case expects \u2014 one defect per test \u2014 so it passes once that defect is fixed."," For each element or field the change adds, assert that it is there (shown, or present in the response); clicking"," or filling it is not an assertion. For each it removes or renames, assert that it is gone: not shown, a count of 0,"," the field missing, a 404, the old option no longer offered."," Where a case's steps give the exact requests, send them as they are rather than working them out again, and keep"," those tests together in one file.",...E?[]:[" A test runs against a fresh app, and again against one it already ran on. It makes the data it needs in its own"," setup (a file to share, a record to edit) rather than relying on what exists only because this session made it;"," what the app's setup recipe creates is there in CI too. A value the app keeps unique (a name, slug, SKU, email) is"," made unique to the run, the one change to a case's exact requests, and the test finds and asserts by the value it"," sent. When it ends, failed or not, it deletes what it made, through the API where it can."]," The plan is where to start, not the limit: if you notice something else the change should do, or could break,"," while you write and run these, test that too, filed under the plan requirement it guards. A test of what it could"," break that passes stays only when it is the one test of its requirement; remove one that another of your tests",' already covers, and list its case under notWritten with a reason that starts "Passing:" and says what it checked \u2014'," never a journey's test, which stays whether it"," passes or fails. Where nothing says what should happen, only decide it for what"," the change does; how older code treats odd input is not this PR's to test. One case for each kind of input is"," enough: add another only where it could fail while the first passes.","2. Run the tests you wrote together, once, in the foreground, with a short per-test timeout so a hung test fails",` fast; wait for the run to finish (one Bash call may run up to ${aM} minutes), and don't end the session with a test`," run still going: the session is non-interactive and ends when you stop, so nothing would report its result.","3. For each failure, work out whose fault it is, and settle it outside the test before you call it a defect. The"," outcome the test expects must be one the PR, spec or code requires, not one you chose: an exact status where only a"," refusal is required, a message or format nothing states, a value read from a column picked by position. Where only"," part of it is required, assert only that part. A failure before the step that checks the case (sign-in, setup,"," seeding, a helper, an element not found, a timeout) is the test's until you have seen the app do it wrong: look at"," the page or send the request yourself, and find the line of application code that does it."," If the test checks what its case requires and the app does something else, that's a defect in the change: keep the"," test as written, mark the case and its file as a defect, and report the bug with that line. Never change an expected"," outcome to match what the app does. If the test itself is wrong (selector, setup, data, import, timing, an outcome"," nothing requires), fix it and re-run only the tests that failed (e.g. pytest --lf, playwright test --last-failed, or"," by name); a failing case you could not fix is marked defect: false, never a defect. Give each failing case the",` runner's failure as it printed it ("failure"), and each case you keep as a defect its "cause", the application line,`,` and "requires", the words of the PR description or the plan's requirement that state what its failing check expects,`," quoted. When no words state it, the check asserts more than is required: assert only what is, and re-run it."," Don't re-run the whole set at"," the end: the results you already have stand.","4. Then run the repo's existing tests that reach the code the change touches, the way its CI runs them (its command"," and environment), selected as narrowly as still covers that code: the whole suite only when it runs in a few"," minutes. For each one that fails, find out whether this change made it fail; when you can't tell, run it on the"," base branch in a scratch copy outside the"," checkout (e.g. git worktree add under /tmp). One that asserts behaviour the change deliberately replaced is stale:"," update it, or delete it where the behaviour is gone, and re-run it; give staleBecause for it \u2014 the requirement the"," change deliberately alters and the changed line of application code (path:line) that alters it. One that catches"," something the change breaks is a defect: leave it failing and report it. Leave alone failures the change did not cause.","5. If the repo has a linter or formatter for test code, run it on your files, and record the exact command you ran:"," Testbot re-runs it, and sends back what it still reports. The application's manifests and"," lockfiles are its owner's: a test in the repo's own framework uses only what the repo already declares, and you"," never add, upgrade or remove a package in the application's own manifests or lockfiles for Testbot's tests.",E?" For the app's code, reach it the way the repo's in-process tests do (its test client, fixtures and helpers).":" For calls to the app, use the client and helpers the repo's existing tests use for that service."," A browser test goes in the repo's own browser suite where it has one (Selenium, Cypress, Capybara), with its page objects."," Tooling the repo doesn't declare (Playwright where it has none) goes in the Testbot test package"," above, never in the application's manifests; with no such package, write the test with what the repo has."," The one exception is a test that must run inside the application's existing test environment and cannot"," without a package it lacks: add it to the manifest of the checkout the test is in, keep the manifest's"," constraints, upgrade nothing else, update that manifest's lockfile with its own package manager (the updated lockfile"," must name the added package), run the existing tests of that environment, and list it in dependenciesAdded"," with the reason and the manifest's path relative to its checkout; for another checkout of the change,"," name that checkout as the entry's repository (its owner/repo).","",'In your result, give each file you added or updated its "functions": each test function in it as the runner names',"it, with the plan case ids it checks, so that every case it covers is checked by a function you name. Give each","issue its plan finding id and the requirement or clause ids it violates. Name each test for what it checks: no plan","id in a test's name or title, except a label the description or spec itself gives the requirement (AC-3).","","Apart from that scratch copy, stay inside the checkout and leave it on the commit it is on,","with the change's files in place: run or read the base in the scratch copy (or with git show <commit>:<file>), never","by checking the checkout out, resetting or stashing it. Don't commit or push, and leave the tests in the working tree.",`Once you've run the tests the first time, write your result as JSON (below) to ${t.checkpoint}, and keep it current`,"as you go: that file is what's","read. End with a one-line final message \u2014 don't repeat the JSON in it. The report is read by the PR author: describe","their code, not this process.","",fr(Pr)].join(`
723
723
  `)}function _Lt(e,t){let r=t.skyrampApi?.helper?.env.SKYRAMP_API_SDK;return r&&t.skyrampApi?.decision.placement==="test-package"?XM(r):Sd(e)?NE(e.inputs.skyrampMcp):void 0}function NR(e,t,r){let n=kt.join(e.outputsDir,t);return js.writeFileSync(n,JSON.stringify(lr(r,e.secretValues??[]),null,2)+`
724
724
  `),kt.relative(e.runDir,n)}function QLt(e,t){return $K(t.dependenciesAdded??[],Ag(e.inputs.repositoryPath,e.inputs.relatedRepos??[])).added}function K1e(e,t){let r=e.prepared?.dependencyBaseline??new Set;return[...PM(e,t,r),...wLe(e)]}var DLt={name:W1e,verify(e,t){let{value:r}=hr(Pr,t.stdout,Lt(e),e.secretValues);if(!r)return{name:this.name,passed:!0};let n=K1e(e,QLt(e,r));return n.length?{...ce(this.name,n),repairable:!0}:{name:this.name,passed:!0}}};function OLt(e,t){let r=Z(e,_o);if(!r)return[];let n=t.tests.map(i=>i.file),s=r.packages.filter(i=>{try{let o=SQe(e.inputs.repositoryPath,i,n);return o&&as(e).info(`[write] no test went in the new test package ${i.dir}: removed it`),!o}catch{return!0}});return Y1e(e,s),s}function MLt(e,t){let{write:r,settled:n}=s1e(t);n.length&&as(e).warn(`[write] failed before any check, with no application line \u2014 not a defect: ${n.join(", ")}`);let{write:s,settled:i}=c1e(r,n$e(e));i.length&&as(e).warn(`[write] quoted no requirement for what its failing check expects \u2014 not a defect: ${i.join(", ")}`);let o=hn(e)?QFe(s,cw(e).browserJourneys):s;OLt(e,o);let a=tQe(o,e.inputs.repositoryPath,e.inputs.relatedRepos??[]),c=K1e(e,a.finalize.dependencies.added);return c.length&&(a.finalize.ready=!1,a.finalize.blockers=[...a.finalize.blockers,...c.slice(0,4)]),a.dropped.length&&as(e).warn(`[write] not on disk, left out of adapt.json: ${a.dropped.join(", ")}`),a.ambiguous.length&&as(e).warn(`[write] in both the repo under test and a related checkout, kept the repo under test's: ${a.ambiguous.join(", ")}`),{artifacts:{write:NR(e,"write.json",o),adapt:NR(e,"adapt.json",a.adapt),generate:NR(e,"generate.json",a.generate),"generate-ui":NR(e,"generate-ui.json",a.generateUi),finalize:NR(e,"finalize.json",a.finalize)},placed:a.placed}}async function X1e(e,t){let{artifacts:r,placed:n}=MLt(e,t);if(hn(e))return r;let s=new Map(n.map(o=>[o.written,o])),i=await wUe({write:t,outputsDir:e.outputsDir,resolve:o=>{let a=s.get(o),c=a&&kt.resolve(a.root,a.file);return a&&c&&js.existsSync(c)&&js.statSync(c).isFile()?{abs:c,root:kt.resolve(a.root),file:a.file,...a.repository?{repository:a.repository}:{}}:void 0},env:{...Z1e(e),...e.sutAuthEnv??{}},...e.deadlineMs!==void 0?{deadlineMs:e.deadlineMs}:{},...e.signal?{signal:e.signal}:{}}).catch(o=>{as(e).warn(`[write] recording the browser tests' videos failed: ${o instanceof Error?o.message:String(o)}`)});return i?.skipped.length&&as(e).info(`[write] no video for ${i.skipped.map(o=>`${o.fileName} (${o.reason})`).join(", ")}`),{...r,...i?{"ui-videos":kt.relative(e.runDir,kt.join(e.outputsDir,$d))}:{}}}function Z1e(e){let t={};try{let n=hn(e)?eX(e):[],s=hR(n.flatMap(({job:i})=>i.toolSteps),process.env,void 0,fR(Ld,e.inputs.repositoryPath));t={...n.length?fFe(e.outputsDir):{},...s.env}}catch{}let r=e.prepared?.appUrls?.env??{};try{return{...pM(Dd(Mo(e.inputs.repositoryPath))),...t,...r,...Qd}}catch{return{...t,...r,...Qd}}}function eX(e){let t=new Map;for(let{repo:r,run:n}of L6(e)){let s=CM(nw(r),n.cwd,n.argv);s&&t.set(`${r}\0${s.workflow}\0${s.job}`,{repo:r,job:s})}return[...t.values()]}function FLt(e){let t=hR(eX(e).flatMap(({job:n})=>n.toolSteps),process.env,void 0,fR(Ld,e.inputs.repositoryPath)),r=t.missing.map(n=>`${n.tool} ${n.version}`);return[...t.notes.length?[`The CI test job's tool versions are first on PATH for you and for native-run (${t.notes.map(n=>n.replace(/^provisioning: /,"")).join("; ")}).`]:[],...r.length?[`The CI test job sets up ${r.join(", ")}, which this runner does not have: a test that needs it cannot run here, and native-run reports it as blocked by the environment. That is not a defect: change no code or test for it.`]:[]]}var e$e="write-stale-updates",LLt={name:e$e,verify(e,t){let{value:r}=hr(Pr,t.stdout,Lt(e),e.secretValues);if(!r)return{name:this.name,passed:!0};let n=Ag(e.inputs.repositoryPath,e.inputs.relatedRepos??[]),s=E=>{let y=n(E);return Bd(y)?Af(e.inputs,y.repository,y.rel):E},o=[{path:e.inputs.repositoryPath},...e.inputs.relatedRepos??[]].flatMap(({path:E,slug:y})=>{try{return(0,H1e.execFileSync)("git",["-C",E,"diff","--name-only","--diff-filter=d","HEAD"],{encoding:"utf-8"}).split(`
725
725
  `).filter(C=>C&&Mf(C)&&!C.startsWith(".skyramp/")).map(C=>Af(e.inputs,y,C))}catch{return[]}}),a=r.tests.filter(E=>E.change!=="added"&&E.change!=="deleted").map(E=>({file:s(E.file),changed:E.change!=="unchanged",ran:E.result.exit!==null})),c=JQe(o,a),l=r.tests.filter(E=>E.change==="updated");if(!l.length)return js.writeFileSync(kt.join(e.outputsDir,UE),`[]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skyramp/testbot-core",
3
- "version": "0.13.0-v3.181",
3
+ "version": "0.13.0-v3.182",
4
4
  "description": "Skyramp Testbot engine: AI-powered deterministic test generation, execution and maintenance",
5
5
  "main": "dist/main.js",
6
6
  "//files": "An explicit allowlist, never a glob. `dist/` is gitignored, so without this key npm's .gitignore fallback would publish a package with no engine in it; with a glob it would publish the sourcemaps, whose sourcesContent embeds every src/*.ts verbatim. eval-entry.js is a development entry and must not ship. dist/VERSION.json exists only in a release-engine.yml build. assets/gen-reference/ is the staged codegen reference corpus, which generate.ts finds at ../assets from dist/.",