tines 0.0.195 → 0.0.196

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.
Files changed (3) hide show
  1. package/README.md +27 -0
  2. package/dist/index.js +1559 -215
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3124,14 +3124,14 @@ Expecting one of '${allowedValues.join("', '")}'`);
3124
3124
  */
3125
3125
  helpInformation(contextOptions) {
3126
3126
  const helper = this.createHelp();
3127
- const context = this._getOutputContext(contextOptions);
3127
+ const context2 = this._getOutputContext(contextOptions);
3128
3128
  helper.prepareContext({
3129
- error: context.error,
3130
- helpWidth: context.helpWidth,
3131
- outputHasColors: context.hasColors
3129
+ error: context2.error,
3130
+ helpWidth: context2.helpWidth,
3131
+ outputHasColors: context2.hasColors
3132
3132
  });
3133
3133
  const text4 = helper.formatHelp(this, helper);
3134
- if (context.hasColors) return text4;
3134
+ if (context2.hasColors) return text4;
3135
3135
  return this._outputConfiguration.stripColor(text4);
3136
3136
  }
3137
3137
  /**
@@ -3299,15 +3299,15 @@ Expecting one of '${allowedValues.join("', '")}'`);
3299
3299
  Expecting one of '${allowedValues.join("', '")}'`);
3300
3300
  }
3301
3301
  const helpEvent = `${position}Help`;
3302
- this.on(helpEvent, (context) => {
3302
+ this.on(helpEvent, (context2) => {
3303
3303
  let helpStr;
3304
3304
  if (typeof text4 === "function") {
3305
- helpStr = text4({ error: context.error, command: context.command });
3305
+ helpStr = text4({ error: context2.error, command: context2.command });
3306
3306
  } else {
3307
3307
  helpStr = text4;
3308
3308
  }
3309
3309
  if (helpStr) {
3310
- context.write(`${helpStr}
3310
+ context2.write(`${helpStr}
3311
3311
  `);
3312
3312
  }
3313
3313
  });
@@ -3401,6 +3401,12 @@ function readBodyValue(value, stdin = processStdin) {
3401
3401
  }
3402
3402
 
3403
3403
  // ../shared/src/types.ts
3404
+ var STATE_CATEGORIES = [
3405
+ "backlog",
3406
+ "active",
3407
+ "awaiting_human",
3408
+ "done"
3409
+ ];
3404
3410
  function runRefLabel(run) {
3405
3411
  return run.issue_ref ? `run on ${run.issue_ref.project_name}/${run.issue_ref.number}` : `run ${run.run_id}`;
3406
3412
  }
@@ -3421,7 +3427,9 @@ var LABEL_COLORS = [
3421
3427
  "violet",
3422
3428
  "pink"
3423
3429
  ];
3430
+ var LABEL_NAME_MAX = 50;
3424
3431
  var PROMPT_MAX_BYTES = 32 * 1024;
3432
+ var SKILL_MAX_FILES = 20;
3425
3433
  var SKILL_MAX_TOTAL_BYTES = 100 * 1024;
3426
3434
  var AGENT_GUIDELINES_NAME = "agent-guidelines";
3427
3435
  var JOURNAL_NAME = "journal";
@@ -3451,6 +3459,7 @@ function repoDirFromUrl(url) {
3451
3459
  return "repo";
3452
3460
  return base;
3453
3461
  }
3462
+ var ARTIFACT_TYPES = ["file", "text", "link", "pr", "folder"];
3454
3463
  var ARTIFACT_FILE_MAX_BYTES = 25 * 1024 * 1024;
3455
3464
  var ARTIFACT_TEXT_MAX_BYTES = 256 * 1024;
3456
3465
  var ARTIFACT_FOLDER_MAX_BYTES = 50 * 1024 * 1024;
@@ -3464,13 +3473,13 @@ function canonicalGitHubRepoUrl(url) {
3464
3473
  function parsePrSpec(spec) {
3465
3474
  const short = spec.trim().match(/^([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)#(\d+)$/);
3466
3475
  if (short) {
3467
- const repoUrl2 = canonicalGitHubRepoUrl(`https://github.com/${short[1]}`);
3468
- return repoUrl2 ? { repo_url: repoUrl2, number: Number.parseInt(short[2], 10) } : null;
3476
+ const repoUrl3 = canonicalGitHubRepoUrl(`https://github.com/${short[1]}`);
3477
+ return repoUrl3 ? { repo_url: repoUrl3, number: Number.parseInt(short[2], 10) } : null;
3469
3478
  }
3470
3479
  const url = spec.trim().match(/^(.*?)\/pull\/(\d+)(?:[/?#].*)?$/);
3471
3480
  if (!url) return null;
3472
- const repoUrl = canonicalGitHubRepoUrl(url[1]);
3473
- return repoUrl ? { repo_url: repoUrl, number: Number.parseInt(url[2], 10) } : null;
3481
+ const repoUrl2 = canonicalGitHubRepoUrl(url[1]);
3482
+ return repoUrl2 ? { repo_url: repoUrl2, number: Number.parseInt(url[2], 10) } : null;
3474
3483
  }
3475
3484
  var MODEL_TIERS = ["smartest", "balanced", "cheapest"];
3476
3485
  var RUNNER_ONLINE_WINDOW_MS = 2 * 60 * 1e3;
@@ -3840,12 +3849,12 @@ function declaredContentType(reqs, type) {
3840
3849
  // ../shared/src/artifact-site.ts
3841
3850
  var ARTIFACT_SITE_LINK_TTL_MS = 60 * 60 * 1e3;
3842
3851
  var ARTIFACT_SITE_INDEX = "index.html";
3843
- function siteEntry(type, contentType, files = []) {
3852
+ function siteEntry(type, contentType, files2 = []) {
3844
3853
  if (type === "file" || type === "text") {
3845
3854
  return (contentType ?? "").toLowerCase().startsWith("text/html") ? "" : null;
3846
3855
  }
3847
3856
  if (type === "folder") {
3848
- return files.some((f) => f.path === ARTIFACT_SITE_INDEX) ? ARTIFACT_SITE_INDEX : null;
3857
+ return files2.some((f) => f.path === ARTIFACT_SITE_INDEX) ? ARTIFACT_SITE_INDEX : null;
3849
3858
  }
3850
3859
  return null;
3851
3860
  }
@@ -4076,12 +4085,12 @@ function createApiClient(options) {
4076
4085
  /** JSON upsert for text/link/pr: creates the artifact or appends a version. */
4077
4086
  putArtifact: (issueId, name2, body) => request("PUT", artifactPath(issueId, name2), body),
4078
4087
  /** Raw-body upload for `file`: creates the artifact or appends a version. */
4079
- uploadArtifactFile: async (issueId, name2, bytes, opts) => {
4088
+ uploadArtifactFile: async (issueId, name2, bytes2, opts) => {
4080
4089
  const res = await raw(
4081
4090
  "PUT",
4082
4091
  artifactPath(issueId, name2, `/file?filename=${encodeURIComponent(opts.filename)}`),
4083
4092
  {
4084
- body: bytes,
4093
+ body: bytes2,
4085
4094
  headers: { "content-type": opts.contentType }
4086
4095
  }
4087
4096
  );
@@ -4091,9 +4100,9 @@ function createApiClient(options) {
4091
4100
  * Multipart snapshot upload for `folder`: every file of the new version
4092
4101
  * in one request (path as the part filename, MIME as the part type).
4093
4102
  */
4094
- uploadArtifactFolder: async (issueId, name2, files) => {
4103
+ uploadArtifactFolder: async (issueId, name2, files2) => {
4095
4104
  const form = new FormData();
4096
- for (const file of files) {
4105
+ for (const file of files2) {
4097
4106
  form.append(
4098
4107
  "file",
4099
4108
  new Blob([file.bytes], { type: file.contentType }),
@@ -4416,10 +4425,10 @@ var INHERIT_RUNNER_ID = "*";
4416
4425
 
4417
4426
  // ../shared/src/issue-transfer.ts
4418
4427
  var compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
4419
- function conflictsByKey(context) {
4420
- const repositories = new Map(context.repos.map((repo) => [repo.item_id, repo]));
4428
+ function conflictsByKey(context2) {
4429
+ const repositories = new Map(context2.repos.map((repo) => [repo.item_id, repo]));
4421
4430
  return new Map(
4422
- context.conflicts.map((conflict) => {
4431
+ context2.conflicts.map((conflict) => {
4423
4432
  const ids = [...new Set(conflict.item_ids)].sort(compare);
4424
4433
  const key = JSON.stringify([conflict.dir, ids]);
4425
4434
  return [
@@ -4460,6 +4469,8 @@ function deriveTransferConflictDeltas(before, after) {
4460
4469
  }
4461
4470
 
4462
4471
  // ../shared/src/library/types.ts
4472
+ var LIBRARY_V3_MAX_DEPTH = 64;
4473
+ var LIBRARY_V3_MAX_RECORDS = 1e3;
4463
4474
  var LibraryValidationError = class extends Error {
4464
4475
  constructor(diagnostics) {
4465
4476
  super(diagnostics[0]?.message ?? "Invalid library document");
@@ -4508,6 +4519,11 @@ var oneOf = (values) => (v, p) => {
4508
4519
  var nullable = (check) => (v, p) => {
4509
4520
  if (v !== null) check(v, p);
4510
4521
  };
4522
+ var array = (check, max = 1e3) => (v, p) => {
4523
+ if (!Array.isArray(v)) invalid(p, "invalid_type", "Expected an array");
4524
+ if (v.length > max) invalid(p, "package_too_large", `Array exceeds ${max} entries`);
4525
+ for (let i = 0; i < v.length; i++) check(v[i], pointer(p, i));
4526
+ };
4511
4527
  function object(v, p) {
4512
4528
  if (!v || typeof v !== "object" || Array.isArray(v) || ![null, Object.prototype].includes(Object.getPrototypeOf(v)))
4513
4529
  invalid(p, "invalid_type", "Expected a plain object");
@@ -4539,14 +4555,166 @@ var id = (v, p) => {
4539
4555
  if (!/^[A-Za-z0-9:_-]{1,80}$/.test(v))
4540
4556
  invalid(p, "invalid_local_id", "Invalid document-local ID");
4541
4557
  };
4558
+ var labelName = (v, p) => {
4559
+ text2(LABEL_NAME_MAX, true)(v, p);
4560
+ const name2 = v.trim();
4561
+ if (name2.startsWith("-") || /[\u0000-\u001f\u007f]/.test(name2))
4562
+ invalid(
4563
+ p,
4564
+ "invalid_field",
4565
+ "Label names cannot start with a hyphen or contain control characters"
4566
+ );
4567
+ };
4568
+ var slug = (v, p) => {
4569
+ text2(100, true)(v, p);
4570
+ if (!/^[a-z0-9-]+$/.test(v))
4571
+ invalid(p, "invalid_field", "Expected a slug ([a-z0-9-]+)");
4572
+ };
4542
4573
  var bundledState = shape({ kind: oneOf(["bundled_state"]), state_id: id });
4543
4574
  var systemState = shape({
4544
4575
  kind: oneOf(["system_state"]),
4545
4576
  workflow: oneOf(["Standard"]),
4546
4577
  state_name: text2(100, true)
4547
4578
  });
4579
+ var stateRef = (library) => discriminated("kind", {
4580
+ bundled_state: bundledState,
4581
+ ...library ? { system_state: systemState } : {}
4582
+ });
4548
4583
  var bundledWorkflow = shape({ kind: oneOf(["bundled_workflow"]), workflow_id: id });
4549
4584
  var projectRef = shape({ kind: oneOf(["input_project"]), input_id: id });
4585
+ var requirement = (v, p) => {
4586
+ shape(
4587
+ { artifact: slug },
4588
+ { type: oneOf(ARTIFACT_TYPES), content_type: text2(100, true), description: text2(500) }
4589
+ )(v, p);
4590
+ const o = v;
4591
+ if (o.content_type !== void 0 && o.type !== "file" && o.type !== "text")
4592
+ invalid(pointer(p, "content_type"), "invalid_field", "content_type requires type file or text");
4593
+ };
4594
+ var workflow = (library) => shape({
4595
+ id,
4596
+ name: text2(200, true),
4597
+ description: text2(1e4),
4598
+ initial_state_id: id,
4599
+ states: array(
4600
+ shape({
4601
+ id,
4602
+ name: text2(100, true),
4603
+ category: oneOf(STATE_CATEGORIES),
4604
+ inherits_from: nullable(stateRef(library))
4605
+ })
4606
+ ),
4607
+ transitions: array(
4608
+ shape({
4609
+ id,
4610
+ name: text2(100, true),
4611
+ from_state_id: id,
4612
+ to_state_id: id,
4613
+ requires: array(requirement)
4614
+ })
4615
+ )
4616
+ });
4617
+ var workspacePath = (v, p) => {
4618
+ text2(500, true)(v, p);
4619
+ const path2 = v;
4620
+ if (path2.includes("\\") || path2.includes("=") || path2.split("/").some((s) => !s || s === "." || s === ".."))
4621
+ invalid(
4622
+ p,
4623
+ "invalid_path",
4624
+ "Expected a relative workspace path without empty, dot or parent segments, backslashes or equals"
4625
+ );
4626
+ };
4627
+ var bytes = (v) => new TextEncoder().encode(v).byteLength;
4628
+ var prompt = (v, p) => {
4629
+ text2(PROMPT_MAX_BYTES)(v, p);
4630
+ if (bytes(v) > PROMPT_MAX_BYTES)
4631
+ invalid(p, "package_too_large", `Prompt exceeds ${PROMPT_MAX_BYTES} UTF-8 bytes`);
4632
+ };
4633
+ var files = (v, p) => {
4634
+ array(shape({ id, path: workspacePath, content: text2(SKILL_MAX_TOTAL_BYTES) }), SKILL_MAX_FILES)(
4635
+ v,
4636
+ p
4637
+ );
4638
+ const entries = v;
4639
+ const seen = /* @__PURE__ */ new Set();
4640
+ let size = 0;
4641
+ for (const [i, file] of entries.entries()) {
4642
+ if (seen.has(file.path))
4643
+ invalid(pointer(pointer(p, i), "path"), "duplicate_path", "Duplicate skill file path");
4644
+ seen.add(file.path);
4645
+ size += bytes(file.path) + bytes(file.content);
4646
+ }
4647
+ if (size > SKILL_MAX_TOTAL_BYTES)
4648
+ invalid(p, "package_too_large", `Skill exceeds ${SKILL_MAX_TOTAL_BYTES} UTF-8 bytes`);
4649
+ };
4650
+ var repoUrl = (v, p) => {
4651
+ text2(1e3, true)(v, p);
4652
+ let url;
4653
+ try {
4654
+ url = new URL(v);
4655
+ } catch {
4656
+ invalid(p, "invalid_repo_url", "Expected a GitHub HTTPS repository URL");
4657
+ }
4658
+ if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash || !canonicalGitHubRepoUrl(v))
4659
+ invalid(
4660
+ p,
4661
+ "invalid_repo_url",
4662
+ "Expected a GitHub HTTPS repository URL without credentials, query or fragment"
4663
+ );
4664
+ };
4665
+ var context = (library) => {
4666
+ const scope = library ? {
4667
+ scope: shape({}, { project_id: id, state: stateRef(true), label_id: id }),
4668
+ journal: bool
4669
+ } : { state_id: id };
4670
+ const common = {
4671
+ id,
4672
+ name: text2(100, true),
4673
+ description: text2(1e3),
4674
+ ...scope
4675
+ };
4676
+ return (v, p) => {
4677
+ discriminated("kind", {
4678
+ prompt: shape({ ...common, kind: oneOf(["prompt"]), body: prompt }),
4679
+ skill: shape({ ...common, name: slug, kind: oneOf(["skill"]), files }),
4680
+ repo: shape({
4681
+ ...common,
4682
+ kind: oneOf(["repo"]),
4683
+ // Whole-library transfer preserves ordinary repo declarations, including
4684
+ // local/file URLs. Workflow packages are shareable and stay GitHub-only.
4685
+ repo_url: library ? text2(1e3, true) : repoUrl,
4686
+ repo_branch: nullable(text2(200, true)),
4687
+ repo_dir: nullable(workspacePath)
4688
+ })
4689
+ })(v, p);
4690
+ const o = v;
4691
+ if (library && o.journal && o.kind !== "prompt")
4692
+ invalid(pointer(p, "journal"), "invalid_field", "Only prompts may be journals");
4693
+ };
4694
+ };
4695
+ var input = (v, p) => {
4696
+ shape(
4697
+ {
4698
+ id,
4699
+ key: text2(64, true),
4700
+ type: oneOf(["text", "workflow", "label", "project"]),
4701
+ label: text2(200, true),
4702
+ description: text2(1e3),
4703
+ required: bool,
4704
+ default: nullable(text2(1e4))
4705
+ },
4706
+ { required_states: array(text2(100, true)) }
4707
+ )(v, p);
4708
+ const o = v;
4709
+ if (!/^[a-z][a-z0-9_]{0,63}$/.test(o.key))
4710
+ invalid(pointer(p, "key"), "invalid_input_key", "Invalid input key");
4711
+ if (o.required_states !== void 0 && o.type !== "workflow")
4712
+ invalid(
4713
+ pointer(p, "required_states"),
4714
+ "invalid_field",
4715
+ "Only workflow inputs may require states"
4716
+ );
4717
+ };
4550
4718
  var preset = discriminated("kind", {
4551
4719
  hourly: shape({ kind: oneOf(["hourly"]), every_hours: integer }, { minute: integer }),
4552
4720
  daily: shape({ kind: oneOf(["daily"]), time: text2(5, true) }),
@@ -4585,6 +4753,695 @@ var schedule = shape({
4585
4753
  require_all_closed: bool,
4586
4754
  start_state: nullable(bundledState)
4587
4755
  });
4756
+ function validateLibraryV3Shape(value) {
4757
+ const envelope = { format: oneOf(["tines.library"]), version: oneOf([3]), exported_at: integer };
4758
+ const digest = (v, p) => {
4759
+ text2(71)(v, p);
4760
+ if (v !== "" && !/^sha256:[0-9a-f]{64}$/.test(v))
4761
+ invalid(p, "invalid_digest", "Expected sha256 and 64 lowercase hex digits");
4762
+ };
4763
+ discriminated("profile", {
4764
+ workflow: shape(
4765
+ {
4766
+ ...envelope,
4767
+ profile: oneOf(["workflow"]),
4768
+ main_workflow_id: id,
4769
+ workflows: array(workflow(false)),
4770
+ context: array(context(false)),
4771
+ inputs: array(input),
4772
+ text_uses: array(
4773
+ shape({
4774
+ id,
4775
+ target: shape({
4776
+ record_id: id,
4777
+ field: oneOf([
4778
+ "description",
4779
+ "body",
4780
+ "content",
4781
+ "title_template",
4782
+ "description_template"
4783
+ ])
4784
+ }),
4785
+ input_id: id,
4786
+ token: text2(30100, true)
4787
+ })
4788
+ ),
4789
+ schedules: array(schedule),
4790
+ routing: array(
4791
+ shape({
4792
+ id,
4793
+ scope: shape({ state_id: id }, { project: projectRef }),
4794
+ tier: oneOf(MODEL_TIERS)
4795
+ })
4796
+ )
4797
+ },
4798
+ { digest }
4799
+ ),
4800
+ library: shape(
4801
+ {
4802
+ ...envelope,
4803
+ profile: oneOf(["library"]),
4804
+ workflows: array(workflow(true)),
4805
+ context: array(context(true)),
4806
+ projects: array(
4807
+ shape({
4808
+ id,
4809
+ name: text2(200, true),
4810
+ description: text2(1e4),
4811
+ default_workflow: nullable(
4812
+ discriminated("kind", {
4813
+ bundled_workflow: bundledWorkflow,
4814
+ system_workflow: shape({
4815
+ kind: oneOf(["system_workflow"]),
4816
+ name: oneOf(["Standard"])
4817
+ })
4818
+ })
4819
+ )
4820
+ })
4821
+ ),
4822
+ labels: array(shape({ id, name: labelName, color: oneOf(LABEL_COLORS) }))
4823
+ },
4824
+ { digest }
4825
+ )
4826
+ })(value, "");
4827
+ }
4828
+
4829
+ // ../shared/src/library/inputs.ts
4830
+ function inputToken(key, defaultValue) {
4831
+ const escaped = (defaultValue ?? "").replaceAll("\\", "\\\\").replaceAll(":", "\\:").replaceAll("}", "\\}");
4832
+ return `{{${key}:${escaped}}}`;
4833
+ }
4834
+ function renderDeclaredTokens(source, declarations) {
4835
+ const tokens = /* @__PURE__ */ new Set();
4836
+ for (const { token } of declarations) {
4837
+ if (!token || tokens.has(token))
4838
+ throw new LibraryValidationError([
4839
+ {
4840
+ path: "/text_uses",
4841
+ code: "duplicate_text_use",
4842
+ message: "Tokens must be non-empty and unique within a field"
4843
+ }
4844
+ ]);
4845
+ tokens.add(token);
4846
+ }
4847
+ const matches = [];
4848
+ declarations.forEach(({ token }, index) => {
4849
+ for (let offset2 = 0; offset2 < source.length; ) {
4850
+ const start = source.indexOf(token, offset2);
4851
+ if (start < 0) break;
4852
+ matches.push({
4853
+ start,
4854
+ end: start + token.length,
4855
+ index,
4856
+ escaped: start > 0 && source[start - 1] === "\\"
4857
+ });
4858
+ offset2 = start + 1;
4859
+ }
4860
+ });
4861
+ matches.sort((a, b) => a.start - b.start || a.end - b.end);
4862
+ for (let i = 1; i < matches.length; i++) {
4863
+ if (matches[i].start < matches[i - 1].end)
4864
+ throw new LibraryValidationError([
4865
+ {
4866
+ path: "/text_uses",
4867
+ code: "overlapping_text_use",
4868
+ message: "Declared token occurrences overlap"
4869
+ }
4870
+ ]);
4871
+ }
4872
+ let value = "";
4873
+ let offset = 0;
4874
+ const counts = declarations.map(() => 0);
4875
+ for (const match of matches) {
4876
+ value += source.slice(offset, match.escaped ? match.start - 1 : match.start);
4877
+ value += match.escaped ? declarations[match.index].token : declarations[match.index].value;
4878
+ if (!match.escaped) counts[match.index]++;
4879
+ offset = match.end;
4880
+ }
4881
+ return { value: value + source.slice(offset), counts };
4882
+ }
4883
+
4884
+ // ../shared/src/library/references.ts
4885
+ function validateLibraryV3References(document) {
4886
+ validateLibraryV3Shape(document);
4887
+ const ids = /* @__PURE__ */ new Map();
4888
+ let count = 0;
4889
+ const add = (id2, path2) => {
4890
+ count++;
4891
+ if (count > LIBRARY_V3_MAX_RECORDS)
4892
+ invalid(path2, "package_too_large", `Package exceeds ${LIBRARY_V3_MAX_RECORDS} records`);
4893
+ if (ids.has(id2)) invalid(path2, "duplicate_local_id", `Local ID already used at ${ids.get(id2)}`);
4894
+ ids.set(id2, path2);
4895
+ };
4896
+ const unique = (values, path2, code) => {
4897
+ if (new Set(values).size !== values.length) invalid(path2, code, "Values must be unique");
4898
+ };
4899
+ const workflows = new Map(document.workflows.map((w) => [w.id, w]));
4900
+ const states = /* @__PURE__ */ new Map();
4901
+ const fields = /* @__PURE__ */ new Map();
4902
+ const requireRef = (exists, path2, message3 = "Reference does not identify the required record kind") => {
4903
+ if (!exists) invalid(path2, "invalid_reference", message3);
4904
+ };
4905
+ const system = (ref, path2) => {
4906
+ requireRef(
4907
+ ["Open", "Human Review", "Closed"].includes(ref.state_name),
4908
+ path2,
4909
+ "Unknown Standard state"
4910
+ );
4911
+ };
4912
+ for (const [wi, w] of document.workflows.entries()) {
4913
+ const p = `/workflows/${wi}`;
4914
+ add(w.id, `${p}/id`);
4915
+ fields.set(w.id, { description: w.description });
4916
+ if (!w.states.length)
4917
+ invalid(`${p}/states`, "no_states", "A workflow needs at least one state");
4918
+ unique(
4919
+ w.states.map((s) => s.name.trim()),
4920
+ `${p}/states`,
4921
+ "duplicate_state_name"
4922
+ );
4923
+ for (const [si, s] of w.states.entries()) {
4924
+ const sp = `${p}/states/${si}`;
4925
+ add(s.id, `${sp}/id`);
4926
+ states.set(s.id, { state: s, workflow: w.id, path: sp });
4927
+ }
4928
+ const local = new Map(w.states.map((s) => [s.id, s]));
4929
+ const initial = local.get(w.initial_state_id);
4930
+ requireRef(!!initial, `${p}/initial_state_id`, "Initial state must belong to its workflow");
4931
+ if (initial && initial.category !== "backlog" && initial.category !== "active")
4932
+ invalid(
4933
+ `${p}/initial_state_id`,
4934
+ "invalid_initial_state",
4935
+ "Initial state must be backlog or active"
4936
+ );
4937
+ unique(
4938
+ w.transitions.map((t) => `${t.from_state_id}\0${t.name.trim().toLowerCase()}`),
4939
+ `${p}/transitions`,
4940
+ "duplicate_action"
4941
+ );
4942
+ for (const [ti, t] of w.transitions.entries()) {
4943
+ const tp = `${p}/transitions/${ti}`;
4944
+ add(t.id, `${tp}/id`);
4945
+ requireRef(local.has(t.from_state_id), `${tp}/from_state_id`);
4946
+ requireRef(local.has(t.to_state_id), `${tp}/to_state_id`);
4947
+ if (t.from_state_id === t.to_state_id)
4948
+ invalid(tp, "self_transition", "Self transitions are not allowed");
4949
+ unique(
4950
+ t.requires.map((r) => r.artifact),
4951
+ `${tp}/requires`,
4952
+ "duplicate_requirement"
4953
+ );
4954
+ }
4955
+ }
4956
+ for (const { state, path: path2 } of states.values()) {
4957
+ const ref = state.inherits_from;
4958
+ if (ref?.kind === "bundled_state")
4959
+ requireRef(states.has(ref.state_id), `${path2}/inherits_from`);
4960
+ else if (ref?.kind === "system_state") system(ref, `${path2}/inherits_from`);
4961
+ }
4962
+ for (const entry of states.values()) {
4963
+ const chain = /* @__PURE__ */ new Set();
4964
+ let current = entry;
4965
+ let length = 0;
4966
+ while (current) {
4967
+ if (chain.has(current.state.id))
4968
+ invalid(
4969
+ `${entry.path}/inherits_from`,
4970
+ "inheritance_cycle",
4971
+ "State inheritance contains a cycle"
4972
+ );
4973
+ chain.add(current.state.id);
4974
+ length++;
4975
+ const ref = current.state.inherits_from;
4976
+ if (ref?.kind === "system_state") length++;
4977
+ if (length > 3)
4978
+ invalid(
4979
+ `${entry.path}/inherits_from`,
4980
+ "inheritance_depth",
4981
+ "Inheritance may contain at most three states"
4982
+ );
4983
+ current = ref?.kind === "bundled_state" ? states.get(ref.state_id) : void 0;
4984
+ }
4985
+ }
4986
+ const contextKeys = /* @__PURE__ */ new Set();
4987
+ for (const [ci, item] of document.context.entries()) {
4988
+ const p = `/context/${ci}`;
4989
+ add(item.id, `${p}/id`);
4990
+ fields.set(item.id, {
4991
+ description: item.description,
4992
+ ...item.kind === "prompt" ? { body: item.body } : {}
4993
+ });
4994
+ let scope;
4995
+ if ("state_id" in item) {
4996
+ requireRef(states.has(item.state_id), `${p}/state_id`);
4997
+ scope = item.state_id;
4998
+ } else {
4999
+ const ref = item.scope.state;
5000
+ if (ref?.kind === "bundled_state") requireRef(states.has(ref.state_id), `${p}/scope/state`);
5001
+ if (ref?.kind === "system_state") system(ref, `${p}/scope/state`);
5002
+ if (document.profile !== "library")
5003
+ invalid(p, "invalid_scope", "Package context must have exact state scope");
5004
+ if (item.scope.project_id)
5005
+ requireRef(
5006
+ document.projects.some((r) => r.id === item.scope.project_id),
5007
+ `${p}/scope/project_id`
5008
+ );
5009
+ if (item.scope.label_id)
5010
+ requireRef(
5011
+ document.labels.some((r) => r.id === item.scope.label_id),
5012
+ `${p}/scope/label_id`
5013
+ );
5014
+ scope = JSON.stringify([
5015
+ item.scope.project_id ?? null,
5016
+ ref?.kind === "bundled_state" ? ["bundled", ref.state_id] : ref ? ["system", ref.state_name] : null,
5017
+ item.scope.label_id ?? null
5018
+ ]);
5019
+ }
5020
+ const key = JSON.stringify([scope, item.kind, item.name.trim()]);
5021
+ if (contextKeys.has(key))
5022
+ invalid(p, "duplicate_context", "Context kind/name must be unique at its exact scope");
5023
+ contextKeys.add(key);
5024
+ if (item.kind === "skill")
5025
+ for (const [fi, file] of item.files.entries()) {
5026
+ add(file.id, `${p}/files/${fi}/id`);
5027
+ fields.set(file.id, { content: file.content });
5028
+ }
5029
+ }
5030
+ if (document.profile === "library") {
5031
+ for (const [i, p] of document.projects.entries()) {
5032
+ add(p.id, `/projects/${i}/id`);
5033
+ if (p.default_workflow?.kind === "bundled_workflow")
5034
+ requireRef(
5035
+ workflows.has(p.default_workflow.workflow_id),
5036
+ `/projects/${i}/default_workflow`
5037
+ );
5038
+ }
5039
+ for (const [i, label] of document.labels.entries()) add(label.id, `/labels/${i}/id`);
5040
+ unique(
5041
+ document.projects.map((p) => p.name.trim()),
5042
+ "/projects",
5043
+ "duplicate_project"
5044
+ );
5045
+ unique(
5046
+ document.labels.map((l) => l.name.trim().toLowerCase()),
5047
+ "/labels",
5048
+ "duplicate_label"
5049
+ );
5050
+ return;
5051
+ }
5052
+ requireRef(
5053
+ workflows.has(document.main_workflow_id),
5054
+ "/main_workflow_id",
5055
+ "Main workflow does not exist"
5056
+ );
5057
+ const reached = /* @__PURE__ */ new Set();
5058
+ const pending = [document.main_workflow_id];
5059
+ while (pending.length) {
5060
+ const id2 = pending.pop();
5061
+ if (reached.has(id2)) continue;
5062
+ reached.add(id2);
5063
+ for (const s of workflows.get(id2).states)
5064
+ if (s.inherits_from?.kind === "bundled_state")
5065
+ pending.push(states.get(s.inherits_from.state_id).workflow);
5066
+ }
5067
+ for (const [i, w] of document.workflows.entries())
5068
+ if (!reached.has(w.id))
5069
+ invalid(
5070
+ `/workflows/${i}`,
5071
+ "disconnected_workflow",
5072
+ "Every dependency workflow must be reachable through inheritance"
5073
+ );
5074
+ const inputs = new Map(document.inputs.map((input2) => [input2.id, input2]));
5075
+ unique(
5076
+ document.inputs.map((input2) => input2.key),
5077
+ "/inputs",
5078
+ "duplicate_input_key"
5079
+ );
5080
+ for (const [i, input2] of document.inputs.entries()) {
5081
+ add(input2.id, `/inputs/${i}/id`);
5082
+ if (input2.required_states)
5083
+ unique(
5084
+ input2.required_states.map((s) => s.trim()),
5085
+ `/inputs/${i}/required_states`,
5086
+ "duplicate_required_state"
5087
+ );
5088
+ }
5089
+ const project = (id2, path2) => requireRef(inputs.get(id2)?.type === "project", path2, "Expected a project input declaration");
5090
+ for (const [i, schedule2] of document.schedules.entries()) {
5091
+ const p = `/schedules/${i}`;
5092
+ add(schedule2.id, `${p}/id`);
5093
+ requireRef(workflows.has(schedule2.workflow.workflow_id), `${p}/workflow`);
5094
+ project(schedule2.project.input_id, `${p}/project`);
5095
+ if (schedule2.start_state)
5096
+ requireRef(
5097
+ states.get(schedule2.start_state.state_id)?.workflow === schedule2.workflow.workflow_id,
5098
+ `${p}/start_state`,
5099
+ "Start state must belong to the scheduled workflow"
5100
+ );
5101
+ fields.set(schedule2.id, {
5102
+ title_template: schedule2.title_template,
5103
+ description_template: schedule2.description_template
5104
+ });
5105
+ }
5106
+ const routingScopes = /* @__PURE__ */ new Set();
5107
+ for (const [i, routing] of document.routing.entries()) {
5108
+ const p = `/routing/${i}`;
5109
+ add(routing.id, `${p}/id`);
5110
+ requireRef(states.has(routing.scope.state_id), `${p}/scope/state_id`);
5111
+ if (routing.scope.project) project(routing.scope.project.input_id, `${p}/scope/project`);
5112
+ const key = JSON.stringify([routing.scope.state_id, routing.scope.project?.input_id ?? null]);
5113
+ if (routingScopes.has(key))
5114
+ invalid(p, "duplicate_routing", "Only one tier preference is allowed per scope");
5115
+ routingScopes.add(key);
5116
+ }
5117
+ const uses = /* @__PURE__ */ new Map();
5118
+ for (const [i, use] of document.text_uses.entries()) {
5119
+ const p = `/text_uses/${i}`;
5120
+ add(use.id, `${p}/id`);
5121
+ const input2 = inputs.get(use.input_id);
5122
+ requireRef(!!input2, `${p}/input_id`);
5123
+ const target = fields.get(use.target.record_id);
5124
+ requireRef(
5125
+ !!target && Object.hasOwn(target, use.target.field),
5126
+ `${p}/target`,
5127
+ "Text use must target an allowed field on this record kind"
5128
+ );
5129
+ if (use.token !== inputToken(input2.key, input2.default))
5130
+ invalid(`${p}/token`, "invalid_token", "Token must match its declared input key and default");
5131
+ const key = JSON.stringify([use.target.record_id, use.target.field]);
5132
+ const group = uses.get(key) ?? {
5133
+ source: target[use.target.field],
5134
+ declarations: [],
5135
+ paths: []
5136
+ };
5137
+ group.declarations.push({ token: use.token, value: "" });
5138
+ group.paths.push(p);
5139
+ uses.set(key, group);
5140
+ }
5141
+ for (const group of uses.values()) {
5142
+ const result = renderDeclaredTokens(group.source, group.declarations);
5143
+ result.counts.forEach((count2, i) => {
5144
+ if (!count2)
5145
+ invalid(
5146
+ pointer(group.paths[i], "token"),
5147
+ "unused_text_use",
5148
+ "Declaration must match at least one unescaped occurrence"
5149
+ );
5150
+ });
5151
+ }
5152
+ }
5153
+
5154
+ // ../shared/src/library/canonical.ts
5155
+ function canonicalValue(value, path2 = "", depth = 0) {
5156
+ if (depth > 64) invalid(path2, "maximum_depth", "JSON nesting exceeds 64");
5157
+ if (typeof value === "string") validateUnicode(value, path2);
5158
+ if (value === null || typeof value === "boolean" || typeof value === "string") {
5159
+ return JSON.stringify(value);
5160
+ }
5161
+ if (typeof value === "number") {
5162
+ if (!Number.isFinite(value))
5163
+ throw new LibraryValidationError([
5164
+ { path: "", code: "invalid_number", message: "Numbers must be finite" }
5165
+ ]);
5166
+ return JSON.stringify(value);
5167
+ }
5168
+ if (Array.isArray(value)) {
5169
+ for (let i = 0; i < value.length; i++)
5170
+ if (!Object.hasOwn(value, i))
5171
+ invalid(pointer(path2, i), "invalid_value", "Sparse arrays are not JSON values");
5172
+ return `[${value.map((v, i) => canonicalValue(v, pointer(path2, i), depth + 1)).join(",")}]`;
5173
+ }
5174
+ if (typeof value === "object") {
5175
+ if (![Object.prototype, null].includes(Object.getPrototypeOf(value)))
5176
+ invalid(path2, "invalid_value", "Expected a plain JSON object");
5177
+ for (const key of Object.keys(value)) validateUnicode(key, pointer(path2, key));
5178
+ return `{${Object.keys(value).sort().map(
5179
+ (key) => `${JSON.stringify(key)}:${canonicalValue(value[key], pointer(path2, key), depth + 1)}`
5180
+ ).join(",")}}`;
5181
+ }
5182
+ throw new LibraryValidationError([
5183
+ {
5184
+ path: "",
5185
+ code: "invalid_value",
5186
+ message: "Undefined and non-JSON values are not canonicalizable"
5187
+ }
5188
+ ]);
5189
+ }
5190
+ function canonicalizeLibraryValue(value) {
5191
+ return canonicalValue(value);
5192
+ }
5193
+ async function libraryDocumentDigest(document) {
5194
+ const { digest: _digest, ...unsigned } = document;
5195
+ validateLibraryV3References(unsigned);
5196
+ const bytes2 = new TextEncoder().encode(canonicalValue(unsigned));
5197
+ if (new TextEncoder().encode(canonicalValue({ ...unsigned, digest: `sha256:${"0".repeat(64)}` })).byteLength > LIBRARY_MAX_BYTES)
5198
+ invalid("", "package_too_large", `Document exceeds ${LIBRARY_MAX_BYTES} bytes`);
5199
+ const hash = await crypto.subtle.digest("SHA-256", bytes2);
5200
+ return `sha256:${Array.from(new Uint8Array(hash), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
5201
+ }
5202
+
5203
+ // ../shared/src/library/parse.ts
5204
+ var StrictJsonParser = class {
5205
+ constructor(source) {
5206
+ this.source = source;
5207
+ }
5208
+ source;
5209
+ offset = 0;
5210
+ parse() {
5211
+ const value = this.value(0, "");
5212
+ this.space();
5213
+ if (this.offset !== this.source.length)
5214
+ this.fail("", "invalid_json", "Unexpected trailing input");
5215
+ return value;
5216
+ }
5217
+ value(depth, path2) {
5218
+ if (depth > LIBRARY_V3_MAX_DEPTH)
5219
+ this.fail(path2, "maximum_depth", `JSON nesting exceeds ${LIBRARY_V3_MAX_DEPTH}`);
5220
+ this.space();
5221
+ const char = this.source[this.offset];
5222
+ if (char === "{") return this.object(depth + 1, path2);
5223
+ if (char === "[") return this.array(depth + 1, path2);
5224
+ if (char === '"') return this.string(path2);
5225
+ if (char === "t" && this.take("true")) return true;
5226
+ if (char === "f" && this.take("false")) return false;
5227
+ if (char === "n" && this.take("null")) return null;
5228
+ if (char === "-" || char >= "0" && char <= "9") return this.number(path2);
5229
+ this.fail(path2, "invalid_json", "Expected a JSON value");
5230
+ }
5231
+ object(depth, path2) {
5232
+ this.offset++;
5233
+ const result = /* @__PURE__ */ Object.create(null);
5234
+ const keys = /* @__PURE__ */ new Set();
5235
+ this.space();
5236
+ if (this.source[this.offset] === "}") {
5237
+ this.offset++;
5238
+ return result;
5239
+ }
5240
+ while (true) {
5241
+ this.space();
5242
+ if (this.source[this.offset] !== '"')
5243
+ this.fail(path2, "invalid_json", "Expected an object key");
5244
+ const key = this.string(path2);
5245
+ const keyPath = `${path2}/${key.replaceAll("~", "~0").replaceAll("/", "~1")}`;
5246
+ if (keys.has(key))
5247
+ this.fail(keyPath, "duplicate_key", `Duplicate object key ${JSON.stringify(key)}`);
5248
+ keys.add(key);
5249
+ this.space();
5250
+ if (this.source[this.offset++] !== ":")
5251
+ this.fail(keyPath, "invalid_json", "Expected colon after object key");
5252
+ result[key] = this.value(depth, keyPath);
5253
+ this.space();
5254
+ const separator = this.source[this.offset++];
5255
+ if (separator === "}") return result;
5256
+ if (separator !== ",") this.fail(path2, "invalid_json", "Expected comma or closing brace");
5257
+ }
5258
+ }
5259
+ array(depth, path2) {
5260
+ this.offset++;
5261
+ const result = [];
5262
+ this.space();
5263
+ if (this.source[this.offset] === "]") {
5264
+ this.offset++;
5265
+ return result;
5266
+ }
5267
+ while (true) {
5268
+ result.push(this.value(depth, `${path2}/${result.length}`));
5269
+ this.space();
5270
+ const separator = this.source[this.offset++];
5271
+ if (separator === "]") return result;
5272
+ if (separator !== ",") this.fail(path2, "invalid_json", "Expected comma or closing bracket");
5273
+ }
5274
+ }
5275
+ string(path2) {
5276
+ const start = this.offset++;
5277
+ while (this.offset < this.source.length) {
5278
+ const code = this.source.charCodeAt(this.offset);
5279
+ if (code === 34) {
5280
+ this.offset++;
5281
+ let value;
5282
+ try {
5283
+ value = JSON.parse(this.source.slice(start, this.offset));
5284
+ } catch {
5285
+ this.fail(path2, "invalid_json", "Invalid JSON string escape");
5286
+ }
5287
+ for (let i = 0; i < value.length; i++) {
5288
+ const unit = value.charCodeAt(i);
5289
+ if (unit >= 55296 && unit <= 56319) {
5290
+ const next = value.charCodeAt(++i);
5291
+ if (!(next >= 56320 && next <= 57343))
5292
+ this.fail(path2, "invalid_unicode", "Lone UTF-16 surrogate is not allowed");
5293
+ } else if (unit >= 56320 && unit <= 57343)
5294
+ this.fail(path2, "invalid_unicode", "Lone UTF-16 surrogate is not allowed");
5295
+ }
5296
+ return value;
5297
+ }
5298
+ if (code < 32) this.fail(path2, "invalid_json", "Unescaped control character in string");
5299
+ if (code === 92) this.offset++;
5300
+ this.offset++;
5301
+ }
5302
+ this.fail(path2, "invalid_json", "Unterminated JSON string");
5303
+ }
5304
+ number(path2) {
5305
+ const rest = this.source.slice(this.offset);
5306
+ const match = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/.exec(rest);
5307
+ if (!match) this.fail(path2, "invalid_json", "Invalid number");
5308
+ this.offset += match[0].length;
5309
+ const value = Number(match[0]);
5310
+ if (!Number.isFinite(value)) this.fail(path2, "invalid_number", "Numbers must be finite");
5311
+ return value;
5312
+ }
5313
+ take(word) {
5314
+ if (!this.source.startsWith(word, this.offset)) return false;
5315
+ this.offset += word.length;
5316
+ return true;
5317
+ }
5318
+ space() {
5319
+ while (/[ \t\r\n]/.test(this.source[this.offset] ?? "x")) this.offset++;
5320
+ }
5321
+ fail(path2, code, message3) {
5322
+ throw new LibraryValidationError([{ path: path2, code, message: message3 }]);
5323
+ }
5324
+ };
5325
+ var TOP_LEVEL = {
5326
+ workflow: [
5327
+ "format",
5328
+ "version",
5329
+ "profile",
5330
+ "exported_at",
5331
+ "digest",
5332
+ "main_workflow_id",
5333
+ "workflows",
5334
+ "context",
5335
+ "inputs",
5336
+ "text_uses",
5337
+ "schedules",
5338
+ "routing"
5339
+ ],
5340
+ library: [
5341
+ "format",
5342
+ "version",
5343
+ "profile",
5344
+ "exported_at",
5345
+ "digest",
5346
+ "projects",
5347
+ "labels",
5348
+ "workflows",
5349
+ "context"
5350
+ ]
5351
+ };
5352
+ function structuralDocument(value, allowMissingDigest) {
5353
+ if (!value || typeof value !== "object" || Array.isArray(value))
5354
+ throw new LibraryValidationError([
5355
+ { path: "", code: "invalid_document", message: "Library document must be an object" }
5356
+ ]);
5357
+ const object3 = value;
5358
+ if (object3.format !== "tines.library")
5359
+ throw new LibraryValidationError([
5360
+ { path: "/format", code: "invalid_format", message: "Expected tines.library" }
5361
+ ]);
5362
+ if (object3.version !== 3)
5363
+ throw new LibraryValidationError([
5364
+ {
5365
+ path: "/version",
5366
+ code: "unsupported_version",
5367
+ message: `Unsupported library version ${String(object3.version)}`
5368
+ }
5369
+ ]);
5370
+ if (object3.profile !== "workflow" && object3.profile !== "library")
5371
+ throw new LibraryValidationError([
5372
+ { path: "/profile", code: "invalid_profile", message: "Expected workflow or library profile" }
5373
+ ]);
5374
+ const allowed = TOP_LEVEL[object3.profile];
5375
+ const unknown = Object.keys(object3).find((key) => !allowed.includes(key));
5376
+ if (unknown)
5377
+ throw new LibraryValidationError([
5378
+ { path: `/${unknown}`, code: "unknown_field", message: `Unknown field ${unknown}` }
5379
+ ]);
5380
+ if (!Number.isSafeInteger(object3.exported_at) || object3.exported_at < 0)
5381
+ throw new LibraryValidationError([
5382
+ {
5383
+ path: "/exported_at",
5384
+ code: "invalid_integer",
5385
+ message: "exported_at must be a non-negative safe integer"
5386
+ }
5387
+ ]);
5388
+ if (!(allowMissingDigest && object3.digest === void 0) && (typeof object3.digest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(object3.digest)))
5389
+ throw new LibraryValidationError([
5390
+ {
5391
+ path: "/digest",
5392
+ code: "invalid_digest",
5393
+ message: "digest must be sha256 followed by 64 lowercase hex characters"
5394
+ }
5395
+ ]);
5396
+ for (const key of allowed) {
5397
+ if (key === "digest" && allowMissingDigest) continue;
5398
+ if (!(key in object3))
5399
+ throw new LibraryValidationError([
5400
+ { path: `/${key}`, code: "missing_field", message: `Missing required field ${key}` }
5401
+ ]);
5402
+ }
5403
+ return {
5404
+ ...object3,
5405
+ digest: typeof object3.digest === "string" ? object3.digest : ""
5406
+ };
5407
+ }
5408
+ async function parseLibraryV3Document(input2, options = {}) {
5409
+ const bytes2 = typeof input2 === "string" ? new TextEncoder().encode(input2) : input2;
5410
+ if (bytes2.byteLength > LIBRARY_MAX_BYTES)
5411
+ throw new LibraryValidationError([
5412
+ {
5413
+ path: "",
5414
+ code: "package_too_large",
5415
+ message: `Document exceeds ${LIBRARY_MAX_BYTES} bytes`
5416
+ }
5417
+ ]);
5418
+ let source;
5419
+ try {
5420
+ source = typeof input2 === "string" ? input2 : new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(input2);
5421
+ } catch {
5422
+ throw new LibraryValidationError([
5423
+ { path: "", code: "invalid_utf8", message: "Document is not valid UTF-8" }
5424
+ ]);
5425
+ }
5426
+ const document = structuralDocument(
5427
+ new StrictJsonParser(source).parse(),
5428
+ options.allowMissingDigest ?? false
5429
+ );
5430
+ validateLibraryV3References(document);
5431
+ const actual = await libraryDocumentDigest(document);
5432
+ if (document.digest && document.digest !== actual)
5433
+ throw new LibraryValidationError([
5434
+ {
5435
+ path: "/digest",
5436
+ code: "digest_mismatch",
5437
+ message: `Document digest does not match; expected ${actual}`
5438
+ }
5439
+ ]);
5440
+ return { ...document, digest: actual };
5441
+ }
5442
+ function parseStrictLibraryJson(source) {
5443
+ return new StrictJsonParser(source).parse();
5444
+ }
4588
5445
 
4589
5446
  // ../shared/src/library/choices.ts
4590
5447
  var PACKAGE_CHOICES_MAX_BYTES = 512 * 1024;
@@ -4737,48 +5594,48 @@ function commentLines(comment) {
4737
5594
  function sinceLastRunLines(since, now = Date.now()) {
4738
5595
  const prev = since.previous_run;
4739
5596
  const ended = prev.ended_at === null ? "not ended" : `ended ${ageLabel(prev.ended_at, now)} ago`;
4740
- const lines = [
5597
+ const lines2 = [
4741
5598
  `since the last run (${prev.state_at_start_name ?? "unknown state"}, ${prev.run_id} ${ended}):`
4742
5599
  ];
4743
5600
  const t = since.transition;
4744
5601
  if (t) {
4745
5602
  const via = t.action ? `via "${t.action}"` : "moved directly";
4746
5603
  const stale = since.stale_artifacts.length > 0 ? ` \u2014 now stale: ${since.stale_artifacts.join(", ")}` : "";
4747
- lines.push(
5604
+ lines2.push(
4748
5605
  ` moved from ${t.from_state.name} ${via} by ${actorLabel(t.actor)} ${ageLabel(t.at, now)} ago${stale}`
4749
5606
  );
4750
5607
  }
4751
- for (const c of since.comments) lines.push(...commentLines(c));
5608
+ for (const c of since.comments) lines2.push(...commentLines(c));
4752
5609
  if (since.comment_count > since.comments.length) {
4753
5610
  const hidden = since.comment_count - since.comments.length;
4754
- lines.push(` \u2026 and ${hidden} earlier comment${hidden === 1 ? "" : "s"}`);
5611
+ lines2.push(` \u2026 and ${hidden} earlier comment${hidden === 1 ? "" : "s"}`);
4755
5612
  }
4756
- return lines;
5613
+ return lines2;
4757
5614
  }
4758
5615
  function roundLines(round, now = Date.now()) {
4759
5616
  const b = round.boundary;
4760
5617
  const from = b ? `since ${actorLabel(b.actor)} ${b.action ? `moved "${b.action}"` : "moved it directly"} ${timestamp(b.at)}` : `since created ${timestamp(round.boundary_at)}`;
4761
- const lines = [`round (${round.run_count} run${round.run_count === 1 ? "" : "s"} ${from}):`];
5618
+ const lines2 = [`round (${round.run_count} run${round.run_count === 1 ? "" : "s"} ${from}):`];
4762
5619
  for (const stage of round.stages) {
4763
5620
  const [latest, ...earlier] = stage.runs;
4764
5621
  if (!latest) continue;
4765
- lines.push(` ${stage.state.name ?? stage.state.id} \u2014 ${runHeadline(latest, now)}`);
5622
+ lines2.push(` ${stage.state.name ?? stage.state.id} \u2014 ${runHeadline(latest, now)}`);
4766
5623
  if (latest.artifacts.length > 0) {
4767
- lines.push(` ${latest.artifacts.map(artifactChangeLabel).join(" \xB7 ")}`);
5624
+ lines2.push(` ${latest.artifacts.map(artifactChangeLabel).join(" \xB7 ")}`);
4768
5625
  }
4769
- for (const run of earlier) lines.push(` ${earlierAttemptLine(run, now)}`);
5626
+ for (const run of earlier) lines2.push(` ${earlierAttemptLine(run, now)}`);
4770
5627
  if (latest.summary_comment) {
4771
- lines.push(` summary (${latest.summary_comment.id}):`);
4772
- for (const line of latest.summary_comment.body.split("\n")) lines.push(` ${line}`);
5628
+ lines2.push(` summary (${latest.summary_comment.id}):`);
5629
+ for (const line of latest.summary_comment.body.split("\n")) lines2.push(` ${line}`);
4773
5630
  }
4774
5631
  const earlierComments = latest.earlier_comment_ids;
4775
5632
  if (earlierComments.length > 0) {
4776
- lines.push(
5633
+ lines2.push(
4777
5634
  ` ${earlierComments.length} earlier comment${earlierComments.length === 1 ? "" : "s"}: ${earlierComments.join(", ")}`
4778
5635
  );
4779
5636
  }
4780
5637
  }
4781
- return lines;
5638
+ return lines2;
4782
5639
  }
4783
5640
  function runHeadline(run, now) {
4784
5641
  const cost = runCostLabel(run);
@@ -4824,9 +5681,9 @@ function runRow(run) {
4824
5681
  timestamp(run.created_at)
4825
5682
  ];
4826
5683
  }
4827
- function byteSize(bytes) {
5684
+ function byteSize(bytes2) {
4828
5685
  const units = ["B", "KB", "MB", "GB", "TB"];
4829
- let value = Math.max(0, bytes);
5686
+ let value = Math.max(0, bytes2);
4830
5687
  let unit = 0;
4831
5688
  while (value >= 1024 && unit < units.length - 1) {
4832
5689
  value /= 1024;
@@ -5144,15 +6001,15 @@ async function resolveStateFlag(api, ref) {
5144
6001
  if (sep2 < 1 || sep2 === ref.length - 1) {
5145
6002
  die(`--state must look like <workflow>/<state>, got "${ref}"`);
5146
6003
  }
5147
- const workflow = await resolveWorkflow(api, ref.slice(0, sep2));
5148
- const stateRef = ref.slice(sep2 + 1);
5149
- const state = workflow.states.find((s) => s.name === stateRef) ?? workflow.states.find((s) => s.id === stateRef);
6004
+ const workflow2 = await resolveWorkflow(api, ref.slice(0, sep2));
6005
+ const stateRef2 = ref.slice(sep2 + 1);
6006
+ const state = workflow2.states.find((s) => s.name === stateRef2) ?? workflow2.states.find((s) => s.id === stateRef2);
5150
6007
  if (!state) {
5151
6008
  die(
5152
- `workflow "${workflow.name}" has no state "${stateRef}" (have: ${workflow.states.map((s) => s.name).join(", ")})`
6009
+ `workflow "${workflow2.name}" has no state "${stateRef2}" (have: ${workflow2.states.map((s) => s.name).join(", ")})`
5153
6010
  );
5154
6011
  }
5155
- return { workflow, state };
6012
+ return { workflow: workflow2, state };
5156
6013
  }
5157
6014
  var collect = (value, previous) => [...previous, value];
5158
6015
  async function resolveRunner(api, ref) {
@@ -5216,12 +6073,12 @@ function withScopeFlags(cmd) {
5216
6073
  return cmd.option("-p, --project <name>", "scope: project name or id").option("-s, --state <workflow/state>", "scope: workflow-qualified state").option("-i, --issue <ref>", "scope: issue (<project>/<number>)").option("-l, --label <name>", "scope: issue label name or id").addHelpText("after", SCOPE_FLAGS_HELP);
5217
6074
  }
5218
6075
  function register(program3) {
5219
- const context = program3.command("context").description(
6076
+ const context2 = program3.command("context").description(
5220
6077
  "Manage context items (prompts, skills, repo pointers) scoped to projects, states, and issues"
5221
6078
  );
5222
6079
  withList(
5223
6080
  withScopeFlags(
5224
- context.command("list").description(
6081
+ context2.command("list").description(
5225
6082
  "List context items (scope filters match every item whose scope includes the element)"
5226
6083
  ).option("-k, --kind <kind>", "filter by kind: prompt, skill, or repo").option("--exact", "only items whose scope sets exactly the given dimensions").option("-q, --search <text>", "search names and descriptions")
5227
6084
  )
@@ -5259,7 +6116,7 @@ function register(program3) {
5259
6116
  }
5260
6117
  );
5261
6118
  withCommon(
5262
- context.command("show <id>").description("Show a context item (skills include their files)")
6119
+ context2.command("show <id>").description("Show a context item (skills include their files)")
5263
6120
  ).action(async (id2, opts) => {
5264
6121
  const item = await client(opts).getContextItem(id2);
5265
6122
  if (opts.json) return printJson(item);
@@ -5267,7 +6124,7 @@ function register(program3) {
5267
6124
  });
5268
6125
  withCommon(
5269
6126
  withScopeFlags(
5270
- context.command("create").description("Create a context item scoped to a project, state, and/or issue").requiredOption("-k, --kind <kind>", "prompt, skill, or repo").requiredOption(
6127
+ context2.command("create").description("Create a context item scoped to a project, state, and/or issue").requiredOption("-k, --kind <kind>", "prompt, skill, or repo").requiredOption(
5271
6128
  "-n, --name <name>",
5272
6129
  "item name (slug-like for skills; the dedup/override key)"
5273
6130
  ).option("-d, --description <text>", "one-liner shown in lists").option("--body <md>", "prompt body: inline Markdown or @file (escape a literal @ as @@)").option(
@@ -5303,7 +6160,7 @@ function register(program3) {
5303
6160
  );
5304
6161
  withCommon(
5305
6162
  withScopeFlags(
5306
- context.command("edit <id>").description("Edit a context item: payload, name, description, or scope").option("-n, --name <name>", "rename the item").option("-d, --description <text>", "set the description").option("--body <md>", "prompt body: inline Markdown or @file (escape a literal @ as @@)").option("--file <path>=@<local>", "add or replace a skill file (repeatable)", collect, []).option("--remove-file <path>", "remove a skill file (repeatable)", collect, []).option("--repo-url <url>", "repo: clone URL").option("--branch <branch>", "repo: branch (empty string clears it)").option("--dir <dir>", "repo: checkout directory (empty string restores the URL default)").option(
6163
+ context2.command("edit <id>").description("Edit a context item: payload, name, description, or scope").option("-n, --name <name>", "rename the item").option("-d, --description <text>", "set the description").option("--body <md>", "prompt body: inline Markdown or @file (escape a literal @ as @@)").option("--file <path>=@<local>", "add or replace a skill file (repeatable)", collect, []).option("--remove-file <path>", "remove a skill file (repeatable)", collect, []).option("--repo-url <url>", "repo: clone URL").option("--branch <branch>", "repo: branch (empty string clears it)").option("--dir <dir>", "repo: checkout directory (empty string restores the URL default)").option(
5307
6164
  "--unset <dimension>",
5308
6165
  "drop a scope dimension: project, state, issue, or label (repeatable)",
5309
6166
  collect,
@@ -5336,19 +6193,19 @@ function register(program3) {
5336
6193
  if (current.kind !== "skill")
5337
6194
  die(`--file/--remove-file only apply to skills (this is a ${current.kind})`);
5338
6195
  if (body.expected_version === void 0) body.expected_version = current.version;
5339
- const files = new Map((current.files ?? []).map((f) => [f.path, f.content]));
6196
+ const files2 = new Map((current.files ?? []).map((f) => [f.path, f.content]));
5340
6197
  for (const path2 of opts.removeFile) {
5341
- if (!files.delete(path2)) {
6198
+ if (!files2.delete(path2)) {
5342
6199
  die(
5343
- `no file "${path2}" in skill "${current.name}" (have: ${[...files.keys()].join(", ") || "none"})`
6200
+ `no file "${path2}" in skill "${current.name}" (have: ${[...files2.keys()].join(", ") || "none"})`
5344
6201
  );
5345
6202
  }
5346
6203
  }
5347
6204
  for (const spec of opts.file) {
5348
6205
  const f = parseFileSpec(spec);
5349
- files.set(f.path, f.content);
6206
+ files2.set(f.path, f.content);
5350
6207
  }
5351
- body.files = [...files.entries()].map(([path2, content]) => ({ path: path2, content }));
6208
+ body.files = [...files2.entries()].map(([path2, content]) => ({ path: path2, content }));
5352
6209
  }
5353
6210
  if (opts.repoUrl !== void 0) body.repo_url = opts.repoUrl;
5354
6211
  if (opts.branch !== void 0) body.repo_branch = opts.branch === "" ? null : opts.branch;
@@ -5363,7 +6220,7 @@ function register(program3) {
5363
6220
  console.log(`updated ${item.kind} "${item.name}" (${item.id}) \u2014 scope: ${item.scope.label}`);
5364
6221
  }
5365
6222
  );
5366
- withCommon(context.command("delete <id>").description("Delete a context item")).action(
6223
+ withCommon(context2.command("delete <id>").description("Delete a context item")).action(
5367
6224
  async (id2, opts) => {
5368
6225
  const api = client(opts);
5369
6226
  const item = await api.getContextItem(id2);
@@ -5372,7 +6229,7 @@ function register(program3) {
5372
6229
  }
5373
6230
  );
5374
6231
  withCommon(
5375
- context.command("init").description('Seed the global "agent-guidelines" prompt (a no-op if it already exists)')
6232
+ context2.command("init").description('Seed the global "agent-guidelines" prompt (a no-op if it already exists)')
5376
6233
  ).action(async (opts) => {
5377
6234
  const api = client(opts);
5378
6235
  const items = await listAll(
@@ -5424,98 +6281,98 @@ function repoLine(change) {
5424
6281
  return ` checkout: ${show(before)} => ${show(after)}`;
5425
6282
  }
5426
6283
  function routingLines(preview) {
5427
- const lines = ["", "Routing:"];
6284
+ const lines2 = ["", "Routing:"];
5428
6285
  for (const [label, explainer] of [
5429
6286
  [" before", preview.routing.before],
5430
6287
  [" after ", preview.routing.after]
5431
6288
  ]) {
5432
6289
  if (!explainer) {
5433
- lines.push(`${label}: no explanation available`);
6290
+ lines2.push(`${label}: no explanation available`);
5434
6291
  continue;
5435
6292
  }
5436
- lines.push(`${label}: ${explainer.verdict}`);
6293
+ lines2.push(`${label}: ${explainer.verdict}`);
5437
6294
  if (explainer.pin) {
5438
- lines.push(
6295
+ lines2.push(
5439
6296
  ` pin: runner ${explainer.pin.runner_name ?? explainer.pin.runner_id}, tier ${explainer.pin.tier ?? "default"}`
5440
6297
  );
5441
6298
  }
5442
6299
  for (const check of explainer.checks) {
5443
- lines.push(
6300
+ lines2.push(
5444
6301
  ` ${check.ok ? "pass" : "FAIL"} ${check.name}: ${check.detail}${check.action?.label ? ` (${check.action.label})` : ""}`
5445
6302
  );
5446
- if (check.action?.cli) lines.push(` fix: ${check.action.cli}`);
6303
+ if (check.action?.cli) lines2.push(` fix: ${check.action.cli}`);
5447
6304
  }
5448
6305
  if (explainer.matched_rule)
5449
- lines.push(` matched rule ${explainer.matched_rule.scope_label}`);
6306
+ lines2.push(` matched rule ${explainer.matched_rule.scope_label}`);
5450
6307
  for (const tie of explainer.ambiguous_rules) {
5451
- lines.push(` tied rule ${tie.scope_label}`);
6308
+ lines2.push(` tied rule ${tie.scope_label}`);
5452
6309
  }
5453
6310
  if (explainer.runner_rule)
5454
- lines.push(` runner source rule ${explainer.runner_rule.scope_label}`);
5455
- if (explainer.tier_override) lines.push(` tier override ${explainer.tier_override}`);
6311
+ lines2.push(` runner source rule ${explainer.runner_rule.scope_label}`);
6312
+ if (explainer.tier_override) lines2.push(` tier override ${explainer.tier_override}`);
5456
6313
  for (const target of explainer.targets) {
5457
- lines.push(
6314
+ lines2.push(
5458
6315
  ` target ${target.runner_name} \u2014 ${target.tier}${target.model ? ` / ${target.model}` : ""}: ${target.verdict} \u2014 ${target.detail}`
5459
6316
  );
5460
6317
  }
5461
6318
  if (explainer.active_run)
5462
- lines.push(
6319
+ lines2.push(
5463
6320
  ` active run ${explainer.active_run.id} (${explainer.active_run.status})`
5464
6321
  );
5465
6322
  if (explainer.queue_position !== null)
5466
- lines.push(` queue position ${explainer.queue_position}`);
5467
- lines.push(
6323
+ lines2.push(` queue position ${explainer.queue_position}`);
6324
+ lines2.push(
5468
6325
  ` attempts ${explainer.attempt_count}/${explainer.attempt_limit}; parked ${explainer.parked ? "yes" : "no"}`
5469
6326
  );
5470
6327
  }
5471
- lines.push(" Capacity, heartbeats and spending are advisory and may change at any moment.");
5472
- return lines;
6328
+ lines2.push(" Capacity, heartbeats and spending are advisory and may change at any moment.");
6329
+ return lines2;
5473
6330
  }
5474
6331
  function repoDetail(repo) {
5475
6332
  return `${repo.url}; branch ${repo.branch ?? "repository default branch"}; directory ${repo.dir}`;
5476
6333
  }
5477
6334
  function effectiveRepositoryLines(preview) {
5478
- const lines = ["", "Effective repositories:"];
5479
- for (const [label, context] of [
6335
+ const lines2 = ["", "Effective repositories:"];
6336
+ for (const [label, context2] of [
5480
6337
  [" before", preview.context.before],
5481
6338
  [" after ", preview.context.after]
5482
6339
  ]) {
5483
- lines.push(`${label}:`);
5484
- if (!context.repos.length) lines.push(" none");
5485
- for (const repo of context.repos) {
5486
- lines.push(` ${repo.name} \u2014 effective (${repo.scope.label})`);
5487
- lines.push(` ${repoDetail(repo)}`);
5488
- for (const loser of context.overridden.filter(
6340
+ lines2.push(`${label}:`);
6341
+ if (!context2.repos.length) lines2.push(" none");
6342
+ for (const repo of context2.repos) {
6343
+ lines2.push(` ${repo.name} \u2014 effective (${repo.scope.label})`);
6344
+ lines2.push(` ${repoDetail(repo)}`);
6345
+ for (const loser of context2.overridden.filter(
5489
6346
  (item) => item.kind === "repo" && item.name === repo.name
5490
6347
  )) {
5491
- lines.push(
6348
+ lines2.push(
5492
6349
  ` overridden candidate ${loser.name} (${loser.scope.label}) \u2014 overridden by ${loser.overridden_by}`
5493
6350
  );
5494
- lines.push(
6351
+ lines2.push(
5495
6352
  ` ${loser.repo ? repoDetail(loser.repo) : "checkout details unavailable from this server"}`
5496
6353
  );
5497
6354
  }
5498
6355
  }
5499
6356
  }
5500
- return lines;
6357
+ return lines2;
5501
6358
  }
5502
6359
  function conflictLines(preview) {
5503
- const lines = ["", "Repository checkout conflicts:"];
6360
+ const lines2 = ["", "Repository checkout conflicts:"];
5504
6361
  const deltas = deriveTransferConflictDeltas(preview.context.before, preview.context.after);
5505
- if (!deltas.length) return [...lines, " none"];
6362
+ if (!deltas.length) return [...lines2, " none"];
5506
6363
  const participants = (items) => items.map(
5507
6364
  (item) => item.name && item.scope_label ? `${item.name} (${item.scope_label})` : item.item_id
5508
6365
  ).join(", ");
5509
6366
  for (const delta of deltas) {
5510
- lines.push(` ${delta.change[0].toUpperCase()}${delta.change.slice(1)} \u2014 ${delta.dir}`);
5511
- if (delta.before.length) lines.push(` Before: ${participants(delta.before)}`);
5512
- if (delta.after.length) lines.push(` After: ${participants(delta.after)}`);
6367
+ lines2.push(` ${delta.change[0].toUpperCase()}${delta.change.slice(1)} \u2014 ${delta.dir}`);
6368
+ if (delta.before.length) lines2.push(` Before: ${participants(delta.before)}`);
6369
+ if (delta.after.length) lines2.push(` After: ${participants(delta.after)}`);
5513
6370
  }
5514
- return lines;
6371
+ return lines2;
5515
6372
  }
5516
6373
  function formatTransferPreview(preview) {
5517
6374
  const p = preview.preserved;
5518
- const lines = [
6375
+ const lines2 = [
5519
6376
  `Move ${preview.old_ref.ref} from ${preview.source.name} to ${preview.destination.name}`,
5520
6377
  ` ${preview.number_notice} The issue keeps its stable ID (${preview.issue_id}) and every old ref keeps working.`,
5521
6378
  "",
@@ -5526,37 +6383,37 @@ function formatTransferPreview(preview) {
5526
6383
  ` pins: ${p.pinned_runner_id ?? "none"}${p.pinned_tier ? ` (${p.pinned_tier})` : ""}; attempts ${p.attempt_count}${p.parked ? "; parked" : ""}`
5527
6384
  ];
5528
6385
  const changes = inspectableChanges(preview);
5529
- lines.push("", changes.length ? "Guidance:" : "Guidance: none.");
6386
+ lines2.push("", changes.length ? "Guidance:" : "Guidance: none.");
5530
6387
  changes.forEach((change, index) => {
5531
- lines.push(` [${index}] ${change.kind} "${change.name}" \u2014 ${CHANGE_LABEL[change.change]}`);
6388
+ lines2.push(` [${index}] ${change.kind} "${change.name}" \u2014 ${CHANGE_LABEL[change.change]}`);
5532
6389
  const scopeBefore = change.scope_before?.label ?? "\u2014";
5533
6390
  const scopeAfter = change.scope_after?.label ?? "\u2014";
5534
- lines.push(` scope: ${scopeBefore} => ${scopeAfter}`);
5535
- lines.push(
6391
+ lines2.push(` scope: ${scopeBefore} => ${scopeAfter}`);
6392
+ lines2.push(
5536
6393
  ` before: ${effectiveness(Boolean(change.scope_before), change.effective_before)}; after: ${effectiveness(Boolean(change.scope_after), change.effective_after)}`
5537
6394
  );
5538
6395
  const repo = repoLine(change);
5539
- if (repo) lines.push(repo);
6396
+ if (repo) lines2.push(repo);
5540
6397
  });
5541
- lines.push(...effectiveRepositoryLines(preview), ...conflictLines(preview));
5542
- lines.push(...routingLines(preview));
6398
+ lines2.push(...effectiveRepositoryLines(preview), ...conflictLines(preview));
6399
+ lines2.push(...routingLines(preview));
5543
6400
  if (preview.schedule) {
5544
- lines.push(
6401
+ lines2.push(
5545
6402
  "",
5546
6403
  `Schedule: ${preview.schedule.name} (${preview.schedule.project_name}) \u2014 ${preview.schedule.notice}`
5547
6404
  );
5548
6405
  }
5549
6406
  if (preview.blockers.length) {
5550
- lines.push("", "Cannot move yet:");
6407
+ lines2.push("", "Cannot move yet:");
5551
6408
  for (const blocker of preview.blockers) {
5552
- lines.push(
6409
+ lines2.push(
5553
6410
  ` ${blocker.code}: ${blocker.message}${blocker.remedy ? ` (${blocker.remedy})` : ""}`
5554
6411
  );
5555
6412
  }
5556
6413
  }
5557
6414
  if (preview.noop)
5558
- lines.push("", "This issue is already in that project; moving would change nothing.");
5559
- return lines.join("\n");
6415
+ lines2.push("", "This issue is already in that project; moving would change nothing.");
6416
+ return lines2.join("\n");
5560
6417
  }
5561
6418
  function formatTransferItem(preview, index) {
5562
6419
  const change = inspectableChanges(preview)[index];
@@ -5722,10 +6579,10 @@ function effectiveContentType(type, source, explicit, sniff) {
5722
6579
  if (source.kind === "file-stdin") return "application/octet-stream";
5723
6580
  return void 0;
5724
6581
  }
5725
- function planAttach(input, sniff) {
5726
- const { ref, name: name2, positional, flags, probe } = input;
6582
+ function planAttach(input2, sniff) {
6583
+ const { ref, name: name2, positional, flags, probe } = input2;
5727
6584
  assertOneSource(flags, positional);
5728
- const gates = flags.ignoreGates ? [] : input.gates;
6585
+ const gates = flags.ignoreGates ? [] : input2.gates;
5729
6586
  const plan = positional !== void 0 ? planPositional(name2, positional, gates, probe, flags) : planFlags(flags, probe);
5730
6587
  if (gates.length > 0) {
5731
6588
  checkAccepted(plan, gates, ref, name2, flags, positional, sniff);
@@ -6119,14 +6976,14 @@ function readTextSource(source) {
6119
6976
  throw new Error(`not a text source: ${source.kind}`);
6120
6977
  }
6121
6978
  function walkFolder(dir) {
6122
- const files = [];
6979
+ const files2 = [];
6123
6980
  const walk = (abs, rel) => {
6124
6981
  for (const entry of readdirSync(abs, { withFileTypes: true })) {
6125
6982
  const nextAbs = join2(abs, entry.name);
6126
6983
  const nextRel = rel ? `${rel}/${entry.name}` : entry.name;
6127
6984
  if (entry.isDirectory()) walk(nextAbs, nextRel);
6128
6985
  else if (entry.isFile()) {
6129
- files.push({
6986
+ files2.push({
6130
6987
  path: nextRel,
6131
6988
  contentType: sniffContentType(entry.name),
6132
6989
  bytes: readFileSync4(nextAbs)
@@ -6135,7 +6992,7 @@ function walkFolder(dir) {
6135
6992
  }
6136
6993
  };
6137
6994
  walk(dir, "");
6138
- return files;
6995
+ return files2;
6139
6996
  }
6140
6997
  function printExplainer(issue, ex) {
6141
6998
  console.log(`${issue.project_name}/#${issue.number} ${issue.title}`);
@@ -6576,34 +7433,34 @@ function register2(program3) {
6576
7433
  ).action(async (ref, opts) => {
6577
7434
  const api = client(opts);
6578
7435
  const issue = await resolveIssue(api, ref);
6579
- const context = await api.getIssueContext(issue.id);
6580
- if (opts.json && !opts.out) return printJson(context);
7436
+ const context2 = await api.getIssueContext(issue.id);
7437
+ if (opts.json && !opts.out) return printJson(context2);
6581
7438
  if (opts.out === void 0) {
6582
- if (context.prompt.text) console.log(context.prompt.text);
6583
- if (context.skills.length > 0) {
7439
+ if (context2.prompt.text) console.log(context2.prompt.text);
7440
+ if (context2.skills.length > 0) {
6584
7441
  console.log(`
6585
- skills: ${context.skills.map((s) => s.name).join(", ")}`);
7442
+ skills: ${context2.skills.map((s) => s.name).join(", ")}`);
6586
7443
  }
6587
- for (const repo of context.repos) {
7444
+ for (const repo of context2.repos) {
6588
7445
  console.log(
6589
7446
  `repo: ${repo.name} ${repo.url}${repo.branch ? `#${repo.branch}` : ""} \u2192 ${repo.dir}/`
6590
7447
  );
6591
7448
  }
6592
- for (const o of context.overridden) {
7449
+ for (const o of context2.overridden) {
6593
7450
  console.log(
6594
7451
  `overridden: ${o.kind} "${o.name}" [${o.scope.label}] (overridden by ${o.overridden_by})`
6595
7452
  );
6596
7453
  }
6597
- for (const c of context.conflicts) {
7454
+ for (const c of context2.conflicts) {
6598
7455
  console.log(
6599
7456
  `conflict: repos ${c.item_ids.join(", ")} all resolve to checkout dir "${c.dir}"`
6600
7457
  );
6601
7458
  }
6602
7459
  return;
6603
7460
  }
6604
- if (context.conflicts.length > 0) {
7461
+ if (context2.conflicts.length > 0) {
6605
7462
  die(
6606
- `refusing to write: checkout-directory conflict${context.conflicts.length === 1 ? "" : "s"} among the effective repos (${context.conflicts.map((c) => `"${c.dir}": ${c.item_ids.join(", ")}`).join("; ")}); rename or re-dir the items first`
7463
+ `refusing to write: checkout-directory conflict${context2.conflicts.length === 1 ? "" : "s"} among the effective repos (${context2.conflicts.map((c) => `"${c.dir}": ${c.item_ids.join(", ")}`).join("; ")}); rename or re-dir the items first`
6607
7464
  );
6608
7465
  }
6609
7466
  if (existsSync3(opts.out) && readdirSync(opts.out).length > 0 && !opts.force) {
@@ -6612,20 +7469,20 @@ skills: ${context.skills.map((s) => s.name).join(", ")}`);
6612
7469
  mkdirSync2(opts.out, { recursive: true });
6613
7470
  writeFileSync2(
6614
7471
  join2(opts.out, "prompt.md"),
6615
- context.prompt.text ? `${context.prompt.text}
7472
+ context2.prompt.text ? `${context2.prompt.text}
6616
7473
  ` : ""
6617
7474
  );
6618
- for (const skill of context.skills) {
7475
+ for (const skill of context2.skills) {
6619
7476
  for (const file of skill.files) {
6620
7477
  const target = join2(opts.out, "skills", skill.name, file.path);
6621
7478
  mkdirSync2(dirname2(target), { recursive: true });
6622
7479
  writeFileSync2(target, file.content);
6623
7480
  }
6624
7481
  }
6625
- writeFileSync2(join2(opts.out, "repos.json"), `${JSON.stringify(context.repos, null, 2)}
7482
+ writeFileSync2(join2(opts.out, "repos.json"), `${JSON.stringify(context2.repos, null, 2)}
6626
7483
  `);
6627
7484
  console.log(
6628
- `wrote ${opts.out}/prompt.md, ${context.skills.length} skill${context.skills.length === 1 ? "" : "s"}, repos.json (${context.repos.length} repo${context.repos.length === 1 ? "" : "s"})`
7485
+ `wrote ${opts.out}/prompt.md, ${context2.skills.length} skill${context2.skills.length === 1 ? "" : "s"}, repos.json (${context2.repos.length} repo${context2.repos.length === 1 ? "" : "s"})`
6629
7486
  );
6630
7487
  });
6631
7488
  withCommon(
@@ -6646,9 +7503,9 @@ skills: ${context.skills.map((s) => s.name).join(", ")}`);
6646
7503
  ).action(async (ref, opts) => {
6647
7504
  const api = client(opts);
6648
7505
  const issue = await resolveIssue(api, ref);
6649
- const prompt = await api.getIssuePrompt(issue.id);
6650
- if (opts.json) return printJson(prompt);
6651
- console.log(prompt.text);
7506
+ const prompt2 = await api.getIssuePrompt(issue.id);
7507
+ if (opts.json) return printJson(prompt2);
7508
+ console.log(prompt2.text);
6652
7509
  });
6653
7510
  const artifactsCmd = issues.command("artifacts").description(
6654
7511
  "Typed, versioned attachments on an issue \u2014 the work products transition requirements gate on"
@@ -6711,11 +7568,11 @@ skills: ${context.skills.map((s) => s.name).join(", ")}`);
6711
7568
  v.reaffirmed_from !== null ? `reaffirmed v${v.reaffirmed_from}` : v.file_count !== null ? `${v.file_count} file${v.file_count === 1 ? "" : "s"}` : v.filename ?? v.url ?? (v.pr_repo_url ? prRefLabel(v) : "")
6712
7569
  ])
6713
7570
  );
6714
- const files = artifact.current_version.files;
6715
- if (files && files.length > 0) {
7571
+ const files2 = artifact.current_version.files;
7572
+ if (files2 && files2.length > 0) {
6716
7573
  console.log(`
6717
7574
  files (v${artifact.current_version.version}):`);
6718
- table(files.map((f) => [` ${f.path}`, f.content_type, `${f.size_bytes} bytes`]));
7575
+ table(files2.map((f) => [` ${f.path}`, f.content_type, `${f.size_bytes} bytes`]));
6719
7576
  }
6720
7577
  });
6721
7578
  withCommon(
@@ -6754,30 +7611,30 @@ files (v${artifact.current_version.version}):`);
6754
7611
  let siteHtml = null;
6755
7612
  if (plan.type === "folder") {
6756
7613
  const dir = plan.source.dir;
6757
- const files = walkFolder(dir);
6758
- if (files.length === 0) die(`${dir} contains no files to snapshot`);
6759
- const index = files.find((f) => f.path === ARTIFACT_SITE_INDEX);
7614
+ const files2 = walkFolder(dir);
7615
+ if (files2.length === 0) die(`${dir} contains no files to snapshot`);
7616
+ const index = files2.find((f) => f.path === ARTIFACT_SITE_INDEX);
6760
7617
  if (index) siteHtml = index.bytes.toString("utf8");
6761
- artifact = await api.uploadArtifactFolder(issue.id, name2, files);
7618
+ artifact = await api.uploadArtifactFolder(issue.id, name2, files2);
6762
7619
  if (opts.description !== void 0) {
6763
7620
  artifact = await api.putArtifact(issue.id, name2, { description: opts.description });
6764
7621
  }
6765
7622
  } else if (plan.type === "file") {
6766
7623
  const fromPath = plan.source.kind === "file-path" ? plan.source.path : null;
6767
- let bytes;
7624
+ let bytes2;
6768
7625
  if (fromPath === null) {
6769
7626
  if (process.stdin.isTTY) die('"-" reads the file from stdin, but stdin is a terminal');
6770
- bytes = readFileSync4(0);
7627
+ bytes2 = readFileSync4(0);
6771
7628
  } else {
6772
7629
  try {
6773
- bytes = readFileSync4(fromPath);
7630
+ bytes2 = readFileSync4(fromPath);
6774
7631
  } catch (err) {
6775
7632
  die(`cannot read ${fromPath}: ${err instanceof Error ? err.message : String(err)}`);
6776
7633
  }
6777
7634
  }
6778
7635
  const contentType = plan.contentType ?? (fromPath === null ? "application/octet-stream" : sniffContentType(fromPath));
6779
- if (siteEntry("file", contentType) !== null) siteHtml = bytes.toString("utf8");
6780
- artifact = await api.uploadArtifactFile(issue.id, name2, bytes, {
7636
+ if (siteEntry("file", contentType) !== null) siteHtml = bytes2.toString("utf8");
7637
+ artifact = await api.uploadArtifactFile(issue.id, name2, bytes2, {
6781
7638
  filename: plan.filename ?? (fromPath === null ? name2 : basename(fromPath)),
6782
7639
  contentType
6783
7640
  });
@@ -6901,9 +7758,9 @@ files (v${artifact.current_version.version}):`);
6901
7758
  `refusing to write folder artifact into non-empty directory "${opts.out}"; choose a new or empty directory`
6902
7759
  );
6903
7760
  }
6904
- const files = version.files ?? [];
7761
+ const files2 = version.files ?? [];
6905
7762
  let total = 0;
6906
- for (const file of files) {
7763
+ for (const file of files2) {
6907
7764
  const content2 = await api.getArtifactContent(issue.id, name2, {
6908
7765
  version: opts.version,
6909
7766
  path: file.path
@@ -6914,26 +7771,26 @@ files (v${artifact.current_version.version}):`);
6914
7771
  total += content2.bytes.byteLength;
6915
7772
  }
6916
7773
  return console.log(
6917
- `wrote ${files.length} file${files.length === 1 ? "" : "s"} (${total} bytes) from "${name2}" v${version.version} into ${opts.out}/`
7774
+ `wrote ${files2.length} file${files2.length === 1 ? "" : "s"} (${total} bytes) from "${name2}" v${version.version} into ${opts.out}/`
6918
7775
  );
6919
7776
  }
6920
7777
  const content = await api.getArtifactContent(issue.id, name2, { version: opts.version });
6921
- const bytes = Buffer.from(content.bytes);
7778
+ const bytes2 = Buffer.from(content.bytes);
6922
7779
  if (opts.out !== void 0) {
6923
7780
  let target2 = opts.out;
6924
7781
  if (existsSync3(target2) && statSync2(target2).isDirectory()) {
6925
7782
  target2 = join2(target2, version.filename ?? name2);
6926
7783
  }
6927
- writeFileSync2(target2, bytes);
6928
- return console.log(`wrote ${target2} (${bytes.byteLength} bytes, ${content.content_type})`);
7784
+ writeFileSync2(target2, bytes2);
7785
+ return console.log(`wrote ${target2} (${bytes2.byteLength} bytes, ${content.content_type})`);
6929
7786
  }
6930
7787
  if ((content.content_type ?? "").startsWith("text/")) {
6931
- process.stdout.write(bytes);
7788
+ process.stdout.write(bytes2);
6932
7789
  return;
6933
7790
  }
6934
7791
  const target = version.filename ?? name2;
6935
- writeFileSync2(target, bytes);
6936
- console.log(`wrote ${target} (${bytes.byteLength} bytes, ${content.content_type})`);
7792
+ writeFileSync2(target, bytes2);
7793
+ console.log(`wrote ${target} (${bytes2.byteLength} bytes, ${content.content_type})`);
6937
7794
  }
6938
7795
  );
6939
7796
  withCommon(
@@ -7016,14 +7873,14 @@ async function journalItemAt(api, scope) {
7016
7873
  );
7017
7874
  return items.find((i) => i.name === JOURNAL_NAME) ?? null;
7018
7875
  }
7019
- function stateScope(issue, state, workflow) {
7876
+ function stateScope(issue, state, workflow2) {
7020
7877
  return {
7021
7878
  project_id: issue.project_id,
7022
7879
  project_name: issue.project_name,
7023
7880
  workflow_state_id: state.id,
7024
7881
  workflow_state_name: state.name,
7025
- workflow_id: workflow.id,
7026
- workflow_name: workflow.name,
7882
+ workflow_id: workflow2.id,
7883
+ workflow_name: workflow2.name,
7027
7884
  issue_id: null,
7028
7885
  issue_ref: null,
7029
7886
  label_id: null,
@@ -7032,11 +7889,11 @@ function stateScope(issue, state, workflow) {
7032
7889
  label: `project ${issue.project_name} \xB7 state ${state.name}`
7033
7890
  };
7034
7891
  }
7035
- async function resolveJournal(api, ref, stateRef) {
7892
+ async function resolveJournal(api, ref, stateRef2) {
7036
7893
  const issue = await resolveIssue(api, ref);
7037
- if (stateRef !== void 0) {
7038
- const { workflow, state } = await resolveStateFlag(api, stateRef);
7039
- const scope = stateScope(issue, state, workflow);
7894
+ if (stateRef2 !== void 0) {
7895
+ const { workflow: workflow2, state } = await resolveStateFlag(api, stateRef2);
7896
+ const scope = stateScope(issue, state, workflow2);
7040
7897
  return { issue, scope, note: null, item: await journalItemAt(api, scope) };
7041
7898
  }
7042
7899
  try {
@@ -7336,14 +8193,14 @@ function register6(program3) {
7336
8193
  for (const starter of response.items) {
7337
8194
  console.log(`${starter.id} \u2014 ${starter.name}`);
7338
8195
  console.log(` ${starter.description}`);
7339
- for (const input of starter.inputs) {
8196
+ for (const input2 of starter.inputs) {
7340
8197
  console.log(
7341
- ` --${inputFlag(input)}${input.required ? " (required)" : " (optional)"} \u2014 ${input.description ?? input.label}`
8198
+ ` --${inputFlag(input2)}${input2.required ? " (required)" : " (optional)"} \u2014 ${input2.description ?? input2.label}`
7342
8199
  );
7343
8200
  }
7344
- for (const workflow of starter.creates.workflows) {
8201
+ for (const workflow2 of starter.creates.workflows) {
7345
8202
  console.log(
7346
- ` workflow: ${workflow.name}${workflow.default ? " (default)" : ""} \u2014 ${workflow.states.join(" \u2192 ")}`
8203
+ ` workflow: ${workflow2.name}${workflow2.default ? " (default)" : ""} \u2014 ${workflow2.states.join(" \u2192 ")}`
7347
8204
  );
7348
8205
  }
7349
8206
  for (const item of starter.creates.context) console.log(` ${item.kind}: ${item.name}`);
@@ -7391,7 +8248,7 @@ function register6(program3) {
7391
8248
  )
7392
8249
  ).action(
7393
8250
  async (name2, opts) => {
7394
- const prompt = typeof opts.prompt === "string" ? readBodyValue(opts.prompt) : void 0;
8251
+ const prompt2 = typeof opts.prompt === "string" ? readBodyValue(opts.prompt) : void 0;
7395
8252
  const suppliedInputs = [
7396
8253
  ["repo_url", "--repo", opts.repo],
7397
8254
  ["repo_branch", "--branch", opts.branch],
@@ -7413,7 +8270,7 @@ function register6(program3) {
7413
8270
  `unknown starter "${opts.starter}"; choose ${response.items.map((item) => item.id).join(", ")} (see \`tines projects starters\`)`
7414
8271
  );
7415
8272
  }
7416
- const declared = new Map(starter.inputs.map((input) => [input.key, input]));
8273
+ const declared = new Map(starter.inputs.map((input2) => [input2.key, input2]));
7417
8274
  for (const [key, flag, value] of suppliedInputs) {
7418
8275
  if (value !== void 0 && !declared.has(key))
7419
8276
  die(`${flag} is not used by starter "${starter.id}"`);
@@ -7422,14 +8279,14 @@ function register6(program3) {
7422
8279
  for (const [key, , value] of suppliedInputs) {
7423
8280
  if (value !== void 0 && declared.has(key)) inputs[key] = value.trim();
7424
8281
  }
7425
- for (const input of starter.inputs) {
7426
- const value = inputs[input.key] ?? "";
7427
- if (input.required && !value)
7428
- die(`starter "${starter.id}" requires --${inputFlag(input)}`);
7429
- const max = input.max ?? 1e4;
7430
- if (value.length > max) die(`--${inputFlag(input)} is longer than ${max} characters`);
8282
+ for (const input2 of starter.inputs) {
8283
+ const value = inputs[input2.key] ?? "";
8284
+ if (input2.required && !value)
8285
+ die(`starter "${starter.id}" requires --${inputFlag(input2)}`);
8286
+ const max = input2.max ?? 1e4;
8287
+ if (value.length > max) die(`--${inputFlag(input2)} is longer than ${max} characters`);
7431
8288
  }
7432
- if (starter.creates.workflows.some((workflow) => workflow.default) && opts.defaultWorkflow) {
8289
+ if (starter.creates.workflows.some((workflow2) => workflow2.default) && opts.defaultWorkflow) {
7433
8290
  die(`starter "${starter.id}" sets the default workflow; do not pass --default-workflow`);
7434
8291
  }
7435
8292
  starterRequest = { id: starter.id, inputs };
@@ -7444,7 +8301,7 @@ function register6(program3) {
7444
8301
  name: name2,
7445
8302
  description: opts.description,
7446
8303
  default_workflow_id: workflowId,
7447
- ...typeof opts.prompt === "string" ? { initial_prompt: prompt } : opts.prompt === false ? { initial_prompt: "" } : {},
8304
+ ...typeof opts.prompt === "string" ? { initial_prompt: prompt2 } : opts.prompt === false ? { initial_prompt: "" } : {},
7448
8305
  ...starterRequest ? { starter: starterRequest } : {}
7449
8306
  };
7450
8307
  const project = await api.createProject(body);
@@ -7453,9 +8310,9 @@ function register6(program3) {
7453
8310
  `created project "${project.name}" (${project.id})${typeof opts.prompt === "string" ? ' with its "conventions" prompt' : ""}`
7454
8311
  );
7455
8312
  if (project.starter) {
7456
- for (const workflow of project.starter.workflows) {
8313
+ for (const workflow2 of project.starter.workflows) {
7457
8314
  console.log(
7458
- `workflow: ${workflow.name} (${workflow.id})${workflow.reused ? " \u2014 reused" : ""}`
8315
+ `workflow: ${workflow2.name} (${workflow2.id})${workflow2.reused ? " \u2014 reused" : ""}`
7459
8316
  );
7460
8317
  }
7461
8318
  for (const item of project.starter.context)
@@ -7540,10 +8397,10 @@ default workflow: ${defaultWorkflow}`);
7540
8397
  console.log(`deleted project "${project.name}" (${project.id})`);
7541
8398
  });
7542
8399
  }
7543
- function inputFlag(input) {
7544
- if (input.key === "repo_url") return "repo";
7545
- if (input.key === "repo_branch") return "branch";
7546
- return input.key.replaceAll("_", "-");
8400
+ function inputFlag(input2) {
8401
+ if (input2.key === "repo_url") return "repo";
8402
+ if (input2.key === "repo_branch") return "branch";
8403
+ return input2.key.replaceAll("_", "-");
7547
8404
  }
7548
8405
  function isBlank(starter) {
7549
8406
  return starter.creates.workflows.length === 0 && starter.creates.context.length === 0 && starter.creates.first_issue === null;
@@ -7703,25 +8560,25 @@ function renderStreamEvent(event) {
7703
8560
  }
7704
8561
  return [];
7705
8562
  case "assistant": {
7706
- const lines = [];
8563
+ const lines2 = [];
7707
8564
  for (const block of event.message?.content ?? []) {
7708
8565
  if (block.type === "text" && block.text?.trim()) {
7709
- lines.push(`[agent] ${clip(block.text.trim(), 2e3)}`);
8566
+ lines2.push(`[agent] ${clip(block.text.trim(), 2e3)}`);
7710
8567
  } else if (block.type === "tool_use") {
7711
8568
  const args = block.input === void 0 ? "" : JSON.stringify(block.input);
7712
- lines.push(`[tool] ${block.name ?? "tool"} ${clip(args, 300)}`.trimEnd());
8569
+ lines2.push(`[tool] ${block.name ?? "tool"} ${clip(args, 300)}`.trimEnd());
7713
8570
  }
7714
8571
  }
7715
- return lines;
8572
+ return lines2;
7716
8573
  }
7717
8574
  case "user": {
7718
- const lines = [];
8575
+ const lines2 = [];
7719
8576
  for (const block of event.message?.content ?? []) {
7720
8577
  if (block.type === "tool_result" && block.is_error) {
7721
- lines.push(`[tool] error: ${clip(resultText(block.content), 300)}`);
8578
+ lines2.push(`[tool] error: ${clip(resultText(block.content), 300)}`);
7722
8579
  }
7723
8580
  }
7724
- return lines;
8581
+ return lines2;
7725
8582
  }
7726
8583
  case "result": {
7727
8584
  const parts = [];
@@ -7729,9 +8586,9 @@ function renderStreamEvent(event) {
7729
8586
  if (typeof event.total_cost_usd === "number")
7730
8587
  parts.push(`$${event.total_cost_usd.toFixed(2)}`);
7731
8588
  const detail = parts.length > 0 ? ` (${parts.join(", ")})` : "";
7732
- const lines = [`[session] result: ${event.subtype ?? "done"}${detail}`];
7733
- if (event.is_error && event.result) lines.push(`[error] ${clip(event.result, 2e3)}`);
7734
- return lines;
8589
+ const lines2 = [`[session] result: ${event.subtype ?? "done"}${detail}`];
8590
+ if (event.is_error && event.result) lines2.push(`[error] ${clip(event.result, 2e3)}`);
8591
+ return lines2;
7735
8592
  }
7736
8593
  case "rate_limit_event": {
7737
8594
  const info = event.rate_limit_info;
@@ -7975,16 +8832,16 @@ var CodexStreamRenderer = class {
7975
8832
  ).length : 0;
7976
8833
  const complete = supplied === 4 && Object.keys(rawUsage).length === 4 && rawCached + rawWrite <= totalInput;
7977
8834
  const previousInput = previous ? metric(previous.input_tokens) !== void 0 && metric(previous.cached_input_tokens) !== void 0 && metric(previous.cache_write_input_tokens) !== void 0 ? previous.input_tokens - previous.cached_input_tokens - previous.cache_write_input_tokens : void 0 : void 0;
7978
- const input = complete ? totalInput - rawCached - rawWrite : void 0;
8835
+ const input2 = complete ? totalInput - rawCached - rawWrite : void 0;
7979
8836
  let status = complete ? "complete" : supplied < 4 ? "missing" : "invalid";
7980
8837
  if (complete && previous && ["input_tokens", "cached_input_tokens", "cache_write_input_tokens", "output_tokens"].some(
7981
8838
  (key) => rawUsage[key] < previous[key]
7982
- ) || input !== void 0 && previousInput !== void 0 && input < previousInput) {
8839
+ ) || input2 !== void 0 && previousInput !== void 0 && input2 < previousInput) {
7983
8840
  this.stickyStatus = "nonmonotonic";
7984
8841
  }
7985
8842
  status = this.stickyStatus ?? status;
7986
8843
  const usage = {};
7987
- if (complete) usage.input_tokens = input;
8844
+ if (complete) usage.input_tokens = input2;
7988
8845
  if (rawCached !== void 0) usage.cache_read_tokens = rawCached;
7989
8846
  if (rawWrite !== void 0) usage.cache_write_tokens = rawWrite;
7990
8847
  if (output !== void 0) usage.output_tokens = output;
@@ -8211,17 +9068,17 @@ function keepWorkspace(mode, outcome) {
8211
9068
  function shellQuote(value) {
8212
9069
  return `'${value.replaceAll("'", `'\\''`)}'`;
8213
9070
  }
8214
- function expandCommandTemplate(template, input) {
8215
- return template.replaceAll("{prompt_file}", shellQuote(input.promptFile)).replaceAll("{workspace}", shellQuote(input.workspace)).replaceAll("{model}", input.model ? shellQuote(input.model) : "''");
9071
+ function expandCommandTemplate(template, input2) {
9072
+ return template.replaceAll("{prompt_file}", shellQuote(input2.promptFile)).replaceAll("{workspace}", shellQuote(input2.workspace)).replaceAll("{model}", input2.model ? shellQuote(input2.model) : "''");
8216
9073
  }
8217
- function buildHarnessInvocation(spec, input) {
9074
+ function buildHarnessInvocation(spec, input2) {
8218
9075
  switch (spec.harness) {
8219
9076
  case "claude_code":
8220
9077
  return {
8221
9078
  file: "sh",
8222
9079
  args: [
8223
9080
  "-c",
8224
- `claude -p${input.resumeSessionId ? ` --resume ${shellQuote(input.resumeSessionId)}` : ""} --output-format stream-json --verbose${input.model ? ` --model ${shellQuote(input.model)}` : ""} < ${shellQuote(input.promptFile)}`
9081
+ `claude -p${input2.resumeSessionId ? ` --resume ${shellQuote(input2.resumeSessionId)}` : ""} --output-format stream-json --verbose${input2.model ? ` --model ${shellQuote(input2.model)}` : ""} < ${shellQuote(input2.promptFile)}`
8225
9082
  ]
8226
9083
  };
8227
9084
  case "codex":
@@ -8231,13 +9088,13 @@ function buildHarnessInvocation(spec, input) {
8231
9088
  "exec",
8232
9089
  "--json",
8233
9090
  "--skip-git-repo-check",
8234
- ...input.model ? ["--model", input.model] : [],
8235
- input.prompt
9091
+ ...input2.model ? ["--model", input2.model] : [],
9092
+ input2.prompt
8236
9093
  ]
8237
9094
  };
8238
9095
  case "custom": {
8239
9096
  if (!spec.command) throw new Error("the custom harness needs a --command template");
8240
- return { file: "sh", args: ["-c", expandCommandTemplate(spec.command, input)] };
9097
+ return { file: "sh", args: ["-c", expandCommandTemplate(spec.command, input2)] };
8241
9098
  }
8242
9099
  }
8243
9100
  }
@@ -8253,16 +9110,16 @@ function formatLaunchCommand(invocation) {
8253
9110
  return invocation.args[1];
8254
9111
  return [invocation.file, ...invocation.args].map(launchWord).join(" ");
8255
9112
  }
8256
- function formatLaunchBanner(invocation, input, meta) {
9113
+ function formatLaunchBanner(invocation, input2, meta) {
8257
9114
  const fields = [
8258
9115
  `harness=${meta.harness}`,
8259
- `model=${input.model ?? "(fixed)"}`,
9116
+ `model=${input2.model ?? "(fixed)"}`,
8260
9117
  `timeout=${meta.timeoutMinutes}m`,
8261
9118
  `cli=${meta.cliVersion}`,
8262
- `workspace=${input.workspace}`,
9119
+ `workspace=${input2.workspace}`,
8263
9120
  // The one line that says a send-back reused a session rather than
8264
9121
  // re-exploring the repository from zero.
8265
- ...input.resumeSessionId ? [`resumed=${meta.resumedFromRunId ?? input.resumeSessionId}`] : []
9122
+ ...input2.resumeSessionId ? [`resumed=${meta.resumedFromRunId ?? input2.resumeSessionId}`] : []
8266
9123
  ];
8267
9124
  return `$ ${formatLaunchCommand(invocation)}
8268
9125
  # tines runner: ${fields.join(" ")}
@@ -8445,9 +9302,9 @@ var LogBatcher = class {
8445
9302
  let lost = 0;
8446
9303
  while (this.pending.length > 1 && held > cap) {
8447
9304
  const dropped = this.pending.shift();
8448
- const bytes = Buffer.byteLength(dropped.chunk, "utf8");
8449
- held -= bytes;
8450
- lost += bytes;
9305
+ const bytes2 = Buffer.byteLength(dropped.chunk, "utf8");
9306
+ held -= bytes2;
9307
+ lost += bytes2;
8451
9308
  }
8452
9309
  const head = this.pending[0];
8453
9310
  head.chunk = `[log] ${lost} bytes lost (supervisor unreachable)
@@ -8602,8 +9459,8 @@ async function installAgentCli(opts) {
8602
9459
  return { binDir: binDirOf(prefix), version, source: "fresh" };
8603
9460
  }
8604
9461
  function tail(output) {
8605
- const lines = output.trim().split("\n").filter(Boolean).slice(-3);
8606
- return lines.length > 0 ? `: ${lines.join(" / ")}` : "";
9462
+ const lines2 = output.trim().split("\n").filter(Boolean).slice(-3);
9463
+ return lines2.length > 0 ? `: ${lines2.join(" / ")}` : "";
8607
9464
  }
8608
9465
  function message(err) {
8609
9466
  return err instanceof Error ? err.message : String(err);
@@ -10269,7 +11126,352 @@ function register10(program3) {
10269
11126
  }
10270
11127
 
10271
11128
  // src/commands/workflows.ts
11129
+ import { readFileSync as readFileSync11 } from "node:fs";
11130
+
11131
+ // src/workflow-packages.ts
11132
+ import { createInterface as createInterface4 } from "node:readline/promises";
11133
+ import { createHash } from "node:crypto";
10272
11134
  import { readFileSync as readFileSync10 } from "node:fs";
11135
+ var PACKAGE_STDIN = "-";
11136
+ var MAX_LOCAL_JSON_BYTES = 32 * 1024 * 1024;
11137
+ function readPackageSource(path2) {
11138
+ try {
11139
+ const bytes2 = readFileSync10(path2 === PACKAGE_STDIN ? 0 : path2);
11140
+ if (bytes2.byteLength > MAX_LOCAL_JSON_BYTES)
11141
+ throw new Error(`input exceeds ${MAX_LOCAL_JSON_BYTES} bytes`);
11142
+ return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes2);
11143
+ } catch (error) {
11144
+ throw new Error(
11145
+ `cannot read ${path2 === PACKAGE_STDIN ? "stdin" : path2}: ${error instanceof Error ? error.message : String(error)}`
11146
+ );
11147
+ }
11148
+ }
11149
+ function readStrictObject(path2, label) {
11150
+ const value = parseStrictLibraryJson(readPackageSource(path2));
11151
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
11152
+ throw new Error(`${label} must contain a JSON object`);
11153
+ }
11154
+ return value;
11155
+ }
11156
+ function normalizeApiBase(value) {
11157
+ return new URL(value).toString().replace(/\/$/, "");
11158
+ }
11159
+ function saveWorkflowPackagePlan(path2, apiBase, plan) {
11160
+ const saved = {
11161
+ format: "tines.workflow-install-plan",
11162
+ version: 1,
11163
+ api_base: normalizeApiBase(apiBase),
11164
+ document_digest: plan.document_digest,
11165
+ plan
11166
+ };
11167
+ writeJsonFile(path2, saved, { secret: true });
11168
+ return saved;
11169
+ }
11170
+ function readWorkflowPackagePlan(path2) {
11171
+ const saved = readStrictObject(path2, "plan file");
11172
+ const outerKeys = ["format", "version", "api_base", "document_digest", "plan"];
11173
+ const planKeys = [
11174
+ "operations",
11175
+ "document",
11176
+ "resolved",
11177
+ "allocation",
11178
+ "plan_id",
11179
+ "plan_digest",
11180
+ "document_digest",
11181
+ "issued_at",
11182
+ "expires_at",
11183
+ "actor_key",
11184
+ "compiler_version",
11185
+ "plan_token",
11186
+ "budget"
11187
+ ];
11188
+ if (Object.keys(saved).length !== outerKeys.length || !Object.keys(saved).every((key) => outerKeys.includes(key)) || saved.format !== "tines.workflow-install-plan" || saved.version !== 1 || typeof saved.api_base !== "string" || typeof saved.document_digest !== "string" || !saved.plan || typeof saved.plan !== "object" || typeof saved.plan.plan_token !== "string" || typeof saved.plan.plan_digest !== "string" || Object.keys(saved.plan).length !== planKeys.length || !Object.keys(saved.plan).every((key) => planKeys.includes(key))) {
11189
+ throw new Error("invalid workflow package plan file");
11190
+ }
11191
+ const result = saved;
11192
+ assertPlanBinding(result.plan);
11193
+ return result;
11194
+ }
11195
+ function tokenPayload(token) {
11196
+ const parts = token.split(".");
11197
+ if (token.length > 7e5 || parts.length !== 3 || parts[0] !== "wip1" || !parts.slice(1).every((part) => /^[A-Za-z0-9_-]+$/.test(part)))
11198
+ throw new Error("invalid workflow package plan token");
11199
+ try {
11200
+ const bytes2 = Buffer.from(parts[1], "base64url");
11201
+ if (bytes2.toString("base64url") !== parts[1]) throw new Error();
11202
+ const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes2));
11203
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error();
11204
+ return value;
11205
+ } catch {
11206
+ throw new Error("invalid workflow package plan token");
11207
+ }
11208
+ }
11209
+ function expectedWorkflowPackageOperations(plan) {
11210
+ const operations = [];
11211
+ for (const workflow2 of plan.resolved.workflows) {
11212
+ const workflowId = plan.allocation.records[workflow2.id]?.id;
11213
+ const href = `/workflows/${workflowId}`;
11214
+ operations.push({
11215
+ action: "create",
11216
+ kind: "workflow",
11217
+ local_id: workflow2.id,
11218
+ id: workflowId,
11219
+ name: workflow2.name,
11220
+ href,
11221
+ relationship: workflow2.id === plan.document.main_workflow_id ? "main" : "dependency"
11222
+ });
11223
+ for (const state of workflow2.states)
11224
+ operations.push({
11225
+ action: "create",
11226
+ kind: "state",
11227
+ local_id: state.id,
11228
+ id: plan.allocation.records[state.id]?.id,
11229
+ name: state.name,
11230
+ href: `${href}#state-${plan.allocation.records[state.id]?.id}`
11231
+ });
11232
+ for (const transition of workflow2.transitions)
11233
+ operations.push({
11234
+ action: "create",
11235
+ kind: "transition",
11236
+ local_id: transition.id,
11237
+ id: plan.allocation.records[transition.id]?.id,
11238
+ name: transition.name,
11239
+ href
11240
+ });
11241
+ }
11242
+ for (const item of plan.resolved.context) {
11243
+ const workflow2 = plan.resolved.workflows.find(
11244
+ (candidate) => candidate.states.some((state) => state.id === item.state_id)
11245
+ );
11246
+ const href = `/context?workflow=${plan.allocation.records[workflow2?.id ?? ""]?.id}&q=${encodeURIComponent(item.name)}`;
11247
+ operations.push({
11248
+ action: "create",
11249
+ kind: item.kind,
11250
+ local_id: item.id,
11251
+ id: plan.allocation.records[item.id]?.id,
11252
+ name: item.name,
11253
+ href
11254
+ });
11255
+ if (item.kind === "skill")
11256
+ for (const file of item.files)
11257
+ operations.push({
11258
+ action: "create",
11259
+ kind: "file",
11260
+ local_id: file.id,
11261
+ id: plan.allocation.records[file.id]?.id,
11262
+ name: file.path,
11263
+ href
11264
+ });
11265
+ }
11266
+ for (const input2 of plan.resolved.inputs) {
11267
+ if (input2.mode === "create")
11268
+ operations.push({
11269
+ action: "create",
11270
+ kind: "label",
11271
+ local_id: input2.input_id,
11272
+ id: input2.id,
11273
+ name: input2.value,
11274
+ href: "/labels"
11275
+ });
11276
+ if (input2.mode === "reuse")
11277
+ operations.push({
11278
+ action: "reuse",
11279
+ kind: input2.type,
11280
+ local_id: input2.input_id,
11281
+ id: input2.id,
11282
+ name: input2.value,
11283
+ href: input2.type === "workflow" ? `/workflows/${input2.id}` : input2.type === "project" ? `/projects/${input2.id}` : "/labels"
11284
+ });
11285
+ }
11286
+ for (const skip of plan.resolved.skipped)
11287
+ operations.push({
11288
+ action: "skip",
11289
+ kind: skip.kind,
11290
+ local_id: skip.local_id,
11291
+ id: null,
11292
+ name: skip.kind === "schedule" ? plan.document.schedules.find((item) => item.id === skip.local_id)?.name : plan.document.routing.find((item) => item.id === skip.local_id)?.tier,
11293
+ href: null
11294
+ });
11295
+ return operations;
11296
+ }
11297
+ function assertPlanBinding(plan) {
11298
+ const payload = tokenPayload(plan.plan_token);
11299
+ const { plan_digest: signedDigest, ...unsigned } = payload;
11300
+ const digest = `sha256:${createHash("sha256").update(canonicalizeLibraryValue({ plan: unsigned, resolved: plan.resolved })).digest("hex")}`;
11301
+ const mirrors = {
11302
+ id: plan.plan_id,
11303
+ plan_digest: plan.plan_digest,
11304
+ document_digest: plan.document_digest,
11305
+ issued_at: plan.issued_at,
11306
+ expires_at: plan.expires_at,
11307
+ actor_key: plan.actor_key,
11308
+ compiler_version: plan.compiler_version,
11309
+ allocation: plan.allocation,
11310
+ budget: plan.budget
11311
+ };
11312
+ for (const [field, value] of Object.entries(mirrors)) {
11313
+ const signedField = field === "id" ? "id" : field;
11314
+ if (canonicalizeLibraryValue(payload[signedField]) !== canonicalizeLibraryValue(value))
11315
+ throw new Error(`saved workflow package plan has modified ${field}`);
11316
+ }
11317
+ if (signedDigest !== digest || plan.plan_digest !== digest)
11318
+ throw new Error("saved workflow package plan digest does not bind its review");
11319
+ if (canonicalizeLibraryValue(plan.operations) !== canonicalizeLibraryValue(expectedWorkflowPackageOperations(plan)))
11320
+ throw new Error("saved workflow package plan has modified operations");
11321
+ }
11322
+ var lines = (heading, values) => values.length ? [heading, ...values.map((value) => ` ${value}`)] : [heading, " none"];
11323
+ function formatWorkflowPackageReview(plan) {
11324
+ const { document, resolved } = plan;
11325
+ const output = [
11326
+ `Workflow package plan ${plan.plan_digest}`,
11327
+ ` file: ${plan.document_digest}`,
11328
+ ` valid until: ${new Date(plan.expires_at).toISOString()}`,
11329
+ ` budget: ${plan.budget.statements} statements, ${plan.budget.max_parameters} parameters, ${plan.budget.max_sql_bytes} SQL bytes, ${plan.budget.max_value_bytes} value bytes`,
11330
+ "",
11331
+ ...lines(
11332
+ "Operations:",
11333
+ plan.operations.map(
11334
+ (operation) => `${operation.action} ${operation.kind} ${operation.local_id}: ${operation.name}${operation.relationship ? ` (${operation.relationship})` : ""}${operation.id ? ` [${operation.id}]` : ""}`
11335
+ )
11336
+ ),
11337
+ "",
11338
+ ...lines(
11339
+ "Workflows and gates:",
11340
+ document.workflows.flatMap((workflow2) => [
11341
+ `${workflow2.id}: ${resolved.names[workflow2.id] ?? workflow2.name} \u2014 ${workflow2.description}`,
11342
+ ...workflow2.states.map(
11343
+ (state) => ` state ${state.id}: ${state.name} (${state.category})${state.inherits_from ? ` inherits ${state.inherits_from.state_id}` : ""}`
11344
+ ),
11345
+ ...workflow2.transitions.map(
11346
+ (transition) => ` transition ${transition.id}: ${transition.name} ${transition.from_state_id} -> ${transition.to_state_id}; gates ${transition.requires.length ? transition.requires.map((gate) => `${gate.artifact}${gate.type ? `:${gate.type}` : ""}${gate.content_type ? `:${gate.content_type}` : ""}`).join(", ") : "none"}`
11347
+ )
11348
+ ])
11349
+ ),
11350
+ "",
11351
+ ...lines(
11352
+ "Inputs:",
11353
+ document.inputs.map((declaration) => {
11354
+ const input2 = resolved.inputs.find((candidate) => candidate.input_id === declaration.id);
11355
+ return `${declaration.id} ${declaration.key}: ${declaration.label} \u2014 ${declaration.description}
11356
+ type: ${declaration.type}; required: ${declaration.required ? "yes" : "no"}; default: ${declaration.default ?? "none"}; required states: ${declaration.required_states?.join(", ") || "none"}
11357
+ resolution: ${input2?.mode ?? "missing"}; value: ${input2?.value ?? "missing"}${input2?.id ? ` [${input2.id}]` : ""}`;
11358
+ })
11359
+ ),
11360
+ "",
11361
+ ...lines(
11362
+ "Original and rendered text:",
11363
+ resolved.patches.map(
11364
+ (patch) => `${patch.record_id}.${patch.field}
11365
+ original: ${patch.original}
11366
+ rendered: ${patch.rendered}
11367
+ uses: ${patch.uses.length ? patch.uses.map((use) => `${use.id} -> ${use.input_id} (${use.count} occurrence${use.count === 1 ? "" : "s"})`).join(", ") : "none"}`
11368
+ )
11369
+ ),
11370
+ "",
11371
+ ...lines(
11372
+ "Context:",
11373
+ document.context.flatMap((item) => {
11374
+ const header = `${item.kind} ${item.id} in ${item.state_id}: ${item.name} \u2014 ${item.description}`;
11375
+ if (item.kind === "prompt") return [header, ` body: ${item.body}`];
11376
+ if (item.kind === "repo")
11377
+ return [
11378
+ header,
11379
+ ` repository: ${item.repo_url}; branch ${item.repo_branch ?? "default"}; directory ${item.repo_dir ?? "default"}`
11380
+ ];
11381
+ return [
11382
+ header,
11383
+ ...item.files.map((file) => ` file ${file.id} ${file.path}:
11384
+ ${file.content}`)
11385
+ ];
11386
+ })
11387
+ ),
11388
+ "",
11389
+ ...lines(
11390
+ "Schedules:",
11391
+ resolved.schedules.map(
11392
+ (schedule2) => `${schedule2.local_id}: ${resolved.names[schedule2.local_id] ?? schedule2.definition.name}; project ${schedule2.project_id}; ${JSON.stringify(schedule2.definition)}`
11393
+ )
11394
+ ),
11395
+ "",
11396
+ ...lines(
11397
+ "Routing capabilities:",
11398
+ resolved.routing.flatMap((routing) => [
11399
+ `${routing.local_id}: ${routing.tier}; winning rule ${routing.winning_rule_id}`,
11400
+ ...routing.targets.map(
11401
+ (target) => ` ${target.name} (${target.status}) ${target.model ?? "default model"} / ${target.config}; supported ${target.supported ? "yes" : "no"}`
11402
+ ),
11403
+ ...routing.warnings.map((warning) => ` warning: ${warning}`)
11404
+ ])
11405
+ ),
11406
+ "",
11407
+ ...lines(
11408
+ "Skipped:",
11409
+ resolved.skipped.map((item) => `${item.kind} ${item.local_id}`)
11410
+ )
11411
+ ];
11412
+ return output.join("\n");
11413
+ }
11414
+ function formatValidation(result) {
11415
+ const output = [result.valid ? "valid workflow package" : "invalid workflow package"];
11416
+ if (result.digest) output.push(`digest: ${result.digest}`);
11417
+ for (const diagnostic of result.diagnostics) {
11418
+ output.push(`${diagnostic.path || "/"}: ${diagnostic.code}: ${diagnostic.message}`);
11419
+ }
11420
+ output.push(
11421
+ `limits: ${result.limits.max_document_bytes} bytes, ${result.limits.max_records} records, depth ${result.limits.max_depth}`
11422
+ );
11423
+ return output.join("\n");
11424
+ }
11425
+ async function localDocument(path2) {
11426
+ const raw = readPackageSource(path2);
11427
+ const document = await parseLibraryV3Document(raw);
11428
+ if (document.profile !== "workflow") throw new Error("expected a workflow-profile package");
11429
+ return { raw, document };
11430
+ }
11431
+ function canonicalWorkflowPackage(document) {
11432
+ return canonicalizeLibraryValue(document);
11433
+ }
11434
+ async function askToInstall(plan) {
11435
+ const rl = createInterface4({ input: process.stdin, output: process.stderr });
11436
+ try {
11437
+ process.stderr.write(`${formatWorkflowPackageReview(plan)}
11438
+ `);
11439
+ const answer = await rl.question(`Install exactly plan ${plan.plan_digest}? [y/N] `).catch(() => "");
11440
+ return /^y(es)?$/i.test(answer.trim());
11441
+ } finally {
11442
+ rl.close();
11443
+ }
11444
+ }
11445
+ async function recoverOrInstall(api, raw, plan) {
11446
+ const checked = (receipt) => {
11447
+ if (receipt.id !== plan.plan_id || receipt.document_digest !== plan.document_digest || receipt.plan_digest !== plan.plan_digest)
11448
+ throw new Error("recovered workflow package receipt does not match the saved plan");
11449
+ return receipt;
11450
+ };
11451
+ try {
11452
+ return checked(await api.getWorkflowPackageReceipt(plan.plan_id));
11453
+ } catch (error) {
11454
+ if (!(error instanceof ApiError) || error.status !== 404) throw error;
11455
+ }
11456
+ const request = {
11457
+ document_json: raw,
11458
+ plan_token: plan.plan_token,
11459
+ confirmation: { plan_digest: plan.plan_digest }
11460
+ };
11461
+ try {
11462
+ return checked(await api.installWorkflowPackage(request));
11463
+ } catch (first) {
11464
+ if (!(first instanceof ApiNetworkError)) throw first;
11465
+ try {
11466
+ return checked(await api.getWorkflowPackageReceipt(plan.plan_id));
11467
+ } catch (recovery) {
11468
+ if (!(recovery instanceof ApiError) || recovery.status !== 404) throw first;
11469
+ }
11470
+ return checked(await api.installWorkflowPackage(request));
11471
+ }
11472
+ }
11473
+
11474
+ // src/commands/workflows.ts
10273
11475
  function readJsonBody(inline, file) {
10274
11476
  if (inline !== void 0 && file !== void 0) {
10275
11477
  die("pass the JSON inline or with --file, not both");
@@ -10278,14 +11480,14 @@ function readJsonBody(inline, file) {
10278
11480
  if (file !== void 0 && file !== "-") {
10279
11481
  let raw;
10280
11482
  try {
10281
- raw = readFileSync10(file, "utf8");
11483
+ raw = readFileSync11(file, "utf8");
10282
11484
  } catch (err) {
10283
11485
  die(`cannot read ${file}: ${err instanceof Error ? err.message : String(err)}`);
10284
11486
  }
10285
11487
  return parseJsonObject(raw, file);
10286
11488
  }
10287
11489
  if (file === "-" || !process.stdin.isTTY) {
10288
- const raw = readFileSync10(0, "utf8");
11490
+ const raw = readFileSync11(0, "utf8");
10289
11491
  if (raw.trim() === "") {
10290
11492
  if (file === "-") die("no JSON on stdin");
10291
11493
  return void 0;
@@ -10351,8 +11553,8 @@ name issues must carry (see: tines issues artifacts --help).
10351
11553
  async function loadLibrary(api) {
10352
11554
  const workflows = await listAll((page) => api.listWorkflows(page));
10353
11555
  const states = /* @__PURE__ */ new Map();
10354
- for (const workflow of workflows) {
10355
- for (const state of workflow.states) states.set(state.id, { workflow, state });
11556
+ for (const workflow2 of workflows) {
11557
+ for (const state of workflow2.states) states.set(state.id, { workflow: workflow2, state });
10356
11558
  }
10357
11559
  const children = /* @__PURE__ */ new Map();
10358
11560
  for (const entry of states.values()) {
@@ -10370,13 +11572,13 @@ function stateLabel(lib, id2) {
10370
11572
  return entry ? qualify(entry) : id2;
10371
11573
  }
10372
11574
  function inheritanceLines(lib, state) {
10373
- const lines = [];
11575
+ const lines2 = [];
10374
11576
  if (state.inherits_from !== null) {
10375
- lines.push(` inherits from: ${stateLabel(lib, state.inherits_from)}`);
11577
+ lines2.push(` inherits from: ${stateLabel(lib, state.inherits_from)}`);
10376
11578
  }
10377
11579
  const kids = lib.children.get(state.id) ?? [];
10378
- if (kids.length > 0) lines.push(` inherited by: ${kids.map(qualify).join(", ")}`);
10379
- return lines;
11580
+ if (kids.length > 0) lines2.push(` inherited by: ${kids.map(qualify).join(", ")}`);
11581
+ return lines2;
10380
11582
  }
10381
11583
  async function resolveStateBases(states, library) {
10382
11584
  if (!Array.isArray(states)) return;
@@ -10396,24 +11598,24 @@ async function resolveStateBases(states, library) {
10396
11598
  function resolveBasePair(lib, pair, stateName) {
10397
11599
  const sep2 = pair.indexOf("/");
10398
11600
  const workflowRef = pair.slice(0, sep2).trim();
10399
- const stateRef = pair.slice(sep2 + 1).trim();
11601
+ const stateRef2 = pair.slice(sep2 + 1).trim();
10400
11602
  const where = `state "${stateName}" inherits from "${pair}"`;
10401
- if (!workflowRef || !stateRef) {
11603
+ if (!workflowRef || !stateRef2) {
10402
11604
  die(`${where}, which is not a <workflow>/<state> pair`);
10403
11605
  }
10404
11606
  const byName = lib.workflows.filter((w) => w.name === workflowRef);
10405
11607
  if (byName.length > 1)
10406
11608
  die(`${where}, but workflow name "${workflowRef}" is ambiguous; use an id`);
10407
- const workflow = lib.workflows.find((w) => w.id === workflowRef) ?? byName[0];
10408
- if (!workflow) {
11609
+ const workflow2 = lib.workflows.find((w) => w.id === workflowRef) ?? byName[0];
11610
+ if (!workflow2) {
10409
11611
  die(
10410
11612
  `${where}, but there is no workflow "${workflowRef}" (have: ${lib.workflows.map((w) => w.name).join(", ")})`
10411
11613
  );
10412
11614
  }
10413
- const state = workflow.states.find((s) => s.name === stateRef) ?? workflow.states.find((s) => s.id === stateRef);
11615
+ const state = workflow2.states.find((s) => s.name === stateRef2) ?? workflow2.states.find((s) => s.id === stateRef2);
10414
11616
  if (!state) {
10415
11617
  die(
10416
- `${where}, but workflow "${workflow.name}" has no state "${stateRef}" (have: ${workflow.states.map((s) => s.name).join(", ")})`
11618
+ `${where}, but workflow "${workflow2.name}" has no state "${stateRef2}" (have: ${workflow2.states.map((s) => s.name).join(", ")})`
10417
11619
  );
10418
11620
  }
10419
11621
  return state.id;
@@ -10451,6 +11653,7 @@ warning: ${w}`);
10451
11653
  }
10452
11654
  function register11(program3) {
10453
11655
  const workflows = program3.command("workflows").description("Manage the workflow library");
11656
+ registerPackageCommands(workflows);
10454
11657
  withList(workflows.command("list").description("List the workflow library")).action(
10455
11658
  async (opts) => {
10456
11659
  const api = client(opts);
@@ -10561,6 +11764,147 @@ see \`tines workflows create --help\` for the expected shape`
10561
11764
  console.log(`deleted workflow "${wf.name}" (${wf.id})`);
10562
11765
  });
10563
11766
  }
11767
+ var collect2 = (value, previous) => [...previous, value];
11768
+ async function resolveExportState(api, ref) {
11769
+ const all = await listAll((page) => api.listWorkflows(page));
11770
+ const exact = all.flatMap((workflow2) => workflow2.states).find((state) => state.id === ref);
11771
+ if (exact) return exact.id;
11772
+ if (ref.includes("/")) {
11773
+ const separator = ref.indexOf("/");
11774
+ const workflow2 = pickWorkflow(all, ref.slice(0, separator).trim());
11775
+ const states = workflow2.states.filter(
11776
+ (state) => state.name === ref.slice(separator + 1).trim()
11777
+ );
11778
+ if (states.length === 1) return states[0].id;
11779
+ }
11780
+ const named = all.flatMap((workflow2) => workflow2.states).filter((state) => state.name === ref);
11781
+ if (named.length === 1) return named[0].id;
11782
+ if (named.length > 1) die(`state name "${ref}" is ambiguous; use an id`);
11783
+ die(`no state named "${ref}"; use a state id or <workflow>/<state>`);
11784
+ }
11785
+ function registerPackageCommands(workflows) {
11786
+ withCommon(
11787
+ workflows.command("export <workflow-id-or-unambiguous-name>").description("Export a canonical workflow package JSON document").option("--project <id-or-name>", "source project for selected project-bound configuration").option("--schedule <id>", "include this source schedule ID (repeatable)", collect2, []).option(
11788
+ "--tier <state-ref=tier>",
11789
+ "include a state tier preference (repeatable)",
11790
+ collect2,
11791
+ []
11792
+ ).option("--project-routing", "make every --tier selector project scoped").option("--inputs <file>", "JSON object containing input declarations and text uses")
11793
+ ).action(
11794
+ async (ref, opts) => {
11795
+ const api = client(opts);
11796
+ const all = await listAll((page) => api.listWorkflows(page));
11797
+ const workflow2 = pickWorkflow(all, ref);
11798
+ const sourceProject = opts.project ? await resolveProject(api, opts.project) : void 0;
11799
+ if ((opts.schedule.length || opts.projectRouting) && !sourceProject)
11800
+ die("--project is required with --schedule or --project-routing");
11801
+ const tiers = [];
11802
+ for (const selector of opts.tier) {
11803
+ const separator = selector.lastIndexOf("=");
11804
+ if (separator < 1) die(`invalid --tier "${selector}"; expected <state-ref>=<tier>`);
11805
+ const tier = selector.slice(separator + 1);
11806
+ if (!["smartest", "balanced", "cheapest"].includes(tier))
11807
+ die(`invalid tier "${tier}"; expected smartest, balanced, or cheapest`);
11808
+ tiers.push({
11809
+ state_id: await resolveExportState(api, selector.slice(0, separator)),
11810
+ tier,
11811
+ project_scoped: Boolean(opts.projectRouting)
11812
+ });
11813
+ }
11814
+ const authoring = opts.inputs ? readStrictObject(opts.inputs, "inputs file") : void 0;
11815
+ const document = await api.exportWorkflowPackage(workflow2.id, {
11816
+ source_project_id: sourceProject?.id,
11817
+ schedule_ids: opts.schedule,
11818
+ tiers,
11819
+ authoring
11820
+ });
11821
+ process.stdout.write(`${canonicalWorkflowPackage(document)}
11822
+ `);
11823
+ }
11824
+ );
11825
+ withCommon(
11826
+ workflows.command("validate <file>").description("Validate a workflow package file (use - for stdin)")
11827
+ ).action(async (path2, opts) => {
11828
+ const result = await client(opts).validateLibrary({ document_json: readPackageSource(path2) });
11829
+ if (opts.json) printJson(result);
11830
+ else console.log(formatValidation(result));
11831
+ if (!result.valid) process.exitCode = 1;
11832
+ });
11833
+ withCommon(
11834
+ workflows.command("preview <file>").description("Prepare and fully review a destination workflow package plan").option("--choices <file>", "destination choices JSON file").option("--plan-out <file>", "atomically save the signed plan for a later install")
11835
+ ).action(async (path2, opts) => {
11836
+ const raw = readPackageSource(path2);
11837
+ const choices = opts.choices ? readStrictObject(opts.choices, "choices file") : void 0;
11838
+ const plan = await client(opts).prepareWorkflowPackage({ document_json: raw, choices });
11839
+ if (opts.planOut) saveWorkflowPackagePlan(opts.planOut, resolveUrl(opts), plan);
11840
+ if (opts.json) printJson(plan);
11841
+ else console.log(formatWorkflowPackageReview(plan));
11842
+ });
11843
+ withCommon(
11844
+ workflows.command("install <file>").description("Install one exactly reviewed workflow package plan").option("--choices <file>", "destination choices JSON file (interactive preparation only)").option("--plan <file>", "signed plan saved by workflows preview").option("--confirm <plan-digest>", "exact reviewed plan digest (required outside a TTY)")
11845
+ ).action(
11846
+ async (path2, opts) => {
11847
+ if (path2 === "-" && process.stdin.isTTY)
11848
+ die('"-" reads the package from stdin, but stdin is a terminal');
11849
+ if (!opts.plan && !process.stdin.isTTY)
11850
+ die(
11851
+ "non-interactive install requires a separate prior preview with --plan-out, then --plan and --confirm"
11852
+ );
11853
+ if (opts.plan && opts.choices)
11854
+ die("--choices cannot be used with --plan; the saved choices are authoritative");
11855
+ if (path2 === "-" && !opts.plan)
11856
+ die(
11857
+ "stdin package input cannot also provide interactive confirmation; use preview --plan-out first"
11858
+ );
11859
+ const { raw, document } = await localDocument(path2);
11860
+ const apiBase = normalizeUrl(resolveUrl(opts));
11861
+ const api = client(opts);
11862
+ let plan;
11863
+ let planPath = opts.plan;
11864
+ if (planPath) {
11865
+ const saved = readWorkflowPackagePlan(planPath);
11866
+ if (normalizeUrl(saved.api_base) !== apiBase)
11867
+ die(`plan belongs to ${saved.api_base}, not ${apiBase}`);
11868
+ if (saved.document_digest !== document.digest || saved.plan.document_digest !== document.digest || canonicalWorkflowPackage(saved.plan.document) !== canonicalWorkflowPackage(document))
11869
+ die(
11870
+ `package digest ${document.digest} does not match saved plan ${saved.document_digest}`
11871
+ );
11872
+ plan = saved.plan;
11873
+ } else {
11874
+ const choices = opts.choices ? readStrictObject(opts.choices, "choices file") : void 0;
11875
+ plan = await api.prepareWorkflowPackage({ document_json: raw, choices });
11876
+ planPath = `${path2}.plan.json`;
11877
+ saveWorkflowPackagePlan(planPath, apiBase, plan);
11878
+ console.error(`saved retryable signed plan to ${planPath}`);
11879
+ }
11880
+ if (opts.confirm !== void 0 && opts.confirm !== plan.plan_digest)
11881
+ die(`confirmation digest does not match reviewed plan ${plan.plan_digest}`);
11882
+ if (!process.stdin.isTTY && opts.confirm !== plan.plan_digest)
11883
+ die(`non-interactive install requires --confirm ${plan.plan_digest}`);
11884
+ if (process.stdin.isTTY && opts.confirm === void 0) {
11885
+ if (!await askToInstall(plan)) die("installation declined");
11886
+ } else {
11887
+ process.stderr.write(`${formatWorkflowPackageReview(plan)}
11888
+ `);
11889
+ }
11890
+ const receipt = await recoverOrInstall(api, raw, plan);
11891
+ if (opts.json) printJson(receipt);
11892
+ else {
11893
+ console.log(`installed workflow package (${receipt.id})`);
11894
+ console.log(`plan: ${receipt.plan_digest}`);
11895
+ for (const object3 of receipt.objects)
11896
+ console.log(` ${object3.kind}: ${object3.name} \u2014 ${object3.href}`);
11897
+ }
11898
+ }
11899
+ );
11900
+ }
11901
+ function normalizeUrl(value) {
11902
+ try {
11903
+ return new URL(value).toString().replace(/\/$/, "");
11904
+ } catch {
11905
+ die(`invalid API base URL: ${value}`);
11906
+ }
11907
+ }
10564
11908
 
10565
11909
  // src/commands/usage.ts
10566
11910
  var money = (value) => usageCostLabel(value);
@@ -10647,14 +11991,14 @@ function register12(program3) {
10647
11991
  if (opts.state && !opts.workflow) throw new Error("--state requires --workflow");
10648
11992
  const api = client(opts);
10649
11993
  const project = opts.project && opts.project !== "unknown" ? (await resolveProject(api, opts.project)).id : opts.project;
10650
- const workflow = opts.workflow && opts.workflow !== "unknown" ? (await resolveWorkflow(api, opts.workflow)).id : opts.workflow;
11994
+ const workflow2 = opts.workflow && opts.workflow !== "unknown" ? (await resolveWorkflow(api, opts.workflow)).id : opts.workflow;
10651
11995
  const runner = opts.runner && opts.runner !== "unknown" ? (await resolveRunner(api, opts.runner)).id : opts.runner;
10652
11996
  const report = await api.getUsage({
10653
11997
  window: opts.window,
10654
11998
  from: opts.from,
10655
11999
  to: opts.to,
10656
12000
  project,
10657
- workflow,
12001
+ workflow: workflow2,
10658
12002
  state: opts.state,
10659
12003
  runner,
10660
12004
  tier: opts.tier,