@moretea-labs/forge 1.7.0 → 1.7.2

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 (175) hide show
  1. package/AGENTS.md +2 -0
  2. package/CHANGELOG.md +26 -0
  3. package/CLAUDE.md +2 -0
  4. package/THIRD_PARTY_NOTICES.md +1 -1
  5. package/agents/openai.yaml +1 -1
  6. package/assets/partials-agents/02-operating-mode.partial.md +2 -0
  7. package/assets/plugin-registry.v1.json +3 -3
  8. package/assets/skill-version.json +2 -2
  9. package/assets/templates/helpers/check-architecture-sync.sh +17 -0
  10. package/assets/templates/helpers/inspect-project-state.ts +8 -6
  11. package/assets/templates/helpers/migrate-workflow-docs.ts +9 -7
  12. package/assets/templates/helpers/workflow-contract.ts +5 -1
  13. package/docs/architecture/CURRENT.md +12 -4
  14. package/docs/forge-plugin-management.md +1 -1
  15. package/docs/operations/controller-performance-and-502.md +19 -0
  16. package/docs/operations/homebrew.md +2 -2
  17. package/package.json +5 -2
  18. package/scripts/architecture-event.ts +2 -820
  19. package/scripts/architecture-queue.sh +2 -645
  20. package/scripts/archive-architecture-request.sh +2 -242
  21. package/scripts/archive-workflow.sh +1 -291
  22. package/scripts/benchmark-work-efficiency.ts +0 -0
  23. package/scripts/capability-config.ts +2 -392
  24. package/scripts/capability-resolver.ts +2 -503
  25. package/scripts/capture-plan.sh +1 -332
  26. package/scripts/check-agent-tooling.sh +2 -1531
  27. package/scripts/check-architecture-sync.sh +2 -302
  28. package/scripts/check-brain-manifest.sh +2 -253
  29. package/scripts/check-context-files.sh +2 -47
  30. package/scripts/check-deploy-sql-order.sh +2 -115
  31. package/scripts/check-runtime-architecture.mjs +243 -16
  32. package/scripts/check-skill-version.ts +3 -191
  33. package/scripts/check-source-duplication.mjs +61 -0
  34. package/scripts/check-stable-baseline.ts +0 -0
  35. package/scripts/check-task-sync.sh +2 -63
  36. package/scripts/check-task-workflow.sh +2 -1000
  37. package/scripts/codex-handoff-resume.sh +2 -267
  38. package/scripts/context-contract-sync.sh +2 -514
  39. package/scripts/contract-run.ts +2 -463
  40. package/scripts/contract-worktree.sh +1 -702
  41. package/scripts/ensure-task-workflow.sh +1 -1192
  42. package/scripts/inspect-project-state.ts +4 -279
  43. package/scripts/lib/project-init-lib.sh +7 -2
  44. package/scripts/lib/run-forge-helper.sh +19 -0
  45. package/scripts/lib/run-forge-helper.ts +26 -0
  46. package/scripts/migrate-project-template.sh +1 -33
  47. package/scripts/migrate-workflow-docs.ts +4 -412
  48. package/scripts/new-plan.sh +1 -180
  49. package/scripts/new-spec.sh +1 -47
  50. package/scripts/new-sprint.sh +1 -46
  51. package/scripts/plan-to-todo.sh +1 -771
  52. package/scripts/prepare-codex-handoff.sh +2 -201
  53. package/scripts/prepare-handoff.sh +1 -78
  54. package/scripts/refresh-current-status.sh +2 -467
  55. package/scripts/run-governed-gate.ts +3 -0
  56. package/scripts/select-agent-context-blocks.sh +2 -64
  57. package/scripts/ship-worktrees.sh +1 -531
  58. package/scripts/sprint-backlog.sh +1 -834
  59. package/scripts/stage-runtime-release.ts +2 -1
  60. package/scripts/summarize-failures.sh +2 -123
  61. package/scripts/switch-plan.sh +1 -150
  62. package/scripts/sync-brain-docs.sh +2 -379
  63. package/scripts/sync-generated-authority.mjs +175 -0
  64. package/scripts/verify-contract.sh +2 -593
  65. package/scripts/verify-sprint.sh +1 -500
  66. package/scripts/workflow-contract.ts +4 -201
  67. package/scripts/workstream-sync.sh +2 -261
  68. package/src/cli/agent-jobs/executor-health.ts +1 -1
  69. package/src/cli/commands/plugin.ts +12 -3
  70. package/src/cli/controller/check-runner.ts +7 -2
  71. package/src/cli/controller/context-pack.ts +46 -21
  72. package/src/cli/github/contracts.ts +16 -0
  73. package/src/cli/github/github.ts +2 -16
  74. package/src/cli/github/session.ts +2 -1
  75. package/src/cli/local-bridge/facade-api.ts +37 -47
  76. package/src/cli/local-bridge/server.ts +2 -2
  77. package/src/cli/mcp/legacy-tool-service.ts +10 -33
  78. package/src/cli/mcp/post-finalize-work-attribution.ts +150 -0
  79. package/src/cli/mcp/repository-tools.ts +125 -101
  80. package/src/cli/mcp/server.ts +57 -13
  81. package/src/cli/mcp/tool-contract.ts +14 -0
  82. package/src/cli/mcp/toolset.ts +2 -2
  83. package/src/cli/mcp/transports/http.ts +23 -0
  84. package/src/cli/repositories/command-classifier.ts +2 -2
  85. package/src/cli/repositories/command-executor.ts +24 -6
  86. package/src/cli/repositories/repository-snapshot.ts +5 -2
  87. package/src/cli/repositories/structured-git.ts +57 -3
  88. package/src/cli/repository/inspector.ts +11 -3
  89. package/src/runtime/context/generic-lsp-provider.ts +1 -1
  90. package/src/runtime/context/semantic-navigation-contract.ts +61 -0
  91. package/src/runtime/context/semantic-navigation.ts +9 -61
  92. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +69 -29
  93. package/src/runtime/control-plane/execution/repository-work-attribution.ts +106 -0
  94. package/src/runtime/control-plane/execution/retained-work-resume.ts +203 -0
  95. package/src/runtime/control-plane/execution/work-completion-authority.ts +58 -0
  96. package/src/runtime/control-plane/execution/work-evidence-policy.ts +200 -0
  97. package/src/runtime/control-plane/execution/work-execution-support.ts +386 -0
  98. package/src/runtime/control-plane/execution/work-finalization-service.ts +1913 -0
  99. package/src/runtime/control-plane/execution/work-handle-authority.ts +150 -0
  100. package/src/runtime/control-plane/execution/work-handle-store.ts +1 -1
  101. package/src/runtime/control-plane/execution/work-operation-service.ts +468 -0
  102. package/src/runtime/control-plane/execution/work-path-scope.ts +37 -0
  103. package/src/runtime/control-plane/execution/work-preparation-service.ts +479 -0
  104. package/src/runtime/control-plane/execution/work-process-evidence.ts +129 -0
  105. package/src/runtime/control-plane/execution/work-task-receipt.ts +4 -43
  106. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +13 -3
  107. package/src/runtime/control-plane/execution/work-verification-context.ts +56 -0
  108. package/src/runtime/control-plane/execution/work-verification-service.ts +388 -0
  109. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +5 -1
  110. package/src/runtime/control-plane/facade/controller-round-relay.ts +164 -37
  111. package/src/runtime/control-plane/facade/controller-session-store.ts +183 -24
  112. package/src/runtime/control-plane/facade/goal-workloop-access.ts +15 -1
  113. package/src/runtime/control-plane/facade/goal-workloop.ts +329 -148
  114. package/src/runtime/control-plane/facade/index.ts +2 -0
  115. package/src/runtime/control-plane/facade/plan-contract-store.ts +90 -22
  116. package/src/runtime/control-plane/facade/repository-work-admission.ts +147 -0
  117. package/src/runtime/control-plane/facade/requirement-authority.ts +83 -0
  118. package/src/runtime/control-plane/facade/types.ts +68 -5
  119. package/src/runtime/control-plane/facade/work-contract-store.ts +3 -209
  120. package/src/runtime/control-plane/facade/work-state-machine.ts +250 -0
  121. package/src/runtime/control-plane/global-scheduler/maintenance.ts +14 -7
  122. package/src/runtime/control-plane/global-scheduler/scheduler.ts +5 -1
  123. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +13 -0
  124. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +15 -6
  125. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +54 -5
  126. package/src/runtime/control-plane/launcher/thin-launcher.ts +56 -7
  127. package/src/runtime/control-plane/persistence/requirement-store.ts +0 -12
  128. package/src/runtime/control-plane/repo-actor/registry.ts +4 -2
  129. package/src/runtime/control-plane/routing/route-policy.ts +18 -6
  130. package/src/runtime/control-plane/routing/workspace-admission.ts +70 -0
  131. package/src/runtime/evidence/process-check-execution.ts +18 -0
  132. package/src/runtime/execution/jobs/child-reference.ts +6 -2
  133. package/src/runtime/execution/process-runtime/check-result.ts +53 -18
  134. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +7 -1
  135. package/src/runtime/execution/process-runtime/command-facade.ts +39 -17
  136. package/src/runtime/execution/process-runtime/lightweight-managed.ts +20 -3
  137. package/src/runtime/execution/process-runtime/persisted-check.ts +4 -1
  138. package/src/runtime/execution/process-runtime/runtime.ts +10 -0
  139. package/src/runtime/execution/process-runtime/types.ts +2 -12
  140. package/src/runtime/execution/work-activity.ts +21 -0
  141. package/src/runtime/execution/workers/executor.ts +18 -13
  142. package/src/runtime/gateway/mcp/execution-tools.ts +35 -2930
  143. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +61 -0
  144. package/src/runtime/gateway/mcp/router.ts +2 -2
  145. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +21 -2
  146. package/src/runtime/gateway/mcp/runtime-tools.ts +815 -1015
  147. package/src/runtime/plugins/app-store-connect-adapter.ts +93 -28
  148. package/src/runtime/plugins/browser-adapter.ts +256 -1083
  149. package/src/runtime/plugins/browser-handoff-host.ts +2 -25
  150. package/src/runtime/plugins/browser-handoff.ts +18 -3
  151. package/src/runtime/plugins/browser-macos-bridge.ts +121 -16
  152. package/src/runtime/plugins/browser-manifest-surface.ts +603 -0
  153. package/src/runtime/plugins/browser-native-session-service.ts +222 -0
  154. package/src/runtime/plugins/browser-runtime-contract.ts +2 -0
  155. package/src/runtime/plugins/browser-runtime.ts +10 -0
  156. package/src/runtime/plugins/browser-session-store.ts +121 -0
  157. package/src/runtime/plugins/browser-session-types.ts +98 -0
  158. package/src/runtime/plugins/desktop-operator-registration.ts +4 -4
  159. package/src/runtime/plugins/external-adapter.ts +92 -58
  160. package/src/runtime/plugins/macos-capability-broker.ts +0 -8
  161. package/src/runtime/plugins/store.ts +66 -9
  162. package/src/runtime/plugins/types.ts +3 -1
  163. package/src/runtime/recovery/maintenance-executor.ts +61 -25
  164. package/src/runtime/root/package-connector-service.ts +46 -4
  165. package/src/runtime/root/package-runtime-release.ts +3 -2
  166. package/src/runtime/root/package-runtime-service.ts +48 -3
  167. package/src/runtime/root/release-manifest.ts +137 -16
  168. package/src/runtime/root/release-materialize.ts +109 -35
  169. package/src/runtime/root/types.ts +10 -0
  170. package/src/runtime/standalone-recovery/core.ts +425 -194
  171. package/src/runtime/standalone-recovery/entry.ts +1 -1
  172. package/src/runtime/standalone-recovery/installer.ts +1 -1
  173. package/src/runtime/workflow/schedules/engine.ts +113 -87
  174. package/src/runtime/workflow/schedules/settlement.ts +26 -23
  175. package/src/runtime/workflow/schedules/store.ts +79 -10
@@ -1,5 +1,5 @@
1
1
  import { createPrivateKey, sign } from 'crypto';
2
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
2
+ import { accessSync, constants as fsConstants, existsSync, mkdirSync, promises as fsPromises, readFileSync, statSync, writeFileSync } from 'fs';
3
3
  import { dirname, join } from 'path';
4
4
  import { CONTROLLER_SCOPE_REPO_ID, controllerSystemRoot } from '../../cli/repositories/controller-home';
5
5
  import type {
@@ -19,6 +19,8 @@ const CONFIG_ROOT = '.forge/plugins';
19
19
  const LEGACY_CONFIG_ROOT = '.repo-harness/plugins';
20
20
  const API_BASE_URL = 'https://api.appstoreconnect.apple.com';
21
21
  const DEFAULT_TIMEOUT_MS = 60_000;
22
+ const PRIVATE_KEY_READ_MAX_ATTEMPTS = 4;
23
+ const PRIVATE_KEY_READ_RETRY_BASE_MS = 10;
22
24
 
23
25
  type AppStoreConnectProviderKind = 'mock' | 'app-store-connect-api';
24
26
 
@@ -154,20 +156,82 @@ function envValue(name: string): string | undefined {
154
156
  return stringValue(process.env[name]);
155
157
  }
156
158
 
157
- function privateKeyMaterial(config: AppStoreConnectPluginConfig): { key?: string; source?: string; warning?: string } {
159
+ interface AppStoreConnectPrivateKeyReference {
160
+ available: boolean;
161
+ source?: string;
162
+ warning?: string;
163
+ }
164
+
165
+ function privateKeyPathReference(path: string, source: string): AppStoreConnectPrivateKeyReference {
166
+ if (!existsSync(path)) {
167
+ return { available: false, source, warning: 'Configured App Store Connect private key path does not exist.' };
168
+ }
169
+ try {
170
+ if (!statSync(path).isFile()) {
171
+ return { available: false, source, warning: 'Configured App Store Connect private key path is not a regular file.' };
172
+ }
173
+ accessSync(path, fsConstants.R_OK);
174
+ return { available: true, source };
175
+ } catch {
176
+ // Capability discovery and auth_status are metadata reads. They must never
177
+ // synchronously consume secret bytes just to decide whether a credential
178
+ // reference is usable; filesystem contention (including macOS EDEADLK)
179
+ // therefore degrades readiness instead of escaping as a raw read error.
180
+ return { available: false, source, warning: 'Configured App Store Connect private key path is not readable.' };
181
+ }
182
+ }
183
+
184
+ function privateKeyReference(config: AppStoreConnectPluginConfig): AppStoreConnectPrivateKeyReference {
158
185
  const inline = envValue('FORGE_ASC_PRIVATE_KEY');
159
- if (inline) return { key: inline.replace(/\\n/g, '\n'), source: 'env:FORGE_ASC_PRIVATE_KEY' };
186
+ if (inline) return { available: true, source: 'env:FORGE_ASC_PRIVATE_KEY' };
160
187
  const envKeyPath = envValue('FORGE_ASC_PRIVATE_KEY_PATH');
161
- if (envKeyPath) {
162
- return existsSync(envKeyPath)
163
- ? { key: readFileSync(envKeyPath, 'utf-8'), source: 'env:FORGE_ASC_PRIVATE_KEY_PATH' }
164
- : { source: 'env:FORGE_ASC_PRIVATE_KEY_PATH', warning: 'Configured App Store Connect private key path does not exist.' };
165
- }
166
- if (config.privateKeyPath) {
167
- return existsSync(config.privateKeyPath)
168
- ? { key: readFileSync(config.privateKeyPath, 'utf-8'), source: 'config:privateKeyPath' }
169
- : { source: 'config:privateKeyPath', warning: 'Configured App Store Connect private key path does not exist.' };
188
+ if (envKeyPath) return privateKeyPathReference(envKeyPath, 'env:FORGE_ASC_PRIVATE_KEY_PATH');
189
+ if (config.privateKeyPath) return privateKeyPathReference(config.privateKeyPath, 'config:privateKeyPath');
190
+ return { available: false };
191
+ }
192
+
193
+ function fsErrorCode(error: unknown): string | undefined {
194
+ if (!error || typeof error !== 'object' || !('code' in error)) return undefined;
195
+ const code = (error as { code?: unknown }).code;
196
+ return typeof code === 'string' ? code : undefined;
197
+ }
198
+
199
+ async function readPrivateKeyFile(path: string, source: string): Promise<{ key?: string; source: string; warning?: string }> {
200
+ for (let attempt = 1; attempt <= PRIVATE_KEY_READ_MAX_ATTEMPTS; attempt += 1) {
201
+ try {
202
+ return { key: await fsPromises.readFile(path, 'utf-8'), source };
203
+ } catch (error) {
204
+ const code = fsErrorCode(error);
205
+ if (code === 'ENOENT') {
206
+ return { source, warning: 'Configured App Store Connect private key path does not exist.' };
207
+ }
208
+ if (code === 'EDEADLK' && attempt < PRIVATE_KEY_READ_MAX_ATTEMPTS) {
209
+ await new Promise((resolve) => setTimeout(resolve, PRIVATE_KEY_READ_RETRY_BASE_MS * attempt));
210
+ continue;
211
+ }
212
+ if (code === 'EDEADLK') {
213
+ throw new AssistantPluginError(
214
+ 'PLUGIN_PROVIDER_UNAVAILABLE',
215
+ 'App Store Connect private key is temporarily unavailable because the filesystem is busy.',
216
+ { retryable: true, details: { credentialSource: source, code } },
217
+ );
218
+ }
219
+ throw new AssistantPluginError(
220
+ 'PLUGIN_AUTH_REQUIRED',
221
+ 'Configured App Store Connect private key could not be read.',
222
+ { retryable: false, details: { credentialSource: source, code } },
223
+ );
224
+ }
170
225
  }
226
+ throw new AssistantPluginError('PLUGIN_PROVIDER_UNAVAILABLE', 'App Store Connect private key is temporarily unavailable.', { retryable: true });
227
+ }
228
+
229
+ async function privateKeyMaterial(config: AppStoreConnectPluginConfig): Promise<{ key?: string; source?: string; warning?: string }> {
230
+ const inline = envValue('FORGE_ASC_PRIVATE_KEY');
231
+ if (inline) return { key: inline.replace(/\\n/g, '\n'), source: 'env:FORGE_ASC_PRIVATE_KEY' };
232
+ const envKeyPath = envValue('FORGE_ASC_PRIVATE_KEY_PATH');
233
+ if (envKeyPath) return readPrivateKeyFile(envKeyPath, 'env:FORGE_ASC_PRIVATE_KEY_PATH');
234
+ if (config.privateKeyPath) return readPrivateKeyFile(config.privateKeyPath, 'config:privateKeyPath');
171
235
  return {};
172
236
  }
173
237
 
@@ -188,12 +252,12 @@ function resolveAuth(config: AppStoreConnectPluginConfig): AppStoreConnectAuthSt
188
252
 
189
253
  const issuerId = envValue('FORGE_ASC_ISSUER_ID') ?? config.issuerId;
190
254
  const keyId = envValue('FORGE_ASC_KEY_ID') ?? config.keyId;
191
- const key = privateKeyMaterial(config);
255
+ const key = privateKeyReference(config);
192
256
  const errors: string[] = [];
193
257
  const warnings: string[] = key.warning ? [key.warning] : [];
194
258
  if (!issuerId) errors.push('Set FORGE_ASC_ISSUER_ID or configure issuer_id.');
195
259
  if (!keyId) errors.push('Set FORGE_ASC_KEY_ID or configure key_id.');
196
- if (!key.key) errors.push('Set FORGE_ASC_PRIVATE_KEY, FORGE_ASC_PRIVATE_KEY_PATH, or configure private_key_path.');
260
+ if (!key.available) errors.push('Set FORGE_ASC_PRIVATE_KEY, FORGE_ASC_PRIVATE_KEY_PATH, or configure private_key_path.');
197
261
 
198
262
  return {
199
263
  provider: 'app-store-connect-api',
@@ -269,10 +333,10 @@ function derSignatureToJose(signature: Buffer): string {
269
333
  return Buffer.concat([derIntegerToJose(r), derIntegerToJose(s)]).toString('base64url');
270
334
  }
271
335
 
272
- function createJwt(config: AppStoreConnectPluginConfig): string {
336
+ async function createJwt(config: AppStoreConnectPluginConfig): Promise<string> {
273
337
  const issuerId = envValue('FORGE_ASC_ISSUER_ID') ?? config.issuerId;
274
338
  const keyId = envValue('FORGE_ASC_KEY_ID') ?? config.keyId;
275
- const key = privateKeyMaterial(config).key;
339
+ const key = (await privateKeyMaterial(config)).key;
276
340
  if (!issuerId || !keyId || !key) throw new AssistantPluginError('PLUGIN_AUTH_REQUIRED', 'App Store Connect API credentials are incomplete.', { retryable: false });
277
341
 
278
342
  const issuedAt = Math.floor(Date.now() / 1000);
@@ -298,7 +362,7 @@ async function apiRequest<T>(config: AppStoreConnectPluginConfig, options: {
298
362
  const response = await fetch(url, {
299
363
  method: options.method ?? 'GET',
300
364
  headers: {
301
- authorization: `Bearer ${createJwt(config)}`,
365
+ authorization: `Bearer ${await createJwt(config)}`,
302
366
  accept: 'application/json',
303
367
  ...(options.body === undefined ? {} : { 'content-type': 'application/json' }),
304
368
  },
@@ -807,20 +871,21 @@ export async function executeAppStoreConnectPluginAction(input: AssistantPluginA
807
871
 
808
872
  if (config.provider === 'mock') return mockResponse(input.actionId, input.args, config);
809
873
  const auth = resolveAuth(config);
874
+ if (input.actionId === 'auth_status') {
875
+ return {
876
+ ready: config.enabled && auth.ready,
877
+ provider: auth.provider,
878
+ issuerId: auth.issuerId ? 'configured' : undefined,
879
+ keyId: auth.keyId ? 'configured' : undefined,
880
+ credentialSource: auth.credentialSource,
881
+ errors: auth.errors,
882
+ warnings: auth.warnings,
883
+ userFacingStatus: userFacingAscStatus(config, auth),
884
+ };
885
+ }
810
886
  if (!config.enabled || !auth.ready) throw new AssistantPluginError('PLUGIN_NOT_READY', 'App Store Connect plugin is not ready.', { retryable: false, details: { enabled: config.enabled, errors: auth.errors } });
811
887
 
812
888
  switch (input.actionId) {
813
- case 'auth_status':
814
- return {
815
- ready: auth.ready,
816
- provider: auth.provider,
817
- issuerId: auth.issuerId ? 'configured' : undefined,
818
- keyId: auth.keyId ? 'configured' : undefined,
819
- credentialSource: auth.credentialSource,
820
- errors: auth.errors,
821
- warnings: auth.warnings,
822
- userFacingStatus: userFacingAscStatus(config, auth),
823
- };
824
889
  case 'list_apps':
825
890
  return apiRequest(config, { path: '/v1/apps', query: { 'filter[bundleId]': stringValue(input.args.bundle_id), 'filter[name]': stringValue(input.args.name), limit: limit(input.args.limit) } });
826
891
  case 'list_bundle_ids':