@lambdacurry/arbor 0.22.41 → 0.23.1

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 (2) hide show
  1. package/dist/arbor.js +302 -21
  2. package/package.json +1 -1
package/dist/arbor.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // package.json
3
3
  var package_default = {
4
4
  name: "@lambdacurry/arbor",
5
- version: "0.22.41",
5
+ version: "0.23.1",
6
6
  description: "The Arbor CLI — a shared workspace for people and agents. The human + headless-agent write path over Arbor's guarded operation surface.",
7
7
  keywords: [
8
8
  "agents",
@@ -4931,6 +4931,20 @@ var LIFECYCLE_TYPES = [
4931
4931
  "canceled",
4932
4932
  "archived"
4933
4933
  ];
4934
+ var LIFECYCLE_TYPE_DESCRIPTIONS = {
4935
+ backlog: "accepted, not committed",
4936
+ active: "committed and in flight",
4937
+ "needs-review": "waiting on a read",
4938
+ stuck: "in flight, blocked",
4939
+ standing: "open-forever; never owed a conclusion (AD-200)",
4940
+ resolved: "the objective was met",
4941
+ canceled: "we decided not to",
4942
+ archived: "left the record; refile with `thread_update`"
4943
+ };
4944
+ function transitionThreadActionDescription() {
4945
+ const types = LIFECYCLE_TYPES.map((type) => `\`${type}\` ${LIFECYCLE_TYPE_DESCRIPTIONS[type]}`).join("; ");
4946
+ return `Move a Thread by key, case-insensitive name, or type (\`--to resolved\` still works as a bare type); illegal jumps rejected. ${types}; live menu is \`thread_get.transitionsFrom\`.`;
4947
+ }
4934
4948
  var CONTRIBUTION_TYPES = [
4935
4949
  "comment",
4936
4950
  "question",
@@ -6437,6 +6451,24 @@ var agentPairings = sqliteTable("agent_pairings", {
6437
6451
  userCodeIdx: uniqueIndex("agent_pairings_user_code_idx").on(t.userCode),
6438
6452
  orgStatusIdx: index("agent_pairings_org_status_idx").on(t.orgId, t.status)
6439
6453
  }));
6454
+ var workflows = sqliteTable("workflows", {
6455
+ id: text("id").primaryKey(),
6456
+ orgId: text("org_id").notNull().references(() => orgs.id),
6457
+ name: text("name").notNull(),
6458
+ summary: text("summary").notNull(),
6459
+ labels: text("labels", { mode: "json" }).$type().notNull(),
6460
+ createdAt: ts("created_at").notNull(),
6461
+ updatedAt: ts("updated_at").notNull()
6462
+ }, (t) => ({ orgIdx: index("workflows_org_idx").on(t.orgId) }));
6463
+ var spaceWorkflows = sqliteTable("space_workflows", {
6464
+ spaceId: text("space_id").notNull().references(() => spaces.id),
6465
+ workflowId: text("workflow_id").notNull().references(() => workflows.id),
6466
+ isDefault: integer2("is_default", { mode: "boolean" }).notNull().default(false)
6467
+ }, (t) => ({
6468
+ pk: primaryKey({ columns: [t.spaceId, t.workflowId] }),
6469
+ workflowIdx: index("space_workflows_workflow_idx").on(t.workflowId),
6470
+ oneDefaultUq: uniqueIndex("space_workflows_one_default_uq").on(t.spaceId).where(sql`${t.isDefault} = 1`)
6471
+ }));
6440
6472
  var topics = sqliteTable("topics", {
6441
6473
  id: text("id").primaryKey(),
6442
6474
  spaceId: text("space_id").notNull().references(() => spaces.id),
@@ -6444,9 +6476,13 @@ var topics = sqliteTable("topics", {
6444
6476
  purpose: text("purpose"),
6445
6477
  guidance: text("guidance", { mode: "json" }).$type().notNull().default([]),
6446
6478
  contributionLanes: text("contribution_lanes", { mode: "json" }).$type().notNull().default([]),
6479
+ workflowId: text("workflow_id").references(() => workflows.id),
6447
6480
  archivedAt: ts("archived_at"),
6448
6481
  createdAt: ts("created_at").notNull()
6449
- }, (t) => ({ spaceIdx: index("topics_space_idx").on(t.spaceId) }));
6482
+ }, (t) => ({
6483
+ spaceIdx: index("topics_space_idx").on(t.spaceId),
6484
+ workflowIdx: index("topics_workflow_idx").on(t.workflowId)
6485
+ }));
6450
6486
  var initiatives = sqliteTable("initiatives", {
6451
6487
  id: text("id").primaryKey(),
6452
6488
  orgId: text("org_id").notNull().references(() => orgs.id),
@@ -6475,6 +6511,8 @@ var threads = sqliteTable("threads", {
6475
6511
  objective: text("objective").notNull(),
6476
6512
  status: text("status").$type().notNull().default("active"),
6477
6513
  lifecycleType: text("lifecycle_type").$type().notNull().default("active"),
6514
+ lifecycleLabel: text("lifecycle_label").notNull().default("active"),
6515
+ rank: text("rank"),
6478
6516
  waysToHelp: text("ways_to_help", { mode: "json" }).$type().notNull().default([]),
6479
6517
  openQuestions: text("open_questions", { mode: "json" }).$type().notNull().default([]),
6480
6518
  expectedOutput: text("expected_output").notNull().default(""),
@@ -6482,7 +6520,8 @@ var threads = sqliteTable("threads", {
6482
6520
  }, (t) => ({
6483
6521
  topicIdx: index("threads_topic_idx").on(t.topicId),
6484
6522
  spaceIdx: index("threads_space_idx").on(t.spaceId),
6485
- lifecycleTypeIdx: index("threads_lifecycle_type_idx").on(t.lifecycleType)
6523
+ lifecycleTypeIdx: index("threads_lifecycle_type_idx").on(t.lifecycleType),
6524
+ topicLifecycleRankIdx: index("threads_topic_lifecycle_rank_idx").on(t.topicId, t.lifecycleType, t.rank)
6486
6525
  }));
6487
6526
  var computerConfigs = sqliteTable("computer_configs", {
6488
6527
  id: text("id").primaryKey(),
@@ -7701,6 +7740,150 @@ var STATUS_OPENNESS = {
7701
7740
  var typesWhere = (sense) => Object.keys(STATUS_OPENNESS).filter((s) => STATUS_OPENNESS[s][sense]);
7702
7741
  var WORKING_THREAD_STATUSES = typesWhere("working");
7703
7742
  var CURRENT_WORK_THREAD_STATUSES = typesWhere("currentWork");
7743
+ // ../core/src/lifecycle/rank.ts
7744
+ var THREAD_RANK_MAX = 128;
7745
+ var BASE62_CLASS = "[0-9A-Za-z]";
7746
+ var NON_ZERO_BASE62 = "[1-9A-Za-z]";
7747
+ var JIRA_LEXORANK = "[0-2]\\|[0-9a-z]+:[0-9a-z]*";
7748
+ function figmaFamilyPattern() {
7749
+ const arms = [];
7750
+ for (let i = 0;i < 26; i++) {
7751
+ const extra = String(i + 1);
7752
+ const lower = String.fromCharCode(97 + i);
7753
+ const upper = String.fromCharCode(90 - i);
7754
+ const rest = `${BASE62_CLASS}{${extra}}(?:${BASE62_CLASS}*${NON_ZERO_BASE62})?`;
7755
+ arms.push(`${lower}${rest}`, `${upper}${rest}`);
7756
+ }
7757
+ return arms.join("|");
7758
+ }
7759
+ var THREAD_RANK_PATTERN = new RegExp(`^(?:${JIRA_LEXORANK}|${figmaFamilyPattern()})$`);
7760
+ // ../core/src/lifecycle/workflow.ts
7761
+ var WORKFLOW_SUMMARY_MAX = 500;
7762
+ var WORKFLOW_LABEL_DESCRIPTION_MAX = 200;
7763
+ var LIFECYCLE_TYPE_SET = new Set(LIFECYCLE_TYPES);
7764
+ // ../core/src/lifecycle/builtins.ts
7765
+ var BUILTIN_WORKFLOW_IDS = [
7766
+ "standard",
7767
+ "queue",
7768
+ "content",
7769
+ "experiment",
7770
+ "funnel"
7771
+ ];
7772
+ var col = (key, name, type, order, extra) => ({ key, name, type, order, ...extra });
7773
+ var STANDARD = {
7774
+ id: "standard",
7775
+ name: "Standard",
7776
+ summary: "One label per type using the protocol's own words — the zero-config default and the only all-eight workflow.",
7777
+ labels: [
7778
+ col("backlog", "Backlog", "backlog", 0),
7779
+ col("active", "Active", "active", 1),
7780
+ col("needs-review", "Needs review", "needs-review", 2),
7781
+ col("stuck", "Stuck", "stuck", 3),
7782
+ col("standing", "Standing", "standing", 4),
7783
+ col("resolved", "Resolved", "resolved", 5),
7784
+ col("canceled", "Canceled", "canceled", 6),
7785
+ col("archived", "Archived", "archived", 7)
7786
+ ]
7787
+ };
7788
+ var QUEUE = {
7789
+ id: "queue",
7790
+ name: "Queue",
7791
+ summary: "Arrivals are handled or refused; no review gate; stuck names an external requester.",
7792
+ labels: [
7793
+ col("triage", "Triage", "backlog", 0, {
7794
+ description: "Arrived; not yet accepted or refused."
7795
+ }),
7796
+ col("accepted", "Accepted", "active", 1, { description: "Taken; being handled." }),
7797
+ col("waiting-on-requester", "Waiting on requester", "stuck", 2, {
7798
+ description: "Blocked on the person who sent it, not an internal blocker."
7799
+ }),
7800
+ col("handled", "Handled", "resolved", 3, { description: "The arrival was worked." }),
7801
+ col("declined", "Declined", "canceled", 4, { description: "We decided not to take it." }),
7802
+ col("duplicate", "Duplicate", "archived", 5, {
7803
+ description: "Same as an existing thread; the original holds the work."
7804
+ })
7805
+ ]
7806
+ };
7807
+ var CONTENT = {
7808
+ id: "content",
7809
+ name: "Content",
7810
+ summary: "Draft, edit, approve, publish — two review gates by two authorities, plus a named return path.",
7811
+ labels: [
7812
+ col("pitch", "Pitch", "backlog", 0, { description: "Proposed, not yet in draft." }),
7813
+ col("drafting", "Drafting", "active", 1, {
7814
+ isDefaultForType: true,
7815
+ description: "Being written."
7816
+ }),
7817
+ col("changes-requested", "Changes requested", "active", 2, {
7818
+ description: "Failed a review; the bounce is the work."
7819
+ }),
7820
+ col("in-edit", "In edit", "needs-review", 3, {
7821
+ isDefaultForType: true,
7822
+ description: "Editorial review."
7823
+ }),
7824
+ col("final-approval", "Final approval", "needs-review", 4, {
7825
+ description: "Sign-off by a different authority than edit."
7826
+ }),
7827
+ col("waiting-on-assets", "Waiting on assets", "stuck", 5, {
7828
+ description: "Blocked on material, not a reader."
7829
+ }),
7830
+ col("published", "Published", "resolved", 6, { description: "Out." }),
7831
+ col("killed", "Killed", "canceled", 7, { description: "We decided not to ship it." })
7832
+ ]
7833
+ };
7834
+ var EXPERIMENT = {
7835
+ id: "experiment",
7836
+ name: "Experiment",
7837
+ summary: "A hypothesis is run and owes a readout; two honest endings — adopted, or not.",
7838
+ labels: [
7839
+ col("hypothesis", "Hypothesis", "backlog", 0, {
7840
+ description: "Proposed trial, not yet running."
7841
+ }),
7842
+ col("running", "Running", "active", 1, {
7843
+ description: "Finite trial in flight; owed a readout."
7844
+ }),
7845
+ col("readout", "Readout", "needs-review", 2, {
7846
+ description: "Findings waiting on a reader."
7847
+ }),
7848
+ col("adopted", "Adopted", "resolved", 3, {
7849
+ description: "The hypothesis won; we will keep it."
7850
+ }),
7851
+ col("rejected", "Rejected", "canceled", 4, {
7852
+ description: "The hypothesis ran; we decided not to adopt."
7853
+ })
7854
+ ]
7855
+ };
7856
+ var FUNNEL = {
7857
+ id: "funnel",
7858
+ name: "Funnel",
7859
+ summary: "A ladder of sequential stages against a counterparty, ending won or lost.",
7860
+ labels: [
7861
+ col("lead", "Lead", "backlog", 0, { description: "Not yet qualified." }),
7862
+ col("qualified", "Qualified", "active", 1, {
7863
+ isDefaultForType: true,
7864
+ description: "In the ladder; not yet a proposal."
7865
+ }),
7866
+ col("proposal", "Proposal", "active", 2, {
7867
+ description: "Proposal is out / submitted."
7868
+ }),
7869
+ col("negotiation", "Negotiation", "active", 3, { description: "Terms being worked." }),
7870
+ col("waiting-on-counterparty", "Waiting on counterparty", "stuck", 4, {
7871
+ description: "Blocked on them, not on an internal read."
7872
+ }),
7873
+ col("won", "Won", "resolved", 5, { description: "Closed won." }),
7874
+ col("lost", "Lost", "canceled", 6, {
7875
+ description: "We did not win; keep the reason findable."
7876
+ })
7877
+ ]
7878
+ };
7879
+ var BUILTIN_WORKFLOWS = {
7880
+ standard: STANDARD,
7881
+ queue: QUEUE,
7882
+ content: CONTENT,
7883
+ experiment: EXPERIMENT,
7884
+ funnel: FUNNEL
7885
+ };
7886
+ var STANDARD_WORKFLOW = BUILTIN_WORKFLOWS.standard;
7704
7887
  // ../core/src/ops/secret.ts
7705
7888
  var SECRET_PERMISSIONS = [
7706
7889
  "create",
@@ -8413,8 +8596,27 @@ var thread3 = looseObject({
8413
8596
  title: string2(),
8414
8597
  objective: string2(),
8415
8598
  status: string2().optional(),
8599
+ lifecycleType: string2().optional(),
8600
+ lifecycleLabel: string2().optional(),
8601
+ rank: nullableString.optional(),
8416
8602
  url: string2().optional()
8417
8603
  });
8604
+ var workflowSummary = looseObject({
8605
+ id,
8606
+ name: string2(),
8607
+ summary: string2(),
8608
+ origin: string2().optional(),
8609
+ isDefault: boolean2().optional(),
8610
+ labels: array(jsonObject).optional(),
8611
+ createdAt: string2().optional(),
8612
+ updatedAt: string2().optional()
8613
+ });
8614
+ var transitionFrom = looseObject({
8615
+ label: string2(),
8616
+ name: string2(),
8617
+ type: string2(),
8618
+ description: string2().optional()
8619
+ });
8418
8620
  var topic2 = looseObject({
8419
8621
  id,
8420
8622
  spaceId: id.optional(),
@@ -8714,14 +8916,16 @@ var MCP_OUTPUT_SCHEMAS = {
8714
8916
  topic_update: looseObject({
8715
8917
  id,
8716
8918
  title: string2().optional(),
8717
- purpose: nullableString.optional()
8919
+ purpose: nullableString.optional(),
8920
+ workflowId: nullableString.optional()
8718
8921
  }),
8719
8922
  create_thread: looseObject({ threadId: id, url: string2().optional() }),
8720
8923
  thread_update: looseObject({
8721
8924
  id,
8722
8925
  title: string2().optional(),
8723
8926
  objective: string2().optional(),
8724
- topicId: string2().optional()
8927
+ topicId: string2().optional(),
8928
+ rank: nullableString.optional()
8725
8929
  }),
8726
8930
  set_organization_computer: looseObject({ config: computerConfigLayer.nullable() }),
8727
8931
  set_space_computer: looseObject({ config: computerConfigLayer.nullable() }),
@@ -8868,7 +9072,8 @@ var MCP_OUTPUT_SCHEMAS = {
8868
9072
  distillate: array(jsonObject).optional(),
8869
9073
  watching: boolean2().optional(),
8870
9074
  cursor: string2().optional(),
8871
- unchanged: boolean2().optional()
9075
+ unchanged: boolean2().optional(),
9076
+ transitionsFrom: array(transitionFrom).optional()
8872
9077
  }),
8873
9078
  thread_assign: looseObject({
8874
9079
  threadId: id,
@@ -8914,7 +9119,10 @@ var MCP_OUTPUT_SCHEMAS = {
8914
9119
  title: string2().optional(),
8915
9120
  purpose: nullableString.optional(),
8916
9121
  visibility: string2().optional(),
8917
- stage: string2().optional()
9122
+ stage: string2().optional(),
9123
+ attached: jsonObject.optional(),
9124
+ defaulted: jsonObject.optional(),
9125
+ detached: jsonObject.optional()
8918
9126
  }),
8919
9127
  space_get: looseObject({
8920
9128
  space: space3,
@@ -8922,11 +9130,14 @@ var MCP_OUTPUT_SCHEMAS = {
8922
9130
  members: array(member).optional(),
8923
9131
  total: number2().int().min(0),
8924
9132
  nextCursor: string2().optional(),
8925
- computerConfig: resolvedComputerConfig.nullable().optional()
9133
+ computerConfig: resolvedComputerConfig.nullable().optional(),
9134
+ workflows: array(workflowSummary).optional(),
9135
+ defaultWorkflow: workflowSummary.optional()
8926
9136
  }),
8927
9137
  topic_get: looseObject({
8928
9138
  topic: topic2,
8929
9139
  space: space3,
9140
+ workflow: workflowSummary.optional(),
8930
9141
  threads: array(thread3).optional(),
8931
9142
  promotedArtifacts: array(artifact4).optional(),
8932
9143
  total: number2().int().min(0),
@@ -9660,6 +9871,22 @@ var MCP_OUTPUT_SCHEMAS = {
9660
9871
  namePath: string2().optional()
9661
9872
  })),
9662
9873
  truncated: boolean2()
9874
+ }),
9875
+ workflow_create: looseObject({
9876
+ id,
9877
+ name: string2(),
9878
+ summary: string2(),
9879
+ labels: array(jsonObject)
9880
+ }),
9881
+ workflow_update: looseObject({
9882
+ id,
9883
+ name: string2(),
9884
+ summary: string2(),
9885
+ labels: array(jsonObject)
9886
+ }),
9887
+ workflow_list: looseObject({
9888
+ builtins: array(workflowSummary),
9889
+ catalog: array(workflowSummary)
9663
9890
  })
9664
9891
  };
9665
9892
  function outputSchemaFor(actionName) {
@@ -10029,6 +10256,9 @@ var MCP_TOOL_ANNOTATIONS = {
10029
10256
  topic_update: additive,
10030
10257
  create_thread: additive,
10031
10258
  thread_update: additive,
10259
+ workflow_create: additive,
10260
+ workflow_update: additive,
10261
+ workflow_list: readOnly,
10032
10262
  thread_assign: idempotent,
10033
10263
  thread_list: readOnly,
10034
10264
  set_organization_computer: destructiveIdempotent,
@@ -10387,7 +10617,7 @@ Arbor's MCP is the collaboration and durable-knowledge surface. Configure, autho
10387
10617
  3. ANSWER through requests. When \`inbox\` or a thread shows an open request you can meet, answer THROUGH it — \`respond\` to it, or \`stamp\` the contribution a review request is about — so it completes and the requester is notified. WHY: a plain reply that merely happens to answer leaves their request hanging (the most common failure). Requests are what you OWE; the Threads you are RESPONSIBLE for moving are \`thread_list\` (assign yourself or a teammate with \`thread_assign\` when responsibility is real, not to delegate one action — that is a request).
10388
10618
  4. REVIEW honestly; promote the standout, sparingly. \`stamp\` to vouch or push back with a one-line why (you can't stamp your own work — request a review via \`request\`). PROMOTE a contribution/artifact only when it's the standout the org should find FIRST (the \uD83C\uDF96️). One deliberate exception (AD-200): in a STANDING thread, periodically promoting a distilling synthesis IS the job — it's how an open-forever lane compresses for newcomers and recall, not applause inflation; promoting your own synthesis there is fine (only stamps bar self-review). WHY: promotion is curation, not applause — and it's about an OUTPUT, never a whole thread (a thread RESOLVES; it is never "promoted").
10389
10619
  5. EDIT, don't repost. \`edit\` your own contribution to sharpen it; the record stays audited. A genuinely different point is a NEW contribution.
10390
- 6. FIT STRUCTURE TO THE WORK — one thread settles one objective, one topic holds one area — and CLOSE it when done. Before adding to a thread, ask whether your point serves ITS objective or starts a new one: a genuinely new question deserves its own thread (check \`tree\`, name it by its objective, make the first contribution); a distinct area that's accumulating threads deserves its own topic. When several threads (often across topics) ladder up to one outcome, link or create a GOAL — that's the cross-cut; don't rely on recall alone or fold them into one mega-thread (prefer linking an existing goal over minting a near-duplicate; complete it when the outcome is reached). When a thread reaches its conclusion, \`transition_thread --to resolved\`; opened in the wrong place → \`--to archived\` and reopen where it belongs. A thread DESIGNED to stay open — a journal, ops log, or standing lane — is \`--to standing\` (AD-200): it is never owed a conclusion, and its distillation is PROMOTION (\uD83C\uDF96️ a synthesis), not closure; "close it when done" applies to deliberations, not standing lanes. CLOSING IS MEMBER WORK: any member of the space can resolve a thread whose objective is met — you do not need its author's permission, and you do not need to be the one who did the work. Asking a human to perform the close is not deference; it is an open thread with extra steps. If the objective is met, close it and say why. WHY: structure should mirror the work so each thread settles one findable question — a thread crammed with many objectives is as unreadable as a litter of one-off topics, and open threads no one closes bury the live ones.
10620
+ 6. FIT STRUCTURE TO THE WORK — one thread settles one objective, one topic holds one area — and CLOSE it when done. Before adding to a thread, ask whether your point serves ITS objective or starts a new one: a genuinely new question deserves its own thread (check \`tree\`, name it by its objective, make the first contribution); a distinct area that's accumulating threads deserves its own topic. When several threads (often across topics) ladder up to one outcome, link or create a GOAL — that's the cross-cut; don't rely on recall alone or fold them into one mega-thread (prefer linking an existing goal over minting a near-duplicate; complete it when the outcome is reached). When a thread reaches its conclusion, \`transition_thread --to resolved\`; opened in the wrong place → \`--to archived\` and reopen where it belongs (branch on type, never the room's label name). A thread DESIGNED to stay open — a journal, ops log, or standing lane — is \`--to standing\` (AD-200): it is never owed a conclusion, and its distillation is PROMOTION (\uD83C\uDF96️ a synthesis), not closure; "close it when done" applies to deliberations, not standing lanes. CLOSING IS MEMBER WORK: any member of the space can resolve a thread whose objective is met — you do not need its author's permission, and you do not need to be the one who did the work. Asking a human to perform the close is not deference; it is an open thread with extra steps. If the objective is met, close it and say why. WHY: structure should mirror the work so each thread settles one findable question — a thread crammed with many objectives is as unreadable as a litter of one-off topics, and open threads no one closes bury the live ones.
10391
10621
  7. ACT, THEN REPORT — escalate by exception (AD-197). Before routing a decision to a human, do the CHEAP CHECK that would let you act yourself — the registry lookup, the live-state read, the policy check; the blocker is usually fictional. Then: recoverable + conclusive evidence → act and say why. Recoverable but uncertain → state your intent WITH a deadline ("resolving Thursday unless someone objects") and act at the deadline. Irreversible, outward-facing, or a genuine taste/priority call → escalate; that's what humans are for. Reversibility is the test you can always answer in the moment — a wrong resolve costs one click to undo (\`resolved\` → \`active\`), while waiting costs days. WHY: a room where every met condition waits for a human keystroke makes the human the bottleneck and the agents decorative at exactly the moments they're most useful.
10392
10622
 
10393
10623
  8. DURABLE OUTPUT IS AN ARTIFACT, NOT A LONG CONTRIBUTION (AD-201). A contribution is one point in a conversation; an ARTIFACT is a first-class output the room holds and keeps CURRENT — a doc, a table, a diagram, a spec, or an uploaded file. When your point is really a deliverable the team will read or UPDATE later — a table of options, a reference doc, a plan, a rubric — make it an artifact (\`artifact\` verb=create with kind doc/table/diagram/html + \`source\`, or promote a file) instead of pasting it into prose. Text-kind artifacts are LIVING: \`artifact_get\` it, then \`artifact\` verb=edit naming the \`baseVersion\` you read — patches apply exact-match and atomically, one version per call, every version attributed. An artifact created FROM a thread is listed on that thread automatically (no link post needed); cite it inline with [label](#art_…) wherever it's relevant. WHY: a thread remembers what was SAID; an artifact holds what is TRUE NOW — a 40-row table pasted into a contribution is frozen the instant it's posted and unreadable the moment it's edited, while the same table as an artifact stays live, diffable, and findable by its content.
@@ -10513,6 +10743,14 @@ var PAGINATION_INPUT = {
10513
10743
  cursor: string2().optional().describe("opaque cursor from a prior response's nextCursor (use with --limit)"),
10514
10744
  all: boolean2().optional().describe("explicit complete retrieval — skip the bounded default page. Prefer limit+cursor unless you need the whole collection")
10515
10745
  };
10746
+ var WORKFLOW_LABEL_INPUT = object({
10747
+ key: string2().min(1).describe("stable id within this workflow"),
10748
+ name: string2().min(1).describe("room-facing column name"),
10749
+ type: _enum(LIFECYCLE_TYPES).describe("protocol type this column occupies"),
10750
+ order: number2().int().describe("column order, 0-based"),
10751
+ isDefaultForType: boolean2().optional().describe("required when this type has more than one label"),
10752
+ description: string2().max(WORKFLOW_LABEL_DESCRIPTION_MAX).optional().describe("optional teaching prose; no consumer branches on it")
10753
+ });
10516
10754
  var LIVE_PREVIEW_CONFIG_INPUT = {
10517
10755
  livePreview: object({
10518
10756
  command: string2().min(1).max(4000).describe("explicit project preview command"),
@@ -10897,11 +11135,12 @@ var ACTION_DEFINITIONS = [
10897
11135
  {
10898
11136
  name: "create_topic",
10899
11137
  title: "Create a topic",
10900
- description: "Create a Topic for a durable sub-area that will hold multiple Threads (check tree first so structure stays scarce); otherwise keep the work in a Thread. Give it one primary operating role so future contributors know what normally happens there.",
11138
+ description: "Create a Topic for a durable sub-area that will hold multiple Threads (check tree first so structure stays scarce); otherwise keep the work in a Thread. Give it one primary operating role, and pick `--workflow` from `space_get.workflows` by summary or omit to inherit the Space default.",
10901
11139
  inputSchema: {
10902
11140
  spaceId: string2().describe("the space to create the topic in, spc_…"),
10903
11141
  title: string2().min(1).describe("the topic's name — an area of work, not a question"),
10904
- purpose: string2().optional().describe("what this Topic accomplishes and what belongs here, leading with its primary role — Direction, Delivery, Operations, Intake, Coordination, or Learning; name an external or split source of truth or a handoff boundary only when it materially matters")
11142
+ purpose: string2().optional().describe("what this Topic accomplishes and what belongs here, leading with its primary role — Direction, Delivery, Operations, Intake, Coordination, or Learning; name an external or split source of truth or a handoff boundary only when it materially matters"),
11143
+ workflow: string2().optional().describe("catalog id from `space_get.workflows` (pick by summary); must already be attached to this Space")
10905
11144
  },
10906
11145
  surfaces: ["mcp", "cli"],
10907
11146
  toolset: "structure",
@@ -10910,11 +11149,12 @@ var ACTION_DEFINITIONS = [
10910
11149
  {
10911
11150
  name: "topic_update",
10912
11151
  title: "Edit a topic's title or purpose",
10913
- description: "Edit a Topic's title or purpose facts, patching only supplied fields. Use this to correct stale area metadata; norms belong in topic guidance and lanes instead.",
11152
+ description: "Edit a Topic's title or purpose facts, or select `--workflow` from `space_get.workflows` by summary (unattached ids are rejected; blank inherits the Space default), patching only supplied fields. Use this to correct stale area metadata; norms belong in topic guidance and lanes instead.",
10914
11153
  inputSchema: {
10915
11154
  topicId: string2().describe("the topic to edit, top_…"),
10916
11155
  title: string2().min(1).optional().describe("a new title — an area of work, not a question"),
10917
- purpose: string2().optional().describe("a new purpose — what this Topic accomplishes and what belongs here, leading with its primary role (Direction, Delivery, Operations, Intake, Coordination, or Learning); name an external or split source of truth or a handoff boundary only when it materially matters")
11156
+ purpose: string2().optional().describe("a new purpose — what this Topic accomplishes and what belongs here, leading with its primary role (Direction, Delivery, Operations, Intake, Coordination, or Learning); name an external or split source of truth or a handoff boundary only when it materially matters"),
11157
+ workflow: string2().nullable().optional().describe("catalog id from `space_get.workflows` (pick by summary); must be attached to the Space; blank or null inherits the Space default")
10918
11158
  },
10919
11159
  surfaces: ["mcp", "cli"],
10920
11160
  toolset: "spaces",
@@ -10985,12 +11225,13 @@ var ACTION_DEFINITIONS = [
10985
11225
  {
10986
11226
  name: "thread_update",
10987
11227
  title: "Edit a thread's title, objective, or topic",
10988
- description: "Correct a Thread's title or objective, or move it under another Topic of the same Space, patching only supplied fields; use it to make done clear or refile misfiled work instead of archiving and reopening. Status moves stay with `transition_thread`, and the temporary help list with `charter` ways_to_help.",
11228
+ description: "Correct a Thread's title or objective, or move it under another Topic of the same Space, patching only supplied fields; use it to make done clear or refile misfiled work instead of archiving and reopening. Status moves stay with `transition_thread`, column order with `--rank`, and the temporary help list with `charter` ways_to_help.",
10989
11229
  inputSchema: {
10990
11230
  threadId: string2().describe("the thread to edit, thr_…"),
10991
11231
  title: string2().min(1).optional().describe("a new title — the question, outcome, investigation, instance, or standing concern"),
10992
11232
  objective: string2().min(1).optional().describe("a new objective — what this Thread should accomplish; for finite work, what done means"),
10993
- topicId: string2().optional().describe("move the thread under this topic of the same space, top_…")
11233
+ topicId: string2().optional().describe("move the thread under this topic of the same space, top_…"),
11234
+ rank: string2().max(THREAD_RANK_MAX).regex(THREAD_RANK_PATTERN).nullable().optional().describe("LexoRank string for card order inside one lifecycle column; omit to leave unchanged, null to restore recency")
10994
11235
  },
10995
11236
  surfaces: ["mcp", "cli"],
10996
11237
  toolset: "loop",
@@ -11510,10 +11751,10 @@ var ACTION_DEFINITIONS = [
11510
11751
  {
11511
11752
  name: "transition_thread",
11512
11753
  title: "Move a thread's lifecycle",
11513
- description: "Move a Thread among backlog, active, needs-review, stuck, standing, resolved, canceled, or archived with an optional reason; illegal lifecycle jumps are rejected. Park accepted-not-committed work in backlog, resolve finite Decide/Deliver/Investigate/Manage work once its outcome is in, cancel work the room decided not to do, archive retired work (refile misfiled work with `thread_update` instead), use standing only for intentionally open-ended concerns, and curate standout outputs separately.",
11754
+ description: transitionThreadActionDescription(),
11514
11755
  inputSchema: {
11515
11756
  threadId: string2().describe("the thread to move, thr_…"),
11516
- to: _enum(LIFECYCLE_TYPES).describe("target Thread lifecycle type"),
11757
+ to: string2().min(1).describe("label key, case-insensitive label name, or bare type (`resolved` still works as a type name)"),
11517
11758
  reason: string2().optional().describe("optional one-line why, recorded on the event")
11518
11759
  },
11519
11760
  surfaces: ["mcp", "cli"],
@@ -12084,17 +12325,57 @@ var ACTION_DEFINITIONS = [
12084
12325
  {
12085
12326
  name: "space_update",
12086
12327
  title: "Edit a space's config",
12087
- description: "Edit a Space's purpose, title, or visibility; org owner/admin or that Space's admin only, and only supplied fields are patched. Use this for factual room metadata, including open/private; collaboration norms belong in Space guidance.",
12328
+ description: "Edit a Space's purpose, title, or visibility; org owner/admin or that Space's admin only, and only supplied fields are patched. Use this for factual room metadata, including open/private, or org-admin `--attach-workflow` / `--detach-workflow` / `--default-workflow`; collaboration norms belong in Space guidance.",
12088
12329
  inputSchema: {
12089
12330
  spaceId: string2().describe("the space, spc_…"),
12090
12331
  purpose: string2().optional().describe("why this boundary exists and what it is around — commonly a Product/System, Program/Function, Portfolio/Network, or Practice/Community — plus its dominant work modes (Build, Operate, Coordinate, Discuss, Share) when that helps; plain prose, not metadata syntax"),
12091
12332
  title: string2().optional().describe("a new title for the space"),
12092
- visibility: _enum(["open", "private"]).optional().describe("open or private (invite-only)")
12333
+ visibility: _enum(["open", "private"]).optional().describe("open or private (invite-only)"),
12334
+ attachWorkflow: string2().optional().describe("org-admin: attach this catalog workflow id to the Space allowlist"),
12335
+ detachWorkflow: string2().optional().describe("org-admin: detach this catalog workflow; rejected if a Topic still selects it"),
12336
+ defaultWorkflow: string2().optional().describe("org-admin: name which attached workflow new Topics inherit")
12093
12337
  },
12094
12338
  surfaces: ["mcp", "cli"],
12095
12339
  toolset: "spaces",
12096
12340
  run: forward("space.update")
12097
12341
  },
12342
+ {
12343
+ name: "workflow_create",
12344
+ title: "Create a catalog workflow",
12345
+ description: "Copy a built-in into the org catalog (`--from-builtin`) or author name, summary, and labels. Org-admin; copy-then-edit — the built-in constant is never mutated in place.",
12346
+ inputSchema: {
12347
+ fromBuiltin: _enum(BUILTIN_WORKFLOW_IDS).optional().describe("copy this built-in template into the catalog"),
12348
+ name: string2().min(1).optional().describe("catalog name; defaults from the built-in"),
12349
+ summary: string2().max(WORKFLOW_SUMMARY_MAX).optional().describe("required unless `--from-builtin`; how an agent picks this workflow"),
12350
+ labels: array(WORKFLOW_LABEL_INPUT).optional().describe("required unless `--from-builtin`; ordered columns")
12351
+ },
12352
+ surfaces: ["mcp", "cli"],
12353
+ toolset: "structure",
12354
+ run: forward("workflow.create")
12355
+ },
12356
+ {
12357
+ name: "workflow_update",
12358
+ title: "Edit a catalog workflow",
12359
+ description: "Replace a catalog workflow's summary and labels (optional name); org-admin. A switch that would retype a live thread is rejected with the blocking threads enumerated.",
12360
+ inputSchema: {
12361
+ workflowId: string2().describe("the catalog workflow, wfl_…"),
12362
+ name: string2().min(1).optional().describe("a new catalog name"),
12363
+ summary: string2().min(1).max(WORKFLOW_SUMMARY_MAX).describe("required summary — how an agent picks this workflow"),
12364
+ labels: array(WORKFLOW_LABEL_INPUT).min(1).describe("the full replacement label set")
12365
+ },
12366
+ surfaces: ["mcp", "cli"],
12367
+ toolset: "structure",
12368
+ run: forward("workflow.update")
12369
+ },
12370
+ {
12371
+ name: "workflow_list",
12372
+ title: "List org workflows",
12373
+ description: "List built-in templates and this org's catalog copies, each with its required summary. Org-admin; members read the Space allowlist on `space_get`.",
12374
+ inputSchema: {},
12375
+ surfaces: ["mcp", "cli"],
12376
+ toolset: "structure",
12377
+ run: forward("workflow.list")
12378
+ },
12098
12379
  {
12099
12380
  name: "agent_register",
12100
12381
  title: "Register an agent",
@@ -12189,7 +12470,7 @@ var ACTION_DEFINITIONS = [
12189
12470
  {
12190
12471
  name: "space_get",
12191
12472
  title: "Read a space",
12192
- description: "Read a Space's durable context—purpose, guidance, contribution lanes, Topics—and a paged member roster with roles, capabilities, and charters (default 50; `total` is the full roster size). Continue with `cursor`, or use `all=true` / `--all` only when you need every member; it cannot modify membership or guidance.",
12473
+ description: "Read a Space's durable context—purpose, guidance, contribution lanes, attached workflows with summaries, Topics—and a paged member roster with roles, capabilities, and charters (default 50; `total` is the full roster size). Continue with `cursor`, or use `all=true` / `--all` only when you need every member; it cannot modify membership or guidance.",
12193
12474
  inputSchema: {
12194
12475
  spaceId: string2().min(1).describe("the space id"),
12195
12476
  ...PAGINATION_INPUT
@@ -12201,7 +12482,7 @@ var ACTION_DEFINITIONS = [
12201
12482
  {
12202
12483
  name: "topic_get",
12203
12484
  title: "Read a topic",
12204
- description: "Read a Topic's durable context, guidance, open requests, promoted artifacts, and a paged Thread roster (default 50; `total` is the full roster size). Continue with `cursor`, or use `all=true` / `--all` only for intentional complete enumeration; for a concrete question in a large Topic, prefer Topic-scoped `recall`.",
12485
+ description: "Read a Topic's durable context, guidance, selected workflow summary, open requests, promoted artifacts, and a paged Thread roster (default 50; `total` is the full roster size). Continue with `cursor`, or use `all=true` / `--all` only for intentional complete enumeration; for a concrete question in a large Topic, prefer Topic-scoped `recall`.",
12205
12486
  inputSchema: {
12206
12487
  topicId: string2().min(1).describe("the topic id"),
12207
12488
  ...PAGINATION_INPUT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lambdacurry/arbor",
3
- "version": "0.22.41",
3
+ "version": "0.23.1",
4
4
  "description": "The Arbor CLI — a shared workspace for people and agents. The human + headless-agent write path over Arbor's guarded operation surface.",
5
5
  "keywords": [
6
6
  "agents",