@drisp/cli 0.7.5 → 0.7.7

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.
@@ -8,7 +8,7 @@ import {
8
8
  installWorkflowPlugins,
9
9
  resolveWorkflow,
10
10
  writeGlobalConfig
11
- } from "./chunk-UQWOGG3L.js";
11
+ } from "./chunk-HZ6GRLOM.js";
12
12
  import "./chunk-G3PMV62Z.js";
13
13
 
14
14
  // src/setup/steps/WorkflowInstallWizard.tsx
@@ -93,4 +93,4 @@ function WorkflowInstallWizard({ source, onDone }) {
93
93
  export {
94
94
  WorkflowInstallWizard as default
95
95
  };
96
- //# sourceMappingURL=WorkflowInstallWizard-4GRQGM42.js.map
96
+ //# sourceMappingURL=WorkflowInstallWizard-VVEEJW25.js.map
@@ -54,7 +54,7 @@ import {
54
54
  verifyRendered,
55
55
  wakesFreshAfterHandover,
56
56
  workflowRegistryDir
57
- } from "./chunk-UQWOGG3L.js";
57
+ } from "./chunk-HZ6GRLOM.js";
58
58
  import {
59
59
  __commonJS,
60
60
  __toESM
@@ -14892,7 +14892,7 @@ async function restoreCheckpoint(store, input) {
14892
14892
 
14893
14893
  // ../../modules/runtime/src/software-version.ts
14894
14894
  import "fs";
14895
- var runnerSoftwareVersion = true ? "0.7.5" : JSON.parse(readFileSync2(new URL("../../../apps/cli/package.json", import.meta.url), "utf8")).version;
14895
+ var runnerSoftwareVersion = true ? "0.7.7" : JSON.parse(readFileSync2(new URL("../../../apps/cli/package.json", import.meta.url), "utf8")).version;
14896
14896
 
14897
14897
  // ../../modules/runtime/src/connection.ts
14898
14898
  var RunnerDisconnectedError = class extends Error {
@@ -20176,7 +20176,33 @@ var fillSchema = external_exports.strictObject({
20176
20176
  areas: external_exports.array(external_exports.enum(AREAS)).min(1).refine((areas) => new Set(areas).size === areas.length, {
20177
20177
  error: "areas must be unique"
20178
20178
  }),
20179
- qa: external_exports.enum(["none", "web", "macos"]),
20179
+ qa: external_exports.enum(["none", "web", "macos", "both"]),
20180
+ profile: external_exports.enum(["implementation", "documentation", "investigation"]).optional(),
20181
+ rationale: external_exports.string().min(1).max(2e3).refine((text) => !TEMPLATE_STRUCTURE.test(text), {
20182
+ error: INSTRUCTIONS_MESSAGE
20183
+ }).optional(),
20184
+ acceptanceEvidence: external_exports.array(
20185
+ external_exports.string().min(1).max(2e3).refine((text) => !TEMPLATE_STRUCTURE.test(text), {
20186
+ error: INSTRUCTIONS_MESSAGE
20187
+ })
20188
+ ).min(1).optional(),
20189
+ testFirst: external_exports.strictObject({
20190
+ enabled: external_exports.boolean(),
20191
+ reason: external_exports.string().min(1).max(1e3).refine((text) => !TEMPLATE_STRUCTURE.test(text), {
20192
+ error: INSTRUCTIONS_MESSAGE
20193
+ })
20194
+ }).optional(),
20195
+ review: external_exports.strictObject({
20196
+ enabled: external_exports.boolean(),
20197
+ reason: external_exports.string().min(1).max(1e3).refine((text) => !TEMPLATE_STRUCTURE.test(text), {
20198
+ error: INSTRUCTIONS_MESSAGE
20199
+ })
20200
+ }).optional(),
20201
+ checks: external_exports.array(
20202
+ external_exports.string().min(1).max(500).regex(
20203
+ /^(?:npm (?:run|test|--prefix apps\/cli run)|npx vitest run|make (?:-C apps\/macos|check|typecheck)|git diff --check)(?: [a-zA-Z0-9_./:=@-]+)*$/
20204
+ )
20205
+ ).min(1).optional(),
20180
20206
  sizeClass: external_exports.enum(["S", "M", "L"]),
20181
20207
  instructions: external_exports.string().min(1).max(6e3).refine((text) => !TEMPLATE_STRUCTURE.test(text), {
20182
20208
  error: INSTRUCTIONS_MESSAGE
@@ -20211,6 +20237,9 @@ var planInputSchema = external_exports.strictObject({
20211
20237
  // Names the issue's directory under `.athena/workflows/<slugId>/`, so only a Linear identifier.
20212
20238
  identifier: external_exports.string().regex(/^[A-Z][A-Z0-9]*-[0-9]+$/),
20213
20239
  title: external_exports.string(),
20240
+ description: external_exports.string().optional(),
20241
+ acceptanceCriteria: external_exports.string().optional(),
20242
+ external: external_exports.boolean().optional(),
20214
20243
  url: external_exports.string().url(),
20215
20244
  branchName: external_exports.string().nullable(),
20216
20245
  state: external_exports.strictObject({
@@ -20240,7 +20269,12 @@ var SKIP_REASONS = [
20240
20269
  "ready-for-human",
20241
20270
  "merged",
20242
20271
  "done-pr-open",
20243
- "parent-only"
20272
+ "parent-only",
20273
+ "external",
20274
+ "needs-info",
20275
+ "needs-triage",
20276
+ "wontfix",
20277
+ "not-ready-for-agent"
20244
20278
  ];
20245
20279
  var count = external_exports.number().int().nonnegative();
20246
20280
  var issueEntrySchema = external_exports.strictObject({
@@ -20249,7 +20283,9 @@ var issueEntrySchema = external_exports.strictObject({
20249
20283
  title: external_exports.string(),
20250
20284
  blockedBy: external_exports.array(external_exports.string()),
20251
20285
  areas: external_exports.array(external_exports.enum(AREAS)).min(1),
20252
- qa: external_exports.enum(["none", "web", "macos"]),
20286
+ qa: external_exports.enum(["none", "web", "macos", "both"]),
20287
+ profile: external_exports.enum(["implementation", "documentation", "investigation"]).optional(),
20288
+ scopeFingerprint: external_exports.string().optional(),
20253
20289
  sizeClass: external_exports.enum(["S", "M", "L"]),
20254
20290
  tokenCap: external_exports.number().int().positive(),
20255
20291
  model: external_exports.string(),
@@ -20441,6 +20477,32 @@ function mergeGuidance(earlier, guidance) {
20441
20477
  return rounds.slice(-GUIDANCE_ROUNDS);
20442
20478
  }
20443
20479
 
20480
+ // src/core/build/eligibility.ts
20481
+ import { createHash as createHash3 } from "crypto";
20482
+ function eligibilityReason(issue) {
20483
+ if (issue.external) return "external";
20484
+ for (const label of [
20485
+ "ready-for-human",
20486
+ "needs-info",
20487
+ "needs-triage",
20488
+ "wontfix"
20489
+ ])
20490
+ if (issue.labels.includes(label)) return label;
20491
+ if (issue.state.type === "triage") return "needs-triage";
20492
+ if (!issue.labels.includes("ready-for-agent")) return "not-ready-for-agent";
20493
+ return void 0;
20494
+ }
20495
+ function scopeFingerprint(issue) {
20496
+ return createHash3("sha256").update(
20497
+ JSON.stringify([
20498
+ issue.title,
20499
+ issue.description ?? "",
20500
+ issue.acceptanceCriteria ?? "",
20501
+ issue.parentId
20502
+ ])
20503
+ ).digest("hex");
20504
+ }
20505
+
20444
20506
  // src/core/build/planner.ts
20445
20507
  function skipReason(issue, pullRequests) {
20446
20508
  const prs = issue.branchName ? pullRequests[issue.branchName] : void 0;
@@ -20449,6 +20511,8 @@ function skipReason(issue, pullRequests) {
20449
20511
  if (issue.state.type === "completed") return "done";
20450
20512
  if (issue.state.type === "canceled") return "canceled";
20451
20513
  if (prs?.merged) return "merged";
20514
+ const eligibility = eligibilityReason(issue);
20515
+ if (eligibility) return eligibility;
20452
20516
  return void 0;
20453
20517
  }
20454
20518
  var byIdentifier = (a, b) => a.localeCompare(b, "en", { numeric: true });
@@ -20492,6 +20556,7 @@ function shownFromFill(fill) {
20492
20556
  return {
20493
20557
  areas: fill.areas,
20494
20558
  qa: fill.qa,
20559
+ ...fill.profile ? { profile: fill.profile } : {},
20495
20560
  sizeClass: fill.sizeClass,
20496
20561
  tokenCap: limits.maxRunTokens,
20497
20562
  model: limits.model,
@@ -20590,11 +20655,21 @@ function planBuild(context) {
20590
20655
  ({ children, blockers } = blockingGraph(entries));
20591
20656
  }
20592
20657
  const held = new Set(
20593
- skipped.filter((skip) => skip.reason === "ready-for-human").map((skip) => skip.id)
20658
+ skipped.filter(
20659
+ (skip) => !["done", "merged", "done-pr-open", "parent-only"].includes(
20660
+ skip.reason
20661
+ )
20662
+ ).map((skip) => skip.id)
20594
20663
  );
20595
20664
  const waitsFor = (issue) => [
20596
20665
  .../* @__PURE__ */ new Set([
20597
- ...issue.blockedBy.filter((id2) => blockers.has(id2) || held.has(id2)),
20666
+ ...issue.blockedBy.filter(
20667
+ (id2) => !input.issues.some(
20668
+ (each) => each.id === id2 && (each.state.type === "completed" || skipped.some(
20669
+ (skip) => skip.id === id2 && skip.reason === "merged"
20670
+ ))
20671
+ )
20672
+ ),
20598
20673
  ...children.get(issue.id) ?? [],
20599
20674
  ...input.issues.filter((each) => each.parentId === issue.id && held.has(each.id)).map((each) => each.id)
20600
20675
  ])
@@ -20624,6 +20699,7 @@ function planBuild(context) {
20624
20699
  title: issue.title,
20625
20700
  blockedBy: waitsFor(issue),
20626
20701
  ...shownFromFill(fill),
20702
+ scopeFingerprint: scopeFingerprint(issue),
20627
20703
  workflowName: workflowName2
20628
20704
  });
20629
20705
  workflows.push({
@@ -21195,7 +21271,7 @@ async function closeOrphanedRuns(store, {
21195
21271
  }
21196
21272
 
21197
21273
  // ../../modules/runtime/src/run-artifacts.ts
21198
- import { createHash as createHash3 } from "crypto";
21274
+ import { createHash as createHash4 } from "crypto";
21199
21275
  import { mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
21200
21276
  import { tmpdir as tmpdir5 } from "os";
21201
21277
  import { dirname as dirname4, join as join7 } from "path";
@@ -21238,7 +21314,7 @@ function runArtifactEmitter(directory, record, midTurnMinGapMs = MID_TURN_MIN_GA
21238
21314
  } catch {
21239
21315
  continue;
21240
21316
  }
21241
- const hash = createHash3("sha256").update(bytes).digest("hex");
21317
+ const hash = createHash4("sha256").update(bytes).digest("hex");
21242
21318
  if (emitted[name] === hash) continue;
21243
21319
  let json;
21244
21320
  try {
@@ -21943,6 +22019,8 @@ export {
21943
22019
  repoLayoutAt,
21944
22020
  approvalAnswer,
21945
22021
  mergeGuidance,
22022
+ eligibilityReason,
22023
+ scopeFingerprint,
21946
22024
  planBuild,
21947
22025
  finalizePlan,
21948
22026
  fallbackBranch,
@@ -21967,4 +22045,4 @@ export {
21967
22045
  runMachineRunners,
21968
22046
  writeIdentityOnce
21969
22047
  };
21970
- //# sourceMappingURL=chunk-TG5EGPD2.js.map
22048
+ //# sourceMappingURL=chunk-D246FR4I.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  runMachineRunners
3
- } from "./chunk-TG5EGPD2.js";
3
+ } from "./chunk-D246FR4I.js";
4
4
 
5
5
  // src/app/entry/runnerDaemon.ts
6
6
  if (process.argv.includes("--help")) {
@@ -13,4 +13,4 @@ if (process.argv.includes("--help")) {
13
13
  process.exitCode = 1;
14
14
  });
15
15
  }
16
- //# sourceMappingURL=chunk-N4B7HLBN.js.map
16
+ //# sourceMappingURL=chunk-D5VZT7M3.js.map
@@ -21281,7 +21281,7 @@ var GATES = [
21281
21281
  ];
21282
21282
 
21283
21283
  // src/core/workflows/builtins/orchestrator/workflow.md
21284
- var workflow_default = '# Orchestrator: plan and run a Build\n\nYou plan and run a Drisp Build of one Plan: the open issues of one Linear project. The Run\'s prompt names the\nLinear project, the Plan key and the project folder, which is your working directory. You never write code here:\neach issue is built by its own child Run, which `drisp build tick` starts.\n\nThe Runner sets these for you:\n\n- `DRISP_RUN_ARTIFACTS`: this Run\'s artifact directory. Files you write there are shown to the member.\n- `DRISP_RUN_ID`, `DRISP_RUNNER_ID`, `DRISP_RUN_TOKEN`, `DRISP_HUB_URL`: this Run, its Runner and its right to start\n child Runs; `drisp build` reads them itself.\n\n## 1. Read Linear (first Turn)\n\nWith the `linear` MCP server, read the project and every issue in it, then write\n`$DRISP_RUN_ARTIFACTS/input/plan-input.json` (create the `input` directory) in exactly this shape. Every issue carries\nevery field, empty when it has none. `readAt` is the output of\n`node -e "console.log(new Date().toISOString())"` run just before you read Linear; never write a time yourself:\n\n```json\n{\n "version": 1,\n "readAt": "<the command\'s output, e.g. 2026-10-11T09:30:00.000Z>",\n "project": {\n "id": "<the project\'s `uuid`, not its `id`>",\n "name": "<the project\'s `name`>",\n "slugId": "<the last `-`-separated part of the project\'s `url`>"\n },\n "issues": [\n {\n "id": "<the issue\'s `uuid`, not its `id`>",\n "identifier": "<the issue\'s `id`, e.g. DRI-12>",\n "title": "<the issue\'s `title`>",\n "url": "<the issue\'s `url`>",\n "branchName": "<the issue\'s `gitBranchName`, or null>",\n "state": {\n "type": "<the issue\'s `statusType`: backlog|unstarted|started|completed|canceled|triage>",\n "name": "<the issue\'s `status`>"\n },\n "labels": ["<each of the issue\'s `labels`>"],\n "parentId": "<the parent issue\'s `uuid`, or null>",\n "blockedBy": ["<the `uuid` of each issue in `relations.blockedBy`>"],\n "prioritySortOrder": 0\n }\n ]\n}\n```\n\nLinear\'s MCP names fields differently from this file, and the difference matters: the app finds each issue\'s row by\nits UUID, so a plan that carries identifiers shows no Build state on any row.\n\n- The MCP\'s `id` is the readable identifier (`DRI-12`, or `P-DRI-7` for a project); the UUID is in `uuid`. Every\n `id`, `parentId` and `blockedBy` value in `plan-input.json` is a UUID (`7f7b03f7-accf-46b2-98fb-abadfed76594`).\n- An issue\'s `parentId` and `relations.blockedBy` from the MCP name issues by identifier. Look each one up: use the\n `uuid` of the project\'s issue with that identifier, or `get_issue` it when it is outside the project.\n- Read relations with `get_issue` and `includeRelations: true`; read the project\'s issues with `list_issues` and\n `fields` including `uuid`.\n- The project\'s `url` ends in its `slugId` (`\u2026/project/build-sandbox-b7640301cd64` \u2192 `b7640301cd64`).\n\n`drisp build plan` rejects a plan with an identifier where a UUID belongs and names the field to fix.\n\nInclude every issue of the project, done and canceled ones too: the plan decides what to skip. Then run:\n\n```sh\ndrisp build plan --input "$DRISP_RUN_ARTIFACTS/input/plan-input.json"\n```\n\nIt prints one JSON object. Act on its `status`.\n\n## 2. `needs_fills`\n\n`fills` lists issues that need a `fill.json` (`identifier`, `title`, `url`, `path`) and `templateVersion` is the value\neach fill must carry. For each listed issue: read it through `linear` (description, acceptance criteria, comments) and\nthe repo\'s ADRs and CONTEXT files it touches, then write its `fill.json` at `path`:\n\n```json\n{\n "version": 1,\n "templateVersion": "<templateVersion, copied exactly>",\n "areas": ["macos|web|cli|hub|protocol|docs", "\u2026"],\n "qa": "none|web|macos",\n "sizeClass": "S|M|L",\n "instructions": "<what the child needs beyond the issue>",\n "ownWork": true\n}\n```\n\n- `areas`: the parts of the repo the change touches, each once.\n- `qa`: `web` when the change shows in a web page, `macos` when it shows in the macOS app, else `none`.\n- `sizeClass`: `S` for a change of a file or two, `M` for a feature across a few modules, `L` for a large or risky\n change.\n- `instructions` (at most 6000 characters): start with one short paragraph saying what to build, then restate the\n acceptance criteria, name the ADRs it touches and the parts of the codebase involved. Plain prose and `-` lists\n only: no headings, code fences or HTML comments (`drisp build plan` rejects them).\n- `ownWork`: `false` only for an issue with sub-issues whose own description asks for nothing beyond them, such as\n a PRD above its slices; the plan then skips it and waits for its sub-issues instead. Otherwise `true`.\n\nThen run `drisp build plan` again with the same `--input` path.\n\n## 3. `planned` or `failed`\n\nOn `planned` the Build plan is written and shown to the member, who decides whether to start it: end the Turn with\n`<!-- AWAITING_APPROVAL -->` as the Journal\'s last line. On `failed` the reason is shown instead: end with\n`<!-- WORKFLOW_COMPLETE -->`.\n\n## 4. Every later wake: tick\n\nYou are woken when the member answers the approval question (with Start, or with words of their own before it) and whenever one of your child Runs closes or parks.\nEach time: read Linear again into `$DRISP_RUN_ARTIFACTS/input/plan-input.json` as in step 1 (with a new `readAt`),\nthen run:\n\n```sh\ndrisp build tick --input "$DRISP_RUN_ARTIFACTS/input/plan-input.json"\n```\n\nIt prints one JSON object. Act on its `action`, and repeat until an action ends the Turn:\n\n- `refresh_input`: read Linear into the input again (a newer `readAt`) and tick again.\n- `replan`: the member wrote words before starting, and `guidance` lists every round of them, oldest first. Plan again\n with them as the member\'s wishes: read the issues they concern (and the repo) as in step 2, rewrite the `fill.json`\n of each issue the guidance changes (`instructions`, `areas`, `qa`, `sizeClass`, or `ownWork` to leave an issue out),\n and run `drisp build plan` again with the same `--input` path. Then end as in step 3: with\n `<!-- AWAITING_APPROVAL -->` on `planned`, `<!-- WORKFLOW_COMPLETE -->` on `failed`. Write nothing but those\n `fill.json` files.\n- `repair`: for each of `issues`, read it in Linear first and skip an issue Linear now shows Done or Canceled (its\n child got there first); otherwise move it to Done and post its `comment` verbatim through `linear`. Then read\n Linear into the input again and tick again.\n- `rebase`: for each of `items`, in its `worktree`: `git rebase origin/main`, resolve the conflicts, then\n `git push --force-with-lease origin <branch>` (the item\'s `branch`) if the branch is on the remote. Never push any\n other branch. If a rebase cannot be resolved, `git rebase --abort`.\n Then read Linear into the input again and tick again, adding `--rebase-failed <identifier>` for each that failed.\n- `wait`: end the Turn with `<!-- WAITING_ON_CHILDREN -->` as the Journal\'s last line.\n- `park`: end the Turn with `<!-- NEEDS_HUMAN: <message> -->` as the Journal\'s last line, copying `message`\n verbatim.\n- `complete`: end the Turn with `<!-- WORKFLOW_COMPLETE -->` as the Journal\'s last line.\n\n## Rules\n\n- Never start, cancel or edit Runs, Workflows, worktrees or branches except as an action says; `drisp build` does\n what is needed.\n- Never write to Linear except a `repair`; never open a pull request.\n- Write only `plan-input.json` and the `fill.json` files `drisp build plan` asks for. Never write `answer.json`.\n';
21284
+ var workflow_default = '# Orchestrator: plan and run a Build\n\nYou plan and run a Drisp Build of one Plan: the open issues of one Linear project. The Run\'s prompt names the\nLinear project, the Plan key and the project folder, which is your working directory. You never write code here:\neach issue is built by its own child Run, which `drisp build tick` starts.\n\nThe Runner sets these for you:\n\n- `DRISP_RUN_ARTIFACTS`: this Run\'s artifact directory. Files you write there are shown to the member.\n- `DRISP_RUN_ID`, `DRISP_RUNNER_ID`, `DRISP_RUN_TOKEN`, `DRISP_HUB_URL`: this Run, its Runner and its right to start\n child Runs; `drisp build` reads them itself.\n\n## 1. Read Linear (first Turn)\n\nWith the `linear` MCP server, read the project and every issue in it, then write\n`$DRISP_RUN_ARTIFACTS/input/plan-input.json` (create the `input` directory) in exactly this shape. Every issue carries\nevery field, empty when it has none. `readAt` is the output of\n`node -e "console.log(new Date().toISOString())"` run just before you read Linear; never write a time yourself:\n\n```json\n{\n "version": 1,\n "readAt": "<the command\'s output, e.g. 2026-10-11T09:30:00.000Z>",\n "project": {\n "id": "<the project\'s `uuid`, not its `id`>",\n "name": "<the project\'s `name`>",\n "slugId": "<the last `-`-separated part of the project\'s `url`>"\n },\n "issues": [\n {\n "id": "<the issue\'s `uuid`, not its `id`>",\n "identifier": "<the issue\'s `id`, e.g. DRI-12>",\n "title": "<the issue\'s `title`>",\n "description": "<full current description>",\n "acceptanceCriteria": "<acceptance criteria, empty when included in description>",\n "external": false,\n "url": "<the issue\'s `url`>",\n "branchName": "<the issue\'s `gitBranchName`, or null>",\n "state": {\n "type": "<the issue\'s `statusType`: backlog|unstarted|started|completed|canceled|triage>",\n "name": "<the issue\'s `status`>"\n },\n "labels": ["<each of the issue\'s `labels`>"],\n "parentId": "<the parent issue\'s `uuid`, or null>",\n "blockedBy": ["<the `uuid` of each issue in `relations.blockedBy`>"],\n "prioritySortOrder": 0\n }\n ]\n}\n```\n\nLinear\'s MCP names fields differently from this file, and the difference matters: the app finds each issue\'s row by\nits UUID, so a plan that carries identifiers shows no Build state on any row.\n\n- The MCP\'s `id` is the readable identifier (`DRI-12`, or `P-DRI-7` for a project); the UUID is in `uuid`. Every\n `id`, `parentId` and `blockedBy` value in `plan-input.json` is a UUID (`7f7b03f7-accf-46b2-98fb-abadfed76594`).\n- An issue\'s `parentId` and `relations.blockedBy` from the MCP name issues by identifier. Look each one up: use the\n `uuid` of the project\'s issue with that identifier, or `get_issue` it when it is outside the project.\n- Read relations with `get_issue` and `includeRelations: true`; read the project\'s issues with `list_issues` and\n `fields` including `uuid`.\n- The project\'s `url` ends in its `slugId` (`\u2026/project/build-sandbox-b7640301cd64` \u2192 `b7640301cd64`).\n\n`drisp build plan` rejects a plan with an identifier where a UUID belongs and names the field to fix.\n\nAlso include every referenced blocker outside the project, with `external: true`, its current status and UUID. Follow blocker relations recursively; deduplicate by UUID. Never mark a project issue external. External issues are read only and never scheduled. Preserve actual prioritySortOrder when available.\n\nOnly ready-for-agent issues without conflicting ready-for-human, needs-info, needs-triage or wontfix labels are eligible. Do not invent or change labels to make an issue eligible.\n\nInclude every issue of the project, done and canceled ones too: the plan decides what to skip. Then run:\n\n```sh\ndrisp build plan --input "$DRISP_RUN_ARTIFACTS/input/plan-input.json"\n```\n\nIt prints one JSON object. Act on its `status`.\n\n## 2. `needs_fills`\n\n`fills` lists issues that need a `fill.json` (`identifier`, `title`, `url`, `path`) and `templateVersion` is the value\neach fill must carry. For each listed issue: read it through `linear` (description, acceptance criteria, comments) and\nthe repo\'s ADRs and CONTEXT files it touches, then write its `fill.json` at `path`:\n\n```json\n{\n "version": 1,\n "templateVersion": "<templateVersion, copied exactly>",\n "areas": ["macos|web|cli|hub|protocol|docs", "\u2026"],\n "qa": "none|web|macos|both",\n "profile": "implementation|documentation|investigation",\n "rationale": "<why these steps are appropriate>",\n "acceptanceEvidence": [\n "<each acceptance criterion and the evidence that demonstrates it>"\n ],\n "sizeClass": "S|M|L",\n "instructions": "<what the child needs beyond the issue>",\n "ownWork": true\n}\n```\n\n- `areas`: the parts of the repo the change touches, each once.\n- `profile`: implementation for behavior changes, documentation for prose-only repository changes, investigation for findings with no repository changes. Implementation defaults to TDD and two-reviewer code review; documentation and investigation omit these by default. Choose the profile from the actual deliverable, not the issue size.\n- Optional `testFirst` and `review`: `{ "enabled": true|false, "reason": "<issue-specific justification>" }`. Retain tests and review for behavioral changes; omit only when not applicable and explain why. These selections also control required skills.\n- Optional `checks`: a nonempty list of actual validation commands from the repo, using the supported npm, vitest, make or git diff command families. Read repository tooling and add the relevant test/build checks: typechecking alone does not prove behavior. If a necessary command is unsupported, state that limitation in instructions and require human attention, never claim validation passed.\n- `acceptanceEvidence`: cover every criterion, including the method and expected outcome.\n- `qa`: `both` when both interfaces change, `web` when the change shows in a web page, `macos` when it shows in the macOS app, else `none`.\n- `sizeClass`: `S` for a change of a file or two, `M` for a feature across a few modules, `L` for a large or risky\n change.\n- `instructions` (at most 6000 characters): start with one short paragraph saying what to build, then restate the\n acceptance criteria, name the ADRs it touches and the parts of the codebase involved. Plain prose and `-` lists\n only: no headings, code fences or HTML comments (`drisp build plan` rejects them).\n- `ownWork`: `false` only for an issue with sub-issues whose own description asks for nothing beyond them, such as\n a PRD above its slices; the plan then skips it and waits for its sub-issues instead. Otherwise `true`.\n\nThen run `drisp build plan` again with the same `--input` path.\n\n## 3. `planned` or `failed`\n\nOn `planned` the Build plan is written and shown to the member, who decides whether to start it: end the Turn with\n`<!-- AWAITING_APPROVAL -->` as the Journal\'s last line. On `failed` the reason is shown instead: end with\n`<!-- WORKFLOW_COMPLETE -->`.\n\n## 4. Every later wake: tick\n\nYou are woken when the member answers the approval question (with Start, or with words of their own before it) and whenever one of your child Runs closes or parks.\nEach time: read Linear again into `$DRISP_RUN_ARTIFACTS/input/plan-input.json` as in step 1 (with a new `readAt`),\nincluding full descriptions, acceptance criteria, labels and external blocker status. A scope change holds the affected issue for a fresh Build and approval. Then run:\n\n```sh\ndrisp build tick --input "$DRISP_RUN_ARTIFACTS/input/plan-input.json"\n```\n\nIt prints one JSON object. Act on its `action`, and repeat until an action ends the Turn:\n\n- `refresh_input`: read Linear into the input again (a newer `readAt`) and tick again.\n- `replan`: the member wrote words before starting, and `guidance` lists every round of them, oldest first. Plan again\n with them as the member\'s wishes: read the issues they concern (and the repo) as in step 2, rewrite the `fill.json`\n of each issue the guidance changes (`instructions`, `areas`, `qa`, `sizeClass`, or `ownWork` to leave an issue out),\n and run `drisp build plan` again with the same `--input` path. Then end as in step 3: with\n `<!-- AWAITING_APPROVAL -->` on `planned`, `<!-- WORKFLOW_COMPLETE -->` on `failed`. Write nothing but those\n `fill.json` files.\n- `repair`: for each of `issues`, read it in Linear first and skip an issue Linear now shows Done or Canceled (its\n child got there first); otherwise move it to Done and post its `comment` verbatim through `linear`. Then read\n Linear into the input again and tick again.\n- `rebase`: for each of `items`, in its `worktree`: `git rebase origin/main`, resolve the conflicts, then\n `git push --force-with-lease origin <branch>` (the item\'s `branch`) if the branch is on the remote. Never push any\n other branch. If a rebase cannot be resolved, `git rebase --abort`.\n Then read Linear into the input again and tick again, adding `--rebase-failed <identifier>` for each that failed.\n- `wait`: end the Turn with `<!-- WAITING_ON_CHILDREN -->` as the Journal\'s last line.\n- `park`: end the Turn with `<!-- NEEDS_HUMAN: <message> -->` as the Journal\'s last line, copying `message`\n verbatim.\n- `complete`: end the Turn with `<!-- WORKFLOW_COMPLETE -->` as the Journal\'s last line.\n\n## Rules\n\n- Never start, cancel or edit Runs, Workflows, worktrees or branches except as an action says; `drisp build` does\n what is needed.\n- Never write to Linear except a `repair`; never open a pull request.\n- Write only `plan-input.json` and the `fill.json` files `drisp build plan` asks for. Never write `answer.json`.\n';
21285
21285
 
21286
21286
  // src/core/workflows/builtins/orchestrator/index.ts
21287
21287
  var ORCHESTRATOR_WORKFLOW_NAME = "orchestrator";
@@ -21440,6 +21440,14 @@ Journal's last line.
21440
21440
 
21441
21441
  {instructions}
21442
21442
 
21443
+ ## Evidence receipts
21444
+
21445
+ Progress is not proof that a step passed. Follow each Finish instruction only after its acceptance condition holds.
21446
+ Record actual results, commands, output paths and acceptance criteria, never a generic claim of success.
21447
+ Commit repository changes before recording validation evidence. Changes to HEAD or source invalidate validation
21448
+ receipts: rerun affected tests, review, checks and QA, including after a rebase, before verifying the Merge step.
21449
+ Keep evidence files under .athena/evidence/ and do not commit them after validation, since a new commit invalidates receipts.
21450
+
21443
21451
  ## Gates
21444
21452
 
21445
21453
  Work through the gates in this order. Start each gate by running the command its "Start" line gives, which reports the
@@ -21464,10 +21472,17 @@ is in \`ignored\`: it does not block the merge and its name goes in the evidence
21464
21472
  empty, wait a minute and run it again. When \`failed\` is not empty, fix the change; if it still fails, the Merge gate
21465
21473
  cannot pass. Never judge a check by its timing.
21466
21474
 
21475
+ Immediately before merging, verify the current revision again: {mergeVerify}. If evidence is stale, rerun the
21476
+ selected validation steps and record fresh receipts before continuing.
21477
+
21467
21478
  Merge with \`gh pr merge --squash --delete-branch --match-head-commit <sha>\`, where \`<sha>\` is HEAD after the last
21468
21479
  push. When it exits non-zero, \`gh pr view --json state\` decides: \`MERGED\` means the merge happened (a worktree
21469
21480
  cannot check out main afterwards); anything else means it did not.
21470
21481
 
21482
+ After a merge, and only then, run \`drisp build merged <pr url>\` in this worktree: it records the merged pull request as the
21483
+ \`pull_request\` run artifact and exits non-zero if the pull request is not merged. Never run it before the merge or
21484
+ after a failed one; if it fails only because GitHub has not caught up, wait a few seconds and run it again.
21485
+
21471
21486
  ## Evidence comment
21472
21487
 
21473
21488
  Post exactly this on the Linear issue, filling the angle brackets:
@@ -21502,12 +21517,13 @@ No gate text: the loop's caps apply.`;
21502
21517
 
21503
21518
  // src/core/workflows/builtins/build-child/render.ts
21504
21519
  var RENDERER_RULES = {
21505
- alwaysSkills: ["tdd", "code-review"],
21520
+ alwaysSkills: [],
21521
+ profiles: ["implementation", "documentation", "investigation"],
21506
21522
  alwaysMcpServers: ["linear"],
21507
21523
  areaSkills: {
21508
21524
  macos: ["swiftui-expert-skill", "apple-docs", "apple-hig-components"]
21509
21525
  },
21510
- qaMcpServers: { web: ["drisp-browser"] },
21526
+ qaMcpServers: { web: ["drisp-browser"], both: ["drisp-browser"] },
21511
21527
  npmTestAreas: ["cli", "hub", "protocol"],
21512
21528
  /**
21513
21529
  * The Checks gate's commands, resolved from the fill's areas and the repo's layout at the plan's commit: a repo
@@ -21570,6 +21586,7 @@ var RENDERER_RULES = {
21570
21586
  shell: [
21571
21587
  "drisp build checks",
21572
21588
  "drisp build step",
21589
+ "drisp build merged",
21573
21590
  "gh pr checks",
21574
21591
  "gh pr create",
21575
21592
  "gh pr list",
@@ -21611,7 +21628,10 @@ var LAYOUT_MARKERS = [
21611
21628
  "apps/cli/package.json"
21612
21629
  ];
21613
21630
  function checkCommands(fill, layout) {
21631
+ if (fill.checks) return [...fill.checks];
21614
21632
  const rules = RENDERER_RULES.checks;
21633
+ if (fill.profile === "documentation" || fill.profile === "investigation")
21634
+ return [...rules.otherwise];
21615
21635
  if (layout.includes("Makefile"))
21616
21636
  return [
21617
21637
  ...rules.rootMakefile,
@@ -21630,7 +21650,11 @@ function checkCommands(fill, layout) {
21630
21650
  function installCommand(fill, layout) {
21631
21651
  const { install } = RENDERER_RULES.checks;
21632
21652
  if (!layout.includes(install.needs)) return void 0;
21633
- const needsNode = layout.includes("Makefile") || checkCommands(fill, layout).some((command) => /^np[mx] /.test(command));
21653
+ const needsNode = checkCommands(fill, layout).some(
21654
+ (command) => /^np[mx] /.test(command) || layout.includes("Makefile") && RENDERER_RULES.checks.rootMakefile.some(
21655
+ (rootCommand) => rootCommand === command
21656
+ )
21657
+ );
21634
21658
  return needsNode ? install.command : void 0;
21635
21659
  }
21636
21660
  var GENERATED_NAME = /^build--([a-z0-9]+)--([A-Z][A-Z0-9]*-[0-9]+)$/;
@@ -21648,6 +21672,8 @@ function renderedSkills(fill) {
21648
21672
  return [
21649
21673
  .../* @__PURE__ */ new Set([
21650
21674
  ...RENDERER_RULES.alwaysSkills,
21675
+ ...usesTestFirst(fill) ? ["tdd"] : [],
21676
+ ...usesReview(fill) ? ["code-review"] : [],
21651
21677
  ...fill.areas.flatMap((area) => RENDERER_RULES.areaSkills[area] ?? [])
21652
21678
  ])
21653
21679
  ].sort();
@@ -21660,8 +21686,24 @@ function renderedMcpServers(fill) {
21660
21686
  ])
21661
21687
  ].sort();
21662
21688
  }
21689
+ function shellCommands(fill) {
21690
+ const commands = [...RENDERER_RULES.permissions.shell];
21691
+ return fill.profile !== "investigation" ? commands : commands.filter(
21692
+ (command) => ![
21693
+ "drisp build merged",
21694
+ "gh pr create",
21695
+ "gh pr merge",
21696
+ "git add",
21697
+ "git commit",
21698
+ "git push",
21699
+ "git rebase",
21700
+ "git restore"
21701
+ ].includes(command)
21702
+ );
21703
+ }
21663
21704
  function renderedPermissions(fill) {
21664
- const { edit, shell, deny } = RENDERER_RULES.permissions;
21705
+ const { edit, deny } = RENDERER_RULES.permissions;
21706
+ const shell = shellCommands(fill);
21665
21707
  return {
21666
21708
  allow: [
21667
21709
  ...edit,
@@ -21671,10 +21713,29 @@ function renderedPermissions(fill) {
21671
21713
  deny: [...deny]
21672
21714
  };
21673
21715
  }
21674
- function gateTitles(fill) {
21675
- return GATES.filter((gate) => gate.title !== "QA" || fill.qa !== "none").map(
21676
- (gate) => gate.title
21716
+ var usesTestFirst = (fill) => fill.testFirst?.enabled ?? (!fill.profile || fill.profile === "implementation");
21717
+ var usesReview = (fill) => fill.review?.enabled ?? (!fill.profile || fill.profile === "implementation");
21718
+ function selectedGates(fill) {
21719
+ const gates = GATES.filter(
21720
+ (gate) => (gate.title !== "QA" || fill.qa !== "none") && (gate.title !== "Test first" || usesTestFirst(fill)) && (gate.title !== "Code review" || usesReview(fill)) && (fill.profile !== "investigation" || !["Pull request", "Merge"].includes(gate.title))
21721
+ ).map(
21722
+ (gate) => fill.profile === "investigation" && gate.title === "Checks" ? {
21723
+ ...gate,
21724
+ passesWhen: "{install}{checks} pass; each finding is supported by a source or reproducible observation and each acceptance criterion has evidence; unresolved questions are stated explicitly"
21725
+ } : fill.profile === "investigation" && gate.title === "Evidence" ? {
21726
+ ...gate,
21727
+ passesWhen: "findings and evidence for each acceptance criterion are posted on the Linear issue, with unresolved questions explicitly stated; the issue is moved to Done only when all acceptance criteria are satisfied"
21728
+ } : gate
21677
21729
  );
21730
+ if (!usesTestFirst(fill))
21731
+ gates.splice(1, 0, {
21732
+ title: fill.profile === "investigation" ? "Investigate" : "Implement",
21733
+ passesWhen: fill.profile === "investigation" ? "the question is investigated and findings address every acceptance criterion; no repository changes are made" : "the requested change is implemented and each acceptance criterion is checked"
21734
+ });
21735
+ return gates;
21736
+ }
21737
+ function gateTitles(fill) {
21738
+ return selectedGates(fill).map((gate) => gate.title);
21678
21739
  }
21679
21740
  function summarizeInstructions(instructions) {
21680
21741
  const paragraph = (instructions.trim().split(/\n\s*\n/)[0] ?? "").replace(/\s+/g, " ").trim();
@@ -21689,21 +21750,29 @@ function checksText(commands) {
21689
21750
  return quoted.length === 1 ? quoted[0] : `${quoted.slice(0, -1).join(", ")} and ${quoted.at(-1)}`;
21690
21751
  }
21691
21752
  var fillSlots = (template, slots) => template.replace(/\{(\w+)\}/g, (whole, key) => slots[key] ?? whole);
21753
+ function selectionFlags(fill) {
21754
+ return `${fill.profile ? ` --profile ${fill.profile}` : ""}${fill.testFirst ? ` --test-first ${fill.testFirst.enabled}` : ""}${fill.review ? ` --review ${fill.review.enabled}` : ""}`;
21755
+ }
21692
21756
  function renderStages(fill, checks, install) {
21693
- const gates = GATES.filter((gate) => gate.title !== "QA" || fill.qa !== "none");
21757
+ const gates = selectedGates(fill);
21694
21758
  return gates.map((gate, index) => {
21695
21759
  const passesWhen = fillSlots(gate.passesWhen, {
21696
21760
  install: install ? `after \`${install}\`, ` : "",
21697
21761
  checks: checksText(checks),
21698
- qa: fill.qa === "none" ? "" : QA_SECTIONS[fill.qa]
21762
+ qa: fill.qa === "none" ? "" : fill.qa === "both" ? `${QA_SECTIONS.web}; also ${QA_SECTIONS.macos}` : QA_SECTIONS[fill.qa]
21699
21763
  });
21764
+ const command = `drisp build step ${index + 1} --qa ${fill.qa}${selectionFlags(fill)}`;
21700
21765
  const stage = fillSlots(STAGE_TEMPLATE, {
21701
21766
  n: String(index + 1),
21702
21767
  title: gate.title,
21703
- stepCommand: `drisp build step ${index + 1} --qa ${fill.qa}`,
21768
+ stepCommand: command,
21704
21769
  passesWhen
21705
21770
  });
21706
- return stage + (gate.cannotPass ? fillSlots(CANNOT_PASS_TEMPLATE, { cannotPass: gate.cannotPass }) : NO_GATE_TEXT_TEMPLATE);
21771
+ return stage + (["Merge", "Evidence"].includes(gate.title) ? `
21772
+
21773
+ Before doing this step: run \`${command} --verify\`. Missing or stale evidence must be refreshed first.` : "") + `
21774
+
21775
+ Finish: write the actual result and supporting paths to \`.athena/evidence/step-${index + 1}.md\`, then run \`${command} --complete --evidence .athena/evidence/step-${index + 1}.md\`. This records an agent attestation, not independent verification.` + (gate.cannotPass ? fillSlots(CANNOT_PASS_TEMPLATE, { cannotPass: gate.cannotPass }) : NO_GATE_TEXT_TEMPLATE);
21707
21776
  }).join("\n\n");
21708
21777
  }
21709
21778
  function render(fill, workflowName, layout) {
@@ -21713,7 +21782,7 @@ function render(fill, workflowName, layout) {
21713
21782
  const limits = sizeClassLimits(fill.sizeClass);
21714
21783
  const workflow = {
21715
21784
  name: workflowName,
21716
- description: fillSlots(DESCRIPTION_TEMPLATE, {
21785
+ description: fill.profile === "investigation" ? `Investigates ${parsed.identifier} and delivers findings with acceptance evidence.` : fillSlots(DESCRIPTION_TEMPLATE, {
21717
21786
  identifier: parsed.identifier
21718
21787
  }),
21719
21788
  promptTemplate: "{input}",
@@ -21730,12 +21799,40 @@ function render(fill, workflowName, layout) {
21730
21799
  permissions: renderedPermissions(fill)
21731
21800
  };
21732
21801
  const checks = checkCommands(fill, layout);
21733
- const workflowMd = fillSlots(WORKFLOW_MD_TEMPLATE, {
21802
+ let template = WORKFLOW_MD_TEMPLATE;
21803
+ if (!usesReview(fill))
21804
+ template = template.replaceAll(
21805
+ "both reviewers pass;",
21806
+ "review is omitted by the approved plan;"
21807
+ ).replaceAll(
21808
+ "Review: Standards \u2713 \xB7 Spec \u2713",
21809
+ "Review: omitted by the approved plan"
21810
+ );
21811
+ if (fill.profile === "investigation") {
21812
+ template = template.replace(
21813
+ /## Merge gate[\s\S]*?## Linear/,
21814
+ "## Findings\n\nPost findings and acceptance evidence on the Linear issue. No pull request or merge is required.\n\n## Linear"
21815
+ );
21816
+ template = template.replace(
21817
+ "Your Linear writes are exactly: In Progress in Explore, `PR: <url>` in Pull request, the evidence comment and Done in\nEvidence.",
21818
+ "Your Linear writes are In Progress in Explore, findings and evidence, and Done in Evidence."
21819
+ );
21820
+ }
21821
+ const workflowMd = fillSlots(template, {
21734
21822
  identifier: parsed.identifier,
21735
- instructions: fill.instructions.trim(),
21823
+ instructions: [
21824
+ fill.instructions.trim(),
21825
+ fill.rationale ? `Plan rationale: ${fill.rationale}` : "",
21826
+ fill.testFirst ? `Test first: ${fill.testFirst.enabled ? "included" : "omitted"} \u2014 ${fill.testFirst.reason}` : "",
21827
+ fill.review ? `Code review: ${fill.review.enabled ? "included" : "omitted"} \u2014 ${fill.review.reason}` : "",
21828
+ ...(fill.acceptanceEvidence ?? []).map(
21829
+ (item) => `Acceptance evidence: ${item}`
21830
+ )
21831
+ ].filter(Boolean).join("\n\n"),
21736
21832
  gates: renderStages(fill, checks, installCommand(fill, layout)),
21833
+ mergeVerify: `\`drisp build step ${gateTitles(fill).indexOf("Merge") + 1} --qa ${fill.qa}${selectionFlags(fill)} --verify\``,
21737
21834
  checksEvidence: checks.map((command) => `${command} \u2713`).join(" \xB7 "),
21738
- shellCommands: checksText([...RENDERER_RULES.permissions.shell])
21835
+ shellCommands: checksText(shellCommands(fill))
21739
21836
  });
21740
21837
  return { workflowJson: `${JSON.stringify(workflow, null, 2)}
21741
21838
  `, workflowMd };
@@ -24250,4 +24347,4 @@ export {
24250
24347
  compileWorkflowPlan,
24251
24348
  collectMcpServersWithOptions
24252
24349
  };
24253
- //# sourceMappingURL=chunk-UQWOGG3L.js.map
24350
+ //# sourceMappingURL=chunk-HZ6GRLOM.js.map
package/dist/cli.js CHANGED
@@ -37,6 +37,7 @@ import {
37
37
  cursorToVisualPosition,
38
38
  defaultWorktreesRoot,
39
39
  detectClaudeVersion,
40
+ eligibilityReason,
40
41
  ensureBuildWorktree,
41
42
  ensureChildWorktree,
42
43
  expandHome,
@@ -126,6 +127,7 @@ import {
126
127
  runnerServiceUnit,
127
128
  runnerSoftwareVersion,
128
129
  runnerUpdateRequestSchema,
130
+ scopeFingerprint,
129
131
  serviceInstallationId,
130
132
  sessionsDir,
131
133
  sliceAnsi,
@@ -140,7 +142,7 @@ import {
140
142
  visualPositionToOffset,
141
143
  wrapText,
142
144
  writeIdentityOnce
143
- } from "./chunk-TG5EGPD2.js";
145
+ } from "./chunk-D246FR4I.js";
144
146
  import {
145
147
  generateId as generateId2
146
148
  } from "./chunk-PQIB66SG.js";
@@ -198,7 +200,7 @@ import {
198
200
  workflowRegistryDir,
199
201
  writeGlobalConfig,
200
202
  writeProjectConfig
201
- } from "./chunk-UQWOGG3L.js";
203
+ } from "./chunk-HZ6GRLOM.js";
202
204
  import {
203
205
  __toESM
204
206
  } from "./chunk-G3PMV62Z.js";
@@ -206,10 +208,10 @@ import {
206
208
  // src/app/entry/cli.tsx
207
209
  import { render } from "ink";
208
210
  import meow from "meow";
209
- import fs10 from "fs";
211
+ import fs11 from "fs";
210
212
  import { createRequire } from "module";
211
213
  import os5 from "os";
212
- import path10 from "path";
214
+ import path11 from "path";
213
215
  import { fileURLToPath } from "url";
214
216
 
215
217
  // src/app/shell/sessionUiState.ts
@@ -5328,14 +5330,14 @@ function extractWebSearch(_input, response) {
5328
5330
  return { type: "text", content: extractTextContent(response), maxLines: 10 };
5329
5331
  }
5330
5332
  function extractNotebookEdit(input, _response) {
5331
- const path11 = typeof input["notebook_path"] === "string" ? input["notebook_path"] : "";
5333
+ const path12 = typeof input["notebook_path"] === "string" ? input["notebook_path"] : "";
5332
5334
  const mode = typeof input["edit_mode"] === "string" ? input["edit_mode"] : "replace";
5333
5335
  const source = typeof input["new_source"] === "string" ? input["new_source"] : "";
5334
- if (!source) return { type: "text", content: `${mode} cell in ${path11}` };
5336
+ if (!source) return { type: "text", content: `${mode} cell in ${path12}` };
5335
5337
  return {
5336
5338
  type: "code",
5337
5339
  content: source,
5338
- language: detectLanguage(path11),
5340
+ language: detectLanguage(path12),
5339
5341
  maxLines: 10
5340
5342
  };
5341
5343
  }
@@ -5861,10 +5863,10 @@ function extractToolSubject(toolName, toolInput) {
5861
5863
  const parts = [];
5862
5864
  const pattern = str(input, "pattern");
5863
5865
  const glob = str(input, "glob");
5864
- const path11 = str(input, "path");
5866
+ const path12 = str(input, "path");
5865
5867
  if (pattern) parts.push(`"${pattern}"`);
5866
5868
  if (glob) parts.push(glob);
5867
- if (path11) parts.push(`in ${path11}`);
5869
+ if (path12) parts.push(`in ${path12}`);
5868
5870
  return parts.length > 0 ? parts.join(" ") : void 0;
5869
5871
  }
5870
5872
  case "Skill":
@@ -13095,7 +13097,7 @@ var thisMachine = (installationId) => ({
13095
13097
  os: `${platform()} ${release()}`.slice(0, 64),
13096
13098
  softwareVersion: runnerSoftwareVersion
13097
13099
  });
13098
- var post = (origin, path11, body) => fetch(new URL(path11, origin), {
13100
+ var post = (origin, path12, body) => fetch(new URL(path12, origin), {
13099
13101
  method: "POST",
13100
13102
  headers: { "Content-Type": "application/json" },
13101
13103
  body: JSON.stringify(body)
@@ -13114,8 +13116,8 @@ async function alreadyConnected(existing, { forTeam, ownIdentity }) {
13114
13116
  const problem = own ? await credentialAccepted(own.config) ? void 0 : "lapsed" : forTeam(existing.orgId).length ? "other_runner" : "missing";
13115
13117
  const runner = { orgId: existing.orgId, runnerId: existing.runnerId, name: existing.name };
13116
13118
  if (own && problem === void 0) return { status: "already_connected", ...runner, path: own.path };
13117
- const path11 = (own ?? forTeam(existing.orgId)[0])?.path;
13118
- return { status: "identity_unusable", ...runner, problem, ...path11 ? { path: path11 } : {} };
13119
+ const path12 = (own ?? forTeam(existing.orgId)[0])?.path;
13120
+ return { status: "identity_unusable", ...runner, problem, ...path12 ? { path: path12 } : {} };
13119
13121
  }
13120
13122
  async function connectWithCode({
13121
13123
  code,
@@ -13187,20 +13189,20 @@ async function saveIdentity(origin, credential, { single, forTeam }, directory)
13187
13189
  name: credential.name
13188
13190
  });
13189
13191
  const replaced = [];
13190
- for (const { path: path12 } of forTeam(credential.orgId)) {
13191
- const aside = `${path12}.replaced-${Date.now()}`;
13192
- await rename(path12, aside);
13192
+ for (const { path: path13 } of forTeam(credential.orgId)) {
13193
+ const aside = `${path13}.replaced-${Date.now()}`;
13194
+ await rename(path13, aside);
13193
13195
  replaced.push(aside);
13194
13196
  }
13195
- const path11 = single ?? teamIdentityPath(origin, credential.orgId, directory);
13197
+ const path12 = single ?? teamIdentityPath(origin, credential.orgId, directory);
13196
13198
  try {
13197
- await writeIdentityOnce(path11, JSON.stringify(config, null, 2) + "\n");
13199
+ await writeIdentityOnce(path12, JSON.stringify(config, null, 2) + "\n");
13198
13200
  } catch (error) {
13199
13201
  if (error.code === "EEXIST")
13200
- throw new Error(`Another Connect wrote a Runner identity for this Team at ${path11} meanwhile; it was left unchanged.`);
13202
+ throw new Error(`Another Connect wrote a Runner identity for this Team at ${path12} meanwhile; it was left unchanged.`);
13201
13203
  throw error;
13202
13204
  }
13203
- return { status: "connected", config, path: path11, replaced };
13205
+ return { status: "connected", config, path: path12, replaced };
13204
13206
  }
13205
13207
  function pathSegment(value) {
13206
13208
  const segment = encodeURIComponent(value);
@@ -13240,7 +13242,7 @@ async function refuseExisting(target) {
13240
13242
  throw new Error(`This machine already has a Runner${name ? ` (${name})` : ""} configured at ${target}.`);
13241
13243
  }
13242
13244
  async function disconnectRunner({
13243
- identity: { path: path11, config },
13245
+ identity: { path: path12, config },
13244
13246
  serviceManager,
13245
13247
  othersRemain
13246
13248
  }) {
@@ -13273,7 +13275,7 @@ async function disconnectRunner({
13273
13275
  await restore();
13274
13276
  throw new Error(`The Hub refused to disconnect ${name} (${body.error ?? response.status}); it is still connected.`);
13275
13277
  }
13276
- await forgetRunner(path11);
13278
+ await forgetRunner(path12);
13277
13279
  if (othersRemain) await restore();
13278
13280
  else await serviceManager?.remove();
13279
13281
  return result;
@@ -13426,18 +13428,18 @@ async function runRunnerCommandV2(input, options = {}) {
13426
13428
  `This machine is already connected to this Team as ${outcome.name}, but ${why}. ` + (code ? `The code was not used: connect a new Runner in its place with ${retry}. ` : `Connect a new Runner in its place with a Connect code from Settings \u2192 Runners: ${retry}. `) + `The old ${outcome.name} stays listed in Settings \u2192 Runners, offline.`
13427
13429
  );
13428
13430
  }
13429
- const { config, path: path11, replaced } = outcome;
13431
+ const { config, path: path12, replaced } = outcome;
13430
13432
  for (const aside of replaced)
13431
13433
  process.stdout.write(
13432
13434
  `The Hub no longer recognised this machine's earlier identity for this Team; it was moved aside to ${aside}.
13433
13435
  `
13434
13436
  );
13435
13437
  process.stdout.write(
13436
- `Connected as ${config.name ?? config.runnerId}. Identity saved to ${path11}.
13438
+ `Connected as ${config.name ?? config.runnerId}. Identity saved to ${path12}.
13437
13439
  `
13438
13440
  );
13439
13441
  if (services) {
13440
- const service = await ensureService(services, config, path11);
13442
+ const service = await ensureService(services, config, path12);
13441
13443
  process.stdout.write(`${serviceStarted(service)}
13442
13444
  `);
13443
13445
  return 0;
@@ -13446,7 +13448,7 @@ async function runRunnerCommandV2(input, options = {}) {
13446
13448
  process.stdout.write(
13447
13449
  "No launchd or systemd here to run the Runner as a background service; running it in the foreground instead.\n"
13448
13450
  );
13449
- await runConfiguredRunner(path11, options.signal);
13451
+ await runConfiguredRunner(path12, options.signal);
13450
13452
  return 0;
13451
13453
  }
13452
13454
  case "disconnect": {
@@ -13517,9 +13519,9 @@ async function runRunnerCommandV2(input, options = {}) {
13517
13519
  configured: true,
13518
13520
  // One entry per Team this machine serves; credentials are never printed.
13519
13521
  teams: identities.map(
13520
- ({ path: path11, config: { token: _token, ...config } }) => ({
13522
+ ({ path: path12, config: { token: _token, ...config } }) => ({
13521
13523
  ...config,
13522
- path: path11
13524
+ path: path12
13523
13525
  })
13524
13526
  ),
13525
13527
  service: service ? {
@@ -13615,7 +13617,7 @@ async function recoveryIdentity(runId) {
13615
13617
  }
13616
13618
 
13617
13619
  // src/app/entry/buildCommand.ts
13618
- import { execFileSync } from "child_process";
13620
+ import { execFileSync as execFileSync2 } from "child_process";
13619
13621
 
13620
13622
  // src/core/build/planCommand.ts
13621
13623
  import fs6 from "fs";
@@ -13755,7 +13757,7 @@ function runBuildPlan(deps) {
13755
13757
  const fixturePullRequests = fixtureDir ? readJson(path6.join(fixtureDir, "pull-requests.json")) : void 0;
13756
13758
  const pullRequests = fixturePullRequests ?? Object.fromEntries(
13757
13759
  input.issues.filter(
13758
- (issue) => issue.branchName && issue.state.type !== "canceled" && !issue.labels.includes("ready-for-human")
13760
+ (issue) => issue.branchName && issue.state.type !== "canceled" && !eligibilityReason(issue)
13759
13761
  ).map((issue) => [
13760
13762
  issue.branchName,
13761
13763
  deps.pullRequests(deps.folder, issue.branchName)
@@ -13864,9 +13866,9 @@ var DrispClient = class _DrispClient {
13864
13866
  if (!match) throw new Error("invalid_hub_url");
13865
13867
  return new _DrispClient(url.origin, decodeURIComponent(match[1]), async () => token);
13866
13868
  }
13867
- async request(path11, body) {
13869
+ async request(path12, body) {
13868
13870
  const response = await fetch(
13869
- `${this.baseUrl}/v2/orgs/${encodeURIComponent(this.orgId)}${path11}`,
13871
+ `${this.baseUrl}/v2/orgs/${encodeURIComponent(this.orgId)}${path12}`,
13870
13872
  {
13871
13873
  method: body === void 0 ? "GET" : "POST",
13872
13874
  headers: {
@@ -14266,8 +14268,22 @@ function occupies(view) {
14266
14268
  function assessTick(facts) {
14267
14269
  const { entries, orchestratorRunId } = facts;
14268
14270
  const state = structuredClone(facts.state);
14271
+ for (const [id, reason] of Object.entries(state.needsPerson))
14272
+ if (reason.startsWith("Issue is no longer eligible") || reason.startsWith("Waiting for prerequisite"))
14273
+ delete state.needsPerson[id];
14269
14274
  const views = new Map(facts.children.map((view) => [view.runId, view]));
14270
14275
  const issues = new Map(facts.issues.map((issue) => [issue.id, issue]));
14276
+ const parentOnly = new Set(
14277
+ facts.skipped?.filter((issue) => issue.reason === "parent-only").map((issue) => issue.id) ?? []
14278
+ );
14279
+ const expandBlocker = (id, seen = /* @__PURE__ */ new Set()) => {
14280
+ if (!parentOnly.has(id) || seen.has(id)) return [id];
14281
+ const children = facts.issues.filter((issue) => issue.parentId === id);
14282
+ if (!children.length) return [id];
14283
+ return children.flatMap(
14284
+ (issue) => expandBlocker(issue.id, /* @__PURE__ */ new Set([...seen, id]))
14285
+ );
14286
+ };
14271
14287
  const attemptsOf = (id) => state.attempts[id] ?? [];
14272
14288
  for (const entry of entries) {
14273
14289
  const prefix = `${orchestratorRunId}:${entry.identifier}:a`;
@@ -14310,7 +14326,7 @@ function assessTick(facts) {
14310
14326
  const status = /* @__PURE__ */ new Map();
14311
14327
  const skippedBlocker = (id) => {
14312
14328
  const linear = issues.get(id)?.state.type;
14313
- return linear === "completed" || linear === "canceled" ? "done" : "blocked";
14329
+ return linear === "completed" ? "done" : "blocked";
14314
14330
  };
14315
14331
  const repair = [];
14316
14332
  const repaired = new Set(state.repaired ?? []);
@@ -14321,7 +14337,7 @@ function assessTick(facts) {
14321
14337
  const linear = issues.get(entry.id)?.state.type;
14322
14338
  const statusOf = () => {
14323
14339
  if (linear === "completed" || linear === "canceled") return "done";
14324
- const prs = prsOf(entry);
14340
+ const prs = entry.profile === "investigation" ? void 0 : prsOf(entry);
14325
14341
  if (prs?.merged) {
14326
14342
  if (!repaired.has(entry.id) && !(newest && occupies(view))) {
14327
14343
  const prUrl = prs.mergedUrl ?? branchOf(entry);
@@ -14336,18 +14352,36 @@ function assessTick(facts) {
14336
14352
  return "done";
14337
14353
  }
14338
14354
  if (newest && (!view || !view.closed)) return "live";
14355
+ const current = issues.get(entry.id);
14356
+ if (!current || eligibilityReason(current)) {
14357
+ state.needsPerson[entry.id] = `Issue is no longer eligible (${current ? eligibilityReason(current) : "missing from Linear"}). Update Linear, then Build again.`;
14358
+ return "needs_person";
14359
+ }
14360
+ if (entry.scopeFingerprint && entry.scopeFingerprint !== scopeFingerprint(current)) {
14361
+ state.needsPerson[entry.id] = "Issue scope changed after approval. Build again to review the revised plan.";
14362
+ return "needs_person";
14363
+ }
14339
14364
  if (newest?.outcome === "template_mismatch") return "replan";
14340
14365
  if (newest?.outcome === "completed")
14341
- state.needsPerson[entry.id] ??= BUILD_STRINGS.completedUnmerged;
14366
+ state.needsPerson[entry.id] ??= entry.profile === "investigation" ? "The investigation ended without marking its accepted findings Done in Linear. Review its evidence." : BUILD_STRINGS.completedUnmerged;
14342
14367
  if (state.needsPerson[entry.id] || newest && !mayRetry(attempts))
14343
14368
  return "needs_person";
14344
- const blockers = entry.blockedBy.map(
14345
- (id) => status.get(id) ?? skippedBlocker(id)
14346
- );
14369
+ const blockers = [
14370
+ .../* @__PURE__ */ new Set([
14371
+ ...entry.blockedBy,
14372
+ ...current.blockedBy.flatMap((id) => expandBlocker(id))
14373
+ ])
14374
+ ].map((id) => {
14375
+ if (issues.get(id)?.state.type === "canceled") return "blocked";
14376
+ return status.get(id) ?? (entries.some((each) => each.id === id) && issues.get(id)?.state.type !== "completed" ? "pending" : skippedBlocker(id));
14377
+ });
14347
14378
  if (blockers.some(
14348
14379
  (b) => b === "needs_person" || b === "replan" || b === "blocked"
14349
- ))
14380
+ )) {
14381
+ if (current.blockedBy.some((id) => !entry.blockedBy.includes(id)))
14382
+ state.needsPerson[entry.id] = "Waiting for prerequisite work. Resolve its readiness, completion or cancellation in Linear, then resume.";
14350
14383
  return "blocked";
14384
+ }
14351
14385
  return blockers.every((b) => b === "done") ? "ready" : "pending";
14352
14386
  };
14353
14387
  status.set(entry.id, statusOf());
@@ -14399,6 +14433,12 @@ function tickAction(input) {
14399
14433
  const done = input.entries.filter(
14400
14434
  (entry) => input.status.get(entry.id) === "done"
14401
14435
  ).length;
14436
+ if (done !== input.entries.length)
14437
+ return {
14438
+ action: "park",
14439
+ reason: "attention",
14440
+ message: `Built ${done} of ${input.entries.length}. Remaining issues are blocked or need attention. Resolve the issue or dependency, then resume; Build again if the approved scope changed.`
14441
+ };
14402
14442
  return {
14403
14443
  action: "complete",
14404
14444
  message: `Built ${done} of ${input.entries.length}`
@@ -14523,6 +14563,7 @@ async function runBuildTick(deps) {
14523
14563
  const entries = plan.groups.flatMap((group) => group.issues).filter((entry) => approved.has(entry.id));
14524
14564
  const issues = new Map(input.issues.map((issue) => [issue.id, issue]));
14525
14565
  const assessment = assessTick({
14566
+ skipped: plan.skipped,
14526
14567
  orchestratorRunId,
14527
14568
  entries,
14528
14569
  issues: input.issues,
@@ -14725,8 +14766,16 @@ function evaluateRemoteChecks(runs) {
14725
14766
  }
14726
14767
 
14727
14768
  // src/core/build/step.ts
14769
+ import { execFileSync } from "child_process";
14770
+ import { createHash as createHash2 } from "crypto";
14728
14771
  import fs8 from "fs";
14729
14772
  import path8 from "path";
14773
+ var receiptSchema = external_exports.strictObject({
14774
+ title: external_exports.string().min(1),
14775
+ revision: external_exports.string().regex(/^[0-9a-f]{64}$/),
14776
+ evidence: external_exports.string().min(1).max(16e3),
14777
+ kind: external_exports.literal("agent-attestation")
14778
+ });
14730
14779
  var QA_KINDS = fillSchema.shape.qa.options;
14731
14780
  function runBuildStep(input) {
14732
14781
  const directory = requireEnv(input.env, "DRISP_RUN_ARTIFACTS");
@@ -14735,7 +14784,22 @@ function runBuildStep(input) {
14735
14784
  throw new BuildUsageError(
14736
14785
  `--qa must be one of ${QA_KINDS.join(", ")}, not "${qa}".`
14737
14786
  );
14738
- const titles = gateTitles({ qa });
14787
+ const parsedProfile = fillSchema.shape.profile.safeParse(input.profile);
14788
+ if (!parsedProfile.success)
14789
+ throw new BuildUsageError("Unknown workflow profile.");
14790
+ const profile = parsedProfile.data;
14791
+ const selection = (value) => {
14792
+ if (value === void 0) return void 0;
14793
+ if (value !== "true" && value !== "false")
14794
+ throw new BuildUsageError("Step selection must be true or false.");
14795
+ return { enabled: value === "true", reason: "Approved workflow" };
14796
+ };
14797
+ const titles = gateTitles({
14798
+ qa,
14799
+ profile,
14800
+ testFirst: selection(input.testFirst),
14801
+ review: selection(input.review)
14802
+ });
14739
14803
  const index = /^[0-9]+$/.test(input.n ?? "") ? Number(input.n) : 0;
14740
14804
  if (index < 1 || index > titles.length)
14741
14805
  throw new BuildUsageError(
@@ -14745,10 +14809,56 @@ function runBuildStep(input) {
14745
14809
  throw new BuildUsageError(
14746
14810
  `DRISP_RUN_ARTIFACTS (${directory}) is not a directory.`
14747
14811
  );
14812
+ const receiptsPath = path8.join(directory, "gate_evidence.json");
14813
+ let receipts = [];
14814
+ if (fs8.existsSync(receiptsPath)) {
14815
+ try {
14816
+ receipts = external_exports.strictObject({ receipts: external_exports.array(receiptSchema).max(100) }).parse(JSON.parse(fs8.readFileSync(receiptsPath, "utf8"))).receipts;
14817
+ } catch {
14818
+ throw new BuildUsageError("Gate evidence is invalid.");
14819
+ }
14820
+ }
14821
+ const revision = receipts.length || input.complete || input.verify ? workRevision(input.cwd ?? process.cwd()) : "";
14822
+ const fresh = (receipt) => ["Explore", "Pull request", "Evidence", "Merge"].includes(receipt.title) || receipt.revision === revision;
14823
+ if (input.verify || input.complete && titles[index - 1] === "Evidence") {
14824
+ const missing = titles.slice(0, index - 1).filter((title) => !receipts.some((r) => r.title === title && fresh(r)));
14825
+ if (missing.length)
14826
+ throw new BuildUsageError(
14827
+ `Missing or stale evidence: ${missing.join(", ")}. Repeat those steps on the current revision.`
14828
+ );
14829
+ }
14830
+ if (input.complete) {
14831
+ if (!input.evidence)
14832
+ throw new BuildUsageError(
14833
+ "--complete requires --evidence <file> containing the result and supporting paths."
14834
+ );
14835
+ let evidence;
14836
+ try {
14837
+ evidence = fs8.readFileSync(
14838
+ path8.resolve(input.cwd ?? process.cwd(), input.evidence),
14839
+ "utf8"
14840
+ ).trim();
14841
+ } catch {
14842
+ throw new BuildUsageError("Cannot read the evidence file.");
14843
+ }
14844
+ if (!evidence || evidence.length > 16e3)
14845
+ throw new BuildUsageError("Evidence must contain 1\u201316000 characters.");
14846
+ receipts = receipts.filter((r) => r.title !== titles[index - 1]);
14847
+ receipts.push({
14848
+ title: titles[index - 1],
14849
+ revision,
14850
+ evidence,
14851
+ kind: "agent-attestation"
14852
+ });
14853
+ fs8.writeFileSync(receiptsPath, JSON.stringify({ receipts }) + "\n");
14854
+ }
14748
14855
  const artifact = {
14749
14856
  index,
14750
14857
  title: titles[index - 1],
14751
- total: titles.length
14858
+ total: titles.length,
14859
+ ...receipts.length ? {
14860
+ completed: receipts.filter(fresh).map((r) => titles.indexOf(r.title) + 1).filter((n) => n > 0)
14861
+ } : {}
14752
14862
  };
14753
14863
  fs8.writeFileSync(
14754
14864
  path8.join(directory, "step.json"),
@@ -14757,19 +14867,96 @@ function runBuildStep(input) {
14757
14867
  );
14758
14868
  return artifact;
14759
14869
  }
14870
+ function workRevision(cwd) {
14871
+ const git2 = (...args) => execFileSync("git", args, {
14872
+ cwd,
14873
+ encoding: "utf8",
14874
+ stdio: ["ignore", "pipe", "pipe"]
14875
+ });
14876
+ try {
14877
+ const hash = createHash2("sha256").update(git2("rev-parse", "HEAD")).update(git2("diff", "HEAD", "--", ".", ":!.athena/evidence"));
14878
+ for (const name of git2("ls-files", "--others", "--exclude-standard", "-z").split("\0").filter(Boolean).sort()) {
14879
+ if (name.startsWith(".athena/evidence/")) continue;
14880
+ hash.update(name).update(fs8.readFileSync(path8.join(cwd, name)));
14881
+ }
14882
+ return hash.digest("hex");
14883
+ } catch {
14884
+ throw new BuildUsageError("Evidence must be recorded in a git worktree.");
14885
+ }
14886
+ }
14887
+
14888
+ // src/core/build/merged.ts
14889
+ import fs9 from "fs";
14890
+ import path9 from "path";
14891
+ var pullRequestArtifactSchema = external_exports.strictObject({
14892
+ url: external_exports.url(),
14893
+ number: external_exports.number().int().positive(),
14894
+ sha: external_exports.string().regex(/^[0-9a-f]{40}$/),
14895
+ mergedAt: external_exports.iso.datetime()
14896
+ });
14897
+ function runBuildMerged(input) {
14898
+ const directory = requireEnv(input.env, "DRISP_RUN_ARTIFACTS");
14899
+ if (!fs9.statSync(directory, { throwIfNoEntry: false })?.isDirectory())
14900
+ throw new BuildUsageError(
14901
+ `DRISP_RUN_ARTIFACTS (${directory}) is not a directory.`
14902
+ );
14903
+ let view;
14904
+ try {
14905
+ view = JSON.parse(
14906
+ input.gh(
14907
+ [
14908
+ "pr",
14909
+ "view",
14910
+ ...input.pullRequest ? [input.pullRequest] : [],
14911
+ "--json",
14912
+ "state,url,number,mergeCommit,mergedAt"
14913
+ ],
14914
+ input.cwd
14915
+ )
14916
+ );
14917
+ } catch {
14918
+ throw new BuildUsageError("gh pr view did not report this pull request.");
14919
+ }
14920
+ if (view.state !== "MERGED")
14921
+ throw new BuildUsageError(
14922
+ `This pull request is not merged (state ${String(view.state)}).`
14923
+ );
14924
+ const parsed = pullRequestArtifactSchema.safeParse({
14925
+ url: view.url,
14926
+ number: view.number,
14927
+ sha: view.mergeCommit?.oid,
14928
+ mergedAt: view.mergedAt
14929
+ });
14930
+ if (!parsed.success)
14931
+ throw new BuildUsageError(
14932
+ `The merged pull request has a bad shape: ${parsed.error.issues.map((issue) => issue.path.join(".")).join(", ")}.`
14933
+ );
14934
+ fs9.writeFileSync(
14935
+ path9.join(directory, "pull_request.json"),
14936
+ `${JSON.stringify(parsed.data)}
14937
+ `
14938
+ );
14939
+ return parsed.data;
14940
+ }
14760
14941
 
14761
14942
  // src/app/entry/buildCommand.ts
14762
14943
  var USAGE3 = `Usage: drisp build plan --input <path>
14763
14944
  drisp build tick --input <path> [--rebase-failed <IDENTIFIER>]...
14764
14945
  drisp build checks
14765
- drisp build step <n> [--qa none|web|macos]
14946
+ drisp build step <n> [--qa none|web|macos|both] [--profile implementation|documentation|investigation]
14947
+ [--test-first true|false] [--review true|false] [--complete --evidence <file>] [--verify]
14948
+ drisp build merged [<pr url or number>]
14766
14949
  plan and tick run inside an Orchestrator Run (DRISP_RUN_ID, DRISP_RUNNER_ID and DRISP_RUN_ARTIFACTS set; tick also
14767
14950
  DRISP_RUN_TOKEN and DRISP_HUB_URL). DRISP_BUILD_PLAN_INPUT=<path> stands in for plan's --input, with
14768
14951
  fills/<IDENTIFIER>.json beside it. checks runs in a child's worktree: the merge gate's verdict on HEAD's GitHub runs.
14769
14952
  step runs in any Run with DRISP_RUN_ARTIFACTS set: it writes the step artifact {index, title, total} for gate <n> of the
14770
- build-child template (QA is a gate unless --qa none) and refuses an index outside the list.`;
14953
+ selected build-child profile (QA is a gate unless --qa none) and refuses an index outside the list.
14954
+ --complete --evidence <file> records an agent attestation bound to the source revision; --verify requires fresh
14955
+ receipts for all preceding steps. Keep evidence files under .athena/evidence/.
14956
+ merged runs in a child's worktree after its pull request merged: it writes the pull_request artifact
14957
+ {url, number, sha, mergedAt} and refuses a pull request that is not merged.`;
14771
14958
  function githubPullRequests(folder, branch) {
14772
- const output = execFileSync(
14959
+ const output = execFileSync2(
14773
14960
  "gh",
14774
14961
  [
14775
14962
  "pr",
@@ -14808,7 +14995,7 @@ function hostCapabilities(folder) {
14808
14995
  }
14809
14996
  var defaultChecks = {
14810
14997
  head: (cwd) => git(cwd, ["rev-parse", "HEAD"]),
14811
- gh: (args, cwd) => execFileSync("gh", args, {
14998
+ gh: (args, cwd) => execFileSync2("gh", args, {
14812
14999
  cwd,
14813
15000
  encoding: "utf8",
14814
15001
  stdio: ["ignore", "pipe", "pipe"]
@@ -14885,10 +15072,25 @@ async function runBuildCommand(input) {
14885
15072
  case "checks":
14886
15073
  output = runBuildChecks(input.cwd, input.checks ?? defaultChecks);
14887
15074
  break;
15075
+ case "merged":
15076
+ output = runBuildMerged({
15077
+ env: env2,
15078
+ cwd: input.cwd,
15079
+ ...input.positional?.[0] ? { pullRequest: input.positional[0] } : {},
15080
+ gh: (input.checks ?? defaultChecks).gh
15081
+ });
15082
+ break;
14888
15083
  case "step":
14889
15084
  output = runBuildStep({
14890
15085
  env: env2,
14891
15086
  n: input.positional?.[0],
15087
+ cwd: input.cwd,
15088
+ profile: input.flags.profile,
15089
+ testFirst: input.flags.testFirst,
15090
+ review: input.flags.review,
15091
+ complete: input.flags.complete,
15092
+ verify: input.flags.verify,
15093
+ evidence: input.flags.evidence,
14892
15094
  ...input.flags.qa ? { qa: input.flags.qa } : {}
14893
15095
  });
14894
15096
  break;
@@ -14913,10 +15115,10 @@ ${USAGE3}
14913
15115
  import process7 from "process";
14914
15116
 
14915
15117
  // src/harnesses/claude/system/doctorEnvironment.ts
14916
- import { execFileSync as execFileSync2 } from "child_process";
14917
- import * as fs9 from "fs";
15118
+ import { execFileSync as execFileSync3 } from "child_process";
15119
+ import * as fs10 from "fs";
14918
15120
  import * as os4 from "os";
14919
- import * as path9 from "path";
15121
+ import * as path10 from "path";
14920
15122
  var MANAGED_POLICY_KEYS = [
14921
15123
  "forceLoginMethod",
14922
15124
  "forceLoginOrgUUID",
@@ -14991,7 +15193,7 @@ function readEnforcement(scopes) {
14991
15193
  }
14992
15194
  function tryRichAuth(claudeBinary) {
14993
15195
  try {
14994
- const output = execFileSync2(claudeBinary, ["auth", "status"], {
15196
+ const output = execFileSync3(claudeBinary, ["auth", "status"], {
14995
15197
  timeout: 1e4,
14996
15198
  encoding: "utf-8",
14997
15199
  stdio: ["ignore", "pipe", "pipe"]
@@ -15020,8 +15222,8 @@ function collectEnvironment(options = {}) {
15020
15222
  const detectVersion = options.detectClaudeVersionFn ?? detectClaudeVersion;
15021
15223
  const runAuthStatus = options.runClaudeAuthStatusFn ?? runClaudeAuthStatus;
15022
15224
  const resolveForwarder = options.resolveHookForwarderCommandFn ?? resolveHookForwarderCommand;
15023
- const statFn = options.statFn ?? ((p) => fs9.statSync(p));
15024
- const readFileFn = options.readFileFn ?? ((p) => fs9.readFileSync(p, "utf8"));
15225
+ const statFn = options.statFn ?? ((p) => fs10.statSync(p));
15226
+ const readFileFn = options.readFileFn ?? ((p) => fs10.readFileSync(p, "utf8"));
15025
15227
  const settingsPaths = resolveClaudeSettingsSurfacePaths(
15026
15228
  homeDir,
15027
15229
  cwd,
@@ -15066,7 +15268,7 @@ function collectEnvironment(options = {}) {
15066
15268
  });
15067
15269
  let globalConfigPresent = false;
15068
15270
  try {
15069
- statFn(path9.join(homeDir, ".claude.json"));
15271
+ statFn(path10.join(homeDir, ".claude.json"));
15070
15272
  globalConfigPresent = true;
15071
15273
  } catch {
15072
15274
  globalConfigPresent = false;
@@ -15546,13 +15748,13 @@ function runRunsCommand(input) {
15546
15748
  // src/app/entry/cli.tsx
15547
15749
  import { jsx as jsx24 } from "react/jsx-runtime";
15548
15750
  function resolvePackageJsonPath(entryUrl) {
15549
- let currentDir = path10.dirname(fileURLToPath(entryUrl));
15751
+ let currentDir = path11.dirname(fileURLToPath(entryUrl));
15550
15752
  for (; ; ) {
15551
- const candidatePath = path10.join(currentDir, "package.json");
15552
- if (fs10.existsSync(candidatePath)) {
15753
+ const candidatePath = path11.join(currentDir, "package.json");
15754
+ if (fs11.existsSync(candidatePath)) {
15553
15755
  return candidatePath;
15554
15756
  }
15555
- const parentDir = path10.dirname(currentDir);
15757
+ const parentDir = path11.dirname(currentDir);
15556
15758
  if (parentDir === currentDir) {
15557
15759
  throw new Error("Could not locate package.json for CLI metadata.");
15558
15760
  }
@@ -15724,7 +15926,8 @@ var cli = meow(
15724
15926
  build plan --input <path> Plan a Build inside an Orchestrator Run (prints one JSON object)
15725
15927
  build tick --input <path> Run one step of a started Build (prints the next action; [--rebase-failed <ID>]...)
15726
15928
  build checks The merge gate's verdict on HEAD's GitHub runs (in a child's worktree)
15727
- build step <n> Report the Run's current step: writes the step run artifact ([--qa none|web|macos])
15929
+ build step <n> Report the Run's current step: writes the step run artifact ([--qa none|web|macos|both], --profile, --complete --evidence, --verify)
15930
+ build merged Record the merged pull request: writes the pull_request run artifact
15728
15931
  telemetry [action] Manage anonymous telemetry (enable/disable/status)
15729
15932
  doctor Diagnose Claude headless setup (use with --harness=claude)
15730
15933
 
@@ -15862,6 +16065,12 @@ var cli = meow(
15862
16065
  type: "string",
15863
16066
  isMultiple: true
15864
16067
  },
16068
+ profile: { type: "string" },
16069
+ testFirst: { type: "string" },
16070
+ review: { type: "string" },
16071
+ complete: { type: "boolean" },
16072
+ verify: { type: "boolean" },
16073
+ evidence: { type: "string" },
15865
16074
  qa: {
15866
16075
  type: "string"
15867
16076
  },
@@ -15904,7 +16113,7 @@ var cli = meow(
15904
16113
  }
15905
16114
  );
15906
16115
  async function main() {
15907
- const projectDir = path10.resolve(cli.flags.projectDir);
16116
+ const projectDir = path11.resolve(cli.flags.projectDir);
15908
16117
  const [command, ...commandArgs] = cli.input;
15909
16118
  const isRunCommand = command === "run" || command === "exec";
15910
16119
  if (command && !KNOWN_COMMANDS.has(command)) {
@@ -15958,7 +16167,7 @@ Available commands: ${[...KNOWN_COMMANDS].join(", ")}`
15958
16167
  await exitWith(1);
15959
16168
  return;
15960
16169
  }
15961
- const { default: WorkflowInstallWizard } = await import("./WorkflowInstallWizard-4GRQGM42.js");
16170
+ const { default: WorkflowInstallWizard } = await import("./WorkflowInstallWizard-VVEEJW25.js");
15962
16171
  const { waitUntilExit } = render(
15963
16172
  /* @__PURE__ */ jsx24(
15964
16173
  WorkflowInstallWizard,
@@ -16044,6 +16253,12 @@ Available commands: ${[...KNOWN_COMMANDS].join(", ")}`
16044
16253
  positional: buildArgs,
16045
16254
  flags: {
16046
16255
  ...typeof cli.flags.qa === "string" ? { qa: cli.flags.qa } : {},
16256
+ profile: cli.flags.profile,
16257
+ testFirst: cli.flags.testFirst,
16258
+ review: cli.flags.review,
16259
+ complete: cli.flags.complete,
16260
+ verify: cli.flags.verify,
16261
+ evidence: cli.flags.evidence,
16047
16262
  ...typeof cli.flags.input === "string" ? { input: cli.flags.input } : {},
16048
16263
  ...cli.flags.rebaseFailed?.length ? { rebaseFailed: cli.flags.rebaseFailed } : {}
16049
16264
  },
@@ -16125,8 +16340,8 @@ Available commands: ${[...KNOWN_COMMANDS].join(", ")}`
16125
16340
  const showSetup = isRunCommand ? false : shouldShowSetup({
16126
16341
  cliInput: cli.input,
16127
16342
  setupComplete: globalConfig.setupComplete,
16128
- globalConfigExists: fs10.existsSync(
16129
- path10.join(os5.homedir(), ".config", "athena", "config.json")
16343
+ globalConfigExists: fs11.existsSync(
16344
+ path11.join(os5.homedir(), ".config", "athena", "config.json")
16130
16345
  )
16131
16346
  });
16132
16347
  let runtimeConfig;
@@ -1,6 +1,6 @@
1
- import "./chunk-N4B7HLBN.js";
2
- import "./chunk-TG5EGPD2.js";
1
+ import "./chunk-D5VZT7M3.js";
2
+ import "./chunk-D246FR4I.js";
3
3
  import "./chunk-PQIB66SG.js";
4
- import "./chunk-UQWOGG3L.js";
4
+ import "./chunk-HZ6GRLOM.js";
5
5
  import "./chunk-G3PMV62Z.js";
6
6
  //# sourceMappingURL=dashboard-daemon.js.map
package/dist/runner.js CHANGED
@@ -1,6 +1,6 @@
1
- import "./chunk-N4B7HLBN.js";
2
- import "./chunk-TG5EGPD2.js";
1
+ import "./chunk-D5VZT7M3.js";
2
+ import "./chunk-D246FR4I.js";
3
3
  import "./chunk-PQIB66SG.js";
4
- import "./chunk-UQWOGG3L.js";
4
+ import "./chunk-HZ6GRLOM.js";
5
5
  import "./chunk-G3PMV62Z.js";
6
6
  //# sourceMappingURL=runner.js.map
package/package.json CHANGED
@@ -17,7 +17,7 @@
17
17
  "hooks",
18
18
  "dashboard"
19
19
  ],
20
- "version": "0.7.5",
20
+ "version": "0.7.7",
21
21
  "license": "MIT",
22
22
  "bin": {
23
23
  "athena": "dist/cli.js",