tines 0.0.322 → 0.0.324

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -40,6 +40,7 @@ tines issues create <project> --title "…" -d @description.md
40
40
  tines issues create <project> --title "Linked" --blocked-by Other/12 --blocks Other/14 --duplicate-of Other/9
41
41
  tines issues show <project>/<number>
42
42
  tines issues move <project>/<number> <action> # a workflow transition
43
+ tines issues move <project>/<number> <action> --expect-revision <n> # refuse unless the issue is still at the decision revision `issues show` printed
43
44
  tines issues hold <project>/<number> # stop new admission, keep permission
44
45
  tines issues release <project>/<number> # resume eligibility if permission remains on
45
46
  tines issues cancel-run <project>/<number> <run-id> # request bounded cancellation
@@ -81,6 +82,12 @@ every evidence page with no ceiling and takes no `--max-items`.
81
82
  When a recurrence is also supplied, the relationships apply only to the first issue; later
82
83
  scheduled instances start without copied relationships.
83
84
 
85
+ `workflows show` prints a workflow's `revision`, which advances by one on every committed
86
+ save. `workflows edit <workflow> … --expect-revision <n>` (or `"expected_revision"` in the
87
+ JSON body; the flag wins) refuses the update with `workflow_conflict`, writing nothing, when
88
+ the workflow is no longer at that revision. The CLI never fills the revision in for you and
89
+ never retries: re-read with `workflows show`, re-apply your change, and send the new revision.
90
+
84
91
  For a shared project's recurring schedule, `schedules list` and `schedules show` report
85
92
  your saved future-instance permission and its epoch. They are read receipts: the CLI and
86
93
  API keys cannot turn personal permission on or off. Open the schedule in the browser to
package/dist/index.js CHANGED
@@ -7631,6 +7631,21 @@ function printIssueLinks(links) {
7631
7631
  table(linkRows(links.duplicated_by));
7632
7632
  }
7633
7633
  }
7634
+ var MOVE_REVISION_HELP = `
7635
+ Every state change advances the issue's decision revision by one (\`tines
7636
+ issues show\` prints it), so leaving a state and returning to it is a different
7637
+ revision. Pass --expect-revision <n> to have the move refused with
7638
+ decision_refresh_required \u2014 nothing written \u2014 when the issue is no longer on
7639
+ the state visit you read. Without it, only a change that commits during this
7640
+ command is caught.
7641
+ `;
7642
+ function parseDecisionRevisionOption(value) {
7643
+ const result = Number(value);
7644
+ if (!/^(0|[1-9]\d*)$/.test(value) || !Number.isSafeInteger(result)) {
7645
+ throw new Error("expect-revision must be a non-negative integer");
7646
+ }
7647
+ return result;
7648
+ }
7634
7649
  function printIssueDetail(issue) {
7635
7650
  const shared = issue;
7636
7651
  if (shared.mode === "member") {
@@ -7680,6 +7695,8 @@ ${comment.body}`
7680
7695
  `own state: ${issue.state.name} (${issue.state.category}) \u2014 dormant while this is a duplicate`
7681
7696
  );
7682
7697
  }
7698
+ if (issue.decision_revision !== void 0)
7699
+ console.log(`decision revision: ${issue.decision_revision}`);
7683
7700
  if (issue.project_archived_at !== null) {
7684
7701
  console.log(
7685
7702
  `project archived: ${timestamp(issue.project_archived_at)} \u2014 this issue is read-only`
@@ -7984,7 +8001,11 @@ function register3(program3) {
7984
8001
  }
7985
8002
  );
7986
8003
  withCommon(
7987
- issues.command("move <ref> <action>").description("Take a current transition (members: no permission change)")
8004
+ issues.command("move <ref> <action>").description("Take a current transition (members: no permission change)").option(
8005
+ "--expect-revision <n>",
8006
+ "refuse the move unless the issue is still at this decision revision (see `tines issues show`)",
8007
+ parseDecisionRevisionOption
8008
+ ).addHelpText("after", MOVE_REVISION_HELP)
7988
8009
  ).action(async (ref, action2, opts) => {
7989
8010
  const api = client(opts);
7990
8011
  const issue = await resolveIssue(api, ref);
@@ -8026,7 +8047,30 @@ function register3(program3) {
8026
8047
  } : { transition_id: transition.transition_id };
8027
8048
  }
8028
8049
  }
8029
- const moved = await api.transitionIssue(issue.id, request);
8050
+ if (opts.expectRevision !== void 0) {
8051
+ if (request.transition_id === void 0) {
8052
+ const transition = issue.allowed_transitions.find(
8053
+ (candidate) => candidate.name.toLowerCase() === action2.toLowerCase()
8054
+ );
8055
+ if (!transition)
8056
+ throw new Error(`No current transition named "${action2}"; refresh the issue`);
8057
+ request = { transition_id: transition.transition_id };
8058
+ }
8059
+ request = {
8060
+ ...request,
8061
+ expected_state_id: issue.state.id,
8062
+ expected_decision_revision: opts.expectRevision
8063
+ };
8064
+ }
8065
+ const moved = await api.transitionIssue(issue.id, request).catch((error) => {
8066
+ if (opts.expectRevision === void 0 || !(error instanceof ApiError) || error.code !== "decision_refresh_required")
8067
+ throw error;
8068
+ const current = error.details?.current_decision_revision;
8069
+ die(
8070
+ `${error.message} (${error.code})
8071
+ hint: re-read with \`tines issues show ${ref}\`, then retry with --expect-revision ${typeof current === "number" ? current : "<decision revision>"}`
8072
+ );
8073
+ });
8030
8074
  if (opts.json) return printJson(moved);
8031
8075
  const movedMember = moved;
8032
8076
  console.log(
@@ -14899,9 +14943,18 @@ function resolveBasePair(lib, pair, stateName) {
14899
14943
  }
14900
14944
  return state.id;
14901
14945
  }
14946
+ var WORKFLOW_REVISION_HELP = `
14947
+ Every committed update advances the workflow's revision by one (\`tines
14948
+ workflows show\` prints it). Pass --expect-revision <n>, or "expected_revision"
14949
+ in the JSON body (the flag wins), to have the update refused with
14950
+ workflow_conflict \u2014 nothing written \u2014 when the workflow is no longer at the
14951
+ revision you read. Without it, only a save that commits during this command is
14952
+ caught.
14953
+ `;
14902
14954
  function printWorkflowDetail(wf, lib) {
14903
14955
  console.log(`${wf.name}${wf.is_system ? " (standard, read-only)" : ""} [${wf.id}]`);
14904
14956
  if (wf.description) console.log(wf.description);
14957
+ console.log(`revision: ${wf.revision}`);
14905
14958
  console.log("\nstates:");
14906
14959
  const byId = new Map(wf.states.map((s) => [s.id, s]));
14907
14960
  const rows = wf.states.length === 0 ? [] : formatTable(
@@ -15012,7 +15065,11 @@ see \`tines workflows create --help\` for the expected shape`
15012
15065
  }
15013
15066
  );
15014
15067
  withCommon(
15015
- workflows.command("edit <id-or-name> [json]").description("Update a workflow from a JSON definition and/or flags").option("-f, --file <path>", 'read the JSON definition from a file ("-" for stdin)').option("-n, --name <name>", "rename the workflow").option("-d, --description <text>", "set the description").option("--initial-state <id-or-name>", "set the initial state").option("--no-prompts", 'allow new states without initial "prompt" instructions').addHelpText("after", WORKFLOW_JSON_HELP)
15068
+ workflows.command("edit <id-or-name> [json]").description("Update a workflow from a JSON definition and/or flags").option("-f, --file <path>", 'read the JSON definition from a file ("-" for stdin)').option("-n, --name <name>", "rename the workflow").option("-d, --description <text>", "set the description").option("--initial-state <id-or-name>", "set the initial state").option("--no-prompts", 'allow new states without initial "prompt" instructions').option(
15069
+ "--expect-revision <n>",
15070
+ "refuse the update unless the workflow is still at this revision (see `tines workflows show`)",
15071
+ (value) => parsePositiveIntegerOption(value, "expect-revision")
15072
+ ).addHelpText("after", WORKFLOW_JSON_HELP + WORKFLOW_REVISION_HELP)
15016
15073
  ).action(
15017
15074
  async (ref, inline, opts) => {
15018
15075
  const api = client(opts);
@@ -15024,10 +15081,18 @@ see \`tines workflows create --help\` for the expected shape`
15024
15081
  if (opts.name !== void 0) body.name = opts.name;
15025
15082
  if (opts.description !== void 0) body.description = opts.description;
15026
15083
  if (opts.initialState !== void 0) body.initial_state = opts.initialState;
15027
- if (Object.keys(body).length === 0) {
15084
+ if (opts.expectRevision !== void 0) body.expected_revision = opts.expectRevision;
15085
+ if (Object.keys(body).every((key) => key === "expected_revision")) {
15028
15086
  die("nothing to update: pass JSON and/or --name/--description/--initial-state");
15029
15087
  }
15030
- const updated = await api.updateWorkflow(wf.id, body);
15088
+ const updated = await api.updateWorkflow(wf.id, body).catch((error) => {
15089
+ if (!(error instanceof ApiError) || error.code !== "workflow_conflict") throw error;
15090
+ const current = error.details?.current_revision;
15091
+ die(
15092
+ `${error.message} (${error.code})
15093
+ hint: re-read with \`tines workflows show ${wf.id}\`, then retry with --expect-revision ${typeof current === "number" ? current : "<revision>"}`
15094
+ );
15095
+ });
15031
15096
  if (opts.json) return printJson(updated);
15032
15097
  console.log(`updated workflow "${updated.name}" (${updated.id})
15033
15098
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tines",
3
- "version": "0.0.322",
3
+ "version": "0.0.324",
4
4
  "description": "CLI for Tines, an orchestration layer for AI agents",
5
5
  "repository": {
6
6
  "type": "git",