@cat-factory/server 0.49.1 → 0.49.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/ContainerAgentExecutor.d.ts.map +1 -1
- package/dist/agents/ContainerAgentExecutor.js +3 -522
- package/dist/agents/ContainerAgentExecutor.js.map +1 -1
- package/dist/agents/containerAgentResult.d.ts +20 -0
- package/dist/agents/containerAgentResult.d.ts.map +1 -0
- package/dist/agents/containerAgentResult.js +263 -0
- package/dist/agents/containerAgentResult.js.map +1 -0
- package/dist/agents/prompts.d.ts +71 -0
- package/dist/agents/prompts.d.ts.map +1 -0
- package/dist/agents/prompts.js +280 -0
- package/dist/agents/prompts.js.map +1 -0
- package/dist/http/env.d.ts +9 -1
- package/dist/http/env.d.ts.map +1 -1
- package/dist/modules/workspaces/WorkspaceController.d.ts.map +1 -1
- package/dist/modules/workspaces/WorkspaceController.js +10 -11
- package/dist/modules/workspaces/WorkspaceController.js.map +1 -1
- package/package.json +8 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContainerAgentExecutor.d.ts","sourceRoot":"","sources":["../../src/agents/ContainerAgentExecutor.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,oBAAoB,EACzB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,QAAQ,
|
|
1
|
+
{"version":3,"file":"ContainerAgentExecutor.d.ts","sourceRoot":"","sources":["../../src/agents/ContainerAgentExecutor.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,oBAAoB,EACzB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,QAAQ,EAKb,KAAK,kBAAkB,EACxB,MAAM,qBAAqB,CAAA;AAS5B,OAAO,EACL,KAAK,YAAY,EAWlB,MAAM,qBAAqB,CAAA;AAiC5B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,0CAA0C,CAAA;AACvF,OAAO,EAAmB,KAAK,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAGnF,YAAY,EAAE,sBAAsB,EAAE,CAAA;AAItC,MAAM,WAAW,UAAU;IACzB,cAAc,EAAE,MAAM,CAAA;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,MAAM,iBAAiB,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAA;AAEpG;;;;;GAKG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAClC,cAAc,EAAE,MAAM,EACtB,GAAG,CAAC,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,KAChD,OAAO,CAAC,MAAM,CAAC,CAAA;AAEpB;;;;;;;GAOG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAC7B,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,KACzB,OAAO,CAAC,OAAO,CAAC,CAAA;AAErB,0EAA0E;AAC1E,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;CACf;AAED,gFAAgF;AAChF,MAAM,MAAM,sBAAsB,GAAG,CACnC,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,kBAAkB,KACvB,OAAO,CAAC,uBAAuB,CAAC,CAAA;AAErC;;;;;;GAMG;AACH,MAAM,MAAM,8BAA8B,GAAG,CAC3C,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,kBAAkB,KACvB,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAEhC,kFAAkF;AAClF,MAAM,MAAM,uBAAuB,GAAG,CACpC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,KACjD,OAAO,CAAC,IAAI,CAAC,CAAA;AA+BlB;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAWzD;AAuFD,sFAAsF;AACtF,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,QAAQ,GAAG,QAAQ,CAAA;CAC9B;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,IAAI,EAAE,UAAU,KAAK,UAAU,CAAA;AA6EhE,MAAM,WAAW,kCAAkC;IACjD,6FAA6F;IAC7F,gBAAgB,EAAE,sBAAsB,CAAA;IACxC,yEAAyE;IACzE,YAAY,EAAE,YAAY,CAAA;IAC1B,8EAA8E;IAC9E,iBAAiB,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,KAAK,QAAQ,GAAG,SAAS,CAAA;IACxE;;;OAGG;IACH,4BAA4B,CAAC,EAAE,CAC7B,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,aAAa,CAAC,EAAE,MAAM,KACnB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IAChC,2DAA2D;IAC3D,iBAAiB,EAAE,iBAAiB,CAAA;IACpC;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAA;IAC9E,iFAAiF;IACjF,qBAAqB,EAAE,qBAAqB,CAAA;IAC5C;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;IACnC,gFAAgF;IAChF,cAAc,EAAE,uBAAuB,CAAA;IACvC;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,sBAAsB,CAAA;IAC/C;;;;OAIG;IACH,8BAA8B,CAAC,EAAE,8BAA8B,CAAA;IAC/D,gGAAgG;IAChG,uBAAuB,CAAC,EAAE,uBAAuB,CAAA;IACjD;;;;;;;;;;;OAWG;IACH,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,kBAAkB,GAAG,SAAS,KAAK,OAAO,CAAA;IAC7F;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAC5F;;;;;;OAMG;IACH,uBAAuB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAC1F;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAA;IACpB,gFAAgF;IAChF,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC;;;;;;;;OAQG;IACH,uBAAuB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACnE;;;;;OAKG;IACH,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B;;;;;;OAMG;IACH,yBAAyB,CAAC,EAAE,oBAAoB,CAAA;CACjD;AAKD;;;;;;;;;;;;;GAaG;AACH,qBAAa,sBAAuB,YAAW,kBAAkB;IAmBnD,OAAO,CAAC,QAAQ,CAAC,IAAI;IAlBjC,uFAAuF;IACvF,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAiB;IAEtC;;;;;;;;;OASG;IACH,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAoB;IAEtD,gFAAgF;IAChF,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAa;IAEzC,YAA6B,IAAI,EAAE,kCAAkC,EASpE;IAED,qFAAqF;IACrF,SAAS,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAE5C;IAED;;;;;OAKG;IACG,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAiChE;IAED,mFAAmF;IAC7E,OAAO,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAkF7D;IAED;;;;;;OAMG;IACG,OAAO,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAEnD;IAED;;;;OAIG;IACG,GAAG,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAY3D;IAED;;;;OAIG;IACG,YAAY,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAG5D;IAED;;;;;;;OAOG;IACG,YAAY,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAI7D;IAED;;;;;;OAMG;IACH,OAAO,CAAC,eAAe;IAiBvB,uFAAuF;IACvF,OAAO,CAAC,UAAU;IAalB,qFAAqF;YACvE,YAAY;IA4I1B;;;;OAIG;YACW,WAAW;IAoGzB;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;IAoGrB;;;;;;;;OAQG;IACH,OAAO,CAAC,wBAAwB;IAsFhC;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,wBAAwB;CAoLjC"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {} from '@cat-factory/kernel';
|
|
2
2
|
import { CONTEXT_BUDGET, CredentialRequiredError, renderTaskContext, SUBSCRIPTION_VENDORS, isIndividualVendor, } from '@cat-factory/kernel';
|
|
3
3
|
import { resolveInstanceTypeId } from '@cat-factory/contracts';
|
|
4
|
-
import { agentTuningFor,
|
|
4
|
+
import { agentTuningFor, composeBlockSystemPrompt, FOLLOW_UP_GUIDANCE, isContainerBackedCompanion, isProxyableProvider, isReadOnlyAgentKind, registeredAgentStep, systemPromptFor, userPromptFor, webResearchGuidanceFor, } from '@cat-factory/agents';
|
|
5
5
|
import { ModelRouter } from './ModelRouter.js';
|
|
6
|
+
import { toRunResult } from './containerAgentResult.js';
|
|
7
|
+
import { BLUEPRINT_SHAPE_HINT, BLUEPRINT_SYSTEM_PROMPT, blueprintUserPrompt, MERGE_ASSESSMENT_SHAPE_HINT, MERGER_SYSTEM_PROMPT, mergerUserPrompt, ON_CALL_ASSESSMENT_SHAPE_HINT, ON_CALL_SYSTEM_PROMPT, onCallUserPrompt, prBody, SPEC_SHAPE_HINT, SPEC_WRITER_SYSTEM_PROMPT, specWriterUserPrompt, TEST_REPORT_SHAPE_HINT, testerInfraSpec, UI_TEST_REPORT_SHAPE_HINT, } from './prompts.js';
|
|
6
8
|
import { BLUEPRINTS_AGENT_KIND, CI_FIXER_AGENT_KIND, CONFLICT_RESOLVER_AGENT_KIND, FIXER_AGENT_KIND, MERGER_AGENT_KIND, ON_CALL_AGENT_KIND, SPEC_WRITER_AGENT_KIND, TESTER_AGENT_KIND, UI_TESTER_AGENT_KIND, } from '@cat-factory/orchestration';
|
|
7
9
|
import { RunnerJobClient } from './RunnerJobClient.js';
|
|
8
10
|
/**
|
|
@@ -201,122 +203,6 @@ function buildContextFiles(context) {
|
|
|
201
203
|
}
|
|
202
204
|
/** Poll cadence for the non-durable `run()` fallback (the durable driver sleeps between polls itself). */
|
|
203
205
|
const RUN_POLL_INTERVAL_MS = 5_000;
|
|
204
|
-
/** Role prompt the Blueprinter step's agent runs under (returns the tree as JSON). */
|
|
205
|
-
const BLUEPRINT_SYSTEM_PROMPT = 'You are a Domain-Driven Design architect mapping this repository. Decompose it ' +
|
|
206
|
-
'into ONE top-level service and the modules inside it, where each module is a ' +
|
|
207
|
-
'DOMAIN — a cohesive area of the BUSINESS, in the language of the problem space ' +
|
|
208
|
-
'(a DDD bounded context / aggregate / subdomain). Name modules after business ' +
|
|
209
|
-
'concepts, not technical layers. ' +
|
|
210
|
-
'A module MUST represent a business capability or domain model (e.g. Billing, ' +
|
|
211
|
-
'Catalog, Ordering, Identity), NOT a technical layer or shape: "api", "routes", ' +
|
|
212
|
-
'"controllers", "utils", "helpers", "lib", "common", "config", "types", "models", ' +
|
|
213
|
-
'"db" and the like are NOT domains and MUST NOT be modules. ' +
|
|
214
|
-
'Group the genuinely non-business, technical/cross-cutting plumbing (persistence ' +
|
|
215
|
-
'wiring, HTTP/transport, logging, configuration, auth middleware, build/deploy, ' +
|
|
216
|
-
'shared utilities) into a SINGLE module named "infrastructure" rather than ' +
|
|
217
|
-
'scattering it into many technical modules. ' +
|
|
218
|
-
'Prefer organising code by domain (the ubiquitous language) over organising by ' +
|
|
219
|
-
'file type. Anchor every node to the codebase with explicit repo-relative ' +
|
|
220
|
-
'file/directory references. Keep names short and descriptive. ' +
|
|
221
|
-
'Respond with ONLY a JSON object of shape {"type","name","summary","references":[],' +
|
|
222
|
-
'"modules":[{"name","summary","references":[]}]} — no prose, no code fences. ' +
|
|
223
|
-
FINAL_ANSWER_IN_REPLY;
|
|
224
|
-
/** Role prompt the spec-writer step runs under (returns the spec doc as JSON). */
|
|
225
|
-
const SPEC_WRITER_SYSTEM_PROMPT = 'You maintain the PRESCRIPTIVE specification for a service. READ the specification ' +
|
|
226
|
-
'already committed to the repository under `spec/` (the baseline): start with ' +
|
|
227
|
-
'`spec/overview.md` for the module → feature index, then open the relevant ' +
|
|
228
|
-
'`spec/modules/<module>/<feature>.json` shards for the detail you need. You are also ' +
|
|
229
|
-
'given the ' +
|
|
230
|
-
'requirements of ONE task. Apply that task as an INCREMENT onto the baseline: add ' +
|
|
231
|
-
'requirements for what the task introduces, and adjust existing requirements ONLY ' +
|
|
232
|
-
'where the task changes their expected behaviour. Leave every other part of the ' +
|
|
233
|
-
'baseline spec untouched. Translate ONLY what the task requirements state — do NOT ' +
|
|
234
|
-
'invent requirements, fill gaps, or design beyond them (missing requirements are the ' +
|
|
235
|
-
'requirements step’s job, not yours). ' +
|
|
236
|
-
'The spec captures ONLY BUSINESS requirements — externally-observable behaviour, ' +
|
|
237
|
-
'product rules and acceptance criteria. PURELY TECHNICAL work (a refactor, a ' +
|
|
238
|
-
'dependency bump, internal restructuring, build/infra or other non-functional change ' +
|
|
239
|
-
'that does NOT alter what the system does for its users) introduces no business ' +
|
|
240
|
-
'requirements, and "NO NEW SPECS" is a valid, correct outcome for it: do NOT invent ' +
|
|
241
|
-
'requirements to justify a change, and do NOT re-document technical/architecture ' +
|
|
242
|
-
'detail here. When this task is purely technical, leave the baseline spec untouched ' +
|
|
243
|
-
'and respond with ONLY {"noBusinessSpecs": true} (no other fields, no prose, no code ' +
|
|
244
|
-
'fences). Otherwise return the full document as below. ' +
|
|
245
|
-
'The spec is a two-level taxonomy: MODULES ' +
|
|
246
|
-
'(domains, e.g. "Auth") each containing GROUPS (features, e.g. "Login"). Every ' +
|
|
247
|
-
'requirement AND every domain rule lives inside a specific feature group: a group ' +
|
|
248
|
-
'carries both its `requirements` and the `rules` scoped to it. There is NO catch-all — ' +
|
|
249
|
-
'a cross-cutting concern goes in a `common` or `infrastructure` module that is ITSELF ' +
|
|
250
|
-
'split into specific feature groups. CRUCIALLY, reuse the EXISTING taxonomy: place ' +
|
|
251
|
-
'each new requirement/rule into the closest-fitting existing module and feature, ' +
|
|
252
|
-
'reusing its EXACT name, and create a new module or feature ONLY when nothing fits — ' +
|
|
253
|
-
'never a near-duplicate of an existing one (no "Authentication" beside "Auth", no ' +
|
|
254
|
-
'"User Login" beside "Login"). Each requirement is phrased as "The system SHALL …" ' +
|
|
255
|
-
'with a MoSCoW priority (must/should/could) and structured Given/When/Then acceptance ' +
|
|
256
|
-
'criteria. Acceptance-scenario coverage is a FIRST-CLASS deliverable: every ' +
|
|
257
|
-
'requirement the task adds or changes MUST carry complete acceptance criteria — the ' +
|
|
258
|
-
'happy path AND the invalid-input / error / edge / boundary cases the requirements ' +
|
|
259
|
-
'imply — since the Gherkin `.feature` files and the runnable tests are derived ' +
|
|
260
|
-
'mechanically from them. Preserve the baseline’s existing `sourceBlockIds`; tag the ' +
|
|
261
|
-
'requirements this task adds or changes with this task’s block id. Return the ' +
|
|
262
|
-
'COMPLETE updated specification (baseline plus this increment), not a diff. You have ' +
|
|
263
|
-
'NO repository write access and MUST NOT write, edit, or commit any file: the platform ' +
|
|
264
|
-
'persists the specification you return, so returning it IS the whole job. Respond ' +
|
|
265
|
-
'with ONLY a JSON object of ' +
|
|
266
|
-
'shape {"service","summary","modules":[{"name","summary","groups":[{"name","summary",' +
|
|
267
|
-
'"requirements":[{"id","title","statement","kind","priority","sourceBlockIds":[],' +
|
|
268
|
-
'"acceptance":[{"id","given","when","outcome"}]}],"rules":[{"id","rule","rationale",' +
|
|
269
|
-
'"sourceBlockIds":[]}]}]}]} ' +
|
|
270
|
-
'(each acceptance criterion is a Given/When/Then, with the Then clause in `outcome`) — ' +
|
|
271
|
-
'no prose, no code fences. ' +
|
|
272
|
-
FINAL_ANSWER_IN_REPLY;
|
|
273
|
-
/** Role prompt the `merger` step runs under (scores the PR; returns JSON only). */
|
|
274
|
-
const MERGER_SYSTEM_PROMPT = 'You are a release manager assessing a pull request before merge. Inspect the ' +
|
|
275
|
-
'diff between the PR head branch and the base branch and judge three axes, each ' +
|
|
276
|
-
'as a number from 0 (trivial/safe) to 1 (severe): complexity (how intricate the ' +
|
|
277
|
-
'change is), risk (how likely it is to break something), and impact (blast radius ' +
|
|
278
|
-
'if it does). Be conservative. Respond with ONLY a JSON object of shape ' +
|
|
279
|
-
'{"complexity":0.0,"risk":0.0,"impact":0.0,"rationale":"…"} — no prose, no code fences. ' +
|
|
280
|
-
FINAL_ANSWER_IN_REPLY;
|
|
281
|
-
/** Compact shape hint fed to the structured-output repair call for the blueprint tree. */
|
|
282
|
-
const BLUEPRINT_SHAPE_HINT = 'Expected a service tree: {"type": string, "name": string, "summary": string, ' +
|
|
283
|
-
'"references": string[], "modules": [{"name": string, "summary": string, ' +
|
|
284
|
-
'"references": string[]}]}.';
|
|
285
|
-
/** Compact shape hint fed to the structured-output repair call for the spec doc. */
|
|
286
|
-
const SPEC_SHAPE_HINT = 'Expected a requirements document with a two-level taxonomy — module (domain) → ' +
|
|
287
|
-
'group (feature) — where each group carries BOTH its requirements and the domain ' +
|
|
288
|
-
'rules scoped to it: {"service": string, "summary": string, "modules": [{"name": ' +
|
|
289
|
-
'string, "summary": string, "groups": [{"name": string, "summary": string, ' +
|
|
290
|
-
'"requirements": [{"id": string, "title": string, "statement": string, "kind": ' +
|
|
291
|
-
'string, "priority": string, "sourceBlockIds": string[], "acceptance": [{"given": ' +
|
|
292
|
-
'string, "when": string, "outcome": string}]}], "rules": [{"id": string, "rule": ' +
|
|
293
|
-
'string, "rationale": string, "sourceBlockIds": string[]}]}]}]}. For a purely ' +
|
|
294
|
-
'technical task with no business requirements, the document is instead just ' +
|
|
295
|
-
'{"noBusinessSpecs": true}.';
|
|
296
|
-
/** Compact shape hint fed to the structured-output repair call for the merger assessment. */
|
|
297
|
-
const MERGE_ASSESSMENT_SHAPE_HINT = 'Expected a merge assessment: {"complexity": number 0..1, "risk": number 0..1, ' +
|
|
298
|
-
'"impact": number 0..1, "rationale": string}.';
|
|
299
|
-
/** Compact shape hint fed to the structured-output repair call for the on-call assessment. */
|
|
300
|
-
const ON_CALL_ASSESSMENT_SHAPE_HINT = 'Expected an on-call assessment: {"culpritConfidence": number 0..1, "recommendation": ' +
|
|
301
|
-
'"revert"|"hold"|"monitor", "rationale": string, "evidence": string[]}.';
|
|
302
|
-
/** Compact shape hint fed to the structured-output repair call for the tester report. */
|
|
303
|
-
const TEST_REPORT_SHAPE_HINT = 'Expected a test report: {"greenlight": boolean, "summary": string, "tested": string[], ' +
|
|
304
|
-
'"outcomes": [{"name": string, "status": "passed"|"failed"|"skipped", "detail"?: string}], ' +
|
|
305
|
-
'"concerns": [{"title": string, "detail": string, "severity": "low"|"medium"|"high"|"critical"}]}.';
|
|
306
|
-
/** Shape hint for the UI tester: a test report that also lists captured screenshots. */
|
|
307
|
-
const UI_TEST_REPORT_SHAPE_HINT = TEST_REPORT_SHAPE_HINT.replace(/\}\.$/, '') +
|
|
308
|
-
', "screenshots": [{"view": string, "artifactId": string, "hash"?: string}]}. Each ' +
|
|
309
|
-
'screenshot must be a distinct view you captured and uploaded to the artifact store.';
|
|
310
|
-
const ON_CALL_SYSTEM_PROMPT = 'You are an on-call engineer investigating a possible post-release regression. A ' +
|
|
311
|
-
'recently merged pull request shipped, and the evidence below (alerting Datadog ' +
|
|
312
|
-
'monitors/SLOs and recent error logs) suggests the service regressed afterward. Read ' +
|
|
313
|
-
'the PR diff on the head branch and weigh whether THIS change is the likely cause — ' +
|
|
314
|
-
'beware correlation vs causation; a coincident deploy is not proof. You may read and ' +
|
|
315
|
-
'inspect any file, but you MUST NOT modify, commit or revert anything; a human decides ' +
|
|
316
|
-
'whether to revert. Respond with ONLY a JSON object of shape ' +
|
|
317
|
-
'{"culpritConfidence":0.0,"recommendation":"revert"|"hold"|"monitor","rationale":"…",' +
|
|
318
|
-
'"evidence":["…"]} — no prose, no code fences. ' +
|
|
319
|
-
FINAL_ANSWER_IN_REPLY;
|
|
320
206
|
/**
|
|
321
207
|
* An {@link AgentExecutor} that performs implementation work in a real sandbox:
|
|
322
208
|
* it dispatches a per-run container running the Pi coding agent (a per-run
|
|
@@ -1055,409 +941,4 @@ export class ContainerAgentExecutor {
|
|
|
1055
941
|
return undefined;
|
|
1056
942
|
}
|
|
1057
943
|
}
|
|
1058
|
-
/**
|
|
1059
|
-
* Map a finished runner {@link RunnerJobResult} into the engine's {@link AgentRunResult}.
|
|
1060
|
-
* Every built-in agent now dispatches the single manifest-driven `agent` kind, so the
|
|
1061
|
-
* result carries either a structured `custom` JSON (explore agents), an opened `prUrl`
|
|
1062
|
-
* (the coder), or just `pushed` (the in-place fixers / conflict-resolver). No `model` here:
|
|
1063
|
-
* the proxy meters tokens and the async path doesn't carry the provider ref to the poll
|
|
1064
|
-
* site; `usage` is likewise omitted (metered by the proxy).
|
|
1065
|
-
*/
|
|
1066
|
-
function toRunResult(result, agentKind) {
|
|
1067
|
-
// A generic, structured `agent` (explore) job returns its parsed JSON as `custom`. A
|
|
1068
|
-
// migrated built-in kind has it coerced into the well-known engine field here, KIND-AWARE
|
|
1069
|
-
// — the conservative coercion that used to live in the bespoke harness handlers
|
|
1070
|
-
// (blueprint/spec/merge/on-call/test) now runs backend-side, so the engine's
|
|
1071
|
-
// resolvers/gates see `blueprintService`/`spec`/`mergeAssessment`/`onCallAssessment`/
|
|
1072
|
-
// `testReport` exactly as before. Any other kind (a registered custom kind) surfaces the
|
|
1073
|
-
// raw JSON as `custom` for its post-op to coerce/render from.
|
|
1074
|
-
if (result.custom !== undefined) {
|
|
1075
|
-
// Blueprinter: coerce into `blueprintService` (board reconcile + `blueprintPostOp`
|
|
1076
|
-
// render/commit). A nameless/garbage tree coerces to null ⇒ left unset.
|
|
1077
|
-
if (agentKind === BLUEPRINTS_AGENT_KIND) {
|
|
1078
|
-
const service = coerceBlueprintService(result.custom, '');
|
|
1079
|
-
return {
|
|
1080
|
-
output: result.summary?.trim() || 'Service blueprint updated.',
|
|
1081
|
-
...(service ? { blueprintService: service } : {}),
|
|
1082
|
-
};
|
|
1083
|
-
}
|
|
1084
|
-
// Spec-writer: coerce into `spec` (engine strict-validate + `specPostOp` shard/commit).
|
|
1085
|
-
// The doc must carry its OWN `service` name (no repo-name rescue — backwards-compat is a
|
|
1086
|
-
// non-goal); a nameless/garbage doc coerces to null ⇒ left unset (no ingest, no commit).
|
|
1087
|
-
if (agentKind === SPEC_WRITER_AGENT_KIND) {
|
|
1088
|
-
// A purely TECHNICAL task has no business requirements to specify: the writer signals
|
|
1089
|
-
// `noBusinessSpecs` and we leave the baseline spec untouched (NO `spec` channel, so
|
|
1090
|
-
// `specPostOp` commits nothing). The engine reads the flag to infer the block's
|
|
1091
|
-
// `technical` label (with the spec-companion's corroboration). Checked first so a
|
|
1092
|
-
// model that returned both the flag and a stray baseline echo never commits over it.
|
|
1093
|
-
const custom = result.custom;
|
|
1094
|
-
if (custom && typeof custom === 'object' && custom.noBusinessSpecs === true) {
|
|
1095
|
-
return {
|
|
1096
|
-
output: result.summary?.trim() ||
|
|
1097
|
-
'No business requirements to specify — this is a technical task.',
|
|
1098
|
-
noBusinessSpecs: true,
|
|
1099
|
-
};
|
|
1100
|
-
}
|
|
1101
|
-
const spec = coerceSpecDoc(result.custom, '');
|
|
1102
|
-
return {
|
|
1103
|
-
output: result.summary?.trim() || 'Service specification updated.',
|
|
1104
|
-
...(spec ? { spec } : {}),
|
|
1105
|
-
};
|
|
1106
|
-
}
|
|
1107
|
-
if (agentKind === MERGER_AGENT_KIND) {
|
|
1108
|
-
return {
|
|
1109
|
-
output: result.summary?.trim() || 'Pull request assessed.',
|
|
1110
|
-
mergeAssessment: coerceMergeAssessment(result.custom, result.summary),
|
|
1111
|
-
};
|
|
1112
|
-
}
|
|
1113
|
-
if (agentKind === ON_CALL_AGENT_KIND) {
|
|
1114
|
-
return {
|
|
1115
|
-
output: result.summary?.trim() || 'Release regression investigated.',
|
|
1116
|
-
onCallAssessment: coerceOnCallAssessment(result.custom, result.summary),
|
|
1117
|
-
};
|
|
1118
|
-
}
|
|
1119
|
-
// Tester: coerce into `testReport` (greenlight-or-loop the fixer; the conservative
|
|
1120
|
-
// greenlight/blocking rule the harness `/test` handler applied now runs in
|
|
1121
|
-
// `coerceTestReport`, re-applied defensively by the TesterController).
|
|
1122
|
-
if (agentKind === TESTER_AGENT_KIND || agentKind === UI_TESTER_AGENT_KIND) {
|
|
1123
|
-
return {
|
|
1124
|
-
output: result.summary?.trim() || 'Testing complete.',
|
|
1125
|
-
testReport: coerceTestReport(result.custom, result.summary),
|
|
1126
|
-
// The in-container docker-compose stand-up record (local-infra tester) — forwarded so
|
|
1127
|
-
// the engine can persist its captured logs on the Tester step. Harness-produced, so
|
|
1128
|
-
// no coercion; the TesterController validates it defensively before persisting.
|
|
1129
|
-
...(result.infraSetup ? { infraSetup: result.infraSetup } : {}),
|
|
1130
|
-
};
|
|
1131
|
-
}
|
|
1132
|
-
return {
|
|
1133
|
-
output: result.summary?.trim() || 'Agent run complete.',
|
|
1134
|
-
custom: result.custom,
|
|
1135
|
-
};
|
|
1136
|
-
}
|
|
1137
|
-
// A coding job that opened a PR (the coder + any PR-opening coding agent): surface the PR
|
|
1138
|
-
// STRUCTURALLY so the engine records it on the block and the board links to it. Checked
|
|
1139
|
-
// BEFORE `pushed` — a coding run reports BOTH `pushed:true` AND `prUrl`, so the PR must win
|
|
1140
|
-
// over the in-place-fixer text below or it would be silently dropped.
|
|
1141
|
-
if (result.prUrl) {
|
|
1142
|
-
const summary = result.summary?.trim() || 'Implementation complete.';
|
|
1143
|
-
return {
|
|
1144
|
-
output: `${summary}\n\nPR: ${result.prUrl}`,
|
|
1145
|
-
pullRequest: {
|
|
1146
|
-
url: result.prUrl,
|
|
1147
|
-
...(prNumberFromUrl(result.prUrl) !== undefined
|
|
1148
|
-
? { number: prNumberFromUrl(result.prUrl) }
|
|
1149
|
-
: {}),
|
|
1150
|
-
...(result.branch ? { branch: result.branch } : {}),
|
|
1151
|
-
},
|
|
1152
|
-
};
|
|
1153
|
-
}
|
|
1154
|
-
// An in-place coding job with no PR (ci-fixer / fixer / conflict-resolver): it pushed back
|
|
1155
|
-
// onto the existing branch (or was a clean no-op). The engine's CI / conflicts gate
|
|
1156
|
-
// re-checks the real signal regardless; map to a sensible output. The agent's own summary
|
|
1157
|
-
// is used when present (e.g. the conflict-resolver's "Resolved merge conflicts …").
|
|
1158
|
-
if (result.pushed !== undefined) {
|
|
1159
|
-
return {
|
|
1160
|
-
output: result.summary?.trim() ||
|
|
1161
|
-
(result.pushed ? 'Pushed changes to the branch.' : 'No changes were produced.'),
|
|
1162
|
-
};
|
|
1163
|
-
}
|
|
1164
|
-
return { output: result.summary?.trim() || 'Implementation complete.' };
|
|
1165
|
-
}
|
|
1166
|
-
/** Extract the PR number from a GitHub pull-request URL (`.../pull/42`). */
|
|
1167
|
-
function prNumberFromUrl(url) {
|
|
1168
|
-
const match = /\/pull\/(\d+)/.exec(url);
|
|
1169
|
-
if (!match)
|
|
1170
|
-
return undefined;
|
|
1171
|
-
const n = Number(match[1]);
|
|
1172
|
-
return Number.isFinite(n) ? n : undefined;
|
|
1173
|
-
}
|
|
1174
|
-
/**
|
|
1175
|
-
* Clamp a value to a 0..1 number, defaulting to `fallback` for anything that is not a
|
|
1176
|
-
* finite number (or a non-empty numeric string). Crucially, `null`, `''`, `false` and `[]`
|
|
1177
|
-
* fall back rather than coercing to `0` — `Number()` turns all of them into a finite `0`,
|
|
1178
|
-
* which would silently make a garbage merger score read as "trivial/safe" and defeat the
|
|
1179
|
-
* conservative-on-garbage default that replaces the harness's old `diffExaminable` guard.
|
|
1180
|
-
*/
|
|
1181
|
-
function clamp01(value, fallback) {
|
|
1182
|
-
const n = typeof value === 'number'
|
|
1183
|
-
? value
|
|
1184
|
-
: typeof value === 'string' && value.trim() !== ''
|
|
1185
|
-
? Number(value)
|
|
1186
|
-
: Number.NaN;
|
|
1187
|
-
if (!Number.isFinite(n))
|
|
1188
|
-
return fallback;
|
|
1189
|
-
return Math.min(1, Math.max(0, n));
|
|
1190
|
-
}
|
|
1191
|
-
/** First non-empty of the agent's rationale or run summary (capped), else a stable default. */
|
|
1192
|
-
function coerceRationale(rationale, summary) {
|
|
1193
|
-
if (typeof rationale === 'string' && rationale.trim())
|
|
1194
|
-
return rationale;
|
|
1195
|
-
if (summary?.trim())
|
|
1196
|
-
return summary.slice(0, 2000);
|
|
1197
|
-
return 'No rationale provided.';
|
|
1198
|
-
}
|
|
1199
|
-
/**
|
|
1200
|
-
* Coerce a migrated `merger` agent's structured JSON into the engine's merge assessment.
|
|
1201
|
-
* This is the conservative coercion the harness `/merge` handler used to do: a missing or
|
|
1202
|
-
* garbage score defaults to 1 (severe → routes to human review rather than a silent
|
|
1203
|
-
* auto-merge), and the rationale falls back to the agent's summary. The harness's extra
|
|
1204
|
-
* container-side `diffExaminable` guard (force 1/1/1 when the base diff was unreadable) is
|
|
1205
|
-
* not reproducible backend-side; the conservative-on-garbage default covers the same risk.
|
|
1206
|
-
*/
|
|
1207
|
-
function coerceMergeAssessment(raw, summary) {
|
|
1208
|
-
const o = (typeof raw === 'object' && raw !== null ? raw : {});
|
|
1209
|
-
return {
|
|
1210
|
-
complexity: clamp01(o.complexity, 1),
|
|
1211
|
-
risk: clamp01(o.risk, 1),
|
|
1212
|
-
impact: clamp01(o.impact, 1),
|
|
1213
|
-
rationale: coerceRationale(o.rationale, summary),
|
|
1214
|
-
};
|
|
1215
|
-
}
|
|
1216
|
-
/**
|
|
1217
|
-
* Coerce a migrated `on-call` agent's structured JSON into the engine's release-regression
|
|
1218
|
-
* assessment — the conservative coercion the harness `/on-call` handler used to do: a
|
|
1219
|
-
* missing confidence defaults to 0 (don't imply the PR is at fault without evidence) and a
|
|
1220
|
-
* missing recommendation defaults to `hold` (a human decides).
|
|
1221
|
-
*/
|
|
1222
|
-
function coerceOnCallAssessment(raw, summary) {
|
|
1223
|
-
const o = (typeof raw === 'object' && raw !== null ? raw : {});
|
|
1224
|
-
const evidence = Array.isArray(o.evidence)
|
|
1225
|
-
? o.evidence.filter((e) => typeof e === 'string')
|
|
1226
|
-
: [];
|
|
1227
|
-
return {
|
|
1228
|
-
culpritConfidence: clamp01(o.culpritConfidence, 0),
|
|
1229
|
-
recommendation: o.recommendation === 'revert' || o.recommendation === 'monitor' ? o.recommendation : 'hold',
|
|
1230
|
-
rationale: coerceRationale(o.rationale, summary),
|
|
1231
|
-
evidence,
|
|
1232
|
-
};
|
|
1233
|
-
}
|
|
1234
|
-
const TEST_SEVERITIES = new Set(['low', 'medium', 'high', 'critical']);
|
|
1235
|
-
const TEST_STATUSES = new Set(['passed', 'failed', 'skipped']);
|
|
1236
|
-
/**
|
|
1237
|
-
* Coerce a migrated `tester` agent's structured JSON into the engine's {@link TestReport} —
|
|
1238
|
-
* the conservative coercion the harness `/test` handler used to do, defaulting every field
|
|
1239
|
-
* safely so a malformed reply still parses (the engine strict-validates it). Crucially a
|
|
1240
|
-
* greenlight is honoured ONLY when no BLOCKING (high/critical) concern is open, so a model
|
|
1241
|
-
* that greenlights with an open blocker can't auto-pass; low/medium concerns are advisory.
|
|
1242
|
-
* The engine's TesterController re-applies this rule defensively.
|
|
1243
|
-
*/
|
|
1244
|
-
function coerceTestReport(raw, summary) {
|
|
1245
|
-
const o = (typeof raw === 'object' && raw !== null ? raw : {});
|
|
1246
|
-
const outcomes = Array.isArray(o.outcomes)
|
|
1247
|
-
? o.outcomes
|
|
1248
|
-
.filter((x) => typeof x === 'object' && x !== null)
|
|
1249
|
-
.map((x) => ({
|
|
1250
|
-
name: typeof x.name === 'string' ? x.name : '(unnamed)',
|
|
1251
|
-
status: TEST_STATUSES.has(x.status) ? x.status : 'skipped',
|
|
1252
|
-
...(typeof x.detail === 'string' && x.detail ? { detail: x.detail } : {}),
|
|
1253
|
-
}))
|
|
1254
|
-
: [];
|
|
1255
|
-
const concerns = Array.isArray(o.concerns)
|
|
1256
|
-
? o.concerns
|
|
1257
|
-
.filter((x) => typeof x === 'object' && x !== null)
|
|
1258
|
-
.map((x) => ({
|
|
1259
|
-
title: typeof x.title === 'string' ? x.title : '(concern)',
|
|
1260
|
-
detail: typeof x.detail === 'string' ? x.detail : '',
|
|
1261
|
-
severity: TEST_SEVERITIES.has(x.severity) ? x.severity : 'medium',
|
|
1262
|
-
}))
|
|
1263
|
-
: [];
|
|
1264
|
-
const blocking = concerns.some((c) => c.severity === 'high' || c.severity === 'critical');
|
|
1265
|
-
const environment = o.environment === 'local' || o.environment === 'ephemeral' ? o.environment : undefined;
|
|
1266
|
-
// The UI tester reports the screenshots it captured + uploaded (artifact ids); keep
|
|
1267
|
-
// only the well-formed entries (a view name + an artifact id), passing the optionals
|
|
1268
|
-
// through. Absent/empty for the API tester.
|
|
1269
|
-
const screenshots = Array.isArray(o.screenshots)
|
|
1270
|
-
? o.screenshots
|
|
1271
|
-
.filter((x) => typeof x === 'object' && x !== null)
|
|
1272
|
-
.filter((x) => typeof x.view === 'string' && typeof x.artifactId === 'string')
|
|
1273
|
-
.map((x) => ({
|
|
1274
|
-
view: x.view,
|
|
1275
|
-
artifactId: x.artifactId,
|
|
1276
|
-
...(typeof x.hash === 'string' && x.hash ? { hash: x.hash } : {}),
|
|
1277
|
-
...(typeof x.width === 'number' ? { width: x.width } : {}),
|
|
1278
|
-
...(typeof x.height === 'number' ? { height: x.height } : {}),
|
|
1279
|
-
...(typeof x.referenceArtifactId === 'string' && x.referenceArtifactId
|
|
1280
|
-
? { referenceArtifactId: x.referenceArtifactId }
|
|
1281
|
-
: {}),
|
|
1282
|
-
}))
|
|
1283
|
-
: [];
|
|
1284
|
-
// An abort signal: the Tester reported it can't run a meaningful test at all (its env never
|
|
1285
|
-
// came up, a dependency is missing). Carry the reason through and force the greenlight off —
|
|
1286
|
-
// an abort is never release-ready, and the engine routes it to a human instead of the fixer.
|
|
1287
|
-
// The presence of the `abort` object IS the signal: never let a blank/oversized `reason`
|
|
1288
|
-
// downgrade that intent back into a (pointless) fixer loop, so fall back to a generic reason
|
|
1289
|
-
// and cap it like `summary` (the reason is shown to the human + stored on the step verbatim).
|
|
1290
|
-
const abortRaw = (typeof o.abort === 'object' && o.abort !== null ? o.abort : null);
|
|
1291
|
-
const abortReason = abortRaw
|
|
1292
|
-
? (typeof abortRaw.reason === 'string' && abortRaw.reason.trim()
|
|
1293
|
-
? abortRaw.reason.trim()
|
|
1294
|
-
: 'the Tester could not run a meaningful test').slice(0, 2000)
|
|
1295
|
-
: undefined;
|
|
1296
|
-
return {
|
|
1297
|
-
greenlight: o.greenlight === true && !blocking && !abortReason,
|
|
1298
|
-
summary: typeof o.summary === 'string' && o.summary ? o.summary : (summary?.slice(0, 2000) ?? ''),
|
|
1299
|
-
tested: Array.isArray(o.tested)
|
|
1300
|
-
? o.tested.filter((t) => typeof t === 'string')
|
|
1301
|
-
: [],
|
|
1302
|
-
outcomes,
|
|
1303
|
-
concerns,
|
|
1304
|
-
...(environment ? { environment } : {}),
|
|
1305
|
-
...(screenshots.length ? { screenshots } : {}),
|
|
1306
|
-
...(abortReason ? { abort: { reason: abortReason } } : {}),
|
|
1307
|
-
};
|
|
1308
|
-
}
|
|
1309
|
-
/**
|
|
1310
|
-
* The Blueprinter's task prompt. The agent now reads any existing blueprint from its own
|
|
1311
|
-
* read-only checkout (the harness no longer pre-injects the baseline tree), so the prompt
|
|
1312
|
-
* tells it to read `blueprints/` and update-or-create, then return the complete tree as
|
|
1313
|
-
* JSON. The backend `blueprintPostOp` renders + commits the artifact from that tree.
|
|
1314
|
-
*/
|
|
1315
|
-
function blueprintUserPrompt() {
|
|
1316
|
-
return [
|
|
1317
|
-
'Map this repository into the canonical service → modules blueprint, anchored to real ' +
|
|
1318
|
-
'file/directory references.',
|
|
1319
|
-
'',
|
|
1320
|
-
'If a blueprint already exists in the repository (read `blueprints/blueprint.json` and ' +
|
|
1321
|
-
'`blueprints/overview.md`), UPDATE it to reflect the current code: keep accurate ' +
|
|
1322
|
-
'modules, add new ones, and refine summaries + references. Otherwise create it from ' +
|
|
1323
|
-
'scratch. Return the COMPLETE tree (not a diff).',
|
|
1324
|
-
'',
|
|
1325
|
-
'Respond with ONLY the JSON object for the service tree — no prose, no code fences.',
|
|
1326
|
-
].join('\n');
|
|
1327
|
-
}
|
|
1328
|
-
/**
|
|
1329
|
-
* The spec-writer's task prompt — the instructions + baseline-read + taxonomy-reuse guidance
|
|
1330
|
-
* the bespoke harness `/spec` handler used to build (`buildUserPrompt`/`renderTaxonomyInventory`,
|
|
1331
|
-
* which used to inject the baseline doc + its module→feature inventory). The agent now reads
|
|
1332
|
-
* the baseline from its own read-only checkout under `spec/`, so the prompt tells it to read +
|
|
1333
|
-
* reuse the existing taxonomy rather than pre-injecting it. Carries ONLY this task's
|
|
1334
|
-
* requirements (the block description IS the task's reworked/incorporated requirements), so an
|
|
1335
|
-
* unmerged sibling task's work never bleeds in. The backend `specPostOp` shards + commits the
|
|
1336
|
-
* returned tree.
|
|
1337
|
-
*/
|
|
1338
|
-
function specWriterUserPrompt(context) {
|
|
1339
|
-
const block = context.block;
|
|
1340
|
-
const header = `### ${block.title || '(untitled task)'}${block.id ? ` (block ${block.id})` : ''}`;
|
|
1341
|
-
// Honour an explicit human-set BUSINESS/TECHNICAL label: a task pinned business HAS
|
|
1342
|
-
// business requirements, so the "no new specs" escape hatch is withdrawn; a task pinned
|
|
1343
|
-
// technical is told the empty outcome is expected. Left unset, the writer self-determines.
|
|
1344
|
-
const technicalGuidance = block.technical === false
|
|
1345
|
-
? 'This task is explicitly flagged BUSINESS: it HAS business requirements, so you MUST ' +
|
|
1346
|
-
'return the full updated specification. Do NOT respond with {"noBusinessSpecs": true}.'
|
|
1347
|
-
: block.technical === true
|
|
1348
|
-
? 'This task is explicitly flagged TECHNICAL (a refactor / dependency bump / internal ' +
|
|
1349
|
-
'or non-functional change with NO new externally-observable behaviour): "no business ' +
|
|
1350
|
-
'requirements" is the expected outcome — respond with ONLY {"noBusinessSpecs": true} ' +
|
|
1351
|
-
'and change nothing, unless you find genuine externally-observable behaviour to spec.'
|
|
1352
|
-
: 'If this task is purely TECHNICAL (a refactor / dependency bump / internal or ' +
|
|
1353
|
-
'non-functional change that introduces NO new externally-observable behaviour), it ' +
|
|
1354
|
-
'has no business requirements: respond with ONLY {"noBusinessSpecs": true} and ' +
|
|
1355
|
-
'change nothing.';
|
|
1356
|
-
return [
|
|
1357
|
-
'Apply this ONE task as an INCREMENT onto the service specification.',
|
|
1358
|
-
'',
|
|
1359
|
-
'First READ the specification already committed to the repository under `spec/` (the ' +
|
|
1360
|
-
'baseline as merged before this task): open `spec/overview.md` for the module → feature ' +
|
|
1361
|
-
'index, then the relevant `spec/modules/<module>/<feature>.json` shards. Keep every part ' +
|
|
1362
|
-
'of the baseline this task does not touch exactly as-is, preserving its `sourceBlockIds`; ' +
|
|
1363
|
-
'adjust an existing requirement only where this task changes its behaviour. Map each new ' +
|
|
1364
|
-
'requirement/rule into the closest-fitting EXISTING module and feature, reusing its EXACT ' +
|
|
1365
|
-
'name — create a new module or feature ONLY when nothing fits (never a near-duplicate). ' +
|
|
1366
|
-
'If no spec exists yet, start one as a module (domain) → feature (group) taxonomy.',
|
|
1367
|
-
'',
|
|
1368
|
-
'Requirements for the ONE task to apply (its clarified description). Translate ONLY what ' +
|
|
1369
|
-
'these state into BUSINESS requirements (externally-observable behaviour, product rules, ' +
|
|
1370
|
-
'acceptance criteria) with COMPLETE acceptance-scenario coverage — do NOT invent ' +
|
|
1371
|
-
'requirements or fill gaps they leave:',
|
|
1372
|
-
'',
|
|
1373
|
-
`${header}\n\n${block.description?.trim() || '(no description)'}`,
|
|
1374
|
-
'',
|
|
1375
|
-
technicalGuidance +
|
|
1376
|
-
' Otherwise return the COMPLETE updated document (baseline plus this task’s ' +
|
|
1377
|
-
'increment), not a diff. Respond with ONLY the JSON object — no prose, no code fences.',
|
|
1378
|
-
].join('\n');
|
|
1379
|
-
}
|
|
1380
|
-
/**
|
|
1381
|
-
* The merger's task prompt — the instructions + diff guidance the bespoke harness `/merge`
|
|
1382
|
-
* handler used to build. Kept backend-side now that the merger dispatches the generic
|
|
1383
|
-
* explore agent. Names the PR/branches so the agent diffs against the right base.
|
|
1384
|
-
*/
|
|
1385
|
-
function mergerUserPrompt(context, repo) {
|
|
1386
|
-
const prNumber = context.block.pullRequest?.number;
|
|
1387
|
-
const branch = context.block.pullRequest?.branch ?? repo.baseBranch;
|
|
1388
|
-
const pr = prNumber !== undefined ? ` (PR #${prNumber})` : '';
|
|
1389
|
-
return [
|
|
1390
|
-
'Assess the pull request on the head branch against the base branch and return the ' +
|
|
1391
|
-
'complexity / risk / impact scores + rationale as JSON.',
|
|
1392
|
-
'',
|
|
1393
|
-
`The pull request${pr} is on branch \`${branch}\`; the base branch is ` +
|
|
1394
|
-
`\`${repo.baseBranch}\`. Inspect the change (e.g. \`git fetch origin ${repo.baseBranch}\` ` +
|
|
1395
|
-
`then \`git diff origin/${repo.baseBranch}...HEAD\`) and score complexity, risk and impact.`,
|
|
1396
|
-
'',
|
|
1397
|
-
'Respond with ONLY a JSON object {"complexity":0.0,"risk":0.0,"impact":0.0,"rationale":"…"}.',
|
|
1398
|
-
].join('\n');
|
|
1399
|
-
}
|
|
1400
|
-
/**
|
|
1401
|
-
* The on-call agent's task prompt — the regression evidence (the generic block/prior-output
|
|
1402
|
-
* prompt) plus the locate-the-merged-commit guidance the bespoke harness `/on-call` handler
|
|
1403
|
-
* used to build. The released PR already merged into the base branch (its work branch is
|
|
1404
|
-
* gone), so the agent is on the base branch and is told how to find the merged commit.
|
|
1405
|
-
*/
|
|
1406
|
-
function onCallUserPrompt(context, repo) {
|
|
1407
|
-
const prNumber = context.block.pullRequest?.number;
|
|
1408
|
-
const headBranch = context.block.pullRequest?.branch;
|
|
1409
|
-
const pr = prNumber !== undefined ? `#${prNumber}` : '';
|
|
1410
|
-
const locate = prNumber
|
|
1411
|
-
? `It merged as a commit referencing ${pr} — find it with \`git log --oneline -n 50\` ` +
|
|
1412
|
-
`(squash/merge commits include \`(${pr})\`; a merge commit mentions \`#${prNumber}\`), then ` +
|
|
1413
|
-
`inspect it with \`git show <sha>\`.`
|
|
1414
|
-
: headBranch
|
|
1415
|
-
? `Its work branch was \`${headBranch}\` (now deleted) — find the merged commit in ` +
|
|
1416
|
-
`\`git log --oneline -n 50\` and inspect it with \`git show <sha>\`.`
|
|
1417
|
-
: `Find the most recent merge/feature commit with \`git log --oneline -n 50\` and inspect ` +
|
|
1418
|
-
`it with \`git show <sha>\`.`;
|
|
1419
|
-
return [
|
|
1420
|
-
userPromptFor(context, { materialized: true }),
|
|
1421
|
-
'',
|
|
1422
|
-
`You are on the base branch \`${repo.baseBranch}\`, which already contains the released ` +
|
|
1423
|
-
`pull request ${pr}. ${locate} Correlate that change with the regression evidence above. ` +
|
|
1424
|
-
`Beware correlation vs causation.`,
|
|
1425
|
-
'',
|
|
1426
|
-
'Respond with ONLY a JSON object {"culpritConfidence":0.0,"recommendation":"revert"|"hold"|"monitor","rationale":"…","evidence":["…"]}.',
|
|
1427
|
-
].join('\n');
|
|
1428
|
-
}
|
|
1429
|
-
/**
|
|
1430
|
-
* The tester's infra stand-up spec for the generic agent job, from the block's
|
|
1431
|
-
* `tester.environment` config + the resolved service: a `local` run carries the
|
|
1432
|
-
* docker-compose path (or the explicit no-infra flag) for the harness to stand the
|
|
1433
|
-
* dependencies up + tear them down around the run; an `ephemeral` run carries the
|
|
1434
|
-
* provisioned environment URL. Byte-identical to the old bespoke `/test` body's `test`
|
|
1435
|
-
* object — only the field name changed (`test` → `infra`).
|
|
1436
|
-
*/
|
|
1437
|
-
function testerInfraSpec(context) {
|
|
1438
|
-
const env = context.block.agentConfig?.['tester.environment'] === 'local' ? 'local' : 'ephemeral';
|
|
1439
|
-
const service = context.service;
|
|
1440
|
-
return {
|
|
1441
|
-
environment: env,
|
|
1442
|
-
...(env === 'local'
|
|
1443
|
-
? {
|
|
1444
|
-
noInfraDependencies: service?.noInfraDependencies === true,
|
|
1445
|
-
...(service?.testComposePath ? { composePath: service.testComposePath } : {}),
|
|
1446
|
-
}
|
|
1447
|
-
: {}),
|
|
1448
|
-
...(env === 'ephemeral' && context.environment?.url
|
|
1449
|
-
? { environmentUrl: context.environment.url }
|
|
1450
|
-
: {}),
|
|
1451
|
-
};
|
|
1452
|
-
}
|
|
1453
|
-
function prBody(context) {
|
|
1454
|
-
const lines = [
|
|
1455
|
-
`Automated implementation for block **${context.block.title}** (${context.block.type}).`,
|
|
1456
|
-
'',
|
|
1457
|
-
context.block.description || '(no description)',
|
|
1458
|
-
'',
|
|
1459
|
-
`Pipeline: ${context.pipelineName}`,
|
|
1460
|
-
];
|
|
1461
|
-
return lines.join('\n');
|
|
1462
|
-
}
|
|
1463
944
|
//# sourceMappingURL=ContainerAgentExecutor.js.map
|