@gajae-code/coding-agent 0.3.2 → 0.4.1

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.
Files changed (125) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/types/config/model-registry.d.ts +17 -10
  3. package/dist/types/config/models-config-schema.d.ts +37 -0
  4. package/dist/types/config/settings-schema.d.ts +5 -0
  5. package/dist/types/edit/diff.d.ts +16 -0
  6. package/dist/types/edit/modes/replace.d.ts +7 -0
  7. package/dist/types/extensibility/gjc-plugins/activation.d.ts +14 -0
  8. package/dist/types/extensibility/gjc-plugins/index.d.ts +9 -0
  9. package/dist/types/extensibility/gjc-plugins/injection.d.ts +31 -0
  10. package/dist/types/extensibility/gjc-plugins/loader.d.ts +3 -0
  11. package/dist/types/extensibility/gjc-plugins/paths.d.ts +8 -0
  12. package/dist/types/extensibility/gjc-plugins/schema.d.ts +3 -0
  13. package/dist/types/extensibility/gjc-plugins/state.d.ts +9 -0
  14. package/dist/types/extensibility/gjc-plugins/tools.d.ts +8 -0
  15. package/dist/types/extensibility/gjc-plugins/types.d.ts +64 -0
  16. package/dist/types/extensibility/gjc-plugins/validation.d.ts +4 -0
  17. package/dist/types/extensibility/skills.d.ts +9 -1
  18. package/dist/types/gjc-runtime/state-runtime.d.ts +22 -0
  19. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -2
  20. package/dist/types/harness-control-plane/storage.d.ts +7 -0
  21. package/dist/types/lsp/client.d.ts +1 -0
  22. package/dist/types/modes/bridge/bridge-mode.d.ts +2 -0
  23. package/dist/types/modes/prompt-action-autocomplete.d.ts +2 -2
  24. package/dist/types/modes/rpc/rpc-client.d.ts +19 -1
  25. package/dist/types/modes/rpc/rpc-types.d.ts +179 -2
  26. package/dist/types/modes/shared/agent-wire/approval-gate.d.ts +57 -0
  27. package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +16 -1
  28. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +47 -0
  29. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +7 -0
  30. package/dist/types/modes/shared/agent-wire/handshake.d.ts +11 -1
  31. package/dist/types/modes/shared/agent-wire/protocol.d.ts +3 -1
  32. package/dist/types/modes/shared/agent-wire/responses.d.ts +1 -1
  33. package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +27 -0
  34. package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +68 -0
  35. package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +161 -0
  36. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +61 -0
  37. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +114 -0
  38. package/dist/types/modes/shared/agent-wire/workflow-gate-schema.d.ts +39 -0
  39. package/dist/types/modes/theme/theme.d.ts +2 -1
  40. package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
  41. package/dist/types/sdk.d.ts +7 -0
  42. package/dist/types/session/agent-session.d.ts +10 -0
  43. package/dist/types/session/blob-store.d.ts +17 -0
  44. package/dist/types/session/messages.d.ts +3 -0
  45. package/dist/types/session/session-storage.d.ts +6 -0
  46. package/dist/types/skill-state/active-state.d.ts +13 -0
  47. package/dist/types/thinking.d.ts +3 -2
  48. package/dist/types/tools/index.d.ts +3 -0
  49. package/package.json +9 -7
  50. package/src/cli.ts +14 -0
  51. package/src/commands/harness.ts +192 -7
  52. package/src/commands/ultragoal.ts +1 -21
  53. package/src/config/model-equivalence.ts +1 -1
  54. package/src/config/model-registry.ts +32 -5
  55. package/src/config/models-config-schema.ts +7 -2
  56. package/src/config/settings-schema.ts +4 -1
  57. package/src/defaults/gjc/skills/deep-interview/SKILL.md +19 -23
  58. package/src/defaults/gjc/skills/ralplan/SKILL.md +7 -7
  59. package/src/discovery/claude-plugins.ts +25 -5
  60. package/src/edit/diff.ts +64 -1
  61. package/src/edit/modes/replace.ts +60 -2
  62. package/src/extensibility/gjc-plugins/activation.ts +87 -0
  63. package/src/extensibility/gjc-plugins/index.ts +9 -0
  64. package/src/extensibility/gjc-plugins/injection.ts +114 -0
  65. package/src/extensibility/gjc-plugins/loader.ts +131 -0
  66. package/src/extensibility/gjc-plugins/paths.ts +66 -0
  67. package/src/extensibility/gjc-plugins/schema.ts +79 -0
  68. package/src/extensibility/gjc-plugins/state.ts +29 -0
  69. package/src/extensibility/gjc-plugins/tools.ts +47 -0
  70. package/src/extensibility/gjc-plugins/types.ts +97 -0
  71. package/src/extensibility/gjc-plugins/validation.ts +76 -0
  72. package/src/extensibility/skills.ts +39 -7
  73. package/src/gjc-runtime/state-runtime.ts +93 -2
  74. package/src/gjc-runtime/state-writer.ts +17 -1
  75. package/src/gjc-runtime/ultragoal-runtime.ts +76 -121
  76. package/src/gjc-runtime/workflow-manifest.generated.json +5 -0
  77. package/src/gjc-runtime/workflow-manifest.ts +2 -2
  78. package/src/harness-control-plane/storage.ts +144 -2
  79. package/src/hashline/hash.ts +23 -0
  80. package/src/hooks/skill-state.ts +2 -0
  81. package/src/internal-urls/docs-index.generated.ts +5 -5
  82. package/src/lsp/client.ts +7 -0
  83. package/src/modes/acp/acp-agent.ts +25 -2
  84. package/src/modes/bridge/bridge-mode.ts +124 -2
  85. package/src/modes/controllers/input-controller.ts +14 -2
  86. package/src/modes/prompt-action-autocomplete.ts +49 -10
  87. package/src/modes/rpc/rpc-client.ts +79 -3
  88. package/src/modes/rpc/rpc-mode.ts +67 -0
  89. package/src/modes/rpc/rpc-types.ts +224 -2
  90. package/src/modes/shared/agent-wire/approval-gate.ts +151 -0
  91. package/src/modes/shared/agent-wire/command-dispatch.ts +97 -4
  92. package/src/modes/shared/agent-wire/command-validation.ts +25 -1
  93. package/src/modes/shared/agent-wire/deep-interview-gate.ts +222 -0
  94. package/src/modes/shared/agent-wire/event-envelope.ts +13 -0
  95. package/src/modes/shared/agent-wire/handshake.ts +43 -3
  96. package/src/modes/shared/agent-wire/protocol.ts +7 -0
  97. package/src/modes/shared/agent-wire/responses.ts +2 -2
  98. package/src/modes/shared/agent-wire/scopes.ts +2 -0
  99. package/src/modes/shared/agent-wire/unattended-action-policy.ts +341 -0
  100. package/src/modes/shared/agent-wire/unattended-audit.ts +175 -0
  101. package/src/modes/shared/agent-wire/unattended-run-controller.ts +406 -0
  102. package/src/modes/shared/agent-wire/unattended-session.ts +180 -0
  103. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +324 -0
  104. package/src/modes/shared/agent-wire/workflow-gate-schema.ts +331 -0
  105. package/src/modes/theme/theme.ts +6 -0
  106. package/src/prompts/system/system-prompt.md +9 -0
  107. package/src/runtime-mcp/client.ts +7 -4
  108. package/src/runtime-mcp/manager.ts +45 -13
  109. package/src/runtime-mcp/transports/http.ts +40 -14
  110. package/src/runtime-mcp/transports/stdio.ts +11 -10
  111. package/src/sdk.ts +47 -0
  112. package/src/session/agent-session.ts +211 -2
  113. package/src/session/blob-store.ts +84 -0
  114. package/src/session/messages.ts +3 -0
  115. package/src/session/session-manager.ts +390 -33
  116. package/src/session/session-storage.ts +26 -0
  117. package/src/setup/provider-onboarding.ts +2 -2
  118. package/src/skill-state/active-state.ts +89 -1
  119. package/src/task/discovery.ts +7 -1
  120. package/src/task/executor.ts +16 -2
  121. package/src/thinking.ts +8 -2
  122. package/src/tools/ask.ts +39 -9
  123. package/src/tools/index.ts +3 -0
  124. package/src/tools/skill.ts +15 -3
  125. package/src/utils/edit-mode.ts +1 -1
@@ -1,4 +1,4 @@
1
- import { THINKING_EFFORTS } from "@gajae-code/ai";
1
+ import { THINKING_EFFORTS } from "@gajae-code/ai/model-thinking";
2
2
  import { TASK_SIMPLE_MODES } from "../task/simple-mode";
3
3
  import { getThinkingLevelMetadata } from "../thinking";
4
4
  import { EDIT_MODES } from "../utils/edit-mode";
@@ -2775,6 +2775,8 @@ export const SETTINGS_SCHEMA = {
2775
2775
  "thinkingBudgets.high": { type: "number", default: 16384 },
2776
2776
 
2777
2777
  "thinkingBudgets.xhigh": { type: "number", default: 32768 },
2778
+
2779
+ "thinkingBudgets.max": { type: "number", default: 65536 },
2778
2780
  } as const;
2779
2781
 
2780
2782
  // ═══════════════════════════════════════════════════════════════════════════
@@ -2955,6 +2957,7 @@ export interface ThinkingBudgetsSettings {
2955
2957
  medium: number;
2956
2958
  high: number;
2957
2959
  xhigh: number;
2960
+ max: number;
2958
2961
  }
2959
2962
 
2960
2963
  export interface SttSettings {
@@ -530,28 +530,24 @@ After the spec is written, mark it `pending approval` and present execution opti
530
530
 
531
531
  **Options:**
532
532
 
533
- 1. **Refine with ralplan consensus (Recommended)**
534
- - Description: "Consensus-refine this spec with Planner/Architect/Critic, then stop for explicit execution approval. Maximum quality."
535
- - Action: Only after the user selects this option, invoke `/skill:ralplan --consensus --direct` with the spec file path as context. The `--direct` flag skips the ralplan skill's interview phase (the deep interview already gathered requirements), while `--consensus` triggers the Planner/Architect/Critic loop. When consensus completes and produces a plan in `.gjc/plans/`, stop with that plan marked `pending approval`; do not automatically invoke execution or any other execution skill.
536
- - Pipeline: `deep-interview spec → explicit approval to refine → ralplan --consensus --direct → pending approval → separate execution approval`
533
+ 1. **Refine with ralplan consensus (Recommended — default for almost all specs)**
534
+ - Description: "Consensus-refine this spec with Planner/Architect/Critic, then stop for explicit execution approval. Maximum quality. Prefer this unless the spec is already implementation-ready and trivially simple."
535
+ - Action: Only after the user selects this option, invoke `/skill:ralplan` with the spec file path as context. Ralplan is already the Planner Architect Critic consensus workflow, so no extra slash-skill flags are required or supported. When consensus completes and produces a plan in `.gjc/plans/`, stop with that plan marked `pending approval`; do not automatically invoke execution or any other execution skill.
536
+ - Pipeline: `deep-interview spec → explicit approval to refine → ralplan → pending approval → separate execution approval`
537
537
 
538
- 2. **Execute with team**
539
- - Description: "Full autonomous pipelineplanning, parallel implementation, QA, validation. Faster but without consensus refinement."
540
- - Action: Invoke `/skill:team` with the spec file path as context only after the user explicitly selects this execution option. The spec replaces team planning input.
538
+ 2. **Execute with ultragoal (only when spec is already implementation-ready and really simple)**
539
+ - Description: "Goal-tracked autonomous executiondrives the spec to completion with verification. Skip ralplan refinement only when the spec is concrete, low-risk, and trivially small."
540
+ - Action: Invoke `/skill:ultragoal` with the spec file path as context only after the user explicitly selects this execution option. The spec replaces ultragoal planning input. Recommend this only when the spec needs no further planning; otherwise route through ralplan refinement first.
541
541
 
542
- 3. **Execute with team**
543
- - Description: "Persistence loop with architect verificationkeeps working until all acceptance criteria pass"
544
- - Action: Invoke `/skill:team` with the spec file path as the task definition.
542
+ 3. **Execute with team (only when implementation-ready, simple, AND tmux parallelization is required)**
543
+ - Description: "N coordinated parallel agents in tmux only when the spec is already implementation-ready and genuinely needs tmux-based interactive worker parallelization."
544
+ - Action: Invoke `/skill:team` with the spec file path as the shared plan only after the user explicitly selects this option. Reserve this for the narrow case where the spec is simple/ready and tmux interactive parallel workers are actually needed; otherwise prefer ralplan refinement, then ultragoal.
545
545
 
546
- 4. **Execute with team**
547
- - Description: "N coordinated parallel agents — fastest execution for large specs"
548
- - Action: Invoke `/skill:team` with the spec file path as the shared plan.
549
-
550
- 5. **Refine further**
546
+ 4. **Refine further**
551
547
  - Description: "Continue interviewing to improve clarity (current: {score}%)"
552
548
  - Action: Return to Phase 2 interview loop.
553
549
 
554
- **IMPORTANT:** On explicit execution selection, **MUST** use the chosen bundled GJC workflow skill entrypoint (`/skill:ralplan` or `/skill:team`) inside the agent session. `gjc ralplan` is a native CLI that accepts the documented skill flags and seeds local `.gjc/state` receipts; agent sessions should still drive the consensus loop through `/skill:ralplan`. `gjc team` is a native tmux runtime command and may be used only when the Team workflow explicitly requires the CLI runtime. Do NOT implement directly. The deep-interview agent is a requirements agent, not an execution agent. If oversized initial context was summarized, pass the spec and prompt-safe summary forward, not the raw oversized source material. Without explicit execution selection, stop with the spec marked `pending approval`.
550
+ **IMPORTANT:** On explicit execution selection, **MUST** use the chosen bundled GJC workflow skill entrypoint (`/skill:ralplan`, `/skill:ultragoal`, or `/skill:team`) inside the agent session. `gjc ralplan` is a native CLI that accepts the documented skill flags and seeds local `.gjc/state` receipts; agent sessions should still drive the consensus loop through `/skill:ralplan`. Implementation handoff defaults to `/skill:ultragoal`; `/skill:team` is reserved for when tmux-based interactive worker parallelization is genuinely required, and `gjc team` is a native tmux runtime command used only when the Team workflow explicitly requires the CLI runtime. Do NOT implement directly. The deep-interview agent is a requirements agent, not an execution agent. If oversized initial context was summarized, pass the spec and prompt-safe summary forward, not the raw oversized source material. Without explicit execution selection, stop with the spec marked `pending approval`.
555
551
 
556
552
  ### Phase 5b: Handoff before chain
557
553
 
@@ -577,7 +573,7 @@ Stage 1: Deep Interview Stage 2: ralplan consensus Stage 3: Separ
577
573
  ┌─────────────────────┐ ┌───────────────────────────┐ ┌──────────────────────┐
578
574
  │ Socratic Q&A │ │ Planner creates plan │ │ User chooses if/how │
579
575
  │ Ambiguity scoring │───>│ Architect reviews │───>│ execution proceeds │
580
- │ Challenge agents │ │ Critic validates │ │ via team or ultragoal
576
+ │ Challenge agents │ │ Critic validates │ │ via ultragoal (default)
581
577
  │ Spec crystallization│ │ Loop until consensus │ │ no auto-handoff │
582
578
  │ Gate: ≤<resolvedThresholdPercent> ambiguity│ │ ADR + RALPLAN-DR summary │ │ │
583
579
  └─────────────────────┘ └───────────────────────────┘ └──────────────────────┘
@@ -604,7 +600,7 @@ Skipping any stage is possible but reduces quality assurance:
604
600
  - Round 0 topology confirmation happens before ambiguity scoring; Phase 2 scoring must honor locked topology and rotate targeting across active components when more than one is present
605
601
  - Use `gjc state write` / `gjc state read` for interview state persistence; the initial and subsequent deep-interview state payloads must include `threshold_source` alongside `threshold`; do not edit `.gjc/state` directly without force override.
606
602
  - Use the GJC workflow CLI to save the final spec at `.gjc/specs/deep-interview-{slug}.md` exactly; do not use `write`, `edit`, or `ast_edit` directly on `.gjc/` paths without force override.
607
- - Use public GJC workflow entrypoints to bridge to ralplan/team only after explicit execution approval — never implement directly
603
+ - Use public GJC workflow entrypoints to bridge to ralplan, ultragoal, or team only after explicit execution approval — never implement directly. Implementation handoff defaults to ultragoal; reserve team for when tmux-based interactive worker parallelization is genuinely required.
608
604
  - Challenge agent modes are prompt injections, not separate agent spawns
609
605
  - Use internal fragment auto-modes only at their documented hooks: `auto-research-greenfield.md` between Step 2a and 2b for greenfield `research: true` questions, and `auto-answer-uncertain.md` as Step 2b′ after `ask` resolves and before scoring.
610
606
  - Fragment auto-modes are loaded on demand as `kind: "skill-fragment"`; they are not public workflow skills, not slash-command/discoverable, and not `skill://` registrations.
@@ -734,7 +730,7 @@ Why bad: 45% ambiguity means nearly half the requirements are unclear. The mathe
734
730
  - [ ] Spec includes: topology, goal, constraints, acceptance criteria, clarity breakdown, transcript
735
731
  - [ ] Execution bridge presented via the `ask` tool
736
732
  - [ ] Selected execution mode invoked via public GJC workflow entrypoint only after explicit execution approval (never direct implementation)
737
- - [ ] If 3-stage pipeline selected: ralplan --consensus --direct invoked, then stopped with the consensus plan marked `pending approval` until the user explicitly approves execution
733
+ - [ ] If 3-stage pipeline selected: `/skill:ralplan` invoked with the spec as context, then stopped with the consensus plan marked `pending approval` until the user explicitly approves execution
738
734
  - [ ] State cleaned up after approved workflow handoff
739
735
  - [ ] Brownfield confirmation questions cite repo evidence (file/path/pattern) before asking the user to decide
740
736
  - [ ] Scope-fuzzy tasks can trigger ontology-style questioning to stabilize the core entity before feature elaboration
@@ -783,7 +779,7 @@ Team routing: "Your request is quite open-ended. Would you like to run a deep in
783
779
  [Yes, interview first] [No, expand directly]
784
780
  ```
785
781
 
786
- If the user chooses interview, team routing invokes `/skill:deep-interview`. When the interview completes and the user selects "Execute with team", the spec becomes Phase 0 output and team proceeds from the approved spec.
782
+ If the user chooses interview, team routing invokes `/skill:deep-interview`. When the interview completes and the user selects an execution path (ultragoal by default, or team when tmux-based interactive parallelization is required), the spec becomes Phase 0 output and the chosen workflow proceeds from the approved spec.
787
783
 
788
784
  ## Approval-Gated Pipeline: deep-interview → ralplan → pending approval
789
785
 
@@ -794,17 +790,17 @@ The recommended refinement path chains clarity and feasibility gates, then stops
794
790
  → Socratic Q&A until ambiguity ≤ <resolvedThresholdPercent>
795
791
  → Spec written to .gjc/specs/deep-interview-{slug}.md
796
792
  → User explicitly selects "Refine with ralplan consensus"
797
- → /skill:ralplan --consensus --direct (spec as input, skip interview)
793
+ → /skill:ralplan (spec as input)
798
794
  → Planner creates implementation plan from spec
799
795
  → Architect reviews for architectural soundness
800
796
  → Critic validates quality and testability
801
797
  → Loop until consensus (max 5 iterations)
802
798
  → Consensus plan written to .gjc/plans/
803
799
  → Stop with the consensus plan marked pending approval
804
- → Only a separate explicit execution approval may invoke team or ultragoal
800
+ → Only a separate explicit execution approval may invoke execution (ultragoal by default; team only when tmux-based interactive worker parallelization is required)
805
801
  ```
806
802
 
807
- **The ralplan skill receives the spec with `--consensus --direct` flags** because the deep interview already did the requirements gathering. The `--direct` flag (supported by the ralplan skill, which ralplan aliases) skips the interview phase and goes straight to Planner → Architect → Critic consensus. The consensus plan includes:
803
+ **The ralplan skill receives the spec as context through `/skill:ralplan`** because ralplan is already the GJC Planner → Architect → Critic consensus workflow. The consensus plan includes:
808
804
  - RALPLAN-DR summary (Principles, Decision Drivers, Options)
809
805
  - ADR (Decision, Drivers, Alternatives, Why chosen, Consequences)
810
806
  - Testable acceptance criteria (inherited from deep-interview spec)
@@ -61,7 +61,7 @@ The consensus workflow:
61
61
  - Viable Options (>=2) with bounded pros/cons
62
62
  - If only one viable option remains, explicit invalidation rationale for alternatives
63
63
  - Deliberate mode only: pre-mortem (3 scenarios) + expanded test plan (unit/integration/e2e/observability)
64
- 2. **User feedback** *(--interactive only)*: If `--interactive` is set, use `AskUserQuestion` to present the draft plan **plus the Principles / Drivers / Options summary** before review (Proceed to review / Request changes / Skip review). Otherwise, automatically proceed to review.
64
+ 2. **User feedback** *(--interactive only)*: If `--interactive` is set, use the `ask` tool to present the draft plan **plus the Principles / Drivers / Options summary** before review (Proceed to review / Request changes / Skip review). Otherwise, automatically proceed to review.
65
65
  3. **Architect** reviews for architectural soundness and must provide the strongest steelman antithesis, at least one real tradeoff tension, and (when possible) synthesis — **await completion before step 4**. In deliberate mode, Architect should explicitly flag principle violations.
66
66
  - The Architect agent/subagent must persist its review with `gjc ralplan --write --stage architect --stage_n <N> --artifact "..." --json`, then return the receipt/path plus compact verdict/status (`CLEAR`/`WATCH`/`BLOCK`, `APPROVE`/`COMMENT`/`REQUEST CHANGES`) instead of pasting the full review body.
67
67
  4. **Critic** evaluates against quality criteria — run only after step 3 completes. Critic must enforce principle-option consistency, fair alternatives, risk mitigation clarity, testable acceptance criteria, and concrete verification steps. In deliberate mode, Critic must reject missing/weak pre-mortem or expanded test plan.
@@ -74,9 +74,9 @@ The consensus workflow:
74
74
  d. Return to Critic evaluation
75
75
  e. Repeat this loop until Critic returns `APPROVE` or 5 iterations are reached
76
76
  f. If 5 iterations are reached without `APPROVE`, present the best version to the user
77
- 6. On Critic approval, mark the plan `pending approval` unless explicit execution approval has already been captured, persist the ADR/final plan via `gjc ralplan --write --stage final --stage_n <N> --artifact "..."`, and do not directly edit `.gjc/plans`. *(--interactive only)* If `--interactive` is set, use `AskUserQuestion` to present the plan with approval options (Approve execution via team (Recommended) / Compact then return for execution approval / Request changes / Reject). Final plan must include ADR (Decision, Drivers, Alternatives considered, Why chosen, Consequences, Follow-ups). Otherwise, output the final plan and stop before any mutation or delegation.
78
- 7. *(--interactive only)* User chooses: Approve team execution, Request changes, or Reject
79
- 8. *(--interactive only)* On approval: invoke `/skill:team` for execution -- never implement directly
77
+ 6. On Critic approval, mark the plan `pending approval` unless explicit execution approval has already been captured, persist the ADR/final plan via `gjc ralplan --write --stage final --stage_n <N> --artifact "..."`, and do not directly edit `.gjc/plans`. *(--interactive only)* If `--interactive` is set, use the `ask` tool to present the plan with approval options (Approve execution via ultragoal (Recommended) / Approve execution via team (only when tmux-based interactive worker parallelization is required) / Compact then return for execution approval / Request changes / Reject). Final plan must include ADR (Decision, Drivers, Alternatives considered, Why chosen, Consequences, Follow-ups). Otherwise, output the final plan and stop before any mutation or delegation.
78
+ 7. *(--interactive only)* User chooses: Approve ultragoal execution (recommended), Approve team execution (tmux parallelization only), Request changes, or Reject
79
+ 8. *(--interactive only)* On approval: invoke `/skill:ultragoal` for execution by default; invoke `/skill:team` only when the user explicitly needs tmux-based interactive worker parallelization -- never implement directly
80
80
 
81
81
  Before invoking `/skill:team` or `/skill:ultragoal`, mark ralplan ready for handoff so the skill tool's chain guard permits the transition:
82
82
 
@@ -121,7 +121,7 @@ Set `--planner-resumable true` only when the parent session is provably persiste
121
121
 
122
122
  ### Why the Gate Exists
123
123
 
124
- Execution modes (team, team, team, team, team) spin up heavy multi-agent orchestration. When launched on a vague request like "team improve the app", agents have no clear target — they waste cycles on scope discovery that should happen during planning, often delivering partial or misaligned work that requires rework.
124
+ Execution skills (`ultragoal` and `team`) drive implementation rather than scope discovery. When launched on a vague request like "team improve the app", agents have no clear target — they waste cycles on scope discovery that should happen during planning, often delivering partial or misaligned work that requires rework.
125
125
 
126
126
  The ralplan-first gate intercepts underspecified execution requests and redirects them through the ralplan consensus planning workflow. This ensures:
127
127
  - **Explicit scope**: A PRD defines exactly what will be built
@@ -177,8 +177,8 @@ The gate auto-passes when it detects **any** concrete signal. You do not need al
177
177
  - **Architect** reviews for soundness
178
178
  - **Critic** validates quality and testability
179
179
  5. On consensus approval, user chooses execution path:
180
- - **team**: parallel coordinated agents (recommended)
181
- - **team**: sequential execution with verification
180
+ - **ultragoal**: goal-tracked autonomous execution with verification (recommended default)
181
+ - **team**: N coordinated parallel agents in tmux — only when tmux-based interactive worker parallelization is required
182
182
  6. Execution begins with a clear, bounded plan
183
183
 
184
184
  ### Troubleshooting
@@ -13,6 +13,7 @@ import { type Skill, skillCapability } from "../capability/skill";
13
13
  import { type SlashCommand, slashCommandCapability } from "../capability/slash-command";
14
14
  import { type CustomTool, toolCapability } from "../capability/tool";
15
15
  import type { LoadContext, LoadResult } from "../capability/types";
16
+ import { rootContainsGjcManifest } from "../extensibility/gjc-plugins";
16
17
  import {
17
18
  type ClaudePluginRoot,
18
19
  createSourceMeta,
@@ -91,6 +92,25 @@ async function resolvePluginDir(
91
92
  };
92
93
  }
93
94
 
95
+ async function listNonGjcPluginRoots(
96
+ home: string,
97
+ cwd: string,
98
+ ): Promise<{ roots: ClaudePluginRoot[]; warnings: string[] }> {
99
+ const { roots, warnings } = await listClaudePluginRoots(home, cwd);
100
+ const filteredRoots: ClaudePluginRoot[] = [];
101
+ const filteredWarnings = [...warnings];
102
+
103
+ for (const root of roots) {
104
+ if (await rootContainsGjcManifest(root.path)) {
105
+ filteredWarnings.push(`[claude-plugins] Skipping gajae-code plugin root (binding-only): ${root.path}`);
106
+ continue;
107
+ }
108
+ filteredRoots.push(root);
109
+ }
110
+
111
+ return { roots: filteredRoots, warnings: filteredWarnings };
112
+ }
113
+
94
114
  // =============================================================================
95
115
  // Skills
96
116
  // =============================================================================
@@ -99,7 +119,7 @@ async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
99
119
  const items: Skill[] = [];
100
120
  const warnings: string[] = [];
101
121
 
102
- const { roots, warnings: rootWarnings } = await listClaudePluginRoots(ctx.home, ctx.cwd);
122
+ const { roots, warnings: rootWarnings } = await listNonGjcPluginRoots(ctx.home, ctx.cwd);
103
123
  warnings.push(...rootWarnings);
104
124
 
105
125
  const results = await Promise.all(
@@ -134,7 +154,7 @@ async function loadSlashCommands(ctx: LoadContext): Promise<LoadResult<SlashComm
134
154
  const items: SlashCommand[] = [];
135
155
  const warnings: string[] = [];
136
156
 
137
- const { roots, warnings: rootWarnings } = await listClaudePluginRoots(ctx.home, ctx.cwd);
157
+ const { roots, warnings: rootWarnings } = await listNonGjcPluginRoots(ctx.home, ctx.cwd);
138
158
  warnings.push(...rootWarnings);
139
159
 
140
160
  const results = await Promise.all(
@@ -174,7 +194,7 @@ async function loadHooks(ctx: LoadContext): Promise<LoadResult<Hook>> {
174
194
  const items: Hook[] = [];
175
195
  const warnings: string[] = [];
176
196
 
177
- const { roots, warnings: rootWarnings } = await listClaudePluginRoots(ctx.home, ctx.cwd);
197
+ const { roots, warnings: rootWarnings } = await listNonGjcPluginRoots(ctx.home, ctx.cwd);
178
198
  warnings.push(...rootWarnings);
179
199
 
180
200
  const hookTypes = ["pre", "post"] as const;
@@ -221,7 +241,7 @@ async function loadTools(ctx: LoadContext): Promise<LoadResult<CustomTool>> {
221
241
  const items: CustomTool[] = [];
222
242
  const warnings: string[] = [];
223
243
 
224
- const { roots, warnings: rootWarnings } = await listClaudePluginRoots(ctx.home, ctx.cwd);
244
+ const { roots, warnings: rootWarnings } = await listNonGjcPluginRoots(ctx.home, ctx.cwd);
225
245
  warnings.push(...rootWarnings);
226
246
 
227
247
  const results = await Promise.all(
@@ -258,7 +278,7 @@ async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>>
258
278
  const items: MCPServer[] = [];
259
279
  const warnings: string[] = [];
260
280
 
261
- const { roots, warnings: rootWarnings } = await listClaudePluginRoots(ctx.home, ctx.cwd);
281
+ const { roots, warnings: rootWarnings } = await listNonGjcPluginRoots(ctx.home, ctx.cwd);
262
282
  warnings.push(...rootWarnings);
263
283
 
264
284
  for (const root of roots) {
package/src/edit/diff.ts CHANGED
@@ -4,6 +4,8 @@
4
4
  * Provides diff string generation and the replace-mode edit logic
5
5
  * used when not in patch mode.
6
6
  */
7
+
8
+ import { createRequire } from "node:module";
7
9
  import * as Diff from "diff";
8
10
  import { resolveToCwd } from "../tools/path-utils";
9
11
  import { DEFAULT_FUZZY_THRESHOLD, EditMatchError, findMatch } from "./modes/replace";
@@ -54,12 +56,73 @@ function formatNumberedDiffLine(prefix: "+" | "-" | " ", lineNum: number, conten
54
56
  return `${prefix}${lineNum}|${content}`;
55
57
  }
56
58
 
59
+ type DiffLinePart = {
60
+ added?: boolean;
61
+ removed?: boolean;
62
+ value: string;
63
+ };
64
+
65
+ type DiffLinesFn = (oldStr: string, newStr: string) => DiffLinePart[];
66
+
67
+ const require = createRequire(import.meta.url);
68
+ const DIFF_LINES_TEST_OVERRIDE_UNSET = Symbol("DIFF_LINES_TEST_OVERRIDE_UNSET");
69
+
70
+ let cachedNativeDiffLines: DiffLinesFn | null | undefined;
71
+ let diffLinesTestOverride: DiffLinesFn | null | typeof DIFF_LINES_TEST_OVERRIDE_UNSET = DIFF_LINES_TEST_OVERRIDE_UNSET;
72
+
73
+ function resolveNativeDiffLines(): DiffLinesFn | undefined {
74
+ if (diffLinesTestOverride !== DIFF_LINES_TEST_OVERRIDE_UNSET) {
75
+ return diffLinesTestOverride ?? undefined;
76
+ }
77
+
78
+ if (cachedNativeDiffLines !== undefined) {
79
+ return cachedNativeDiffLines ?? undefined;
80
+ }
81
+
82
+ try {
83
+ const natives = require("@gajae-code/natives") as { diffLines?: unknown };
84
+ cachedNativeDiffLines = typeof natives.diffLines === "function" ? (natives.diffLines as DiffLinesFn) : null;
85
+ } catch {
86
+ cachedNativeDiffLines = null;
87
+ }
88
+
89
+ return cachedNativeDiffLines ?? undefined;
90
+ }
91
+
92
+ function diffLinesWithFallback(oldContent: string, newContent: string): DiffLinePart[] {
93
+ const nativeDiffLines = resolveNativeDiffLines();
94
+ if (nativeDiffLines) {
95
+ try {
96
+ return nativeDiffLines(oldContent, newContent);
97
+ } catch {
98
+ // Fall through to the JS implementation if the native export fails at runtime.
99
+ }
100
+ }
101
+
102
+ return Diff.diffLines(oldContent, newContent);
103
+ }
104
+
105
+ export function __setDiffLinesForTest(diffLines: DiffLinesFn | null): void {
106
+ diffLinesTestOverride = diffLines;
107
+ }
108
+
109
+ export function __clearDiffLinesForTest(): void {
110
+ diffLinesTestOverride = DIFF_LINES_TEST_OVERRIDE_UNSET;
111
+ cachedNativeDiffLines = undefined;
112
+ }
113
+
114
+ export function __getNativeDiffLinesForTest(): DiffLinesFn | undefined {
115
+ return resolveNativeDiffLines();
116
+ }
117
+
57
118
  /**
58
119
  * Generate a unified diff string with line numbers and context.
59
120
  * Returns both the diff string and the first changed line number (in the new file).
60
121
  */
61
122
  export function generateDiffString(oldContent: string, newContent: string, contextLines = 4): DiffResult {
62
- const parts = Diff.diffLines(oldContent, newContent);
123
+ // Native line diff (Rust port of jsdiff `Diff.diffLines`, byte-identical
124
+ // output) — avoids the pure-JS Myers blowup (>1s on ~1MB files).
125
+ const parts = diffLinesWithFallback(oldContent, newContent);
63
126
  const output: string[] = [];
64
127
 
65
128
  let oldLineNum = 1;
@@ -21,6 +21,40 @@ import {
21
21
  restoreLineEndings,
22
22
  stripBom,
23
23
  } from "../normalize";
24
+
25
+ type NativeBestFuzzyMatchResult = {
26
+ best?: FuzzyMatch;
27
+ aboveThresholdCount: number;
28
+ secondBestScore: number;
29
+ };
30
+
31
+ type NativeSequenceFuzzyResult = {
32
+ index?: number;
33
+ confidence: number;
34
+ matchCount: number;
35
+ matchIndices: number[];
36
+ secondBestScore: number;
37
+ };
38
+
39
+ let scoreSequenceFuzzyNative:
40
+ | ((lines: string[], pattern: string[], start: number, eof: boolean) => NativeSequenceFuzzyResult)
41
+ | undefined;
42
+ let findBestFuzzyMatchNative:
43
+ | ((content: string, target: string, threshold: number) => NativeBestFuzzyMatchResult)
44
+ | undefined;
45
+ void import("../../../../natives/native/index.js")
46
+ .then(mod => {
47
+ if (typeof mod.h02ScoreSequenceFuzzy === "function") {
48
+ scoreSequenceFuzzyNative = mod.h02ScoreSequenceFuzzy;
49
+ }
50
+ if (typeof mod.h01FindBestFuzzyMatch === "function") {
51
+ findBestFuzzyMatchNative = mod.h01FindBestFuzzyMatch;
52
+ }
53
+ })
54
+ .catch(() => {
55
+ // Native unavailable; fuzzy matching uses the TS fallback.
56
+ });
57
+
24
58
  import { readEditFileText, serializeEditFileText } from "../read-file";
25
59
  import type { EditToolDetails, LspBatchRequest } from "../renderer";
26
60
 
@@ -444,7 +478,7 @@ function findBestFuzzyMatchCore(
444
478
  return { best, aboveThresholdCount, secondBestScore };
445
479
  }
446
480
 
447
- function findBestFuzzyMatch(content: string, target: string, threshold: number): BestFuzzyMatchResult {
481
+ export function findBestFuzzyMatch(content: string, target: string, threshold: number): BestFuzzyMatchResult {
448
482
  const contentLines = content.split("\n");
449
483
  const targetLines = target.split("\n");
450
484
 
@@ -489,7 +523,8 @@ export function findMatch(
489
523
 
490
524
  // Try fuzzy match
491
525
  const threshold = options.threshold ?? DEFAULT_FUZZY_THRESHOLD;
492
- const { best, aboveThresholdCount, secondBestScore } = findBestFuzzyMatch(content, target, threshold);
526
+ const { best, aboveThresholdCount, secondBestScore } =
527
+ findBestFuzzyMatchNative?.(content, target, threshold) ?? findBestFuzzyMatch(content, target, threshold);
493
528
 
494
529
  if (!best) {
495
530
  return {};
@@ -682,6 +717,29 @@ export function seekSequence(
682
717
  return { index: undefined, confidence: 0 };
683
718
  }
684
719
 
720
+ const nativeFuzzyResult = scoreSequenceFuzzyNative?.(lines, pattern, start, eof);
721
+ if (nativeFuzzyResult?.index !== undefined && nativeFuzzyResult.confidence >= SEQUENCE_FUZZY_THRESHOLD) {
722
+ if (
723
+ nativeFuzzyResult.matchCount > 1 &&
724
+ nativeFuzzyResult.confidence >= DOMINANT_FUZZY_MIN_CONFIDENCE &&
725
+ nativeFuzzyResult.confidence - nativeFuzzyResult.secondBestScore >= DOMINANT_FUZZY_DELTA
726
+ ) {
727
+ return {
728
+ index: nativeFuzzyResult.index,
729
+ confidence: nativeFuzzyResult.confidence,
730
+ matchCount: 1,
731
+ matchIndices: nativeFuzzyResult.matchIndices,
732
+ strategy: "fuzzy-dominant",
733
+ };
734
+ }
735
+ return {
736
+ index: nativeFuzzyResult.index,
737
+ confidence: nativeFuzzyResult.confidence,
738
+ matchCount: nativeFuzzyResult.matchCount,
739
+ matchIndices: nativeFuzzyResult.matchIndices,
740
+ strategy: "fuzzy",
741
+ };
742
+ }
685
743
  // Pass 7: Fuzzy matching - find best match above threshold
686
744
  let bestScore = 0;
687
745
  let secondBestScore = 0;
@@ -0,0 +1,87 @@
1
+ import { logger } from "@gajae-code/utils";
2
+ import { loadGjcPlugins } from "./loader";
3
+ import { discoverGjcPluginRoots } from "./paths";
4
+ import { GjcPluginLoadError, type LoadedGjcPlugin, type LoadedSubskillActivation } from "./types";
5
+
6
+ export interface SubskillActivationResult {
7
+ cleanedArgs: string;
8
+ activation?: LoadedSubskillActivation;
9
+ activeSubskillsToPersist: LoadedSubskillActivation[];
10
+ }
11
+
12
+ export async function resolveSubskillActivationForSkillInvocation(input: {
13
+ cwd: string;
14
+ sessionId?: string;
15
+ threadId?: string;
16
+ turnId?: string;
17
+ skillName: string;
18
+ args: string;
19
+ }): Promise<SubskillActivationResult> {
20
+ const roots = await discoverGjcPluginRoots({ cwd: input.cwd });
21
+ let plugins: LoadedGjcPlugin[];
22
+ try {
23
+ plugins = await loadGjcPlugins(roots);
24
+ } catch (error) {
25
+ if (error instanceof GjcPluginLoadError) throw error;
26
+ logger.warn("Skipping GJC plugin activation set after load error", {
27
+ error: error instanceof Error ? error.message : String(error),
28
+ });
29
+ plugins = [];
30
+ }
31
+
32
+ const bindings = plugins.flatMap(plugin => plugin.bindings);
33
+ const activationsByArg = new Map<string, LoadedSubskillActivation>();
34
+ for (const binding of bindings) {
35
+ if (binding.parent !== input.skillName) continue;
36
+ activationsByArg.set(binding.activationArg, {
37
+ activationArg: binding.activationArg,
38
+ plugin: binding.plugin,
39
+ subskillName: binding.subskillName,
40
+ parent: binding.parent,
41
+ bindsTo: binding.bindsTo,
42
+ phase: binding.phase,
43
+ filePath: binding.filePath,
44
+ toolPaths: binding.toolPaths,
45
+ });
46
+ }
47
+
48
+ const tokens = input.args
49
+ .trim()
50
+ .split(/\s+/)
51
+ .filter(token => token.length > 0);
52
+ let activation: LoadedSubskillActivation | undefined;
53
+ const cleanedTokens: string[] = [];
54
+ let consumed = false;
55
+ for (const token of tokens) {
56
+ if (!consumed && token.startsWith("--") && !token.includes("=")) {
57
+ const candidate = activationsByArg.get(token.slice(2));
58
+ if (candidate) {
59
+ activation = candidate;
60
+ consumed = true;
61
+ continue;
62
+ }
63
+ }
64
+ cleanedTokens.push(token);
65
+ }
66
+
67
+ return {
68
+ cleanedArgs: consumed ? cleanedTokens.join(" ") : input.args,
69
+ activation,
70
+ activeSubskillsToPersist: activation
71
+ ? bindings
72
+ .filter(
73
+ binding => binding.plugin === activation.plugin && binding.activationArg === activation.activationArg,
74
+ )
75
+ .map(binding => ({
76
+ activationArg: binding.activationArg,
77
+ plugin: binding.plugin,
78
+ subskillName: binding.subskillName,
79
+ parent: binding.parent,
80
+ bindsTo: binding.bindsTo,
81
+ phase: binding.phase,
82
+ filePath: binding.filePath,
83
+ toolPaths: binding.toolPaths,
84
+ }))
85
+ : [],
86
+ };
87
+ }
@@ -0,0 +1,9 @@
1
+ export * from "./activation";
2
+ export * from "./injection";
3
+ export * from "./loader";
4
+ export * from "./paths";
5
+ export * from "./schema";
6
+ export * from "./state";
7
+ export * from "./tools";
8
+ export * from "./types";
9
+ export * from "./validation";
@@ -0,0 +1,114 @@
1
+ import { readVisibleSkillActiveState } from "../../skill-state/active-state";
2
+ import { initialPhaseForSkill } from "../../skill-state/initial-phase";
3
+ import { readActiveSubskillsForParent } from "./state";
4
+ import { GJC_SUBSKILL_PARENT_AGENTS, type LoadedSubskillActivation } from "./types";
5
+
6
+ export async function readSubskillBody(filePath: string): Promise<string> {
7
+ const content = await Bun.file(filePath).text();
8
+ return content.replace(/^---\n[\s\S]*?\n---\n/, "").trim();
9
+ }
10
+
11
+ function escapeAttribute(value: string): string {
12
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
13
+ }
14
+
15
+ export function wrapSubskillBlock(
16
+ activation: {
17
+ plugin: string;
18
+ subskillName: string;
19
+ parent: string;
20
+ phase: string;
21
+ activationArg: string;
22
+ filePath: string;
23
+ },
24
+ body: string,
25
+ ): string {
26
+ return `\n\n---\n\n<gjc-subskill plugin="${escapeAttribute(activation.plugin)}" name="${escapeAttribute(activation.subskillName)}" parent="${escapeAttribute(activation.parent)}" phase="${escapeAttribute(activation.phase)}" arg="${escapeAttribute(activation.activationArg)}">\n${body}\n</gjc-subskill>`;
27
+ }
28
+
29
+ export async function resolveCurrentPhaseForParent(input: {
30
+ cwd: string;
31
+ sessionId?: string;
32
+ parent: string;
33
+ explicitPhase?: string;
34
+ }): Promise<string> {
35
+ const explicitPhase = input.explicitPhase?.trim();
36
+ if (explicitPhase) return explicitPhase;
37
+
38
+ const state = await readVisibleSkillActiveState(input.cwd, input.sessionId);
39
+ const persistedPhase = state?.active_skills?.find(entry => entry.skill === input.parent)?.phase?.trim();
40
+ if (persistedPhase) return persistedPhase;
41
+
42
+ if (state?.skill === input.parent) {
43
+ const statePhase = state.phase?.trim();
44
+ if (statePhase) return statePhase;
45
+ }
46
+
47
+ return initialPhaseForSkill(input.parent);
48
+ }
49
+
50
+ export async function buildSubskillInjection(input: {
51
+ cwd: string;
52
+ sessionId?: string;
53
+ skillName: string;
54
+ activation?: LoadedSubskillActivation;
55
+ currentPhase?: string;
56
+ }): Promise<{ block: string; details?: LoadedSubskillActivation } | null> {
57
+ const resolvedPhase = await resolveCurrentPhaseForParent({
58
+ cwd: input.cwd,
59
+ sessionId: input.sessionId,
60
+ parent: input.skillName,
61
+ explicitPhase: input.currentPhase,
62
+ });
63
+
64
+ const directActivation = input.activation;
65
+ if (directActivation?.parent === input.skillName && directActivation.phase === resolvedPhase) {
66
+ const body = await readSubskillBody(directActivation.filePath);
67
+ return { block: wrapSubskillBlock(directActivation, body), details: directActivation };
68
+ }
69
+
70
+ const [entry] = await readActiveSubskillsForParent({
71
+ cwd: input.cwd,
72
+ sessionId: input.sessionId,
73
+ parent: input.skillName,
74
+ phase: resolvedPhase,
75
+ });
76
+ if (!entry) return null;
77
+
78
+ const activation: LoadedSubskillActivation = {
79
+ plugin: entry.plugin,
80
+ subskillName: entry.subskillName,
81
+ parent: entry.parent,
82
+ bindsTo: entry.bindsTo,
83
+ phase: entry.phase,
84
+ activationArg: entry.activationArg,
85
+ filePath: entry.filePath,
86
+ toolPaths: entry.toolPaths,
87
+ };
88
+ const body = await readSubskillBody(activation.filePath);
89
+ return { block: wrapSubskillBlock(activation, body), details: activation };
90
+ }
91
+
92
+ export async function buildAgentSubskillInjection(input: {
93
+ cwd: string;
94
+ sessionId?: string;
95
+ agentName: string;
96
+ }): Promise<string> {
97
+ if (!(GJC_SUBSKILL_PARENT_AGENTS as readonly string[]).includes(input.agentName)) return "";
98
+
99
+ const entries = await readActiveSubskillsForParent({
100
+ cwd: input.cwd,
101
+ sessionId: input.sessionId,
102
+ parent: input.agentName,
103
+ phase: "prompt",
104
+ });
105
+ if (entries.length === 0) return "";
106
+
107
+ const blocks = await Promise.all(
108
+ entries.map(async entry => {
109
+ const body = await readSubskillBody(entry.filePath);
110
+ return wrapSubskillBlock(entry, body);
111
+ }),
112
+ );
113
+ return blocks.join("");
114
+ }