@gajae-code/coding-agent 0.2.0 → 0.2.2
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/CHANGELOG.md +38 -1
- package/dist/types/cli/skills-cli.d.ts +9 -0
- package/dist/types/commands/contribution-prep.d.ts +18 -0
- package/dist/types/commands/session.d.ts +24 -0
- package/dist/types/commands/skills.d.ts +26 -0
- package/dist/types/config/model-registry.d.ts +33 -4
- package/dist/types/config/models-config-schema.d.ts +52 -5
- package/dist/types/config/settings-schema.d.ts +1 -24
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +15 -0
- package/dist/types/gjc-runtime/goal-mode-request.d.ts +1 -1
- package/dist/types/gjc-runtime/launch-tmux.d.ts +12 -11
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +25 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +13 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +37 -5
- package/dist/types/gjc-runtime/tmux-common.d.ts +41 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +17 -0
- package/dist/types/goals/runtime.d.ts +3 -9
- package/dist/types/goals/state.d.ts +3 -6
- package/dist/types/goals/tools/goal-tool.d.ts +1 -69
- package/dist/types/modes/components/model-selector.d.ts +21 -1
- package/dist/types/modes/components/status-line/types.d.ts +0 -3
- package/dist/types/modes/components/status-line.d.ts +0 -3
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -12
- package/dist/types/modes/theme/defaults/index.d.ts +0 -2
- package/dist/types/modes/theme/theme.d.ts +1 -2
- package/dist/types/modes/types.d.ts +1 -7
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/session/contribution-prep.d.ts +47 -0
- package/dist/types/skill-state/active-state.d.ts +4 -0
- package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +6 -1
- package/dist/types/skill-state/workflow-hud.d.ts +9 -4
- package/dist/types/skill-state/workflow-state-contract.d.ts +34 -0
- package/dist/types/slash-commands/builtin-registry.d.ts +1 -0
- package/package.json +7 -7
- package/src/cli/args.ts +17 -2
- package/src/cli/skills-cli.ts +88 -0
- package/src/cli.ts +7 -1
- package/src/commands/contribution-prep.ts +41 -0
- package/src/commands/deep-interview.ts +6 -22
- package/src/commands/launch.ts +10 -1
- package/src/commands/ralplan.ts +10 -22
- package/src/commands/session.ts +150 -0
- package/src/commands/skills.ts +48 -0
- package/src/commands/state.ts +14 -4
- package/src/commands/team.ts +23 -3
- package/src/commit/agentic/index.ts +1 -0
- package/src/commit/pipeline.ts +1 -0
- package/src/config/model-registry.ts +269 -10
- package/src/config/models-config-schema.ts +124 -88
- package/src/config/settings-schema.ts +1 -25
- package/src/config.ts +1 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +14 -13
- package/src/defaults/gjc/skills/ralplan/SKILL.md +14 -2
- package/src/defaults/gjc/skills/team/SKILL.md +29 -7
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +23 -25
- package/src/eval/py/prelude.py +1 -1
- package/src/gjc-runtime/deep-interview-runtime.ts +279 -0
- package/src/gjc-runtime/goal-mode-request.ts +2 -19
- package/src/gjc-runtime/launch-tmux.ts +83 -43
- package/src/gjc-runtime/ralplan-runtime.ts +460 -0
- package/src/gjc-runtime/state-runtime.ts +562 -0
- package/src/gjc-runtime/team-runtime.ts +708 -52
- package/src/gjc-runtime/tmux-common.ts +119 -0
- package/src/gjc-runtime/tmux-sessions.ts +165 -0
- package/src/gjc-runtime/ultragoal-guard.ts +6 -3
- package/src/gjc-runtime/ultragoal-runtime.ts +5 -4
- package/src/goals/runtime.ts +38 -144
- package/src/goals/state.ts +36 -7
- package/src/goals/tools/goal-tool.ts +15 -172
- package/src/hooks/skill-state.ts +31 -12
- package/src/internal-urls/docs-index.generated.ts +4 -3
- package/src/main.ts +10 -1
- package/src/modes/components/model-selector.ts +109 -28
- package/src/modes/components/skill-hud/render.ts +4 -0
- package/src/modes/components/status-line/segments.ts +5 -16
- package/src/modes/components/status-line/types.ts +0 -3
- package/src/modes/components/status-line.ts +0 -6
- package/src/modes/controllers/command-controller.ts +25 -1
- package/src/modes/controllers/input-controller.ts +0 -15
- package/src/modes/controllers/selector-controller.ts +42 -2
- package/src/modes/interactive-mode.ts +18 -219
- package/src/modes/theme/defaults/dark-poimandres.json +0 -1
- package/src/modes/theme/defaults/light-poimandres.json +0 -1
- package/src/modes/theme/theme.ts +0 -6
- package/src/modes/types.ts +1 -7
- package/src/prompts/goals/goal-continuation.md +1 -4
- package/src/prompts/goals/goal-mode-active.md +3 -5
- package/src/prompts/system/system-prompt.md +5 -7
- package/src/prompts/tools/goal.md +4 -4
- package/src/sdk.ts +2 -1
- package/src/session/agent-session.ts +18 -0
- package/src/session/contribution-prep.ts +320 -0
- package/src/setup/provider-onboarding.ts +2 -0
- package/src/skill-state/active-state.ts +38 -0
- package/src/skill-state/deep-interview-mutation-guard.ts +88 -24
- package/src/skill-state/workflow-hud.ts +23 -5
- package/src/skill-state/workflow-state-contract.ts +121 -0
- package/src/slash-commands/acp-builtins.ts +11 -2
- package/src/slash-commands/builtin-registry.ts +40 -13
- package/src/task/commands.ts +1 -5
- package/src/tools/gh.ts +212 -2
- package/src/tools/index.ts +2 -5
- package/dist/types/commands/gjc-runtime-bridge.d.ts +0 -30
- package/dist/types/commands/question.d.ts +0 -7
- package/dist/types/modes/loop-limit.d.ts +0 -22
- package/src/commands/gjc-runtime-bridge.ts +0 -227
- package/src/commands/question.ts +0 -12
- package/src/modes/loop-limit.ts +0 -140
- package/src/prompts/commands/orchestrate.md +0 -49
- package/src/prompts/goals/goal-budget-limit.md +0 -16
- package/src/prompts/tools/create-goal.md +0 -3
- package/src/prompts/tools/get-goal.md +0 -3
- package/src/prompts/tools/update-goal.md +0 -3
|
@@ -11,7 +11,7 @@ Use when the user asks for `ultragoal`, `create-goals`, `complete-goals`, durabl
|
|
|
11
11
|
|
|
12
12
|
## Purpose
|
|
13
13
|
|
|
14
|
-
`ultragoal` turns a brief into repo-native artifacts and then drives a GJC goal safely through the
|
|
14
|
+
`ultragoal` turns a brief into repo-native artifacts and then drives a GJC goal safely through the unified `goal` tool. New plans default to a stable pointer-style aggregate GJC goal for the whole durable plan in `.gjc/ultragoal/goals.json`, including later accepted/appended stories under the original brief constraints, while GJC tracks G001/G002 story progress in the ledger. Ultragoal does not call `/goal clear`; before multiple sequential ultragoal runs in one session/thread, manually run `/goal clear` in the UI so the previous completed aggregate goal does not block or confuse the next `goal({"op":"create"})`.
|
|
15
15
|
|
|
16
16
|
- `.gjc/ultragoal/brief.md`
|
|
17
17
|
- `.gjc/ultragoal/goals.json`
|
|
@@ -30,21 +30,20 @@ gjc ultragoal create-goals --brief "<brief>"
|
|
|
30
30
|
gjc ultragoal create-goals --brief-file <path>
|
|
31
31
|
gjc ultragoal complete-goals
|
|
32
32
|
gjc ultragoal complete-goals --retry-failed
|
|
33
|
-
gjc ultragoal checkpoint --goal-id <id> --status complete --evidence "<evidence>" --gjc-goal-json <get-
|
|
33
|
+
gjc ultragoal checkpoint --goal-id <id> --status complete --evidence "<evidence>" --gjc-goal-json <goal-get-json-or-path> --quality-gate-json <quality-gate-json-or-path>
|
|
34
34
|
gjc ultragoal checkpoint --goal-id <id> --status failed --evidence "<blocker/evidence>"
|
|
35
|
-
gjc ultragoal record-review-blockers --goal-id <id> --title "Resolve final review blockers" --objective "<blocker-resolution objective>" --evidence "<review findings>" --gjc-goal-json <active-get-
|
|
35
|
+
gjc ultragoal record-review-blockers --goal-id <id> --title "Resolve final review blockers" --objective "<blocker-resolution objective>" --evidence "<review findings>" --gjc-goal-json <active-goal-get-json-or-path>
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
Use these exact goal-tool calls for the inline goal state:
|
|
39
39
|
|
|
40
40
|
```json
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
goal({"op":"get"})
|
|
42
|
+
goal({"op":"create","objective":"<printed aggregate or per-story objective>"})
|
|
43
|
+
goal({"op":"complete"})
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
The unified `goal` tool shares the same session goal state as `/goal`; use `goal({"op":"get"})` snapshots inside Ultragoal for ledger reconciliation.
|
|
48
47
|
|
|
49
48
|
## Create goals
|
|
50
49
|
|
|
@@ -61,17 +60,17 @@ Loop until `gjc ultragoal status` reports all goals complete:
|
|
|
61
60
|
|
|
62
61
|
1. Run `gjc ultragoal complete-goals`.
|
|
63
62
|
2. Read the printed handoff.
|
|
64
|
-
3. Call `
|
|
65
|
-
4. If no active GJC goal exists, call `
|
|
63
|
+
3. Call `goal({"op":"get"})`.
|
|
64
|
+
4. If no active GJC goal exists, call `goal({"op":"create","objective":"<printed payload objective>"})` with the printed payload. In aggregate mode, if the same aggregate objective is already active, continue the current GJC story without creating a new GJC goal.
|
|
66
65
|
5. Complete the current GJC story only.
|
|
67
66
|
6. Run a completion audit against the story objective and real artifacts/tests.
|
|
68
|
-
7. Before any `--status complete` checkpoint, run the mandatory final cleanup/review gate below. In aggregate mode, do **not** call `
|
|
67
|
+
7. Before any `--status complete` checkpoint, run the mandatory final cleanup/review gate below. In aggregate mode, do **not** call `goal({"op":"complete"})` for intermediate stories; checkpoint each story with a fresh `goal({"op":"get"})` snapshot whose aggregate objective is still `active`. On the final story, use the same fresh active snapshot to create the final aggregate receipt first; only after that receipt exists may `goal({"op":"complete"})` run.
|
|
69
68
|
8. Checkpoint the durable ledger with that fresh active snapshot. Complete checkpoints require `--quality-gate-json`; the runtime hook rejects closure without a clean architect review:
|
|
70
|
-
`gjc ultragoal checkpoint --goal-id <id> --status complete --evidence "<evidence>" --gjc-goal-json <
|
|
69
|
+
`gjc ultragoal checkpoint --goal-id <id> --status complete --evidence "<evidence>" --gjc-goal-json <goal-get-json-or-path> --quality-gate-json <quality-gate-json-or-path>`
|
|
71
70
|
9. If blocked or failed, checkpoint failure:
|
|
72
71
|
`gjc ultragoal checkpoint --goal-id <id> --status failed --evidence "<blocker/evidence>"`
|
|
73
72
|
11. For legacy per-story completed-goal blockers, preserve the non-terminal blocker with:
|
|
74
|
-
`gjc ultragoal checkpoint --goal-id <id> --status blocked --evidence "<completed legacy GJC goal blocks
|
|
73
|
+
`gjc ultragoal checkpoint --goal-id <id> --status blocked --evidence "<completed legacy GJC goal blocks goal create in this thread>" --gjc-goal-json <goal-get-json-or-path>`
|
|
75
74
|
12. Resume failed goals with `gjc ultragoal complete-goals --retry-failed`.
|
|
76
75
|
|
|
77
76
|
## Dynamic steering
|
|
@@ -104,7 +103,6 @@ Steering invariants:
|
|
|
104
103
|
|
|
105
104
|
UserPromptSubmit uses the same steering API only for structured directives such as `GJC_ULTRAGOAL_STEER: { ... }`, `gjc.ultragoal.steer: { ... }`, or `gjc ultragoal steer: { ... }`. Normal prose does not mutate state, and repeated prompt-submit directives dedupe by prompt signature or idempotency key.
|
|
106
105
|
|
|
107
|
-
|
|
108
106
|
## Role-agent delegation guidance
|
|
109
107
|
|
|
110
108
|
Ultragoal execution should use GJC's bundled role-agent roster when a durable story is large enough to benefit from delegation:
|
|
@@ -126,10 +124,10 @@ For large subgoals with independent slices, the Ultragoal leader must spawn para
|
|
|
126
124
|
|
|
127
125
|
Use ultragoal and team together for a durable Ultragoal story that benefits from one visible tmux worker session. Ultragoal remains leader-owned: `.gjc/ultragoal/goals.json` stores the story plan and `.gjc/ultragoal/ledger.jsonl` stores checkpoints. Team is the single-worker tmux execution engine and returns task/evidence status to the leader.
|
|
128
126
|
|
|
129
|
-
The leader checkpoints Ultragoal from Team evidence with a fresh `
|
|
127
|
+
The leader checkpoints Ultragoal from Team evidence with a fresh `goal({"op":"get"})` snapshot:
|
|
130
128
|
|
|
131
129
|
```sh
|
|
132
|
-
gjc ultragoal checkpoint --goal-id <id> --status complete --evidence "<team evidence mentioning .gjc/ultragoal and <id>>" --gjc-goal-json <fresh-
|
|
130
|
+
gjc ultragoal checkpoint --goal-id <id> --status complete --evidence "<team evidence mentioning .gjc/ultragoal and <id>>" --gjc-goal-json <fresh-goal-get-json-or-path> --quality-gate-json <quality-gate-json-or-path>
|
|
133
131
|
```
|
|
134
132
|
|
|
135
133
|
Workers do not own ultragoal goal state, do not create worker ultragoal ledgers, and do not checkpoint Ultragoal. Workers must not run `gjc ultragoal checkpoint`; checkpoint authority stays with the leader after worker tasks are terminal. Team launch remains explicit; Ultragoal does not auto-launch Team and performs no hidden goal mutation.
|
|
@@ -142,7 +140,7 @@ An ultragoal story cannot be checkpointed `complete` until the active agent has
|
|
|
142
140
|
2. Run a cleanup/refactor review pass on changed files only; if there are no relevant edits, the cleaner still runs and records a passed/no-op report.
|
|
143
141
|
3. Rerun verification after the cleaner pass.
|
|
144
142
|
4. Run a final code review pass and fold it into the strict quality gate. Clean means `architectReview.architectureStatus`, `architectReview.productStatus`, and `architectReview.codeStatus` are all `"CLEAR"`, `architectReview.recommendation` is `"APPROVE"`, executor QA statuses are `"passed"`, iteration is `"passed"` with `fullRerun: true`, every evidence field is non-empty, and every blockers array is empty. `COMMENT`, `WATCH`, `REQUEST CHANGES`, `BLOCK`, missing evidence, or non-empty blockers are non-clean.
|
|
145
|
-
5. If review is non-clean, do **not** call `
|
|
143
|
+
5. If review is non-clean, do **not** call `goal({"op":"complete"})`. Record durable blocker work instead:
|
|
146
144
|
|
|
147
145
|
1. Run targeted implementation verification for the story.
|
|
148
146
|
2. Delegate an `architect` review covering all three lanes:
|
|
@@ -150,12 +148,12 @@ An ultragoal story cannot be checkpointed `complete` until the active agent has
|
|
|
150
148
|
- product-side: user-visible behavior, acceptance criteria, edge cases, regressions.
|
|
151
149
|
- code-side: maintainability, tests, integration points, and unsafe shortcuts.
|
|
152
150
|
3. Delegate an `executor` QA/red-team lane to build and run the e2e/read-teaming QA suite appropriate for the story. This lane must try to break the change, not just confirm the happy path.
|
|
153
|
-
4. If any lane finds an issue, do **not** checkpoint `complete` and do **not** call `
|
|
151
|
+
4. If any lane finds an issue, do **not** checkpoint `complete` and do **not** call `goal({"op":"complete"})`. Record durable blocker work instead:
|
|
154
152
|
```sh
|
|
155
|
-
gjc ultragoal record-review-blockers --goal-id <id> --title "Resolve verification blockers" --objective "<blocker-resolution objective>" --evidence "<architect/executor findings>" --gjc-goal-json <active-get-
|
|
153
|
+
gjc ultragoal record-review-blockers --goal-id <id> --title "Resolve verification blockers" --objective "<blocker-resolution objective>" --evidence "<architect/executor findings>" --gjc-goal-json <active-goal-get-json-or-path>
|
|
156
154
|
```
|
|
157
155
|
5. Complete or steer through the blocker story, then rerun the full blocking verification loop. Repeat until all verifier lanes are clean.
|
|
158
|
-
6. Only after the loop is clean, checkpoint the story as complete with a structured quality gate and a fresh active `
|
|
156
|
+
6. Only after the loop is clean, checkpoint the story as complete with a structured quality gate and a fresh active `goal({"op":"get"})` snapshot. The checkpoint creates a receipt; `goals.json.status` alone is not proof. In aggregate mode, the final aggregate receipt must exist before `goal({"op":"complete"})` is allowed.
|
|
159
157
|
|
|
160
158
|
The native `checkpoint --status complete` command rejects missing or shallow gates. `--quality-gate-json` must include:
|
|
161
159
|
|
|
@@ -197,10 +195,10 @@ Receipts are freshness-scoped:
|
|
|
197
195
|
## Constraints
|
|
198
196
|
|
|
199
197
|
- The shell command cannot directly invoke interactive `/goal`; it emits a model-facing handoff for the active GJC agent.
|
|
200
|
-
- Ultragoal intentionally does not invoke `/goal clear` or hidden `thread/goal/clear`; use only the
|
|
198
|
+
- Ultragoal intentionally does not invoke `/goal clear` or hidden `thread/goal/clear`; use only the unified goal-tool surface: `goal({"op":"get"})`, `goal({"op":"create"})`, and `goal({"op":"complete"})`.
|
|
201
199
|
- After a completed aggregate ultragoal run, clear the goal manually with `/goal clear` before starting another ultragoal run in the same session/thread.
|
|
202
|
-
- Never call `
|
|
203
|
-
- Never call `
|
|
204
|
-
- In aggregate mode, intermediate and final story checkpoints require a matching `active` GJC goal snapshot; the final story checkpoint creates the final aggregate receipt before `
|
|
205
|
-
- Completion checkpoints require read-only goal snapshot reconciliation: pass fresh `
|
|
200
|
+
- Never call `goal({"op":"create"})` when `goal({"op":"get"})` reports a different active goal.
|
|
201
|
+
- Never call `goal({"op":"complete"})` unless the aggregate run or legacy per-story goal is actually complete.
|
|
202
|
+
- In aggregate mode, intermediate and final story checkpoints require a matching `active` GJC goal snapshot; the final story checkpoint creates the final aggregate receipt before `goal({"op":"complete"})` may reconcile the inline goal state.
|
|
203
|
+
- Completion checkpoints require read-only goal snapshot reconciliation: pass fresh `goal({"op":"get"})` JSON/path with `--gjc-goal-json`; shell commands and hooks must not mutate goal state.
|
|
206
204
|
- Treat `ledger.jsonl` as the durable audit trail; checkpoint after every success or failure.
|
package/src/eval/py/prelude.py
CHANGED
|
@@ -431,7 +431,7 @@ if "__gjc_prelude_loaded__" not in globals():
|
|
|
431
431
|
return data.get("value")
|
|
432
432
|
|
|
433
433
|
class _ToolProxy:
|
|
434
|
-
"""`tool.<name>(args)` proxy mirroring the JS
|
|
434
|
+
"""`tool.<name>(args)` proxy mirroring the JS session tool API."""
|
|
435
435
|
|
|
436
436
|
__slots__ = ("_base", "_token", "_session")
|
|
437
437
|
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { syncSkillActiveState } from "../skill-state/active-state";
|
|
5
|
+
import { buildDeepInterviewHudSummary } from "../skill-state/workflow-hud";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Native implementation of `gjc deep-interview`.
|
|
9
|
+
*
|
|
10
|
+
* The CLI itself does not run the Socratic interview; that lives inside the `/skill:deep-interview`
|
|
11
|
+
* skill executed by the agent. This handler validates the documented argument-hint surface
|
|
12
|
+
* (`[--quick|--standard|--deep] <idea>`), seeds `.gjc/state/deep-interview-state.json`, and
|
|
13
|
+
* updates the shared HUD rail via `syncSkillActiveState` so the active interview is visible to
|
|
14
|
+
* the TUI.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export interface DeepInterviewCommandResult {
|
|
18
|
+
status: number;
|
|
19
|
+
stdout?: string;
|
|
20
|
+
stderr?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const PATH_COMPONENT_RE = /^[A-Za-z0-9_-][A-Za-z0-9._-]{0,63}$/;
|
|
24
|
+
|
|
25
|
+
const DEFAULT_AMBIGUITY_THRESHOLD = 0.05;
|
|
26
|
+
|
|
27
|
+
const RESOLUTION_THRESHOLDS = {
|
|
28
|
+
quick: 0.6,
|
|
29
|
+
standard: 0.5,
|
|
30
|
+
deep: 0.35,
|
|
31
|
+
} as const;
|
|
32
|
+
|
|
33
|
+
type DeepInterviewResolution = keyof typeof RESOLUTION_THRESHOLDS;
|
|
34
|
+
|
|
35
|
+
class DeepInterviewCommandError extends Error {
|
|
36
|
+
constructor(
|
|
37
|
+
public readonly exitStatus: number,
|
|
38
|
+
message: string,
|
|
39
|
+
) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.name = "DeepInterviewCommandError";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const VALUE_FLAGS = new Set(["--session-id", "--threshold", "--threshold-source"]);
|
|
46
|
+
|
|
47
|
+
function flagValue(args: readonly string[], flag: string): string | undefined {
|
|
48
|
+
const index = args.indexOf(flag);
|
|
49
|
+
if (index < 0) return undefined;
|
|
50
|
+
return args[index + 1];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function hasFlag(args: readonly string[], flag: string): boolean {
|
|
54
|
+
return args.includes(flag);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function assertSafePathComponent(value: string, label: string): void {
|
|
58
|
+
if (!PATH_COMPONENT_RE.test(value) || value.includes("..")) {
|
|
59
|
+
throw new DeepInterviewCommandError(2, `invalid path component for --${label}: ${value}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function encodeSessionSegment(value: string): string {
|
|
64
|
+
return encodeURIComponent(value).replaceAll(".", "%2E");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface ResolvedDeepInterviewArgs {
|
|
68
|
+
resolution: DeepInterviewResolution;
|
|
69
|
+
threshold: number;
|
|
70
|
+
thresholdSource: string;
|
|
71
|
+
sessionId?: string;
|
|
72
|
+
idea: string;
|
|
73
|
+
json: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function readSettingsAmbiguityThreshold(
|
|
77
|
+
settingsPath: string,
|
|
78
|
+
): Promise<{ threshold: number; source: string } | undefined> {
|
|
79
|
+
let raw: string;
|
|
80
|
+
try {
|
|
81
|
+
raw = await fs.readFile(settingsPath, "utf-8");
|
|
82
|
+
} catch (error) {
|
|
83
|
+
const err = error as NodeJS.ErrnoException;
|
|
84
|
+
if (err.code === "ENOENT") return undefined;
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
let parsed: unknown;
|
|
88
|
+
try {
|
|
89
|
+
parsed = JSON.parse(raw);
|
|
90
|
+
} catch {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
const candidate = (parsed as { gjc?: { deepInterview?: { ambiguityThreshold?: unknown } } })?.gjc?.deepInterview
|
|
94
|
+
?.ambiguityThreshold;
|
|
95
|
+
if (typeof candidate !== "number" || !Number.isFinite(candidate) || candidate <= 0 || candidate > 1) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
return { threshold: candidate, source: settingsPath };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function resolveConfiguredAmbiguityThreshold(
|
|
102
|
+
cwd: string,
|
|
103
|
+
): Promise<{ threshold: number; source: string } | undefined> {
|
|
104
|
+
const projectSettings = path.join(cwd, ".gjc", "settings.json");
|
|
105
|
+
const projectValue = await readSettingsAmbiguityThreshold(projectSettings);
|
|
106
|
+
if (projectValue) return projectValue;
|
|
107
|
+
const configDir = process.env.GJC_CONFIG_DIR?.trim() || path.join(os.homedir(), ".gjc");
|
|
108
|
+
const userSettings = path.join(configDir, "settings.json");
|
|
109
|
+
return await readSettingsAmbiguityThreshold(userSettings);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function resolveDeepInterviewArgs(args: readonly string[], cwd: string): Promise<ResolvedDeepInterviewArgs> {
|
|
113
|
+
const sessionId = flagValue(args, "--session-id")?.trim() || undefined;
|
|
114
|
+
if (sessionId) assertSafePathComponent(sessionId, "session-id");
|
|
115
|
+
|
|
116
|
+
const explicitResolutions = (["quick", "standard", "deep"] as const).filter(name => hasFlag(args, `--${name}`));
|
|
117
|
+
if (explicitResolutions.length > 1) {
|
|
118
|
+
throw new DeepInterviewCommandError(2, "pass at most one of --quick, --standard, --deep");
|
|
119
|
+
}
|
|
120
|
+
const resolution: DeepInterviewResolution | undefined = explicitResolutions[0];
|
|
121
|
+
|
|
122
|
+
// Precedence: --threshold > settings.json (project then user) > resolution flag default > 0.05.
|
|
123
|
+
let threshold: number = DEFAULT_AMBIGUITY_THRESHOLD;
|
|
124
|
+
let thresholdSource = "default";
|
|
125
|
+
const thresholdOverride = flagValue(args, "--threshold");
|
|
126
|
+
if (thresholdOverride !== undefined) {
|
|
127
|
+
const parsed = Number(thresholdOverride);
|
|
128
|
+
if (!Number.isFinite(parsed) || parsed <= 0 || parsed > 1) {
|
|
129
|
+
throw new DeepInterviewCommandError(
|
|
130
|
+
2,
|
|
131
|
+
`invalid --threshold: ${thresholdOverride}. Expected 0 < threshold <= 1.`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
threshold = parsed;
|
|
135
|
+
thresholdSource = flagValue(args, "--threshold-source")?.trim() || "flag:--threshold";
|
|
136
|
+
} else {
|
|
137
|
+
const configured = await resolveConfiguredAmbiguityThreshold(cwd);
|
|
138
|
+
if (configured) {
|
|
139
|
+
threshold = configured.threshold;
|
|
140
|
+
thresholdSource = configured.source;
|
|
141
|
+
} else if (resolution) {
|
|
142
|
+
threshold = RESOLUTION_THRESHOLDS[resolution];
|
|
143
|
+
thresholdSource = `flag:--${resolution}`;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const ideaParts: string[] = [];
|
|
148
|
+
let skipNext = false;
|
|
149
|
+
for (const arg of args) {
|
|
150
|
+
if (skipNext) {
|
|
151
|
+
skipNext = false;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (VALUE_FLAGS.has(arg)) {
|
|
155
|
+
skipNext = true;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (arg === "--quick" || arg === "--standard" || arg === "--deep" || arg === "--json") continue;
|
|
159
|
+
if (arg.startsWith("-")) {
|
|
160
|
+
throw new DeepInterviewCommandError(2, `unknown flag for gjc deep-interview: ${arg}`);
|
|
161
|
+
}
|
|
162
|
+
ideaParts.push(arg);
|
|
163
|
+
}
|
|
164
|
+
const idea = ideaParts.join(" ").trim();
|
|
165
|
+
const effectiveResolution: DeepInterviewResolution = resolution ?? "standard";
|
|
166
|
+
return {
|
|
167
|
+
resolution: effectiveResolution,
|
|
168
|
+
threshold,
|
|
169
|
+
thresholdSource,
|
|
170
|
+
sessionId,
|
|
171
|
+
idea,
|
|
172
|
+
json: hasFlag(args, "--json"),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function seedDeepInterviewState(cwd: string, resolved: ResolvedDeepInterviewArgs): Promise<string> {
|
|
177
|
+
const stateDir = resolved.sessionId
|
|
178
|
+
? path.join(cwd, ".gjc", "state", "sessions", encodeSessionSegment(resolved.sessionId))
|
|
179
|
+
: path.join(cwd, ".gjc", "state");
|
|
180
|
+
await fs.mkdir(stateDir, { recursive: true });
|
|
181
|
+
const statePath = path.join(stateDir, "deep-interview-state.json");
|
|
182
|
+
const now = new Date().toISOString();
|
|
183
|
+
const payload: Record<string, unknown> = {
|
|
184
|
+
active: true,
|
|
185
|
+
current_phase: "interviewing",
|
|
186
|
+
skill: "deep-interview",
|
|
187
|
+
resolution: resolved.resolution,
|
|
188
|
+
threshold: resolved.threshold,
|
|
189
|
+
threshold_source: resolved.thresholdSource,
|
|
190
|
+
state: {
|
|
191
|
+
initial_idea: resolved.idea,
|
|
192
|
+
rounds: [],
|
|
193
|
+
current_ambiguity: 1.0,
|
|
194
|
+
threshold: resolved.threshold,
|
|
195
|
+
threshold_source: resolved.thresholdSource,
|
|
196
|
+
},
|
|
197
|
+
updated_at: now,
|
|
198
|
+
};
|
|
199
|
+
if (resolved.sessionId) payload.session_id = resolved.sessionId;
|
|
200
|
+
await fs.writeFile(statePath, `${JSON.stringify(payload, null, 2)}\n`);
|
|
201
|
+
return statePath;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function syncDeepInterviewHud(options: {
|
|
205
|
+
cwd: string;
|
|
206
|
+
sessionId?: string;
|
|
207
|
+
phase: string;
|
|
208
|
+
ambiguity?: number;
|
|
209
|
+
threshold?: number;
|
|
210
|
+
roundCount?: number;
|
|
211
|
+
specStatus?: string;
|
|
212
|
+
}): Promise<void> {
|
|
213
|
+
try {
|
|
214
|
+
await syncSkillActiveState({
|
|
215
|
+
cwd: options.cwd,
|
|
216
|
+
skill: "deep-interview",
|
|
217
|
+
active: options.phase !== "complete",
|
|
218
|
+
phase: options.phase,
|
|
219
|
+
sessionId: options.sessionId,
|
|
220
|
+
source: "gjc-deep-interview-native",
|
|
221
|
+
hud: buildDeepInterviewHudSummary({
|
|
222
|
+
phase: options.phase,
|
|
223
|
+
ambiguity: options.ambiguity,
|
|
224
|
+
threshold: options.threshold,
|
|
225
|
+
roundCount: options.roundCount,
|
|
226
|
+
specStatus: options.specStatus,
|
|
227
|
+
updatedAt: new Date().toISOString(),
|
|
228
|
+
}),
|
|
229
|
+
});
|
|
230
|
+
} catch {
|
|
231
|
+
// HUD sync is best-effort and must not change command semantics.
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export async function runNativeDeepInterviewCommand(
|
|
236
|
+
args: string[],
|
|
237
|
+
cwd = process.cwd(),
|
|
238
|
+
): Promise<DeepInterviewCommandResult> {
|
|
239
|
+
try {
|
|
240
|
+
const resolved = await resolveDeepInterviewArgs(args, cwd);
|
|
241
|
+
if (!resolved.idea) {
|
|
242
|
+
throw new DeepInterviewCommandError(
|
|
243
|
+
2,
|
|
244
|
+
'gjc deep-interview requires an idea, e.g. `gjc deep-interview "<idea>"`.',
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
const statePath = await seedDeepInterviewState(cwd, resolved);
|
|
248
|
+
await syncDeepInterviewHud({
|
|
249
|
+
cwd,
|
|
250
|
+
sessionId: resolved.sessionId,
|
|
251
|
+
phase: "interviewing",
|
|
252
|
+
ambiguity: 1,
|
|
253
|
+
threshold: resolved.threshold,
|
|
254
|
+
roundCount: 0,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const summary = {
|
|
258
|
+
skill: "deep-interview",
|
|
259
|
+
resolution: resolved.resolution,
|
|
260
|
+
threshold: resolved.threshold,
|
|
261
|
+
threshold_source: resolved.thresholdSource,
|
|
262
|
+
idea: resolved.idea,
|
|
263
|
+
state_path: statePath,
|
|
264
|
+
handoff: "Run `/skill:deep-interview` inside the GJC agent to drive the Socratic interview loop.",
|
|
265
|
+
};
|
|
266
|
+
const stdout = resolved.json
|
|
267
|
+
? `${JSON.stringify(summary, null, 2)}\n`
|
|
268
|
+
: [
|
|
269
|
+
`Seeded deep-interview ${resolved.resolution} run at ${statePath}.`,
|
|
270
|
+
`Threshold: ${(resolved.threshold * 100).toFixed(0)}% (source: ${resolved.thresholdSource}).`,
|
|
271
|
+
"Run `/skill:deep-interview` inside the GJC agent to execute the interview.",
|
|
272
|
+
"",
|
|
273
|
+
].join("\n");
|
|
274
|
+
return { status: 0, stdout };
|
|
275
|
+
} catch (error) {
|
|
276
|
+
if (error instanceof DeepInterviewCommandError) return { status: error.exitStatus, stderr: `${error.message}\n` };
|
|
277
|
+
return { status: 1, stderr: `${error instanceof Error ? error.message : String(error)}\n` };
|
|
278
|
+
}
|
|
279
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { Snowflake } from "@gajae-code/utils";
|
|
4
|
-
import type
|
|
4
|
+
import { type Goal, type GoalModeState, normalizeGoal } from "../goals/state";
|
|
5
5
|
import {
|
|
6
6
|
buildSessionContext,
|
|
7
7
|
loadEntriesFromFile,
|
|
@@ -94,24 +94,7 @@ export async function writePendingGoalModeRequest(input: {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
function goalFromModeData(modeData: Record<string, unknown> | undefined): Goal | null {
|
|
97
|
-
|
|
98
|
-
if (typeof candidate !== "object" || candidate === null) return null;
|
|
99
|
-
const goal = candidate as Partial<Goal>;
|
|
100
|
-
if (
|
|
101
|
-
typeof goal.id !== "string" ||
|
|
102
|
-
typeof goal.objective !== "string" ||
|
|
103
|
-
typeof goal.status !== "string" ||
|
|
104
|
-
typeof goal.tokensUsed !== "number" ||
|
|
105
|
-
typeof goal.timeUsedSeconds !== "number" ||
|
|
106
|
-
typeof goal.createdAt !== "number" ||
|
|
107
|
-
typeof goal.updatedAt !== "number"
|
|
108
|
-
) {
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
|
-
if (!["active", "paused", "budget-limited", "complete", "dropped"].includes(goal.status)) {
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
return goal as Goal;
|
|
97
|
+
return normalizeGoal(modeData?.goal);
|
|
115
98
|
}
|
|
116
99
|
|
|
117
100
|
function isNonTerminalGoal(goal: Goal | null): goal is Goal {
|
|
@@ -1,12 +1,30 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import type { Args } from "../cli/args";
|
|
3
|
+
import {
|
|
4
|
+
buildGjcTmuxProfileCommands,
|
|
5
|
+
buildGjcTmuxSessionName,
|
|
6
|
+
buildGjcTmuxSessionSlug,
|
|
7
|
+
GJC_DEFAULT_TMUX_SESSION,
|
|
8
|
+
GJC_TMUX_COMMAND_ENV,
|
|
9
|
+
GJC_TMUX_MOUSE_ENV,
|
|
10
|
+
GJC_TMUX_PROFILE_ENV,
|
|
11
|
+
GJC_TMUX_SESSION_PREFIX,
|
|
12
|
+
type GjcTmuxProfileCommand,
|
|
13
|
+
resolveGjcTmuxCommand,
|
|
14
|
+
} from "./tmux-common";
|
|
15
|
+
import { findGjcTmuxSessionByBranch } from "./tmux-sessions";
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
buildGjcTmuxProfileCommands,
|
|
19
|
+
GJC_DEFAULT_TMUX_SESSION,
|
|
20
|
+
GJC_TMUX_COMMAND_ENV,
|
|
21
|
+
GJC_TMUX_MOUSE_ENV,
|
|
22
|
+
GJC_TMUX_PROFILE_ENV,
|
|
23
|
+
GJC_TMUX_SESSION_PREFIX,
|
|
24
|
+
};
|
|
3
25
|
|
|
4
|
-
export const GJC_DEFAULT_TMUX_SESSION = "gajae_code";
|
|
5
26
|
export const GJC_TMUX_LAUNCHED_ENV = "GJC_TMUX_LAUNCHED";
|
|
6
27
|
export const GJC_LAUNCH_POLICY_ENV = "GJC_LAUNCH_POLICY";
|
|
7
|
-
export const GJC_TMUX_COMMAND_ENV = "GJC_TMUX_COMMAND";
|
|
8
|
-
export const GJC_TMUX_PROFILE_ENV = "GJC_TMUX_PROFILE";
|
|
9
|
-
export const GJC_TMUX_MOUSE_ENV = "GJC_MOUSE";
|
|
10
28
|
|
|
11
29
|
type LaunchPolicy = "direct" | "tmux";
|
|
12
30
|
|
|
@@ -26,6 +44,10 @@ export interface TmuxLaunchContext {
|
|
|
26
44
|
tty?: TtyState;
|
|
27
45
|
spawnSync?: TmuxSpawnSync;
|
|
28
46
|
tmuxAvailable?: boolean;
|
|
47
|
+
worktreeBranch?: string | null;
|
|
48
|
+
currentBranch?: string | null;
|
|
49
|
+
existingBranchSessionName?: string | null;
|
|
50
|
+
project?: string | null;
|
|
29
51
|
}
|
|
30
52
|
|
|
31
53
|
export interface TmuxSpawnResult {
|
|
@@ -50,12 +72,9 @@ export interface TmuxLaunchPlan {
|
|
|
50
72
|
cwd: string;
|
|
51
73
|
innerCommand: string;
|
|
52
74
|
newSessionArgs: string[];
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
export interface GjcTmuxProfileCommand {
|
|
57
|
-
description: string;
|
|
58
|
-
args: string[];
|
|
75
|
+
branch?: string | null;
|
|
76
|
+
attachSessionName?: string;
|
|
77
|
+
project?: string | null;
|
|
59
78
|
}
|
|
60
79
|
|
|
61
80
|
export interface GjcTmuxProfileResult {
|
|
@@ -70,6 +89,9 @@ export interface GjcTmuxProfileContext {
|
|
|
70
89
|
cwd?: string;
|
|
71
90
|
env?: NodeJS.ProcessEnv;
|
|
72
91
|
spawnSync?: TmuxSpawnSync;
|
|
92
|
+
branch?: string | null;
|
|
93
|
+
branchSlug?: string | null;
|
|
94
|
+
project?: string | null;
|
|
73
95
|
}
|
|
74
96
|
|
|
75
97
|
interface CommandResolutionContext {
|
|
@@ -103,35 +125,14 @@ function shellQuote(value: string): string {
|
|
|
103
125
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
104
126
|
}
|
|
105
127
|
|
|
106
|
-
function envDisabled(value: string | undefined): boolean {
|
|
107
|
-
const normalized = value?.trim().toLowerCase();
|
|
108
|
-
return normalized === "0" || normalized === "false" || normalized === "off" || normalized === "no";
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export function buildGjcTmuxProfileCommands(
|
|
112
|
-
target: string,
|
|
113
|
-
env: NodeJS.ProcessEnv = process.env,
|
|
114
|
-
): GjcTmuxProfileCommand[] {
|
|
115
|
-
if (envDisabled(env[GJC_TMUX_PROFILE_ENV])) return [];
|
|
116
|
-
const commands: GjcTmuxProfileCommand[] = [
|
|
117
|
-
{ description: "mark GJC tmux ownership", args: ["set-option", "-t", target, "@gjc-profile", "1"] },
|
|
118
|
-
{ description: "enable tmux clipboard integration", args: ["set-option", "-t", target, "set-clipboard", "on"] },
|
|
119
|
-
{
|
|
120
|
-
description: "make copy-mode selection readable",
|
|
121
|
-
args: ["set-window-option", "-t", target, "mode-style", "fg=colour231,bg=colour60"],
|
|
122
|
-
},
|
|
123
|
-
];
|
|
124
|
-
if (!envDisabled(env[GJC_TMUX_MOUSE_ENV]))
|
|
125
|
-
commands.unshift({
|
|
126
|
-
description: "enable tmux mouse scrolling",
|
|
127
|
-
args: ["set-option", "-t", target, "mouse", "on"],
|
|
128
|
-
});
|
|
129
|
-
return commands;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
128
|
export function applyGjcTmuxProfile(context: GjcTmuxProfileContext): GjcTmuxProfileResult {
|
|
133
129
|
const env = context.env ?? process.env;
|
|
134
|
-
const
|
|
130
|
+
const branchSlug = context.branch ? buildGjcTmuxSessionSlug(context.branch) : (context.branchSlug ?? null);
|
|
131
|
+
const commands = buildGjcTmuxProfileCommands(context.target, env, {
|
|
132
|
+
branch: context.branch ?? null,
|
|
133
|
+
branchSlug,
|
|
134
|
+
project: context.project ?? null,
|
|
135
|
+
});
|
|
135
136
|
if (commands.length === 0) return { skipped: true, commands: [], failures: [] };
|
|
136
137
|
const spawnSync = context.spawnSync ?? defaultSpawnSync;
|
|
137
138
|
const cwd = context.cwd ?? process.cwd();
|
|
@@ -160,6 +161,25 @@ function buildInnerCommand(context: CommandResolutionContext, rawArgs: string[])
|
|
|
160
161
|
return `exec env ${GJC_TMUX_LAUNCHED_ENV}=1 ${quoted}`;
|
|
161
162
|
}
|
|
162
163
|
|
|
164
|
+
function readCurrentBranch(cwd: string): string | null {
|
|
165
|
+
try {
|
|
166
|
+
const result = Bun.spawnSync(["git", "symbolic-ref", "--quiet", "--short", "HEAD"], {
|
|
167
|
+
cwd,
|
|
168
|
+
stdout: "pipe",
|
|
169
|
+
stderr: "ignore",
|
|
170
|
+
});
|
|
171
|
+
if (result.exitCode !== 0) return null;
|
|
172
|
+
const branch = result.stdout.toString().trim();
|
|
173
|
+
return branch || null;
|
|
174
|
+
} catch {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function cleanupCreatedTmuxSession(plan: TmuxLaunchPlan, spawnSync: TmuxSpawnSync, options: TmuxSpawnOptions): void {
|
|
180
|
+
spawnSync(plan.tmuxCommand, ["kill-session", "-t", `=${plan.sessionName}`], options);
|
|
181
|
+
}
|
|
182
|
+
|
|
163
183
|
export function buildDefaultTmuxLaunchPlan(context: TmuxLaunchContext): TmuxLaunchPlan | undefined {
|
|
164
184
|
const env = context.env ?? process.env;
|
|
165
185
|
const policy = parseLaunchPolicy(env);
|
|
@@ -171,10 +191,18 @@ export function buildDefaultTmuxLaunchPlan(context: TmuxLaunchContext): TmuxLaun
|
|
|
171
191
|
if (policy === "tmux" && !isInteractiveRootLaunch(context.parsed, tty)) return undefined;
|
|
172
192
|
|
|
173
193
|
const cwd = context.cwd ?? process.cwd();
|
|
174
|
-
const
|
|
175
|
-
const
|
|
194
|
+
const branch = context.worktreeBranch ?? context.currentBranch ?? readCurrentBranch(cwd);
|
|
195
|
+
const project = context.project ?? cwd;
|
|
196
|
+
const sessionName = buildGjcTmuxSessionName(env, { branch });
|
|
197
|
+
const tmuxCommand = resolveGjcTmuxCommand(env);
|
|
176
198
|
const tmuxAvailable = context.tmuxAvailable ?? Bun.which(tmuxCommand) !== null;
|
|
177
199
|
if (!tmuxAvailable) return undefined;
|
|
200
|
+
const existingBranchSessionName =
|
|
201
|
+
"existingBranchSessionName" in context
|
|
202
|
+
? (context.existingBranchSessionName ?? undefined)
|
|
203
|
+
: context.worktreeBranch
|
|
204
|
+
? findGjcTmuxSessionByBranch(context.worktreeBranch, env, project)?.name
|
|
205
|
+
: undefined;
|
|
178
206
|
const innerCommand = buildInnerCommand(
|
|
179
207
|
{
|
|
180
208
|
cwd,
|
|
@@ -189,7 +217,9 @@ export function buildDefaultTmuxLaunchPlan(context: TmuxLaunchContext): TmuxLaun
|
|
|
189
217
|
cwd,
|
|
190
218
|
innerCommand,
|
|
191
219
|
newSessionArgs: ["new-session", "-d", "-s", sessionName, "-c", cwd, innerCommand],
|
|
192
|
-
|
|
220
|
+
branch,
|
|
221
|
+
project,
|
|
222
|
+
attachSessionName: existingBranchSessionName,
|
|
193
223
|
};
|
|
194
224
|
}
|
|
195
225
|
|
|
@@ -217,17 +247,27 @@ export function launchDefaultTmuxIfNeeded(context: TmuxLaunchContext): boolean {
|
|
|
217
247
|
stdout: "inherit",
|
|
218
248
|
stderr: "inherit",
|
|
219
249
|
};
|
|
250
|
+
if (plan.attachSessionName) {
|
|
251
|
+
const attached = spawnSync(plan.tmuxCommand, ["attach-session", "-t", `=${plan.attachSessionName}`], options);
|
|
252
|
+
return attached.exitCode === 0;
|
|
253
|
+
}
|
|
220
254
|
const created = spawnSync(plan.tmuxCommand, plan.newSessionArgs, options);
|
|
221
255
|
if (created.exitCode === 0) {
|
|
222
|
-
applyGjcTmuxProfile({
|
|
256
|
+
const profile = applyGjcTmuxProfile({
|
|
223
257
|
tmuxCommand: plan.tmuxCommand,
|
|
224
258
|
target: plan.sessionName,
|
|
225
259
|
cwd: plan.cwd,
|
|
226
260
|
env,
|
|
227
261
|
spawnSync,
|
|
262
|
+
branch: plan.branch,
|
|
263
|
+
project: plan.project,
|
|
228
264
|
});
|
|
265
|
+
if (profile.failures.length > 0) {
|
|
266
|
+
cleanupCreatedTmuxSession(plan, spawnSync, options);
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
229
269
|
}
|
|
230
|
-
|
|
231
|
-
|
|
270
|
+
if (created.exitCode !== 0) return false;
|
|
271
|
+
const attached = spawnSync(plan.tmuxCommand, ["attach-session", "-t", plan.sessionName], options);
|
|
232
272
|
return attached.exitCode === 0;
|
|
233
273
|
}
|