@spexcode/spec-cli 0.6.6 → 0.6.8

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 (88) hide show
  1. package/bin/spex.mjs +56 -19
  2. package/dist/cli.js +102 -59
  3. package/dist/client.d.ts +1 -3
  4. package/dist/client.js +49 -30
  5. package/dist/codex-runtime-generations.d.ts +11 -0
  6. package/dist/codex-runtime-generations.js +46 -9
  7. package/dist/delivery-lock.d.ts +2 -0
  8. package/dist/delivery-lock.js +58 -0
  9. package/dist/doctor.js +53 -11
  10. package/dist/execution-trace.d.ts +1 -0
  11. package/dist/execution-trace.js +2 -2
  12. package/dist/gateway-hub.js +2 -1
  13. package/dist/gateway.js +6 -3
  14. package/dist/graphCache.js +32 -2
  15. package/dist/graphSnapshot.js +57 -2
  16. package/dist/graphStream.d.ts +2 -0
  17. package/dist/graphStream.js +83 -3
  18. package/dist/guide.js +20 -7
  19. package/dist/harness-select.js +16 -3
  20. package/dist/harness.d.ts +15 -3
  21. package/dist/harness.js +331 -50
  22. package/dist/help.js +11 -8
  23. package/dist/hook-prompts.js +8 -0
  24. package/dist/host-resources.js +29 -8
  25. package/dist/host.d.ts +7 -0
  26. package/dist/host.js +93 -0
  27. package/dist/index.js +324 -22
  28. package/dist/init.js +1 -1
  29. package/dist/lint.js +70 -35
  30. package/dist/listen.d.ts +3 -2
  31. package/dist/listen.js +14 -2
  32. package/dist/machine-peer.js +1 -1
  33. package/dist/materialize.d.ts +2 -2
  34. package/dist/materialize.js +176 -35
  35. package/dist/pty-bridge.js +14 -14
  36. package/dist/reviews.js +12 -7
  37. package/dist/runtime-ownership.d.ts +11 -0
  38. package/dist/runtime-ownership.js +79 -1
  39. package/dist/session-application.d.ts +23 -0
  40. package/dist/session-application.js +189 -0
  41. package/dist/session-declarations.js +13 -1
  42. package/dist/session-files.d.ts +6 -0
  43. package/dist/session-files.js +13 -1
  44. package/dist/session-follow.js +39 -22
  45. package/dist/session-record-lock.d.ts +3 -0
  46. package/dist/session-record-lock.js +94 -0
  47. package/dist/session-runtime-adapter.d.ts +44 -0
  48. package/dist/session-runtime-adapter.js +37 -0
  49. package/dist/session-timeline.d.ts +25 -2
  50. package/dist/session-timeline.js +68 -11
  51. package/dist/session-web.js +4 -4
  52. package/dist/sessions.d.ts +108 -15
  53. package/dist/sessions.js +1465 -744
  54. package/dist/source-list.d.ts +13 -0
  55. package/dist/source-list.js +99 -0
  56. package/dist/source-read.d.ts +16 -0
  57. package/dist/source-read.js +84 -0
  58. package/dist/spec-attachments.d.ts +7 -0
  59. package/dist/spec-attachments.js +89 -0
  60. package/dist/spec-body-edit.d.ts +23 -0
  61. package/dist/spec-body-edit.js +138 -0
  62. package/dist/supervise.js +15 -6
  63. package/dist/transcript-reader.d.ts +36 -0
  64. package/dist/transcript-reader.js +251 -0
  65. package/hooks/dispatch.sh +19 -31
  66. package/hooks/harness.sh +6 -6
  67. package/package.json +6 -6
  68. package/templates/hooks/post-checkout +4 -2
  69. package/templates/hooks/post-merge +2 -1
  70. package/templates/hooks/pre-commit +5 -3
  71. package/templates/hooks/reference-transaction +5 -3
  72. package/templates/spec/project/.plugins/commands/spec.md +2 -7
  73. package/templates/spec/project/.plugins/core/idle/idle.sh +4 -10
  74. package/templates/spec/project/.plugins/core/idle/spec.md +1 -1
  75. package/templates/spec/project/.plugins/core/mark-active/mark-active.sh +22 -24
  76. package/templates/spec/project/.plugins/core/mark-active/spec.md +10 -2
  77. package/templates/spec/project/.plugins/core/session-fail/fail.sh +8 -7
  78. package/templates/spec/project/.plugins/core/session-fail/spec.md +3 -1
  79. package/templates/spec/project/.plugins/core/session-listen/session-listen.sh +133 -0
  80. package/templates/spec/project/.plugins/core/session-listen/spec.md +36 -0
  81. package/templates/spec/project/.plugins/core/spec.md +2 -0
  82. package/templates/spec/project/.plugins/core/stop-gate/spec.md +1 -1
  83. package/templates/spec/project/.plugins/core/stop-gate/stop-gate.sh +17 -20
  84. package/templates/spec/project/.plugins/skills/merge/spec.md +33 -0
  85. package/templates/spec/project/.plugins/skills/spec.md +2 -6
  86. package/templates/spec/project/.plugins/spec.md +7 -0
  87. package/hooks/compat/mark-active-0.5.2-eef1.fixture +0 -53
  88. package/hooks/compat/mark-active-sed-v0.fixture +0 -46
@@ -6,11 +6,17 @@ const KNOWN = HARNESSES.map((h) => h.id);
6
6
  // the one-line repair every missing-selection error carries — the field is REQUIRED, never defaulted,
7
7
  // because with many harnesses a silent "deliver everywhere" would litter the adopter's tree with artifacts
8
8
  // for tools they never installed.
9
- const MISSING = `spexcode.json has no "harnesses" field — the delivery targets are an EXPLICIT choice, never a default. Declare it, e.g. "harnesses": ["claude"] — members are native ids (${KNOWN.join(', ')}) or {"plugin":"<folder>"}. Fresh adoption: \`spex init --harness <ids>\` stamps it.`;
9
+ const MISSING = `spexcode.json has no "harnesses" field — the delivery targets are an EXPLICIT choice, never a default. Declare it, e.g. "harnesses": ["claude"] — members are native ids (${KNOWN.join(', ')}) or {"plugin":"<folder>"}, and [] means deliver into NO harness (the L0-only posture, \`spex init --harness none\`). Fresh adoption: \`spex init --harness <ids>\` stamps it.`;
10
+ // the CLI spelling of the empty set. `--harness none` is a whole-selection word, not a member: mixing it with
11
+ // a real target is a contradiction, so it only translates when it IS the entire flag (otherwise it falls
12
+ // through as an unknown id and resolveHarnessTargets says why).
13
+ const NO_DELIVERY = 'none';
10
14
  // parse `spex init --harness <spec>` into the raw JSON members the `harnesses` field carries: a comma-
11
15
  // separated list where `plugin:<folder>` means a plugin target and anything else is a native id. Pure
12
16
  // spelling translation — legality (unknown ids, plugin exclusivity) stays with resolveHarnessTargets.
13
17
  export function parseHarnessFlag(spec) {
18
+ if (spec.trim() === NO_DELIVERY)
19
+ return [];
14
20
  return spec.split(',').map((s) => s.trim()).filter(Boolean)
15
21
  .map((s) => (s.startsWith('plugin:') ? { plugin: s.slice('plugin:'.length) } : s));
16
22
  }
@@ -22,15 +28,22 @@ export function resolveHarnessTargets(raw) {
22
28
  throw new Error(MISSING);
23
29
  if (!Array.isArray(raw))
24
30
  throw new Error(`spexcode.json "harnesses" must be an ARRAY of targets (got ${typeof raw}). Members are native ids (${KNOWN.join(', ')}) or {"plugin":"<folder>"}.`);
31
+ // THE EMPTY SET IS A LEGAL, EXPLICIT CHOICE: deliver into no harness at all. That is the L0-only posture —
32
+ // the spec tree, the lint, and the git hooks, with zero artifacts written into any agent's config. It is
33
+ // distinct from a MISSING field (undefined/null, rejected above): [] is somebody saying "none", not
34
+ // somebody forgetting to choose. Everything downstream already handles it — materialize's erase phase
35
+ // prunes whatever a previous selection delivered, and the published allowlist admits nothing.
25
36
  if (raw.length === 0)
26
- throw new Error(`spexcode.json "harnesses" is EMPTY — list at least one target: native ids (${KNOWN.join(', ')}) or {"plugin":"<folder>"}.`);
37
+ return [];
27
38
  const targets = [];
28
39
  for (const m of raw) {
29
40
  if (typeof m === 'string') {
30
41
  if (m === 'plugin')
31
42
  throw new Error(`spexcode.json "harnesses": a plugin target needs an EXPLICIT landing folder — write {"plugin":"<folder>"} (e.g. {"plugin":".adopter-a"}), not the bare string "plugin", because each host agent reads a different plugins dir.`);
43
+ if (m === NO_DELIVERY)
44
+ throw new Error(`spexcode.json "harnesses": "${NO_DELIVERY}" is not a member — it is the CLI spelling of the WHOLE selection being empty. Write "harnesses": [] for no delivery, or drop "${NO_DELIVERY}" and keep the real targets (\`spex init --harness none\` cannot be combined with another id).`);
32
45
  if (!KNOWN.includes(m))
33
- throw new Error(`spexcode.json "harnesses": unknown harness id "${m}" — known native ids are ${KNOWN.join(', ')}, or use {"plugin":"<folder>"}.`);
46
+ throw new Error(`spexcode.json "harnesses": unknown harness id "${m}" — known native ids are ${KNOWN.join(', ')}, or use {"plugin":"<folder>"}; [] (\`--harness ${NO_DELIVERY}\`) delivers into no harness at all.`);
34
47
  targets.push({ kind: 'native', id: m });
35
48
  }
36
49
  else if (m && typeof m === 'object' && !Array.isArray(m) && 'plugin' in m) {
package/dist/harness.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { type SlashCommand } from './slash-commands.js';
2
2
  import { type ExecutionTrace, type ExecutionTurn } from './execution-trace.js';
3
+ import { type TranscriptRead, type TranscriptRange } from './transcript-reader.js';
3
4
  import { type HarnessId } from '@spexcode/spec-core';
4
5
  export type { HarnessId } from '@spexcode/spec-core';
5
6
  export type HarnessLivenessRecord = {
@@ -23,6 +24,7 @@ export type TurnFailure = {
23
24
  export type FailureSubscription = {
24
25
  close(): void;
25
26
  readonly closed: Promise<string | null>;
27
+ readonly ready?: Promise<boolean>;
26
28
  };
27
29
  export type DeliveryTransportState = {
28
30
  kind: 'reachable';
@@ -58,7 +60,7 @@ export type SharedRuntimeDescriptor = {
58
60
  mutationGuard?: (targetReferenceId: string, opts?: {
59
61
  coldReceipt?: unknown;
60
62
  }) => Promise<SharedRuntimeMutationGuard>;
61
- probe(): Promise<SharedRuntimeProbe>;
63
+ probe(referenceIds?: readonly string[]): Promise<SharedRuntimeProbe>;
62
64
  };
63
65
  export type SharedRuntimeMutationGuard = {
64
66
  healthy: boolean;
@@ -122,6 +124,7 @@ export interface Harness {
122
124
  readonly ownsRendezvous: boolean;
123
125
  readonly paneTitleIsSelfSummary: boolean;
124
126
  executionTrace(threadId: string, turn: ExecutionTurn | null): ExecutionTrace | null;
127
+ readTranscript(threadId: string, range: TranscriptRange): Promise<TranscriptRead>;
125
128
  launchCmd(id: string, runtimeDir?: string, cmd?: string): string;
126
129
  baseCmd(cmd?: string): string;
127
130
  oneShotTurn?(prompt: string, cmd?: string): {
@@ -139,8 +142,10 @@ export interface Harness {
139
142
  agentDir(proj: string): string | null;
140
143
  shim(dispatch: string, spex: string): {
141
144
  content: string;
145
+ hooks?: Record<string, unknown[]>;
142
146
  cmd: (e: string) => string;
143
147
  };
148
+ shimOwnership: 'exclusive' | 'shared-json';
144
149
  writeTrust(proj: string, cmdFor: (e: string) => string): readonly string[];
145
150
  slashCommands(): SlashCommand[];
146
151
  liveness(rec: HarnessLivenessRecord, tmuxAlive: boolean, runtimeDir?: string, pane?: PaneProbe, socketLive?: boolean): 'online' | 'offline';
@@ -226,7 +231,7 @@ export declare const rendezvousListening: (id: string, timeoutMs?: number) => Pr
226
231
  export declare const codexAppServerSock: (dir?: string) => string;
227
232
  export declare const codexAppServerPid: (dir?: string) => string;
228
233
  export declare const codexAppServerReceipt: (dir?: string) => string;
229
- export declare function deliverViaRendezvous(id: string, text: string, mid?: string): Promise<DispatchResult>;
234
+ export declare function deliverViaRendezvous(id: string, text: string, mid?: string, wallMs?: number): Promise<DispatchResult>;
230
235
  export declare function deliverViaClaudeRendezvous(id: string, text: string, mid?: string, runtimeDir?: string): Promise<DispatchResult>;
231
236
  export declare function deliverViaSocketOrWake(id: string, text: string, mid: string | undefined, coldWake: () => Promise<DispatchResult>, unprovenError: string): Promise<DispatchResult>;
232
237
  type JsonRpc = {
@@ -242,6 +247,7 @@ type JsonRpc = {
242
247
  export declare function codexHandshakeMessages(threadId: string): JsonRpc[];
243
248
  export declare function codexInjectMessage(threadId: string, text: string, cwd: string | undefined, activeTurnId: string | null, id?: number, clientUserMessageId?: string): JsonRpc;
244
249
  export declare function activeTurnIdFromThread(readResult: unknown): string | null;
250
+ export declare function codexObservedActiveTurnId(threadId: string): string | null;
245
251
  export declare function codexBinary(codexCmd: string): string;
246
252
  export type CodexThreadPolicy = {
247
253
  approvalPolicy?: 'untrusted' | 'on-request' | 'never';
@@ -253,6 +259,7 @@ export declare function reportHeadlessTurnExit(id: string, harness: string, code
253
259
  export declare function headlessTurnFailureShell(harness: string, swallow?: boolean): string;
254
260
  export declare function sessionIdentityEnvVars(): string[];
255
261
  export declare function codexLaunchCommand(id: string, codexCmd?: string, serverCmd?: string, dir?: string, attachTui?: boolean): string;
262
+ export declare const CODEX_TURN_OBSERVER_SUBSCRIBE_MS = 30000;
256
263
  export declare function codexTurnFailureObserver(rec: HarnessDeliveryRecord, onFailure: (failure: TurnFailure) => void): FailureSubscription;
257
264
  export declare const CODEX_THREAD_SOURCE_KINDS: readonly ["cli", "vscode", "exec", "appServer", "subAgent", "subAgentReview", "subAgentCompact", "subAgentThreadSpawn", "subAgentOther", "unknown"];
258
265
  export declare function codexLoadedReferenceIds(sock: string): Promise<{
@@ -282,7 +289,7 @@ export declare function codexSharedRuntimeProbe(dir?: string, endpoint?: Readonl
282
289
  receiptFile: string;
283
290
  logFile: string;
284
291
  socketPath: string;
285
- }>): Promise<SharedRuntimeProbe>;
292
+ }>, referenceIds?: readonly string[]): Promise<SharedRuntimeProbe>;
286
293
  export declare function codexStartThreadParams(cwd?: string, bypassHookTrust?: boolean, shellEnv?: Record<string, string>, policy?: CodexThreadPolicy): Record<string, unknown>;
287
294
  export declare function codexStartThread(sock: string, cwd?: string, bypassHookTrust?: boolean, shellEnv?: Record<string, string>, policy?: CodexThreadPolicy): Promise<{
288
295
  ok: true;
@@ -301,6 +308,11 @@ export declare function codexRolloutBytes(threadId: string, root?: string): {
301
308
  export declare function waitForCodexRollout(threadId: string, timeoutMs?: number): Promise<boolean>;
302
309
  export declare function writeManagedBlock(file: string, body: string, comment?: readonly [string, string]): boolean;
303
310
  export declare function removeManagedBlock(file: string, comment?: readonly [string, string], deleteIfEmpty?: boolean): void;
311
+ export declare function writeManagedJsonHooks(file: string, hooks: Record<string, unknown[]>): boolean;
312
+ export declare function removeManagedJsonHooks(file: string): void;
313
+ export declare function sharedShimHasHostContent(file: string): boolean;
314
+ export declare const GENERATED_MARK = "<!-- spexcode:generated -->";
315
+ export declare function isGeneratedArtifact(file: string): boolean;
304
316
  export declare function codexHookHash(snakeEvent: string, command: string, timeout?: number, asyncFlag?: boolean): string;
305
317
  export declare function writeCodexTrust(proj: string, events: readonly string[], cmdFor: (e: string) => string): string;
306
318
  export declare function paneTreeRunsCodex(pane?: PaneProbe): boolean;