ttsc 0.34.3 → 0.36.0

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 (87) hide show
  1. package/driver/compilation_snapshot.go +267 -0
  2. package/driver/compilation_snapshot_keeps_inputs_across_stages_test.go +130 -0
  3. package/driver/compilation_snapshot_preserves_checked_emission_test.go +100 -0
  4. package/driver/graph.go +9 -3
  5. package/driver/program.go +45 -14
  6. package/lib/compiler/internal/CompilationSnapshot.d.ts +33 -0
  7. package/lib/compiler/internal/CompilationSnapshot.js +90 -0
  8. package/lib/compiler/internal/CompilationSnapshot.js.map +1 -0
  9. package/lib/compiler/internal/build/BuildExecution.d.ts +33 -4
  10. package/lib/compiler/internal/build/BuildExecution.js +81 -8
  11. package/lib/compiler/internal/build/BuildExecution.js.map +1 -1
  12. package/lib/compiler/internal/build/RunBuildOptions.d.ts +2 -0
  13. package/lib/compiler/internal/build/RuntimeBuildCache.d.ts +47 -0
  14. package/lib/compiler/internal/build/RuntimeBuildCache.js +235 -0
  15. package/lib/compiler/internal/build/RuntimeBuildCache.js.map +1 -0
  16. package/lib/compiler/internal/build/RuntimeBuildInputs.d.ts +44 -0
  17. package/lib/compiler/internal/build/RuntimeBuildInputs.js +282 -0
  18. package/lib/compiler/internal/build/RuntimeBuildInputs.js.map +1 -0
  19. package/lib/compiler/internal/build/RuntimeInputProof.d.ts +50 -0
  20. package/lib/compiler/internal/build/RuntimeInputProof.js +315 -0
  21. package/lib/compiler/internal/build/RuntimeInputProof.js.map +1 -0
  22. package/lib/compiler/internal/build/TsgoArguments.d.ts +5 -4
  23. package/lib/compiler/internal/build/TsgoArguments.js +11 -5
  24. package/lib/compiler/internal/build/TsgoArguments.js.map +1 -1
  25. package/lib/compiler/internal/build/appendBuildOutput.js +4 -0
  26. package/lib/compiler/internal/build/appendBuildOutput.js.map +1 -1
  27. package/lib/compiler/internal/build/runBuild.d.ts +3 -3
  28. package/lib/compiler/internal/build/runBuild.js +17 -4
  29. package/lib/compiler/internal/build/runBuild.js.map +1 -1
  30. package/lib/compiler/internal/build/runExternalEmitProvenance.d.ts +12 -13
  31. package/lib/compiler/internal/build/runExternalEmitProvenance.js +24 -18
  32. package/lib/compiler/internal/build/runExternalEmitProvenance.js.map +1 -1
  33. package/lib/compiler/internal/parseNativeTransformOutput.js +4 -0
  34. package/lib/compiler/internal/parseNativeTransformOutput.js.map +1 -1
  35. package/lib/compiler/internal/runNativeCheckWithObservations.d.ts +18 -14
  36. package/lib/compiler/internal/runNativeCheckWithObservations.js +42 -18
  37. package/lib/compiler/internal/runNativeCheckWithObservations.js.map +1 -1
  38. package/lib/compiler/internal/sharedHost/inheritedSidecarEnv.d.ts +2 -1
  39. package/lib/compiler/internal/sharedHost/inheritedSidecarEnv.js +4 -1
  40. package/lib/compiler/internal/sharedHost/inheritedSidecarEnv.js.map +1 -1
  41. package/lib/compiler/internal/transformProjectInMemory.d.ts +3 -3
  42. package/lib/compiler/internal/transformProjectInMemory.js +29 -5
  43. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  44. package/lib/flags/FLAG_SCHEMA.js +7 -0
  45. package/lib/flags/FLAG_SCHEMA.js.map +1 -1
  46. package/lib/launcher/internal/parseTtsxCLI.d.ts +1 -0
  47. package/lib/launcher/internal/parseTtsxCLI.js +1 -0
  48. package/lib/launcher/internal/parseTtsxCLI.js.map +1 -1
  49. package/lib/launcher/internal/prepareExecution.d.ts +2 -0
  50. package/lib/launcher/internal/prepareExecution.js +6 -0
  51. package/lib/launcher/internal/prepareExecution.js.map +1 -1
  52. package/lib/launcher/internal/runTtsx.js +5 -0
  53. package/lib/launcher/internal/runTtsx.js.map +1 -1
  54. package/lib/launcher/internal/runtime/installRuntimeHooks.js +7 -0
  55. package/lib/launcher/internal/runtime/installRuntimeHooks.js.map +1 -1
  56. package/lib/plugin/internal/load/loadProjectPlugins.js +1 -0
  57. package/lib/plugin/internal/load/loadProjectPlugins.js.map +1 -1
  58. package/lib/plugin/internal/source/SourceBuildCacheLayout.d.ts +2 -0
  59. package/lib/plugin/internal/source/SourceBuildCacheLayout.js +3 -0
  60. package/lib/plugin/internal/source/SourceBuildCacheLayout.js.map +1 -1
  61. package/lib/structures/ITtscCompilerTransformation.d.ts +5 -0
  62. package/lib/structures/ITtscPluginCapabilities.d.ts +13 -2
  63. package/package.json +8 -8
  64. package/src/compiler/internal/CompilationSnapshot.ts +97 -0
  65. package/src/compiler/internal/build/BuildExecution.ts +128 -20
  66. package/src/compiler/internal/build/RunBuildOptions.ts +3 -0
  67. package/src/compiler/internal/build/RuntimeBuildCache.ts +252 -0
  68. package/src/compiler/internal/build/RuntimeBuildInputs.ts +349 -0
  69. package/src/compiler/internal/build/RuntimeInputProof.ts +346 -0
  70. package/src/compiler/internal/build/TsgoArguments.ts +11 -5
  71. package/src/compiler/internal/build/appendBuildOutput.ts +4 -0
  72. package/src/compiler/internal/build/runBuild.ts +20 -5
  73. package/src/compiler/internal/build/runExternalEmitProvenance.ts +25 -23
  74. package/src/compiler/internal/parseNativeTransformOutput.ts +5 -0
  75. package/src/compiler/internal/runNativeCheckWithObservations.ts +41 -18
  76. package/src/compiler/internal/sharedHost/inheritedSidecarEnv.ts +8 -1
  77. package/src/compiler/internal/transformProjectInMemory.ts +46 -5
  78. package/src/flags/FLAG_SCHEMA.ts +7 -0
  79. package/src/launcher/internal/parseTtsxCLI.ts +1 -0
  80. package/src/launcher/internal/prepareExecution.ts +13 -0
  81. package/src/launcher/internal/runTtsx.ts +5 -0
  82. package/src/launcher/internal/runtime/installRuntimeHooks.ts +11 -0
  83. package/src/plugin/internal/load/loadProjectPlugins.ts +1 -0
  84. package/src/plugin/internal/source/SourceBuildCacheLayout.ts +4 -0
  85. package/src/structures/ITtscCompilerTransformation.ts +6 -0
  86. package/src/structures/ITtscPluginCapabilities.ts +14 -2
  87. package/utility/host.go +22 -1
@@ -7,36 +7,40 @@ import type { TtscBuildResult } from "../../structures/internal/TtscBuildResult"
7
7
  import { parseReferenceGraph } from "./parseNativeTransformOutput";
8
8
 
9
9
  /**
10
- * Execute a check with its same-generation input observations, preserving the
11
- * selected host, original diagnostics, streams and status.
10
+ * Execute a check or opted-in build with same-generation input observations,
11
+ * preserving the selected host, original diagnostics, streams and status.
12
12
  *
13
- * The selected native verb must be check before negotiating this check-only
14
- * channel. A check-stage descriptor can also serve effectful fix/format verbs;
15
- * those retain their original result and never acquire an invented check graph.
16
- * An opted-in host writes a private sidecar even when its check fails. Hosts
13
+ * The selected native verb must be check, or build with the separate
14
+ * emitObservations capability, before negotiating its generation channel. A
15
+ * check-stage descriptor can also serve effectful fix/format verbs; those
16
+ * retain their original result and never acquire an invented check graph. An
17
+ * opted-in host writes a private sidecar even when its check fails. Hosts
17
18
  * without that transport run their original command under their existing
18
19
  * descriptor and declared-input contract; omission does not assert complete
19
20
  * driver observations. No check is rerun to infer what the earlier generation
20
21
  * read. Missing metadata after an interrupted failed opted-in check likewise
21
- * withholds reuse; missing or malformed successful metadata is a protocol
22
- * error, not an empty proof.
22
+ * withholds reuse. Missing or malformed successful check metadata is a protocol
23
+ * error. Build metadata is optional cache evidence: unavailable setup, records
24
+ * or cleanup retain the original build result with incomplete observations, so
25
+ * loss of caching cannot turn a fresh successful emit into failure.
23
26
  *
24
27
  * The callback receives only additional transport arguments. This operation
25
28
  * owns their accepted canonical private directory and attempts file and empty
26
29
  * directory removal without recursive deletion. Setup observes ownership before
27
30
  * entering the cleanup boundary, so a setup observation failure can leave the
28
31
  * allocation unclaimed. Cleanup failures retain the original thrown failure or
29
- * completed result as their cause. Before reading and removing the artifact,
30
- * the parent must still have its captured native identity; a replaced directory
31
- * is left untouched and reported as failure. These observations do not provide
32
- * an atomic descriptor or ABA guarantee.
32
+ * completed check result as their cause. Before reading and removing the
33
+ * artifact, the parent must still have its captured native identity; a replaced
34
+ * directory is left untouched and withholds build reuse or reports check
35
+ * failure. These observations do not provide an atomic descriptor or ABA
36
+ * guarantee.
33
37
  *
34
- * @evidence contracts/common.md#principled-implementation Only an actual check verb with the declared capability negotiates same-generation observations; effectful write verbs retain their own result without being projected as reusable check generations. The selected check generation's sidecar carries driver-observed input states; hosts with a different observation contract keep their original command and declared-input authority without fabricating driver completeness or incompleteness.
38
+ * @evidence contracts/common.md#principled-implementation Actual check and build verbs negotiate independent declared observation capabilities; fix/format verbs retain their own result. Build witness IO/protocol failures preserve the one original build with incomplete observations, while checks retain their strict metadata contract. Legacy hosts keep their original command without fabricated completeness.
35
39
  * @evidence contracts/common.md#clear-and-simple-design The actual verb and descriptor opt-in jointly select one adapter for strict wire decoding and private artifact lifetime; its callback retains command and diagnostic normalization ownership.
36
40
  * @evidence contracts/common.md#prohibited-implementation-shortcuts Neither a later project-input query nor a separate transform can reconstruct check observations; unknown hosts receive no guessed flag and malformed metadata cannot become a complete empty observation.
37
41
  * @evidence contracts/common.md#meaningful-documentation Native paragraphs distinguish failed-check metadata, unavailable transport, protocol errors and cleanup ownership, with body and tags separated according to the documentation skill.
38
42
  * @evidence contracts/portability.md#os-neutral-implementation Node native absolute paths locate the canonical private artifact, and an additional argv element conveys it without shell quoting or platform-specific filename guesses.
39
- * @evidence contracts/performance.md#efficient-algorithms One original check callback runs; this adapter adds canonical temp acquisition/native ownership observations, complete sidecar read/JSON decoding and indexed input membership/witness validation. Work and temporary storage follow wire/path bytes and declared inputs plus delegated check runtime; it does not add a second compiler generation or source-content rehash, and supplies no callback timeout or metadata-byte ceiling.
43
+ * @evidence contracts/performance.md#efficient-algorithms One original check or build callback runs; this adapter adds canonical temp acquisition/native ownership observations, complete sidecar read/JSON decoding and indexed input membership/witness validation. Work and temporary storage follow wire/path bytes and declared inputs plus delegated producer runtime; it does not add a second compiler generation or source-content rehash, and supplies no callback timeout or metadata-byte ceiling.
40
44
  *
41
45
  * @evidenceExclude contracts/performance.md#reuse-equivalent-work This adapter supplies the generation's proof to its cache owner rather than retaining or sharing a completed check itself.
42
46
  *
@@ -48,17 +52,32 @@ export function runNativeCheckWithObservations(
48
52
  /** Actual argv verb; legacy direct callers already represent a check. */
49
53
  command: string = "check",
50
54
  ): TtscBuildResult {
51
- if (command !== "check" || plugin.capabilities?.checkObservations !== true)
55
+ const emitting =
56
+ command === "build" && plugin.capabilities?.emitObservations === true;
57
+ if (
58
+ !emitting &&
59
+ (command !== "check" || plugin.capabilities?.checkObservations !== true)
60
+ )
52
61
  return run([]);
53
62
 
54
- const directory = createCanonicalTempDirectory("ttsc-check-observations-");
55
- const ownership = fs.lstatSync(directory);
63
+ let directory: string;
64
+ let ownership: fs.Stats;
65
+ try {
66
+ directory = createCanonicalTempDirectory("ttsc-check-observations-");
67
+ ownership = fs.lstatSync(directory);
68
+ } catch (error) {
69
+ if (emitting) return { ...run([]), observationsComplete: false };
70
+ throw error;
71
+ }
56
72
  const file = path.join(directory, "observations.json");
57
73
  let completed: TtscBuildResult | undefined;
58
74
  let failed = false;
59
75
  let failure: unknown;
60
76
  try {
61
- completed = run(["--check-observations-json=" + file]);
77
+ completed = run([
78
+ (emitting ? "--emit-observations-json=" : "--check-observations-json=") +
79
+ file,
80
+ ]);
62
81
  if (completed.processCompletedNormally !== true)
63
82
  return { ...completed, observationsComplete: false };
64
83
  let text: string;
@@ -68,6 +87,7 @@ export function runNativeCheckWithObservations(
68
87
  throw new Error("check observation artifact is not an ordinary file");
69
88
  text = fs.readFileSync(file, "utf8");
70
89
  } catch (error) {
90
+ if (emitting) return { ...completed, observationsComplete: false };
71
91
  if (
72
92
  completed.status !== 0 &&
73
93
  (error as NodeJS.ErrnoException).code === "ENOENT"
@@ -80,6 +100,7 @@ export function runNativeCheckWithObservations(
80
100
  try {
81
101
  return { ...completed, ...parseObservations(JSON.parse(text)) };
82
102
  } catch (error) {
103
+ if (emitting) return { ...completed, observationsComplete: false };
83
104
  throw new Error("ttsc: invalid check observation metadata", {
84
105
  cause: new AggregateError([completed, error]),
85
106
  });
@@ -108,6 +129,8 @@ export function runNativeCheckWithObservations(
108
129
  }
109
130
  }
110
131
  if (errors.length !== 0) {
132
+ if (emitting && completed !== undefined && !failed)
133
+ return { ...completed, observationsComplete: false };
111
134
  const primary = failed ? failure : completed;
112
135
  throw new AggregateError(
113
136
  [primary, ...errors],
@@ -1,3 +1,4 @@
1
+ import { CompilationSnapshot } from "../CompilationSnapshot";
1
2
  import { SidecarEnvironment } from "./SidecarEnvironment";
2
3
  import { clearInheritedSemanticConfigPath } from "./clearInheritedSemanticConfigPath";
3
4
  import { clearInheritedTsgoArgs } from "./clearInheritedTsgoArgs";
@@ -6,7 +7,8 @@ import { publishLinkedTransformPlugins } from "./publishLinkedTransformPlugins";
6
7
  /**
7
8
  * Merge inherited and caller environments with native variable-name identity,
8
9
  * then clear per-invocation payloads this lane never published: forwarded tsgo
9
- * arguments, semantic config ownership and the linked-plugin manifest.
10
+ * arguments, compilation snapshot, semantic config ownership and the
11
+ * linked-plugin manifest.
10
12
  *
11
13
  * Use it at every spawn whose child can reach `driver.LoadProgram` — the
12
14
  * `api-compile` / `api-transform` hosts, and the plugin loader, whose `ttsx`
@@ -40,6 +42,11 @@ export function inheritedSidecarEnv(
40
42
  );
41
43
  clearInheritedTsgoArgs(env, callerEnv);
42
44
  clearInheritedSemanticConfigPath(env, callerEnv);
45
+ SidecarEnvironment.write(
46
+ env,
47
+ CompilationSnapshot.ENV,
48
+ SidecarEnvironment.read(callerEnv, CompilationSnapshot.ENV),
49
+ );
43
50
  publishLinkedTransformPlugins(env, callerEnv, []);
44
51
  return env;
45
52
  }
@@ -11,6 +11,7 @@ import type { ITtscCompilerTransformation } from "../../structures/ITtscCompiler
11
11
  import type { ITtscLoadedNativePlugin } from "../../structures/internal/ITtscLoadedNativePlugin";
12
12
  import type { ITtscParsedProjectConfig } from "../../structures/internal/ITtscParsedProjectConfig";
13
13
  import type { TtscBuildResult } from "../../structures/internal/TtscBuildResult";
14
+ import { CompilationSnapshot } from "./CompilationSnapshot";
14
15
  import { appendBuildOutput, mergeCheckGraphs } from "./build/appendBuildOutput";
15
16
  import { normalizeBuildOutput } from "./build/normalizeBuildOutput";
16
17
  import { buildNativeCompiler } from "./buildNativeCompiler";
@@ -60,10 +61,10 @@ import { spawnNative } from "./spawnNative";
60
61
  * @evidence contracts/common.md#clear-and-simple-design One router composes built-in and plugin-backed transforms; envelope parsers, environment construction, proof merging and negative-only observation limits have separate shared helpers.
61
62
  * @evidence contracts/common.md#prohibited-implementation-shortcuts Missing input proof cannot be repaired by a postcompile baseline; conventional deferred config reads accept only the actual native consumer's proof, while malformed required TypeScript output is a protocol error.
62
63
  * @evidence contracts/common.md#meaningful-documentation Native paragraphs describe transform lanes and stage ordering; separated result members distinguish output, advisory graph/dependency data, evaluation witnesses and unstable source markers under documentation guidance.
63
- * @evidence contracts/performance.md#efficient-algorithms Stage filtering, declared-input Sets and proof maps scale with plugin/input counts; merged paths are sorted with path-text comparison costs. Each stage can rehash/re-realpath declared inputs, serialize plugin/config records and parse complete output; discovery, native capability probes, source builds and child execution remain delegated costs of this call. No byte/work ceiling or measured dominant-cost ranking is asserted.
64
- * @evidence contracts/performance.md#reuse-equivalent-work Source-plugin artifacts are shared by their owning loader; this transform intentionally produces a current generation and gives downstream consumers the graph, source states and host witnesses required to qualify reuse.
64
+ * @evidence contracts/performance.md#efficient-algorithms Stage filtering, declared-input Sets and proof maps scale with plugin/input counts; merged paths are sorted with path-text comparison costs. Each stage can rehash/re-realpath declared inputs, serialize plugin/config records and parse complete output; discovery, native capability probes, source builds and child execution remain delegated costs of this call. The invocation snapshot adds full captured-population parsing and dirty document encoding/writes per native stage. No byte/work ceiling or measured dominant-cost ranking is asserted.
65
+ * @evidence contracts/performance.md#reuse-equivalent-work Descriptor/source discovery precedes snapshot allocation so invocation-private paths do not vary its environment cache identity. Source-plugin artifacts are shared by their owning loader; this transform intentionally produces a current generation and gives downstream consumers the graph, source states and host witnesses required to qualify reuse.
65
66
  *
66
- * @evidence contracts/performance.md#bound-retention-and-release-resources This router retains discovery records, per-stage proof maps and accumulated diagnostics/output until synchronous stage completion, then transfers the returned envelope. Native capture/observation owners attempt their own cleanup; suppressed cleanup failures and descendant release remain unconfirmed where those owners do not join them. Artifact-cache retention belongs to the builder, and no stage timeout or input/output byte ceiling is supplied here.
67
+ * @evidence contracts/performance.md#bound-retention-and-release-resources This router retains discovery records, per-stage proof maps and accumulated diagnostics/output until synchronous stage completion, then transfers the returned envelope. The invocation coordinator attempts identity-checked snapshot removal and propagates or aggregates removal failure. Other native capture/observation owners attempt their own cleanup; suppressed cleanup failures and descendant release remain unconfirmed where those owners do not join them. Artifact-cache retention belongs to the builder, and no stage timeout or input/output byte ceiling is supplied here.
67
68
  *
68
69
  * @evidence contracts/portability.md#os-neutral-implementation Native paths remain filesystem coordinates, child argv are arrays, and environment helpers preserve Windows key equivalence and explicit Node/tsgo/config anchors without shell parsing or native method patching.
69
70
  */
@@ -123,6 +124,16 @@ export function transformProjectInMemory(options: ITtscCompilerContext): {
123
124
  projectRoot: options.projectRoot,
124
125
  tsconfig: options.tsconfig,
125
126
  });
127
+ return CompilationSnapshot.run(options.env, (env) =>
128
+ transformWithSnapshot({ ...options, env }, loaded),
129
+ );
130
+ }
131
+
132
+ /** Run native checks and transformation after descriptor/source discovery. */
133
+ function transformWithSnapshot(
134
+ options: ITtscCompilerContext,
135
+ loaded: ReturnType<typeof loadProjectPlugins>,
136
+ ): ReturnType<typeof transformProjectInMemory> {
126
137
  if (loaded.nativePlugins.length !== 0) {
127
138
  // Every step below runs binaries built from these sources, so the output
128
139
  // is a function of their state, which the envelope carries for a consumer
@@ -393,7 +404,11 @@ function transformProjectWithPlugins(
393
404
  );
394
405
  return {
395
406
  ...envelopeSideChannels(transformed),
396
- graph: mergeCheckGraphs(checked.graph, transformed.graph),
407
+ graph: mergeTransformGraphs(
408
+ checked.graph,
409
+ transformed.graph,
410
+ checks.length,
411
+ ),
397
412
  ...observationLimitations(
398
413
  [loaded, checked, transformed],
399
414
  hostInputHashes,
@@ -476,7 +491,7 @@ function transformProjectWithPlugins(
476
491
  );
477
492
  return {
478
493
  ...envelopeSideChannels(output),
479
- graph: mergeCheckGraphs(checked.graph, output.graph),
494
+ graph: mergeTransformGraphs(checked.graph, output.graph, checks.length),
480
495
  ...observationLimitations(
481
496
  [loaded, checked, output],
482
497
  hostInputHashes,
@@ -708,6 +723,7 @@ function runNativeChecks(
708
723
  stdout: "",
709
724
  stderr: "",
710
725
  };
726
+ let snapshotsComplete = true;
711
727
  for (const plugin of checks) {
712
728
  const checked = runNativeCheckWithObservations(plugin, (extraArgs) => {
713
729
  const res = spawnNative(
@@ -748,7 +764,12 @@ function runNativeChecks(
748
764
  project.root,
749
765
  );
750
766
  });
767
+ snapshotsComplete &&= checked.graph?.compilationSnapshot !== undefined;
751
768
  result = appendBuildOutput(result, checked);
769
+ if (!snapshotsComplete && result.graph?.compilationSnapshot !== undefined) {
770
+ const { compilationSnapshot: _snapshot, ...unqualified } = result.graph;
771
+ result.graph = unqualified;
772
+ }
752
773
  if (result.status !== 0) {
753
774
  return result;
754
775
  }
@@ -877,3 +898,23 @@ function nativePluginEnv(
877
898
  );
878
899
  return env;
879
900
  }
901
+
902
+ /**
903
+ * A legacy check with no graph cannot certify the transform's shared input
904
+ * view.
905
+ */
906
+ function mergeTransformGraphs(
907
+ checked: TtscBuildResult["graph"],
908
+ transformed: TtscBuildResult["graph"],
909
+ checks: number,
910
+ ): TtscBuildResult["graph"] {
911
+ const graph = mergeCheckGraphs(checked, transformed);
912
+ if (
913
+ graph === undefined ||
914
+ checks === 0 ||
915
+ checked?.compilationSnapshot !== undefined
916
+ )
917
+ return graph;
918
+ const { compilationSnapshot: _snapshot, ...unqualified } = graph;
919
+ return unqualified;
920
+ }
@@ -352,6 +352,13 @@ export const FLAG_SCHEMA: readonly FlagSpec[] = [
352
352
  // -------------------------------------------------------------------------
353
353
  // ttsx-specific options.
354
354
  // -------------------------------------------------------------------------
355
+ {
356
+ name: "--no-cache",
357
+ kind: "boolean",
358
+ subcommands: ["ttsx"],
359
+ consumedBy: ["launcher"],
360
+ description: "Disable persistent runtime project emit reuse.",
361
+ },
355
362
  {
356
363
  name: "--require",
357
364
  aliases: ["-r"],
@@ -120,6 +120,7 @@ export function parseTtsxCLI(argv: readonly string[]) {
120
120
  checkers: getNumber(result, "--checkers"),
121
121
  cwd: getString(result, "--cwd"),
122
122
  entry,
123
+ noCache: getBoolean(result, "--no-cache") === true,
123
124
  noPlugins: getBoolean(result, "--no-plugins") === true,
124
125
  passthrough: postEntryArgs,
125
126
  preload,
@@ -65,6 +65,9 @@ export function prepareExecution(
65
65
 
66
66
  /** Internal cache key for more than one checked entry in this process. */
67
67
  runtimeCacheKey?: string;
68
+
69
+ /** False bypasses persistent project emits while keeping normal execution. */
70
+ runtimeCache?: boolean;
68
71
  } = {},
69
72
  ): {
70
73
  /** Claimed runtime generation whose cleanup ownership transfers to the caller. */
@@ -383,6 +386,15 @@ function createProjectContext(
383
386
  runtimeCacheKey,
384
387
  processDir,
385
388
  pluginCacheDir: explicitCacheDir === undefined ? undefined : cacheDir,
389
+ runtimeBuildCacheDir:
390
+ options.runtimeCache === false ||
391
+ env.TTSX_DISABLE_CACHE === "1" ||
392
+ env.TTSC_PLUGIN_DESCRIPTOR_LOAD === "1"
393
+ ? undefined
394
+ : path.join(
395
+ cacheRoot,
396
+ SourceBuildCacheLayout.RUNTIME_BUILD_CACHE_DIRNAME,
397
+ ),
386
398
  orphanCacheDir: path.join(
387
399
  cacheRoot,
388
400
  SourceBuildCacheLayout.ORPHAN_CACHE_DIRNAME,
@@ -527,6 +539,7 @@ function buildProject(
527
539
  emit: true,
528
540
  env: options.env,
529
541
  cacheDir: context.pluginCacheDir,
542
+ runtimeCacheDir: context.runtimeBuildCacheDir,
530
543
  outDir: context.emitDir,
531
544
  // Every output this build writes stays in ttsx's private directory: a
532
545
  // declared `declarationDir`, `tsBuildInfoFile`, or `outFile`, and any
@@ -110,6 +110,7 @@ async function run(
110
110
  cacheDir: resolveCacheDir(cwd, parsed.cacheDir),
111
111
  checkers: parsed.checkers,
112
112
  cwd,
113
+ env: parsed.noCache ? { TTSX_DISABLE_CACHE: "1" } : undefined,
113
114
  passthrough: parsed.tsgoFlags,
114
115
  // `--no-plugins` builds the entry's owning project with plugin
115
116
  // discovery and loading disabled. ttsc's own config loaders use it
@@ -121,6 +122,7 @@ async function run(
121
122
  // tsconfig deliberately does not satisfy.
122
123
  plugins: parsed.noPlugins ? false : undefined,
123
124
  project: parsed.project,
125
+ runtimeCache: !parsed.noCache,
124
126
  singleThreaded: parsed.singleThreaded,
125
127
  });
126
128
  await settleSignals();
@@ -159,6 +161,7 @@ function printHelp(): void {
159
161
  " -P, --project <file> Use an explicit tsconfig.json",
160
162
  " --cwd <dir> Resolve entry/project relative to this directory",
161
163
  " --cache-dir <dir> Override the runner and source-plugin cache root",
164
+ " --no-cache Disable persistent runtime project emit reuse",
162
165
  " --binary <path> Use an explicit tsgo binary",
163
166
  " --no-plugins Build the project without ttsc plugins",
164
167
  " -r, --require <module> Preload a module before the entrypoint",
@@ -244,6 +247,7 @@ async function runJavaScriptEntry(
244
247
  ];
245
248
  const env: NodeJS.ProcessEnv = {
246
249
  ...process.env,
250
+ ...(parsed.noCache ? { TTSX_DISABLE_CACHE: "1" } : {}),
247
251
  // ttsc's own helpers run with what the user passed (#1740).
248
252
  ...TtsxNodeOptions.give(
249
253
  process.env,
@@ -329,6 +333,7 @@ async function runPreparedEntry(
329
333
  ];
330
334
  const runtimeEnv: NodeJS.ProcessEnv = {
331
335
  ...process.env,
336
+ ...(parsed.noCache ? { TTSX_DISABLE_CACHE: "1" } : {}),
332
337
  // ttsc's own helpers run with what the user passed (#1740).
333
338
  ...TtsxNodeOptions.give(
334
339
  process.env,
@@ -22,7 +22,9 @@ import { createCanonicalTempDirectory } from "../../../internal/createCanonicalT
22
22
  import { runtimeExecutableIdentity } from "../../../internal/runtimeExecutableIdentity";
23
23
  import { moduleResolutionBaseSelects } from "../../../plugin/internal/load/moduleResolutionBaseSelects";
24
24
  import { PluginContentIdentities } from "../../../plugin/internal/source/PluginContentIdentities";
25
+ import { SourceBuildCacheLayout } from "../../../plugin/internal/source/SourceBuildCacheLayout";
25
26
  import { recordCacheFileUse } from "../../../plugin/internal/source/recordCacheFileUse";
27
+ import { resolveSourceBuildCachePaths } from "../../../plugin/internal/source/resolveSourceBuildCachePaths";
26
28
  import { buildSingleRootProject } from "../buildSingleRootProject";
27
29
  import { inlineServedSourceMap } from "../inlineServedSourceMap";
28
30
  import { runtimeCompilerArgs } from "../runtimeCompilerArgs";
@@ -2192,6 +2194,15 @@ function buildDependency(
2192
2194
  fs.mkdirSync(emitDir, { recursive: true });
2193
2195
  const result = runBuild({
2194
2196
  cwd: project.root,
2197
+ runtimeCacheDir:
2198
+ process.env.TTSX_DISABLE_CACHE === "1" ||
2199
+ process.env.TTSC_PLUGIN_DESCRIPTOR_LOAD === "1"
2200
+ ? undefined
2201
+ : path.join(
2202
+ runtimeRecordStore()?.root ??
2203
+ resolveSourceBuildCachePaths(project.root).root,
2204
+ SourceBuildCacheLayout.RUNTIME_BUILD_CACHE_DIRNAME,
2205
+ ),
2195
2206
  forceEmitProvenance: true,
2196
2207
  // Emit-only means diagnostics never withhold the emit, so a dependency's
2197
2208
  // own `noEmitOnError` is switched off: honoured, it would turn any
@@ -824,6 +824,7 @@ export namespace loadProjectPlugins {
824
824
  ...record.capabilities,
825
825
  compilerArgsCwd: true,
826
826
  emitProvenance: true,
827
+ emitObservations: true,
827
828
  }
828
829
  : record.capabilities,
829
830
  config: record.config,
@@ -80,6 +80,9 @@ export namespace SourceBuildCacheLayout {
80
80
  /** Directory of the lowered orphan sources inside the cache root. */
81
81
  export const ORPHAN_CACHE_DIRNAME = "ttsx-orphan";
82
82
 
83
+ /** Checksummed completed runtime project emits, independent of run lifetime. */
84
+ export const RUNTIME_BUILD_CACHE_DIRNAME = "runtime-builds";
85
+
83
86
  /**
84
87
  * The parts of the cache root whose entries are single files, each collected
85
88
  * by `pruneCacheFileRoot` and removed by `ttsc clean`.
@@ -90,6 +93,7 @@ export namespace SourceBuildCacheLayout {
90
93
  IDENTITY_CACHE_DIRNAME,
91
94
  ANSWER_CACHE_DIRNAME,
92
95
  ORPHAN_CACHE_DIRNAME,
96
+ RUNTIME_BUILD_CACHE_DIRNAME,
93
97
  ];
94
98
 
95
99
  /**
@@ -51,6 +51,12 @@ export namespace ITtscCompilerTransformation {
51
51
  * @evidence contracts/portability.md#os-neutral-implementation Graph keys use the producer's cwd-relative slash spelling with absolute slash paths for outside-root or cross-volume inputs; this protocol spelling is distinct from reported native realpaths carried by inputRealpaths and observations, which can retain lexical fallback on native VFS errors. The reported compiler case policy governs compiler membership comparisons rather than an OS-name assumption; absent policy or proof does not establish native filesystem capabilities.
52
52
  */
53
53
  export interface IReferenceGraph {
54
+ /**
55
+ * Shared native read-once input view; current delivery authority, never
56
+ * disk freshness.
57
+ */
58
+ compilationSnapshot?: string;
59
+
54
60
  /**
55
61
  * Effective native possible-root suffixes and output-directory exclusions.
56
62
  * A report that differs from a host's provisional policy requires a new
@@ -83,13 +83,25 @@ export interface ITtscPluginCapabilities {
83
83
  * from an output filename or configuration prediction.
84
84
  *
85
85
  * When omitted, ttsc does not pass the option to a strict third-party host. A
86
- * runtime operation requiring verified emit provenance must then report that
87
- * the selected host does not support it.
86
+ * runtime operation then uses the existing externally observed provenance
87
+ * adapter, whose incomplete proof remains visible to the runtime consumer.
88
88
  *
89
89
  * @default false
90
90
  */
91
91
  emitProvenance?: true;
92
92
 
93
+ /**
94
+ * Whether build accepts `--emit-observations-json=<absolute private path>`
95
+ * and publishes its generation's compiler and native input observations.
96
+ * Missing support keeps runtime emits fresh rather than assuming reusable
97
+ * input completeness. This protocol is independent of output ownership. Hosts
98
+ * publish the same observation envelope as checkObservations, including
99
+ * failures, before releasing their Program.
100
+ *
101
+ * @default false
102
+ */
103
+ emitObservations?: true;
104
+
93
105
  /**
94
106
  * Whether the sidecar accepts ttsc's `--project-context-json` identity
95
107
  * protocol. The payload keeps lexical selection paths, physical Program
package/utility/host.go CHANGED
@@ -40,6 +40,9 @@ type hostOptions struct {
40
40
  // checkObservationsJSON is the caller-owned absolute check input result path.
41
41
  checkObservationsJSON string
42
42
 
43
+ // emitObservationsJSON is the caller-owned absolute build input result path.
44
+ emitObservationsJSON string
45
+
43
46
  // observationsIncomplete points to the resident host's committed generation
44
47
  // state. It is nil for non-resident calls and updated only after success.
45
48
  observationsIncomplete *bool
@@ -201,6 +204,10 @@ func RunBuild(args []string) int {
201
204
  // diagnostics. Analysis-only success writes an empty map. The original build
202
205
  // status remains authoritative; absent or invalid metadata supplies no ownership
203
206
  // proof. Callers own the artifact's removal.
207
+ // A separately negotiated absolute emit-observations path receives this same
208
+ // Program's compiler and native input witnesses before its checker lease closes,
209
+ // including failing loaded builds. Unavailable optional observations withhold
210
+ // reuse without changing the build status. They supply no authority by themselves.
204
211
  // Program.Close returns its checker lease; it does not remove emitted files or
205
212
  // private metadata. Summary and diagnostic stream writes are best-effort here,
206
213
  // so a zero status does not certify delivery to the supplied io.Writer values.
@@ -211,7 +218,7 @@ func RunBuild(args []string) int {
211
218
  // @evidence contracts/common.md#meaningful-documentation Native paragraphs identify stream ownership, failure statuses, private metadata admission and caller artifact removal following the documentation skill.
212
219
  // @evidence contracts/portability.md#os-neutral-implementation Driver loading and DefaultWriteFile own native paths and filesystem APIs; output streams are not replaced globally.
213
220
  // @evidence contracts/performance.md#efficient-algorithms Admission and loading include argument/plugin-manifest parsing, native project and diagnostic work. Emission dispatches linked AST transforms through the driver, which owns native generation and one authored-coordinate correction. The final writer indexes compiler carrier/map paths and shifts generated map coordinates only when it inserts missing preamble text, including JSON/base64/VLQ and output-byte work. Optional provenance adds output-path candidate collection, successful-write bookkeeping, source-proof checks, owner sorting and JSON/file publication; costs depend on source/graph, output and metadata bytes rather than only the number of emit calls.
214
- // @evidence contracts/performance.md#reuse-equivalent-work The same loaded compiler generation validates and emits; linked program hooks remain latched rather than independently reexecuted for output files.
221
+ // @evidence contracts/performance.md#reuse-equivalent-work The same loaded compiler generation validates, emits and optionally publishes its input witnesses; linked program hooks remain latched rather than independently reexecuted for output files. Completed-generation reuse belongs to the caller.
215
222
  // @evidence contracts/performance.md#bound-retention-and-release-resources Successful loading transfers one Program checker lease to a deferred Close on analysis-only, normal and failure returns. The final writer retains output-path decisions and maps awaiting their carrier without a byte cap, releases pending map entries as their carrier arrives, and reports unfinished pairs at emit completion. Facade graphs, provenance maps, writer closures and serialization buffers lose local ownership when the invocation ends; Close does not destroy all program memory, supplied-stream state or emitted disk artifacts. Callers own stream lifetime and metadata/output removal, with no invocation-enforced byte cap.
216
223
  func RunBuildWithIO(args []string, stdout, stderr io.Writer) int {
217
224
  opts, ok := parseHostOptions("build", args, stdout, stderr)
@@ -224,6 +231,13 @@ func RunBuildWithIO(args []string, stdout, stderr io.Writer) int {
224
231
  return 2
225
232
  }
226
233
  defer prog.Close()
234
+ defer func() {
235
+ if opts.emitObservationsJSON != "" {
236
+ if err := driver.WriteCheckObservationsJSON(opts.emitObservationsJSON, prog); err != nil {
237
+ fmt.Fprintf(opts.stderr, "ttsc utility: emit observations write failed: %v\n", err)
238
+ }
239
+ }
240
+ }()
227
241
  driver.WritePrettyDiagnostics(opts.stderr, diags, opts.cwd)
228
242
  if opts.noEmit {
229
243
  if len(diags) != 0 {
@@ -446,8 +460,10 @@ func parseHostOptions(command string, args []string, stdout, stderr io.Writer) (
446
460
  tsgoArgsRaw := fs.String("tsgo-args", "", "JSON array of forwarded tsgo CLI flags")
447
461
  provenanceJSON := ""
448
462
  checkObservationsJSON := ""
463
+ emitObservationsJSON := ""
449
464
  if command == "build" {
450
465
  fs.StringVar(&provenanceJSON, "emit-provenance-json", "", "private absolute emit provenance result path")
466
+ fs.StringVar(&emitObservationsJSON, "emit-observations-json", "", "private absolute build input result path")
451
467
  }
452
468
  if command == "check" {
453
469
  fs.StringVar(&checkObservationsJSON, "check-observations-json", "", "private absolute check input result path")
@@ -463,6 +479,10 @@ func parseHostOptions(command string, args []string, stdout, stderr io.Writer) (
463
479
  fmt.Fprintln(stderr, "ttsc utility: check observations path must be absolute")
464
480
  return hostOptions{}, false
465
481
  }
482
+ if emitObservationsJSON != "" && !filepath.IsAbs(emitObservationsJSON) {
483
+ fmt.Fprintln(stderr, "ttsc utility: emit observations path must be absolute")
484
+ return hostOptions{}, false
485
+ }
466
486
  var tsgoArgs []string
467
487
  if *tsgoArgsRaw != "" {
468
488
  if err := json.Unmarshal([]byte(*tsgoArgsRaw), &tsgoArgs); err != nil {
@@ -507,6 +527,7 @@ func parseHostOptions(command string, args []string, stdout, stderr io.Writer) (
507
527
  stderr: stderr,
508
528
  provenanceJSON: provenanceJSON,
509
529
  checkObservationsJSON: checkObservationsJSON,
530
+ emitObservationsJSON: emitObservationsJSON,
510
531
  }, true
511
532
  }
512
533