@astrosheep/square 0.3.11 → 0.3.13

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 (127) hide show
  1. package/codex-plugin/.codex-plugin/plugin.json +1 -1
  2. package/codex-plugin/hooks/hooks.json +9 -0
  3. package/dist/activity-feed.d.ts +14 -0
  4. package/dist/activity-feed.js +5 -5
  5. package/dist/activity.d.ts +9 -0
  6. package/dist/activity.js +112 -96
  7. package/dist/artifact.d.ts +20 -0
  8. package/dist/artifact.js +317 -648
  9. package/dist/automatic-session.d.ts +5 -0
  10. package/dist/automatic-session.js +86 -0
  11. package/dist/boundary-presentation.d.ts +5 -0
  12. package/dist/boundary-presentation.js +6 -5
  13. package/dist/claude-hook.d.ts +9 -0
  14. package/dist/claude-hook.js +35 -3
  15. package/dist/cli/context.d.ts +35 -0
  16. package/dist/cli/context.js +26 -31
  17. package/dist/cli/harness-command.d.ts +28 -0
  18. package/dist/cli/harness-command.js +30 -15
  19. package/dist/cli/maintenance-commands.d.ts +7 -0
  20. package/dist/cli/maintenance-commands.js +12 -69
  21. package/dist/cli/meta-commands.d.ts +5 -0
  22. package/dist/cli/observation-commands.d.ts +20 -0
  23. package/dist/cli/observation-commands.js +159 -139
  24. package/dist/cli/program.d.ts +2 -0
  25. package/dist/cli/program.js +3 -12
  26. package/dist/cli/registry.d.ts +9 -0
  27. package/dist/cli/registry.js +2 -4
  28. package/dist/cli/square-commands.d.ts +34 -0
  29. package/dist/cli/square-commands.js +90 -84
  30. package/dist/cmd/notify-once.d.ts +2 -0
  31. package/dist/codex-hook.d.ts +10 -0
  32. package/dist/codex-hook.js +35 -3
  33. package/dist/decisions.d.ts +92 -0
  34. package/dist/decisions.js +93 -81
  35. package/dist/delivery-health.d.ts +21 -0
  36. package/dist/delivery-health.js +41 -34
  37. package/dist/delivery.d.ts +81 -0
  38. package/dist/delivery.js +71 -69
  39. package/dist/file-lock.d.ts +6 -0
  40. package/dist/harness-claude.d.ts +19 -0
  41. package/dist/harness-claude.js +8 -3
  42. package/dist/harness-codex.d.ts +29 -0
  43. package/dist/harness-codex.js +2 -2
  44. package/dist/harness-links.d.ts +17 -0
  45. package/dist/harness-links.js +2 -2
  46. package/dist/harness-pi.d.ts +11 -0
  47. package/dist/harness-pi.js +2 -2
  48. package/dist/harness-stage.d.ts +6 -0
  49. package/dist/harness.d.ts +20 -0
  50. package/dist/harness.js +5 -3
  51. package/dist/help.d.ts +6 -0
  52. package/dist/help.js +12 -13
  53. package/dist/identity.d.ts +13 -0
  54. package/dist/inbox.d.ts +2 -0
  55. package/dist/inbox.js +15 -19
  56. package/dist/index.d.ts +13 -0
  57. package/dist/index.js +31 -67
  58. package/dist/landing.d.ts +10 -0
  59. package/dist/landing.js +95 -0
  60. package/dist/list.d.ts +1 -0
  61. package/dist/list.js +39 -61
  62. package/dist/model.d.ts +147 -0
  63. package/dist/model.js +12 -8
  64. package/dist/notifications.d.ts +30 -0
  65. package/dist/notifications.js +83 -91
  66. package/dist/open-square.d.ts +11 -0
  67. package/dist/open-square.js +4 -0
  68. package/dist/participant-identity.d.ts +1 -0
  69. package/dist/participant-identity.js +3 -0
  70. package/dist/paseo-connection.d.ts +15 -0
  71. package/dist/paseo-delivery.d.ts +14 -0
  72. package/dist/paseo-state.d.ts +11 -0
  73. package/dist/paseo-timeline.d.ts +16 -0
  74. package/dist/presence.d.ts +3 -0
  75. package/dist/presence.js +42 -0
  76. package/dist/presentation.d.ts +91 -0
  77. package/dist/presentation.js +52 -63
  78. package/dist/presented.d.ts +11 -0
  79. package/dist/registry.d.ts +58 -0
  80. package/dist/registry.js +36 -3
  81. package/dist/routes.d.ts +42 -0
  82. package/dist/runtime.d.ts +45 -0
  83. package/dist/runtime.js +48 -55
  84. package/dist/search.d.ts +11 -0
  85. package/dist/square-core.d.ts +111 -0
  86. package/dist/square-core.js +63 -9
  87. package/dist/square-facade.d.ts +86 -0
  88. package/dist/square-facade.js +1 -0
  89. package/dist/square-file-adapter.d.ts +18 -0
  90. package/dist/square-file-adapter.js +70 -0
  91. package/dist/square-storage.d.ts +20 -0
  92. package/dist/square-storage.js +171 -0
  93. package/dist/square-wiring.d.ts +19 -0
  94. package/dist/square-wiring.js +70 -0
  95. package/dist/square.d.ts +2 -0
  96. package/dist/state-cell.d.ts +14 -0
  97. package/dist/state-cell.js +1 -0
  98. package/dist/stream.d.ts +3 -0
  99. package/dist/stream.js +19 -23
  100. package/dist/time.d.ts +6 -0
  101. package/dist/views.d.ts +95 -0
  102. package/dist/views.js +81 -0
  103. package/dist/wake-attempts.d.ts +39 -0
  104. package/dist/wake-attempts.js +8 -4
  105. package/dist/wake-evidence.d.ts +12 -0
  106. package/dist/wake-evidence.js +7 -10
  107. package/dist/wake-port.d.ts +20 -0
  108. package/dist/wake-sink.d.ts +12 -0
  109. package/dist/wakes.d.ts +40 -0
  110. package/dist/wakes.js +62 -0
  111. package/dist/watch.d.ts +2 -0
  112. package/dist/watch.js +89 -210
  113. package/extensions/square-opencode.js +27 -1
  114. package/extensions/square-pi.js +13 -0
  115. package/guides/participant.md +7 -174
  116. package/package.json +5 -4
  117. package/skills/brainstorm/SKILL.md +30 -30
  118. package/skills/square/.claude-plugin/plugin.json +1 -1
  119. package/skills/square/SKILL.md +56 -29
  120. package/skills/square/hooks/hooks.json +9 -0
  121. package/skills/square-feedback/SKILL.md +9 -9
  122. package/dist/compact.js +0 -26
  123. package/dist/doctor.js +0 -36
  124. package/dist/square-application.js +0 -262
  125. package/template.md +0 -4
  126. package/templates/architect.md +0 -4
  127. package/templates/brainstorm.md +0 -4
@@ -0,0 +1,5 @@
1
+ export type AutomaticProvider = 'codex' | 'claude' | 'opencode' | 'pi';
2
+ export declare function publicSquarePath(cwd: string): string;
3
+ export declare function automaticParticipant(provider: AutomaticProvider, sessionId: string, env: NodeJS.ProcessEnv): string;
4
+ export declare function automaticSessionStart(provider: AutomaticProvider, sessionId: string, cwd: string, env?: NodeJS.ProcessEnv): Promise<string | undefined>;
5
+ export declare function automaticSessionEnd(provider: AutomaticProvider, sessionId: string, cwd: string, env?: NodeJS.ProcessEnv): Promise<void>;
@@ -0,0 +1,86 @@
1
+ import { createHash } from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { openSquare } from './square-file-adapter.js';
5
+ import { closeOpenSquare } from './open-square.js';
6
+ import { Square } from './square-wiring.js';
7
+ import { entryPresentation } from './views.js';
8
+ import { canonicalSquarePath, lookupSession, lookupSessionBindings, recordSessionDone, recordSessionJoin } from './registry.js';
9
+ import { participantIdentity, renderAmbientEvent } from './presentation.js';
10
+ import { validateName } from './model.js';
11
+ const providerEnv = {
12
+ codex: 'CODEX_THREAD_ID',
13
+ claude: 'CLAUDE_CODE_SESSION_ID',
14
+ opencode: 'OPENCODE_SESSION_ID',
15
+ pi: 'SQUARE_PI_SESSION_ID',
16
+ };
17
+ export function publicSquarePath(cwd) {
18
+ return path.join(cwd, '.square', 'PUBLIC.square');
19
+ }
20
+ export function automaticParticipant(provider, sessionId, env) {
21
+ const configured = env.SQUARE_PARTICIPANT_NAME?.trim();
22
+ if (configured) {
23
+ validateName(configured);
24
+ return configured;
25
+ }
26
+ const digest = createHash('sha256').update(sessionId, 'utf8').digest('hex').slice(0, 12);
27
+ return `${provider}-${digest}`;
28
+ }
29
+ export async function automaticSessionStart(provider, sessionId, cwd, env = process.env) {
30
+ const squarePath = publicSquarePath(cwd);
31
+ if (!fs.existsSync(squarePath))
32
+ return undefined;
33
+ let reader;
34
+ try {
35
+ reader = await openSquare(squarePath);
36
+ }
37
+ catch (error) {
38
+ process.stderr.write(`! PUBLIC.square unreadable: ${error instanceof Error ? error.message : String(error)}\n`);
39
+ return undefined;
40
+ }
41
+ const name = automaticParticipant(provider, sessionId, env);
42
+ const bindings = lookupSession(sessionId);
43
+ const before = await entryPresentation(reader, name);
44
+ if (bindings.some((binding) => canonicalSquarePath(binding.squarePath) === canonicalSquarePath(squarePath) && binding.name === name) && before.joined) {
45
+ await closeOpenSquare(reader);
46
+ return undefined;
47
+ }
48
+ await closeOpenSquare(reader);
49
+ const square = await Square.at({ path: squarePath });
50
+ try {
51
+ await square.join(name);
52
+ const channel = provider === 'claude' ? 'claude-code' : provider;
53
+ recordSessionJoin(sessionId, name, squarePath, channel, { ...env, [providerEnv[provider]]: sessionId });
54
+ const afterSquare = await openSquare(squarePath);
55
+ const after = await entryPresentation(afterSquare, name, 10).finally(() => closeOpenSquare(afterSquare));
56
+ const activity = after.recentActivities.map((event) => renderAmbientEvent(event, name, {
57
+ now: Date.now(),
58
+ preview: 200,
59
+ actNumber: event.kind === 'say' ? after.sayNumbers[event.index] : undefined,
60
+ })).filter(Boolean).join('\n\n');
61
+ return [`You joined the public square as ${participantIdentity(name)}.`, after.scene, after.context ? `context\n${after.context}` : '', activity ? `recent activity\n${activity}` : ''].filter(Boolean).join('\n\n');
62
+ }
63
+ finally {
64
+ await square.close();
65
+ }
66
+ }
67
+ export async function automaticSessionEnd(provider, sessionId, cwd, env = process.env) {
68
+ const squarePath = publicSquarePath(cwd);
69
+ const channel = provider === 'claude' ? 'claude-code' : provider;
70
+ const binding = lookupSessionBindings(sessionId).find((item) => canonicalSquarePath(item.squarePath) === canonicalSquarePath(squarePath) && item.channel === channel);
71
+ if (binding === undefined || !fs.existsSync(squarePath))
72
+ return;
73
+ const reader = await openSquare(squarePath);
74
+ const joined = await entryPresentation(reader, binding.name).finally(() => closeOpenSquare(reader));
75
+ if (!joined.joined)
76
+ return;
77
+ const square = await Square.at({ path: squarePath });
78
+ try {
79
+ const participant = await square.join(binding.name);
80
+ await participant.done();
81
+ }
82
+ finally {
83
+ await square.close();
84
+ }
85
+ recordSessionDone(sessionId, binding.name, squarePath, channel, env);
86
+ }
@@ -0,0 +1,5 @@
1
+ import type { InboxMembership } from './model.js';
2
+ /** A fresh blocking catch owns only the notifications admitted by its filter. */
3
+ export declare function pendingAtBoundary(inbox: InboxMembership[]): InboxMembership[];
4
+ export declare function renderPendingAtBoundary(inbox: InboxMembership[]): string;
5
+ export declare function presentPendingAtBoundary<T>(sessionId: string, present: (context: string) => T, lookup?: (sessionId: string) => Promise<InboxMembership[]> | InboxMembership[], env?: NodeJS.ProcessEnv): Promise<T | undefined>;
@@ -1,6 +1,6 @@
1
1
  import { leaseOwnsNotification, notificationMessageId } from './delivery.js';
2
2
  import { sessionInbox } from './inbox.js';
3
- import { participantCommandPrefix } from './presentation.js';
3
+ import { participantCommandPrefix, participantIdentity } from './presentation.js';
4
4
  import { presentOnce } from './presented.js';
5
5
  const BODY_MAX = 200;
6
6
  const CONTEXT_MAX = 1200;
@@ -16,7 +16,7 @@ export function pendingAtBoundary(inbox) {
16
16
  return membership;
17
17
  return {
18
18
  ...membership,
19
- notifications: membership.notifications.filter((notification) => !leaseOwnsNotification(lease, { ...notification, recipient: membership.name })),
19
+ notifications: membership.notifications.filter((notification) => !leaseOwnsNotification(lease, notification)),
20
20
  };
21
21
  })
22
22
  .filter((membership) => membership.notifications.length > 0);
@@ -43,7 +43,7 @@ export function renderPendingAtBoundary(inbox) {
43
43
  const command = `${participantCommandPrefix(membership.squarePath, membership.name)} catch --now`;
44
44
  const id = notificationMessageId(membership.squarePath, notification.actIndex);
45
45
  const block = [
46
- `${id} · ${membership.squarePath}: @${membership.name} from @${notification.actor} (${notification.route})`,
46
+ `${id} · ${membership.squarePath}: ${participantIdentity(membership.name)} from ${participantIdentity(notification.actor)} (${notification.route})`,
47
47
  bodyPreview(notification.body),
48
48
  `Ack with: ${command}`,
49
49
  ].join('\n');
@@ -72,6 +72,7 @@ export function renderPendingAtBoundary(inbox) {
72
72
  ...footer,
73
73
  ].join('\n');
74
74
  }
75
- export function presentPendingAtBoundary(sessionId, present, lookup = sessionInbox, env = process.env) {
76
- return presentOnce(sessionId, (id) => pendingAtBoundary(lookup(id)), (inbox) => present(renderPendingAtBoundary(inbox)), env);
75
+ export async function presentPendingAtBoundary(sessionId, present, lookup = sessionInbox, env = process.env) {
76
+ const inbox = await lookup(sessionId);
77
+ return presentOnce(sessionId, () => pendingAtBoundary(inbox), (inbox) => present(renderPendingAtBoundary(inbox)), env);
77
78
  }
@@ -0,0 +1,9 @@
1
+ import type { InboxMembership } from './model.js';
2
+ export interface NativeHookInput {
3
+ session_id?: unknown;
4
+ hook_event_name?: unknown;
5
+ cwd?: unknown;
6
+ }
7
+ export declare function runClaudeHookAsync(inputText: string, env?: NodeJS.ProcessEnv): Promise<string>;
8
+ export declare function claudeHookResponse(input: NativeHookInput, lookup?: (sessionId: string) => Promise<InboxMembership[]> | InboxMembership[], env?: NodeJS.ProcessEnv): Promise<object | undefined>;
9
+ export declare function runClaudeHook(inputText: string, env?: NodeJS.ProcessEnv): Promise<string>;
@@ -1,13 +1,45 @@
1
1
  import { presentPendingAtBoundary } from './boundary-presentation.js';
2
2
  import { sessionInbox } from './inbox.js';
3
- export function claudeHookResponse(input, lookup = sessionInbox, env = process.env) {
3
+ import { automaticSessionEnd, automaticSessionStart } from './automatic-session.js';
4
+ export async function runClaudeHookAsync(inputText, env = process.env) {
5
+ let input;
6
+ try {
7
+ input = JSON.parse(inputText);
8
+ }
9
+ catch {
10
+ return '';
11
+ }
12
+ if (input === null || typeof input !== 'object')
13
+ return '';
14
+ const value = input;
15
+ if (typeof value.session_id !== 'string' || typeof value.cwd !== 'string')
16
+ return runClaudeHook(inputText, env);
17
+ if (value.hook_event_name === 'SessionStart' || value.hook_event_name === 'SessionResume') {
18
+ try {
19
+ const context = await automaticSessionStart('claude', value.session_id, value.cwd, env);
20
+ return context === undefined ? '' : `${JSON.stringify({ hookSpecificOutput: { hookEventName: value.hook_event_name, additionalContext: context } })}\n`;
21
+ }
22
+ catch {
23
+ return '';
24
+ }
25
+ }
26
+ if (value.hook_event_name === 'SessionEnd') {
27
+ try {
28
+ await automaticSessionEnd('claude', value.session_id, value.cwd, env);
29
+ }
30
+ catch { /* end remains bounded */ }
31
+ return '';
32
+ }
33
+ return runClaudeHook(inputText, env);
34
+ }
35
+ export async function claudeHookResponse(input, lookup = sessionInbox, env = process.env) {
4
36
  if (typeof input.session_id !== 'string' || input.session_id === '')
5
37
  return undefined;
6
38
  if (input.hook_event_name !== 'PostToolBatch')
7
39
  return undefined;
8
40
  return presentPendingAtBoundary(input.session_id, (context) => ({ hookSpecificOutput: { hookEventName: 'PostToolBatch', additionalContext: context } }), lookup, env);
9
41
  }
10
- export function runClaudeHook(inputText, env = process.env) {
42
+ export async function runClaudeHook(inputText, env = process.env) {
11
43
  let input;
12
44
  try {
13
45
  input = JSON.parse(inputText);
@@ -17,6 +49,6 @@ export function runClaudeHook(inputText, env = process.env) {
17
49
  }
18
50
  if (input === null || typeof input !== 'object')
19
51
  return '';
20
- const response = claudeHookResponse(input, sessionInbox, env);
52
+ const response = await claudeHookResponse(input, sessionInbox, env);
21
53
  return response === undefined ? '' : `${JSON.stringify(response)}\n`;
22
54
  }
@@ -0,0 +1,35 @@
1
+ import { type HardCap } from '../model.js';
2
+ export declare const DEFAULT_SQUARE_PATH = ".square/SQUARE.square";
3
+ export interface CommandContext {
4
+ squarePath: string;
5
+ name?: string;
6
+ homeDir: string;
7
+ command: string;
8
+ }
9
+ export interface CommandSpec<Intent = unknown, Result = void> {
10
+ parse(argv: string[], context: CommandContext): Intent;
11
+ execute(intent: Intent, context: CommandContext): Promise<Result> | Result;
12
+ present(result: Result, context: CommandContext): void;
13
+ }
14
+ export declare function readStdinSync(): string;
15
+ export declare function resolveBody(arg: string): string;
16
+ export declare function readPipedBodyFallback(): string | undefined;
17
+ export declare function fail(message: string, exitCode?: number): never;
18
+ export declare function usage(command: string): never;
19
+ export declare function requireValue(args: string[], index: number, flag: string): string;
20
+ export declare function parseDurationMs(value: string, flag: string): number;
21
+ export declare function parsePositiveInteger(value: string, flag: string): number;
22
+ export declare function parseNonNegativeInteger(value: string, flag: string): number;
23
+ export declare function parseHardCap(value: string): HardCap;
24
+ export declare function parseNameList(value: string, flag: string): string[];
25
+ export declare function requireParticipant(name: string | undefined): string;
26
+ export interface ParsedGlobalArgs {
27
+ squarePath: string;
28
+ explicitSquarePath: boolean;
29
+ multipleSquares: boolean;
30
+ name?: string;
31
+ args: string[];
32
+ }
33
+ export declare function parseGlobalArgs(rawArgs: string[]): ParsedGlobalArgs;
34
+ export declare function locationIsRequired(command: string): boolean;
35
+ export declare function defaultContext(command: string, squarePath: string, name?: string): CommandContext;
@@ -1,10 +1,9 @@
1
1
  import fs from 'node:fs';
2
2
  import os from 'node:os';
3
- import path from 'node:path';
4
- import { loadSquare } from '../artifact.js';
5
3
  import { commandUsageHint } from '../help.js';
6
4
  import { parseParticipantList, validateName } from '../model.js';
7
- export const DEFAULT_SQUARE_PATH = '.square/SQUARE.md';
5
+ import { localParticipantName } from '../registry.js';
6
+ export const DEFAULT_SQUARE_PATH = '.square/SQUARE.square';
8
7
  export function readStdinSync() {
9
8
  try {
10
9
  return fs.readFileSync(0, 'utf8');
@@ -88,30 +87,17 @@ export function requireParticipant(name) {
88
87
  validateName(name);
89
88
  return name;
90
89
  }
91
- function resolveDefaultSquarePath() {
92
- const directory = path.join(process.cwd(), '.square');
93
- let entries;
94
- try {
95
- entries = fs.readdirSync(directory, { withFileTypes: true });
96
- }
97
- catch {
98
- return { path: DEFAULT_SQUARE_PATH, multiple: false };
99
- }
100
- const candidates = [];
101
- for (const entry of entries) {
102
- if (!entry.isFile() || !entry.name.endsWith('.md'))
103
- continue;
104
- const fullPath = path.join(directory, entry.name);
105
- try {
106
- const doc = loadSquare(fullPath);
107
- candidates.push({ relPath: path.relative(process.cwd(), fullPath), at: doc.acts.at(-1)?.at ?? fs.statSync(fullPath).mtimeMs });
108
- }
109
- catch {
110
- // Unreadable artifacts are not implicit command targets.
111
- }
112
- }
113
- candidates.sort((a, b) => b.at - a.at);
114
- return { path: candidates[0]?.relPath ?? DEFAULT_SQUARE_PATH, multiple: candidates.length > 1 };
90
+ const LOCATION_REQUIRED_COMMANDS = new Set(['join', 'catch', 'express', 'done', 'hold', 'resume']);
91
+ function configuredLocation() {
92
+ const value = process.env.SQUARE_LOCATION?.trim();
93
+ return value === '' ? undefined : value;
94
+ }
95
+ function configuredName() {
96
+ const value = process.env.SQUARE_PARTICIPANT_NAME?.trim();
97
+ if (!value)
98
+ return undefined;
99
+ validateName(value);
100
+ return value;
115
101
  }
116
102
  export function parseGlobalArgs(rawArgs) {
117
103
  const args = [...rawArgs];
@@ -129,14 +115,23 @@ export function parseGlobalArgs(rawArgs) {
129
115
  index -= 1;
130
116
  }
131
117
  }
118
+ if (name === undefined)
119
+ name = configuredName();
132
120
  if (name !== undefined)
133
121
  validateName(name);
134
122
  const explicitSquarePath = requestedPath !== undefined;
135
123
  const command = args[0];
136
- const resolved = !explicitSquarePath && !['ls', 'list', 'version', 'install', 'uninstall'].includes(command ?? '')
137
- ? resolveDefaultSquarePath()
138
- : { path: requestedPath ?? DEFAULT_SQUARE_PATH, multiple: false };
139
- return { squarePath: resolved.path, explicitSquarePath, multipleSquares: resolved.multiple, name, args };
124
+ const configured = configuredLocation();
125
+ if (command !== undefined && !['--help', '-h'].includes(command) && locationIsRequired(command) && requestedPath === undefined && configured === undefined) {
126
+ fail(`✕ ${command} needs a square location\n» square ls`);
127
+ }
128
+ const squarePath = requestedPath ?? configured ?? DEFAULT_SQUARE_PATH;
129
+ if (name === undefined && command !== undefined && locationIsRequired(command))
130
+ name = localParticipantName(squarePath);
131
+ return { squarePath, explicitSquarePath: explicitSquarePath || configured !== undefined, multipleSquares: false, name, args };
132
+ }
133
+ export function locationIsRequired(command) {
134
+ return LOCATION_REQUIRED_COMMANDS.has(command);
140
135
  }
141
136
  export function defaultContext(command, squarePath, name) {
142
137
  return { command, squarePath, name, homeDir: os.homedir() };
@@ -0,0 +1,28 @@
1
+ import { executeHarnessTarget, type HarnessAction } from '../harness.js';
2
+ import { type CommandSpec } from './context.js';
3
+ export type HarnessCommandAction = HarnessAction;
4
+ export interface ParsedHarnessCommand {
5
+ target?: string;
6
+ }
7
+ export interface ParsedInstallCommand {
8
+ targets: string[];
9
+ all: boolean;
10
+ force: boolean;
11
+ }
12
+ export interface HarnessCommandResult {
13
+ lines: string[];
14
+ notes: string[];
15
+ failures: string[];
16
+ }
17
+ type HarnessTargetExecutor = typeof executeHarnessTarget;
18
+ export declare function executeTargetBatch(targets: string[], action: 'install' | 'uninstall', context: {
19
+ homeDir: string;
20
+ squarePath?: string;
21
+ force: boolean;
22
+ }, executeTarget?: HarnessTargetExecutor): Promise<HarnessCommandResult>;
23
+ export declare const installCommand: CommandSpec<ParsedInstallCommand, HarnessCommandResult>;
24
+ export declare const uninstallCommand: CommandSpec<ParsedInstallCommand, HarnessCommandResult>;
25
+ export declare const harnessCommand: CommandSpec<ParsedHarnessCommand, HarnessCommandResult>;
26
+ export declare function formatHarnessResult(result: HarnessCommandResult): string;
27
+ export declare function runHarnessCommand(argv: string[], squarePath?: string): Promise<string>;
28
+ export {};
@@ -26,24 +26,38 @@ function parseInstall(argv, action) {
26
26
  throw new Error(`Cannot combine ${action} --all with named targets`);
27
27
  return { targets, all, force };
28
28
  }
29
+ export async function executeTargetBatch(targets, action, context, executeTarget = executeHarnessTarget) {
30
+ const results = [];
31
+ const failures = [];
32
+ for (const target of targets) {
33
+ try {
34
+ results.push(await executeTarget(target, action, context));
35
+ }
36
+ catch (error) {
37
+ failures.push(`✕ ${target} ${action} failed: ${error instanceof Error ? error.message : String(error)}`);
38
+ }
39
+ }
40
+ return {
41
+ notes: results.flatMap((result) => result.notes),
42
+ lines: results.flatMap((result) => result.lines),
43
+ failures,
44
+ };
45
+ }
29
46
  function targetCommand(action) {
30
47
  return {
31
48
  parse(argv) { return parseInstall(argv, action); },
32
49
  async execute(intent, context) {
33
- const results = [];
34
- for (const target of installTargets(intent, action)) {
35
- results.push(await executeHarnessTarget(target, action, {
36
- homeDir: context.homeDir,
37
- squarePath: context.squarePath,
38
- force: intent.force,
39
- }));
40
- }
41
- return {
42
- notes: results.flatMap((result) => result.notes),
43
- lines: results.flatMap((result) => result.lines),
44
- };
50
+ return executeTargetBatch(installTargets(intent, action), action, {
51
+ homeDir: context.homeDir,
52
+ squarePath: context.squarePath,
53
+ force: intent.force,
54
+ });
55
+ },
56
+ present(result) {
57
+ process.stdout.write(formatHarnessResult(result));
58
+ if (result.failures.length > 0)
59
+ process.exitCode = 1;
45
60
  },
46
- present(result) { process.stdout.write(formatHarnessResult(result)); },
47
61
  };
48
62
  }
49
63
  export const installCommand = targetCommand('install');
@@ -77,6 +91,7 @@ export const harnessCommand = {
77
91
  return {
78
92
  notes: results.flatMap((result) => result.notes),
79
93
  lines: results.flatMap((result) => result.lines),
94
+ failures: [],
80
95
  };
81
96
  },
82
97
  present(result) {
@@ -84,10 +99,10 @@ export const harnessCommand = {
84
99
  },
85
100
  };
86
101
  export function formatHarnessResult(result) {
87
- return `${[...result.notes, ...result.lines].join('\n')}\n`;
102
+ return `${[...result.notes, ...result.lines, ...result.failures].join('\n')}\n`;
88
103
  }
89
104
  export function runHarnessCommand(argv, squarePath) {
90
- const context = { homeDir: os.homedir(), squarePath: squarePath ?? '.square/SQUARE.md', command: 'harness' };
105
+ const context = { homeDir: os.homedir(), squarePath: squarePath ?? '.square/SQUARE.square', command: 'harness' };
91
106
  const intent = harnessCommand.parse(argv, context);
92
107
  return Promise.resolve(harnessCommand.execute(intent, context)).then(formatHarnessResult);
93
108
  }
@@ -0,0 +1,7 @@
1
+ import { type CommandSpec } from './context.js';
2
+ interface DoctorResult {
3
+ output: string;
4
+ exitCode?: number;
5
+ }
6
+ export declare const doctorCommand: CommandSpec<undefined, DoctorResult>;
7
+ export {};
@@ -1,82 +1,25 @@
1
- import fs from 'node:fs';
2
- import { diagnoseSquare, loadSquare } from '../artifact.js';
3
- import { renderDoctorClean, renderDoctorProblems, renderDoctorRepaired, renderDoctorUnfixable, withPathOutput, } from '../presentation.js';
1
+ import { diagnoseSquareFile } from '../square-storage.js';
2
+ import { renderDoctorClean, renderDoctorUnfixable, withPathOutput } from '../presentation.js';
4
3
  import { inSquareCount } from '../runtime.js';
5
- import { repairSquare } from '../square-application.js';
6
- import { SquareError } from '../model.js';
7
- import { pruneRegistry } from '../registry.js';
8
4
  import { usage } from './context.js';
9
- function readSquareText(squarePath) {
10
- try {
11
- return fs.readFileSync(squarePath, 'utf8');
12
- }
13
- catch (error) {
14
- if (error.code === 'ENOENT')
15
- throw new SquareError('not_found', `square file not found: ${squarePath}`);
16
- throw error;
17
- }
18
- }
19
- function quarantinePath(squarePath) {
20
- return squarePath.replace(/\.md$/, '') + '.quarantine.md';
21
- }
22
- function registryActs(squarePath) {
23
- if (!fs.existsSync(squarePath))
24
- return [];
25
- try {
26
- return loadSquare(squarePath).acts;
27
- }
28
- catch {
29
- // A temporarily unreadable artifact cannot disprove a cache binding.
30
- return undefined;
31
- }
32
- }
33
5
  export const doctorCommand = {
34
6
  parse(argv, context) {
35
- let fix = false;
36
- for (let index = 0; index < argv.length; index++) {
37
- const argument = argv[index];
38
- if (argument === '--fix')
39
- fix = true;
40
- else if (argument === '--before') {
41
- index += 1;
42
- if (argv[index] === undefined)
43
- usage(context.command);
44
- }
45
- else
46
- usage(context.command);
47
- }
48
- return { fix };
7
+ if (argv.length > 0)
8
+ usage(context.command);
9
+ return undefined;
49
10
  },
50
- async execute(intent, context) {
51
- if (!intent.fix) {
52
- const diagnosis = diagnoseSquare(readSquareText(context.squarePath));
53
- if (diagnosis.unfixable) {
54
- return {
55
- output: withPathOutput(context.squarePath, renderDoctorUnfixable(diagnosis.unfixable), { participantCount: inSquareCount(loadSquare(context.squarePath)) }),
56
- exitCode: 2,
57
- };
58
- }
59
- const summary = diagnosis.problems.length === 0 ? renderDoctorClean() : renderDoctorProblems(diagnosis.problems);
11
+ execute(_intent, context) {
12
+ const diagnosis = diagnoseSquareFile(context.squarePath);
13
+ if (diagnosis.unfixable !== undefined || diagnosis.state === undefined) {
60
14
  return {
61
- output: withPathOutput(context.squarePath, summary, { participantCount: inSquareCount(loadSquare(context.squarePath)) }),
62
- exitCode: diagnosis.problems.length === 0 ? 0 : 1,
63
- };
64
- }
65
- const repair = await repairSquare(context.squarePath);
66
- if (repair.diagnosis.unfixable) {
67
- return {
68
- output: withPathOutput(context.squarePath, renderDoctorUnfixable(repair.diagnosis.unfixable), { participantCount: inSquareCount(loadSquare(context.squarePath)) }),
15
+ output: withPathOutput(context.squarePath, renderDoctorUnfixable(diagnosis.unfixable ?? 'the snapshot could not be decoded')),
69
16
  exitCode: 2,
70
17
  };
71
18
  }
72
- const repaired = repair.repaired;
73
- const registry = pruneRegistry(registryActs);
74
- if (registry.removed > 0) {
75
- repaired.actions.push({ message: `pruned ${registry.removed} obsolete registry membership(s)` });
76
- }
77
- const sidecar = quarantinePath(context.squarePath);
78
19
  return {
79
- output: withPathOutput(context.squarePath, renderDoctorRepaired(repaired.actions, repaired.quarantinedBlocks.length, repaired.quarantinedBlocks.length > 0 ? sidecar : undefined), { participantCount: inSquareCount(loadSquare(context.squarePath)) }),
20
+ output: withPathOutput(context.squarePath, renderDoctorClean(), {
21
+ participantCount: inSquareCount(diagnosis.state),
22
+ }),
80
23
  };
81
24
  },
82
25
  present(result) {
@@ -0,0 +1,5 @@
1
+ import { type CommandSpec } from './context.js';
2
+ export declare const helpCommand: CommandSpec<{
3
+ command?: string;
4
+ }, string>;
5
+ export declare const versionCommand: CommandSpec<undefined, string>;
@@ -0,0 +1,20 @@
1
+ import { type ActivitiesOptions, type WatchOptions } from '../model.js';
2
+ import { type CommandSpec } from './context.js';
3
+ export declare const listCommand: CommandSpec<string[]>;
4
+ interface StreamIntent {
5
+ ndjson: boolean;
6
+ forName?: string;
7
+ }
8
+ export declare const streamCommand: CommandSpec<StreamIntent>;
9
+ export declare const catchCommand: CommandSpec<WatchOptions>;
10
+ export declare const historyCommand: CommandSpec<ActivitiesOptions, string>;
11
+ export declare const participantsCommand: CommandSpec<undefined, string>;
12
+ export declare const statusCommand: CommandSpec<undefined, string>;
13
+ interface InboxIntent {
14
+ sessionId: string;
15
+ json: boolean;
16
+ }
17
+ export declare const inboxCommand: CommandSpec<InboxIntent, string>;
18
+ export declare const claudeHookCommand: CommandSpec<undefined, string>;
19
+ export declare const codexHookCommand: CommandSpec<undefined, string>;
20
+ export {};