forgeos 0.1.0-alpha.26 → 0.1.0-alpha.28

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 (69) hide show
  1. package/.npmignore +6 -0
  2. package/AGENTS.md +1 -1
  3. package/CHANGELOG.md +20 -0
  4. package/bin/forge.mjs +0 -0
  5. package/docs/changelog.md +22 -0
  6. package/package.json +7 -1
  7. package/src/forge/_generated/releaseManifest.json +1 -1
  8. package/src/forge/_generated/releaseManifest.ts +3 -3
  9. package/src/forge/agent-adapters/index.ts +63 -18
  10. package/src/forge/agent-adapters/types.ts +6 -1
  11. package/src/forge/cli/changed.ts +25 -20
  12. package/src/forge/cli/output.ts +1 -1
  13. package/src/forge/compiler/integration/add.ts +38 -1
  14. package/src/forge/compiler/integration/plan.ts +2 -11
  15. package/src/forge/compiler/orchestrator/fast-check.ts +4 -0
  16. package/src/forge/compiler/orchestrator/manifest.ts +1 -1
  17. package/src/forge/compiler/orchestrator/plan.ts +163 -1
  18. package/src/forge/compiler/orchestrator/types.ts +1 -1
  19. package/src/forge/runtime/executor.ts +18 -15
  20. package/src/forge/version.ts +1 -1
  21. package/src/forge/workspace/change-summary.ts +34 -0
  22. package/adapters/java/target/classes/dev/forgeos/adapter/Auth.class +0 -0
  23. package/adapters/java/target/classes/dev/forgeos/adapter/Diagnostic.class +0 -0
  24. package/adapters/java/target/classes/dev/forgeos/adapter/Entry.class +0 -0
  25. package/adapters/java/target/classes/dev/forgeos/adapter/EntryKind.class +0 -0
  26. package/adapters/java/target/classes/dev/forgeos/adapter/ErrorInfo.class +0 -0
  27. package/adapters/java/target/classes/dev/forgeos/adapter/Forge.class +0 -0
  28. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeCall.class +0 -0
  29. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeContext.class +0 -0
  30. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeHandler.class +0 -0
  31. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeHttpHandler.class +0 -0
  32. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeRegistry$EntryOption.class +0 -0
  33. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeRegistry$RegisteredEntry.class +0 -0
  34. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeRegistry$RegistryOption.class +0 -0
  35. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeRegistry.class +0 -0
  36. package/adapters/java/target/classes/dev/forgeos/adapter/Json.class +0 -0
  37. package/adapters/java/target/classes/dev/forgeos/adapter/Manifest.class +0 -0
  38. package/adapters/java/target/classes/dev/forgeos/adapter/RequestEnvelope.class +0 -0
  39. package/adapters/java/target/classes/dev/forgeos/adapter/ResponseEnvelope.class +0 -0
  40. package/adapters/java/target/classes/dev/forgeos/adapter/Risk.class +0 -0
  41. package/adapters/java/target/classes/dev/forgeos/adapter/Schemas.class +0 -0
  42. package/adapters/java/target/classes/dev/forgeos/adapter/Service.class +0 -0
  43. package/adapters/java/target/classes/dev/forgeos/adapter/TransactionMode.class +0 -0
  44. package/adapters/java/target/classes/dev/forgeos/adapter/TypedForgeHandler.class +0 -0
  45. package/adapters/java/target/forge-java-adapter-0.1.0-alpha.11.jar +0 -0
  46. package/adapters/java/target/maven-archiver/pom.properties +0 -3
  47. package/adapters/java/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +0 -23
  48. package/adapters/java/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +0 -20
  49. package/adapters/java-spring-boot-starter/target/classes/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +0 -1
  50. package/adapters/java-spring-boot-starter/target/classes/dev/forgeos/adapter/spring/ForgeCommand.class +0 -0
  51. package/adapters/java-spring-boot-starter/target/classes/dev/forgeos/adapter/spring/ForgeExternalService.class +0 -0
  52. package/adapters/java-spring-boot-starter/target/classes/dev/forgeos/adapter/spring/ForgeQuery.class +0 -0
  53. package/adapters/java-spring-boot-starter/target/classes/dev/forgeos/adapter/spring/ForgeServiceBeanCondition.class +0 -0
  54. package/adapters/java-spring-boot-starter/target/classes/dev/forgeos/adapter/spring/ForgeSpringAutoConfiguration.class +0 -0
  55. package/adapters/java-spring-boot-starter/target/classes/dev/forgeos/adapter/spring/ForgeSpringRuntime.class +0 -0
  56. package/adapters/java-spring-boot-starter/target/forge-java-spring-boot-starter-0.1.0-alpha.11.jar +0 -0
  57. package/adapters/java-spring-boot-starter/target/maven-archiver/pom.properties +0 -3
  58. package/adapters/java-spring-boot-starter/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +0 -6
  59. package/adapters/java-spring-boot-starter/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +0 -6
  60. package/examples/java-billing/target/classes/dev/forgeos/examples/billing/CreateInvoiceInput.class +0 -0
  61. package/examples/java-billing/target/classes/dev/forgeos/examples/billing/Invoice.class +0 -0
  62. package/examples/java-billing/target/classes/dev/forgeos/examples/billing/Main$EmptyInput.class +0 -0
  63. package/examples/java-billing/target/classes/dev/forgeos/examples/billing/Main$Options.class +0 -0
  64. package/examples/java-billing/target/classes/dev/forgeos/examples/billing/Main.class +0 -0
  65. package/examples/java-billing/target/java-billing-0.1.0-alpha.11-all.jar +0 -0
  66. package/examples/java-billing/target/java-billing-0.1.0-alpha.11.jar +0 -0
  67. package/examples/java-billing/target/maven-archiver/pom.properties +0 -3
  68. package/examples/java-billing/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +0 -5
  69. package/examples/java-billing/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +0 -3
package/.npmignore CHANGED
@@ -5,6 +5,12 @@ node_modules/
5
5
  .forge/
6
6
  tests/
7
7
  examples/
8
+ adapters/**/target/
9
+ examples/**/target/
10
+ *.class
11
+ *.jar
12
+ **/*.class
13
+ **/*.jar
8
14
  src/forge/_generated/**
9
15
  !src/forge/_generated/
10
16
  !src/forge/_generated/releaseManifest.json
package/AGENTS.md CHANGED
@@ -1,4 +1,4 @@
1
- // @forge-generated generator=0.1.0-alpha.26 input=23c00c5407aab4088b55b49905f2b195667a75b8544fa180786ac637ee67e1e5 content=0d493cf0e41b71cb652d5e0e1b0c1f83d2a1281b748321f0b00f0773ba93074e
1
+ // @forge-generated generator=0.1.0-alpha.28 input=17144e8bdfd3e1024bc4ca6ec68a23a3b7c2fba75d3033853d2486fbc51db17f content=0d493cf0e41b71cb652d5e0e1b0c1f83d2a1281b748321f0b00f0773ba93074e
2
2
  # AGENTS.md
3
3
 
4
4
  <!-- forge-generated:start -->
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # forgeos
2
2
 
3
+ ## 0.1.0-alpha.28
4
+
5
+ ### Patch Changes
6
+
7
+ - Accept visible Codex hook canaries as sufficient for local editing while reporting native Codex provenance separately through `nativeTrustStatus`.
8
+
9
+ ## 0.1.0-alpha.27
10
+
11
+ ### Patch Changes
12
+
13
+ - Stabilize `forge add convex` and generated integration artifacts.
14
+
15
+ - Re-emit integration adapters, docs, and testkits from the main generator so `forge generate --check` and `forge verify --smoke` stay clean after `forge add`.
16
+ - Keep partial `forge add` plans from deleting unrelated generated files before the full generator can reconcile the workspace.
17
+ - Include changed package-manager files such as `package.json` and lockfiles in `forge add --json` handoffs.
18
+ - Replace stale fast-check manifest hashes instead of merging them, and invalidate old manifest schemas to avoid phantom generated drift.
19
+ - Skip Bun module mock registration when the active Bun runtime does not expose `Bun.mock.module`, while still applying mock secret env vars.
20
+ - Filter generated and operational filesystem noise from `forge changed --authored` in non-git workspaces.
21
+ - Keep Java build outputs such as `target/`, `.class`, and `.jar` files out of the published npm tarball.
22
+
3
23
  ## 0.1.0-alpha.26
4
24
 
5
25
  ### Patch Changes
package/bin/forge.mjs CHANGED
File without changes
package/docs/changelog.md CHANGED
@@ -6,6 +6,28 @@ The canonical source file in the repository is `CHANGELOG.md`.
6
6
 
7
7
  ## Unreleased
8
8
 
9
+ ## 0.1.0-alpha.28
10
+
11
+ - Accepted visible Codex hook canaries as sufficient for local editing while
12
+ reporting native Codex provenance separately through `nativeTrustStatus`, so
13
+ `waiting-for-user-trust` no longer implies missing user approval when the
14
+ canary path is already working.
15
+ - Added regression coverage and docs for the split between hook approval,
16
+ canary readiness, and trusted native Codex signal proof.
17
+
18
+ ## 0.1.0-alpha.27
19
+
20
+ - Stabilized the `forge add convex` loop: integration docs/testkits are now
21
+ re-emitted by the main generator, partial `forge add` plans no longer remove
22
+ unrelated generated files, stale fast-check manifest hashes are pruned instead
23
+ of merged, and `forge add --json` includes changed package-manager files.
24
+ - Made runtime mock mode tolerate Bun builds without `Bun.mock.module` while
25
+ still applying mock secret environment variables.
26
+ - Filtered generated and operational filesystem noise from
27
+ `forge changed --authored` in non-git workspaces.
28
+ - Excluded Java build outputs such as `target/`, `.class`, and `.jar` files
29
+ from the published npm tarball.
30
+
9
31
  ## 0.1.0-alpha.26
10
32
 
11
33
  - Hardened the field-demo loop after the Team Onboarding app exercise:
package/package.json CHANGED
@@ -1,16 +1,22 @@
1
1
  {
2
2
  "name": "forgeos",
3
- "version": "0.1.0-alpha.26",
3
+ "version": "0.1.0-alpha.28",
4
4
  "description": "Agent-native application framework and compiler for building Forge apps without a mandatory dashboard.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "files": [
8
8
  "adapters/",
9
+ "!adapters/**/target/**",
10
+ "!adapters/**/*.class",
11
+ "!adapters/**/*.jar",
9
12
  "bin/",
10
13
  "docs/cair-protocol.md",
11
14
  "docs/forge-protocol.md",
12
15
  "examples/go-billing/",
13
16
  "examples/java-billing/",
17
+ "!examples/**/target/**",
18
+ "!examples/**/*.class",
19
+ "!examples/**/*.jar",
14
20
  "packages/eslint-plugin-forge/",
15
21
  "schemas/",
16
22
  "src/",
@@ -1 +1 @@
1
- {"defaultProvider":"local","diagnostics":[],"env":{"deployEnv":"FORGE_DEPLOY_ENV","deployId":"FORGE_DEPLOY_ID","publicReleaseId":"NEXT_PUBLIC_FORGE_RELEASE_ID","releaseId":"FORGE_RELEASE_ID"},"gitSha":"unknown","optionalProviders":["local","sentry-compatible","sentry","glitchtip","bugsink","otel","custom"],"packageName":"forgeos","packageVersion":"0.1.0-alpha.26","releaseId":"forgeos@0.1.0-alpha.26+unknown","schemaVersion":"0.1.0"}
1
+ {"defaultProvider":"local","diagnostics":[],"env":{"deployEnv":"FORGE_DEPLOY_ENV","deployId":"FORGE_DEPLOY_ID","publicReleaseId":"NEXT_PUBLIC_FORGE_RELEASE_ID","releaseId":"FORGE_RELEASE_ID"},"gitSha":"unknown","optionalProviders":["local","sentry-compatible","sentry","glitchtip","bugsink","otel","custom"],"packageName":"forgeos","packageVersion":"0.1.0-alpha.28","releaseId":"forgeos@0.1.0-alpha.28+unknown","schemaVersion":"0.1.0"}
@@ -1,4 +1,4 @@
1
- // @forge-generated generator=0.1.0-alpha.26 input=23c00c5407aab4088b55b49905f2b195667a75b8544fa180786ac637ee67e1e5 content=967d4e05aa540b3e3d843fa6ed4516658ab4c364c1d8c42a14f36c57b75dff71
1
+ // @forge-generated generator=0.1.0-alpha.28 input=17144e8bdfd3e1024bc4ca6ec68a23a3b7c2fba75d3033853d2486fbc51db17f content=232067bb5b77f562ad4aa2ddf44f9e3a796ef42205185a28d16ec1ff86cc40b0
2
2
  export const releaseManifest = {
3
3
  "defaultProvider": "local",
4
4
  "diagnostics": [],
@@ -19,7 +19,7 @@ export const releaseManifest = {
19
19
  "custom"
20
20
  ],
21
21
  "packageName": "forgeos",
22
- "packageVersion": "0.1.0-alpha.26",
23
- "releaseId": "forgeos@0.1.0-alpha.26+unknown",
22
+ "packageVersion": "0.1.0-alpha.28",
23
+ "releaseId": "forgeos@0.1.0-alpha.28+unknown",
24
24
  "schemaVersion": "0.1.0"
25
25
  } as const;
@@ -987,27 +987,50 @@ function hookApprovalStatusFor(
987
987
  target: AgentAdapterTarget,
988
988
  installed: boolean,
989
989
  nativeSignals: number,
990
+ observableSignals = 0,
990
991
  memoryReadable = true,
991
- ): "not-required" | "waiting-for-user-trust" | "trusted" | "memory-unavailable" {
992
+ ): "not-required" | "waiting-for-user-trust" | "accepted" | "trusted" | "memory-unavailable" {
992
993
  if (target !== "codex" || !installed) {
993
994
  return "not-required";
994
995
  }
995
996
  if (!memoryReadable) {
996
997
  return "memory-unavailable";
997
998
  }
998
- return nativeSignals > 0 ? "trusted" : "waiting-for-user-trust";
999
+ if (nativeSignals > 0) {
1000
+ return "trusted";
1001
+ }
1002
+ return observableSignals > 0 ? "accepted" : "waiting-for-user-trust";
1003
+ }
1004
+
1005
+ function hookNativeTrustStatusFor(
1006
+ target: AgentAdapterTarget,
1007
+ installed: boolean,
1008
+ nativeSignals: number,
1009
+ memoryReadable = true,
1010
+ ): "not-required" | "waiting-for-native-signal" | "trusted" | "memory-unavailable" {
1011
+ if (target !== "codex" || !installed) {
1012
+ return "not-required";
1013
+ }
1014
+ if (!memoryReadable) {
1015
+ return "memory-unavailable";
1016
+ }
1017
+ return nativeSignals > 0 ? "trusted" : "waiting-for-native-signal";
999
1018
  }
1000
1019
 
1001
1020
  function codexHookApprovalMessage(
1002
1021
  approvalStatus: ReturnType<typeof hookApprovalStatusFor>,
1022
+ nativeTrustStatus: ReturnType<typeof hookNativeTrustStatusFor>,
1003
1023
  canarySignals = 0,
1004
1024
  ): string {
1005
1025
  if (approvalStatus === "waiting-for-user-trust") {
1006
1026
  if (canarySignals > 0) {
1007
- return "ForgeOS can see the Codex smoke canary, but has not seen a normal trusted Codex hook event yet; continue or send one Codex message in this workspace";
1027
+ return "ForgeOS can see the Codex smoke canary; hook approval is accepted, but trusted native Codex signal proof is still pending";
1008
1028
  }
1009
1029
  return "ForgeOS has not seen a trusted native Codex hook signal yet; approve the Codex Desktop hook prompt if shown, then continue a Codex session in this workspace";
1010
1030
  }
1031
+ if (approvalStatus === "accepted" && nativeTrustStatus === "waiting-for-native-signal") {
1032
+ return "Codex hook approval is accepted for local editing; ForgeOS is still waiting for a trusted native Codex signal for stronger provenance";
1033
+ }
1011
1034
  if (approvalStatus === "trusted") {
1012
1035
  return "Codex Desktop hook trust is confirmed by a native hook signal";
1013
1036
  }
@@ -1286,6 +1309,7 @@ async function readAgentHookStatus(options: AgentCommandOptions): Promise<AgentH
1286
1309
  canarySignals: 0,
1287
1310
  approvalRequired: false,
1288
1311
  approvalStatus: "not-required",
1312
+ nativeTrustStatus: "not-required",
1289
1313
  checks: [{ name: "target", ok: false, message: diag.message }],
1290
1314
  nextActions: ["forge agent list-targets --json"],
1291
1315
  diagnostics: [diag],
@@ -1321,9 +1345,10 @@ async function readAgentHookStatus(options: AgentCommandOptions): Promise<AgentH
1321
1345
  const nativeSignals = nativeEvents.length + (queuedHooks?.nativeSignals ?? 0);
1322
1346
  const canarySignals = canaryEvents.length + (queuedHooks?.canarySignals ?? 0);
1323
1347
  const visibleHookSignals = visibleInMemory || queuedEvents > 0;
1324
- const approvalStatus = hookApprovalStatusFor(target, installed, nativeSignals, deltaWritable);
1348
+ const observableSignals = Math.max(usefulSignals, canarySignals);
1349
+ const approvalStatus = hookApprovalStatusFor(target, installed, nativeSignals, observableSignals, deltaWritable);
1350
+ const nativeTrustStatus = hookNativeTrustStatusFor(target, installed, nativeSignals, deltaWritable);
1325
1351
  const approvalRequired = approvalStatus === "waiting-for-user-trust";
1326
- const trustedHookSignals = target === "codex" ? nativeSignals > 0 : true;
1327
1352
  const codexHookInspection = installTarget === "codex"
1328
1353
  ? inspectCodexHookCommands(options.workspaceRoot)
1329
1354
  : undefined;
@@ -1340,7 +1365,7 @@ async function readAgentHookStatus(options: AgentCommandOptions): Promise<AgentH
1340
1365
  const usesLightweightRunner = codexHookInspection?.usesLightweightRunner === true;
1341
1366
  const hookCommandHealthy = installTarget !== "codex" || (usesLightweightRunner && !usesLegacyForgeCli);
1342
1367
  const hookVersionHealthy = installTarget !== "codex" || !usesLightweightRunner || codexVersionMatch?.matches === true;
1343
- const ok = installed && bridgeWritable && deltaWritable && visibleHookSignals && usefulSignals > 0 && trustedHookSignals && !approvalRequired && hookCommandHealthy && hookVersionHealthy;
1368
+ const ok = installed && bridgeWritable && deltaWritable && visibleHookSignals && usefulSignals > 0 && !approvalRequired && hookCommandHealthy && hookVersionHealthy;
1344
1369
  const nextActions = ok
1345
1370
  ? uniqueCommands([
1346
1371
  ...(queuedEvents > 0 ? [`forge agent ingest ${source} --file .forge/agent/events.ndjson --json`] : []),
@@ -1374,6 +1399,7 @@ async function readAgentHookStatus(options: AgentCommandOptions): Promise<AgentH
1374
1399
  canarySignals,
1375
1400
  approvalRequired,
1376
1401
  approvalStatus,
1402
+ nativeTrustStatus,
1377
1403
  workspaceRoot: memory.workspaceRoot,
1378
1404
  ignoredOutOfWorkspaceEvents: memory.ignoredOutOfWorkspaceEvents,
1379
1405
  ...(lastAgentSignal(memory.events) ? { lastSignal: lastAgentSignal(memory.events) } : {}),
@@ -1435,28 +1461,29 @@ async function readAgentHookStatus(options: AgentCommandOptions): Promise<AgentH
1435
1461
  },
1436
1462
  {
1437
1463
  name: "native-hook-signal",
1438
- ok: target !== "codex" || !deltaWritable || nativeSignals > 0,
1464
+ ok: target !== "codex" || !deltaWritable || nativeSignals > 0 || approvalStatus === "accepted",
1439
1465
  message: target !== "codex"
1440
1466
  ? "native Codex hook approval is not required for this target"
1441
1467
  : !deltaWritable
1442
1468
  ? "trusted Codex native hook signals cannot be verified until Agent Memory is readable"
1443
1469
  : nativeSignals > 0
1444
1470
  ? `${nativeSignals} trusted Codex native hook signal(s) visible or queued`
1445
- : "Codex has not emitted a trusted native hook signal yet",
1471
+ : "hook approval is accepted, but Codex has not emitted a trusted native hook signal yet",
1446
1472
  evidence: {
1447
1473
  nativeSignals,
1448
1474
  canarySignals,
1449
1475
  queuedNativeSignals: queuedHooks?.nativeSignals ?? 0,
1450
1476
  queuedCanarySignals: queuedHooks?.canarySignals ?? 0,
1451
1477
  memoryReadable: deltaWritable,
1478
+ nativeTrustStatus,
1452
1479
  trustBoundary: target === "codex" ? "codex-desktop-hook-approval" : "not-required",
1453
1480
  },
1454
1481
  },
1455
1482
  {
1456
1483
  name: "codex-hook-approval",
1457
1484
  ok: !approvalRequired && approvalStatus !== "memory-unavailable",
1458
- message: codexHookApprovalMessage(approvalStatus, canarySignals),
1459
- evidence: { approvalStatus, approvalRequired },
1485
+ message: codexHookApprovalMessage(approvalStatus, nativeTrustStatus, canarySignals),
1486
+ evidence: { approvalStatus, approvalRequired, nativeTrustStatus },
1460
1487
  },
1461
1488
  ...(installTarget === "codex"
1462
1489
  ? [
@@ -1556,7 +1583,15 @@ export async function runAgentDoctor(options: AgentCommandOptions): Promise<Agen
1556
1583
  const adapterState = check.missing.length > 0 ? "missing" : check.stale.length > 0 ? "stale" : "ready";
1557
1584
  const installed = !installTarget || hookFiles.missing.length === 0;
1558
1585
  const memoryReadable = memoryDiagnostics.length === 0;
1559
- const approvalStatus = hookApprovalStatusFor(target, Boolean(installTarget && installed), nativeSignals, memoryReadable);
1586
+ const observableSignals = Math.max(usefulSignals, canarySignals);
1587
+ const approvalStatus = hookApprovalStatusFor(
1588
+ target,
1589
+ Boolean(installTarget && installed),
1590
+ nativeSignals,
1591
+ observableSignals,
1592
+ memoryReadable,
1593
+ );
1594
+ const nativeTrustStatus = hookNativeTrustStatusFor(target, Boolean(installTarget && installed), nativeSignals, memoryReadable);
1560
1595
  const approvalRequired = approvalStatus === "waiting-for-user-trust";
1561
1596
  const hookBridgeState = !installTarget
1562
1597
  ? "not-supported"
@@ -1635,28 +1670,29 @@ export async function runAgentDoctor(options: AgentCommandOptions): Promise<Agen
1635
1670
  },
1636
1671
  {
1637
1672
  name: "native-hook-signal",
1638
- ok: !installTarget || target !== "codex" || !memoryReadable || nativeSignals > 0,
1673
+ ok: !installTarget || target !== "codex" || !memoryReadable || nativeSignals > 0 || approvalStatus === "accepted",
1639
1674
  message: !installTarget || target !== "codex"
1640
1675
  ? "native Codex hook approval is not required for this target"
1641
1676
  : !memoryReadable
1642
1677
  ? "trusted Codex native hook signals cannot be verified until Agent Memory is readable"
1643
1678
  : nativeSignals > 0
1644
1679
  ? `${nativeSignals} trusted Codex native hook signal(s) visible or queued`
1645
- : "Codex has not emitted a trusted native hook signal yet",
1680
+ : "hook approval is accepted, but Codex has not emitted a trusted native hook signal yet",
1646
1681
  evidence: {
1647
1682
  nativeSignals,
1648
1683
  canarySignals,
1649
1684
  queuedNativeSignals: queuedHooks?.nativeSignals ?? 0,
1650
1685
  queuedCanarySignals: queuedHooks?.canarySignals ?? 0,
1651
1686
  memoryReadable,
1687
+ nativeTrustStatus,
1652
1688
  trustBoundary: target === "codex" ? "codex-desktop-hook-approval" : "not-required",
1653
1689
  },
1654
1690
  },
1655
1691
  {
1656
1692
  name: "codex-hook-approval",
1657
1693
  ok: !approvalRequired && approvalStatus !== "memory-unavailable",
1658
- message: codexHookApprovalMessage(approvalStatus, canarySignals),
1659
- evidence: { approvalStatus, approvalRequired },
1694
+ message: codexHookApprovalMessage(approvalStatus, nativeTrustStatus, canarySignals),
1695
+ evidence: { approvalStatus, approvalRequired, nativeTrustStatus },
1660
1696
  },
1661
1697
  { name: "secret-scan", ok: !check.diagnostics.some((diag) => diag.code === FORGE_AGENT_SECRET_LEAK) },
1662
1698
  ];
@@ -1683,6 +1719,7 @@ export async function runAgentDoctor(options: AgentCommandOptions): Promise<Agen
1683
1719
  hookBridge: hookBridgeState,
1684
1720
  approvalRequired,
1685
1721
  approvalStatus,
1722
+ nativeTrustStatus,
1686
1723
  recentEvents: recentEvents.length,
1687
1724
  queuedEvents,
1688
1725
  usefulSignals,
@@ -1832,7 +1869,7 @@ export async function runAgentOnboard(options: AgentCommandOptions): Promise<Age
1832
1869
  name: "hook-smoke",
1833
1870
  ok: hookSmoke.ok,
1834
1871
  message: hookSmoke.approvalRequired
1835
- ? `${target} hook files and canary are visible, but Codex Desktop still needs user trust approval`
1872
+ ? `${target} hook files are installed, but no accepted hook signal is visible yet`
1836
1873
  : hookSmoke.ok
1837
1874
  ? `${target} hooks recorded a useful canary signal`
1838
1875
  : `${target} hooks did not prove memory visibility`,
@@ -1854,6 +1891,8 @@ export async function runAgentOnboard(options: AgentCommandOptions): Promise<Age
1854
1891
  ? "Codex hook trust cannot be verified until Agent Memory is readable"
1855
1892
  : doctor.summary.approvalStatus === "trusted"
1856
1893
  ? "Codex hook trust is confirmed by a native hook signal"
1894
+ : doctor.summary.approvalStatus === "accepted"
1895
+ ? "Codex hook approval is accepted; trusted native signal proof is still pending"
1857
1896
  : "hook approval is not required for this target",
1858
1897
  }]
1859
1898
  : []),
@@ -1899,6 +1938,7 @@ export async function runAgentOnboard(options: AgentCommandOptions): Promise<Age
1899
1938
  hookBridge: doctor.summary.hookBridge,
1900
1939
  approvalRequired: doctor.summary.approvalRequired,
1901
1940
  approvalStatus: doctor.summary.approvalStatus,
1941
+ nativeTrustStatus: doctor.summary.nativeTrustStatus,
1902
1942
  memorySignals: doctor.summary.usefulSignals,
1903
1943
  nativeSignals: doctor.summary.nativeSignals,
1904
1944
  canarySignals: doctor.summary.canarySignals,
@@ -1956,6 +1996,7 @@ export async function runAgentHooksSmoke(options: AgentCommandOptions): Promise<
1956
1996
  canarySignals: 0,
1957
1997
  approvalRequired: false,
1958
1998
  approvalStatus: "not-required",
1999
+ nativeTrustStatus: "not-required",
1959
2000
  checks: [{ name: "target", ok: false, message: diag.message }],
1960
2001
  nextActions: ["forge agent list-targets --json"],
1961
2002
  diagnostics: [diag],
@@ -2060,7 +2101,7 @@ export async function runAgentHooksSmoke(options: AgentCommandOptions): Promise<
2060
2101
  {
2061
2102
  name: "codex-hook-approval",
2062
2103
  ok: status.approvalStatus !== "memory-unavailable",
2063
- message: codexHookApprovalMessage(status.approvalStatus, status.canarySignals),
2104
+ message: codexHookApprovalMessage(status.approvalStatus, status.nativeTrustStatus, status.canarySignals),
2064
2105
  },
2065
2106
  ...(installTarget === "codex"
2066
2107
  ? [
@@ -2110,7 +2151,7 @@ export async function runAgentHooksSmoke(options: AgentCommandOptions): Promise<
2110
2151
  ? [createDiagnostic({
2111
2152
  severity: "warning",
2112
2153
  code: "FORGE_AGENT_HOOK_APPROVAL_REQUIRED",
2113
- message: "Codex Desktop has installed hook files, but ForgeOS has not seen a trusted native hook signal yet. Approve the hook prompt if Codex shows one, then continue a Codex session in this workspace.",
2154
+ message: "Codex Desktop hook approval is still pending because ForgeOS has not seen a canary, useful hook event, or trusted native hook signal yet. Approve the hook prompt if Codex shows one, then continue a Codex session in this workspace.",
2114
2155
  suggestedCommands: hookApprovalNextActions(target, status.canarySignals),
2115
2156
  })]
2116
2157
  : []),
@@ -2147,6 +2188,7 @@ export async function runAgentHooksSmoke(options: AgentCommandOptions): Promise<
2147
2188
  canarySignals: status.canarySignals,
2148
2189
  approvalRequired: status.approvalRequired,
2149
2190
  approvalStatus: status.approvalStatus,
2191
+ nativeTrustStatus: status.nativeTrustStatus,
2150
2192
  ...(canaryEvent ? { lastSignal: lastAgentSignal([canaryEvent]) } : status.lastSignal ? { lastSignal: status.lastSignal } : {}),
2151
2193
  ...(hookRunnerProbe ? { hookRunnerProbe } : {}),
2152
2194
  canary: {
@@ -2309,6 +2351,7 @@ export function formatAgentHuman(result: Awaited<ReturnType<typeof runAgentComma
2309
2351
  `ready to edit: ${result.readyToEdit ? "yes" : "no"}`,
2310
2352
  `hook bridge: ${result.summary.hookBridge}`,
2311
2353
  `hook approval: ${result.summary.approvalStatus}`,
2354
+ `native trust: ${result.summary.nativeTrustStatus}`,
2312
2355
  `generated fresh: ${result.summary.generatedFresh ? "yes" : "no"}`,
2313
2356
  `generated changed: ${result.summary.generatedChangedFiles}`,
2314
2357
  `changed files: ${result.summary.changedFiles}`,
@@ -2326,6 +2369,7 @@ export function formatAgentHuman(result: Awaited<ReturnType<typeof runAgentComma
2326
2369
  `trusted native ready: ${smoke.trustedNativeReady ? "yes" : "no"}`,
2327
2370
  `readiness level: ${smoke.readinessLevel}`,
2328
2371
  `approval: ${smoke.approvalStatus}`,
2372
+ `native trust: ${smoke.nativeTrustStatus}`,
2329
2373
  `native signals: ${smoke.nativeSignals}`,
2330
2374
  `canary signals: ${smoke.canarySignals}`,
2331
2375
  ...smoke.checks.map((check) => `${check.ok ? "OK" : "FAIL"} ${check.name}${check.message ? `: ${check.message}` : ""}`),
@@ -2362,6 +2406,7 @@ export function formatAgentHuman(result: Awaited<ReturnType<typeof runAgentComma
2362
2406
  `native signals: ${result.nativeSignals}`,
2363
2407
  `canary signals: ${result.canarySignals}`,
2364
2408
  `approval: ${result.approvalStatus}`,
2409
+ `native trust: ${result.nativeTrustStatus}`,
2365
2410
  ...("recentEvents" in result ? [`recent events: ${result.recentEvents}`] : []),
2366
2411
  ...("queuedEvents" in result ? [`queued events: ${result.queuedEvents ?? 0}`] : []),
2367
2412
  ...(result.lastSignal ? [`last signal: ${result.lastSignal.kind}${result.lastSignal.summary ? ` - ${result.lastSignal.summary}` : ""}`] : []),
@@ -140,7 +140,8 @@ export interface AgentPrintContextResult {
140
140
  }
141
141
 
142
142
  export type AgentHookBridgeState = "ready" | "missing" | "not-supported" | "waiting-for-user-trust" | "memory-unavailable";
143
- export type AgentHookApprovalStatus = "not-required" | "waiting-for-user-trust" | "trusted" | "memory-unavailable";
143
+ export type AgentHookApprovalStatus = "not-required" | "waiting-for-user-trust" | "accepted" | "trusted" | "memory-unavailable";
144
+ export type AgentHookNativeTrustStatus = "not-required" | "waiting-for-native-signal" | "trusted" | "memory-unavailable";
144
145
  export type AgentHookReadinessLevel = "none" | "canary" | "trusted-native";
145
146
 
146
147
  export interface AgentDoctorResult {
@@ -151,6 +152,7 @@ export interface AgentDoctorResult {
151
152
  hookBridge: AgentHookBridgeState;
152
153
  approvalRequired: boolean;
153
154
  approvalStatus: AgentHookApprovalStatus;
155
+ nativeTrustStatus: AgentHookNativeTrustStatus;
154
156
  recentEvents: number;
155
157
  queuedEvents?: number;
156
158
  usefulSignals: number;
@@ -193,6 +195,7 @@ export interface AgentOnboardResult {
193
195
  hookBridge: AgentHookBridgeState;
194
196
  approvalRequired: boolean;
195
197
  approvalStatus: AgentHookApprovalStatus;
198
+ nativeTrustStatus: AgentHookNativeTrustStatus;
196
199
  memorySignals: number;
197
200
  nativeSignals: number;
198
201
  canarySignals: number;
@@ -239,6 +242,7 @@ export interface AgentHooksSmokeResult {
239
242
  canarySignals: number;
240
243
  approvalRequired: boolean;
241
244
  approvalStatus: AgentHookApprovalStatus;
245
+ nativeTrustStatus: AgentHookNativeTrustStatus;
242
246
  lastSignal?: {
243
247
  kind: string;
244
248
  summary?: string;
@@ -285,6 +289,7 @@ export interface AgentHooksStatusResult {
285
289
  canarySignals: number;
286
290
  approvalRequired: boolean;
287
291
  approvalStatus: AgentHookApprovalStatus;
292
+ nativeTrustStatus: AgentHookNativeTrustStatus;
288
293
  lastSignal?: {
289
294
  kind: string;
290
295
  summary?: string;
@@ -1,5 +1,9 @@
1
1
  import type { CategorizedFileSummary, DiffPlan } from "../workspace/change-summary.ts";
2
- import { buildDiffPlanFromChangeSummary, summarizeChangeTypes } from "../workspace/change-summary.ts";
2
+ import {
3
+ buildDiffPlanFromChangeSummary,
4
+ filterCategorizedSummary,
5
+ summarizeChangeTypes,
6
+ } from "../workspace/change-summary.ts";
3
7
  import { buildWorkspaceGitSummary, type WorkspaceGitSummary } from "../workspace/git-summary.ts";
4
8
 
5
9
  export interface ChangedCommandResult {
@@ -39,6 +43,8 @@ interface GeneratedChangeExplanation {
39
43
  summary: string;
40
44
  }
41
45
 
46
+ const AUTHORED_CHANGE_TYPES = ["source", "tests", "docs", "config", "assets", "other"] as const;
47
+
42
48
  function emptyCategory(summary: CategorizedFileSummary, category: keyof CategorizedFileSummary["byType"]): boolean {
43
49
  return summary.byType[category].count === 0;
44
50
  }
@@ -183,23 +189,22 @@ export function runChangedCommand(workspaceRoot: string, options: { authoredOnly
183
189
  const changed = git.changeSummary.changed;
184
190
  const humanChanges = selectHumanChangeSummary(changed);
185
191
  const derivedChanges = selectDerivedChangeSummary(changed);
186
- const authoredChanged: CategorizedFileSummary = {
187
- ...changed,
188
- total: { ...changed.total, count: humanChanges.total },
189
- byType: {
190
- ...changed.byType,
191
- generated: { count: 0, sample: [], hidden: 0 },
192
- },
193
- primaryTypes: changed.primaryTypes.filter((type) => type !== "generated"),
194
- };
192
+ const authoredChanged = filterCategorizedSummary(changed, [...AUTHORED_CHANGE_TYPES]);
193
+ const authoredStaged = filterCategorizedSummary(git.changeSummary.staged, [...AUTHORED_CHANGE_TYPES]);
194
+ const authoredUnstaged = filterCategorizedSummary(git.changeSummary.unstaged, [...AUTHORED_CHANGE_TYPES]);
195
+ const authoredUntracked = filterCategorizedSummary(git.changeSummary.untracked, [...AUTHORED_CHANGE_TYPES]);
196
+ const viewHumanChanges = options.authoredOnly ? selectHumanChangeSummary(authoredChanged) : humanChanges;
195
197
  const viewChanged = options.authoredOnly ? authoredChanged : changed;
198
+ const viewStaged = options.authoredOnly ? authoredStaged : git.changeSummary.staged;
199
+ const viewUnstaged = options.authoredOnly ? authoredUnstaged : git.changeSummary.unstaged;
200
+ const viewUntracked = options.authoredOnly ? authoredUntracked : git.changeSummary.untracked;
196
201
  const viewDerivedChanges: DerivedChangeSummary = options.authoredOnly
197
202
  ? { total: 0, generated: { count: 0, sample: [], hidden: 0 } }
198
203
  : derivedChanges;
199
204
  const risks = buildRisks(git);
200
205
  const recommendedCommands = buildRecommendedCommands(git);
201
- const reviewFocus = buildReviewFocus(humanChanges, viewDerivedChanges);
202
- const generatedExplanation = buildGeneratedChangeExplanation(humanChanges, viewDerivedChanges);
206
+ const reviewFocus = buildReviewFocus(viewHumanChanges, viewDerivedChanges);
207
+ const generatedExplanation = buildGeneratedChangeExplanation(viewHumanChanges, viewDerivedChanges);
203
208
  const diffPlan: DiffPlan = buildDiffPlanFromChangeSummary(viewChanged);
204
209
  const ok = git.available || git.source === "filesystem";
205
210
 
@@ -213,11 +218,11 @@ export function runChangedCommand(workspaceRoot: string, options: { authoredOnly
213
218
  commit: git.commit,
214
219
  view: options.authoredOnly ? "authored" : "all",
215
220
  changedFiles: viewChanged.total.count,
216
- humanFiles: humanChanges.total,
221
+ humanFiles: viewHumanChanges.total,
217
222
  generatedFiles: viewDerivedChanges.total,
218
- stagedFiles: git.staged.count,
219
- unstagedFiles: git.unstaged.count,
220
- untrackedFiles: git.untracked.count,
223
+ stagedFiles: viewStaged.total.count,
224
+ unstagedFiles: viewUnstaged.total.count,
225
+ untrackedFiles: viewUntracked.total.count,
221
226
  primaryTypes: viewChanged.primaryTypes,
222
227
  changeTypes: summarizeChangeTypes(viewChanged),
223
228
  },
@@ -228,11 +233,11 @@ export function runChangedCommand(workspaceRoot: string, options: { authoredOnly
228
233
  branch: git.branch,
229
234
  commit: git.commit,
230
235
  changed: viewChanged,
231
- staged: git.changeSummary.staged,
232
- unstaged: git.changeSummary.unstaged,
233
- untracked: git.changeSummary.untracked,
236
+ staged: viewStaged,
237
+ unstaged: viewUnstaged,
238
+ untracked: viewUntracked,
234
239
  },
235
- humanChanges,
240
+ humanChanges: viewHumanChanges,
236
241
  derivedChanges: viewDerivedChanges,
237
242
  reviewFocus,
238
243
  generatedExplanation,
@@ -157,11 +157,11 @@ export function buildAddJson(result: ForgeAddResult): Record<string, unknown> {
157
157
  const integrationNextActions =
158
158
  result.exitCode === 0 && result.mode === "integration" && result.targetKind === "forge-integration"
159
159
  ? [
160
+ "forge generate",
160
161
  ...(result.recipePackages ?? []).map((pkg) => `forge deps inspect ${pkg} --json`),
161
162
  ...((result.requiredSecrets?.length ?? 0) > 0 || (result.optionalSecrets?.length ?? 0) > 0
162
163
  ? ["forge secrets check --json", "forge inspect secrets --json"]
163
164
  : []),
164
- "forge generate",
165
165
  "forge check --json",
166
166
  "forge verify --smoke",
167
167
  ]
@@ -344,6 +344,39 @@ function dependencyFromInstall(
344
344
  };
345
345
  }
346
346
 
347
+ function fileHashOrNull(path: string): string | null {
348
+ if (!nodeFileSystem.exists(path)) {
349
+ return null;
350
+ }
351
+ return hashStable(nodeFileSystem.readText(path) ?? "");
352
+ }
353
+
354
+ function snapshotPackageManagerFiles(
355
+ workspaceRoot: string,
356
+ pm: PackageManagerAdapter,
357
+ ): Map<string, string | null> {
358
+ const paths = ["package.json", pm.lockfile];
359
+ return new Map(
360
+ paths.map((path) => [
361
+ path,
362
+ fileHashOrNull(join(workspaceRoot, path)),
363
+ ]),
364
+ );
365
+ }
366
+
367
+ function changedPackageManagerFiles(
368
+ workspaceRoot: string,
369
+ before: Map<string, string | null>,
370
+ ): string[] {
371
+ const changed: string[] = [];
372
+ for (const [path, previousHash] of before) {
373
+ if (fileHashOrNull(join(workspaceRoot, path)) !== previousHash) {
374
+ changed.push(path);
375
+ }
376
+ }
377
+ return changed.sort();
378
+ }
379
+
347
380
  async function analyzeRecipePackages(
348
381
  recipe: NonNullable<ReturnType<typeof resolveRecipe>>,
349
382
  ctx: ReturnType<typeof discover>,
@@ -635,6 +668,7 @@ export async function forgeAdd(
635
668
  const snapshot = snapshotVersionControlled(options.workspaceRoot);
636
669
 
637
670
  try {
671
+ const packageManagerBefore = snapshotPackageManagerFiles(options.workspaceRoot, pm);
638
672
  for (const pkg of recipe.packages) {
639
673
  await pm.add(pkg.packageName, {
640
674
  cwd: options.workspaceRoot,
@@ -735,7 +769,10 @@ export async function forgeAdd(
735
769
  alias: normalized,
736
770
  mode: "integration",
737
771
  ...recipeResultMetadata(recipe),
738
- changed: emitResult.changed,
772
+ changed: [
773
+ ...changedPackageManagerFiles(options.workspaceRoot, packageManagerBefore),
774
+ ...emitResult.changed,
775
+ ],
739
776
  unchanged: emitResult.unchanged,
740
777
  warnings: warningsCombined,
741
778
  errors: [],
@@ -20,7 +20,6 @@ import { PACKAGE_ANALYZER_VERSION } from "../package-graph/constants.ts";
20
20
  import { RECIPE_SCHEMA_VERSION } from "../recipes/definitions.ts";
21
21
  import { hashStable } from "../primitives/hash.ts";
22
22
  import { stableSortEmitFiles, stableSortStrings } from "../primitives/index.ts";
23
- import { detectOrphanedGeneratedFiles } from "../orchestrator/orphans.ts";
24
23
  import type { DiscoverContext } from "../orchestrator/types.ts";
25
24
  import {
26
25
  createRenderContext,
@@ -254,17 +253,9 @@ export function buildIntegrationEmitPlan(input: IntegrationPlanInput): EmitPlan
254
253
  const lockEntry = buildLockEntry(recipe, classified, generatedFiles);
255
254
  const lock = mergeLockPackages(input.existingLock, lockEntry, ctx);
256
255
 
257
- const sortedFiles = stableSortEmitFiles(files);
258
- const plannedPathSet = new Set(sortedFiles.map((file) => file.path));
259
- const orphanedFiles = detectOrphanedGeneratedFiles(
260
- ctx.workspaceRoot,
261
- ctx.generatedDir,
262
- plannedPathSet,
263
- );
264
-
265
256
  return {
266
- files: sortedFiles,
267
- orphanedFiles,
257
+ files: stableSortEmitFiles(files),
258
+ orphanedFiles: [],
268
259
  lock,
269
260
  };
270
261
  }
@@ -8,6 +8,7 @@ import type { GenerateResult } from "../types/cli.ts";
8
8
  import type { Diagnostic } from "../types/diagnostic.ts";
9
9
  import { discover } from "./discover.ts";
10
10
  import { loadManifest } from "./manifest.ts";
11
+ import { ORCHESTRATOR_MANIFEST_VERSION } from "./types.ts";
11
12
 
12
13
  export type FastGenerateCheckResult =
13
14
  | {
@@ -55,6 +56,9 @@ export function runFastGenerateCheck(workspaceRootInput: string): FastGenerateCh
55
56
  const manifest = loadManifest(cacheDir);
56
57
  const trackedFiles = Object.keys(manifest.fileHashes).sort();
57
58
 
59
+ if (manifest.schemaVersion !== ORCHESTRATOR_MANIFEST_VERSION) {
60
+ return { kind: "miss", reason: "manifest schema version changed" };
61
+ }
58
62
  if (!manifest.inputFingerprint || trackedFiles.length === 0) {
59
63
  return { kind: "miss", reason: "manifest missing input fingerprint or file hashes" };
60
64
  }
@@ -83,7 +83,7 @@ export function updateManifestAfterWrite(
83
83
  ): OrchestratorManifest {
84
84
  return {
85
85
  ...manifest,
86
- fileHashes: { ...manifest.fileHashes, ...fileHashes },
86
+ fileHashes,
87
87
  priorAppGraph,
88
88
  inputFingerprint,
89
89
  ...(sourceFileIndex !== undefined ? { sourceFileIndex } : {}),
@@ -94,6 +94,8 @@ import { detectCapabilities } from "../classifier/capabilities.ts";
94
94
  import { detectSecrets } from "../classifier/secrets.ts";
95
95
  import { resolveByPackageName } from "../recipes/registry.ts";
96
96
  import { RECIPE_SCHEMA_VERSION } from "../recipes/definitions.ts";
97
+ import type { IntegrationRecipe } from "../types/integration.ts";
98
+ import type { RuntimeContext } from "../types/runtime.ts";
97
99
  import {
98
100
  FORGE_LOCK_SCHEMA_VERSION,
99
101
  GENERATED_DIR,
@@ -104,6 +106,14 @@ import { hashStable } from "../primitives/hash.ts";
104
106
  import { stableSortEmitFiles } from "../primitives/sort.ts";
105
107
  import { detectOrphanedGeneratedFiles } from "./orphans.ts";
106
108
  import type { DiscoverContext } from "./types.ts";
109
+ import {
110
+ createRenderContext,
111
+ parseAdapterContext,
112
+ renderAdapterModule,
113
+ renderIntegrationDoc,
114
+ renderIntegrationModule,
115
+ renderTestkitModule,
116
+ } from "../integration/render.ts";
107
117
  import {
108
118
  serializeAppGraphJson,
109
119
  serializeDataGraphJson,
@@ -233,6 +243,156 @@ function makeEmitFile(path: string, content: string): EmitFile {
233
243
  };
234
244
  }
235
245
 
246
+ function primaryPackageName(recipe: IntegrationRecipe): string {
247
+ return recipe.packages[0]?.packageName ?? recipe.alias;
248
+ }
249
+
250
+ function packageNamesForRecipe(recipe: IntegrationRecipe): string[] {
251
+ return recipe.packages.map((pkg) => pkg.packageName);
252
+ }
253
+
254
+ function compatibleContextsForRecipe(pkg: ClassifiedPackage): RuntimeContext[] {
255
+ const recipe = pkg.recipe ?? resolveByPackageName(pkg.api.name);
256
+ if (!recipe) {
257
+ return [...pkg.classification.compatible];
258
+ }
259
+
260
+ const contexts = new Set<RuntimeContext>(recipe.contexts.allowed);
261
+ for (const recipePackage of recipe.packages) {
262
+ for (const ctx of recipePackage.contexts?.allowed ?? []) {
263
+ contexts.add(ctx);
264
+ }
265
+ }
266
+ for (const ctx of pkg.classification.compatible) {
267
+ contexts.add(ctx);
268
+ }
269
+ return [...contexts];
270
+ }
271
+
272
+ function shouldEmitAdapter(
273
+ adapterFilename: string,
274
+ compatible: RuntimeContext[],
275
+ ): boolean {
276
+ const context = parseAdapterContext(adapterFilename);
277
+ return context === null || compatible.includes(context);
278
+ }
279
+
280
+ function buildRecipeGeneratedFiles(pkg: ClassifiedPackage): string[] {
281
+ const recipe = pkg.recipe ?? resolveByPackageName(pkg.api.name);
282
+ if (!recipe) {
283
+ return [];
284
+ }
285
+
286
+ const compatible = compatibleContextsForRecipe(pkg);
287
+ const paths: string[] = [];
288
+
289
+ for (const adapter of recipe.adapters) {
290
+ if (shouldEmitAdapter(adapter, compatible)) {
291
+ paths.push(`${GENERATED_DIR}/packages/${adapter}`);
292
+ }
293
+ }
294
+ for (const integration of recipe.integrations ?? []) {
295
+ paths.push(`${GENERATED_DIR}/integrations/${integration}`);
296
+ }
297
+ for (const testkit of recipe.testkits) {
298
+ paths.push(`${GENERATED_DIR}/testkits/${testkit}`);
299
+ }
300
+ for (const doc of recipe.docs) {
301
+ paths.push(`${GENERATED_DIR}/docs/${doc}`);
302
+ }
303
+
304
+ paths.push(
305
+ `${GENERATED_DIR}/runtimeMatrix.ts`,
306
+ `${GENERATED_DIR}/runtimeMatrix.json`,
307
+ `${GENERATED_DIR}/importGuards.ts`,
308
+ `${GENERATED_DIR}/importGuards.json`,
309
+ );
310
+
311
+ return paths.sort();
312
+ }
313
+
314
+ function buildIntegrationArtifactFiles(pkg: ClassifiedPackage): EmitFile[] {
315
+ const recipe = pkg.recipe ?? resolveByPackageName(pkg.api.name);
316
+ if (!recipe) {
317
+ return [];
318
+ }
319
+
320
+ const compatible = compatibleContextsForRecipe(pkg);
321
+ const incompatible = [...pkg.classification.incompatible];
322
+ const secrets = detectSecrets(pkg.api, recipe);
323
+ const packageNames = packageNamesForRecipe(recipe);
324
+ const packageName = primaryPackageName(recipe);
325
+ const templateCtx = createRenderContext({
326
+ alias: recipe.alias,
327
+ recipe,
328
+ context: "server",
329
+ packageName,
330
+ packageNames,
331
+ secrets,
332
+ compatible,
333
+ incompatible,
334
+ });
335
+ const files: EmitFile[] = [];
336
+
337
+ for (const adapter of recipe.adapters) {
338
+ if (!shouldEmitAdapter(adapter, compatible)) {
339
+ continue;
340
+ }
341
+ const context = parseAdapterContext(adapter) ?? "server";
342
+ files.push(
343
+ makeEmitFile(
344
+ `${GENERATED_DIR}/packages/${adapter}`,
345
+ renderAdapterModule({
346
+ alias: recipe.alias,
347
+ recipe,
348
+ context,
349
+ packageName,
350
+ packageNames,
351
+ secrets,
352
+ compatible,
353
+ incompatible,
354
+ }),
355
+ ),
356
+ );
357
+ }
358
+
359
+ for (const integration of recipe.integrations ?? []) {
360
+ files.push(
361
+ makeEmitFile(
362
+ `${GENERATED_DIR}/integrations/${integration}`,
363
+ renderIntegrationModule(integration, templateCtx),
364
+ ),
365
+ );
366
+ }
367
+
368
+ for (const testkit of recipe.testkits) {
369
+ files.push(
370
+ makeEmitFile(
371
+ `${GENERATED_DIR}/testkits/${testkit}`,
372
+ renderTestkitModule(recipe.alias, packageName, templateCtx),
373
+ ),
374
+ );
375
+ }
376
+
377
+ for (const doc of recipe.docs) {
378
+ files.push(
379
+ makeEmitFile(
380
+ `${GENERATED_DIR}/docs/${doc}`,
381
+ renderIntegrationDoc({
382
+ alias: recipe.alias,
383
+ recipe,
384
+ packageNames,
385
+ secrets,
386
+ compatible,
387
+ incompatible,
388
+ }),
389
+ ),
390
+ );
391
+ }
392
+
393
+ return files;
394
+ }
395
+
236
396
  function buildLockEntry(pkg: ClassifiedPackage): ForgeLockEntry {
237
397
  const recipe = pkg.recipe ?? resolveByPackageName(pkg.api.name);
238
398
  const secrets = detectSecrets(pkg.api, recipe ?? undefined);
@@ -250,7 +410,7 @@ function buildLockEntry(pkg: ClassifiedPackage): ForgeLockEntry {
250
410
  secrets,
251
411
  },
252
412
  secrets,
253
- generatedFiles: [],
413
+ generatedFiles: buildRecipeGeneratedFiles(pkg),
254
414
  contentChecksum: pkg.api.contentChecksum,
255
415
  };
256
416
  }
@@ -412,6 +572,7 @@ export function plan(input: PlanInput): EmitPlan {
412
572
  apiSurface,
413
573
  sources: input.ctx.sources,
414
574
  });
575
+ const integrationArtifactFiles = input.classified.flatMap(buildIntegrationArtifactFiles);
415
576
  const supportArtifactsMs = performance.now() - checkpoint;
416
577
  checkpoint = performance.now();
417
578
 
@@ -517,6 +678,7 @@ export function plan(input: PlanInput): EmitPlan {
517
678
  `${GENERATED_DIR}/importGuards.json`,
518
679
  serializeImportGuardsJson(matrix, input.appGraph.moduleGraph),
519
680
  ),
681
+ ...integrationArtifactFiles,
520
682
  makeEmitFile(
521
683
  `${GENERATED_DIR}/dataGraph.ts`,
522
684
  serializeDataGraphTs(dataGraph),
@@ -3,7 +3,7 @@ import type { Dependency } from "../types/package-graph.ts";
3
3
  import type { PackageManager } from "../types/runtime.ts";
4
4
  import type { SourceFileIndex } from "./workspace-index.ts";
5
5
 
6
- export const ORCHESTRATOR_MANIFEST_VERSION = "1";
6
+ export const ORCHESTRATOR_MANIFEST_VERSION = "2";
7
7
 
8
8
  export interface DiscoverContext {
9
9
  workspaceRoot: string;
@@ -213,6 +213,23 @@ function capitalizeSegment(value: string): string {
213
213
  }
214
214
 
215
215
  async function applyMocks(workspaceRoot: string, lock: ForgeLock | null): Promise<void> {
216
+ if (lock) {
217
+ for (const pkg of lock.packages) {
218
+ for (const secret of pkg.secrets) {
219
+ if (!process.env[secret.envVar]) {
220
+ process.env[secret.envVar] = `sk_forge_mock_${secret.envVar.toLowerCase()}`;
221
+ }
222
+ }
223
+ }
224
+ }
225
+
226
+ const bunMock = (globalThis as typeof globalThis & {
227
+ Bun?: { mock?: { module?: (specifier: string, factory: () => unknown) => void } };
228
+ }).Bun?.mock;
229
+ if (typeof bunMock?.module !== "function") {
230
+ return;
231
+ }
232
+
216
233
  const mockMap = loadMockMap(workspaceRoot);
217
234
 
218
235
  for (const [packageName, relativePath] of Object.entries(mockMap).sort()) {
@@ -225,11 +242,7 @@ async function applyMocks(workspaceRoot: string, lock: ForgeLock | null): Promis
225
242
  const factoryName = `create${capitalizeSegment(packageName)}Mock`;
226
243
  const factory = mod[factoryName];
227
244
 
228
- const bunMock = Bun as typeof Bun & {
229
- mock: { module: (specifier: string, factory: () => unknown) => void };
230
- };
231
-
232
- bunMock.mock.module(packageName, () => {
245
+ bunMock.module(packageName, () => {
233
246
  if (typeof factory === "function") {
234
247
  const mockValue = (factory as () => unknown)();
235
248
  if (typeof mockValue === "function") {
@@ -243,16 +256,6 @@ async function applyMocks(workspaceRoot: string, lock: ForgeLock | null): Promis
243
256
  return mod;
244
257
  });
245
258
  }
246
-
247
- if (lock) {
248
- for (const pkg of lock.packages) {
249
- for (const secret of pkg.secrets) {
250
- if (!process.env[secret.envVar]) {
251
- process.env[secret.envVar] = `sk_forge_mock_${secret.envVar.toLowerCase()}`;
252
- }
253
- }
254
- }
255
- }
256
259
  }
257
260
 
258
261
  let activeDbAdapter: DbAdapter | null = null;
@@ -1,3 +1,3 @@
1
- export const FORGEOS_VERSION = "0.1.0-alpha.26";
1
+ export const FORGEOS_VERSION = "0.1.0-alpha.28";
2
2
  export const GENERATOR_VERSION = FORGEOS_VERSION;
3
3
  export const CLI_VERSION = FORGEOS_VERSION;
@@ -183,6 +183,40 @@ export function categorizeFiles(
183
183
  };
184
184
  }
185
185
 
186
+ export function filterCategorizedSummary(
187
+ summary: CategorizedFileSummary,
188
+ includeTypes: ChangeType[],
189
+ sampleSize = 8,
190
+ ): CategorizedFileSummary {
191
+ const include = new Set(includeTypes);
192
+ const byType = Object.fromEntries(
193
+ CHANGE_TYPES.map((type) => {
194
+ const current = summary.byType[type];
195
+ return [
196
+ type,
197
+ include.has(type) ? current : { count: 0, sample: [], hidden: 0 },
198
+ ];
199
+ }),
200
+ ) as Record<ChangeType, FileListSummary>;
201
+ const totalCount = includeTypes.reduce((count, type) => count + summary.byType[type].count, 0);
202
+ const totalSample = includeTypes
203
+ .flatMap((type) => summary.byType[type].sample)
204
+ .sort()
205
+ .slice(0, sampleSize);
206
+ const primaryTypes = CHANGE_TYPES
207
+ .filter((type) => byType[type].count > 0)
208
+ .sort((left, right) => byType[right].count - byType[left].count);
209
+ return {
210
+ total: {
211
+ count: totalCount,
212
+ sample: totalSample,
213
+ hidden: Math.max(0, totalCount - totalSample.length),
214
+ },
215
+ byType,
216
+ primaryTypes,
217
+ };
218
+ }
219
+
186
220
  export function summarizeChangeTypes(summary: CategorizedFileSummary): string {
187
221
  return summary.primaryTypes
188
222
  .slice(0, 5)
@@ -1,3 +0,0 @@
1
- artifactId=forge-java-adapter
2
- groupId=dev.forgeos
3
- version=0.1.0-alpha.11
@@ -1,23 +0,0 @@
1
- dev/forgeos/adapter/TypedForgeHandler.class
2
- dev/forgeos/adapter/Forge.class
3
- dev/forgeos/adapter/Diagnostic.class
4
- dev/forgeos/adapter/ForgeHttpHandler.class
5
- dev/forgeos/adapter/Service.class
6
- dev/forgeos/adapter/Schemas.class
7
- dev/forgeos/adapter/ForgeCall.class
8
- dev/forgeos/adapter/Risk.class
9
- dev/forgeos/adapter/ForgeHandler.class
10
- dev/forgeos/adapter/Entry.class
11
- dev/forgeos/adapter/TransactionMode.class
12
- dev/forgeos/adapter/ResponseEnvelope.class
13
- dev/forgeos/adapter/RequestEnvelope.class
14
- dev/forgeos/adapter/ForgeRegistry$RegistryOption.class
15
- dev/forgeos/adapter/Auth.class
16
- dev/forgeos/adapter/Manifest.class
17
- dev/forgeos/adapter/ForgeContext.class
18
- dev/forgeos/adapter/ForgeRegistry$RegisteredEntry.class
19
- dev/forgeos/adapter/ForgeRegistry$EntryOption.class
20
- dev/forgeos/adapter/EntryKind.class
21
- dev/forgeos/adapter/ForgeRegistry.class
22
- dev/forgeos/adapter/ErrorInfo.class
23
- dev/forgeos/adapter/Json.class
@@ -1,20 +0,0 @@
1
- /home/runner/work/forge/forge/adapters/java/src/main/java/dev/forgeos/adapter/Auth.java
2
- /home/runner/work/forge/forge/adapters/java/src/main/java/dev/forgeos/adapter/Diagnostic.java
3
- /home/runner/work/forge/forge/adapters/java/src/main/java/dev/forgeos/adapter/Entry.java
4
- /home/runner/work/forge/forge/adapters/java/src/main/java/dev/forgeos/adapter/EntryKind.java
5
- /home/runner/work/forge/forge/adapters/java/src/main/java/dev/forgeos/adapter/ErrorInfo.java
6
- /home/runner/work/forge/forge/adapters/java/src/main/java/dev/forgeos/adapter/Forge.java
7
- /home/runner/work/forge/forge/adapters/java/src/main/java/dev/forgeos/adapter/ForgeCall.java
8
- /home/runner/work/forge/forge/adapters/java/src/main/java/dev/forgeos/adapter/ForgeContext.java
9
- /home/runner/work/forge/forge/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHandler.java
10
- /home/runner/work/forge/forge/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHttpHandler.java
11
- /home/runner/work/forge/forge/adapters/java/src/main/java/dev/forgeos/adapter/ForgeRegistry.java
12
- /home/runner/work/forge/forge/adapters/java/src/main/java/dev/forgeos/adapter/Json.java
13
- /home/runner/work/forge/forge/adapters/java/src/main/java/dev/forgeos/adapter/Manifest.java
14
- /home/runner/work/forge/forge/adapters/java/src/main/java/dev/forgeos/adapter/RequestEnvelope.java
15
- /home/runner/work/forge/forge/adapters/java/src/main/java/dev/forgeos/adapter/ResponseEnvelope.java
16
- /home/runner/work/forge/forge/adapters/java/src/main/java/dev/forgeos/adapter/Risk.java
17
- /home/runner/work/forge/forge/adapters/java/src/main/java/dev/forgeos/adapter/Schemas.java
18
- /home/runner/work/forge/forge/adapters/java/src/main/java/dev/forgeos/adapter/Service.java
19
- /home/runner/work/forge/forge/adapters/java/src/main/java/dev/forgeos/adapter/TransactionMode.java
20
- /home/runner/work/forge/forge/adapters/java/src/main/java/dev/forgeos/adapter/TypedForgeHandler.java
@@ -1,3 +0,0 @@
1
- artifactId=forge-java-spring-boot-starter
2
- groupId=dev.forgeos
3
- version=0.1.0-alpha.11
@@ -1,6 +0,0 @@
1
- dev/forgeos/adapter/spring/ForgeServiceBeanCondition.class
2
- dev/forgeos/adapter/spring/ForgeSpringRuntime.class
3
- dev/forgeos/adapter/spring/ForgeExternalService.class
4
- dev/forgeos/adapter/spring/ForgeSpringAutoConfiguration.class
5
- dev/forgeos/adapter/spring/ForgeQuery.class
6
- dev/forgeos/adapter/spring/ForgeCommand.class
@@ -1,6 +0,0 @@
1
- /home/runner/work/forge/forge/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeCommand.java
2
- /home/runner/work/forge/forge/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeExternalService.java
3
- /home/runner/work/forge/forge/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeQuery.java
4
- /home/runner/work/forge/forge/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeServiceBeanCondition.java
5
- /home/runner/work/forge/forge/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringAutoConfiguration.java
6
- /home/runner/work/forge/forge/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringRuntime.java
@@ -1,3 +0,0 @@
1
- artifactId=java-billing
2
- groupId=dev.forgeos.examples
3
- version=0.1.0-alpha.11
@@ -1,5 +0,0 @@
1
- dev/forgeos/examples/billing/Main.class
2
- dev/forgeos/examples/billing/Invoice.class
3
- dev/forgeos/examples/billing/Main$EmptyInput.class
4
- dev/forgeos/examples/billing/Main$Options.class
5
- dev/forgeos/examples/billing/CreateInvoiceInput.class
@@ -1,3 +0,0 @@
1
- /home/runner/work/forge/forge/examples/java-billing/src/main/java/dev/forgeos/examples/billing/CreateInvoiceInput.java
2
- /home/runner/work/forge/forge/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Invoice.java
3
- /home/runner/work/forge/forge/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Main.java