@jentrix/cli 0.5.9 → 0.5.11
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 +138 -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.11";
|
|
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,37 @@ 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 result = await callStructured(caller, "get_artifact", {
|
|
24943
|
+
artifactId: id
|
|
24944
|
+
});
|
|
24945
|
+
const record3 = result.artifact ?? result;
|
|
24946
|
+
titles.set(id, String(record3.title ?? ""));
|
|
24947
|
+
} catch {
|
|
24948
|
+
throw new UsageError(
|
|
24949
|
+
`--basis ${id} does not resolve to an artifact you can read \u2014 push the context first (\`jentrix push context\`), or pass a URL`,
|
|
24950
|
+
EXIT_CODES.NOT_FOUND
|
|
24951
|
+
);
|
|
24952
|
+
}
|
|
24953
|
+
}
|
|
24954
|
+
});
|
|
24955
|
+
}
|
|
24956
|
+
for (const ref of refs) {
|
|
24957
|
+
const title = titles.get(ref);
|
|
24958
|
+
lines.push(
|
|
24959
|
+
title !== void 0 ? `- artifact ${ref} \u2014 ${title}` : `- ${ref}`
|
|
24960
|
+
);
|
|
24961
|
+
}
|
|
24962
|
+
return `Based on:
|
|
24963
|
+
${lines.join("\n")}`;
|
|
24964
|
+
}
|
|
24905
24965
|
async function runRefPush(kind, artifactId, taskId, flags, deps2) {
|
|
24906
24966
|
const credentials = deps2.resolveTarget();
|
|
24907
24967
|
const response = await (deps2.fetchImpl ?? fetch)(
|
|
@@ -24929,54 +24989,72 @@ async function runRefPush(kind, artifactId, taskId, flags, deps2) {
|
|
|
24929
24989
|
void kind;
|
|
24930
24990
|
return 0;
|
|
24931
24991
|
}
|
|
24932
|
-
|
|
24992
|
+
var MINTABLE_PUSH_KINDS = ["findings", "gap", "issue"];
|
|
24993
|
+
function isMintableKind(kind) {
|
|
24994
|
+
return MINTABLE_PUSH_KINDS.includes(kind);
|
|
24995
|
+
}
|
|
24996
|
+
var MINTABLE_ARTIFACT_TYPES = ["FINDINGS", "GAP", "ISSUE"];
|
|
24997
|
+
function mintNoun(type) {
|
|
24998
|
+
return type === "GAP" ? "Gap" : type === "ISSUE" ? "Issue" : "Finding";
|
|
24999
|
+
}
|
|
25000
|
+
async function offerCardMint(kind, payload, flags, deps2, sessionId = null) {
|
|
24933
25001
|
if (!payload.artifactId || !payload.taskId) return;
|
|
24934
25002
|
try {
|
|
24935
|
-
const board = await
|
|
25003
|
+
const board = await findMintBoard(deps2, payload.taskId);
|
|
24936
25004
|
if (!board) {
|
|
24937
25005
|
deps2.writeErr(
|
|
24938
|
-
"note:
|
|
25006
|
+
"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
25007
|
);
|
|
24940
25008
|
return;
|
|
24941
25009
|
}
|
|
24942
25010
|
if (!flags.yes) {
|
|
25011
|
+
const noun = kind === "findings" ? "issues" : "follow-up cards";
|
|
24943
25012
|
deps2.writeErr(
|
|
24944
|
-
`note:
|
|
25013
|
+
`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
25014
|
);
|
|
24946
25015
|
return;
|
|
24947
25016
|
}
|
|
24948
|
-
const minted = await
|
|
25017
|
+
const minted = await mintCard(deps2, {
|
|
24949
25018
|
board,
|
|
24950
25019
|
artifactId: payload.artifactId,
|
|
24951
25020
|
anchorTaskId: payload.taskId,
|
|
25021
|
+
noun: mintNoun(ARTIFACT_TYPE_BY_PUSH_KIND[kind] ?? "FINDINGS"),
|
|
25022
|
+
blocks: Boolean(flags.blocks),
|
|
24952
25023
|
sessionId
|
|
24953
25024
|
});
|
|
24954
25025
|
deps2.writeOut(
|
|
24955
|
-
`Minted
|
|
25026
|
+
`Minted ${minted.key ?? minted.id} on ${board.name} \xB7 artifact linked \xB7 ${flags.blocks ? "BLOCKS" : "RELATES_TO"} ${payload.taskId}`
|
|
24956
25027
|
);
|
|
24957
25028
|
} catch (error2) {
|
|
24958
25029
|
deps2.writeErr(
|
|
24959
|
-
`note: could not offer
|
|
25030
|
+
`note: could not offer a card (${error2 instanceof Error ? error2.message : "unknown"}). The artifact is stored; file the card by hand.`
|
|
24960
25031
|
);
|
|
24961
25032
|
}
|
|
24962
25033
|
}
|
|
24963
25034
|
function mintIssueCommandHint(artifactId, taskId) {
|
|
24964
25035
|
return `jentrix artifact mint-issue --artifact ${artifactId} --from-task ${taskId}`;
|
|
24965
25036
|
}
|
|
24966
|
-
async function
|
|
25037
|
+
async function findMintBoard(deps2, taskId) {
|
|
24967
25038
|
return withCaller(deps2, async (caller) => {
|
|
24968
25039
|
const task = await callStructured(caller, "get_task", { taskId });
|
|
24969
25040
|
const workspaceId = String(task.workspaceId ?? "");
|
|
24970
25041
|
if (!workspaceId) return null;
|
|
24971
25042
|
const boards = (await callStructured(caller, "list_boards", { workspaceId })).boards.filter((b) => b.kind === "BUGS" && !b.archivedAt);
|
|
24972
|
-
const
|
|
24973
|
-
|
|
24974
|
-
|
|
24975
|
-
|
|
24976
|
-
|
|
25043
|
+
const candidates = [
|
|
25044
|
+
...boards,
|
|
25045
|
+
...task.boardId ? [{ id: String(task.boardId), name: String(task.boardName ?? "board") }] : []
|
|
25046
|
+
];
|
|
25047
|
+
for (const board of candidates) {
|
|
25048
|
+
const columns = (await callStructured(caller, "list_columns", { boardId: board.id })).columns;
|
|
25049
|
+
const first = columns[0];
|
|
25050
|
+
if (first) {
|
|
25051
|
+
return { id: board.id, name: board.name, columnId: first.id };
|
|
25052
|
+
}
|
|
25053
|
+
}
|
|
25054
|
+
return null;
|
|
24977
25055
|
});
|
|
24978
25056
|
}
|
|
24979
|
-
async function
|
|
25057
|
+
async function mintCard(deps2, input) {
|
|
24980
25058
|
return withCaller(
|
|
24981
25059
|
deps2,
|
|
24982
25060
|
async (caller, target) => {
|
|
@@ -24985,9 +25063,9 @@ async function mintIssueCard(deps2, input) {
|
|
|
24985
25063
|
});
|
|
24986
25064
|
const created = await callStructured(caller, "create_task", {
|
|
24987
25065
|
columnId: input.board.columnId,
|
|
24988
|
-
title:
|
|
24989
|
-
description: `Minted from a
|
|
24990
|
-
idempotencyKey: `mint
|
|
25066
|
+
title: `${input.noun} from ${String(anchor.key ?? input.anchorTaskId)}`,
|
|
25067
|
+
description: `Minted from a ${input.noun.toLowerCase()} artifact pushed on ${String(anchor.key ?? input.anchorTaskId)}. Origin session artifact: ${input.artifactId}.`,
|
|
25068
|
+
idempotencyKey: `mint-${input.artifactId}`
|
|
24991
25069
|
});
|
|
24992
25070
|
const issueId = String(created.id);
|
|
24993
25071
|
await (deps2.fetchImpl ?? fetch)(
|
|
@@ -25005,7 +25083,7 @@ async function mintIssueCard(deps2, input) {
|
|
|
25005
25083
|
await callStructured(caller, "add_task_link", {
|
|
25006
25084
|
fromTaskId: issueId,
|
|
25007
25085
|
toTaskId: input.anchorTaskId,
|
|
25008
|
-
kind: "RELATES_TO"
|
|
25086
|
+
kind: input.blocks ? "BLOCKS" : "RELATES_TO"
|
|
25009
25087
|
});
|
|
25010
25088
|
return { id: issueId, key: created.key };
|
|
25011
25089
|
},
|
|
@@ -25024,17 +25102,31 @@ async function runMintIssue(flags, deps2) {
|
|
|
25024
25102
|
);
|
|
25025
25103
|
sessionId = marker?.sessionId ?? null;
|
|
25026
25104
|
}
|
|
25027
|
-
const
|
|
25105
|
+
const result = await withCaller(
|
|
25106
|
+
deps2,
|
|
25107
|
+
(caller) => callStructured(caller, "get_artifact", { artifactId: flags.artifact })
|
|
25108
|
+
);
|
|
25109
|
+
const record3 = result.artifact ?? result;
|
|
25110
|
+
const type = String(record3.type ?? "");
|
|
25111
|
+
if (!MINTABLE_ARTIFACT_TYPES.includes(type)) {
|
|
25112
|
+
throw new UsageError(
|
|
25113
|
+
`artifact ${flags.artifact} is ${type || "of unknown type"} \u2014 only FINDINGS, GAP, and ISSUE artifacts mint into cards`,
|
|
25114
|
+
EXIT_CODES.CONFLICT
|
|
25115
|
+
);
|
|
25116
|
+
}
|
|
25117
|
+
const board = await findMintBoard(deps2, flags.fromTask);
|
|
25028
25118
|
if (!board) {
|
|
25029
25119
|
throw new UsageError(
|
|
25030
|
-
"
|
|
25120
|
+
"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
25121
|
EXIT_CODES.NOT_FOUND
|
|
25032
25122
|
);
|
|
25033
25123
|
}
|
|
25034
|
-
const minted = await
|
|
25124
|
+
const minted = await mintCard(deps2, {
|
|
25035
25125
|
board,
|
|
25036
25126
|
artifactId: flags.artifact,
|
|
25037
25127
|
anchorTaskId: flags.fromTask,
|
|
25128
|
+
noun: mintNoun(type),
|
|
25129
|
+
blocks: Boolean(flags.blocks),
|
|
25038
25130
|
sessionId
|
|
25039
25131
|
});
|
|
25040
25132
|
deps2.writeOut(
|
|
@@ -25043,8 +25135,9 @@ async function runMintIssue(flags, deps2) {
|
|
|
25043
25135
|
issueKey: minted.key ?? null,
|
|
25044
25136
|
boardId: board.id,
|
|
25045
25137
|
artifactId: flags.artifact,
|
|
25046
|
-
relatedTaskId: flags.fromTask
|
|
25047
|
-
|
|
25138
|
+
relatedTaskId: flags.fromTask,
|
|
25139
|
+
linkKind: flags.blocks ? "BLOCKS" : "RELATES_TO"
|
|
25140
|
+
}) : `Minted ${minted.key ?? minted.id} on ${board.name} \xB7 artifact linked \xB7 ${flags.blocks ? "BLOCKS" : "RELATES_TO"} ${flags.fromTask}`
|
|
25048
25141
|
);
|
|
25049
25142
|
return 0;
|
|
25050
25143
|
} catch (error2) {
|
|
@@ -25060,10 +25153,13 @@ async function runMintIssue(flags, deps2) {
|
|
|
25060
25153
|
}
|
|
25061
25154
|
function registerMintIssueCommand(artifact, deps2, onExit2) {
|
|
25062
25155
|
artifact.command("mint-issue").description(
|
|
25063
|
-
"Mint
|
|
25064
|
-
).requiredOption("--artifact <id>", "the findings artifact to link").requiredOption(
|
|
25156
|
+
"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."
|
|
25157
|
+
).requiredOption("--artifact <id>", "the findings/gap/issue artifact to link").requiredOption(
|
|
25065
25158
|
"--from-task <id>",
|
|
25066
|
-
"the task the
|
|
25159
|
+
"the task the artifact was pushed on (the card RELATES_TO it)"
|
|
25160
|
+
).option(
|
|
25161
|
+
"--blocks",
|
|
25162
|
+
"the card BLOCKS the source task (acceptance-blocking) instead of RELATES_TO"
|
|
25067
25163
|
).option("--json", "stable JSON output").action(
|
|
25068
25164
|
async (flags) => onExit2(await runMintIssue(flags, deps2))
|
|
25069
25165
|
);
|
|
@@ -25072,6 +25168,11 @@ function registerPushCommand(program3, deps2, onExit2) {
|
|
|
25072
25168
|
program3.command("push <kind> [file]").description(
|
|
25073
25169
|
`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
25170
|
).option("--title <title>", "artifact title (defaults to kind + date)").option(
|
|
25171
|
+
"--basis <ref>",
|
|
25172
|
+
'decision pushes only, repeatable: an artifact id (must resolve) or URL the decision rested on \u2014 written as the structured "Based on:" block',
|
|
25173
|
+
(ref, prior) => [...prior, ref],
|
|
25174
|
+
[]
|
|
25175
|
+
).option(
|
|
25075
25176
|
"--session <id>",
|
|
25076
25177
|
"explicit session id (default: this checkout's alignment)"
|
|
25077
25178
|
).option(
|
|
@@ -25085,7 +25186,10 @@ function registerPushCommand(program3, deps2, onExit2) {
|
|
|
25085
25186
|
"reference an EXISTING artifact from --task instead of uploading (one artifact, N tasks)"
|
|
25086
25187
|
).option(
|
|
25087
25188
|
"--yes",
|
|
25088
|
-
"accept the offers this push makes (mint
|
|
25189
|
+
"accept the offers this push makes (mint a card from findings/gap/issue) without prompting"
|
|
25190
|
+
).option(
|
|
25191
|
+
"--blocks",
|
|
25192
|
+
"with --yes on a findings/gap/issue push: the minted card BLOCKS the aligned task (acceptance-blocking) instead of RELATES_TO"
|
|
25089
25193
|
).option("--json", "stable JSON output").action(
|
|
25090
25194
|
async (kind, file, flags) => onExit2(await runPush(kind, file, flags, deps2))
|
|
25091
25195
|
);
|
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
|
},
|