@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.
- package/claude-plugin/.claude-plugin/plugin.json +1 -1
- package/claude-plugin/commands/jentrix-end.md +24 -8
- package/claude-plugin/commands/jentrix-review.md +42 -0
- package/codex-plugin/plugins/jentrix/.codex-plugin/plugin.json +1 -1
- package/codex-plugin/plugins/jentrix/skills/jentrix-end/SKILL.md +15 -8
- package/codex-plugin/plugins/jentrix/skills/jentrix-review/SKILL.md +22 -0
- package/dist/main.js +136 -34
- package/package.json +1 -1
- package/surface.json +6 -2
|
@@ -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
|
+
"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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
@@ -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
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
20
|
+
4. Offer the operator a one-line project outcome; append only wording they supplied.
|
|
14
21
|
Declining never blocks closure.
|
|
15
|
-
|
|
22
|
+
5. Run `jentrix session end <session-id>` and relay all output, including the exact
|
|
16
23
|
telemetry line and any capture gap.
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
24891
|
-
await
|
|
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
|
-
|
|
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
|
|
25002
|
+
const board = await findMintBoard(deps2, payload.taskId);
|
|
24936
25003
|
if (!board) {
|
|
24937
25004
|
deps2.writeErr(
|
|
24938
|
-
"note:
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
24973
|
-
|
|
24974
|
-
|
|
24975
|
-
|
|
24976
|
-
|
|
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
|
|
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:
|
|
24989
|
-
description: `Minted from a
|
|
24990
|
-
idempotencyKey: `mint
|
|
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
|
|
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
|
-
"
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
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
|
},
|