@sayknow-cli/coding-agent 0.3.5 → 0.3.7

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 (238) hide show
  1. package/README.md +76 -0
  2. package/dist/types/capability/mcp.d.ts +2 -0
  3. package/dist/types/cli/local-provider-smoke.d.ts +59 -0
  4. package/dist/types/cli/notify-cli.d.ts +1 -1
  5. package/dist/types/cli/telegram-cli.d.ts +13 -0
  6. package/dist/types/cli/update-cli.d.ts +24 -1
  7. package/dist/types/commands/local-provider.d.ts +34 -0
  8. package/dist/types/commands/telegram.d.ts +22 -0
  9. package/dist/types/config/keybindings.d.ts +12 -6
  10. package/dist/types/config/model-registry.d.ts +5 -0
  11. package/dist/types/config/models-config-schema.d.ts +5 -0
  12. package/dist/types/config/settings-schema.d.ts +284 -4
  13. package/dist/types/config/settings.d.ts +7 -0
  14. package/dist/types/config/telegram-autostart.d.ts +14 -0
  15. package/dist/types/config/telegram-env-bridge.d.ts +18 -0
  16. package/dist/types/coordinator-mcp/server.d.ts +8 -0
  17. package/dist/types/defaults/skc-defaults.d.ts +7 -0
  18. package/dist/types/discovery/cursor.d.ts +4 -1
  19. package/dist/types/discovery/index.d.ts +0 -1
  20. package/dist/types/discovery/windsurf.d.ts +3 -1
  21. package/dist/types/extensibility/extensions/types.d.ts +11 -0
  22. package/dist/types/i18n/messages/en.d.ts +1 -0
  23. package/dist/types/modes/components/custom-editor.d.ts +6 -1
  24. package/dist/types/modes/components/custom-model-preset-wizard.d.ts +1 -1
  25. package/dist/types/modes/components/hook-selector.d.ts +5 -0
  26. package/dist/types/modes/components/model-selector.d.ts +2 -0
  27. package/dist/types/modes/components/settings-selector.d.ts +5 -1
  28. package/dist/types/modes/components/skill-message.d.ts +1 -0
  29. package/dist/types/modes/components/status-line/types.d.ts +5 -7
  30. package/dist/types/modes/components/status-line.d.ts +15 -3
  31. package/dist/types/modes/components/two-column-body.d.ts +9 -0
  32. package/dist/types/modes/components/welcome.d.ts +9 -1
  33. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  34. package/dist/types/modes/controllers/input-controller.d.ts +6 -1
  35. package/dist/types/modes/controllers/selector-controller.d.ts +10 -1
  36. package/dist/types/modes/interactive-mode.d.ts +2 -0
  37. package/dist/types/modes/prompt-action-autocomplete.d.ts +3 -0
  38. package/dist/types/modes/rpc/rpc-types.d.ts +1 -0
  39. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +41 -7
  40. package/dist/types/modes/slash-command-visibility.d.ts +1 -0
  41. package/dist/types/modes/theme/theme.d.ts +1 -1
  42. package/dist/types/modes/tmux-scroll.d.ts +18 -0
  43. package/dist/types/modes/types.d.ts +5 -1
  44. package/dist/types/modes/utils/terminal-bell.d.ts +3 -0
  45. package/dist/types/notifications/daemon-paths.d.ts +9 -0
  46. package/dist/types/notifications/html-format.d.ts +2 -0
  47. package/dist/types/notifications/recent-activity.d.ts +5 -1
  48. package/dist/types/notifications/telegram-daemon-cli.d.ts +21 -4
  49. package/dist/types/notifications/telegram-daemon-control.d.ts +8 -0
  50. package/dist/types/notifications/telegram-daemon.d.ts +4 -10
  51. package/dist/types/notifications/telegram-reference.d.ts +4 -0
  52. package/dist/types/notifications/threaded-render.d.ts +1 -0
  53. package/dist/types/runtime-mcp/config-writer.d.ts +9 -0
  54. package/dist/types/runtime-mcp/config.d.ts +2 -0
  55. package/dist/types/runtime-mcp/loader.d.ts +2 -0
  56. package/dist/types/runtime-mcp/manager.d.ts +2 -0
  57. package/dist/types/runtime-mcp/types.d.ts +6 -0
  58. package/dist/types/sdk.d.ts +1 -1
  59. package/dist/types/skc-runtime/launch-tmux.d.ts +2 -2
  60. package/dist/types/skc-runtime/ralplan-runtime.d.ts +3 -2
  61. package/dist/types/skc-runtime/restricted-role-agent-bash.d.ts +1 -0
  62. package/dist/types/skc-runtime/session-state-sidecar.d.ts +5 -0
  63. package/dist/types/skc-runtime/state-writer.d.ts +1 -0
  64. package/dist/types/skc-runtime/team-runtime.d.ts +1 -1
  65. package/dist/types/skc-runtime/tmux-common.d.ts +12 -0
  66. package/dist/types/skc-runtime/ultragoal-guard.d.ts +7 -1
  67. package/dist/types/skc-runtime/ultragoal-runtime.d.ts +0 -4
  68. package/dist/types/slash-commands/types.d.ts +2 -0
  69. package/dist/types/system-prompt.d.ts +7 -0
  70. package/dist/types/tools/skill.d.ts +1 -1
  71. package/dist/types/utils/cmux-workspace.d.ts +52 -0
  72. package/dist/types/web/search/index.d.ts +4 -2
  73. package/dist/types/web/search/provider.d.ts +14 -0
  74. package/dist/types/web/search/providers/utils.d.ts +42 -13
  75. package/dist/types/web/search/render.d.ts +1 -0
  76. package/dist/types/workflow/workflow-intent-diff.d.ts +19 -0
  77. package/package.json +8 -8
  78. package/scripts/build-binary.ts +3 -0
  79. package/scripts/verify-insane-vendor.ts +0 -5
  80. package/src/capability/mcp.ts +2 -0
  81. package/src/cli/local-provider-smoke.ts +632 -0
  82. package/src/cli/notify-cli.ts +4 -2
  83. package/src/cli/telegram-cli.ts +156 -0
  84. package/src/cli/update-cli.ts +179 -13
  85. package/src/cli/web-search-cli.ts +5 -7
  86. package/src/cli.ts +20 -1
  87. package/src/commands/launch.ts +47 -2
  88. package/src/commands/local-provider.ts +62 -0
  89. package/src/commands/notify.ts +1 -1
  90. package/src/commands/ralplan.ts +1 -0
  91. package/src/commands/telegram.ts +47 -0
  92. package/src/config/keybindings.ts +15 -6
  93. package/src/config/mcp-schema.json +4 -0
  94. package/src/config/model-registry.ts +55 -1
  95. package/src/config/models-config-schema.ts +9 -0
  96. package/src/config/settings-schema.ts +256 -4
  97. package/src/config/settings.ts +11 -0
  98. package/src/config/telegram-autostart.ts +107 -0
  99. package/src/config/telegram-env-bridge.ts +92 -0
  100. package/src/coordinator-mcp/server.ts +276 -34
  101. package/src/defaults/skc/skills/ralplan/SKILL.md +29 -20
  102. package/src/defaults/skc/skills/ultragoal/SKILL.md +37 -112
  103. package/src/defaults/skc-defaults.ts +16 -0
  104. package/src/discovery/builtin.ts +12 -0
  105. package/src/discovery/cursor.ts +5 -84
  106. package/src/discovery/gemini.ts +3 -89
  107. package/src/discovery/index.ts +4 -1
  108. package/src/discovery/mcp-json.ts +11 -0
  109. package/src/discovery/opencode.ts +5 -122
  110. package/src/discovery/windsurf.ts +4 -82
  111. package/src/edit/read-file.ts +9 -1
  112. package/src/extensibility/extensions/runner.ts +2 -1
  113. package/src/extensibility/extensions/types.ts +11 -0
  114. package/src/extensibility/skills.ts +30 -2
  115. package/src/hooks/skill-state.ts +33 -73
  116. package/src/i18n/messages/de.ts +1 -0
  117. package/src/i18n/messages/en.ts +1 -0
  118. package/src/i18n/messages/es.ts +1 -0
  119. package/src/i18n/messages/fr.ts +1 -0
  120. package/src/i18n/messages/ja.settings.ts +28 -0
  121. package/src/i18n/messages/ja.ts +1 -0
  122. package/src/i18n/messages/ko.settings.ts +28 -0
  123. package/src/i18n/messages/ko.ts +1 -0
  124. package/src/i18n/messages/zh.settings.ts +28 -0
  125. package/src/i18n/messages/zh.ts +1 -0
  126. package/src/internal-urls/docs-index.generated.ts +12 -7
  127. package/src/main.ts +5 -2
  128. package/src/migrate/executor.ts +23 -6
  129. package/src/modes/acp/acp-agent.ts +7 -6
  130. package/src/modes/components/agent-dashboard.ts +3 -35
  131. package/src/modes/components/custom-editor.ts +26 -0
  132. package/src/modes/components/custom-model-preset-wizard.ts +30 -189
  133. package/src/modes/components/extensions/extension-dashboard.ts +2 -48
  134. package/src/modes/components/hook-selector.ts +21 -3
  135. package/src/modes/components/model-selector.ts +246 -11
  136. package/src/modes/components/session-observer-overlay.ts +3 -3
  137. package/src/modes/components/session-selector.ts +19 -9
  138. package/src/modes/components/settings-selector.ts +551 -11
  139. package/src/modes/components/skill-message.ts +18 -10
  140. package/src/modes/components/status-line/presets.ts +11 -0
  141. package/src/modes/components/status-line/segments.ts +16 -19
  142. package/src/modes/components/status-line/types.ts +6 -2
  143. package/src/modes/components/status-line.ts +281 -117
  144. package/src/modes/components/tree-selector.ts +28 -8
  145. package/src/modes/components/two-column-body.ts +36 -0
  146. package/src/modes/components/welcome.ts +165 -30
  147. package/src/modes/controllers/command-controller.ts +26 -2
  148. package/src/modes/controllers/event-controller.ts +102 -4
  149. package/src/modes/controllers/extension-ui-controller.ts +21 -2
  150. package/src/modes/controllers/input-controller.ts +214 -47
  151. package/src/modes/controllers/selector-controller.ts +35 -33
  152. package/src/modes/interactive-mode.ts +36 -30
  153. package/src/modes/prompt-action-autocomplete.ts +48 -8
  154. package/src/modes/rpc/rpc-types.ts +1 -0
  155. package/src/modes/shared/agent-wire/deep-interview-gate.ts +72 -15
  156. package/src/modes/shared/agent-wire/workflow-gate-schema.ts +20 -0
  157. package/src/modes/slash-command-visibility.ts +9 -0
  158. package/src/modes/theme/theme.ts +5 -1
  159. package/src/modes/tmux-scroll.ts +92 -0
  160. package/src/modes/types.ts +8 -1
  161. package/src/modes/utils/hotkeys-markdown.ts +5 -2
  162. package/src/modes/utils/terminal-bell.ts +48 -0
  163. package/src/modes/utils/ui-helpers.ts +76 -3
  164. package/src/notifications/daemon-paths.ts +22 -0
  165. package/src/notifications/html-format.ts +75 -1
  166. package/src/notifications/index.ts +32 -2
  167. package/src/notifications/lifecycle-control-runtime.ts +12 -3
  168. package/src/notifications/recent-activity.ts +32 -8
  169. package/src/notifications/telegram-daemon-cli.ts +125 -13
  170. package/src/notifications/telegram-daemon-control.ts +9 -0
  171. package/src/notifications/telegram-daemon.ts +88 -46
  172. package/src/notifications/telegram-reference.ts +36 -14
  173. package/src/notifications/threaded-render.ts +14 -6
  174. package/src/prompts/agents/architect.md +7 -4
  175. package/src/prompts/agents/critic.md +15 -6
  176. package/src/prompts/agents/planner.md +8 -3
  177. package/src/prompts/system/system-prompt.md +20 -2
  178. package/src/prompts/tools/bash.md +1 -1
  179. package/src/runtime-mcp/config-writer.ts +28 -0
  180. package/src/runtime-mcp/config.ts +7 -0
  181. package/src/runtime-mcp/loader.ts +3 -0
  182. package/src/runtime-mcp/manager.ts +3 -0
  183. package/src/runtime-mcp/types.ts +6 -0
  184. package/src/sdk.ts +10 -11
  185. package/src/session/agent-session.ts +103 -13
  186. package/src/session/session-manager.ts +58 -18
  187. package/src/skc-runtime/launch-tmux.ts +34 -8
  188. package/src/skc-runtime/launch-worktree.ts +16 -2
  189. package/src/skc-runtime/ralplan-runtime.ts +22 -6
  190. package/src/skc-runtime/restricted-role-agent-bash.ts +1 -0
  191. package/src/skc-runtime/session-state-sidecar.ts +167 -24
  192. package/src/skc-runtime/state-runtime.ts +8 -5
  193. package/src/skc-runtime/state-writer.ts +5 -5
  194. package/src/skc-runtime/team-runtime.ts +21 -6
  195. package/src/skc-runtime/tmux-common.ts +16 -0
  196. package/src/skc-runtime/tmux-sessions.ts +13 -9
  197. package/src/skc-runtime/ultragoal-guard.ts +150 -24
  198. package/src/skc-runtime/ultragoal-runtime.ts +16 -124
  199. package/src/skc-runtime/workflow-manifest.generated.json +0 -8
  200. package/src/skc-runtime/workflow-manifest.ts +0 -1
  201. package/src/slash-commands/builtin-registry.ts +136 -23
  202. package/src/slash-commands/types.ts +2 -0
  203. package/src/system-prompt.ts +8 -0
  204. package/src/task/executor.ts +5 -0
  205. package/src/tools/ast-grep.ts +10 -7
  206. package/src/tools/bash.ts +39 -14
  207. package/src/tools/index.ts +8 -1
  208. package/src/tools/skill.ts +1 -1
  209. package/src/utils/cmux-workspace.ts +233 -0
  210. package/src/utils/title-generator.ts +2 -0
  211. package/src/web/kagi.ts +1 -1
  212. package/src/web/parallel.ts +2 -2
  213. package/src/web/search/index.ts +137 -52
  214. package/src/web/search/provider.ts +80 -0
  215. package/src/web/search/providers/anthropic.ts +1 -1
  216. package/src/web/search/providers/brave.ts +1 -1
  217. package/src/web/search/providers/codex.ts +1 -1
  218. package/src/web/search/providers/duckduckgo.ts +1 -1
  219. package/src/web/search/providers/exa.ts +1 -1
  220. package/src/web/search/providers/gemini.ts +7 -3
  221. package/src/web/search/providers/jina.ts +1 -1
  222. package/src/web/search/providers/kimi.ts +6 -1
  223. package/src/web/search/providers/openai-compatible.ts +1 -1
  224. package/src/web/search/providers/parallel.ts +1 -1
  225. package/src/web/search/providers/perplexity.ts +2 -2
  226. package/src/web/search/providers/searxng.ts +1 -1
  227. package/src/web/search/providers/synthetic.ts +1 -1
  228. package/src/web/search/providers/tavily.ts +1 -1
  229. package/src/web/search/providers/utils.ts +67 -17
  230. package/src/web/search/providers/xai.ts +1 -1
  231. package/src/web/search/providers/zai.ts +1 -1
  232. package/src/web/search/render.ts +3 -0
  233. package/src/workflow/workflow-intent-diff.ts +199 -0
  234. package/vendor/insane-search/MANIFEST.json +1 -3
  235. package/vendor/insane-search/engine/tests/test_u1.py +0 -28
  236. package/vendor/insane-search/engine/transport.py +2 -3
  237. package/vendor/insane-search/engine/validators.py +0 -14
  238. package/src/defaults/skc/rules/ponytail.md +0 -68
@@ -69,9 +69,12 @@ function isKnownUltragoalObjective(currentObjective: string): boolean {
69
69
  );
70
70
  }
71
71
 
72
- async function ultragoalReadPaths(cwd: string): Promise<{ paths: UltragoalPaths; sessionId: string | null }> {
73
- const envSessionId = process.env.SKC_SESSION_ID?.trim();
74
- if (envSessionId) return { paths: getUltragoalPaths(cwd, envSessionId), sessionId: envSessionId };
72
+ async function ultragoalReadPaths(
73
+ cwd: string,
74
+ options: { sessionId?: string | null } = {},
75
+ ): Promise<{ paths: UltragoalPaths; sessionId: string | null }> {
76
+ const explicitSessionId = options.sessionId?.trim() || process.env.SKC_SESSION_ID?.trim();
77
+ if (explicitSessionId) return { paths: getUltragoalPaths(cwd, explicitSessionId), sessionId: explicitSessionId };
75
78
  try {
76
79
  const session = await resolveSkcSessionForRead(cwd, { envSessionId: process.env.SKC_SESSION_ID });
77
80
  return { paths: getUltragoalPaths(cwd, session.skcSessionId), sessionId: session.skcSessionId };
@@ -250,13 +253,6 @@ export function validateCompletionReceipt(input: {
250
253
  goalId: input.goal.id,
251
254
  };
252
255
  }
253
- if (hashStructuredValue(event.skcGoalJson) !== receipt.skcGoalSnapshotHash) {
254
- return {
255
- state: "active_stale_receipt",
256
- message: `Ultragoal ${input.goal.id} receipt goal({"op":"get"}) snapshot hash does not match ledger.`,
257
- goalId: input.goal.id,
258
- };
259
- }
260
256
  if (input.goal.updatedAt !== receipt.verifiedAt) {
261
257
  return {
262
258
  state: "active_stale_receipt",
@@ -273,15 +269,28 @@ export function validateCompletionReceipt(input: {
273
269
  goalId: input.goal.id,
274
270
  };
275
271
  }
276
- const missingReceipts = requiredGoals(input.plan).filter(
277
- goal => goal.id !== input.goal.id && !goal.completionVerification,
278
- );
279
- if (missingReceipts.length > 0) {
280
- return {
281
- state: "active_missing_receipt",
282
- message: `Ultragoal final receipt is missing per-goal evidence for: ${missingReceipts.map(goal => goal.id).join(", ")}.`,
283
- goalId: input.goal.id,
284
- };
272
+ for (const priorGoal of requiredGoals(input.plan)) {
273
+ if (priorGoal.id === input.goal.id) continue;
274
+ if (!priorGoal.completionVerification) {
275
+ return {
276
+ state: "active_missing_receipt",
277
+ message: `Ultragoal final receipt is missing per-goal evidence for: ${priorGoal.id}.`,
278
+ goalId: input.goal.id,
279
+ };
280
+ }
281
+ const priorDiagnostic = validateCompletionReceipt({
282
+ plan: input.plan,
283
+ ledger: input.ledger,
284
+ goal: priorGoal,
285
+ receiptKind: "per-goal",
286
+ });
287
+ if (priorDiagnostic.state !== "active_verified_complete") {
288
+ return {
289
+ state: priorDiagnostic.state,
290
+ message: `Ultragoal final receipt requires a valid per-goal receipt for ${priorGoal.id}: ${priorDiagnostic.message}`,
291
+ goalId: input.goal.id,
292
+ };
293
+ }
285
294
  }
286
295
  }
287
296
  return {
@@ -372,11 +381,128 @@ export async function readUltragoalVerificationState(input: {
372
381
  return receiptDiagnostic;
373
382
  }
374
383
 
375
- export async function isUltragoalAskBlocked(cwd: string): Promise<UltragoalAskBlockDiagnostic> {
384
+ export async function verifyUltragoalDurableCompletionState(input: {
385
+ cwd: string;
386
+ sessionId?: string | null;
387
+ }): Promise<UltragoalGuardDiagnostic> {
388
+ let paths: UltragoalPaths;
389
+ let sessionId: string | null;
390
+ try {
391
+ ({ paths, sessionId } = await ultragoalReadPaths(input.cwd, { sessionId: input.sessionId }));
392
+ } catch (error) {
393
+ return {
394
+ state: "unreadable_fail_closed",
395
+ message: `Unable to resolve durable Ultragoal state: ${error instanceof Error ? error.message : String(error)}`,
396
+ };
397
+ }
398
+ if (sessionId === null)
399
+ return { state: "inactive", message: "No active SKC session resolved; ultragoal is inactive." };
400
+ try {
401
+ await fs.stat(paths.dir);
402
+ } catch (error) {
403
+ if (isEnoent(error)) return { state: "inactive", message: "No durable .skc/ultragoal state exists." };
404
+ return {
405
+ state: "unreadable_fail_closed",
406
+ message: `Durable .skc/ultragoal state is present but unreadable: ${error instanceof Error ? error.message : String(error)}`,
407
+ };
408
+ }
409
+
410
+ let plan: UltragoalPlan | null;
411
+ let ledger: UltragoalLedgerEvent[];
412
+ try {
413
+ plan = await readUltragoalPlan(input.cwd, sessionId);
414
+ ledger = await readUltragoalLedger(input.cwd, sessionId);
415
+ } catch (error) {
416
+ return {
417
+ state: "unreadable_fail_closed",
418
+ message: `Unable to read durable Ultragoal state: ${error instanceof Error ? error.message : String(error)}`,
419
+ };
420
+ }
421
+ if (!plan) return { state: "inactive", message: "No Ultragoal plan exists." };
422
+
423
+ if (plan.goals.some(goal => goal.status === "review_blocked")) {
424
+ return {
425
+ state: "active_review_blocked_recorded",
426
+ message: "Ultragoal has recorded review blockers; complete blocker work and rerun verification.",
427
+ };
428
+ }
429
+
430
+ const runState = getUltragoalRunCompletionState(plan);
431
+ if (runState.incompleteGoals.some(goal => goal.status === "blocked" || goal.status === "failed")) {
432
+ return {
433
+ state: "active_dirty_quality_gate",
434
+ message: "Ultragoal has blocked or failed goals; record blockers or rerun verification.",
435
+ };
436
+ }
437
+
438
+ if (plan.skcGoalMode === "per-story") {
439
+ const incomplete = requiredGoals(plan).filter(goal => goal.status !== "complete");
440
+ if (incomplete.length > 0) {
441
+ return {
442
+ state: "active_missing_receipt",
443
+ message: `Ultragoal per-story completion requires all required stories to be complete; incomplete: ${incomplete.map(goal => goal.id).join(", ")}.`,
444
+ goalId: incomplete[0]?.id,
445
+ };
446
+ }
447
+ for (const goal of requiredGoals(plan)) {
448
+ const diagnostic = validateCompletionReceipt({
449
+ plan,
450
+ ledger,
451
+ goal,
452
+ receiptKind: "per-goal",
453
+ });
454
+ if (diagnostic.state !== "active_verified_complete") return diagnostic;
455
+ }
456
+ return {
457
+ state: "active_verified_complete",
458
+ message: "Ultragoal per-story run is verified complete.",
459
+ };
460
+ }
461
+
462
+ const ask = await isUltragoalAskBlocked(input.cwd, { sessionId });
463
+ if (!ask.active) {
464
+ return {
465
+ state: "active_verified_complete",
466
+ message: ask.reason,
467
+ goalId: ask.goalIds?.at(0),
468
+ };
469
+ }
470
+ if (ask.source === "durable_state_unreadable") {
471
+ return {
472
+ state: "unreadable_fail_closed",
473
+ message: ask.reason,
474
+ goalId: ask.goalIds?.at(0),
475
+ };
476
+ }
477
+ if (ask.reason.includes("recorded review blockers")) {
478
+ return {
479
+ state: "active_review_blocked_recorded",
480
+ message: ask.reason,
481
+ goalId: ask.goalIds?.at(0),
482
+ };
483
+ }
484
+ if (ask.reason.includes("incomplete required goals")) {
485
+ return {
486
+ state: "active_missing_final_receipt",
487
+ message: ask.reason,
488
+ goalId: ask.goalIds?.at(0),
489
+ };
490
+ }
491
+ return {
492
+ state: "active_missing_final_receipt",
493
+ message: ask.reason,
494
+ goalId: ask.goalIds?.at(0),
495
+ };
496
+ }
497
+
498
+ export async function isUltragoalAskBlocked(
499
+ cwd: string,
500
+ options: { sessionId?: string | null } = {},
501
+ ): Promise<UltragoalAskBlockDiagnostic> {
376
502
  let paths: UltragoalPaths;
377
503
  let sessionId: string | null;
378
504
  try {
379
- ({ paths, sessionId } = await ultragoalReadPaths(cwd));
505
+ ({ paths, sessionId } = await ultragoalReadPaths(cwd, options));
380
506
  } catch (error) {
381
507
  return activeAskDiagnostic({
382
508
  reason: `Unable to resolve durable Ultragoal state: ${error instanceof Error ? error.message : String(error)}`,
@@ -594,8 +720,8 @@ export async function assertCanCompleteCurrentGoal(input: {
594
720
  sessionId?: string | null;
595
721
  }): Promise<void> {
596
722
  if (!input.cwd) return;
597
- const diagnostic = await readUltragoalVerificationState(input);
598
- if (["inactive", "unrelated_goal", "active_verified_complete"].includes(diagnostic.state)) return;
723
+ const diagnostic = await verifyUltragoalDurableCompletionState(input);
724
+ if (["inactive", "active_verified_complete"].includes(diagnostic.state)) return;
599
725
  const nudge = await consumeUltragoalNudge({
600
726
  cwd: input.cwd,
601
727
  surface: "premature_complete",
@@ -604,7 +730,7 @@ export async function assertCanCompleteCurrentGoal(input: {
604
730
  });
605
731
  if (nudge.nudged) throw new Error(nudge.message);
606
732
  throw new Error(
607
- `${diagnostic.message} Run strict \`skc ultragoal checkpoint --status complete --quality-gate-json <file> --skc-goal-json <file>\` first, or record review blockers and rerun verification.`,
733
+ `${diagnostic.message} Run \`skc ultragoal checkpoint --status complete --quality-gate-json <file>\` first, or record review blockers and rerun verification.`,
608
734
  );
609
735
  }
610
736
 
@@ -2,12 +2,10 @@ import * as crypto from "node:crypto";
2
2
  import * as os from "node:os";
3
3
  import * as path from "node:path";
4
4
  import { inflateSync } from "node:zlib";
5
- import { normalizeGoal } from "../goals/state";
6
- import { buildSessionContext, loadEntriesFromFile, type SessionEntry } from "../session/session-manager";
7
5
  import type { WorkflowHudSummary } from "../skill-state/active-state";
8
6
  import { buildUltragoalHudSummary as buildWorkflowUltragoalHudSummary } from "../skill-state/workflow-hud";
9
7
  import { renderCliWriteReceipt } from "./cli-write-receipt";
10
- import { DEFAULT_ULTRAGOAL_OBJECTIVE, SKC_SESSION_FILE_ENV } from "./goal-mode-request";
8
+ import { DEFAULT_ULTRAGOAL_OBJECTIVE } from "./goal-mode-request";
11
9
  import { sessionUltragoalDir, skcRoot } from "./session-layout";
12
10
  import { resolveSkcSessionForRead, resolveSkcSessionForWrite, writeSessionActivityMarker } from "./session-resolution";
13
11
  import { renderUltragoalStatusMarkdown } from "./state-renderer";
@@ -68,7 +66,6 @@ export interface UltragoalCompletionVerification {
68
66
  skcGoalMode: UltragoalSkcGoalMode;
69
67
  skcObjective: string;
70
68
  qualityGateHash: string;
71
- skcGoalSnapshotHash: string;
72
69
  planGeneration: string;
73
70
  basis: {
74
71
  planHashBeforeCheckpoint: string;
@@ -174,10 +171,9 @@ const ACCEPTED_PROOF_STATUSES = new Set([COVERED_STATUS, "passed", "verified"]);
174
171
  const MIN_SUBSTANTIVE_EVIDENCE_WORDS = 5;
175
172
  const MIN_SUBSTANTIVE_EVIDENCE_CHARS = 32;
176
173
 
177
- const SKC_GOAL_SNAPSHOT_MAX_AGE_MILLISECONDS = 10 * 60 * 1000;
178
- const SKC_GOAL_SNAPSHOT_MAX_FUTURE_SKEW_MILLISECONDS = 60 * 1000;
179
-
180
174
  const SCHEDULABLE_STATUSES = new Set<UltragoalGoalStatus>(["pending", "active", "failed"]);
175
+ const COMPLETE_CHECKPOINT_ALLOWED_PRE_STATUSES = new Set<UltragoalGoalStatus>(["active", "failed"]);
176
+
181
177
  const NATIVE_STEERING_KINDS = [
182
178
  "add_subgoal",
183
179
  "split_subgoal",
@@ -570,7 +566,6 @@ function buildCompletionReceipt(input: {
570
566
  receiptKind: UltragoalReceiptKind;
571
567
  beforeStatus: UltragoalGoalStatus;
572
568
  qualityGateJson: JsonObject;
573
- skcGoalJson: JsonObject;
574
569
  now: string;
575
570
  checkpointLedgerEventId: string;
576
571
  }): UltragoalCompletionVerification {
@@ -593,7 +588,6 @@ function buildCompletionReceipt(input: {
593
588
  skcGoalMode: input.plan.skcGoalMode,
594
589
  skcObjective: input.plan.skcObjective,
595
590
  qualityGateHash: hashStructuredValue(input.qualityGateJson),
596
- skcGoalSnapshotHash: hashStructuredValue(input.skcGoalJson),
597
591
  planGeneration: generation.planGeneration,
598
592
  basis: generation.basis,
599
593
  checkpointLedgerEventId: input.checkpointLedgerEventId,
@@ -2470,93 +2464,24 @@ async function readRequiredCompletionQualityGate(
2470
2464
  return gate;
2471
2465
  }
2472
2466
 
2473
- function snapshotUpdatedAtMilliseconds(value: unknown): number | null {
2474
- if (typeof value === "number" && Number.isFinite(value)) return value;
2475
- if (typeof value !== "string" || value.trim().length === 0) return null;
2476
- const trimmed = value.trim();
2477
- if (/^\d+$/.test(trimmed)) {
2478
- const parsed = Number.parseInt(trimmed, 10);
2479
- return Number.isFinite(parsed) ? parsed : null;
2480
- }
2481
- const parsed = Date.parse(trimmed);
2482
- return Number.isFinite(parsed) ? parsed : null;
2483
- }
2484
-
2485
- function singleSessionLeafId(entries: readonly SessionEntry[]): string | undefined {
2486
- if (entries.length === 0) return undefined;
2487
- const parentIds = new Set(
2488
- entries.map(entry => entry.parentId).filter((parentId): parentId is string => typeof parentId === "string"),
2489
- );
2490
- const leafIds = entries.map(entry => entry.id).filter(id => !parentIds.has(id));
2491
- return leafIds.length === 1 ? leafIds[0] : undefined;
2492
- }
2493
-
2494
- async function readCurrentSessionSkcGoalSnapshot(): Promise<unknown | undefined> {
2495
- const sessionFile = process.env[SKC_SESSION_FILE_ENV]?.trim();
2496
- if (!sessionFile) return undefined;
2497
- const fileEntries = await loadEntriesFromFile(sessionFile);
2498
- const entries = fileEntries.filter((entry): entry is SessionEntry => entry.type !== "session");
2499
- const leafId = singleSessionLeafId(entries);
2500
- if (!leafId) return undefined;
2501
- const context = buildSessionContext(entries, leafId);
2502
- if (context.mode !== "goal" && context.mode !== "goal_paused") return undefined;
2503
- const goal = normalizeGoal(context.modeData?.goal);
2504
- return goal ? { goal } : undefined;
2505
- }
2506
- async function readSkcGoalSnapshot(input: {
2507
- cwd: string;
2508
- value: string | undefined;
2509
- plan: UltragoalPlan;
2510
- goal?: UltragoalGoal;
2511
- required: boolean;
2512
- errorPrefix: string;
2513
- allowCompletedLegacyBlocker?: boolean;
2514
- }): Promise<unknown> {
2515
- const snapshot = input.value?.trim()
2516
- ? await readStructuredValue(input.cwd, input.value)
2517
- : await readCurrentSessionSkcGoalSnapshot();
2518
- if (snapshot === undefined) {
2519
- if (!input.required) return undefined;
2520
- throw new Error(
2521
- `${input.errorPrefix} require an active SKC goal-mode snapshot from the current session or --skc-goal-json; this is the SKC goal-mode receipt, not the .skc/ultragoal/goals.json goal record`,
2522
- );
2523
- }
2524
- const snapshotObject = qualityGateObject(snapshot);
2525
- const detailsObject = qualityGateObject(snapshotObject?.details);
2526
- const goalObject = qualityGateObject(snapshotObject?.goal) ?? qualityGateObject(detailsObject?.goal);
2527
- if (!goalObject)
2528
- throw new Error(
2529
- `${input.errorPrefix} require --skc-goal-json with a goal object from goal({"op":"get"}); pass the active SKC goal-mode snapshot, not the .skc/ultragoal/goals.json goal record`,
2530
- );
2531
- const updatedAt = snapshotUpdatedAtMilliseconds(goalObject.updatedAt);
2532
- if (!updatedAt)
2467
+ function validateCompleteCheckpointTargetGoal(goal: UltragoalGoal): void {
2468
+ if (COMPLETE_CHECKPOINT_ALLOWED_PRE_STATUSES.has(goal.status)) return;
2469
+ if (goal.status === "pending") {
2533
2470
  throw new Error(
2534
- `${input.errorPrefix} require --skc-goal-json goal.updatedAt as epoch milliseconds or an ISO timestamp from goal({"op":"get"}); pass the active SKC goal-mode snapshot, not the .skc/ultragoal/goals.json goal record`,
2471
+ `Cannot checkpoint ${goal.id} as complete while its durable goals.json status is pending; start the goal before completing it.`,
2535
2472
  );
2536
- const nowMilliseconds = Date.now();
2537
- if (updatedAt < nowMilliseconds - SKC_GOAL_SNAPSHOT_MAX_AGE_MILLISECONDS) {
2538
- throw new Error(`${input.errorPrefix} require a fresh --skc-goal-json snapshot`);
2539
- }
2540
- if (updatedAt > nowMilliseconds + SKC_GOAL_SNAPSHOT_MAX_FUTURE_SKEW_MILLISECONDS) {
2541
- throw new Error(`${input.errorPrefix} require --skc-goal-json goal.updatedAt that is not from the future`);
2542
- }
2543
- const objective = typeof goalObject.objective === "string" ? goalObject.objective : "";
2544
- const expectedObjectives = new Set([input.plan.skcObjective, ...(input.plan.skcObjectiveAliases ?? [])]);
2545
- if (input.plan.skcGoalMode === "per-story" && input.goal?.objective) {
2546
- expectedObjectives.add(input.goal.objective);
2547
- }
2548
- if (input.allowCompletedLegacyBlocker && goalObject.status === "complete" && !expectedObjectives.has(objective)) {
2549
- return snapshot;
2550
2473
  }
2551
- if (!expectedObjectives.has(objective)) {
2474
+ if (goal.status === "complete") {
2552
2475
  throw new Error(
2553
- `${input.errorPrefix} require --skc-goal-json objective to match the active SKC goal-mode objective from goal({"op":"get"}), not the .skc/ultragoal/goals.json goal ${input.goal?.id ?? "record"}`,
2476
+ `Cannot checkpoint ${goal.id} as complete with different evidence because its durable goals.json status is already complete.`,
2554
2477
  );
2555
2478
  }
2556
- if (goalObject.status !== "active") {
2557
- throw new Error(`${input.errorPrefix} require --skc-goal-json goal.status to be active`);
2479
+ if (goal.status === "superseded") {
2480
+ throw new Error(`Cannot checkpoint ${goal.id} as complete because its durable goals.json status is superseded.`);
2558
2481
  }
2559
- return snapshot;
2482
+ throw new Error(
2483
+ `Cannot checkpoint ${goal.id} as complete while its durable goals.json status is ${goal.status}; only active or retryable failed goals can be completed.`,
2484
+ );
2560
2485
  }
2561
2486
 
2562
2487
  export async function checkpointUltragoalGoal(input: {
@@ -2564,7 +2489,6 @@ export async function checkpointUltragoalGoal(input: {
2564
2489
  goalId: string;
2565
2490
  status: UltragoalGoalStatus;
2566
2491
  evidence: string;
2567
- skcGoalJson?: string;
2568
2492
  qualityGateJson?: string;
2569
2493
  }): Promise<UltragoalPlan> {
2570
2494
  const plan = await readUltragoalPlan(input.cwd);
@@ -2593,6 +2517,7 @@ export async function checkpointUltragoalGoal(input: {
2593
2517
  // instead of silently dropping it.
2594
2518
  return plan;
2595
2519
  }
2520
+ if (input.status === "complete") validateCompleteCheckpointTargetGoal(goal);
2596
2521
  const changeSet = input.status === "complete" ? await computeCheckpointChangeSet(input.cwd) : undefined;
2597
2522
  const qualityGateJson =
2598
2523
  input.status === "complete"
@@ -2615,25 +2540,6 @@ export async function checkpointUltragoalGoal(input: {
2615
2540
  }
2616
2541
  }
2617
2542
  const receiptKind = input.status === "complete" ? chooseReceiptKind(plan, goal, input.status) : null;
2618
- const skcGoalJson =
2619
- input.status === "complete"
2620
- ? await readSkcGoalSnapshot({
2621
- cwd: input.cwd,
2622
- value: input.skcGoalJson,
2623
- plan,
2624
- goal,
2625
- required: true,
2626
- errorPrefix: "complete checkpoints",
2627
- })
2628
- : await readSkcGoalSnapshot({
2629
- cwd: input.cwd,
2630
- value: input.skcGoalJson,
2631
- plan,
2632
- goal,
2633
- required: false,
2634
- errorPrefix: `${input.status} checkpoints`,
2635
- allowCompletedLegacyBlocker: input.status === "blocked",
2636
- });
2637
2543
  const pendingCheckpointEventId = crypto.randomUUID();
2638
2544
  if (input.status === "complete" && receiptKind && qualityGateJson && !Array.isArray(qualityGateJson)) {
2639
2545
  goal.completionVerification = buildCompletionReceipt({
@@ -2643,7 +2549,6 @@ export async function checkpointUltragoalGoal(input: {
2643
2549
  receiptKind,
2644
2550
  beforeStatus,
2645
2551
  qualityGateJson: qualityGateJson as JsonObject,
2646
- skcGoalJson: skcGoalJson as JsonObject,
2647
2552
  now,
2648
2553
  checkpointLedgerEventId: pendingCheckpointEventId,
2649
2554
  });
@@ -2662,7 +2567,6 @@ export async function checkpointUltragoalGoal(input: {
2662
2567
  goalId: goal.id,
2663
2568
  status: input.status,
2664
2569
  evidence,
2665
- skcGoalJson,
2666
2570
  qualityGateJson,
2667
2571
  completionVerification: goal.completionVerification,
2668
2572
  });
@@ -2682,7 +2586,6 @@ export async function checkpointAndContinueUltragoalGoal(input: {
2682
2586
  goalId: string;
2683
2587
  status: UltragoalGoalStatus;
2684
2588
  evidence: string;
2685
- skcGoalJson?: string;
2686
2589
  qualityGateJson?: string;
2687
2590
  advanceNext?: boolean;
2688
2591
  retryFailed?: boolean;
@@ -3091,19 +2994,14 @@ export async function recordUltragoalReviewBlockers(input: {
3091
2994
  title: string;
3092
2995
  objective: string;
3093
2996
  evidence: string;
3094
- skcGoalJson?: string;
3095
2997
  }): Promise<UltragoalPlan> {
3096
2998
  const objective = input.objective.trim();
3097
2999
  if (!objective) throw new Error("record-review-blockers --objective is required");
3098
- if (!input.skcGoalJson?.trim()) {
3099
- throw new Error('record-review-blockers require --skc-goal-json from a fresh active goal({"op":"get"}) snapshot');
3100
- }
3101
3000
  const plan = await checkpointUltragoalGoal({
3102
3001
  cwd: input.cwd,
3103
3002
  goalId: input.goalId,
3104
3003
  status: "review_blocked",
3105
3004
  evidence: input.evidence,
3106
- skcGoalJson: input.skcGoalJson,
3107
3005
  });
3108
3006
  const persistedPlan = await readUltragoalPlan(input.cwd);
3109
3007
  if (persistedPlan?.state_revision !== undefined) plan.state_revision = persistedPlan.state_revision;
@@ -3551,7 +3449,6 @@ const FLAGS_WITH_VALUES = new Set([
3551
3449
  "--goal-id",
3552
3450
  "--status",
3553
3451
  "--evidence",
3554
- "--skc-goal-json",
3555
3452
  "--quality-gate-json",
3556
3453
  "--executor-qa-json",
3557
3454
  "--executor-qa",
@@ -3605,15 +3502,12 @@ function renderUltragoalHelp(args: readonly string[]): string | null {
3605
3502
  " --status=<value> pending|active|complete|failed|blocked|review_blocked|superseded",
3606
3503
  " --evidence=<value> Completion or checkpoint evidence text",
3607
3504
  " --quality-gate-json=<value> JSON string or path for complete checkpoints",
3608
- " --skc-goal-json=<value> Optional JSON/path override for current goal snapshot; omitted complete checkpoints read current session goal state",
3609
3505
  " --json Output a machine-readable receipt",
3610
3506
  "",
3611
3507
  "COMPLETE CHECKPOINT RECEIPTS",
3612
3508
  " --quality-gate-json must be an object with architectReview, executorQa, and iteration.",
3613
3509
  " executorQa.contractCoverage[] rows require an obligation field; description is not a substitute.",
3614
- " Complete checkpoints use the current session's active SKC goal-mode snapshot when --skc-goal-json is omitted.",
3615
- " Explicit --skc-goal-json values must contain an active SKC goal-mode snapshot, not the .skc/ultragoal/goals.json goal record.",
3616
- " goal.updatedAt may be epoch milliseconds or an ISO timestamp and must be fresh.",
3510
+ " Complete checkpoints validate the target durable goals.json record before writing a receipt.",
3617
3511
  "",
3618
3512
  "EXAMPLES",
3619
3513
  ' $ skc ultragoal checkpoint --goal-id G001 --status blocked --evidence "waiting on review"',
@@ -3949,7 +3843,6 @@ async function dispatchUltragoalCommand(args: string[], cwd: string): Promise<Ul
3949
3843
  goalId,
3950
3844
  status,
3951
3845
  evidence,
3952
- skcGoalJson: flagValue(args, "--skc-goal-json"),
3953
3846
  qualityGateJson: flagValue(args, "--quality-gate-json"),
3954
3847
  advanceNext: status === "complete",
3955
3848
  });
@@ -3981,7 +3874,6 @@ async function dispatchUltragoalCommand(args: string[], cwd: string): Promise<Ul
3981
3874
  title: flagValue(args, "--title") ?? "Resolve final code-review blockers",
3982
3875
  objective: flagValue(args, "--objective") ?? "",
3983
3876
  evidence: flagValue(args, "--evidence") ?? "",
3984
- skcGoalJson: flagValue(args, "--skc-goal-json"),
3985
3877
  });
3986
3878
  const goal = plan.goals.at(-1);
3987
3879
  return {
@@ -1479,14 +1479,6 @@
1479
1479
  "required": true,
1480
1480
  "type": "string"
1481
1481
  },
1482
- {
1483
- "appliesToVerbs": [
1484
- "checkpoint",
1485
- "record-review-blockers"
1486
- ],
1487
- "name": "skc-goal-json",
1488
- "type": "string"
1489
- },
1490
1482
  {
1491
1483
  "appliesToVerbs": [
1492
1484
  "checkpoint"
@@ -271,7 +271,6 @@ export const WORKFLOW_MANIFEST: Record<CanonicalSkcWorkflowSkill, SkillManifest>
271
271
  required: true,
272
272
  appliesToVerbs: ["checkpoint", "record-review-blockers", "steer", "classify-blocker"],
273
273
  },
274
- { name: "skc-goal-json", type: "string", appliesToVerbs: ["checkpoint", "record-review-blockers"] },
275
274
  { name: "quality-gate-json", type: "string", appliesToVerbs: ["checkpoint"] },
276
275
  { name: "goal-id", type: "string", appliesToVerbs: ["steer"] },
277
276
  { name: "goal-id", type: "string", appliesToVerbs: ["classify-blocker"] },