@lambdacurry/arbor 0.22.41 → 0.23.0

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 +279 -19
  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.0",
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,7 @@ 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"),
6478
6515
  waysToHelp: text("ways_to_help", { mode: "json" }).$type().notNull().default([]),
6479
6516
  openQuestions: text("open_questions", { mode: "json" }).$type().notNull().default([]),
6480
6517
  expectedOutput: text("expected_output").notNull().default(""),
@@ -7701,6 +7738,133 @@ var STATUS_OPENNESS = {
7701
7738
  var typesWhere = (sense) => Object.keys(STATUS_OPENNESS).filter((s) => STATUS_OPENNESS[s][sense]);
7702
7739
  var WORKING_THREAD_STATUSES = typesWhere("working");
7703
7740
  var CURRENT_WORK_THREAD_STATUSES = typesWhere("currentWork");
7741
+ // ../core/src/lifecycle/workflow.ts
7742
+ var WORKFLOW_SUMMARY_MAX = 500;
7743
+ var WORKFLOW_LABEL_DESCRIPTION_MAX = 200;
7744
+ var LIFECYCLE_TYPE_SET = new Set(LIFECYCLE_TYPES);
7745
+ // ../core/src/lifecycle/builtins.ts
7746
+ var BUILTIN_WORKFLOW_IDS = [
7747
+ "standard",
7748
+ "queue",
7749
+ "content",
7750
+ "experiment",
7751
+ "funnel"
7752
+ ];
7753
+ var col = (key, name, type, order, extra) => ({ key, name, type, order, ...extra });
7754
+ var STANDARD = {
7755
+ id: "standard",
7756
+ name: "Standard",
7757
+ summary: "One label per type using the protocol's own words — the zero-config default and the only all-eight workflow.",
7758
+ labels: [
7759
+ col("backlog", "Backlog", "backlog", 0),
7760
+ col("active", "Active", "active", 1),
7761
+ col("needs-review", "Needs review", "needs-review", 2),
7762
+ col("stuck", "Stuck", "stuck", 3),
7763
+ col("standing", "Standing", "standing", 4),
7764
+ col("resolved", "Resolved", "resolved", 5),
7765
+ col("canceled", "Canceled", "canceled", 6),
7766
+ col("archived", "Archived", "archived", 7)
7767
+ ]
7768
+ };
7769
+ var QUEUE = {
7770
+ id: "queue",
7771
+ name: "Queue",
7772
+ summary: "Arrivals are handled or refused; no review gate; stuck names an external requester.",
7773
+ labels: [
7774
+ col("triage", "Triage", "backlog", 0, {
7775
+ description: "Arrived; not yet accepted or refused."
7776
+ }),
7777
+ col("accepted", "Accepted", "active", 1, { description: "Taken; being handled." }),
7778
+ col("waiting-on-requester", "Waiting on requester", "stuck", 2, {
7779
+ description: "Blocked on the person who sent it, not an internal blocker."
7780
+ }),
7781
+ col("handled", "Handled", "resolved", 3, { description: "The arrival was worked." }),
7782
+ col("declined", "Declined", "canceled", 4, { description: "We decided not to take it." }),
7783
+ col("duplicate", "Duplicate", "archived", 5, {
7784
+ description: "Same as an existing thread; the original holds the work."
7785
+ })
7786
+ ]
7787
+ };
7788
+ var CONTENT = {
7789
+ id: "content",
7790
+ name: "Content",
7791
+ summary: "Draft, edit, approve, publish — two review gates by two authorities, plus a named return path.",
7792
+ labels: [
7793
+ col("pitch", "Pitch", "backlog", 0, { description: "Proposed, not yet in draft." }),
7794
+ col("drafting", "Drafting", "active", 1, {
7795
+ isDefaultForType: true,
7796
+ description: "Being written."
7797
+ }),
7798
+ col("changes-requested", "Changes requested", "active", 2, {
7799
+ description: "Failed a review; the bounce is the work."
7800
+ }),
7801
+ col("in-edit", "In edit", "needs-review", 3, {
7802
+ isDefaultForType: true,
7803
+ description: "Editorial review."
7804
+ }),
7805
+ col("final-approval", "Final approval", "needs-review", 4, {
7806
+ description: "Sign-off by a different authority than edit."
7807
+ }),
7808
+ col("waiting-on-assets", "Waiting on assets", "stuck", 5, {
7809
+ description: "Blocked on material, not a reader."
7810
+ }),
7811
+ col("published", "Published", "resolved", 6, { description: "Out." }),
7812
+ col("killed", "Killed", "canceled", 7, { description: "We decided not to ship it." })
7813
+ ]
7814
+ };
7815
+ var EXPERIMENT = {
7816
+ id: "experiment",
7817
+ name: "Experiment",
7818
+ summary: "A hypothesis is run and owes a readout; two honest endings — adopted, or not.",
7819
+ labels: [
7820
+ col("hypothesis", "Hypothesis", "backlog", 0, {
7821
+ description: "Proposed trial, not yet running."
7822
+ }),
7823
+ col("running", "Running", "active", 1, {
7824
+ description: "Finite trial in flight; owed a readout."
7825
+ }),
7826
+ col("readout", "Readout", "needs-review", 2, {
7827
+ description: "Findings waiting on a reader."
7828
+ }),
7829
+ col("adopted", "Adopted", "resolved", 3, {
7830
+ description: "The hypothesis won; we will keep it."
7831
+ }),
7832
+ col("rejected", "Rejected", "canceled", 4, {
7833
+ description: "The hypothesis ran; we decided not to adopt."
7834
+ })
7835
+ ]
7836
+ };
7837
+ var FUNNEL = {
7838
+ id: "funnel",
7839
+ name: "Funnel",
7840
+ summary: "A ladder of sequential stages against a counterparty, ending won or lost.",
7841
+ labels: [
7842
+ col("lead", "Lead", "backlog", 0, { description: "Not yet qualified." }),
7843
+ col("qualified", "Qualified", "active", 1, {
7844
+ isDefaultForType: true,
7845
+ description: "In the ladder; not yet a proposal."
7846
+ }),
7847
+ col("proposal", "Proposal", "active", 2, {
7848
+ description: "Proposal is out / submitted."
7849
+ }),
7850
+ col("negotiation", "Negotiation", "active", 3, { description: "Terms being worked." }),
7851
+ col("waiting-on-counterparty", "Waiting on counterparty", "stuck", 4, {
7852
+ description: "Blocked on them, not on an internal read."
7853
+ }),
7854
+ col("won", "Won", "resolved", 5, { description: "Closed won." }),
7855
+ col("lost", "Lost", "canceled", 6, {
7856
+ description: "We did not win; keep the reason findable."
7857
+ })
7858
+ ]
7859
+ };
7860
+ var BUILTIN_WORKFLOWS = {
7861
+ standard: STANDARD,
7862
+ queue: QUEUE,
7863
+ content: CONTENT,
7864
+ experiment: EXPERIMENT,
7865
+ funnel: FUNNEL
7866
+ };
7867
+ var STANDARD_WORKFLOW = BUILTIN_WORKFLOWS.standard;
7704
7868
  // ../core/src/ops/secret.ts
7705
7869
  var SECRET_PERMISSIONS = [
7706
7870
  "create",
@@ -8413,8 +8577,26 @@ var thread3 = looseObject({
8413
8577
  title: string2(),
8414
8578
  objective: string2(),
8415
8579
  status: string2().optional(),
8580
+ lifecycleType: string2().optional(),
8581
+ lifecycleLabel: string2().optional(),
8416
8582
  url: string2().optional()
8417
8583
  });
8584
+ var workflowSummary = looseObject({
8585
+ id,
8586
+ name: string2(),
8587
+ summary: string2(),
8588
+ origin: string2().optional(),
8589
+ isDefault: boolean2().optional(),
8590
+ labels: array(jsonObject).optional(),
8591
+ createdAt: string2().optional(),
8592
+ updatedAt: string2().optional()
8593
+ });
8594
+ var transitionFrom = looseObject({
8595
+ label: string2(),
8596
+ name: string2(),
8597
+ type: string2(),
8598
+ description: string2().optional()
8599
+ });
8418
8600
  var topic2 = looseObject({
8419
8601
  id,
8420
8602
  spaceId: id.optional(),
@@ -8714,7 +8896,8 @@ var MCP_OUTPUT_SCHEMAS = {
8714
8896
  topic_update: looseObject({
8715
8897
  id,
8716
8898
  title: string2().optional(),
8717
- purpose: nullableString.optional()
8899
+ purpose: nullableString.optional(),
8900
+ workflowId: nullableString.optional()
8718
8901
  }),
8719
8902
  create_thread: looseObject({ threadId: id, url: string2().optional() }),
8720
8903
  thread_update: looseObject({
@@ -8868,7 +9051,8 @@ var MCP_OUTPUT_SCHEMAS = {
8868
9051
  distillate: array(jsonObject).optional(),
8869
9052
  watching: boolean2().optional(),
8870
9053
  cursor: string2().optional(),
8871
- unchanged: boolean2().optional()
9054
+ unchanged: boolean2().optional(),
9055
+ transitionsFrom: array(transitionFrom).optional()
8872
9056
  }),
8873
9057
  thread_assign: looseObject({
8874
9058
  threadId: id,
@@ -8914,7 +9098,10 @@ var MCP_OUTPUT_SCHEMAS = {
8914
9098
  title: string2().optional(),
8915
9099
  purpose: nullableString.optional(),
8916
9100
  visibility: string2().optional(),
8917
- stage: string2().optional()
9101
+ stage: string2().optional(),
9102
+ attached: jsonObject.optional(),
9103
+ defaulted: jsonObject.optional(),
9104
+ detached: jsonObject.optional()
8918
9105
  }),
8919
9106
  space_get: looseObject({
8920
9107
  space: space3,
@@ -8922,11 +9109,14 @@ var MCP_OUTPUT_SCHEMAS = {
8922
9109
  members: array(member).optional(),
8923
9110
  total: number2().int().min(0),
8924
9111
  nextCursor: string2().optional(),
8925
- computerConfig: resolvedComputerConfig.nullable().optional()
9112
+ computerConfig: resolvedComputerConfig.nullable().optional(),
9113
+ workflows: array(workflowSummary).optional(),
9114
+ defaultWorkflow: workflowSummary.optional()
8926
9115
  }),
8927
9116
  topic_get: looseObject({
8928
9117
  topic: topic2,
8929
9118
  space: space3,
9119
+ workflow: workflowSummary.optional(),
8930
9120
  threads: array(thread3).optional(),
8931
9121
  promotedArtifacts: array(artifact4).optional(),
8932
9122
  total: number2().int().min(0),
@@ -9660,6 +9850,22 @@ var MCP_OUTPUT_SCHEMAS = {
9660
9850
  namePath: string2().optional()
9661
9851
  })),
9662
9852
  truncated: boolean2()
9853
+ }),
9854
+ workflow_create: looseObject({
9855
+ id,
9856
+ name: string2(),
9857
+ summary: string2(),
9858
+ labels: array(jsonObject)
9859
+ }),
9860
+ workflow_update: looseObject({
9861
+ id,
9862
+ name: string2(),
9863
+ summary: string2(),
9864
+ labels: array(jsonObject)
9865
+ }),
9866
+ workflow_list: looseObject({
9867
+ builtins: array(workflowSummary),
9868
+ catalog: array(workflowSummary)
9663
9869
  })
9664
9870
  };
9665
9871
  function outputSchemaFor(actionName) {
@@ -10029,6 +10235,9 @@ var MCP_TOOL_ANNOTATIONS = {
10029
10235
  topic_update: additive,
10030
10236
  create_thread: additive,
10031
10237
  thread_update: additive,
10238
+ workflow_create: additive,
10239
+ workflow_update: additive,
10240
+ workflow_list: readOnly,
10032
10241
  thread_assign: idempotent,
10033
10242
  thread_list: readOnly,
10034
10243
  set_organization_computer: destructiveIdempotent,
@@ -10387,7 +10596,7 @@ Arbor's MCP is the collaboration and durable-knowledge surface. Configure, autho
10387
10596
  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
10597
  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
10598
  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.
10599
+ 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
10600
  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
10601
 
10393
10602
  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 +10722,14 @@ var PAGINATION_INPUT = {
10513
10722
  cursor: string2().optional().describe("opaque cursor from a prior response's nextCursor (use with --limit)"),
10514
10723
  all: boolean2().optional().describe("explicit complete retrieval — skip the bounded default page. Prefer limit+cursor unless you need the whole collection")
10515
10724
  };
10725
+ var WORKFLOW_LABEL_INPUT = object({
10726
+ key: string2().min(1).describe("stable id within this workflow"),
10727
+ name: string2().min(1).describe("room-facing column name"),
10728
+ type: _enum(LIFECYCLE_TYPES).describe("protocol type this column occupies"),
10729
+ order: number2().int().describe("column order, 0-based"),
10730
+ isDefaultForType: boolean2().optional().describe("required when this type has more than one label"),
10731
+ description: string2().max(WORKFLOW_LABEL_DESCRIPTION_MAX).optional().describe("optional teaching prose; no consumer branches on it")
10732
+ });
10516
10733
  var LIVE_PREVIEW_CONFIG_INPUT = {
10517
10734
  livePreview: object({
10518
10735
  command: string2().min(1).max(4000).describe("explicit project preview command"),
@@ -10897,11 +11114,12 @@ var ACTION_DEFINITIONS = [
10897
11114
  {
10898
11115
  name: "create_topic",
10899
11116
  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.",
11117
+ 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
11118
  inputSchema: {
10902
11119
  spaceId: string2().describe("the space to create the topic in, spc_…"),
10903
11120
  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")
11121
+ 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"),
11122
+ workflow: string2().optional().describe("catalog id from `space_get.workflows` (pick by summary); must already be attached to this Space")
10905
11123
  },
10906
11124
  surfaces: ["mcp", "cli"],
10907
11125
  toolset: "structure",
@@ -10910,11 +11128,12 @@ var ACTION_DEFINITIONS = [
10910
11128
  {
10911
11129
  name: "topic_update",
10912
11130
  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.",
11131
+ 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
11132
  inputSchema: {
10915
11133
  topicId: string2().describe("the topic to edit, top_…"),
10916
11134
  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")
11135
+ 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"),
11136
+ 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
11137
  },
10919
11138
  surfaces: ["mcp", "cli"],
10920
11139
  toolset: "spaces",
@@ -10985,12 +11204,13 @@ var ACTION_DEFINITIONS = [
10985
11204
  {
10986
11205
  name: "thread_update",
10987
11206
  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.",
11207
+ 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` (not stored yet), and the temporary help list with `charter` ways_to_help.",
10989
11208
  inputSchema: {
10990
11209
  threadId: string2().describe("the thread to edit, thr_…"),
10991
11210
  title: string2().min(1).optional().describe("a new title — the question, outcome, investigation, instance, or standing concern"),
10992
11211
  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_…")
11212
+ topicId: string2().optional().describe("move the thread under this topic of the same space, top_…"),
11213
+ rank: string2().optional().describe("LexoRank string for card order inside one lifecycle column; not stored yet (returns 400 until S6)")
10994
11214
  },
10995
11215
  surfaces: ["mcp", "cli"],
10996
11216
  toolset: "loop",
@@ -11510,10 +11730,10 @@ var ACTION_DEFINITIONS = [
11510
11730
  {
11511
11731
  name: "transition_thread",
11512
11732
  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.",
11733
+ description: transitionThreadActionDescription(),
11514
11734
  inputSchema: {
11515
11735
  threadId: string2().describe("the thread to move, thr_…"),
11516
- to: _enum(LIFECYCLE_TYPES).describe("target Thread lifecycle type"),
11736
+ to: string2().min(1).describe("label key, case-insensitive label name, or bare type (`resolved` still works as a type name)"),
11517
11737
  reason: string2().optional().describe("optional one-line why, recorded on the event")
11518
11738
  },
11519
11739
  surfaces: ["mcp", "cli"],
@@ -12084,17 +12304,57 @@ var ACTION_DEFINITIONS = [
12084
12304
  {
12085
12305
  name: "space_update",
12086
12306
  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.",
12307
+ 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
12308
  inputSchema: {
12089
12309
  spaceId: string2().describe("the space, spc_…"),
12090
12310
  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
12311
  title: string2().optional().describe("a new title for the space"),
12092
- visibility: _enum(["open", "private"]).optional().describe("open or private (invite-only)")
12312
+ visibility: _enum(["open", "private"]).optional().describe("open or private (invite-only)"),
12313
+ attachWorkflow: string2().optional().describe("org-admin: attach this catalog workflow id to the Space allowlist"),
12314
+ detachWorkflow: string2().optional().describe("org-admin: detach this catalog workflow; rejected if a Topic still selects it"),
12315
+ defaultWorkflow: string2().optional().describe("org-admin: name which attached workflow new Topics inherit")
12093
12316
  },
12094
12317
  surfaces: ["mcp", "cli"],
12095
12318
  toolset: "spaces",
12096
12319
  run: forward("space.update")
12097
12320
  },
12321
+ {
12322
+ name: "workflow_create",
12323
+ title: "Create a catalog workflow",
12324
+ 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.",
12325
+ inputSchema: {
12326
+ fromBuiltin: _enum(BUILTIN_WORKFLOW_IDS).optional().describe("copy this built-in template into the catalog"),
12327
+ name: string2().min(1).optional().describe("catalog name; defaults from the built-in"),
12328
+ summary: string2().max(WORKFLOW_SUMMARY_MAX).optional().describe("required unless `--from-builtin`; how an agent picks this workflow"),
12329
+ labels: array(WORKFLOW_LABEL_INPUT).optional().describe("required unless `--from-builtin`; ordered columns")
12330
+ },
12331
+ surfaces: ["mcp", "cli"],
12332
+ toolset: "structure",
12333
+ run: forward("workflow.create")
12334
+ },
12335
+ {
12336
+ name: "workflow_update",
12337
+ title: "Edit a catalog workflow",
12338
+ 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.",
12339
+ inputSchema: {
12340
+ workflowId: string2().describe("the catalog workflow, wfl_…"),
12341
+ name: string2().min(1).optional().describe("a new catalog name"),
12342
+ summary: string2().min(1).max(WORKFLOW_SUMMARY_MAX).describe("required summary — how an agent picks this workflow"),
12343
+ labels: array(WORKFLOW_LABEL_INPUT).min(1).describe("the full replacement label set")
12344
+ },
12345
+ surfaces: ["mcp", "cli"],
12346
+ toolset: "structure",
12347
+ run: forward("workflow.update")
12348
+ },
12349
+ {
12350
+ name: "workflow_list",
12351
+ title: "List org workflows",
12352
+ 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`.",
12353
+ inputSchema: {},
12354
+ surfaces: ["mcp", "cli"],
12355
+ toolset: "structure",
12356
+ run: forward("workflow.list")
12357
+ },
12098
12358
  {
12099
12359
  name: "agent_register",
12100
12360
  title: "Register an agent",
@@ -12189,7 +12449,7 @@ var ACTION_DEFINITIONS = [
12189
12449
  {
12190
12450
  name: "space_get",
12191
12451
  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.",
12452
+ 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
12453
  inputSchema: {
12194
12454
  spaceId: string2().min(1).describe("the space id"),
12195
12455
  ...PAGINATION_INPUT
@@ -12201,7 +12461,7 @@ var ACTION_DEFINITIONS = [
12201
12461
  {
12202
12462
  name: "topic_get",
12203
12463
  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`.",
12464
+ 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
12465
  inputSchema: {
12206
12466
  topicId: string2().min(1).describe("the topic id"),
12207
12467
  ...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.0",
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",