@jentrix/cli 0.5.12 → 0.5.14
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-align.md +2 -1
- package/claude-plugin/hooks/hooks.json +5 -5
- package/codex-plugin/plugins/jentrix/.codex-plugin/plugin.json +1 -1
- package/codex-plugin/plugins/jentrix/hooks/hooks.json +7 -7
- package/dist/main.js +157 -49
- package/package.json +1 -1
|
@@ -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.5.
|
|
4
|
+
"version": "0.5.1"
|
|
5
5
|
}
|
|
@@ -59,7 +59,8 @@ and do NOT answer any question yourself.
|
|
|
59
59
|
and move with
|
|
60
60
|
`jentrix task move --task <taskId> --to-column-id <columnId>`. Use only
|
|
61
61
|
the columns the board actually has — never invent a status. `/jentrix-end`
|
|
62
|
-
moves it to
|
|
62
|
+
moves it to the In review column when the work is genuinely finished —
|
|
63
|
+
the terminal move is the operator's Accept, never yours. A
|
|
63
64
|
board that never moves is a board that lies about what is in flight.
|
|
64
65
|
11. If align wrote `./.mcp.json`, relay that line too: MCP servers load at
|
|
65
66
|
session start, so the tools in THIS session still point wherever they
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
-
"description": "M20.1 \u00a715.1 \u2014 lifecycle bridge: hooks append their structured stdin payload (session_id, transcript_path, cwd) to the local session context file via `stacks-runner session-hook`. argv carries only the
|
|
2
|
+
"description": "M20.1 \u00a715.1 \u2014 lifecycle bridge: hooks append their structured stdin payload (session_id, transcript_path, cwd) to the local session context file via `stacks-runner session-hook`. argv carries only the provider and event name (the runner resolves the directory cross-platform \u2014 STA-131: a literal $HOME never expands in Windows hook commands) \u2014 never credentials or transcript content. PreCompact/PostCompact additionally run `jentrix session snapshot`, which FOLLOWS THE CAPTURE CONSENT: capture ON preserves the transcript range about to leave the model's context; capture OFF (the MVP default) records the compaction boundary and NO content, because `capture: \"off\"` was disclosed as typed artifacts only, no transcript. Preservation is never distillation \u2014 a hook has no model turn; /jentrix-checkpoint does that. Compaction-path hooks carry an explicit 10s timeout (STA-51): they run inside the operator's /compact, and a slow artifact push must delay it by seconds, not the 60s default.",
|
|
3
3
|
"hooks": {
|
|
4
4
|
"SessionStart": [
|
|
5
5
|
{
|
|
6
6
|
"hooks": [
|
|
7
7
|
{
|
|
8
8
|
"type": "command",
|
|
9
|
-
"command": "stacks-runner session-hook --
|
|
9
|
+
"command": "stacks-runner session-hook --provider claude --event SessionStart"
|
|
10
10
|
}
|
|
11
11
|
]
|
|
12
12
|
}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"hooks": [
|
|
17
17
|
{
|
|
18
18
|
"type": "command",
|
|
19
|
-
"command": "stacks-runner session-hook --
|
|
19
|
+
"command": "stacks-runner session-hook --provider claude --event SessionEnd"
|
|
20
20
|
}
|
|
21
21
|
]
|
|
22
22
|
}
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"hooks": [
|
|
27
27
|
{
|
|
28
28
|
"type": "command",
|
|
29
|
-
"command": "stacks-runner session-hook --
|
|
29
|
+
"command": "stacks-runner session-hook --provider claude --event PreCompact",
|
|
30
30
|
"timeout": 10
|
|
31
31
|
},
|
|
32
32
|
{
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"hooks": [
|
|
43
43
|
{
|
|
44
44
|
"type": "command",
|
|
45
|
-
"command": "stacks-runner session-hook --
|
|
45
|
+
"command": "stacks-runner session-hook --provider claude --event PostCompact",
|
|
46
46
|
"timeout": 10
|
|
47
47
|
},
|
|
48
48
|
{
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"hooks": [
|
|
7
7
|
{
|
|
8
8
|
"type": "command",
|
|
9
|
-
"command": "stacks-runner session-hook --
|
|
9
|
+
"command": "stacks-runner session-hook --provider codex --event SessionStart"
|
|
10
10
|
}
|
|
11
11
|
]
|
|
12
12
|
}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"hooks": [
|
|
17
17
|
{
|
|
18
18
|
"type": "command",
|
|
19
|
-
"command": "stacks-runner session-hook --
|
|
19
|
+
"command": "stacks-runner session-hook --provider codex --event UserPromptSubmit"
|
|
20
20
|
}
|
|
21
21
|
]
|
|
22
22
|
}
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"hooks": [
|
|
27
27
|
{
|
|
28
28
|
"type": "command",
|
|
29
|
-
"command": "stacks-runner session-hook --
|
|
29
|
+
"command": "stacks-runner session-hook --provider codex --event PostToolUse"
|
|
30
30
|
}
|
|
31
31
|
]
|
|
32
32
|
}
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"hooks": [
|
|
37
37
|
{
|
|
38
38
|
"type": "command",
|
|
39
|
-
"command": "stacks-runner session-hook --
|
|
39
|
+
"command": "stacks-runner session-hook --provider codex --event Stop"
|
|
40
40
|
}
|
|
41
41
|
]
|
|
42
42
|
}
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"hooks": [
|
|
47
47
|
{
|
|
48
48
|
"type": "command",
|
|
49
|
-
"command": "stacks-runner session-hook --
|
|
49
|
+
"command": "stacks-runner session-hook --provider codex --event PreCompact",
|
|
50
50
|
"timeout": 10
|
|
51
51
|
},
|
|
52
52
|
{
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"hooks": [
|
|
63
63
|
{
|
|
64
64
|
"type": "command",
|
|
65
|
-
"command": "stacks-runner session-hook --
|
|
65
|
+
"command": "stacks-runner session-hook --provider codex --event PostCompact",
|
|
66
66
|
"timeout": 10
|
|
67
67
|
},
|
|
68
68
|
{
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"hooks": [
|
|
79
79
|
{
|
|
80
80
|
"type": "command",
|
|
81
|
-
"command": "stacks-runner session-hook --
|
|
81
|
+
"command": "stacks-runner session-hook --provider codex --event SessionEnd"
|
|
82
82
|
}
|
|
83
83
|
]
|
|
84
84
|
}
|
package/dist/main.js
CHANGED
|
@@ -10336,7 +10336,7 @@ import {
|
|
|
10336
10336
|
readFileSync as readFileSync7,
|
|
10337
10337
|
statSync as statSync3
|
|
10338
10338
|
} from "node:fs";
|
|
10339
|
-
import { homedir } from "node:os";
|
|
10339
|
+
import { homedir as homedir2 } from "node:os";
|
|
10340
10340
|
import { basename as basename2, delimiter as delimiter2, dirname as dirname4, join as join6, resolve as resolve4 } from "node:path";
|
|
10341
10341
|
import { fileURLToPath } from "node:url";
|
|
10342
10342
|
|
|
@@ -20593,8 +20593,8 @@ var ConfigError = class extends Error {
|
|
|
20593
20593
|
this.exitCode = exitCode;
|
|
20594
20594
|
}
|
|
20595
20595
|
};
|
|
20596
|
-
function configPathFor(
|
|
20597
|
-
return join(
|
|
20596
|
+
function configPathFor(homedir3) {
|
|
20597
|
+
return join(homedir3, ".config", "stacks", "config.json");
|
|
20598
20598
|
}
|
|
20599
20599
|
var PROJECT_CONFIG_DIR = ".stacks";
|
|
20600
20600
|
function projectConfigPathFor(dir) {
|
|
@@ -20610,8 +20610,8 @@ function findProjectConfigPath(startDir, exists = existsSync) {
|
|
|
20610
20610
|
dir = parent;
|
|
20611
20611
|
}
|
|
20612
20612
|
}
|
|
20613
|
-
function resolveConfigPath(cwd,
|
|
20614
|
-
return findProjectConfigPath(cwd, exists) ?? configPathFor(
|
|
20613
|
+
function resolveConfigPath(cwd, homedir3, exists = existsSync) {
|
|
20614
|
+
return findProjectConfigPath(cwd, exists) ?? configPathFor(homedir3);
|
|
20615
20615
|
}
|
|
20616
20616
|
function scaffoldProjectConfigDir(dir) {
|
|
20617
20617
|
const stacksDir = join(dir, PROJECT_CONFIG_DIR);
|
|
@@ -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.14";
|
|
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) {
|
|
@@ -21909,6 +21909,30 @@ import {
|
|
|
21909
21909
|
writeFileSync as writeFileSync2
|
|
21910
21910
|
} from "node:fs";
|
|
21911
21911
|
import { dirname as dirname2, join as join2 } from "node:path";
|
|
21912
|
+
|
|
21913
|
+
// src/retry.ts
|
|
21914
|
+
async function withRateLimitRetry(fn, options) {
|
|
21915
|
+
const { maxRetries, maxWaitSeconds, sleep, now } = options;
|
|
21916
|
+
const start = now();
|
|
21917
|
+
let attempt = 0;
|
|
21918
|
+
for (; ; ) {
|
|
21919
|
+
const result = await fn();
|
|
21920
|
+
const envelope = envelopeOfResult(result);
|
|
21921
|
+
if (envelope?.error.code !== "RATE_LIMITED") return result;
|
|
21922
|
+
const retryAfter = envelope.error.retryAfterSeconds;
|
|
21923
|
+
if (typeof retryAfter !== "number" || !Number.isFinite(retryAfter)) {
|
|
21924
|
+
return result;
|
|
21925
|
+
}
|
|
21926
|
+
if (retryAfter < 0) return result;
|
|
21927
|
+
if (attempt >= maxRetries) return result;
|
|
21928
|
+
const elapsedMs = now() - start;
|
|
21929
|
+
if (elapsedMs + retryAfter * 1e3 > maxWaitSeconds * 1e3) return result;
|
|
21930
|
+
await sleep(retryAfter * 1e3);
|
|
21931
|
+
attempt += 1;
|
|
21932
|
+
}
|
|
21933
|
+
}
|
|
21934
|
+
|
|
21935
|
+
// src/commands/session.ts
|
|
21912
21936
|
var LEGACY_MARKER_KEY = "__v1__";
|
|
21913
21937
|
function isMarker(value) {
|
|
21914
21938
|
return typeof value === "object" && value !== null && typeof value.sessionId === "string";
|
|
@@ -22029,9 +22053,28 @@ function isRecord5(value) {
|
|
|
22029
22053
|
async function callStructured(caller, name, args) {
|
|
22030
22054
|
return call(caller, name, args);
|
|
22031
22055
|
}
|
|
22056
|
+
var ToolCallError = class extends Error {
|
|
22057
|
+
constructor(message, code = null) {
|
|
22058
|
+
super(message);
|
|
22059
|
+
this.code = code;
|
|
22060
|
+
}
|
|
22061
|
+
code;
|
|
22062
|
+
};
|
|
22032
22063
|
async function call(caller, name, args) {
|
|
22033
|
-
const result = await
|
|
22064
|
+
const result = await withRateLimitRetry(
|
|
22065
|
+
() => caller.callTool({ name, arguments: args }),
|
|
22066
|
+
{
|
|
22067
|
+
maxRetries: 2,
|
|
22068
|
+
maxWaitSeconds: 60,
|
|
22069
|
+
sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
|
|
22070
|
+
now: Date.now
|
|
22071
|
+
}
|
|
22072
|
+
);
|
|
22034
22073
|
if (isRecord5(result) && result.isError === true) {
|
|
22074
|
+
const envelope = envelopeOfResult(result);
|
|
22075
|
+
if (envelope) {
|
|
22076
|
+
throw new ToolCallError(envelope.error.message, envelope.error.code);
|
|
22077
|
+
}
|
|
22035
22078
|
let message = "tool call failed";
|
|
22036
22079
|
if (Array.isArray(result.content)) {
|
|
22037
22080
|
const text = result.content.find(
|
|
@@ -22046,7 +22089,7 @@ async function call(caller, name, args) {
|
|
|
22046
22089
|
}
|
|
22047
22090
|
}
|
|
22048
22091
|
}
|
|
22049
|
-
throw new
|
|
22092
|
+
throw new ToolCallError(message);
|
|
22050
22093
|
}
|
|
22051
22094
|
return isRecord5(result) && isRecord5(result.structuredContent) ? result.structuredContent : {};
|
|
22052
22095
|
}
|
|
@@ -23573,6 +23616,26 @@ function registerSessionCommand(program3, deps2, onExit2) {
|
|
|
23573
23616
|
}
|
|
23574
23617
|
|
|
23575
23618
|
// src/commands/align.ts
|
|
23619
|
+
async function resolveWorkspaceFlag(caller, wanted, pinned) {
|
|
23620
|
+
const listed = await callStructured(caller, "list_workspaces", {});
|
|
23621
|
+
const rows = listed.workspaces ?? [];
|
|
23622
|
+
const match = rows.find(
|
|
23623
|
+
(ws) => ws.id === wanted || ws.slug === wanted.toLowerCase()
|
|
23624
|
+
);
|
|
23625
|
+
if (!match) {
|
|
23626
|
+
throw new UsageError(
|
|
23627
|
+
`--workspace ${wanted} matches none of your workspaces (slugs: ${rows.map((ws) => ws.slug).filter(Boolean).join(", ") || "none visible"})`,
|
|
23628
|
+
EXIT_CODES.NOT_FOUND
|
|
23629
|
+
);
|
|
23630
|
+
}
|
|
23631
|
+
if (pinned && pinned.id !== match.id) {
|
|
23632
|
+
throw new UsageError(
|
|
23633
|
+
`--workspace ${wanted} conflicts with this token's pinned workspace (${pinned.id})`,
|
|
23634
|
+
EXIT_CODES.FORBIDDEN
|
|
23635
|
+
);
|
|
23636
|
+
}
|
|
23637
|
+
return match.id;
|
|
23638
|
+
}
|
|
23576
23639
|
var MAX_WORK_ITEMS = 15;
|
|
23577
23640
|
async function projectBoards(caller, projectId) {
|
|
23578
23641
|
const project = await callStructured(caller, "get_project", { projectId });
|
|
@@ -24364,10 +24427,17 @@ async function runAlign(flags, deps2) {
|
|
|
24364
24427
|
let pendingCreateProject = null;
|
|
24365
24428
|
if (flags.newTask) workItem = `__new__:${flags.newTask}`;
|
|
24366
24429
|
if (flags.newProject && flags.newProject.trim()) {
|
|
24367
|
-
|
|
24430
|
+
let workspaceId = catalog.chosenWorkspaceId ?? catalog.pinnedWorkspace?.id ?? null;
|
|
24431
|
+
if (flags.workspace?.trim()) {
|
|
24432
|
+
workspaceId = await resolveWorkspaceFlag(
|
|
24433
|
+
caller,
|
|
24434
|
+
flags.workspace.trim(),
|
|
24435
|
+
catalog.pinnedWorkspace ?? null
|
|
24436
|
+
);
|
|
24437
|
+
}
|
|
24368
24438
|
if (!workspaceId) {
|
|
24369
24439
|
throw new UsageError(
|
|
24370
|
-
"--new-project needs a resolved workspace (a workspace-pinned token
|
|
24440
|
+
"--new-project needs a resolved workspace (a workspace-pinned token, a repo-matched candidate, or --workspace <id-or-slug>) \u2014 none resolved here"
|
|
24371
24441
|
);
|
|
24372
24442
|
}
|
|
24373
24443
|
pendingCreateProject = {
|
|
@@ -24694,6 +24764,9 @@ function registerAlignCommand(program3, deps2, onExit2) {
|
|
|
24694
24764
|
).option("--project <id-or-slug>", "project to align to").option(
|
|
24695
24765
|
"--new-project <name>",
|
|
24696
24766
|
"create a project in the resolved workspace (seeds a board + repo/board links), then align to it"
|
|
24767
|
+
).option(
|
|
24768
|
+
"--workspace <id-or-slug>",
|
|
24769
|
+
"workspace for --new-project when neither a pinned token nor a repo match resolves one (fresh checkout, non-interactive)"
|
|
24697
24770
|
).option("--task <taskId>", "existing work item to align to").option(
|
|
24698
24771
|
"--new-task <title>",
|
|
24699
24772
|
"create a task (existing ops core) and align to it"
|
|
@@ -24944,7 +25017,20 @@ async function basisBlock(refs, deps2) {
|
|
|
24944
25017
|
});
|
|
24945
25018
|
const record3 = result.artifact ?? result;
|
|
24946
25019
|
titles.set(id, String(record3.title ?? ""));
|
|
24947
|
-
} catch {
|
|
25020
|
+
} catch (error2) {
|
|
25021
|
+
const code = error2 instanceof ToolCallError ? error2.code : null;
|
|
25022
|
+
if (code === "RATE_LIMITED") {
|
|
25023
|
+
throw new UsageError(
|
|
25024
|
+
`--basis ${id} could not be verified \u2014 the server is rate limiting reads; the ref may be fine. Retry the push shortly.`,
|
|
25025
|
+
EXIT_CODES.RATE_LIMITED
|
|
25026
|
+
);
|
|
25027
|
+
}
|
|
25028
|
+
if (code && code !== "NOT_FOUND" && code !== "FORBIDDEN") {
|
|
25029
|
+
throw new UsageError(
|
|
25030
|
+
`--basis ${id} could not be verified (${code}) \u2014 the ref may be fine. Retry the push.`,
|
|
25031
|
+
EXIT_CODES.INTERNAL
|
|
25032
|
+
);
|
|
25033
|
+
}
|
|
24948
25034
|
throw new UsageError(
|
|
24949
25035
|
`--basis ${id} does not resolve to an artifact you can read \u2014 push the context first (\`jentrix push context\`), or pass a URL`,
|
|
24950
25036
|
EXIT_CODES.NOT_FOUND
|
|
@@ -25019,11 +25105,13 @@ async function offerCardMint(kind, payload, flags, deps2, sessionId = null) {
|
|
|
25019
25105
|
artifactId: payload.artifactId,
|
|
25020
25106
|
anchorTaskId: payload.taskId,
|
|
25021
25107
|
noun: mintNoun(ARTIFACT_TYPE_BY_PUSH_KIND[kind] ?? "FINDINGS"),
|
|
25108
|
+
// STA-128: the operator's own claim title, when one was given.
|
|
25109
|
+
artifactTitle: flags.title?.trim() || null,
|
|
25022
25110
|
blocks: Boolean(flags.blocks),
|
|
25023
25111
|
sessionId
|
|
25024
25112
|
});
|
|
25025
25113
|
deps2.writeOut(
|
|
25026
|
-
`Minted ${minted.key ?? minted.id} on ${board.name} \xB7 artifact linked \xB7 ${flags.blocks ? "BLOCKS" : "RELATES_TO"} ${payload.taskId}`
|
|
25114
|
+
`Minted ${minted.key ?? minted.id} on ${board.name} \xB7 ${minted.artifactLinked ? "artifact linked" : "artifact NOT linked (see note)"} \xB7 ${flags.blocks ? "BLOCKS" : "RELATES_TO"} ${payload.taskId}`
|
|
25027
25115
|
);
|
|
25028
25116
|
} catch (error2) {
|
|
25029
25117
|
deps2.writeErr(
|
|
@@ -25061,14 +25149,16 @@ async function mintCard(deps2, input) {
|
|
|
25061
25149
|
const anchor = await callStructured(caller, "get_task", {
|
|
25062
25150
|
taskId: input.anchorTaskId
|
|
25063
25151
|
});
|
|
25152
|
+
const claim = input.artifactTitle?.trim();
|
|
25064
25153
|
const created = await callStructured(caller, "create_task", {
|
|
25065
25154
|
columnId: input.board.columnId,
|
|
25066
|
-
title
|
|
25155
|
+
// The task-title cap is 200 chars; the claim form truncates into it.
|
|
25156
|
+
title: (claim ? `${input.noun}: ${claim}` : `${input.noun} from ${String(anchor.key ?? input.anchorTaskId)}`).slice(0, 200),
|
|
25067
25157
|
description: `Minted from a ${input.noun.toLowerCase()} artifact pushed on ${String(anchor.key ?? input.anchorTaskId)}. Origin session artifact: ${input.artifactId}.`,
|
|
25068
25158
|
idempotencyKey: `mint-${input.artifactId}`
|
|
25069
25159
|
});
|
|
25070
25160
|
const issueId = String(created.id);
|
|
25071
|
-
await (deps2.fetchImpl ?? fetch)(
|
|
25161
|
+
const linkResponse = await (deps2.fetchImpl ?? fetch)(
|
|
25072
25162
|
new URL(taskTarget(issueId).path, stacksBaseUrlOf(target.url)),
|
|
25073
25163
|
{
|
|
25074
25164
|
method: "POST",
|
|
@@ -25080,16 +25170,29 @@ async function mintCard(deps2, input) {
|
|
|
25080
25170
|
body: JSON.stringify({ refArtifactId: input.artifactId })
|
|
25081
25171
|
}
|
|
25082
25172
|
);
|
|
25173
|
+
const artifactLinked = linkResponse.ok;
|
|
25174
|
+
if (!artifactLinked) {
|
|
25175
|
+
deps2.writeErr(
|
|
25176
|
+
`note: minted the card but linking artifact ${input.artifactId} to it failed (HTTP ${linkResponse.status}) \u2014 link it by hand: jentrix push ${pushKindForNoun(input.noun)} --task ${issueId} --ref ${input.artifactId}`
|
|
25177
|
+
);
|
|
25178
|
+
}
|
|
25083
25179
|
await callStructured(caller, "add_task_link", {
|
|
25084
25180
|
fromTaskId: issueId,
|
|
25085
25181
|
toTaskId: input.anchorTaskId,
|
|
25086
25182
|
kind: input.blocks ? "BLOCKS" : "RELATES_TO"
|
|
25087
25183
|
});
|
|
25088
|
-
return {
|
|
25184
|
+
return {
|
|
25185
|
+
id: issueId,
|
|
25186
|
+
key: created.key,
|
|
25187
|
+
artifactLinked
|
|
25188
|
+
};
|
|
25089
25189
|
},
|
|
25090
25190
|
input.sessionId ? { sessionId: input.sessionId } : void 0
|
|
25091
25191
|
);
|
|
25092
25192
|
}
|
|
25193
|
+
function pushKindForNoun(noun) {
|
|
25194
|
+
return noun === "Gap" ? "gap" : noun === "Issue" ? "issue" : "findings";
|
|
25195
|
+
}
|
|
25093
25196
|
async function runMintIssue(flags, deps2) {
|
|
25094
25197
|
try {
|
|
25095
25198
|
let sessionId = null;
|
|
@@ -25126,6 +25229,8 @@ async function runMintIssue(flags, deps2) {
|
|
|
25126
25229
|
artifactId: flags.artifact,
|
|
25127
25230
|
anchorTaskId: flags.fromTask,
|
|
25128
25231
|
noun: mintNoun(type),
|
|
25232
|
+
// STA-128: the stored artifact's claim title rides onto the card.
|
|
25233
|
+
artifactTitle: typeof record3.title === "string" ? record3.title : null,
|
|
25129
25234
|
blocks: Boolean(flags.blocks),
|
|
25130
25235
|
sessionId
|
|
25131
25236
|
});
|
|
@@ -25135,9 +25240,10 @@ async function runMintIssue(flags, deps2) {
|
|
|
25135
25240
|
issueKey: minted.key ?? null,
|
|
25136
25241
|
boardId: board.id,
|
|
25137
25242
|
artifactId: flags.artifact,
|
|
25243
|
+
artifactLinked: minted.artifactLinked,
|
|
25138
25244
|
relatedTaskId: flags.fromTask,
|
|
25139
25245
|
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}`
|
|
25246
|
+
}) : `Minted ${minted.key ?? minted.id} on ${board.name} \xB7 ${minted.artifactLinked ? "artifact linked" : "artifact NOT linked (see note)"} \xB7 ${flags.blocks ? "BLOCKS" : "RELATES_TO"} ${flags.fromTask}`
|
|
25141
25247
|
);
|
|
25142
25248
|
return 0;
|
|
25143
25249
|
} catch (error2) {
|
|
@@ -26020,28 +26126,6 @@ function cellText(value) {
|
|
|
26020
26126
|
return text;
|
|
26021
26127
|
}
|
|
26022
26128
|
|
|
26023
|
-
// src/retry.ts
|
|
26024
|
-
async function withRateLimitRetry(fn, options) {
|
|
26025
|
-
const { maxRetries, maxWaitSeconds, sleep, now } = options;
|
|
26026
|
-
const start = now();
|
|
26027
|
-
let attempt = 0;
|
|
26028
|
-
for (; ; ) {
|
|
26029
|
-
const result = await fn();
|
|
26030
|
-
const envelope = envelopeOfResult(result);
|
|
26031
|
-
if (envelope?.error.code !== "RATE_LIMITED") return result;
|
|
26032
|
-
const retryAfter = envelope.error.retryAfterSeconds;
|
|
26033
|
-
if (typeof retryAfter !== "number" || !Number.isFinite(retryAfter)) {
|
|
26034
|
-
return result;
|
|
26035
|
-
}
|
|
26036
|
-
if (retryAfter < 0) return result;
|
|
26037
|
-
if (attempt >= maxRetries) return result;
|
|
26038
|
-
const elapsedMs = now() - start;
|
|
26039
|
-
if (elapsedMs + retryAfter * 1e3 > maxWaitSeconds * 1e3) return result;
|
|
26040
|
-
await sleep(retryAfter * 1e3);
|
|
26041
|
-
attempt += 1;
|
|
26042
|
-
}
|
|
26043
|
-
}
|
|
26044
|
-
|
|
26045
26129
|
// src/call.ts
|
|
26046
26130
|
async function callTool(client, name, args, options) {
|
|
26047
26131
|
let result;
|
|
@@ -27544,6 +27628,7 @@ function registerPluginCommand(program3, deps2, onExit2) {
|
|
|
27544
27628
|
// src/commands/runner.ts
|
|
27545
27629
|
import { execFile as execFile2, spawn as spawn2 } from "node:child_process";
|
|
27546
27630
|
import { constants, promises as fs } from "node:fs";
|
|
27631
|
+
import { homedir } from "node:os";
|
|
27547
27632
|
import { delimiter, isAbsolute, resolve as resolve3 } from "node:path";
|
|
27548
27633
|
var CLI_RUNNER_PROTOCOL_VERSION = 1;
|
|
27549
27634
|
var RUNNER_TIMEOUT_MS = 35e3;
|
|
@@ -27577,7 +27662,10 @@ function renderOperatorIdentity(operator, workspaceName) {
|
|
|
27577
27662
|
}
|
|
27578
27663
|
async function executable(path) {
|
|
27579
27664
|
try {
|
|
27580
|
-
await fs.access(
|
|
27665
|
+
await fs.access(
|
|
27666
|
+
path,
|
|
27667
|
+
process.platform === "win32" ? constants.F_OK : constants.X_OK
|
|
27668
|
+
);
|
|
27581
27669
|
return await fs.realpath(path);
|
|
27582
27670
|
} catch {
|
|
27583
27671
|
return null;
|
|
@@ -27609,6 +27697,16 @@ async function resolveRunnerExecutable(env = process.env) {
|
|
|
27609
27697
|
}
|
|
27610
27698
|
return null;
|
|
27611
27699
|
}
|
|
27700
|
+
async function resolveClaudeExecutable(env = process.env, home = homedir()) {
|
|
27701
|
+
const names = platformExecutableNames("claude");
|
|
27702
|
+
const onPath = await resolveExecutableOnPath(names, env);
|
|
27703
|
+
if (onPath) return onPath;
|
|
27704
|
+
for (const name of names) {
|
|
27705
|
+
const found = await executable(resolve3(home, ".local", "bin", name));
|
|
27706
|
+
if (found) return found;
|
|
27707
|
+
}
|
|
27708
|
+
return null;
|
|
27709
|
+
}
|
|
27612
27710
|
function invokeRunnerProcess(file, args, stdin, timeoutMs = RUNNER_TIMEOUT_MS) {
|
|
27613
27711
|
if (!isAbsolute(file)) {
|
|
27614
27712
|
return Promise.reject(new Error("runner executable path must be absolute"));
|
|
@@ -28132,8 +28230,9 @@ var RUNTIMES = [
|
|
|
28132
28230
|
provider: "claude",
|
|
28133
28231
|
resolve: (deps2) => deps2.resolveClaude(),
|
|
28134
28232
|
absent: [
|
|
28135
|
-
"note: the `claude` CLI
|
|
28136
|
-
"
|
|
28233
|
+
"note: the `claude` CLI was not found \u2014 skipped its plugin.",
|
|
28234
|
+
" Installed already? This terminal's PATH may predate the install \u2014 reopen it and re-run, or run: jentrix plugin install claude",
|
|
28235
|
+
" Otherwise install Claude Code (https://claude.com/claude-code) first."
|
|
28137
28236
|
]
|
|
28138
28237
|
},
|
|
28139
28238
|
{
|
|
@@ -28302,8 +28401,15 @@ async function runSetupCommand(flags, deps2) {
|
|
|
28302
28401
|
}
|
|
28303
28402
|
const codex = await deps2.resolveCodex();
|
|
28304
28403
|
if (codex) await registerCodexMcp(deps2, codex, pending);
|
|
28305
|
-
if (flags.git !== false && deps2.
|
|
28306
|
-
|
|
28404
|
+
if (flags.git !== false && deps2.cwd() !== deps2.homeDir()) {
|
|
28405
|
+
if (deps2.isInteractive) {
|
|
28406
|
+
await bootstrapRepository(deps2);
|
|
28407
|
+
} else if (!await deps2.resolveGit() || (await deps2.git(["rev-parse", "--is-inside-work-tree"], deps2.cwd())).code !== 0) {
|
|
28408
|
+
say(
|
|
28409
|
+
deps2,
|
|
28410
|
+
"note: skipped the repository offer (not an interactive terminal) \u2014 this folder is not a git work tree, and align requires one. Run `git init` here first."
|
|
28411
|
+
);
|
|
28412
|
+
}
|
|
28307
28413
|
}
|
|
28308
28414
|
say(deps2, "");
|
|
28309
28415
|
if (pending.failedPlugins.length) {
|
|
@@ -28725,8 +28831,8 @@ function readStdin() {
|
|
|
28725
28831
|
process.stdin.on("error", reject);
|
|
28726
28832
|
});
|
|
28727
28833
|
}
|
|
28728
|
-
var CONFIG_PATH = resolveConfigPath(process.cwd(),
|
|
28729
|
-
var HOME_CONFIG_PATH = configPathFor(
|
|
28834
|
+
var CONFIG_PATH = resolveConfigPath(process.cwd(), homedir2());
|
|
28835
|
+
var HOME_CONFIG_PATH = configPathFor(homedir2());
|
|
28730
28836
|
function configFile() {
|
|
28731
28837
|
return readConfigFile(CONFIG_PATH);
|
|
28732
28838
|
}
|
|
@@ -28945,7 +29051,7 @@ var sessionDeps = {
|
|
|
28945
29051
|
closeSync3(fd);
|
|
28946
29052
|
}
|
|
28947
29053
|
},
|
|
28948
|
-
spoolRoot: join6(
|
|
29054
|
+
spoolRoot: join6(homedir2(), ".config", "stacks", "session-spool")
|
|
28949
29055
|
};
|
|
28950
29056
|
var sessionCommand = registerSessionCommand(program2, sessionDeps, onExit);
|
|
28951
29057
|
registerSnapshotCommand(sessionCommand, sessionDeps, onExit);
|
|
@@ -28959,7 +29065,9 @@ var CODEX_PLUGIN_DIR = fileURLToPath(
|
|
|
28959
29065
|
var pluginDeps = {
|
|
28960
29066
|
resolvePluginDir: () => existsSync6(PLUGIN_DIR) && isPluginMarketplaceDir(PLUGIN_DIR) ? PLUGIN_DIR : null,
|
|
28961
29067
|
resolveCodexPluginDir: () => existsSync6(CODEX_PLUGIN_DIR) && isCodexPluginMarketplaceDir(CODEX_PLUGIN_DIR) ? CODEX_PLUGIN_DIR : null,
|
|
28962
|
-
|
|
29068
|
+
// STA-133: PATH plus the native installer's ~/.local/bin, so a terminal
|
|
29069
|
+
// whose PATH predates the Claude Code install still finds the binary.
|
|
29070
|
+
resolveClaude: () => resolveClaudeExecutable(process.env),
|
|
28963
29071
|
resolveCodex: () => resolveExecutableOnPath(platformExecutableNames("codex"), process.env),
|
|
28964
29072
|
resolveRunner: () => resolveRunnerExecutable(process.env),
|
|
28965
29073
|
resolveNpm: () => resolveExecutableOnPath(platformExecutableNames("npm"), process.env),
|
|
@@ -29070,7 +29178,7 @@ registerSetupCommand(
|
|
|
29070
29178
|
}
|
|
29071
29179
|
},
|
|
29072
29180
|
login: (flags) => runLoginCommand(flags, loginDeps),
|
|
29073
|
-
codexConfigPath: () => join6(process.env.CODEX_HOME ?? join6(
|
|
29181
|
+
codexConfigPath: () => join6(process.env.CODEX_HOME ?? join6(homedir2(), ".codex"), "config.toml"),
|
|
29074
29182
|
readTextFile: (path) => existsSync6(path) ? readFileSync7(path, "utf8") : null,
|
|
29075
29183
|
appendTextFile: (path, text) => {
|
|
29076
29184
|
mkdirSync4(dirname4(path), { recursive: true });
|
|
@@ -29079,7 +29187,7 @@ registerSetupCommand(
|
|
|
29079
29187
|
copyFile: (from, to) => copyFileSync(from, to),
|
|
29080
29188
|
now: () => /* @__PURE__ */ new Date(),
|
|
29081
29189
|
cwd: () => process.cwd(),
|
|
29082
|
-
homeDir: () =>
|
|
29190
|
+
homeDir: () => homedir2(),
|
|
29083
29191
|
git: defaultGitRunner,
|
|
29084
29192
|
readLine
|
|
29085
29193
|
},
|