@gajae-code/coding-agent 0.11.7 → 0.11.9

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 (213) hide show
  1. package/CHANGELOG.md +73 -1
  2. package/dist/types/cli/args.d.ts +0 -2
  3. package/dist/types/cli.d.ts +1 -0
  4. package/dist/types/config/keybindings.d.ts +33 -3
  5. package/dist/types/config/model-profile-activation.d.ts +4 -3
  6. package/dist/types/config/model-profile-contract.d.ts +38 -0
  7. package/dist/types/config/model-profiles.d.ts +2 -3
  8. package/dist/types/config/settings-schema.d.ts +15 -2
  9. package/dist/types/edit/modes/patch.d.ts +13 -0
  10. package/dist/types/edit/path-mutation-lock.d.ts +15 -0
  11. package/dist/types/eval/py/executor.d.ts +1 -1
  12. package/dist/types/eval/py/tool-bridge.d.ts +1 -2
  13. package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +12 -5
  14. package/dist/types/gjc-runtime/deep-interview-draft.d.ts +8 -0
  15. package/dist/types/gjc-runtime/deep-interview-payload.d.ts +41 -0
  16. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +32 -17
  17. package/dist/types/gjc-runtime/deep-interview-repair.d.ts +17 -0
  18. package/dist/types/gjc-runtime/deep-interview-state.d.ts +135 -4
  19. package/dist/types/gjc-runtime/repository-binding.d.ts +63 -0
  20. package/dist/types/gjc-runtime/state-writer.d.ts +44 -0
  21. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +1 -0
  22. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +24 -0
  23. package/dist/types/gjc-runtime/workflow-manifest.d.ts +5 -3
  24. package/dist/types/main.d.ts +8 -0
  25. package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +2 -1
  26. package/dist/types/modes/components/tool-status-header.d.ts +3 -2
  27. package/dist/types/modes/components/welcome.d.ts +4 -0
  28. package/dist/types/modes/controllers/command-controller.d.ts +2 -0
  29. package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
  30. package/dist/types/modes/interactive-mode.d.ts +5 -2
  31. package/dist/types/modes/prompt-action-autocomplete.d.ts +6 -1
  32. package/dist/types/modes/prompt-suggestion-controller.d.ts +35 -0
  33. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +2 -2
  34. package/dist/types/modes/types.d.ts +3 -0
  35. package/dist/types/modes/utils/hotkeys-markdown.d.ts +2 -1
  36. package/dist/types/runtime-mcp/manager.d.ts +1 -1
  37. package/dist/types/runtime-mcp/oauth-flow.d.ts +0 -5
  38. package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +14 -0
  39. package/dist/types/runtime-mcp/redaction.d.ts +2 -0
  40. package/dist/types/sdk/broker/broker.d.ts +2 -0
  41. package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
  42. package/dist/types/sdk/bus/chat-daemon-control.d.ts +7 -1
  43. package/dist/types/sdk/bus/config-commands.d.ts +2 -2
  44. package/dist/types/sdk/bus/config.d.ts +2 -0
  45. package/dist/types/sdk/bus/conversation-store.d.ts +1 -0
  46. package/dist/types/sdk/bus/index.d.ts +25 -0
  47. package/dist/types/sdk/bus/notification-orchestration.d.ts +1 -0
  48. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +105 -0
  49. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +15 -3
  50. package/dist/types/sdk/bus/telegram-daemon-control.d.ts +5 -1
  51. package/dist/types/sdk/bus/telegram-daemon.d.ts +128 -1
  52. package/dist/types/sdk/bus/telegram-reference.d.ts +2 -0
  53. package/dist/types/sdk/host/control/operations.d.ts +1 -1
  54. package/dist/types/sdk/host/host.d.ts +1 -1
  55. package/dist/types/sdk/host/query/handlers.d.ts +9 -0
  56. package/dist/types/sdk/index.d.ts +2 -0
  57. package/dist/types/sdk/prompt-status.d.ts +88 -0
  58. package/dist/types/sdk/session-directory.d.ts +1 -1
  59. package/dist/types/sdk/startup-capability.d.ts +3 -0
  60. package/dist/types/session/agent-session.d.ts +2 -0
  61. package/dist/types/session/internal/managed-session-scope.d.ts +22 -3
  62. package/dist/types/session/internal/managed-session-storage.d.ts +24 -4
  63. package/dist/types/session/internal/native-publish-outcome.d.ts +48 -0
  64. package/dist/types/session/session-manager.d.ts +5 -1
  65. package/dist/types/session/session-storage.d.ts +31 -0
  66. package/dist/types/skill-state/active-state.d.ts +4 -0
  67. package/dist/types/task/index.d.ts +3 -1
  68. package/dist/types/task/receipt.d.ts +2 -0
  69. package/dist/types/task/types.d.ts +94 -0
  70. package/dist/types/tools/ask.d.ts +454 -174
  71. package/dist/types/tools/image-gen.d.ts +47 -1
  72. package/dist/types/tools/index.d.ts +4 -0
  73. package/dist/types/utils/prompt-suggestion.d.ts +33 -0
  74. package/package.json +8 -8
  75. package/scripts/capture-platform-shortcut-labels-showcase.ts +342 -0
  76. package/scripts/dogfood-repository-binding.ts +209 -0
  77. package/scripts/generate-sdk-operation-inventory.ts +2 -0
  78. package/scripts/run-test-manifest.ts +4 -4
  79. package/src/cli/args.ts +2 -92
  80. package/src/cli/mcp-cli.ts +4 -21
  81. package/src/cli/notify-cli.ts +5 -38
  82. package/src/cli.ts +50 -0
  83. package/src/commands/deep-interview.ts +9 -4
  84. package/src/commands/sdk.ts +2 -2
  85. package/src/commit/agentic/index.ts +3 -3
  86. package/src/commit/map-reduce/index.ts +2 -2
  87. package/src/config/keybindings.ts +157 -24
  88. package/src/config/model-profile-activation.ts +7 -18
  89. package/src/config/model-profile-contract.ts +161 -0
  90. package/src/config/model-profiles.ts +2 -3
  91. package/src/config/model-registry.ts +11 -1
  92. package/src/config/settings-schema.ts +17 -2
  93. package/src/coordinator-mcp/model-preset.ts +21 -74
  94. package/src/defaults/gjc/skills/deep-interview/SKILL.md +80 -165
  95. package/src/defaults/gjc/skills/ralplan/SKILL.md +3 -1
  96. package/src/edit/index.ts +3 -6
  97. package/src/edit/modes/patch.ts +38 -1
  98. package/src/edit/modes/replace.ts +42 -0
  99. package/src/edit/path-mutation-lock.ts +112 -0
  100. package/src/eval/py/executor.ts +30 -11
  101. package/src/eval/py/prelude.py +2 -2
  102. package/src/eval/py/tool-bridge.ts +34 -18
  103. package/src/extensibility/gjc-plugins/runtime-adapters.ts +2 -1
  104. package/src/gjc-runtime/deep-interview-ambiguity.ts +68 -47
  105. package/src/gjc-runtime/deep-interview-draft.ts +842 -0
  106. package/src/gjc-runtime/deep-interview-payload.ts +262 -0
  107. package/src/gjc-runtime/deep-interview-recorder.ts +294 -226
  108. package/src/gjc-runtime/deep-interview-repair.ts +1674 -0
  109. package/src/gjc-runtime/deep-interview-runtime.ts +264 -103
  110. package/src/gjc-runtime/deep-interview-state.ts +916 -4
  111. package/src/gjc-runtime/launch-tmux.ts +1 -1
  112. package/src/gjc-runtime/managed-owner-supervisor.ts +83 -4
  113. package/src/gjc-runtime/ralplan-runtime.ts +78 -4
  114. package/src/gjc-runtime/repository-binding.ts +267 -0
  115. package/src/gjc-runtime/session-state-sidecar.ts +25 -9
  116. package/src/gjc-runtime/state-writer.ts +166 -2
  117. package/src/gjc-runtime/tmux-owner-isolation.ts +2 -1
  118. package/src/gjc-runtime/tmux-sessions.ts +45 -6
  119. package/src/gjc-runtime/ultragoal-guard.ts +5 -5
  120. package/src/gjc-runtime/ultragoal-runtime.ts +181 -19
  121. package/src/gjc-runtime/workflow-manifest.generated.json +386 -1
  122. package/src/gjc-runtime/workflow-manifest.ts +191 -5
  123. package/src/internal-urls/docs-index.generated.ts +8 -6
  124. package/src/internal-urls/local-protocol.ts +33 -16
  125. package/src/internal-urls/mcp-protocol.ts +73 -11
  126. package/src/main.ts +25 -10
  127. package/src/modes/DESIGN.md +5 -0
  128. package/src/modes/acp/acp-agent.ts +50 -16
  129. package/src/modes/components/model-selector.ts +42 -8
  130. package/src/modes/components/plan-preview-overlay.ts +1 -1
  131. package/src/modes/components/runtime-mcp-add-wizard.ts +5 -0
  132. package/src/modes/components/tool-status-header.ts +20 -4
  133. package/src/modes/components/welcome.ts +32 -20
  134. package/src/modes/controllers/command-controller.ts +97 -54
  135. package/src/modes/controllers/event-controller.ts +10 -0
  136. package/src/modes/controllers/input-controller.ts +7 -2
  137. package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -6
  138. package/src/modes/controllers/selector-controller.ts +46 -54
  139. package/src/modes/interactive-mode.ts +70 -36
  140. package/src/modes/prompt-action-autocomplete.ts +32 -13
  141. package/src/modes/prompt-suggestion-controller.ts +96 -0
  142. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +2 -2
  143. package/src/modes/types.ts +3 -0
  144. package/src/modes/utils/hotkeys-markdown.ts +64 -36
  145. package/src/modes/utils/ui-helpers.ts +5 -3
  146. package/src/prompts/agents/architect.md +0 -1
  147. package/src/prompts/agents/critic.md +0 -1
  148. package/src/prompts/system/prompt-suggestion-system.md +32 -0
  149. package/src/prompts/system/system-prompt.md +7 -4
  150. package/src/prompts/tools/cron.md +3 -1
  151. package/src/runtime-mcp/json-rpc.ts +7 -4
  152. package/src/runtime-mcp/manager.ts +51 -53
  153. package/src/runtime-mcp/oauth-flow.ts +0 -72
  154. package/src/runtime-mcp/plugin-network-boundary.ts +101 -0
  155. package/src/runtime-mcp/redaction.ts +30 -0
  156. package/src/runtime-mcp/transports/http.ts +13 -6
  157. package/src/sdk/broker/broker.ts +34 -20
  158. package/src/sdk/broker/lifecycle.ts +185 -36
  159. package/src/sdk/broker/session-index.ts +11 -1
  160. package/src/sdk/bus/chat-daemon-control.ts +20 -5
  161. package/src/sdk/bus/config-commands.ts +2 -2
  162. package/src/sdk/bus/config.ts +9 -1
  163. package/src/sdk/bus/conversation-store.ts +40 -6
  164. package/src/sdk/bus/index.ts +240 -65
  165. package/src/sdk/bus/lifecycle-commands.ts +47 -3
  166. package/src/sdk/bus/lifecycle-control-runtime.ts +11 -2
  167. package/src/sdk/bus/lifecycle-orchestrator.ts +32 -1
  168. package/src/sdk/bus/notification-orchestration.ts +12 -0
  169. package/src/sdk/bus/notification-service.ts +27 -2
  170. package/src/sdk/bus/prompt-reconciliation.ts +239 -0
  171. package/src/sdk/bus/recent-activity.ts +90 -26
  172. package/src/sdk/bus/telegram-daemon-contract.ts +15 -3
  173. package/src/sdk/bus/telegram-daemon-control.ts +145 -33
  174. package/src/sdk/bus/telegram-daemon.ts +853 -112
  175. package/src/sdk/bus/telegram-reference.ts +20 -2
  176. package/src/sdk/cli/session-cli.ts +15 -2
  177. package/src/sdk/host/control/dispatch.ts +1 -1
  178. package/src/sdk/host/control/operations.ts +1 -1
  179. package/src/sdk/host/host.ts +2 -6
  180. package/src/sdk/host/query/handlers.ts +70 -5
  181. package/src/sdk/index.ts +12 -0
  182. package/src/sdk/prompt-status.ts +97 -0
  183. package/src/sdk/protocol/operation-inventory.generated.json +47 -0
  184. package/src/sdk/protocol/operation-registry.ts +18 -4
  185. package/src/sdk/session-directory.ts +5 -1
  186. package/src/sdk/session.ts +2 -0
  187. package/src/sdk/startup-capability.ts +9 -1
  188. package/src/session/agent-session.ts +138 -5
  189. package/src/session/blob-store.ts +7 -2
  190. package/src/session/internal/managed-session-scope.ts +998 -133
  191. package/src/session/internal/managed-session-storage.ts +268 -203
  192. package/src/session/internal/native-publish-outcome.ts +266 -0
  193. package/src/session/session-manager.ts +406 -360
  194. package/src/session/session-storage.ts +151 -11
  195. package/src/skill-state/active-state.ts +10 -0
  196. package/src/task/executor.ts +13 -5
  197. package/src/task/gjc-command.ts +2 -2
  198. package/src/task/index.ts +121 -15
  199. package/src/task/receipt.ts +3 -0
  200. package/src/task/types.ts +34 -3
  201. package/src/tools/ask.ts +199 -88
  202. package/src/tools/ast-edit.ts +2 -2
  203. package/src/tools/bash-pty-selection.ts +2 -2
  204. package/src/tools/browser/screenshot-format.ts +6 -1
  205. package/src/tools/cron.ts +1 -1
  206. package/src/tools/image-gen.ts +178 -6
  207. package/src/tools/index.ts +4 -0
  208. package/src/tools/render-utils.ts +7 -1
  209. package/src/utils/edit-mode.ts +2 -2
  210. package/src/utils/prompt-suggestion.ts +265 -0
  211. package/src/utils/sixel.ts +3 -3
  212. package/src/web/insane/url-guard.ts +3 -1
  213. package/src/web/scrapers/docs-rs.ts +30 -5
@@ -3,9 +3,11 @@ import * as fs from "node:fs/promises";
3
3
  import * as os from "node:os";
4
4
  import * as path from "node:path";
5
5
  import { YAML } from "bun";
6
- import { syncSkillActiveState } from "../skill-state/active-state";
7
- import { deriveDeepInterviewHud } from "../skill-state/workflow-hud";
8
6
  import { WORKFLOW_STATE_VERSION } from "../skill-state/workflow-state-contract";
7
+ import { scoreToUnits } from "./deep-interview-ambiguity";
8
+ import { runDeepInterviewDraftCommand } from "./deep-interview-draft";
9
+ import { runDeepInterviewPostCommitEffects } from "./deep-interview-recorder";
10
+ import { isDeepInterviewRepairVerb, runDeepInterviewRepairCommand } from "./deep-interview-repair";
9
11
  import {
10
12
  assertDeepInterviewInputWithinLimit,
11
13
  assertDeepInterviewIntentReview,
@@ -17,12 +19,20 @@ import {
17
19
  MAX_INITIAL_CONTEXT_LENGTH,
18
20
  normalizeDeepInterviewEnvelope,
19
21
  reviewDeepInterviewIntent,
22
+ validateDeepInterviewV1Envelope,
20
23
  } from "./deep-interview-state";
21
24
  import { runNativeRalplanCommand } from "./ralplan-runtime";
22
25
  import { modeStatePath, sessionSpecsDir } from "./session-layout";
23
- import { resolveGjcSessionForWrite, writeSessionActivityMarker } from "./session-resolution";
26
+ import { resolveGjcSessionForWrite } from "./session-resolution";
24
27
  import { runNativeStateCommand } from "./state-runtime";
25
- import { appendJsonl, readExistingStateForMutation, writeArtifact, writeWorkflowEnvelopeAtomic } from "./state-writer";
28
+ import {
29
+ appendJsonl,
30
+ readExistingStateForMutation,
31
+ verifyWorkflowEnvelopeReceiptValue,
32
+ withWorkflowStateLock,
33
+ writeArtifact,
34
+ writeWorkflowEnvelopeAtomic,
35
+ } from "./state-writer";
26
36
  import { assertSafePathComponent, CommandError, flagValue, hasFlag } from "./workflow-cli-common";
27
37
 
28
38
  export * from "./deep-interview-recorder";
@@ -354,7 +364,7 @@ async function readSettingsAmbiguityThreshold(
354
364
  } catch (error) {
355
365
  const err = error as NodeJS.ErrnoException;
356
366
  if (err.code === "ENOENT") return undefined;
357
- return undefined;
367
+ throw new Error(`failed to read deep-interview threshold config ${settingsPath}: ${err.message}`);
358
368
  }
359
369
  let parsed: unknown;
360
370
  try {
@@ -364,7 +374,7 @@ async function readSettingsAmbiguityThreshold(
364
374
  }
365
375
  const candidate = (parsed as { gjc?: { deepInterview?: { ambiguityThreshold?: unknown } } })?.gjc?.deepInterview
366
376
  ?.ambiguityThreshold;
367
- if (typeof candidate !== "number" || !Number.isFinite(candidate) || candidate <= 0 || candidate > 1) {
377
+ if (typeof candidate !== "number" || !isIntegralScore(candidate) || candidate <= 0) {
368
378
  return undefined;
369
379
  }
370
380
  return { threshold: candidate, source: settingsPath };
@@ -380,16 +390,23 @@ function modernSettingsPath(): string {
380
390
 
381
391
  async function readModernSettingsAmbiguityThreshold(): Promise<{ threshold: number; source: string } | undefined> {
382
392
  const modernConfigPath = modernSettingsPath();
393
+ let raw: string;
394
+ try {
395
+ raw = await fs.readFile(modernConfigPath, "utf-8");
396
+ } catch (error) {
397
+ const err = error as NodeJS.ErrnoException;
398
+ if (err.code === "ENOENT") return undefined;
399
+ throw new Error(`failed to read deep-interview threshold config ${modernConfigPath}: ${err.message}`);
400
+ }
383
401
  let parsed: unknown;
384
402
  try {
385
- parsed = YAML.parse(await fs.readFile(modernConfigPath, "utf-8"));
403
+ parsed = YAML.parse(raw);
386
404
  } catch {
387
405
  return undefined;
388
406
  }
389
407
  const candidate = (parsed as { gjc?: { deepInterview?: { ambiguityThreshold?: unknown } } })?.gjc?.deepInterview
390
408
  ?.ambiguityThreshold;
391
- if (typeof candidate !== "number" || !Number.isFinite(candidate) || candidate <= 0 || candidate > 1)
392
- return undefined;
409
+ if (typeof candidate !== "number" || !isIntegralScore(candidate) || candidate <= 0) return undefined;
393
410
  return { threshold: candidate, source: modernConfigPath };
394
411
  }
395
412
 
@@ -504,6 +521,15 @@ async function resolveSpecWriteArgs(args: readonly string[], cwd: string): Promi
504
521
  };
505
522
  }
506
523
 
524
+ function isIntegralScore(value: number): boolean {
525
+ try {
526
+ scoreToUnits(value);
527
+ return true;
528
+ } catch {
529
+ return false;
530
+ }
531
+ }
532
+
507
533
  async function resolveDeepInterviewArgs(args: readonly string[], cwd: string): Promise<ResolvedDeepInterviewArgs> {
508
534
  const session = resolveGjcSessionForWrite(cwd, {
509
535
  flagValue: flagValue(args, "--session-id"),
@@ -524,10 +550,10 @@ async function resolveDeepInterviewArgs(args: readonly string[], cwd: string): P
524
550
  const thresholdOverride = flagValue(args, "--threshold");
525
551
  if (thresholdOverride !== undefined) {
526
552
  const parsed = Number(thresholdOverride);
527
- if (!Number.isFinite(parsed) || parsed <= 0 || parsed > 1) {
553
+ if (!isIntegralScore(parsed) || parsed <= 0) {
528
554
  throw new DeepInterviewCommandError(
529
555
  2,
530
- `invalid --threshold: ${thresholdOverride}. Expected 0 < threshold <= 1.`,
556
+ `invalid --threshold: ${thresholdOverride}. Expected integral 1e-4 units in (0, 1].`,
531
557
  );
532
558
  }
533
559
  threshold = parsed;
@@ -545,7 +571,16 @@ async function resolveDeepInterviewArgs(args: readonly string[], cwd: string): P
545
571
 
546
572
  const ideaParts: string[] = [];
547
573
  let skipNext = false;
574
+ let positionalOnly = false;
548
575
  for (const arg of args) {
576
+ if (positionalOnly) {
577
+ ideaParts.push(arg);
578
+ continue;
579
+ }
580
+ if (arg === "--") {
581
+ positionalOnly = true;
582
+ continue;
583
+ }
549
584
  if (skipNext) {
550
585
  skipNext = false;
551
586
  continue;
@@ -719,13 +754,13 @@ export async function persistDeepInterviewSpec(
719
754
  forced: resolved.force,
720
755
  },
721
756
  });
722
- await writeSessionActivityMarker(cwd, resolved.sessionId, { writer: "deep-interview-runtime", path: statePath });
723
- await syncDeepInterviewHud({
757
+ await runDeepInterviewPostCommitEffects({
724
758
  cwd,
725
- sessionId: resolved.sessionId,
726
- payload,
727
- phase: "handoff",
728
- specStatus: "persisted",
759
+ statePath,
760
+ sessionId: resolved.sessionId ?? "",
761
+ envelope: normalizeDeepInterviewEnvelope(payload),
762
+ revision: 0,
763
+ writer: "deep-interview-runtime",
729
764
  });
730
765
 
731
766
  return {
@@ -738,91 +773,169 @@ export async function persistDeepInterviewSpec(
738
773
  };
739
774
  }
740
775
 
741
- async function seedDeepInterviewState(cwd: string, resolved: ResolvedDeepInterviewArgs): Promise<string> {
776
+ function isEmptyPendingTopology(value: unknown): boolean {
777
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
778
+ const topology = value as Record<string, unknown>;
779
+ return (
780
+ topology.status === "pending" &&
781
+ Array.isArray(topology.components) &&
782
+ topology.components.length === 0 &&
783
+ Array.isArray(topology.deferred_components) &&
784
+ topology.deferred_components.length === 0
785
+ );
786
+ }
787
+ function isUnresolvedNativeSetup(value: unknown): boolean {
788
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
789
+ const state = value as Record<string, unknown>;
790
+ return (
791
+ state.setup !== null &&
792
+ typeof state.setup === "object" &&
793
+ !Array.isArray(state.setup) &&
794
+ (state.setup as Record<string, unknown>).status === "unresolved"
795
+ );
796
+ }
797
+ async function seedDeepInterviewState(
798
+ cwd: string,
799
+ resolved: ResolvedDeepInterviewArgs,
800
+ ): Promise<{ statePath: string; warnings: unknown[] }> {
742
801
  const statePath = deepInterviewStatePath(cwd, resolved.sessionId);
743
- assertDeepInterviewInputWithinLimit(resolved.idea, MAX_INITIAL_CONTEXT_LENGTH, "initial_idea");
744
- const now = new Date().toISOString();
745
- const payload: Record<string, unknown> = {
746
- active: true,
747
- current_phase: "interviewing",
748
- skill: "deep-interview",
749
- version: WORKFLOW_STATE_VERSION,
750
- resolution: resolved.resolution,
751
- threshold: resolved.threshold,
752
- threshold_source: resolved.thresholdSource,
753
- state: {
754
- initial_idea: resolved.idea,
755
- intent_contract_required: true,
756
- rounds: [],
757
- established_facts: [],
758
- current_ambiguity: 1.0,
759
- threshold: resolved.threshold,
760
- threshold_source: resolved.thresholdSource,
761
- },
762
- updated_at: now,
763
- };
764
- if (resolved.trace) {
765
- payload.trace = resolved.trace;
766
- (payload.state as Record<string, unknown>).trace = resolved.trace;
767
- (payload.state as Record<string, unknown>).trace_summary = resolved.trace;
768
- (payload.state as Record<string, unknown>).codebase_context = {
769
- source: "trace",
770
- summary: resolved.trace.findings,
771
- relevant_paths: resolved.trace.relevant_paths,
772
- project_hints: resolved.trace.project_hints,
773
- };
774
- }
775
- if (resolved.language) {
776
- payload.language = resolved.language;
777
- (payload.state as Record<string, unknown>).language = resolved.language;
778
- }
779
- if (resolved.sessionId) payload.session_id = resolved.sessionId;
780
- await writeWorkflowEnvelopeAtomic(statePath, payload, {
781
- cwd,
782
- receipt: {
783
- cwd,
784
- skill: "deep-interview",
785
- owner: "gjc-runtime",
786
- command: "gjc deep-interview seed",
787
- sessionId: resolved.sessionId,
788
- nowIso: now,
789
- },
790
- audit: {
791
- category: "state",
792
- verb: "write",
793
- owner: "gjc-runtime",
794
- skill: "deep-interview",
795
- sessionId: resolved.sessionId,
802
+ return withWorkflowStateLock(
803
+ `${statePath}.kickoff`,
804
+ async () => {
805
+ const existing = await readExistingStateForMutation(statePath);
806
+ if (existing.kind === "valid") {
807
+ try {
808
+ if (verifyWorkflowEnvelopeReceiptValue(existing.value, statePath) !== "native-valid")
809
+ throw new Error("invalid receipt");
810
+ validateDeepInterviewV1Envelope(existing.value);
811
+ } catch {
812
+ throw new DeepInterviewCommandError(3, "DI_STATE_CORRUPT");
813
+ }
814
+ const phase =
815
+ typeof existing.value.current_phase === "string" ? existing.value.current_phase : "interviewing";
816
+ if (phase === "complete" || phase === "handoff" || existing.value.active === false)
817
+ throw new DeepInterviewCommandError(3, "DI_PHASE_NOT_REPAIRABLE");
818
+ const priorState =
819
+ existing.value.state && typeof existing.value.state === "object" && !Array.isArray(existing.value.state)
820
+ ? (existing.value.state as Record<string, unknown>)
821
+ : {};
822
+ const exactEmptyNativeSeed =
823
+ isUnresolvedNativeSetup(priorState) &&
824
+ priorState.type === (resolved.trace ? "brownfield" : "greenfield") &&
825
+ existing.value.skill === "deep-interview" &&
826
+ existing.value.active === true &&
827
+ phase === "interviewing" &&
828
+ existing.value.resolution === resolved.resolution &&
829
+ existing.value.threshold === resolved.threshold &&
830
+ existing.value.threshold_source === resolved.thresholdSource &&
831
+ priorState.initial_idea === resolved.idea &&
832
+ priorState.intent_contract_required === true &&
833
+ Array.isArray(priorState.rounds) &&
834
+ priorState.rounds.length === 0 &&
835
+ Array.isArray(priorState.established_facts) &&
836
+ priorState.established_facts.length === 0 &&
837
+ Array.isArray(priorState.ontology_snapshots) &&
838
+ priorState.ontology_snapshots.length === 0 &&
839
+ Array.isArray(priorState.auto_researched_rounds) &&
840
+ priorState.auto_researched_rounds.length === 0 &&
841
+ Array.isArray(priorState.auto_answered_rounds) &&
842
+ priorState.auto_answered_rounds.length === 0 &&
843
+ isEmptyPendingTopology(priorState.topology) &&
844
+ priorState.architect_failures === 0 &&
845
+ priorState.current_ambiguity === 1 &&
846
+ JSON.stringify(existing.value.trace) === JSON.stringify(resolved.trace) &&
847
+ JSON.stringify(existing.value.language) === JSON.stringify(resolved.language);
848
+ if (exactEmptyNativeSeed) return { statePath, warnings: [] };
849
+ throw new DeepInterviewCommandError(2, "DI_INTERVIEW_ALREADY_ACTIVE");
850
+ }
851
+ if (existing.kind === "corrupt") throw new DeepInterviewCommandError(3, "DI_STATE_CORRUPT");
852
+ const now = new Date().toISOString();
853
+ const type = resolved.trace ? "brownfield" : "greenfield";
854
+ const payload: Record<string, unknown> = {
855
+ active: true,
856
+ current_phase: "interviewing",
857
+ schema_version: 1,
858
+ skill: "deep-interview",
859
+ version: WORKFLOW_STATE_VERSION,
860
+ resolution: resolved.resolution,
861
+ threshold: resolved.threshold,
862
+ threshold_units: scoreToUnits(resolved.threshold),
863
+ threshold_source: resolved.thresholdSource,
864
+ state: {
865
+ setup: { status: "unresolved" },
866
+ type,
867
+ initial_idea: resolved.idea,
868
+ intent_contract_required: true,
869
+ rounds: [],
870
+ established_facts: [],
871
+ topology: { status: "pending", components: [], deferred_components: [] },
872
+ ontology_snapshots: [],
873
+ auto_researched_rounds: [],
874
+ auto_answered_rounds: [],
875
+ architect_failures: 0,
876
+ current_ambiguity: 1.0,
877
+ threshold: resolved.threshold,
878
+ threshold_units: scoreToUnits(resolved.threshold),
879
+ threshold_source: resolved.thresholdSource,
880
+ },
881
+ updated_at: now,
882
+ };
883
+ validateDeepInterviewV1Envelope(payload);
884
+ if (resolved.trace) {
885
+ payload.trace = resolved.trace;
886
+ (payload.state as Record<string, unknown>).trace = resolved.trace;
887
+ (payload.state as Record<string, unknown>).trace_summary = resolved.trace;
888
+ (payload.state as Record<string, unknown>).codebase_context = {
889
+ source: "trace",
890
+ summary: resolved.trace.findings,
891
+ relevant_paths: resolved.trace.relevant_paths,
892
+ project_hints: resolved.trace.project_hints,
893
+ };
894
+ }
895
+ if (resolved.language) {
896
+ payload.language = resolved.language;
897
+ (payload.state as Record<string, unknown>).language = resolved.language;
898
+ }
899
+ if (resolved.sessionId) payload.session_id = resolved.sessionId;
900
+ await writeWorkflowEnvelopeAtomic(statePath, payload, {
901
+ cwd,
902
+ receipt: {
903
+ cwd,
904
+ skill: "deep-interview",
905
+ owner: "gjc-runtime",
906
+ command: "gjc deep-interview seed",
907
+ sessionId: resolved.sessionId,
908
+ nowIso: now,
909
+ },
910
+ audit: {
911
+ category: "state",
912
+ verb: "write",
913
+ owner: "gjc-runtime",
914
+ skill: "deep-interview",
915
+ sessionId: resolved.sessionId,
916
+ },
917
+ });
918
+ const stamped = await readExistingStateForMutation(statePath);
919
+ if (stamped.kind !== "valid") throw new DeepInterviewCommandError(3, "DI_STATE_CORRUPT");
920
+ try {
921
+ if (verifyWorkflowEnvelopeReceiptValue(stamped.value, statePath) !== "native-valid")
922
+ throw new Error("invalid receipt");
923
+ validateDeepInterviewV1Envelope(stamped.value);
924
+ } catch {
925
+ throw new DeepInterviewCommandError(3, "DI_STATE_CORRUPT");
926
+ }
927
+ const warnings = await runDeepInterviewPostCommitEffects({
928
+ cwd,
929
+ statePath,
930
+ sessionId: resolved.sessionId ?? "",
931
+ envelope: normalizeDeepInterviewEnvelope(stamped.value),
932
+ revision: typeof stamped.value.state_revision === "number" ? stamped.value.state_revision : 0,
933
+ writer: "deep-interview-runtime",
934
+ });
935
+ return { statePath, warnings };
796
936
  },
797
- });
798
- await writeSessionActivityMarker(cwd, resolved.sessionId, { writer: "deep-interview-runtime", path: statePath });
799
- await syncDeepInterviewHud({ cwd, sessionId: resolved.sessionId, payload, phase: "interviewing" });
800
- return statePath;
801
- }
802
-
803
- async function syncDeepInterviewHud(options: {
804
- cwd: string;
805
- sessionId?: string;
806
- payload: Record<string, unknown>;
807
- phase?: string;
808
- specStatus?: string;
809
- }): Promise<void> {
810
- try {
811
- const phase =
812
- options.phase ??
813
- (typeof options.payload.current_phase === "string" ? options.payload.current_phase : "interviewing");
814
- await syncSkillActiveState({
815
- cwd: options.cwd,
816
- skill: "deep-interview",
817
- active: phase !== "complete",
818
- phase,
819
- sessionId: options.sessionId,
820
- source: "gjc-deep-interview-native",
821
- hud: deriveDeepInterviewHud(options.payload, { phase, specStatus: options.specStatus }),
822
- });
823
- } catch {
824
- // HUD sync is best-effort and must not change command semantics.
825
- }
937
+ { cwd },
938
+ );
826
939
  }
827
940
 
828
941
  async function handleSpecWrite(args: readonly string[], cwd: string): Promise<DeepInterviewCommandResult> {
@@ -888,6 +1001,28 @@ async function handleSpecWrite(args: readonly string[], cwd: string): Promise<De
888
1001
  .join("\n");
889
1002
  return { status: 0, stdout };
890
1003
  }
1004
+ function isDeepInterviewRepairInvocation(args: readonly string[]): boolean {
1005
+ const separator = args.indexOf("--");
1006
+ return (separator === -1 || separator > 0) && isDeepInterviewRepairVerb(args[0] ?? "");
1007
+ }
1008
+ function isDeepInterviewDraftInvocation(args: readonly string[]): boolean {
1009
+ const separator = args.indexOf("--");
1010
+ return (separator === -1 || separator > 0) && args[0] === "draft";
1011
+ }
1012
+ function draftCommandArgs(args: readonly string[]): string[] {
1013
+ const normalized: string[] = [];
1014
+ for (let index = 1; index < args.length; index++) {
1015
+ const argument = args[index];
1016
+ if (argument === "--for") {
1017
+ normalized.push("--kind", args[++index] ?? "");
1018
+ continue;
1019
+ }
1020
+ normalized.push(argument);
1021
+ if (argument === "--json" && !args[index + 1]?.startsWith("--")) continue;
1022
+ if (argument === "--json") normalized.push("true");
1023
+ }
1024
+ return normalized;
1025
+ }
891
1026
 
892
1027
  export async function runNativeDeepInterviewCommand(
893
1028
  args: string[],
@@ -895,14 +1030,39 @@ export async function runNativeDeepInterviewCommand(
895
1030
  ): Promise<DeepInterviewCommandResult> {
896
1031
  try {
897
1032
  if (isDeepInterviewSpecWriteInvocation(args)) return await handleSpecWrite(args, cwd);
1033
+ if (isDeepInterviewRepairInvocation(args)) return await runDeepInterviewRepairCommand(args, cwd);
1034
+ if (isDeepInterviewDraftInvocation(args)) return await runDeepInterviewDraftCommand(draftCommandArgs(args), cwd);
898
1035
  const resolved = await resolveDeepInterviewArgs(args, cwd);
899
1036
  if (!resolved.idea) {
1037
+ const existing = await readExistingStateForMutation(deepInterviewStatePath(cwd, resolved.sessionId));
1038
+ if (existing.kind === "valid") {
1039
+ const phase =
1040
+ typeof existing.value.current_phase === "string" ? existing.value.current_phase : "interviewing";
1041
+ if (phase === "complete" || existing.value.active === false)
1042
+ throw new DeepInterviewCommandError(
1043
+ 2,
1044
+ "deep-interview session is terminal; start a new session to kick off another interview.",
1045
+ );
1046
+ const state = existing.value.state as Record<string, unknown> | undefined;
1047
+ return {
1048
+ status: 0,
1049
+ stdout: `${JSON.stringify({
1050
+ skill: "deep-interview",
1051
+ resumed: true,
1052
+ session_id: resolved.sessionId ?? null,
1053
+ state_path: deepInterviewStatePath(cwd, resolved.sessionId),
1054
+ state_revision: existing.value.state_revision ?? 0,
1055
+ idea: typeof state?.initial_idea === "string" ? state.initial_idea : null,
1056
+ handoff: "/skill:deep-interview",
1057
+ })}\n`,
1058
+ };
1059
+ }
900
1060
  throw new DeepInterviewCommandError(
901
1061
  2,
902
1062
  'gjc deep-interview requires an idea, e.g. `gjc deep-interview "<idea>"`.',
903
1063
  );
904
1064
  }
905
- const statePath = await seedDeepInterviewState(cwd, resolved);
1065
+ const seeded = await seedDeepInterviewState(cwd, resolved);
906
1066
 
907
1067
  const summary = {
908
1068
  skill: "deep-interview",
@@ -912,13 +1072,14 @@ export async function runNativeDeepInterviewCommand(
912
1072
  idea: resolved.idea,
913
1073
  language: resolved.language,
914
1074
  trace: resolved.trace,
915
- state_path: statePath,
1075
+ state_path: seeded.statePath,
1076
+ warnings: seeded.warnings,
916
1077
  handoff: "/skill:deep-interview",
917
1078
  };
918
1079
  const stdout = resolved.json
919
1080
  ? `${JSON.stringify(summary)}\n`
920
1081
  : [
921
- `deep-interview seed state_path=${statePath}`,
1082
+ `deep-interview seed state_path=${seeded.statePath}`,
922
1083
  `resolution=${resolved.resolution} threshold=${resolved.threshold} threshold_source=${resolved.thresholdSource}`,
923
1084
  resolved.trace ? `trace=enabled bounded_paths=${resolved.trace.relevant_paths.length}` : undefined,
924
1085
  "handoff=/skill:deep-interview",