@jentrix/cli 0.5.19 → 0.5.21
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-checkpoint.md +9 -0
- package/claude-plugin/commands/jentrix-end.md +14 -0
- package/codex-plugin/plugins/jentrix/.codex-plugin/plugin.json +3 -3
- package/codex-plugin/plugins/jentrix/hooks/hooks.json +17 -10
- package/codex-plugin/plugins/jentrix/skills/jentrix-align/SKILL.md +7 -0
- package/codex-plugin/plugins/jentrix/skills/jentrix-checkpoint/SKILL.md +7 -0
- package/codex-plugin/plugins/jentrix/skills/jentrix-connect/SKILL.md +7 -2
- package/codex-plugin/plugins/jentrix/skills/jentrix-end/SKILL.md +13 -0
- package/codex-plugin/plugins/jentrix/skills/jentrix-status/SKILL.md +5 -1
- package/dist/main.js +270 -22
- package/package.json +1 -1
- package/surface.json +25 -0
|
@@ -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.3"
|
|
5
5
|
}
|
|
@@ -49,6 +49,15 @@ the plan, when a long investigation resolves, or before handing the work over.
|
|
|
49
49
|
`jentrix task move --task <id> --to-column-id <id>`. Only the columns the
|
|
50
50
|
board actually has.
|
|
51
51
|
|
|
52
|
+
**Cadence duty (evidence floor).** Checkpoint after EVERY commit, not only at
|
|
53
|
+
the end: push the `decision` (with `--basis`) the commit rested on the moment
|
|
54
|
+
it lands, and run its gates through `jentrix push log --from-cmd "<command>"`
|
|
55
|
+
so the exit code and output tail are recorded as a LOG. Record quality must
|
|
56
|
+
track the work, not end-of-session diligence — `jentrix session end` enforces
|
|
57
|
+
this (it refuses on commits with no decision record and on sectioned reports
|
|
58
|
+
with no typed artifacts; a deliberate exception is an ordinary
|
|
59
|
+
`jentrix push gap`).
|
|
60
|
+
|
|
52
61
|
Note for the operator: this records the distillation. On a session aligned
|
|
53
62
|
with `--capture`, the raw pre-compaction range is preserved separately and
|
|
54
63
|
automatically by the `PreCompact` hook, so the bytes survive even when this
|
|
@@ -77,3 +77,17 @@ pipeline), the typed artifacts ARE the record — push them before closing:
|
|
|
77
77
|
step 1, or in the next session. Do not try to reopen it.
|
|
78
78
|
8. Ending the Jentrix session does NOT terminate this Claude Code session —
|
|
79
79
|
say so if the user seems to expect it.
|
|
80
|
+
|
|
81
|
+
**Evidence floor.** `jentrix session end` enforces fixed checks and will
|
|
82
|
+
REFUSE to close when they are unmet: E1 — HEAD moved but no attested DIFF
|
|
83
|
+
(the CLI pushes the real patch itself; just re-run `session end` from the
|
|
84
|
+
aligned checkout); E2 — commits with no `jentrix push decision` and no
|
|
85
|
+
declared-deviation `jentrix push gap`; E3 — a REPORT carrying `## Learnings`
|
|
86
|
+
/ `## Gaps` / `## Decisions` sections while the session has zero
|
|
87
|
+
corresponding typed artifacts (split them into `push learning|gap|decision`,
|
|
88
|
+
never one blob). Comply by pushing the named evidence and retrying, or
|
|
89
|
+
deviate honestly with a `gap` — `--acknowledge-evidence-gaps` closes anyway
|
|
90
|
+
and stamps each unmet check MISSING into Review readiness. Run every gate
|
|
91
|
+
through `jentrix push log --from-cmd "<command>"` (it records the exit code
|
|
92
|
+
and output tail as a LOG and exits with the command's own code), so "tests
|
|
93
|
+
green" claims carry evidence instead of tripping the E4 advisory.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jentrix",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Connect Codex tasks to Jentrix
|
|
3
|
+
"version": "0.2.6",
|
|
4
|
+
"description": "Connect Codex tasks to Jentrix with trusted lifecycle hooks, exact-rollout telemetry recovery, typed artifacts, and operator-confirmed alignment.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Jentrix",
|
|
7
7
|
"url": "https://github.com/jentrix-au"
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"interface": {
|
|
15
15
|
"displayName": "Jentrix",
|
|
16
16
|
"shortDescription": "Connect this Codex task to Jentrix.",
|
|
17
|
-
"longDescription": "Align a Codex task to a Jentrix project and task, preserve typed checkpoints,
|
|
17
|
+
"longDescription": "Align a Codex task to a Jentrix project and task, preserve typed artifacts and checkpoints, capture supported lifecycle evidence through trusted hooks, and recover provider-reported usage from the exact task rollout when hooks are unavailable.",
|
|
18
18
|
"developerName": "Jentrix",
|
|
19
19
|
"category": "Productivity",
|
|
20
20
|
"capabilities": ["Interactive", "Write"],
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"description": "Trusted Codex lifecycle bridge.
|
|
2
|
+
"description": "Trusted Codex lifecycle bridge. Structured hook stdin is appended locally; credentials and transcript content never enter argv. Hooks remain inactive until the operator reviews and trusts them. PreCompact/PostCompact additionally preserve the approved capture boundary; capture off records only the boundary, never transcript content.",
|
|
3
3
|
"hooks": {
|
|
4
4
|
"SessionStart": [
|
|
5
5
|
{
|
|
6
6
|
"hooks": [
|
|
7
7
|
{
|
|
8
8
|
"type": "command",
|
|
9
|
-
"command": "stacks-runner session-hook --provider codex --event SessionStart"
|
|
9
|
+
"command": "stacks-runner session-hook --provider codex --event SessionStart",
|
|
10
|
+
"timeout": 3
|
|
10
11
|
}
|
|
11
12
|
]
|
|
12
13
|
}
|
|
@@ -16,7 +17,8 @@
|
|
|
16
17
|
"hooks": [
|
|
17
18
|
{
|
|
18
19
|
"type": "command",
|
|
19
|
-
"command": "stacks-runner session-hook --provider codex --event UserPromptSubmit"
|
|
20
|
+
"command": "stacks-runner session-hook --provider codex --event UserPromptSubmit",
|
|
21
|
+
"timeout": 3
|
|
20
22
|
}
|
|
21
23
|
]
|
|
22
24
|
}
|
|
@@ -26,7 +28,8 @@
|
|
|
26
28
|
"hooks": [
|
|
27
29
|
{
|
|
28
30
|
"type": "command",
|
|
29
|
-
"command": "stacks-runner session-hook --provider codex --event PostToolUse"
|
|
31
|
+
"command": "stacks-runner session-hook --provider codex --event PostToolUse",
|
|
32
|
+
"timeout": 3
|
|
30
33
|
}
|
|
31
34
|
]
|
|
32
35
|
}
|
|
@@ -36,7 +39,8 @@
|
|
|
36
39
|
"hooks": [
|
|
37
40
|
{
|
|
38
41
|
"type": "command",
|
|
39
|
-
"command": "stacks-runner session-hook --provider codex --event Stop"
|
|
42
|
+
"command": "stacks-runner session-hook --provider codex --event Stop",
|
|
43
|
+
"timeout": 3
|
|
40
44
|
}
|
|
41
45
|
]
|
|
42
46
|
}
|
|
@@ -47,12 +51,13 @@
|
|
|
47
51
|
{
|
|
48
52
|
"type": "command",
|
|
49
53
|
"command": "stacks-runner session-hook --provider codex --event PreCompact",
|
|
50
|
-
"timeout":
|
|
54
|
+
"timeout": 3
|
|
51
55
|
},
|
|
52
56
|
{
|
|
53
57
|
"type": "command",
|
|
54
58
|
"command": "jentrix session snapshot --event PreCompact",
|
|
55
|
-
"timeout": 10
|
|
59
|
+
"timeout": 10,
|
|
60
|
+
"statusMessage": "Preserving the Jentrix capture boundary"
|
|
56
61
|
}
|
|
57
62
|
]
|
|
58
63
|
}
|
|
@@ -63,12 +68,13 @@
|
|
|
63
68
|
{
|
|
64
69
|
"type": "command",
|
|
65
70
|
"command": "stacks-runner session-hook --provider codex --event PostCompact",
|
|
66
|
-
"timeout":
|
|
71
|
+
"timeout": 3
|
|
67
72
|
},
|
|
68
73
|
{
|
|
69
74
|
"type": "command",
|
|
70
75
|
"command": "jentrix session snapshot --event PostCompact",
|
|
71
|
-
"timeout": 10
|
|
76
|
+
"timeout": 10,
|
|
77
|
+
"statusMessage": "Recording the Jentrix compaction boundary"
|
|
72
78
|
}
|
|
73
79
|
]
|
|
74
80
|
}
|
|
@@ -78,7 +84,8 @@
|
|
|
78
84
|
"hooks": [
|
|
79
85
|
{
|
|
80
86
|
"type": "command",
|
|
81
|
-
"command": "stacks-runner session-hook --provider codex --event SessionEnd"
|
|
87
|
+
"command": "stacks-runner session-hook --provider codex --event SessionEnd",
|
|
88
|
+
"timeout": 3
|
|
82
89
|
}
|
|
83
90
|
]
|
|
84
91
|
}
|
|
@@ -34,6 +34,13 @@ description: Align the current Codex task to a Jentrix workspace, project, work
|
|
|
34
34
|
8. Echo the returned server alignment snapshot verbatim. If `.mcp.json` changed,
|
|
35
35
|
explain that the current task must reconnect MCP or restart before using it.
|
|
36
36
|
|
|
37
|
+
If detection names an exact Codex rollout fallback instead of a lifecycle-hook
|
|
38
|
+
record, say what that means: provider token receipts and the opening prompt remain
|
|
39
|
+
collectable, but prompt/tool/assistant lifecycle events are not. Direct the operator
|
|
40
|
+
to review and trust the plugin hooks with `/hooks`, then start a new Codex task for
|
|
41
|
+
full lifecycle coverage. Never claim `coverage: COMPLETE` for token receipts proves
|
|
42
|
+
that hooks ran.
|
|
43
|
+
|
|
37
44
|
TRACE capture is off by default. Pass `--capture` only when the operator explicitly
|
|
38
45
|
approved full hook-derived trace capture. Same-project re-alignment converges;
|
|
39
46
|
different-project re-alignment must be refused until `jentrix session end`.
|
|
@@ -20,3 +20,10 @@ status is stale, read the real columns and correct it.
|
|
|
20
20
|
|
|
21
21
|
The compact hooks preserve only the approved evidence boundary; this skill performs
|
|
22
22
|
the model-authored distillation.
|
|
23
|
+
|
|
24
|
+
**Cadence duty (evidence floor).** Checkpoint after EVERY commit, not only at the
|
|
25
|
+
end: push the `decision` (with `--basis`) the commit rested on the moment it lands,
|
|
26
|
+
and run its gates through `jentrix push log --from-cmd "<command>"` so the exit code
|
|
27
|
+
and output tail land as a LOG. `jentrix session end` enforces this — it refuses on
|
|
28
|
+
commits with no decision record and on sectioned reports with no typed artifacts;
|
|
29
|
+
a deliberate exception is an ordinary `jentrix push gap`.
|
|
@@ -18,5 +18,10 @@ trusted environment and lifecycle-hook context. Relay the command output or erro
|
|
|
18
18
|
verbatim. The command starts the hook-watching host in the background and prints
|
|
19
19
|
its pid; do not start a second host. Never put credentials on the command line.
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
When lifecycle hooks are unavailable, the CLI may recover telemetry only from the
|
|
22
|
+
exact `rollout-*-${CODEX_THREAD_ID}.jsonl` under the trusted Codex home. Accept that
|
|
23
|
+
exact match; never choose the newest session file. Explain that the fallback keeps
|
|
24
|
+
provider token receipts and opening-prompt provenance, but not prompt/tool/assistant
|
|
25
|
+
lifecycle events. Direct the operator to review and trust the plugin hooks with
|
|
26
|
+
`/hooks`, then start a new Codex task for full lifecycle coverage. If trusted task
|
|
27
|
+
identity or the exact rollout is unavailable, relay the refusal and do not guess.
|
|
@@ -29,3 +29,16 @@ description: End a connected Jentrix session after preserving approved outputs,
|
|
|
29
29
|
the command and must not be claimed as captured.
|
|
30
30
|
8. The Jentrix session is sealed after end; do not retry pushes against it. Ending it
|
|
31
31
|
does not terminate the Codex task.
|
|
32
|
+
|
|
33
|
+
**Evidence floor.** `jentrix session end` REFUSES to close on unmet checks: E1 —
|
|
34
|
+
HEAD moved but no attested DIFF (the CLI pushes the real patch itself; re-run
|
|
35
|
+
`session end` from the aligned checkout); E2 — commits with no `jentrix push
|
|
36
|
+
decision` and no declared-deviation `jentrix push gap`; E3 — a REPORT carrying
|
|
37
|
+
`## Learnings` / `## Gaps` / `## Decisions` sections while the session has zero
|
|
38
|
+
corresponding typed artifacts (split them into `push learning|gap|decision`).
|
|
39
|
+
Comply by pushing the named evidence and retrying, or deviate honestly with a
|
|
40
|
+
`gap`; `--acknowledge-evidence-gaps` closes anyway and stamps each unmet check
|
|
41
|
+
MISSING into Review readiness. Run every gate through
|
|
42
|
+
`jentrix push log --from-cmd "<command>"` (records exit code + output tail as a
|
|
43
|
+
LOG, exits with the command's own code) so gate claims carry evidence instead of
|
|
44
|
+
tripping the E4 advisory.
|
|
@@ -8,4 +8,8 @@ description: Show the connected Jentrix session, alignment, capture health, and
|
|
|
8
8
|
|
|
9
9
|
Run `jentrix session status` and append the session id when the user supplied one.
|
|
10
10
|
Relay stdout and stderr verbatim, including capture or telemetry warnings. Never turn
|
|
11
|
-
null token values into zero or infer provider usage that
|
|
11
|
+
null token values into zero or infer provider usage that Codex did not report.
|
|
12
|
+
`coverage: COMPLETE` describes provider token-receipt coverage only; it does not prove
|
|
13
|
+
the lifecycle hooks ran. If the session uses the exact-rollout fallback, report that
|
|
14
|
+
prompt/tool/assistant lifecycle evidence is unavailable and point to `/hooks` plus a
|
|
15
|
+
new Codex task for future full coverage.
|
package/dist/main.js
CHANGED
|
@@ -10324,7 +10324,7 @@ var require_dist = __commonJS({
|
|
|
10324
10324
|
});
|
|
10325
10325
|
|
|
10326
10326
|
// src/main.ts
|
|
10327
|
-
import { spawn as
|
|
10327
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
10328
10328
|
import { createInterface } from "node:readline";
|
|
10329
10329
|
import {
|
|
10330
10330
|
appendFileSync,
|
|
@@ -20966,7 +20966,7 @@ function refreshAccessToken(input) {
|
|
|
20966
20966
|
|
|
20967
20967
|
// src/client.ts
|
|
20968
20968
|
var CLI_NAME = "stacks-cli";
|
|
20969
|
-
var CLI_VERSION = "0.5.
|
|
20969
|
+
var CLI_VERSION = "0.5.21";
|
|
20970
20970
|
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).";
|
|
20971
20971
|
var RELOGIN_MESSAGE = "OAuth session expired and could not be refreshed \u2014 run `jentrix login` to sign in again.";
|
|
20972
20972
|
function originOf2(url2) {
|
|
@@ -21302,6 +21302,7 @@ function safeStringify(value) {
|
|
|
21302
21302
|
}
|
|
21303
21303
|
|
|
21304
21304
|
// src/commands/push.ts
|
|
21305
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
21305
21306
|
import { readFileSync as readFileSync4 } from "node:fs";
|
|
21306
21307
|
|
|
21307
21308
|
// src/align-questions.ts
|
|
@@ -22584,8 +22585,9 @@ async function runSessionStart(provider, flags, deps2) {
|
|
|
22584
22585
|
// outlives the CLI holding a dead credential.
|
|
22585
22586
|
bearer: deps2.resolveTarget().token,
|
|
22586
22587
|
// …and the config file itself, so the host FOLLOWS later rotations
|
|
22587
|
-
// instead of dying with a revoked snapshot (2026-08-08 finding)
|
|
22588
|
-
|
|
22588
|
+
// instead of dying with a revoked snapshot (2026-08-08 finding) —
|
|
22589
|
+
// but ONLY when the token IS the config's (hostConfigPathOf).
|
|
22590
|
+
configPath: hostConfigPathOf(deps2),
|
|
22589
22591
|
repoRoot: inspection.root,
|
|
22590
22592
|
installationId,
|
|
22591
22593
|
mode: "launch",
|
|
@@ -22663,6 +22665,23 @@ function hooksDir(deps2, provider) {
|
|
|
22663
22665
|
function codexHooksDir(deps2) {
|
|
22664
22666
|
return hooksDir(deps2, "codex");
|
|
22665
22667
|
}
|
|
22668
|
+
function readCodexRolloutPath(deps2, sessionId) {
|
|
22669
|
+
const configuredHome = deps2.env.CODEX_HOME?.trim() || null;
|
|
22670
|
+
const userHome = deps2.env.HOME ?? deps2.env.USERPROFILE;
|
|
22671
|
+
const home = configuredHome ?? (userHome ? join2(userHome, ".codex") : null);
|
|
22672
|
+
if (!home) return null;
|
|
22673
|
+
const root = join2(home, "sessions");
|
|
22674
|
+
const suffix = `-${sessionId}.jsonl`;
|
|
22675
|
+
try {
|
|
22676
|
+
const relative = readdirSync(root, {
|
|
22677
|
+
encoding: "utf8",
|
|
22678
|
+
recursive: true
|
|
22679
|
+
}).find((path) => path.endsWith(suffix));
|
|
22680
|
+
return relative ? join2(root, relative) : null;
|
|
22681
|
+
} catch {
|
|
22682
|
+
return null;
|
|
22683
|
+
}
|
|
22684
|
+
}
|
|
22666
22685
|
function readClaudeHookContext(deps2, readFile = (path) => readFileSync2(path, "utf8"), mtimeOf = transcriptMtime, now = Date.now) {
|
|
22667
22686
|
const own = deps2.env[CLAUDE_SESSION_ENV]?.trim() || null;
|
|
22668
22687
|
const home = deps2.env.HOME ?? deps2.env.USERPROFILE;
|
|
@@ -22744,11 +22763,12 @@ function readCodexHookContext(deps2, readFile = (path) => readFileSync2(path, "u
|
|
|
22744
22763
|
const { starts, endedAt } = ledger;
|
|
22745
22764
|
if (own) {
|
|
22746
22765
|
const source = threadId ? CODEX_THREAD_ENV : CODEX_SESSION_ENV;
|
|
22747
|
-
const
|
|
22766
|
+
const hookTranscript = transcriptForSessionIn(ledger, own);
|
|
22767
|
+
const transcriptPath = hookTranscript ?? readCodexRolloutPath(deps2, own);
|
|
22748
22768
|
return {
|
|
22749
22769
|
sessionId: own,
|
|
22750
22770
|
transcriptPath,
|
|
22751
|
-
basis: `${source}=${own} \u2014 this Codex task's own id${
|
|
22771
|
+
basis: `${source}=${own} \u2014 this Codex task's own id${hookTranscript ? " (rollout from its hook record)" : transcriptPath ? " (rollout resolved from Codex sessions)" : ""}`,
|
|
22752
22772
|
newerElsewhere: null
|
|
22753
22773
|
};
|
|
22754
22774
|
}
|
|
@@ -22771,7 +22791,7 @@ function readCodexHookContext(deps2, readFile = (path) => readFileSync2(path, "u
|
|
|
22771
22791
|
const match = matches[0];
|
|
22772
22792
|
return match ? {
|
|
22773
22793
|
sessionId: match.sessionId,
|
|
22774
|
-
transcriptPath: transcriptForSessionIn(ledger, match.sessionId),
|
|
22794
|
+
transcriptPath: transcriptForSessionIn(ledger, match.sessionId) ?? readCodexRolloutPath(deps2, match.sessionId),
|
|
22775
22795
|
basis: `unambiguous SessionStart${match.at ? ` at ${match.at}` : ""} in ${match.cwd}`,
|
|
22776
22796
|
newerElsewhere: null
|
|
22777
22797
|
} : null;
|
|
@@ -22861,6 +22881,8 @@ async function runSessionAttach(flags, deps2) {
|
|
|
22861
22881
|
}
|
|
22862
22882
|
} else if (flags.provider === "claude" && flags.providerSession && !flags.transcriptPath) {
|
|
22863
22883
|
flags.transcriptPath = readClaudeHookTranscript(deps2, flags.providerSession) ?? void 0;
|
|
22884
|
+
} else if (flags.provider === "codex" && flags.providerSession && !flags.transcriptPath) {
|
|
22885
|
+
flags.transcriptPath = readCodexRolloutPath(deps2, flags.providerSession) ?? void 0;
|
|
22864
22886
|
}
|
|
22865
22887
|
if (flags.provider === "codex") hookDir = codexHooksDir(deps2);
|
|
22866
22888
|
if (!flags.providerSession) {
|
|
@@ -22924,8 +22946,9 @@ async function runSessionAttach(flags, deps2) {
|
|
|
22924
22946
|
// outlives the CLI holding a dead credential.
|
|
22925
22947
|
bearer: deps2.resolveTarget().token,
|
|
22926
22948
|
// …and the config file itself, so the host FOLLOWS later rotations
|
|
22927
|
-
// instead of dying with a revoked snapshot (2026-08-08 finding)
|
|
22928
|
-
|
|
22949
|
+
// instead of dying with a revoked snapshot (2026-08-08 finding) —
|
|
22950
|
+
// but ONLY when the token IS the config's (hostConfigPathOf).
|
|
22951
|
+
configPath: hostConfigPathOf(deps2),
|
|
22929
22952
|
repoRoot: inspection.root,
|
|
22930
22953
|
installationId,
|
|
22931
22954
|
mode: "watch",
|
|
@@ -23006,6 +23029,8 @@ async function runSessionStatus(sessionId, flags, deps2) {
|
|
|
23006
23029
|
`Project: ${String(session.projectName)} (${String(session.projectId)})`,
|
|
23007
23030
|
`Repository: ${String(session.repoOwnerName)}`,
|
|
23008
23031
|
`Capture: ${capture}`,
|
|
23032
|
+
// Evidence floor (D3): the skeleton mode the alignment declared.
|
|
23033
|
+
`Skeleton: ${session.alignment?.skeleton === "off" ? "off (--no-skeleton at align)" : "on (content-free activity counts/timing)"}`,
|
|
23009
23034
|
...local,
|
|
23010
23035
|
telemetry.line,
|
|
23011
23036
|
`Summary artifact: ${session.summaryArtifactId ? String(session.summaryArtifactId) : "\u2014"}`
|
|
@@ -23102,6 +23127,87 @@ function telemetryVerdict(sessionId, usage, hostRan, provider = "claude") {
|
|
|
23102
23127
|
warning: `NO TOKEN TELEMETRY: no local session host ever ran for ${sessionId}, so nothing could collect provider usage receipts. Align from inside the provider session (the plugin's hooks record the transcript), or pass --transcript-path.`
|
|
23103
23128
|
};
|
|
23104
23129
|
}
|
|
23130
|
+
function hostConfigPathOf(deps2) {
|
|
23131
|
+
const source = deps2.resolveTarget().tokenSource;
|
|
23132
|
+
return source === void 0 || source === "file" ? deps2.configPath : null;
|
|
23133
|
+
}
|
|
23134
|
+
var MAX_ATTESTED_DIFF_BYTES = 256 * 1024;
|
|
23135
|
+
async function buildAttestedDiffBody(git, root, startHead, endHead, dirty) {
|
|
23136
|
+
const counted = await git(
|
|
23137
|
+
["rev-list", "--count", `${startHead}..${endHead}`],
|
|
23138
|
+
root
|
|
23139
|
+
);
|
|
23140
|
+
if (counted.code !== 0) return null;
|
|
23141
|
+
const commitCount = Number(counted.stdout.trim());
|
|
23142
|
+
if (!Number.isFinite(commitCount) || commitCount === 0) return null;
|
|
23143
|
+
const header = [
|
|
23144
|
+
"Attested delivery \u2014 generated by `jentrix session end` from the aligned checkout (source: cli).",
|
|
23145
|
+
`Range: ${startHead}..${endHead} \xB7 ${commitCount} commit(s)`,
|
|
23146
|
+
""
|
|
23147
|
+
].join("\n");
|
|
23148
|
+
const cap = MAX_ATTESTED_DIFF_BYTES - Buffer.byteLength(header, "utf8");
|
|
23149
|
+
const full = await git(
|
|
23150
|
+
["log", "--patch", "--stat", `${startHead}..${endHead}`],
|
|
23151
|
+
root
|
|
23152
|
+
);
|
|
23153
|
+
let body;
|
|
23154
|
+
if (full.code === 0 && Buffer.byteLength(full.stdout, "utf8") <= cap) {
|
|
23155
|
+
body = full.stdout;
|
|
23156
|
+
} else {
|
|
23157
|
+
const stat = await git(["log", "--stat", `${startHead}..${endHead}`], root);
|
|
23158
|
+
const notice = "[truncated stat-first: the full --patch output exceeded the 256 KB attested-diff cap \u2014 commit list + per-file stat retained; the full patch lives in git]\n\n";
|
|
23159
|
+
let kept = stat.code === 0 ? stat.stdout : "(git log --stat failed)";
|
|
23160
|
+
const room = cap - Buffer.byteLength(notice, "utf8");
|
|
23161
|
+
if (Buffer.byteLength(kept, "utf8") > room) {
|
|
23162
|
+
kept = Buffer.from(kept, "utf8").subarray(0, Math.max(0, room - 64)).toString("utf8").replace(/�+$/, "") + "\n[stat tail truncated at the cap]";
|
|
23163
|
+
}
|
|
23164
|
+
body = notice + kept;
|
|
23165
|
+
}
|
|
23166
|
+
if (dirty) {
|
|
23167
|
+
const delta = await git(["diff", "--stat", "HEAD"], root);
|
|
23168
|
+
body += `
|
|
23169
|
+
|
|
23170
|
+
Uncommitted delta at end (stat only \u2014 not part of the attested range):
|
|
23171
|
+
${delta.code === 0 && delta.stdout.trim() ? delta.stdout : "(unreadable or empty)"}`;
|
|
23172
|
+
}
|
|
23173
|
+
return { body: header + body, commitCount };
|
|
23174
|
+
}
|
|
23175
|
+
async function postAttestedArtifact(deps2, sessionId, input) {
|
|
23176
|
+
const credentials = deps2.resolveTarget();
|
|
23177
|
+
try {
|
|
23178
|
+
const response = await (deps2.fetchImpl ?? fetch)(
|
|
23179
|
+
new URL(
|
|
23180
|
+
`/api/agent-sessions/${sessionId}/artifacts`,
|
|
23181
|
+
stacksBaseUrlOf(credentials.url)
|
|
23182
|
+
),
|
|
23183
|
+
{
|
|
23184
|
+
method: "POST",
|
|
23185
|
+
headers: {
|
|
23186
|
+
authorization: `Bearer ${credentials.token}`,
|
|
23187
|
+
"content-type": "application/json"
|
|
23188
|
+
},
|
|
23189
|
+
body: JSON.stringify({
|
|
23190
|
+
kind: input.kind,
|
|
23191
|
+
title: input.title,
|
|
23192
|
+
body: redactLocal(input.body, deps2.env),
|
|
23193
|
+
attested: true
|
|
23194
|
+
})
|
|
23195
|
+
}
|
|
23196
|
+
);
|
|
23197
|
+
const payload = await response.json().catch(() => ({}));
|
|
23198
|
+
return response.ok ? { ok: true, artifactId: payload.artifactId ?? null, detail: null } : {
|
|
23199
|
+
ok: false,
|
|
23200
|
+
artifactId: null,
|
|
23201
|
+
detail: payload.detail ?? payload.error ?? `HTTP ${response.status}`
|
|
23202
|
+
};
|
|
23203
|
+
} catch (error2) {
|
|
23204
|
+
return {
|
|
23205
|
+
ok: false,
|
|
23206
|
+
artifactId: null,
|
|
23207
|
+
detail: error2 instanceof Error ? error2.message : "network failure"
|
|
23208
|
+
};
|
|
23209
|
+
}
|
|
23210
|
+
}
|
|
23105
23211
|
async function runSessionEnd(sessionIdFlag, flags, deps2) {
|
|
23106
23212
|
try {
|
|
23107
23213
|
const inspection = await inspectRepository(deps2.cwd(), deps2.git);
|
|
@@ -23168,6 +23274,39 @@ async function runSessionEnd(sessionIdFlag, flags, deps2) {
|
|
|
23168
23274
|
);
|
|
23169
23275
|
}
|
|
23170
23276
|
const sessionId = resolved;
|
|
23277
|
+
let commitCount = null;
|
|
23278
|
+
try {
|
|
23279
|
+
const row = await call(caller, "get_agent_session", { sessionId });
|
|
23280
|
+
const startHead = typeof row.startHead === "string" ? row.startHead : null;
|
|
23281
|
+
const attestRepo = inspection && inspection.repoOwnerName === row.repoOwnerName ? inspection : null;
|
|
23282
|
+
if (attestRepo?.head && startHead && attestRepo.head !== startHead && (row.status === "ACTIVE" || row.status === "STARTING")) {
|
|
23283
|
+
const built = await buildAttestedDiffBody(
|
|
23284
|
+
deps2.git ?? defaultGitRunner,
|
|
23285
|
+
attestRepo.root,
|
|
23286
|
+
startHead,
|
|
23287
|
+
attestRepo.head,
|
|
23288
|
+
attestRepo.dirty
|
|
23289
|
+
);
|
|
23290
|
+
if (built) {
|
|
23291
|
+
commitCount = built.commitCount;
|
|
23292
|
+
const pushed = await postAttestedArtifact(deps2, sessionId, {
|
|
23293
|
+
kind: "diff",
|
|
23294
|
+
title: `Attested diff ${startHead.slice(0, 10)}..${attestRepo.head.slice(0, 10)} (${built.commitCount} commit${built.commitCount === 1 ? "" : "s"})`,
|
|
23295
|
+
body: built.body
|
|
23296
|
+
});
|
|
23297
|
+
if (pushed.ok) {
|
|
23298
|
+
deps2.writeOut(
|
|
23299
|
+
`Attested diff pushed \u2192 artifact ${pushed.artifactId ?? "(unknown)"} (${built.commitCount} commit(s), source: cli)`
|
|
23300
|
+
);
|
|
23301
|
+
} else {
|
|
23302
|
+
deps2.writeErr(
|
|
23303
|
+
`note: attested diff push failed (${pushed.detail}) \u2014 the E1 delivery check will refuse the close; retry \`jentrix session end\`, or acknowledge with --acknowledge-evidence-gaps.`
|
|
23304
|
+
);
|
|
23305
|
+
}
|
|
23306
|
+
}
|
|
23307
|
+
}
|
|
23308
|
+
} catch {
|
|
23309
|
+
}
|
|
23171
23310
|
const hostRan = hasLocalCaptureFootprint(deps2, sessionId);
|
|
23172
23311
|
const reportTelemetry = (row) => {
|
|
23173
23312
|
const verdict2 = telemetryVerdict(
|
|
@@ -23253,6 +23392,11 @@ async function runSessionEnd(sessionIdFlag, flags, deps2) {
|
|
|
23253
23392
|
endHead: sameRepo?.head ?? null,
|
|
23254
23393
|
endDirty: sameRepo?.dirty ?? null,
|
|
23255
23394
|
...spooledUsage ? { usage: spooledUsage } : {},
|
|
23395
|
+
// Evidence floor (§5/§6): the CLI-counted commits in the range feed
|
|
23396
|
+
// E2 and the summary's density line; the acknowledge flag closes over
|
|
23397
|
+
// unmet checks with each stamped MISSING into Review readiness.
|
|
23398
|
+
...commitCount !== null ? { commitCount } : {},
|
|
23399
|
+
...flags.acknowledgeEvidenceGaps ? { acknowledgeEvidenceGaps: true } : {},
|
|
23256
23400
|
expectedUpdatedAt: session.updatedAt
|
|
23257
23401
|
});
|
|
23258
23402
|
if (inspection) {
|
|
@@ -23586,6 +23730,12 @@ function reportError(error2, deps2) {
|
|
|
23586
23730
|
}
|
|
23587
23731
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
23588
23732
|
deps2.writeErr(`error: ${message}`);
|
|
23733
|
+
if (message.includes("EVIDENCE_FLOOR")) {
|
|
23734
|
+
deps2.writeErr(
|
|
23735
|
+
"To close anyway with the unmet checks stamped MISSING: `jentrix session end --acknowledge-evidence-gaps` (a declared deviation is an ordinary `jentrix push gap`)."
|
|
23736
|
+
);
|
|
23737
|
+
return EXIT_CODES.CONFLICT;
|
|
23738
|
+
}
|
|
23589
23739
|
if (message.includes("SESSION_ALREADY_BOUND")) return EXIT_CODES.CONFLICT;
|
|
23590
23740
|
if (message.includes("FORBIDDEN") || message.includes("PROJECT_DISCOVERY_REQUIRES_UNPINNED_LOGIN")) {
|
|
23591
23741
|
return EXIT_CODES.FORBIDDEN;
|
|
@@ -23641,7 +23791,12 @@ function registerSessionCommand(program3, deps2, onExit2) {
|
|
|
23641
23791
|
).option("--json", "stable JSON output").action(
|
|
23642
23792
|
async (sessionId, flags) => onExit2(await runSessionStatus(sessionId, flags, deps2))
|
|
23643
23793
|
);
|
|
23644
|
-
session.command("end [sessionId]").description(
|
|
23794
|
+
session.command("end [sessionId]").description(
|
|
23795
|
+
"Close a session, verify capture, and store the RUN_SUMMARY. Pushes the attested delivery patch when HEAD moved, and enforces the evidence-floor checks E1\u2013E4 (refusal names each unmet check and its fix)."
|
|
23796
|
+
).option("--json", "stable JSON output").option(
|
|
23797
|
+
"--acknowledge-evidence-gaps",
|
|
23798
|
+
"close even with unmet evidence-floor checks \u2014 each unmet check is stamped MISSING into the summary's Review readiness"
|
|
23799
|
+
).action(
|
|
23645
23800
|
async (sessionId, flags) => onExit2(await runSessionEnd(sessionId, flags, deps2))
|
|
23646
23801
|
);
|
|
23647
23802
|
return session;
|
|
@@ -23890,10 +24045,11 @@ function sessionBindingCheck(detected) {
|
|
|
23890
24045
|
function detectProvider(flags, deps2) {
|
|
23891
24046
|
if (flags.provider === "codex") {
|
|
23892
24047
|
const hook2 = flags.providerSession ? null : readCodexHookContext(deps2);
|
|
24048
|
+
const providerSessionId = flags.providerSession ?? hook2?.sessionId ?? null;
|
|
23893
24049
|
return {
|
|
23894
24050
|
provider: "codex",
|
|
23895
|
-
providerSessionId
|
|
23896
|
-
transcriptPath: flags.transcriptPath ?? hook2?.transcriptPath ?? null,
|
|
24051
|
+
providerSessionId,
|
|
24052
|
+
transcriptPath: flags.transcriptPath ?? hook2?.transcriptPath ?? (providerSessionId ? readCodexRolloutPath(deps2, providerSessionId) : null),
|
|
23897
24053
|
basis: flags.providerSession ? `codex thread ${flags.providerSession} (explicit)` : hook2?.basis ?? null,
|
|
23898
24054
|
newerElsewhere: null,
|
|
23899
24055
|
transcriptGap: null,
|
|
@@ -24276,9 +24432,13 @@ ${JSON.stringify(session.alignment, null, 2)}`
|
|
|
24276
24432
|
} : {},
|
|
24277
24433
|
...budget !== null ? { tokenBudget: budget } : {},
|
|
24278
24434
|
capture: captureMode,
|
|
24435
|
+
// Evidence floor (D3): only an explicit --no-skeleton is sent; absent
|
|
24436
|
+
// lets the server keep the stored mode (default "on").
|
|
24437
|
+
...flags.skeleton === false ? { skeleton: "off" } : {},
|
|
24279
24438
|
expectedUpdatedAt: session.updatedAt
|
|
24280
24439
|
});
|
|
24281
24440
|
const alignment = aligned.alignment;
|
|
24441
|
+
const skeletonMode = alignment.skeleton === "off" ? "off" : "on";
|
|
24282
24442
|
writeAlignmentMarker(
|
|
24283
24443
|
deps2.configPath,
|
|
24284
24444
|
inspection.root,
|
|
@@ -24326,8 +24486,9 @@ ${JSON.stringify(session.alignment, null, 2)}`
|
|
|
24326
24486
|
mcpUrl: target.url,
|
|
24327
24487
|
bearer: deps2.resolveTarget().token,
|
|
24328
24488
|
// …and the config file itself, so the host FOLLOWS later rotations
|
|
24329
|
-
// instead of dying with a revoked snapshot (2026-08-08 finding)
|
|
24330
|
-
|
|
24489
|
+
// instead of dying with a revoked snapshot (2026-08-08 finding) —
|
|
24490
|
+
// but ONLY when the token IS the config's (hostConfigPathOf).
|
|
24491
|
+
configPath: hostConfigPathOf(deps2),
|
|
24331
24492
|
repoRoot: inspection.root,
|
|
24332
24493
|
installationId,
|
|
24333
24494
|
mode: "watch",
|
|
@@ -24335,6 +24496,7 @@ ${JSON.stringify(session.alignment, null, 2)}`
|
|
|
24335
24496
|
...detected.transcriptPath ? { transcriptPath: detected.transcriptPath } : {},
|
|
24336
24497
|
...detected.hookDir ? { hookDir: detected.hookDir } : {},
|
|
24337
24498
|
captureTrace: captureMode === "on",
|
|
24499
|
+
collectSkeleton: skeletonMode === "on",
|
|
24338
24500
|
spoolRoot: deps2.spoolRoot
|
|
24339
24501
|
});
|
|
24340
24502
|
if (pid !== null) {
|
|
@@ -24809,6 +24971,7 @@ async function runAlign(flags, deps2) {
|
|
|
24809
24971
|
` Agent: ${a.agent?.provider ?? "\u2014"}`,
|
|
24810
24972
|
` Repo: ${a.repo?.ownerName ?? "\u2014"}@${a.repo?.branch ?? "detached"}`,
|
|
24811
24973
|
` Capture: ${a.capture ?? "off"}`,
|
|
24974
|
+
` Skeleton: ${a.skeleton ?? "on"} (content-free activity counts/timing; --no-skeleton opts out)`,
|
|
24812
24975
|
`Alignment snapshot (server-confirmed): ${JSON.stringify(result.alignment)}`
|
|
24813
24976
|
].join("\n")
|
|
24814
24977
|
);
|
|
@@ -24873,6 +25036,9 @@ function registerAlignCommand(program3, deps2, onExit2) {
|
|
|
24873
25036
|
).option(
|
|
24874
25037
|
"--no-capture",
|
|
24875
25038
|
"keep TRACE capture OFF explicitly \u2014 also corrects a wrongly-on record on re-align"
|
|
25039
|
+
).option(
|
|
25040
|
+
"--no-skeleton",
|
|
25041
|
+
"opt out of the content-free activity skeleton (event/tool counts, files-touched, timing buckets) for this session \u2014 default on, independent of capture"
|
|
24876
25042
|
).addOption(
|
|
24877
25043
|
new Option(
|
|
24878
25044
|
"--provider <provider>",
|
|
@@ -24910,7 +25076,8 @@ var PUSH_KINDS = [
|
|
|
24910
25076
|
"prd",
|
|
24911
25077
|
"gap",
|
|
24912
25078
|
"issue",
|
|
24913
|
-
"context"
|
|
25079
|
+
"context",
|
|
25080
|
+
"log"
|
|
24914
25081
|
];
|
|
24915
25082
|
var ARTIFACT_TYPE_BY_PUSH_KIND = {
|
|
24916
25083
|
plan: "PLAN",
|
|
@@ -24925,8 +25092,23 @@ var ARTIFACT_TYPE_BY_PUSH_KIND = {
|
|
|
24925
25092
|
prd: "PRD",
|
|
24926
25093
|
gap: "GAP",
|
|
24927
25094
|
issue: "ISSUE",
|
|
24928
|
-
context: "SOURCE_DIGEST"
|
|
25095
|
+
context: "SOURCE_DIGEST",
|
|
25096
|
+
log: "LOG"
|
|
24929
25097
|
};
|
|
25098
|
+
var MAX_FROM_CMD_OUTPUT_BYTES = 64 * 1024;
|
|
25099
|
+
function fromCmdLogBody(command, exitCode, output) {
|
|
25100
|
+
const header = `$ ${command}
|
|
25101
|
+
exit code: ${exitCode}
|
|
25102
|
+
|
|
25103
|
+
`;
|
|
25104
|
+
if (Buffer.byteLength(output, "utf8") <= MAX_FROM_CMD_OUTPUT_BYTES) {
|
|
25105
|
+
return header + (output || "(no output)");
|
|
25106
|
+
}
|
|
25107
|
+
const notice = `[head truncated: output exceeded ${MAX_FROM_CMD_OUTPUT_BYTES / 1024} KB \u2014 tail retained]
|
|
25108
|
+
`;
|
|
25109
|
+
const tail = Buffer.from(output, "utf8").subarray(-MAX_FROM_CMD_OUTPUT_BYTES).toString("utf8").replace(/^�+/, "");
|
|
25110
|
+
return header + notice + tail;
|
|
25111
|
+
}
|
|
24930
25112
|
function sessionTarget(sessionId) {
|
|
24931
25113
|
return {
|
|
24932
25114
|
path: `/api/agent-sessions/${sessionId}/artifacts`,
|
|
@@ -24936,6 +25118,32 @@ function sessionTarget(sessionId) {
|
|
|
24936
25118
|
function taskTarget(taskId) {
|
|
24937
25119
|
return { path: `/api/tasks/${taskId}/artifacts`, label: `task ${taskId}` };
|
|
24938
25120
|
}
|
|
25121
|
+
function defaultRunCommand(command) {
|
|
25122
|
+
return new Promise((resolve5) => {
|
|
25123
|
+
const child = spawn2(command, { shell: true, windowsHide: true });
|
|
25124
|
+
let tail = Buffer.alloc(0);
|
|
25125
|
+
const keep = (chunk) => {
|
|
25126
|
+
tail = Buffer.concat([tail, chunk]);
|
|
25127
|
+
if (tail.byteLength > MAX_FROM_CMD_OUTPUT_BYTES * 2) {
|
|
25128
|
+
tail = tail.subarray(-MAX_FROM_CMD_OUTPUT_BYTES);
|
|
25129
|
+
}
|
|
25130
|
+
};
|
|
25131
|
+
child.stdout?.on("data", keep);
|
|
25132
|
+
child.stderr?.on("data", keep);
|
|
25133
|
+
child.once(
|
|
25134
|
+
"error",
|
|
25135
|
+
(error2) => resolve5({ code: 127, output: `${error2.message}
|
|
25136
|
+
` })
|
|
25137
|
+
);
|
|
25138
|
+
child.once(
|
|
25139
|
+
"exit",
|
|
25140
|
+
(code, signal) => resolve5({
|
|
25141
|
+
code: code ?? (signal ? 130 : 1),
|
|
25142
|
+
output: tail.toString("utf8")
|
|
25143
|
+
})
|
|
25144
|
+
);
|
|
25145
|
+
});
|
|
25146
|
+
}
|
|
24939
25147
|
async function drainStdin() {
|
|
24940
25148
|
const chunks = [];
|
|
24941
25149
|
for await (const chunk of process.stdin) {
|
|
@@ -24955,6 +25163,21 @@ async function runPush(kind, file, flags, deps2) {
|
|
|
24955
25163
|
"--basis records what a decision rested on \u2014 it only applies to `jentrix push decision`"
|
|
24956
25164
|
);
|
|
24957
25165
|
}
|
|
25166
|
+
if (flags.fromCmd !== void 0) {
|
|
25167
|
+
if (kind !== "log") {
|
|
25168
|
+
throw new UsageError(
|
|
25169
|
+
'--from-cmd captures a command\'s exit code and output as a LOG \u2014 it only applies to `jentrix push log --from-cmd "<command>"`'
|
|
25170
|
+
);
|
|
25171
|
+
}
|
|
25172
|
+
if (file || flags.ref) {
|
|
25173
|
+
throw new UsageError(
|
|
25174
|
+
"--from-cmd generates the LOG body from the command run \u2014 drop the file/--ref"
|
|
25175
|
+
);
|
|
25176
|
+
}
|
|
25177
|
+
if (!flags.fromCmd.trim()) {
|
|
25178
|
+
throw new UsageError("--from-cmd needs a non-empty command");
|
|
25179
|
+
}
|
|
25180
|
+
}
|
|
24958
25181
|
if (flags.ref) {
|
|
24959
25182
|
if (flags.basis?.length) {
|
|
24960
25183
|
throw new UsageError(
|
|
@@ -25007,7 +25230,17 @@ async function runPush(kind, file, flags, deps2) {
|
|
|
25007
25230
|
correlatedSessionId = marker.sessionId;
|
|
25008
25231
|
fallbackTaskId = marker.taskId;
|
|
25009
25232
|
}
|
|
25010
|
-
|
|
25233
|
+
let commandExit = null;
|
|
25234
|
+
let raw;
|
|
25235
|
+
if (flags.fromCmd !== void 0) {
|
|
25236
|
+
const command = flags.fromCmd.trim();
|
|
25237
|
+
deps2.writeErr(`running: ${command}`);
|
|
25238
|
+
const ran = await (deps2.runCommand ?? defaultRunCommand)(command);
|
|
25239
|
+
commandExit = ran.code;
|
|
25240
|
+
raw = fromCmdLogBody(command, ran.code, ran.output);
|
|
25241
|
+
} else {
|
|
25242
|
+
raw = file ? readFileSync4(file, "utf8") : await (deps2.readStdin ?? drainStdin)();
|
|
25243
|
+
}
|
|
25011
25244
|
if (!raw.trim()) {
|
|
25012
25245
|
throw new UsageError(
|
|
25013
25246
|
file ? `${file} is empty` : "stdin is empty \u2014 pipe the content or name a file"
|
|
@@ -25031,6 +25264,9 @@ ${raw}` : raw;
|
|
|
25031
25264
|
...flags.title ? { title: flags.title } : {},
|
|
25032
25265
|
...flags.agent ? { producerLabel: flags.agent } : {},
|
|
25033
25266
|
...flags.agentEmoji ? { producerEmoji: flags.agentEmoji } : {},
|
|
25267
|
+
// D4: only the CLI-generated capture is attested — never a piped
|
|
25268
|
+
// body, whatever its kind.
|
|
25269
|
+
...commandExit !== null ? { attested: true } : {},
|
|
25034
25270
|
body
|
|
25035
25271
|
})
|
|
25036
25272
|
}
|
|
@@ -25086,7 +25322,12 @@ ${raw}` : raw;
|
|
|
25086
25322
|
if (isMintableKind(kind) && payload.taskId) {
|
|
25087
25323
|
await offerCardMint(kind, payload, flags, deps2, correlatedSessionId);
|
|
25088
25324
|
}
|
|
25089
|
-
|
|
25325
|
+
if (commandExit !== null && commandExit !== 0) {
|
|
25326
|
+
deps2.writeErr(
|
|
25327
|
+
`command exited ${commandExit} \u2014 recorded in the LOG; exiting with the command's code`
|
|
25328
|
+
);
|
|
25329
|
+
}
|
|
25330
|
+
return commandExit ?? 0;
|
|
25090
25331
|
} catch (error2) {
|
|
25091
25332
|
if (error2 instanceof UsageError) {
|
|
25092
25333
|
deps2.writeErr(`error: ${error2.message}`);
|
|
@@ -25400,6 +25641,9 @@ function registerPushCommand(program3, deps2, onExit2) {
|
|
|
25400
25641
|
).option(
|
|
25401
25642
|
"--blocks",
|
|
25402
25643
|
"with --yes on a findings/gap/issue push: the minted card BLOCKS the aligned task (acceptance-blocking) instead of RELATES_TO"
|
|
25644
|
+
).option(
|
|
25645
|
+
"--from-cmd <command>",
|
|
25646
|
+
`log pushes only: run the command locally, capture exit code + a 64 KB tail-biased output tail, and push an ATTESTED LOG opening with both \u2014 then exit with the command's own code ("tests green" claims carry evidence)`
|
|
25403
25647
|
).option("--json", "stable JSON output").action(
|
|
25404
25648
|
async (kind, file, flags) => onExit2(await runPush(kind, file, flags, deps2))
|
|
25405
25649
|
);
|
|
@@ -27754,7 +27998,7 @@ function registerPluginCommand(program3, deps2, onExit2) {
|
|
|
27754
27998
|
}
|
|
27755
27999
|
|
|
27756
28000
|
// src/commands/runner.ts
|
|
27757
|
-
import { execFile as execFile2, spawn as
|
|
28001
|
+
import { execFile as execFile2, spawn as spawn3 } from "node:child_process";
|
|
27758
28002
|
import { constants, promises as fs } from "node:fs";
|
|
27759
28003
|
import { homedir } from "node:os";
|
|
27760
28004
|
import { delimiter, isAbsolute, resolve as resolve3 } from "node:path";
|
|
@@ -28188,7 +28432,7 @@ function runRunnerForeground(file, args) {
|
|
|
28188
28432
|
}
|
|
28189
28433
|
const target = execTarget(file, args);
|
|
28190
28434
|
return new Promise((resolveRun, reject) => {
|
|
28191
|
-
const child =
|
|
28435
|
+
const child = spawn3(target.file, target.args, {
|
|
28192
28436
|
stdio: "inherit",
|
|
28193
28437
|
...target.options
|
|
28194
28438
|
});
|
|
@@ -29202,7 +29446,11 @@ var sessionDeps = {
|
|
|
29202
29446
|
configPath: CONFIG_PATH,
|
|
29203
29447
|
resolveTarget: () => {
|
|
29204
29448
|
const target = resolveConfig({ env: process.env, file: configFile() });
|
|
29205
|
-
return {
|
|
29449
|
+
return {
|
|
29450
|
+
token: target.token,
|
|
29451
|
+
url: target.url,
|
|
29452
|
+
tokenSource: target.tokenSource
|
|
29453
|
+
};
|
|
29206
29454
|
},
|
|
29207
29455
|
ensureInstallationId: () => ensureInstallationId(HOME_CONFIG_PATH),
|
|
29208
29456
|
connect: async (target) => {
|
|
@@ -29225,7 +29473,7 @@ var sessionDeps = {
|
|
|
29225
29473
|
planPath
|
|
29226
29474
|
]);
|
|
29227
29475
|
try {
|
|
29228
|
-
const child =
|
|
29476
|
+
const child = spawn4(target.file, target.args, {
|
|
29229
29477
|
detached: true,
|
|
29230
29478
|
stdio: ["ignore", fd, fd],
|
|
29231
29479
|
...target.options
|
package/package.json
CHANGED
package/surface.json
CHANGED
|
@@ -463,6 +463,14 @@
|
|
|
463
463
|
"minLength": 1,
|
|
464
464
|
"type": "string"
|
|
465
465
|
},
|
|
466
|
+
"skeleton": {
|
|
467
|
+
"description": "Activity-skeleton mode the CLI declared for this session (--no-skeleton). Default on, independent of capture.",
|
|
468
|
+
"enum": [
|
|
469
|
+
"on",
|
|
470
|
+
"off"
|
|
471
|
+
],
|
|
472
|
+
"type": "string"
|
|
473
|
+
},
|
|
466
474
|
"taskId": {
|
|
467
475
|
"anyOf": [
|
|
468
476
|
{
|
|
@@ -1655,6 +1663,10 @@
|
|
|
1655
1663
|
"description": "Close a session with an honest lifecycle outcome (COMPLETED normal end / INTERRUPTED crash / CANCELLED pre-first-turn abort). The server verifies the trace-part manifest against durable artifacts, stores the deterministic RUN_SUMMARY, and sets captureComplete ONLY when every expected part and the summary are acknowledged — lifecycle and capture integrity stay orthogonal. Requires expectedUpdatedAt (CAS).",
|
|
1656
1664
|
"inputSchema": {
|
|
1657
1665
|
"properties": {
|
|
1666
|
+
"acknowledgeEvidenceGaps": {
|
|
1667
|
+
"description": "Close even with unmet evidence-floor checks (E1–E3); each unmet check is stamped MISSING into the summary's Review readiness.",
|
|
1668
|
+
"type": "boolean"
|
|
1669
|
+
},
|
|
1658
1670
|
"captureError": {
|
|
1659
1671
|
"anyOf": [
|
|
1660
1672
|
{
|
|
@@ -1666,6 +1678,19 @@
|
|
|
1666
1678
|
}
|
|
1667
1679
|
]
|
|
1668
1680
|
},
|
|
1681
|
+
"commitCount": {
|
|
1682
|
+
"anyOf": [
|
|
1683
|
+
{
|
|
1684
|
+
"maximum": 9007199254740991,
|
|
1685
|
+
"minimum": 0,
|
|
1686
|
+
"type": "integer"
|
|
1687
|
+
},
|
|
1688
|
+
{
|
|
1689
|
+
"type": "null"
|
|
1690
|
+
}
|
|
1691
|
+
],
|
|
1692
|
+
"description": "CLI-counted commits in startHead..endHead; absent = not counted (HEAD movement is then the only signal)."
|
|
1693
|
+
},
|
|
1669
1694
|
"endBranch": {
|
|
1670
1695
|
"anyOf": [
|
|
1671
1696
|
{
|