@ttsc/unplugin 0.31.5 → 0.32.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 (166) hide show
  1. package/lib/core/bridge/TtscProjectRecord.d.cts +8 -0
  2. package/lib/core/bridge/TtscProjectRecord.d.mts +8 -0
  3. package/lib/core/bridge/TtscProjectRecord.d.ts +8 -0
  4. package/lib/core/bridge/readProjectRecordFile.js +5 -1
  5. package/lib/core/bridge/readProjectRecordFile.js.map +1 -1
  6. package/lib/core/bridge/readProjectRecordFile.mjs +5 -1
  7. package/lib/core/bridge/readProjectRecordFile.mjs.map +1 -1
  8. package/lib/core/observer/WatchScope.d.cts +3 -3
  9. package/lib/core/observer/WatchScope.d.mts +3 -3
  10. package/lib/core/observer/WatchScope.d.ts +3 -3
  11. package/lib/core/observer/createInputObserver.d.cts +7 -8
  12. package/lib/core/observer/createInputObserver.d.mts +7 -8
  13. package/lib/core/observer/createInputObserver.d.ts +7 -8
  14. package/lib/core/observer/createInputObserver.js +7 -8
  15. package/lib/core/observer/createInputObserver.js.map +1 -1
  16. package/lib/core/observer/createInputObserver.mjs +7 -8
  17. package/lib/core/observer/createInputObserver.mjs.map +1 -1
  18. package/lib/core/transform/envelope/generationCaseSensitivity.d.cts +32 -0
  19. package/lib/core/transform/envelope/generationCaseSensitivity.d.mts +32 -0
  20. package/lib/core/transform/envelope/generationCaseSensitivity.d.ts +32 -0
  21. package/lib/core/transform/envelope/generationCaseSensitivity.js +39 -0
  22. package/lib/core/transform/envelope/generationCaseSensitivity.js.map +1 -0
  23. package/lib/core/transform/envelope/generationCaseSensitivity.mjs +37 -0
  24. package/lib/core/transform/envelope/generationCaseSensitivity.mjs.map +1 -0
  25. package/lib/core/transform/envelope/selectExternalInputPaths.js +2 -1
  26. package/lib/core/transform/envelope/selectExternalInputPaths.js.map +1 -1
  27. package/lib/core/transform/envelope/selectExternalInputPaths.mjs +2 -1
  28. package/lib/core/transform/envelope/selectExternalInputPaths.mjs.map +1 -1
  29. package/lib/core/transform/envelope/selectNotifiableAbsentInputs.js +2 -1
  30. package/lib/core/transform/envelope/selectNotifiableAbsentInputs.js.map +1 -1
  31. package/lib/core/transform/envelope/selectNotifiableAbsentInputs.mjs +2 -1
  32. package/lib/core/transform/envelope/selectNotifiableAbsentInputs.mjs.map +1 -1
  33. package/lib/core/transform/filesystem/createHostPathIdentityContext.d.cts +9 -1
  34. package/lib/core/transform/filesystem/createHostPathIdentityContext.d.mts +9 -1
  35. package/lib/core/transform/filesystem/createHostPathIdentityContext.d.ts +9 -1
  36. package/lib/core/transform/filesystem/createHostPathIdentityContext.js +10 -2
  37. package/lib/core/transform/filesystem/createHostPathIdentityContext.js.map +1 -1
  38. package/lib/core/transform/filesystem/createHostPathIdentityContext.mjs +10 -2
  39. package/lib/core/transform/filesystem/createHostPathIdentityContext.mjs.map +1 -1
  40. package/lib/core/transform/generation/captureTransformGeneration.js +10 -2
  41. package/lib/core/transform/generation/captureTransformGeneration.js.map +1 -1
  42. package/lib/core/transform/generation/captureTransformGeneration.mjs +10 -2
  43. package/lib/core/transform/generation/captureTransformGeneration.mjs.map +1 -1
  44. package/lib/core/transform/generation/selectPersistentHostInputs.js +2 -1
  45. package/lib/core/transform/generation/selectPersistentHostInputs.js.map +1 -1
  46. package/lib/core/transform/generation/selectPersistentHostInputs.mjs +2 -1
  47. package/lib/core/transform/generation/selectPersistentHostInputs.mjs.map +1 -1
  48. package/lib/core/transform/inputs/nativeInputPredicateState.js.map +1 -1
  49. package/lib/core/transform/inputs/nativeInputPredicateState.mjs.map +1 -1
  50. package/lib/core/transform/tracker/createHostInputMutationTracker.d.cts +7 -1
  51. package/lib/core/transform/tracker/createHostInputMutationTracker.d.mts +7 -1
  52. package/lib/core/transform/tracker/createHostInputMutationTracker.d.ts +7 -1
  53. package/lib/core/transform/tracker/createHostInputMutationTracker.js +8 -2
  54. package/lib/core/transform/tracker/createHostInputMutationTracker.js.map +1 -1
  55. package/lib/core/transform/tracker/createHostInputMutationTracker.mjs +8 -2
  56. package/lib/core/transform/tracker/createHostInputMutationTracker.mjs.map +1 -1
  57. package/lib/core/transform/tracker/createProjectMutationTracker.d.cts +6 -4
  58. package/lib/core/transform/tracker/createProjectMutationTracker.d.mts +6 -4
  59. package/lib/core/transform/tracker/createProjectMutationTracker.d.ts +6 -4
  60. package/lib/core/transform/tracker/createProjectMutationTracker.js +29 -6
  61. package/lib/core/transform/tracker/createProjectMutationTracker.js.map +1 -1
  62. package/lib/core/transform/tracker/createProjectMutationTracker.mjs +29 -6
  63. package/lib/core/transform/tracker/createProjectMutationTracker.mjs.map +1 -1
  64. package/lib/core/transform/transformTtsc.js +5 -1
  65. package/lib/core/transform/transformTtsc.js.map +1 -1
  66. package/lib/core/transform/transformTtsc.mjs +5 -1
  67. package/lib/core/transform/transformTtsc.mjs.map +1 -1
  68. package/lib/core/transform/watch/TtscWatchInputBaseline.d.cts +2 -1
  69. package/lib/core/transform/watch/TtscWatchInputBaseline.d.mts +2 -1
  70. package/lib/core/transform/watch/TtscWatchInputBaseline.d.ts +2 -1
  71. package/lib/core/transform/watch/isWatchInputKeyBaseline.js.map +1 -1
  72. package/lib/core/transform/watch/isWatchInputKeyBaseline.mjs.map +1 -1
  73. package/lib/core/transform/watch/notifyProjectRecord.d.cts +3 -4
  74. package/lib/core/transform/watch/notifyProjectRecord.d.mts +3 -4
  75. package/lib/core/transform/watch/notifyProjectRecord.d.ts +3 -4
  76. package/lib/core/transform/watch/notifyProjectRecord.js +5 -4
  77. package/lib/core/transform/watch/notifyProjectRecord.js.map +1 -1
  78. package/lib/core/transform/watch/notifyProjectRecord.mjs +5 -4
  79. package/lib/core/transform/watch/notifyProjectRecord.mjs.map +1 -1
  80. package/lib/core/transform/watch/recordedDependencyWitnesses.d.cts +30 -0
  81. package/lib/core/transform/watch/recordedDependencyWitnesses.d.mts +30 -0
  82. package/lib/core/transform/watch/recordedDependencyWitnesses.d.ts +30 -0
  83. package/lib/core/transform/watch/recordedDependencyWitnesses.js +51 -0
  84. package/lib/core/transform/watch/recordedDependencyWitnesses.js.map +1 -0
  85. package/lib/core/transform/watch/recordedDependencyWitnesses.mjs +49 -0
  86. package/lib/core/transform/watch/recordedDependencyWitnesses.mjs.map +1 -0
  87. package/lib/core/transform/watch/watchInputEvidenceMatchesBaseline.d.cts +1 -2
  88. package/lib/core/transform/watch/watchInputEvidenceMatchesBaseline.d.mts +1 -2
  89. package/lib/core/transform/watch/watchInputEvidenceMatchesBaseline.d.ts +1 -2
  90. package/lib/core/transform/watch/watchInputEvidenceMatchesBaseline.js +1 -2
  91. package/lib/core/transform/watch/watchInputEvidenceMatchesBaseline.js.map +1 -1
  92. package/lib/core/transform/watch/watchInputEvidenceMatchesBaseline.mjs +1 -2
  93. package/lib/core/transform/watch/watchInputEvidenceMatchesBaseline.mjs.map +1 -1
  94. package/lib/core/tsconfig/TsconfigReadTransaction.d.cts +11 -8
  95. package/lib/core/tsconfig/TsconfigReadTransaction.d.mts +11 -8
  96. package/lib/core/tsconfig/TsconfigReadTransaction.d.ts +11 -8
  97. package/lib/core/tsconfig/TsconfigReadTransaction.js +11 -8
  98. package/lib/core/tsconfig/TsconfigReadTransaction.js.map +1 -1
  99. package/lib/core/tsconfig/TsconfigReadTransaction.mjs +11 -8
  100. package/lib/core/tsconfig/TsconfigReadTransaction.mjs.map +1 -1
  101. package/lib/core/tsconfig/findDeclaredCompilerOption.d.cts +3 -2
  102. package/lib/core/tsconfig/findDeclaredCompilerOption.d.mts +3 -2
  103. package/lib/core/tsconfig/findDeclaredCompilerOption.d.ts +3 -2
  104. package/lib/core/tsconfig/findDeclaredCompilerOption.js +3 -2
  105. package/lib/core/tsconfig/findDeclaredCompilerOption.js.map +1 -1
  106. package/lib/core/tsconfig/findDeclaredCompilerOption.mjs +3 -2
  107. package/lib/core/tsconfig/findDeclaredCompilerOption.mjs.map +1 -1
  108. package/lib/core/tsconfig/findDeclaredFileSpecs.d.cts +3 -2
  109. package/lib/core/tsconfig/findDeclaredFileSpecs.d.mts +3 -2
  110. package/lib/core/tsconfig/findDeclaredFileSpecs.d.ts +3 -2
  111. package/lib/core/tsconfig/findDeclaredFileSpecs.js +3 -2
  112. package/lib/core/tsconfig/findDeclaredFileSpecs.js.map +1 -1
  113. package/lib/core/tsconfig/findDeclaredFileSpecs.mjs +3 -2
  114. package/lib/core/tsconfig/findDeclaredFileSpecs.mjs.map +1 -1
  115. package/lib/core/tsconfig/findDeclaredPaths.d.cts +3 -3
  116. package/lib/core/tsconfig/findDeclaredPaths.d.mts +3 -3
  117. package/lib/core/tsconfig/findDeclaredPaths.d.ts +3 -3
  118. package/lib/core/tsconfig/findDeclaredPaths.js +2 -2
  119. package/lib/core/tsconfig/findDeclaredPaths.mjs +2 -2
  120. package/lib/core/tsconfig/findDeclaredValue.d.cts +2 -1
  121. package/lib/core/tsconfig/findDeclaredValue.d.mts +2 -1
  122. package/lib/core/tsconfig/findDeclaredValue.d.ts +2 -1
  123. package/lib/core/tsconfig/findDeclaredValue.js +2 -1
  124. package/lib/core/tsconfig/findDeclaredValue.js.map +1 -1
  125. package/lib/core/tsconfig/findDeclaredValue.mjs +2 -1
  126. package/lib/core/tsconfig/findDeclaredValue.mjs.map +1 -1
  127. package/lib/core/tsconfig/readEffectiveTsconfigTemplateCompilerOptions.js +1 -1
  128. package/lib/core/tsconfig/readEffectiveTsconfigTemplateCompilerOptions.mjs +1 -1
  129. package/lib/core/tsconfig/readEffectiveTsconfigTemplateFileSpecs.d.cts +2 -2
  130. package/lib/core/tsconfig/readEffectiveTsconfigTemplateFileSpecs.d.mts +2 -2
  131. package/lib/core/tsconfig/readEffectiveTsconfigTemplateFileSpecs.d.ts +2 -2
  132. package/lib/core/tsconfig/readEffectiveTsconfigTemplateFileSpecs.js +2 -2
  133. package/lib/core/tsconfig/readEffectiveTsconfigTemplateFileSpecs.mjs +2 -2
  134. package/lib/core/tsconfig/readProjectMembershipPolicy.d.cts +3 -3
  135. package/lib/core/tsconfig/readProjectMembershipPolicy.d.mts +3 -3
  136. package/lib/core/tsconfig/readProjectMembershipPolicy.d.ts +3 -3
  137. package/lib/core/tsconfig/readProjectMembershipPolicy.js +4 -4
  138. package/lib/core/tsconfig/readProjectMembershipPolicy.mjs +4 -4
  139. package/package.json +4 -4
  140. package/src/core/bridge/TtscProjectRecord.ts +9 -0
  141. package/src/core/bridge/readProjectRecordFile.ts +5 -1
  142. package/src/core/observer/WatchScope.ts +3 -3
  143. package/src/core/observer/createInputObserver.ts +7 -8
  144. package/src/core/transform/envelope/generationCaseSensitivity.ts +46 -0
  145. package/src/core/transform/envelope/selectExternalInputPaths.ts +5 -1
  146. package/src/core/transform/envelope/selectNotifiableAbsentInputs.ts +5 -1
  147. package/src/core/transform/filesystem/createHostPathIdentityContext.ts +9 -1
  148. package/src/core/transform/generation/captureTransformGeneration.ts +10 -0
  149. package/src/core/transform/generation/selectPersistentHostInputs.ts +5 -1
  150. package/src/core/transform/inputs/nativeInputPredicateState.ts +1 -3
  151. package/src/core/transform/tracker/createHostInputMutationTracker.ts +10 -1
  152. package/src/core/transform/tracker/createProjectMutationTracker.ts +34 -6
  153. package/src/core/transform/transformTtsc.ts +5 -1
  154. package/src/core/transform/watch/TtscWatchInputBaseline.ts +2 -1
  155. package/src/core/transform/watch/isWatchInputKeyBaseline.ts +4 -2
  156. package/src/core/transform/watch/notifyProjectRecord.ts +5 -4
  157. package/src/core/transform/watch/recordedDependencyWitnesses.ts +50 -0
  158. package/src/core/transform/watch/watchInputEvidenceMatchesBaseline.ts +1 -2
  159. package/src/core/tsconfig/TsconfigReadTransaction.ts +11 -8
  160. package/src/core/tsconfig/findDeclaredCompilerOption.ts +3 -2
  161. package/src/core/tsconfig/findDeclaredFileSpecs.ts +3 -2
  162. package/src/core/tsconfig/findDeclaredPaths.ts +3 -3
  163. package/src/core/tsconfig/findDeclaredValue.ts +2 -1
  164. package/src/core/tsconfig/readEffectiveTsconfigTemplateCompilerOptions.ts +1 -1
  165. package/src/core/tsconfig/readEffectiveTsconfigTemplateFileSpecs.ts +2 -2
  166. package/src/core/tsconfig/readProjectMembershipPolicy.ts +4 -4
@@ -8,6 +8,7 @@ import { relativeToProject } from "../filesystem/relativeToProject";
8
8
  import { isTransformScratchInput } from "../tsconfig/isTransformScratchInput";
9
9
  import { envelopeDerivation } from "./envelopeDerivation";
10
10
  import { envelopeGraphIndexes } from "./envelopeGraphIndexes";
11
+ import { generationCaseSensitivity } from "./generationCaseSensitivity";
11
12
 
12
13
  /**
13
14
  * The generation's resolution candidates recorded as not files, so its
@@ -72,7 +73,10 @@ export function selectNotifiableAbsentInputs(props: {
72
73
  if (graph === undefined) {
73
74
  return empty;
74
75
  }
75
- const identities = createHostPathIdentityContext(props.filesystem);
76
+ const identities = createHostPathIdentityContext(
77
+ props.filesystem,
78
+ generationCaseSensitivity(props),
79
+ );
76
80
  const graphState = envelopeDerivation({
77
81
  projectRoot: props.projectRoot,
78
82
  result: props.result,
@@ -29,6 +29,8 @@ import type { TtscTransformFilesystemOperations } from "./TtscTransformFilesyste
29
29
  * The returned context memoizes path, realpath and case observations for
30
30
  * repeated questions within one filesystem transaction. Callers must create
31
31
  * a new context when later filesystem changes can invalidate those observations.
32
+ * A caller capturing one generation can hand in that generation's case answers,
33
+ * so its contexts do not each ask the filesystem again (samchon/ttsc#1742).
32
34
  * @evidence contracts/performance.md#bound-retention-and-release-resources
33
35
  * Ownership of the context and its maps transfers to the caller. Retained
34
36
  * entries grow with queried paths and ancestors and have no internal eviction;
@@ -36,9 +38,15 @@ import type { TtscTransformFilesystemOperations } from "./TtscTransformFilesyste
36
38
  */
37
39
  export function createHostPathIdentityContext(
38
40
  filesystem: TtscTransformFilesystemOperations = DEFAULT_FILESYSTEM_OPERATIONS,
41
+ /**
42
+ * Case answers this context shares with the others of its unit of work,
43
+ * observed through the same `filesystem`; omitted, the context asks for
44
+ * itself.
45
+ */
46
+ caseSensitive: TtscTransformFilesystemOperations["caseSensitive"] = filesystem.caseSensitive,
39
47
  ): FilesystemPathIdentityContext {
40
48
  return createFilesystemPathIdentityContext({
41
- caseSensitive: filesystem.caseSensitive,
49
+ caseSensitive,
42
50
  lstat: filesystem.lstat,
43
51
  platform: filesystem.platform,
44
52
  readdir: (directory) =>
@@ -12,6 +12,7 @@ import { TRANSFORM_RESULT_MEMBERSHIP } from "../cache/TRANSFORM_RESULT_MEMBERSHI
12
12
  import type { TtscCachedProjectTransform } from "../cache/TtscCachedProjectTransform";
13
13
  import { refreshFilesystemClockReference } from "../clock/refreshFilesystemClockReference";
14
14
  import { reportDivergentDelivery } from "../diagnostics/reportDivergentDelivery";
15
+ import { generationCaseSensitivity } from "../envelope/generationCaseSensitivity";
15
16
  import { selectDeclaredProjectInputKeys } from "../envelope/selectDeclaredProjectInputKeys";
16
17
  import { selectExternalInputPaths } from "../envelope/selectExternalInputPaths";
17
18
  import { selectNotifiableAbsentInputs } from "../envelope/selectNotifiableAbsentInputs";
@@ -457,6 +458,13 @@ export async function captureTransformGeneration(props: {
457
458
  clockReferenceDirectory ?? scratchDirectory,
458
459
  props.filesystem,
459
460
  );
461
+ // Every context capturing this generation below shares the generation's
462
+ // directory case answers, rather than each asking again (samchon/ttsc#1742).
463
+ const generationCase = generationCaseSensitivity({
464
+ filesystem: props.filesystem,
465
+ projectRoot,
466
+ result,
467
+ });
460
468
  const persistentHostInputs = selectPersistentHostInputs({
461
469
  filesystem: props.filesystem,
462
470
  projectRoot,
@@ -518,6 +526,7 @@ export async function captureTransformGeneration(props: {
518
526
  projectRoot,
519
527
  result,
520
528
  }),
529
+ generationCase,
521
530
  )
522
531
  : undefined;
523
532
  compileTrace?.("bridge-tracker-lifecycle", {
@@ -566,6 +575,7 @@ export async function captureTransformGeneration(props: {
566
575
  .filter((input) => !absentCandidates.has(input))
567
576
  .map((input) => [path.resolve(input), "presence" as const]),
568
577
  ),
578
+ generationCase,
569
579
  )
570
580
  : undefined;
571
581
  compileTrace?.("bridge-tracker-lifecycle", {
@@ -1,5 +1,6 @@
1
1
  import type { ITtscCompilerTransformation } from "ttsc";
2
2
 
3
+ import { generationCaseSensitivity } from "../envelope/generationCaseSensitivity";
3
4
  import { selectListedFiles } from "../envelope/selectListedFiles";
4
5
  import type { TtscTransformFilesystemOperations } from "../filesystem/TtscTransformFilesystemOperations";
5
6
  import { createHostPathIdentityContext } from "../filesystem/createHostPathIdentityContext";
@@ -43,7 +44,10 @@ export function selectPersistentHostInputs(props: {
43
44
  props.temporaryTsconfig === undefined
44
45
  )
45
46
  return inputs;
46
- const identities = createHostPathIdentityContext(props.filesystem);
47
+ const identities = createHostPathIdentityContext(
48
+ props.filesystem,
49
+ generationCaseSensitivity(props),
50
+ );
47
51
  const temporary =
48
52
  props.temporaryTsconfig === undefined
49
53
  ? undefined
@@ -69,9 +69,7 @@ export function nativeInputPredicateState(
69
69
  } else if (kind === "optional-file") {
70
70
  try {
71
71
  digest = filesystem.stat(file).isFile()
72
- ? hash(
73
- Buffer.concat([Buffer.from("file\0"), readBytes()]),
74
- )
72
+ ? hash(Buffer.concat([Buffer.from("file\0"), readBytes()]))
75
73
  : hash("missing\0");
76
74
  } catch (error) {
77
75
  if (!missing(error)) return undefined;
@@ -114,12 +114,21 @@ export async function createHostInputMutationTracker(
114
114
  * filesystem.
115
115
  */
116
116
  scopes?: ReadonlyMap<string, TtscTrackedInputScope>,
117
+ /**
118
+ * The case answers of the generation this tracker watches, for its baseline
119
+ * (`generationCaseSensitivity`); omitted, the baseline observes for itself.
120
+ * An event's re-check always observes afresh.
121
+ */
122
+ caseSensitive?: TtscTransformFilesystemOperations["caseSensitive"],
117
123
  ): Promise<TtscProjectMutationTracker> {
118
124
  const paths =
119
125
  (filesystem.platform ?? process.platform) === "win32"
120
126
  ? path.win32
121
127
  : path.posix;
122
- const identities = createHostPathIdentityContext(filesystem);
128
+ const identities = createHostPathIdentityContext(
129
+ filesystem,
130
+ caseSensitive ?? filesystem.caseSensitive,
131
+ );
123
132
  // The project root in both of its spellings: an input the compiler reported
124
133
  // physically is the project's as much as one spelled as the project was
125
134
  // named, and the root's observer is opened once, under the name.
@@ -38,10 +38,12 @@ import { watchLocationIdentity } from "./watchLocationIdentity";
38
38
  * directory excluded in both lexical and native spellings. Unknown, missing and
39
39
  * linked ancestry remains conservative. Membership rejection withdraws
40
40
  * notification authority except below such a proven excluded directory, so a
41
- * recursive backend's writes to a cache or output tree cost no proof. Named
42
- * events also compare current native identity and case policy with the retained
43
- * identity transaction. Retargeting withdraws notification authority without
44
- * changing the meaning of earlier recorded event spellings.
41
+ * recursive backend's writes to a cache or output tree cost no proof, nor does
42
+ * an edit to an existing unlinked file whose every name the program cannot
43
+ * admit (samchon/ttsc#1737). Named events also compare current native identity
44
+ * and case policy with the retained identity transaction. Retargeting withdraws
45
+ * notification authority without changing the meaning of earlier recorded event
46
+ * spellings.
45
47
  *
46
48
  * @evidence contracts/common.md#principled-implementation
47
49
  * The project walk and membership policy own structural relevance; content
@@ -193,6 +195,26 @@ export async function createProjectMutationTracker(
193
195
  }
194
196
  return excluded;
195
197
  };
198
+ // Whether an event names an existing file no spelling can make a program
199
+ // member: a regular file with no second hard link, whose own and physical
200
+ // names are both names the program cannot admit. A directory alias can move
201
+ // a file into the program but cannot rename it, and a removed name, a link
202
+ // or a directory stays conservative.
203
+ const existingNonProgramFile = (changed: string): boolean => {
204
+ try {
205
+ const stats = filesystem.lstat(changed);
206
+ if (!stats.isFile() || stats.nlink > 1n) return false;
207
+ return (
208
+ !isPossibleProgramFileName(paths.basename(changed), policy) &&
209
+ !isPossibleProgramFileName(
210
+ paths.basename(filesystem.realpath(changed)),
211
+ policy,
212
+ )
213
+ );
214
+ } catch {
215
+ return false;
216
+ }
217
+ };
196
218
  const reportsMembership = (location: string, filename: string): boolean => {
197
219
  const changed = paths.join(location, filename);
198
220
  const current = createHostPathIdentityContext(filesystem);
@@ -221,8 +243,14 @@ export async function createProjectMutationTracker(
221
243
  // except below a directory the walk provably excludes. A recursive backend
222
244
  // (macOS, Windows) hears every write under the root, a cache or an output
223
245
  // directory among them, and withdrawing authority for each sent every later
224
- // delivery to the whole-snapshot proof.
225
- if (!membership && !excludedFromWalk(changed, true))
246
+ // delivery to the whole-snapshot proof. An existing file whose every name
247
+ // the program cannot admit is no member either, a stylesheet or an asset
248
+ // edited in a source directory among them, on every backend.
249
+ if (
250
+ !membership &&
251
+ !excludedFromWalk(changed, true) &&
252
+ !existingNonProgramFile(changed)
253
+ )
226
254
  tracker.unverified = true;
227
255
  return membership;
228
256
  };
@@ -33,6 +33,7 @@ import type { TtscTransformHooks } from "./watch/TtscTransformHooks";
33
33
  import type { TtscWatchSelection } from "./watch/TtscWatchSelection";
34
34
  import { notifyRejectedGenerationInputs } from "./watch/notifyRejectedGenerationInputs";
35
35
  import { prepareProjectRecordDirectories } from "./watch/prepareProjectRecordDirectories";
36
+ import { recordedDependencyWitnesses } from "./watch/recordedDependencyWitnesses";
36
37
 
37
38
  /**
38
39
  * Apply the ttsc plugin transform to a single source file.
@@ -282,10 +283,13 @@ export async function transformTtsc(
282
283
  cache === undefined
283
284
  ? undefined
284
285
  : TRANSFORM_CACHE_CASE_POLICIES.get(cache)?.get(key),
286
+ // A cache without a list of its own takes the names the project's
287
+ // record carries from an earlier process (samchon/ttsc#1743).
285
288
  witnessedDependencies:
286
289
  cache === undefined
287
290
  ? undefined
288
- : TRANSFORM_CACHE_DEPENDENCY_WITNESSES.get(cache)?.get(key),
291
+ : (TRANSFORM_CACHE_DEPENDENCY_WITNESSES.get(cache)?.get(key) ??
292
+ recordedDependencyWitnesses(hooks?.project, tsconfig)),
289
293
  });
290
294
  cache?.set(key, transformed);
291
295
  }
@@ -23,7 +23,8 @@ import type { TtscWatchInputFileBaseline } from "./TtscWatchInputFileBaseline";
23
23
  export interface TtscWatchInputBaseline extends TtscWatchInputFileBaseline {
24
24
  /**
25
25
  * Current version-one native codec facts, captured only for requested kinds.
26
- * These observations do not certify a producer's identityStable flag or scope.
26
+ * These observations do not certify a producer's identityStable flag or
27
+ * scope.
27
28
  */
28
29
  nativePredicates?: Partial<
29
30
  Record<
@@ -115,9 +115,11 @@ function isNativePredicateBaselines(value: unknown): boolean {
115
115
  stableStringify(Object.keys(state).sort()) !==
116
116
  stableStringify(["digest", "realpath"]) ||
117
117
  !isContentHash(state.digest) ||
118
- !(state.realpath === null ||
118
+ !(
119
+ state.realpath === null ||
119
120
  (typeof state.realpath === "string" &&
120
- isAbsoluteFilesystemPath(state.realpath)))
121
+ isAbsoluteFilesystemPath(state.realpath))
122
+ )
121
123
  ) {
122
124
  return false;
123
125
  }
@@ -73,10 +73,9 @@ const HANDED = new WeakMap<
73
73
  * loses acceptance and enters persistence again without rereading generation
74
74
  * inputs. Hosts and bridge readers own current byte proof, including concurrent
75
75
  * changes and unreadable/torn bytes; availability here cannot exclude removal
76
- * after the check. A host that keeps no
77
- * snapshot of the file, Rollup's cache, is handed the digest of the bytes
78
- * written for the generation to compare against instead
79
- * (`TtscProjectRegistration.digest`).
76
+ * after the check. A host that keeps no snapshot of the file, Rollup's cache,
77
+ * is handed the digest of the bytes written for the generation to compare
78
+ * against instead (`TtscProjectRegistration.digest`).
80
79
  *
81
80
  * A record lives below the host's tool directory, or, when that cannot be
82
81
  * written, below the fallback the host accepts (`fallbackToolDirectory`,
@@ -226,7 +225,9 @@ function recordOf(
226
225
  cached: TtscCachedProjectTransform,
227
226
  inputs: readonly TtscWatchInput[],
228
227
  ): TtscProjectRecord {
228
+ const dependencies = cached.externalDependencyInputs ?? [];
229
229
  const record: TtscProjectRecord = {
230
+ ...(dependencies.length === 0 ? {} : { dependencies }),
230
231
  inputs: {},
231
232
  membership:
232
233
  cached.projectDirectories === undefined
@@ -0,0 +1,50 @@
1
+ import { projectRecordFile } from "../../bridge/projectRecordFile";
2
+ import { readProjectRecordFile } from "../../bridge/readProjectRecordFile";
3
+ import type { TtscTransformHooks } from "./TtscTransformHooks";
4
+
5
+ /**
6
+ * The dependency-only paths the project record of `tsconfig` carries
7
+ * (`TtscProjectRecord.dependencies`), for a transform cache that has no witness
8
+ * list of its own yet.
9
+ *
10
+ * A compile learns a plugin-reported dependency path only from its own
11
+ * envelope, and one it had no witness for compiles again
12
+ * (`onlyLearnedCompileFacts`). A transform cache keeps the paths in memory
13
+ * (`TRANSFORM_CACHE_DEPENDENCY_WITNESSES`), so every new cache, a dev-server
14
+ * start or a build process, compiled such a project twice (samchon/ttsc#1743).
15
+ * The record the previous process wrote names them. They only say what to read:
16
+ * the capture still witnesses each path afresh, a path the compile no longer
17
+ * reports costs one read, and one the record does not name is retried as
18
+ * before.
19
+ *
20
+ * @param project The host's record directories, when it keeps records.
21
+ * @param tsconfig The project's tsconfig, as the adapter names it.
22
+ * @returns The record's paths, or `undefined` when no readable record names
23
+ * any.
24
+ * @evidence contracts/common.md#principled-implementation The record is the host's persisted project state, written from a completed generation; its paths are hints that the capture re-witnesses, never certified state.
25
+ * @evidence contracts/common.md#clear-and-simple-design One reader selects the first readable record of the host's directories, in the order the writer tries them.
26
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts No witness or digest is taken from the record; an unreadable or absent record supplies nothing.
27
+ * @evidence contracts/common.md#meaningful-documentation Native paragraphs explain the doubled first compile, the hint semantics and the absent cases.
28
+ * @evidence contracts/portability.md#os-neutral-implementation Record paths come from the host's own directories through `projectRecordFile`.
29
+ * @evidence contracts/performance.md#efficient-algorithms At most two record reads and JSON parses, only when a cache has no witness list for the key.
30
+ * @evidence contracts/performance.md#reuse-equivalent-work A new process reuses the dependency names the last generation's record holds instead of compiling once to learn them.
31
+ * @evidenceExclude contracts/performance.md#bound-retention-and-release-resources The returned list belongs to the caller; nothing is retained here.
32
+ */
33
+ export function recordedDependencyWitnesses(
34
+ project: TtscTransformHooks["project"],
35
+ tsconfig: string,
36
+ ): readonly string[] | undefined {
37
+ if (project === undefined) return undefined;
38
+ for (const directory of [
39
+ project.toolDirectory,
40
+ ...(project.fallbackToolDirectory === undefined
41
+ ? []
42
+ : [project.fallbackToolDirectory]),
43
+ ]) {
44
+ const record = readProjectRecordFile(
45
+ projectRecordFile(directory, tsconfig),
46
+ );
47
+ if (record !== undefined) return record.dependencies;
48
+ }
49
+ return undefined;
50
+ }
@@ -10,8 +10,7 @@ import { isWatchInputKeyBaseline } from "./isWatchInputKeyBaseline";
10
10
  *
11
11
  * A codec matches only the facts the baseline actually captured. Native,
12
12
  * listing and plugin-tree observations require their optional payloads;
13
- * membership is a
14
- * separate project walk and cannot match one path's baseline.
13
+ * membership is a separate project walk and cannot match one path's baseline.
15
14
  *
16
15
  * @evidence contracts/common.md#principled-implementation Identity and every recorded codec fact must agree with a validated baseline; native predicates additionally require a supported version, stable producer identity, matching raw digest and physical target. Narrow discovery facts and absent optional native/listing/tree payloads cannot establish broader observations.
17
16
  * @evidence contracts/common.md#clear-and-simple-design Codec branches reuse the strict baseline guard and native realpath comparator, with one private refinement exposing the broader carrier.
@@ -7,14 +7,16 @@ import { resolveExtendsConfig } from "./resolveExtendsConfig";
7
7
  import { resolveRealPath } from "./resolveRealPath";
8
8
 
9
9
  /**
10
- * Share config observations and contextual selections within one synchronous read.
10
+ * Share config observations and contextual selections within one synchronous
11
+ * read.
11
12
  *
12
13
  * Lexical paths own parsing and relative extends resolution. Physical paths
13
14
  * only guard ancestry cycles. A completed selection can be borrowed when the
14
15
  * current ancestors intersect its encountered physical identities exactly as
15
16
  * they did originally; otherwise a different cycle cut requires evaluation.
16
17
  * Ordered source observations travel with a completed selection, including
17
- * unresolved file candidates, so reuse preserves the caller's invalidation inputs.
18
+ * unresolved file candidates, so reuse preserves the caller's invalidation
19
+ * inputs.
18
20
  *
19
21
  * Create a new transaction for each independent freshness observation. This
20
22
  * owner neither validates reuse across reads nor supplies compiler diagnostics.
@@ -94,12 +96,13 @@ export class TsconfigReadTransaction {
94
96
  /**
95
97
  * Select one declaration, preserving its lexical directory and observations.
96
98
  *
97
- * An undefined own selection inherits. Null masks inheritance without supplying
98
- * a value, as an invalid own file list does; a parent ignores that empty result.
99
- * First queries search later bases first; last queries visit all bases forward.
100
- * The caller's physical ancestor set is copied once and never mutated.
101
- * Selectors must preserve the parsed inputs and give the same declaration for
102
- * the same source; query reuse does not repeat their incidental effects.
99
+ * An undefined own selection inherits. Null masks inheritance without
100
+ * supplying a value, as an invalid own file list does; a parent ignores that
101
+ * empty result. First queries search later bases first; last queries visit
102
+ * all bases forward. The caller's physical ancestor set is copied once and
103
+ * never mutated. Selectors must preserve the parsed inputs and give the same
104
+ * declaration for the same source; query reuse does not repeat their
105
+ * incidental effects.
103
106
  *
104
107
  * @evidence contracts/common.md#principled-implementation A completed answer is reusable only when its encountered physical identities have the same intersection with current ancestry. That preserves every observed cycle decision, including cuts; own declarations and first/last traversal retain lexical origins and key-specific precedence.
105
108
  * @evidence contracts/common.md#clear-and-simple-design A query-local visitor owns selection and contextual completed answers; transaction nodes separately share lexical source, identity and edge observations across keys.
@@ -6,8 +6,9 @@ import { findDeclaredValue } from "./findDeclaredValue";
6
6
  *
7
7
  * Own key presence is significant even for null or undefined values; the caller
8
8
  * decides whether the selected option value is usable. An optional
9
- * decoded-source map or graph transaction belongs to one caller read; independent
10
- * option searches retain their selectors and physical ancestry contexts.
9
+ * decoded-source map or graph transaction belongs to one caller read;
10
+ * independent option searches retain their selectors and physical ancestry
11
+ * contexts.
11
12
  *
12
13
  * @evidence contracts/common.md#principled-implementation
13
14
  * A wrapper preserves key presence through the generic selector, so the own
@@ -13,8 +13,9 @@ import { TsconfigReadTransaction } from "./TsconfigReadTransaction";
13
13
  * anchored at the named declaring config, including independent link aliases.
14
14
  *
15
15
  * @returns The list and its directory, undefined when no array is selected, or
16
- * null when the root config cannot be read as an object. A supplied transaction
17
- * belongs to this freshness observation; later reads require a fresh owner.
16
+ * null when the root config cannot be read as an object. A supplied
17
+ * transaction belongs to this freshness observation; later reads require a
18
+ * fresh owner.
18
19
  * @evidence contracts/common.md#principled-implementation
19
20
  * Own invalid values stop inheritance at that node. Forward inherited
20
21
  * traversal keeps the last array and ignores empty selections, preserving
@@ -1,5 +1,5 @@
1
- import { TsconfigReadTransaction } from "./TsconfigReadTransaction";
2
1
  import type { IDeclaredPaths } from "./IDeclaredPaths";
2
+ import { TsconfigReadTransaction } from "./TsconfigReadTransaction";
3
3
  import { findDeclaredValue } from "./findDeclaredValue";
4
4
 
5
5
  /**
@@ -7,8 +7,8 @@ import { findDeclaredValue } from "./findDeclaredValue";
7
7
  * rooted at `tsconfig`. The own config wins over its bases; within an `extends`
8
8
  * array, later entries win over earlier ones. `seen` breaks circular chains;
9
9
  * the compiler reports the actual config error. Optional decoded sources or
10
- * graph observations share only within the caller's current read; physical cycle
11
- * guards remain branch-local.
10
+ * graph observations share only within the caller's current read; physical
11
+ * cycle guards remain branch-local.
12
12
  *
13
13
  * @evidence contracts/common.md#principled-implementation
14
14
  * The selector accepts a non-array paths record and the shared value reader
@@ -7,7 +7,8 @@ import { TsconfigReadTransaction } from "./TsconfigReadTransaction";
7
7
  * remain anchored at the named config; physical identity only cuts branch
8
8
  * cycles. Independent aliases can therefore resolve different relative bases.
9
9
  * Missing or malformed configs remain best-effort absence; the compiler owns
10
- * configuration diagnostics. Unresolved file candidates remain observed inputs.
10
+ * configuration diagnostics. Unresolved file candidates remain observed
11
+ * inputs.
11
12
  *
12
13
  * A supplied transaction shares source, identity and edge observations across
13
14
  * keys. A decoded-source map remains supported for callers that own that map;
@@ -1,8 +1,8 @@
1
1
  import path from "node:path";
2
2
 
3
- import { TsconfigReadTransaction } from "./TsconfigReadTransaction";
4
3
  import { CONFIG_DIR_TEMPLATE_LIST_OPTIONS } from "./CONFIG_DIR_TEMPLATE_LIST_OPTIONS";
5
4
  import { CONFIG_DIR_TEMPLATE_SCALAR_OPTIONS } from "./CONFIG_DIR_TEMPLATE_SCALAR_OPTIONS";
5
+ import { TsconfigReadTransaction } from "./TsconfigReadTransaction";
6
6
  import { absolutizePathsTarget } from "./absolutizePathsTarget";
7
7
  import { findDeclaredCompilerOption } from "./findDeclaredCompilerOption";
8
8
  import { findDeclaredPaths } from "./findDeclaredPaths";
@@ -15,8 +15,8 @@ const CONFIG_DIR_TEMPLATE_FILE_SPECS = ["exclude", "files", "include"] as const;
15
15
  * Lists use the compiler's last-inherited-array rule: a later base declaring
16
16
  * null cannot erase an earlier array. Invalid elements are preserved so this
17
17
  * overlay does not hide the compiler's configuration diagnostics. The three
18
- * list searches share graph observations only within this call; a later call reads
19
- * current bytes in a fresh transaction.
18
+ * list searches share graph observations only within this call; a later call
19
+ * reads current bytes in a fresh transaction.
20
20
  *
21
21
  * @param configDir The directory `${configDir}` stands for, as
22
22
  * `readEffectiveTsconfigTemplateCompilerOptions` takes it.
@@ -2,9 +2,9 @@ import path from "node:path";
2
2
 
3
3
  import { TYPESCRIPT_TRANSFORM_EXTENSIONS } from "../source/TYPESCRIPT_TRANSFORM_EXTENSIONS";
4
4
  import type { ITtscProjectMembershipPolicy } from "./ITtscProjectMembershipPolicy";
5
- import { TsconfigReadTransaction } from "./TsconfigReadTransaction";
6
5
  import { JAVASCRIPT_INPUT_EXTENSIONS } from "./JAVASCRIPT_INPUT_EXTENSIONS";
7
6
  import { OUTPUT_DIRECTORY_OPTIONS } from "./OUTPUT_DIRECTORY_OPTIONS";
7
+ import { TsconfigReadTransaction } from "./TsconfigReadTransaction";
8
8
  import { absolutizePathsTarget } from "./absolutizePathsTarget";
9
9
  import { findDeclaredFileSpecs } from "./findDeclaredFileSpecs";
10
10
  import { findDeclaredValue } from "./findDeclaredValue";
@@ -40,9 +40,9 @@ const DEFAULT_INCLUDE_SPEC = "**/*";
40
40
  * refuses to guess in the direction that loses correctness.
41
41
  *
42
42
  * Source, identity and extends observations share this transaction. Completed
43
- * selections, including absence, require matching physical ancestry intersections.
44
- * Later calls start fresh, so unchanged metadata cannot conceal changed source
45
- * text or resolution.
43
+ * selections, including absence, require matching physical ancestry
44
+ * intersections. Later calls start fresh, so unchanged metadata cannot conceal
45
+ * changed source text or resolution.
46
46
  *
47
47
  * @evidence contracts/common.md#principled-implementation
48
48
  * Config-derived root specs, allowed extensions and exclusion provenance