@jentrix/cli 0.5.9 → 0.5.10

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.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "jentrix",
3
3
  "description": "Connect this Claude Code session to a Jentrix project: /jentrix-connect binds the current session (trusted lifecycle hooks supply the session id), /jentrix-align runs the MVP alignment wizard, /jentrix-status and /jentrix-end wrap the same session operations. Business logic lives in the Jentrix CLI — removing this plugin leaves `jentrix session attach` as the universal fallback.",
4
- "version": "0.4.0"
4
+ "version": "0.5.0"
5
5
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: End the connected Jentrix session (pushes the final artifacts, verifies capture, stores the RUN_SUMMARY)
3
- allowed-tools: Bash(jentrix session:*), Bash(jentrix push:*)
3
+ allowed-tools: Bash(jentrix session:*), Bash(jentrix push:*), Bash(jentrix artifact:*), Bash(jentrix column:*), Bash(jentrix task:*), Bash(jentrix tool:*)
4
4
  ---
5
5
 
6
6
  End the connected Jentrix session honestly. For an ALIGNED session (the MVP
@@ -12,14 +12,30 @@ pipeline), the typed artifacts ARE the record — push them before closing:
12
12
  what changed, what's next) and run
13
13
  `jentrix push report --title "<short title>"` with the content on stdin.
14
14
  - Push any decisions made this session that were not already pushed:
15
- `jentrix push decision …` (one per decision).
15
+ `jentrix push decision --basis <artifact-id|url> …` (one per decision;
16
+ `--basis` is repeatable and names what the decision rested on — file the
17
+ context first so the ref resolves).
18
+ - **Gap sweep**: every promised-but-undone item becomes a
19
+ `jentrix push gap` — one per gap, title stating the claim ("Windows hook
20
+ path untested"). A session with no output MUST have at least one gap
21
+ explaining why. Push `jentrix push issue` for anything found broken and
22
+ not yet recorded.
16
23
  - Push any durable lessons: `jentrix push learning …`.
17
24
  - If the session produced a diff/patch worth recording, push it:
18
25
  `git diff | jentrix push diff --title "<what it changes>"`.
19
26
  Ask the operator (native choice UI) before pushing anything they might
20
27
  not want recorded; never invent content — summarize what actually
21
28
  happened.
22
- 2. Move the aligned task to the board's **"In review"** column when the work
29
+ 2. **Batched mint confirmations.** Collect this session's gap/issue/findings
30
+ artifacts that are not yet cards (each push printed its offer) and ask the
31
+ operator ONCE, as a single native choice list: which should become cards?
32
+ For each accepted one run
33
+ `jentrix artifact mint-issue --artifact <id> --from-task <taskId>`
34
+ (add `--blocks` when it blocks acceptance of the aligned task). Mints are
35
+ idempotent per artifact (`mint-<artifactId>`), so a retry converges on the
36
+ same card. A declined mint leaves the artifact exactly as pushed — never
37
+ nag again, never mint unasked.
38
+ 3. Move the aligned task to the board's **"In review"** column when the work
23
39
  is finished — not to a terminal one. Acceptance is a human's to record
24
40
  (Accept / Return on the task panel writes who decided, when, and on which
25
41
  artifacts), and an agent grading its own work as Done is the cheapest habit
@@ -32,7 +48,7 @@ pipeline), the typed artifacts ARE the record — push them before closing:
32
48
  terminal column the operator never got to judge. If it is unclear whether
33
49
  the task is done, ask the operator through the native choice UI. Pushing a
34
50
  `findings` or `report` artifact is not by itself proof the task is done.
35
- 3. **Offer a one-line project outcome.** A project that never changes state
51
+ 4. **Offer a one-line project outcome.** A project that never changes state
36
52
  cannot answer the only question a goal exists to answer — are we closer.
37
53
  Ask the operator, through the native choice UI, for one sentence on what
38
54
  this session moved, and record it on the project's goal statement: read the
@@ -42,11 +58,11 @@ pipeline), the typed artifacts ARE the record — push them before closing:
42
58
  `jentrix tool update_project --args '{"projectId":"<id>","goalStatement":"<existing text>\n<YYYY-MM-DD>: <the operator's sentence>","expectedUpdatedAt":"<updatedAt from the read>"}'`.
43
59
  Offer it; never write a sentence they did not say, and never block the
44
60
  close on it — a declined nudge is a normal outcome.
45
- 4. Run `jentrix session end <session-id>` with the Bash tool (ask the user or
61
+ 5. Run `jentrix session end <session-id>` with the Bash tool (ask the user or
46
62
  run `jentrix session status` first when the id is unknown) and relay the
47
63
  output. Telemetry is recorded by the session host's attested path — never
48
64
  report token numbers yourself.
49
- 5. **Relay the closing telemetry line verbatim.** `session end` prints a
65
+ 6. **Relay the closing telemetry line verbatim.** `session end` prints a
50
66
  `Telemetry:` line, and on stderr it may print `NO TOKEN TELEMETRY: …`.
51
67
  Exit 0 does NOT mean telemetry was recorded — on 2026-08-11 a real session
52
68
  closed at exit 0 having recorded none, and nothing said so. If the warning
@@ -54,9 +70,9 @@ pipeline), the typed artifacts ARE the record — push them before closing:
54
70
  another session's transcript) and the repair for the next session
55
71
  (`jentrix align --provider-session <id> --transcript-path <file>`). Do not
56
72
  restate the numbers as your own claim — quote the line.
57
- 6. The session is SEALED once ended: `jentrix push` refuses it
73
+ 7. The session is SEALED once ended: `jentrix push` refuses it
58
74
  (`SESSION_NOT_ACTIVE`), by design. Anything learned at the close —
59
75
  including the telemetry line above — belongs in the report you pushed in
60
76
  step 1, or in the next session. Do not try to reopen it.
61
- 7. Ending the Jentrix session does NOT terminate this Claude Code session —
77
+ 8. Ending the Jentrix session does NOT terminate this Claude Code session —
62
78
  say so if the user seems to expect it.
@@ -0,0 +1,42 @@
1
+ ---
2
+ description: Review a Jentrix session's typed artifacts by category (input → plan → decision → output → issue → gap → learning → record), check the readiness contract, and post the verdict as a task comment
3
+ allowed-tools: Bash(jentrix tool:*), Bash(jentrix artifact:*), Bash(jentrix session:*), Bash(jentrix comment:*)
4
+ ---
5
+
6
+ Run the category walk over one session's record (docs: Reviewing a session).
7
+ Read-only until the final comment; never modify the tree, the board, or the
8
+ artifacts under review.
9
+
10
+ 1. Resolve the session: the argument if one was given, else the checkout's
11
+ aligned session (`jentrix session status`). Note its aligned task — the
12
+ verdict lands there.
13
+ 2. List the record:
14
+ `jentrix artifact list --workspace <id> --session-id <sessionId> --json`
15
+ (or `jentrix tool list_artifacts --args '{"workspaceId":"…","sessionId":"…"}'`).
16
+ Group the rows by review category — input (PROMPT/GOAL/PRD/SOURCE_DIGEST),
17
+ plan (PLAN), decision (DECISION_MEMO), output (DELIVERABLE/REPORT/DIFF/
18
+ PATCH/PR/BRANCH/COMMIT/SCREENSHOT/RECORDING/MOCKUP/CSV/PDF/DOC + anything
19
+ unmapped), issue (ISSUE/FINDINGS), gap (GAP), learning
20
+ (LEARNING/MEMORY_NOMINATION), record (TRACE/RUN_SUMMARY/LOG/EVAL_REPORT/
21
+ POLICY_REPORT) — and walk them in that order.
22
+ 3. Read what needs reading: `jentrix artifact get --artifact-id <id>` returns a
23
+ short-lived download URL; fetch the bodies of the RUN_SUMMARY, each
24
+ DECISION_MEMO, and every output you are judging.
25
+ 4. Check the readiness contract and verify it yourself — do not just quote
26
+ the summary's verdict:
27
+ - ≥ 1 input artifact;
28
+ - every DECISION_MEMO body carries a `Based on:` block, and each
29
+ artifact-id ref in it RESOLVES (`jentrix artifact get --artifact-id`) — a ref that
30
+ 404s is a finding;
31
+ - ≥ 1 output artifact OR ≥ 1 gap explaining why there is none;
32
+ - the RUN_SUMMARY's Capture integrity section reports complete capture or
33
+ capture off by design.
34
+ 5. Post the verdict as ONE comment on the aligned task
35
+ (`jentrix comment create --task <id> --body …`): the per-category counts,
36
+ each contract check with its evidence, what you actually opened, and the
37
+ conclusion — review-ready or not, and what is missing. Verdicts are
38
+ task-scoped; never move the card and never mark anything accepted —
39
+ Accept/Return is the operator's control.
40
+ 6. If the walk surfaced something broken that is not yet on the record,
41
+ offer (never auto-run) `jentrix push issue` / `jentrix push gap` on the
42
+ session, and the mint command for a card.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jentrix",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Connect Codex tasks to Jentrix projects with trusted lifecycle hooks, alignment, checkpoints, and honest session capture.",
5
5
  "author": {
6
6
  "name": "Jentrix",
@@ -6,18 +6,25 @@ description: End a connected Jentrix session after preserving approved outputs,
6
6
  # End the connected session
7
7
 
8
8
  1. For an aligned session, offer to push a concise final `report`, any unrecorded
9
- `decision` or `learning`, and a useful `diff`. Never invent content or record
10
- material the operator declined.
11
- 2. If work is genuinely complete, read the board's real columns and move the task to
9
+ `decision --basis <artifact-id|url>` or `learning`, and a useful `diff`. Run the
10
+ **gap sweep**: every promised-but-undone item becomes a `jentrix push gap` (title
11
+ states the claim), and anything found broken becomes `jentrix push issue`. Never
12
+ invent content or record material the operator declined.
13
+ 2. **Batched mint confirmations**: list this session's gap/issue/findings artifacts
14
+ not yet minted and ask the operator once which should become cards; for each
15
+ accepted one run `jentrix artifact mint-issue --artifact <id> --from-task <id>`
16
+ (`--blocks` when it blocks acceptance). Idempotent per artifact
17
+ (`mint-<artifactId>`); a declined mint leaves the artifact untouched.
18
+ 3. If work is genuinely complete, read the board's real columns and move the task to
12
19
  **In review**. Never self-accept it into Done.
13
- 3. Offer the operator a one-line project outcome; append only wording they supplied.
20
+ 4. Offer the operator a one-line project outcome; append only wording they supplied.
14
21
  Declining never blocks closure.
15
- 4. Run `jentrix session end <session-id>` and relay all output, including the exact
22
+ 5. Run `jentrix session end <session-id>` and relay all output, including the exact
16
23
  telemetry line and any capture gap.
17
- 5. Explain that Codex lifecycle hooks do not report token receipts, so null token
24
+ 6. Explain that Codex lifecycle hooks do not report token receipts, so null token
18
25
  fields are expected on hook-watched sessions. Do not report estimates.
19
- 6. A final-response artifact exists only when a prior `Stop` hook observed assistant
26
+ 7. A final-response artifact exists only when a prior `Stop` hook observed assistant
20
27
  text before closure. The assistant text produced by this closing turn occurs after
21
28
  the command and must not be claimed as captured.
22
- 7. The Jentrix session is sealed after end; do not retry pushes against it. Ending it
29
+ 8. The Jentrix session is sealed after end; do not retry pushes against it. Ending it
23
30
  does not terminate the Codex task.
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: stacks-review
3
+ description: Review a Jentrix session's typed artifacts by category, verify the readiness contract, and post the verdict as a comment on the aligned task.
4
+ ---
5
+
6
+ # Review a session's record
7
+
8
+ 1. Resolve the target session (argument, else `jentrix session status`) and its
9
+ aligned task — the verdict lands there.
10
+ 2. `jentrix tool list_artifacts --args '{"workspaceId":"…","sessionId":"…"}'` and
11
+ group by review category, walking in order: input → plan → decision → output →
12
+ issue → gap → learning → record.
13
+ 3. Read the RUN_SUMMARY, every DECISION_MEMO, and the outputs under judgment via
14
+ `jentrix artifact get --artifact-id <id>` download URLs.
15
+ 4. Verify the readiness contract yourself (never just quote the verdict): ≥1 input;
16
+ every memo's `Based on:` refs resolve; ≥1 output or ≥1 gap; capture integrity
17
+ holds or capture was off by design.
18
+ 5. Post ONE task comment with per-category counts, each check with evidence, and
19
+ the conclusion. Never move cards, never self-accept — Accept/Return belongs to
20
+ the operator.
21
+ 6. Offer (never auto-run) `jentrix push issue|gap` and the mint command for
22
+ anything the walk surfaced that is not yet on the record.
package/dist/main.js CHANGED
@@ -20962,7 +20962,7 @@ function refreshAccessToken(input) {
20962
20962
 
20963
20963
  // src/client.ts
20964
20964
  var CLI_NAME = "stacks-cli";
20965
- var CLI_VERSION = "0.5.9";
20965
+ var CLI_VERSION = "0.5.10";
20966
20966
  var DEAD_TOKEN_MESSAGE = "authentication failed (HTTP 401): the token is dead or expired \u2014 mint a new PAT at /account/tokens on your Jentrix server and update STACKS_TOKEN (or --token / the config file).";
20967
20967
  var RELOGIN_MESSAGE = "OAuth session expired and could not be refreshed \u2014 run `jentrix login` to sign in again.";
20968
20968
  function originOf2(url2) {
@@ -21803,7 +21803,7 @@ async function inspectRepository(cwd, git = defaultGitRunner) {
21803
21803
 
21804
21804
  // src/commands/align.ts
21805
21805
  import { randomUUID as randomUUID2 } from "node:crypto";
21806
- import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
21806
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
21807
21807
  import { join as join3 } from "node:path";
21808
21808
 
21809
21809
  // src/mcp-config.ts
@@ -24135,6 +24135,16 @@ ${JSON.stringify(session.alignment, null, 2)}`
24135
24135
  },
24136
24136
  detected.providerSessionId
24137
24137
  );
24138
+ try {
24139
+ const spoolDir = join3(deps2.spoolRoot, sessionId);
24140
+ mkdirSync3(spoolDir, { recursive: true });
24141
+ writeFileSync3(
24142
+ join3(spoolDir, "prompt-request.json"),
24143
+ JSON.stringify({ requestedAt: (/* @__PURE__ */ new Date()).toISOString() }),
24144
+ { mode: 384 }
24145
+ );
24146
+ } catch {
24147
+ }
24138
24148
  if (!liveHost && detected.provider === "claude" && detected.transcriptPath && detected.providerSessionId && transcriptBelongsTo(detected.transcriptPath, detected.providerSessionId) === false) {
24139
24149
  deps2.writeErr(
24140
24150
  [
@@ -24730,7 +24740,10 @@ var PUSH_KINDS = [
24730
24740
  "learning",
24731
24741
  "prompt",
24732
24742
  "goal",
24733
- "prd"
24743
+ "prd",
24744
+ "gap",
24745
+ "issue",
24746
+ "context"
24734
24747
  ];
24735
24748
  var ARTIFACT_TYPE_BY_PUSH_KIND = {
24736
24749
  plan: "PLAN",
@@ -24742,7 +24755,10 @@ var ARTIFACT_TYPE_BY_PUSH_KIND = {
24742
24755
  learning: "LEARNING",
24743
24756
  prompt: "PROMPT",
24744
24757
  goal: "GOAL",
24745
- prd: "PRD"
24758
+ prd: "PRD",
24759
+ gap: "GAP",
24760
+ issue: "ISSUE",
24761
+ context: "SOURCE_DIGEST"
24746
24762
  };
24747
24763
  function sessionTarget(sessionId) {
24748
24764
  return {
@@ -24767,7 +24783,17 @@ async function runPush(kind, file, flags, deps2) {
24767
24783
  `unknown kind "${kind}" \u2014 expected one of: ${PUSH_KINDS.join(", ")}`
24768
24784
  );
24769
24785
  }
24786
+ if (flags.basis?.length && kind !== "decision") {
24787
+ throw new UsageError(
24788
+ "--basis records what a decision rested on \u2014 it only applies to `jentrix push decision`"
24789
+ );
24790
+ }
24770
24791
  if (flags.ref) {
24792
+ if (flags.basis?.length) {
24793
+ throw new UsageError(
24794
+ "--ref references an existing artifact, so there is no body to carry a basis block \u2014 drop --basis"
24795
+ );
24796
+ }
24771
24797
  if (!flags.task) {
24772
24798
  throw new UsageError(
24773
24799
  "--ref needs --task <id>: a reference links an existing artifact TO a task"
@@ -24820,7 +24846,10 @@ async function runPush(kind, file, flags, deps2) {
24820
24846
  file ? `${file} is empty` : "stdin is empty \u2014 pipe the content or name a file"
24821
24847
  );
24822
24848
  }
24823
- const body = redactLocal(raw, deps2.env);
24849
+ const withBasis = flags.basis?.length ? `${await basisBlock(flags.basis, deps2)}
24850
+
24851
+ ${raw}` : raw;
24852
+ const body = redactLocal(withBasis, deps2.env);
24824
24853
  const credentials = deps2.resolveTarget();
24825
24854
  const post = (to) => (deps2.fetchImpl ?? fetch)(
24826
24855
  new URL(to.path, stacksBaseUrlOf(credentials.url)),
@@ -24887,8 +24916,8 @@ async function runPush(kind, file, flags, deps2) {
24887
24916
  `note: identical content is already stored on task ${payload.sameBytesOnTaskId}. One truth per artifact: \`jentrix push ${kind} --ref <artifactId> --task ${payload.taskId}\` references it instead of keeping two rows that will disagree once one is revised.`
24888
24917
  );
24889
24918
  }
24890
- if (kind === "findings" && payload.taskId) {
24891
- await offerIssueMint(payload, flags, deps2, correlatedSessionId);
24919
+ if (isMintableKind(kind) && payload.taskId) {
24920
+ await offerCardMint(kind, payload, flags, deps2, correlatedSessionId);
24892
24921
  }
24893
24922
  return 0;
24894
24923
  } catch (error2) {
@@ -24902,6 +24931,36 @@ async function runPush(kind, file, flags, deps2) {
24902
24931
  return 1;
24903
24932
  }
24904
24933
  }
24934
+ async function basisBlock(refs, deps2) {
24935
+ const lines = [];
24936
+ const ids = refs.filter((ref) => !/^https?:\/\//i.test(ref));
24937
+ const titles = /* @__PURE__ */ new Map();
24938
+ if (ids.length) {
24939
+ await withCaller(deps2, async (caller) => {
24940
+ for (const id of ids) {
24941
+ try {
24942
+ const artifact = await callStructured(caller, "get_artifact", {
24943
+ artifactId: id
24944
+ });
24945
+ titles.set(id, String(artifact.title ?? ""));
24946
+ } catch {
24947
+ throw new UsageError(
24948
+ `--basis ${id} does not resolve to an artifact you can read \u2014 push the context first (\`jentrix push context\`), or pass a URL`,
24949
+ EXIT_CODES.NOT_FOUND
24950
+ );
24951
+ }
24952
+ }
24953
+ });
24954
+ }
24955
+ for (const ref of refs) {
24956
+ const title = titles.get(ref);
24957
+ lines.push(
24958
+ title !== void 0 ? `- artifact ${ref} \u2014 ${title}` : `- ${ref}`
24959
+ );
24960
+ }
24961
+ return `Based on:
24962
+ ${lines.join("\n")}`;
24963
+ }
24905
24964
  async function runRefPush(kind, artifactId, taskId, flags, deps2) {
24906
24965
  const credentials = deps2.resolveTarget();
24907
24966
  const response = await (deps2.fetchImpl ?? fetch)(
@@ -24929,54 +24988,72 @@ async function runRefPush(kind, artifactId, taskId, flags, deps2) {
24929
24988
  void kind;
24930
24989
  return 0;
24931
24990
  }
24932
- async function offerIssueMint(payload, flags, deps2, sessionId = null) {
24991
+ var MINTABLE_PUSH_KINDS = ["findings", "gap", "issue"];
24992
+ function isMintableKind(kind) {
24993
+ return MINTABLE_PUSH_KINDS.includes(kind);
24994
+ }
24995
+ var MINTABLE_ARTIFACT_TYPES = ["FINDINGS", "GAP", "ISSUE"];
24996
+ function mintNoun(type) {
24997
+ return type === "GAP" ? "Gap" : type === "ISSUE" ? "Issue" : "Finding";
24998
+ }
24999
+ async function offerCardMint(kind, payload, flags, deps2, sessionId = null) {
24933
25000
  if (!payload.artifactId || !payload.taskId) return;
24934
25001
  try {
24935
- const board = await findIssuesBoard(deps2, payload.taskId);
25002
+ const board = await findMintBoard(deps2, payload.taskId);
24936
25003
  if (!board) {
24937
25004
  deps2.writeErr(
24938
- "note: this workspace has no BUGS board, so there is nowhere to mint an issue. Create one (`jentrix board create --kind BUGS`) and re-push, or file the finding by hand."
25005
+ "note: no board can take a card here (no BUGS board, and the task's own board has no column), so nothing was minted. File the card by hand."
24939
25006
  );
24940
25007
  return;
24941
25008
  }
24942
25009
  if (!flags.yes) {
25010
+ const noun = kind === "findings" ? "issues" : "follow-up cards";
24943
25011
  deps2.writeErr(
24944
- `note: findings usually become issues. \`${mintIssueCommandHint(payload.artifactId, payload.taskId)}\` opens a card on ${board.name}, links this artifact to it, and relates it back to this task. Nothing was created.`
25012
+ `note: ${kind} pushes usually become ${noun}. \`${mintIssueCommandHint(payload.artifactId, payload.taskId)}\` opens a card on ${board.name}, links this artifact to it, and relates it back to this task${flags.blocks ? "" : " (add --blocks when it blocks acceptance)"}. Nothing was created.`
24945
25013
  );
24946
25014
  return;
24947
25015
  }
24948
- const minted = await mintIssueCard(deps2, {
25016
+ const minted = await mintCard(deps2, {
24949
25017
  board,
24950
25018
  artifactId: payload.artifactId,
24951
25019
  anchorTaskId: payload.taskId,
25020
+ noun: mintNoun(ARTIFACT_TYPE_BY_PUSH_KIND[kind] ?? "FINDINGS"),
25021
+ blocks: Boolean(flags.blocks),
24952
25022
  sessionId
24953
25023
  });
24954
25024
  deps2.writeOut(
24955
- `Minted issue ${minted.key ?? minted.id} on ${board.name} \xB7 artifact linked \xB7 RELATES_TO ${payload.taskId}`
25025
+ `Minted ${minted.key ?? minted.id} on ${board.name} \xB7 artifact linked \xB7 ${flags.blocks ? "BLOCKS" : "RELATES_TO"} ${payload.taskId}`
24956
25026
  );
24957
25027
  } catch (error2) {
24958
25028
  deps2.writeErr(
24959
- `note: could not offer an issue card (${error2 instanceof Error ? error2.message : "unknown"}). The finding is stored; file the issue by hand.`
25029
+ `note: could not offer a card (${error2 instanceof Error ? error2.message : "unknown"}). The artifact is stored; file the card by hand.`
24960
25030
  );
24961
25031
  }
24962
25032
  }
24963
25033
  function mintIssueCommandHint(artifactId, taskId) {
24964
25034
  return `jentrix artifact mint-issue --artifact ${artifactId} --from-task ${taskId}`;
24965
25035
  }
24966
- async function findIssuesBoard(deps2, taskId) {
25036
+ async function findMintBoard(deps2, taskId) {
24967
25037
  return withCaller(deps2, async (caller) => {
24968
25038
  const task = await callStructured(caller, "get_task", { taskId });
24969
25039
  const workspaceId = String(task.workspaceId ?? "");
24970
25040
  if (!workspaceId) return null;
24971
25041
  const boards = (await callStructured(caller, "list_boards", { workspaceId })).boards.filter((b) => b.kind === "BUGS" && !b.archivedAt);
24972
- const board = boards[0];
24973
- if (!board) return null;
24974
- const columns = (await callStructured(caller, "list_columns", { boardId: board.id })).columns;
24975
- const first = columns[0];
24976
- return first ? { id: board.id, name: board.name, columnId: first.id } : null;
25042
+ const candidates = [
25043
+ ...boards,
25044
+ ...task.boardId ? [{ id: String(task.boardId), name: String(task.boardName ?? "board") }] : []
25045
+ ];
25046
+ for (const board of candidates) {
25047
+ const columns = (await callStructured(caller, "list_columns", { boardId: board.id })).columns;
25048
+ const first = columns[0];
25049
+ if (first) {
25050
+ return { id: board.id, name: board.name, columnId: first.id };
25051
+ }
25052
+ }
25053
+ return null;
24977
25054
  });
24978
25055
  }
24979
- async function mintIssueCard(deps2, input) {
25056
+ async function mintCard(deps2, input) {
24980
25057
  return withCaller(
24981
25058
  deps2,
24982
25059
  async (caller, target) => {
@@ -24985,9 +25062,9 @@ async function mintIssueCard(deps2, input) {
24985
25062
  });
24986
25063
  const created = await callStructured(caller, "create_task", {
24987
25064
  columnId: input.board.columnId,
24988
- title: `Finding from ${String(anchor.key ?? input.anchorTaskId)}`,
24989
- description: `Minted from a findings artifact pushed on ${String(anchor.key ?? input.anchorTaskId)}. Origin session artifact: ${input.artifactId}.`,
24990
- idempotencyKey: `mint-issue-${input.artifactId}`
25065
+ title: `${input.noun} from ${String(anchor.key ?? input.anchorTaskId)}`,
25066
+ description: `Minted from a ${input.noun.toLowerCase()} artifact pushed on ${String(anchor.key ?? input.anchorTaskId)}. Origin session artifact: ${input.artifactId}.`,
25067
+ idempotencyKey: `mint-${input.artifactId}`
24991
25068
  });
24992
25069
  const issueId = String(created.id);
24993
25070
  await (deps2.fetchImpl ?? fetch)(
@@ -25005,7 +25082,7 @@ async function mintIssueCard(deps2, input) {
25005
25082
  await callStructured(caller, "add_task_link", {
25006
25083
  fromTaskId: issueId,
25007
25084
  toTaskId: input.anchorTaskId,
25008
- kind: "RELATES_TO"
25085
+ kind: input.blocks ? "BLOCKS" : "RELATES_TO"
25009
25086
  });
25010
25087
  return { id: issueId, key: created.key };
25011
25088
  },
@@ -25024,17 +25101,30 @@ async function runMintIssue(flags, deps2) {
25024
25101
  );
25025
25102
  sessionId = marker?.sessionId ?? null;
25026
25103
  }
25027
- const board = await findIssuesBoard(deps2, flags.fromTask);
25104
+ const artifact = await withCaller(
25105
+ deps2,
25106
+ (caller) => callStructured(caller, "get_artifact", { artifactId: flags.artifact })
25107
+ );
25108
+ const type = String(artifact.type ?? "");
25109
+ if (!MINTABLE_ARTIFACT_TYPES.includes(type)) {
25110
+ throw new UsageError(
25111
+ `artifact ${flags.artifact} is ${type || "of unknown type"} \u2014 only FINDINGS, GAP, and ISSUE artifacts mint into cards`,
25112
+ EXIT_CODES.CONFLICT
25113
+ );
25114
+ }
25115
+ const board = await findMintBoard(deps2, flags.fromTask);
25028
25116
  if (!board) {
25029
25117
  throw new UsageError(
25030
- "this workspace has no BUGS board, so there is nowhere to mint an issue. Create one (`jentrix board create --kind BUGS`) and retry, or file the finding by hand.",
25118
+ "no board can take a card here (no BUGS board, and the task's own board has no column). Create one and retry, or file the card by hand.",
25031
25119
  EXIT_CODES.NOT_FOUND
25032
25120
  );
25033
25121
  }
25034
- const minted = await mintIssueCard(deps2, {
25122
+ const minted = await mintCard(deps2, {
25035
25123
  board,
25036
25124
  artifactId: flags.artifact,
25037
25125
  anchorTaskId: flags.fromTask,
25126
+ noun: mintNoun(type),
25127
+ blocks: Boolean(flags.blocks),
25038
25128
  sessionId
25039
25129
  });
25040
25130
  deps2.writeOut(
@@ -25043,8 +25133,9 @@ async function runMintIssue(flags, deps2) {
25043
25133
  issueKey: minted.key ?? null,
25044
25134
  boardId: board.id,
25045
25135
  artifactId: flags.artifact,
25046
- relatedTaskId: flags.fromTask
25047
- }) : `Minted issue ${minted.key ?? minted.id} on ${board.name} \xB7 artifact linked \xB7 RELATES_TO ${flags.fromTask}`
25136
+ relatedTaskId: flags.fromTask,
25137
+ linkKind: flags.blocks ? "BLOCKS" : "RELATES_TO"
25138
+ }) : `Minted ${minted.key ?? minted.id} on ${board.name} \xB7 artifact linked \xB7 ${flags.blocks ? "BLOCKS" : "RELATES_TO"} ${flags.fromTask}`
25048
25139
  );
25049
25140
  return 0;
25050
25141
  } catch (error2) {
@@ -25060,10 +25151,13 @@ async function runMintIssue(flags, deps2) {
25060
25151
  }
25061
25152
  function registerMintIssueCommand(artifact, deps2, onExit2) {
25062
25153
  artifact.command("mint-issue").description(
25063
- "Mint an Issues card from a findings artifact: opens a card on the workspace's BUGS board, links the artifact to it, and relates it back to the source task."
25064
- ).requiredOption("--artifact <id>", "the findings artifact to link").requiredOption(
25154
+ "Mint a card from a findings/gap/issue artifact: opens a card on the workspace's BUGS board (else the task's own board), links the artifact to it, and relates it back to the source task."
25155
+ ).requiredOption("--artifact <id>", "the findings/gap/issue artifact to link").requiredOption(
25065
25156
  "--from-task <id>",
25066
- "the task the finding was pushed on (the card RELATES_TO it)"
25157
+ "the task the artifact was pushed on (the card RELATES_TO it)"
25158
+ ).option(
25159
+ "--blocks",
25160
+ "the card BLOCKS the source task (acceptance-blocking) instead of RELATES_TO"
25067
25161
  ).option("--json", "stable JSON output").action(
25068
25162
  async (flags) => onExit2(await runMintIssue(flags, deps2))
25069
25163
  );
@@ -25072,6 +25166,11 @@ function registerPushCommand(program3, deps2, onExit2) {
25072
25166
  program3.command("push <kind> [file]").description(
25073
25167
  `Push a typed artifact (${PUSH_KINDS.join("|")}) from a file or stdin to the ALIGNED session \u2014 redacted locally and server-side, linked to the aligned project/task.`
25074
25168
  ).option("--title <title>", "artifact title (defaults to kind + date)").option(
25169
+ "--basis <ref>",
25170
+ 'decision pushes only, repeatable: an artifact id (must resolve) or URL the decision rested on \u2014 written as the structured "Based on:" block',
25171
+ (ref, prior) => [...prior, ref],
25172
+ []
25173
+ ).option(
25075
25174
  "--session <id>",
25076
25175
  "explicit session id (default: this checkout's alignment)"
25077
25176
  ).option(
@@ -25085,7 +25184,10 @@ function registerPushCommand(program3, deps2, onExit2) {
25085
25184
  "reference an EXISTING artifact from --task instead of uploading (one artifact, N tasks)"
25086
25185
  ).option(
25087
25186
  "--yes",
25088
- "accept the offers this push makes (mint an issue from findings) without prompting"
25187
+ "accept the offers this push makes (mint a card from findings/gap/issue) without prompting"
25188
+ ).option(
25189
+ "--blocks",
25190
+ "with --yes on a findings/gap/issue push: the minted card BLOCKS the aligned task (acceptance-blocking) instead of RELATES_TO"
25089
25191
  ).option("--json", "stable JSON output").action(
25090
25192
  async (kind, file, flags) => onExit2(await runPush(kind, file, flags, deps2))
25091
25193
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jentrix/cli",
3
- "version": "0.5.9",
3
+ "version": "0.5.10",
4
4
  "description": "Command-line client for the Jentrix MCP surface (jentrix tool <name>, generated noun-verb commands).",
5
5
  "keywords": [
6
6
  "jentrix",
package/surface.json CHANGED
@@ -1018,7 +1018,9 @@
1018
1018
  "LEARNING",
1019
1019
  "PROMPT",
1020
1020
  "GOAL",
1021
- "PRD"
1021
+ "PRD",
1022
+ "GAP",
1023
+ "ISSUE"
1022
1024
  ],
1023
1025
  "type": "string"
1024
1026
  },
@@ -10454,7 +10456,9 @@
10454
10456
  "LEARNING",
10455
10457
  "PROMPT",
10456
10458
  "GOAL",
10457
- "PRD"
10459
+ "PRD",
10460
+ "GAP",
10461
+ "ISSUE"
10458
10462
  ],
10459
10463
  "type": "string"
10460
10464
  },