ttsc 0.23.0 → 0.25.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 (149) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +66 -63
  3. package/cmd/ttscgraph/serve.go +264 -48
  4. package/cmd/ttscgraph/serve_answers_a_project_that_implements_its_own_ambient_declaration_test.go +69 -0
  5. package/cmd/ttscgraph/serve_early_error_encode_failure_is_observable_test.go +40 -0
  6. package/cmd/ttscgraph/serve_graph_snapshot_version_matches_typescript_client_test.go +32 -0
  7. package/cmd/ttscgraph/serve_phase_trace_accounts_for_load_failure_test.go +45 -0
  8. package/cmd/ttscgraph/serve_phase_trace_is_opt_in_and_payload_free_test.go +52 -0
  9. package/cmd/ttscgraph/serve_protocol_negotiates_incremental_shards_test.go +44 -0
  10. package/cmd/ttscgraph/serve_session_reloads_commonjs_package_entry_for_esm_test.go +2 -2
  11. package/cmd/ttscgraph/serve_session_reloads_superseding_module_candidates_test.go +46 -20
  12. package/cmd/ttscgraph/serve_session_retries_failed_dump_test.go +9 -9
  13. package/cmd/ttscgraph/serve_shards.go +1390 -0
  14. package/cmd/ttscgraph/serve_shards_abandoned_generation_converges_on_retry_test.go +92 -0
  15. package/cmd/ttscgraph/serve_shards_delete_removed_source_test.go +57 -0
  16. package/cmd/ttscgraph/serve_shards_external_endpoints_test.go +53 -0
  17. package/cmd/ttscgraph/serve_shards_keep_source_distributed_dependencies_at_the_boundary_test.go +108 -0
  18. package/cmd/ttscgraph/serve_shards_match_full_dump_oracle_test.go +88 -0
  19. package/cmd/ttscgraph/serve_shards_merge_pending_and_new_edits_test.go +50 -0
  20. package/cmd/ttscgraph/serve_shards_prune_unreferenced_external_endpoints_test.go +65 -0
  21. package/cmd/ttscgraph/serve_shards_publish_created_source_test.go +92 -0
  22. package/cmd/ttscgraph/serve_shards_rebuild_declaration_edit_test.go +43 -0
  23. package/cmd/ttscgraph/serve_shards_refresh_cross_file_implementation_test.go +63 -0
  24. package/cmd/ttscgraph/serve_shards_refresh_dependent_diagnostics_test.go +80 -0
  25. package/cmd/ttscgraph/serve_shards_rename_moves_source_identity_test.go +95 -0
  26. package/cmd/ttscgraph/serve_shards_retry_preserves_committed_generation_test.go +48 -0
  27. package/cmd/ttscgraph/serve_shards_reuse_unaffected_sources_test.go +121 -0
  28. package/driver/declaration_shape.go +85 -0
  29. package/driver/program.go +39 -10
  30. package/go.sum +22 -22
  31. package/internal/graph/assigned_implementations_of_declaration_file_symbols_leave_no_edge_source_test.go +84 -0
  32. package/internal/graph/assigned_implementations_of_node_modules_symbols_leave_no_edge_source_test.go +85 -0
  33. package/internal/graph/build.go +62 -10
  34. package/internal/graph/build_files_reuses_committed_cross_file_endpoints_test.go +87 -0
  35. package/internal/graph/build_keeps_source_distributed_dependencies_at_the_external_boundary_test.go +74 -0
  36. package/internal/graph/dump.go +84 -43
  37. package/internal/graph/dump_path_mapper_caches_alias_resolution_test.go +29 -0
  38. package/internal/graph/dump_paths.go +134 -11
  39. package/internal/graph/dump_paths_canonicalize_windows_short_root_test.go +39 -0
  40. package/internal/graph/dump_rejects_provenance_only_path_errors_test.go +32 -0
  41. package/internal/graph/dump_serializes_the_full_graph_test.go +2 -2
  42. package/internal/graph/dump_symlink_project_publishes_matching_base_test.go +82 -0
  43. package/internal/graph/edges.go +73 -17
  44. package/internal/graph/exports.go +9 -2
  45. package/internal/graph/graph.go +47 -7
  46. package/internal/graph/graph_kind_contracts_match_their_producers_test.go +121 -0
  47. package/internal/graph/node_modifiers_emit_union_strings_test.go +1 -1
  48. package/internal/graph/provenance.go +11 -1
  49. package/internal/graph/resolve.go +1 -4
  50. package/internal/graph/source.go +23 -0
  51. package/internal/graph/wire_node_ids_share_alias_resolution_test.go +29 -0
  52. package/internal/lspserver/lsp_native_plugin_source.go +5 -6
  53. package/internal/lspserver/lsp_native_resident.go +17 -24
  54. package/lib/TtscService.d.ts +1 -6
  55. package/lib/TtscService.js +2 -2
  56. package/lib/TtscService.js.map +1 -1
  57. package/lib/compiler/internal/captureProcessOutput.d.ts +26 -0
  58. package/lib/compiler/internal/captureProcessOutput.js +92 -0
  59. package/lib/compiler/internal/captureProcessOutput.js.map +1 -0
  60. package/lib/compiler/internal/compileProjectInMemory.js +4 -7
  61. package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
  62. package/lib/compiler/internal/residentCheckProcess.d.ts +0 -2
  63. package/lib/compiler/internal/residentCheckProcess.js +1 -15
  64. package/lib/compiler/internal/residentCheckProcess.js.map +1 -1
  65. package/lib/compiler/internal/residentTransformProcess.d.ts +0 -6
  66. package/lib/compiler/internal/residentTransformProcess.js +0 -25
  67. package/lib/compiler/internal/residentTransformProcess.js.map +1 -1
  68. package/lib/compiler/internal/spawnNative.d.ts +14 -6
  69. package/lib/compiler/internal/spawnNative.js +32 -11
  70. package/lib/compiler/internal/spawnNative.js.map +1 -1
  71. package/lib/compiler/internal/startResidentTransform.d.ts +1 -5
  72. package/lib/compiler/internal/startResidentTransform.js +1 -3
  73. package/lib/compiler/internal/startResidentTransform.js.map +1 -1
  74. package/lib/launcher/internal/prepareExecution.d.ts +2 -1
  75. package/lib/launcher/internal/prepareExecution.js +269 -23
  76. package/lib/launcher/internal/prepareExecution.js.map +1 -1
  77. package/lib/launcher/internal/registerRuntimeHooks.js +6 -1
  78. package/lib/launcher/internal/registerRuntimeHooks.js.map +1 -1
  79. package/lib/launcher/internal/runTtscserver.js +4 -9
  80. package/lib/launcher/internal/runTtscserver.js.map +1 -1
  81. package/lib/launcher/internal/runTtsx.js +2 -55
  82. package/lib/launcher/internal/runTtsx.js.map +1 -1
  83. package/lib/launcher/internal/runtimeHooks.d.ts +25 -2
  84. package/lib/launcher/internal/runtimeHooks.js +344 -69
  85. package/lib/launcher/internal/runtimeHooks.js.map +1 -1
  86. package/lib/plugin/internal/buildSourcePlugin.js +31 -3
  87. package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
  88. package/lib/plugin/internal/descriptorProcessFailure.d.ts +23 -19
  89. package/lib/plugin/internal/descriptorProcessFailure.js +40 -51
  90. package/lib/plugin/internal/descriptorProcessFailure.js.map +1 -1
  91. package/lib/plugin/internal/loadProjectPlugins.d.ts +12 -0
  92. package/lib/plugin/internal/loadProjectPlugins.js +77 -28
  93. package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
  94. package/package.json +8 -8
  95. package/shim/ast/extra-shim.json +4 -4
  96. package/shim/ast/go.mod +14 -14
  97. package/shim/ast/go.sum +20 -20
  98. package/shim/astnav/go.mod +14 -14
  99. package/shim/astnav/go.sum +20 -20
  100. package/shim/bundled/extra-shim.json +1 -1
  101. package/shim/bundled/go.mod +5 -5
  102. package/shim/checker/extra-shim.json +31 -31
  103. package/shim/checker/go.mod +5 -5
  104. package/shim/compiler/extra-shim.json +4 -4
  105. package/shim/compiler/go.mod +14 -14
  106. package/shim/compiler/go.sum +22 -22
  107. package/shim/core/extra-shim.json +4 -4
  108. package/shim/core/go.mod +5 -5
  109. package/shim/diagnosticwriter/go.mod +5 -5
  110. package/shim/lsp/extra-shim.json +4 -4
  111. package/shim/lsp/go.mod +16 -16
  112. package/shim/lsp/go.sum +26 -26
  113. package/shim/parser/extra-shim.json +4 -4
  114. package/shim/parser/go.mod +5 -5
  115. package/shim/printer/go.mod +14 -14
  116. package/shim/printer/go.sum +22 -22
  117. package/shim/scanner/extra-shim.json +4 -4
  118. package/shim/scanner/go.mod +5 -5
  119. package/shim/transformers/go.mod +14 -14
  120. package/shim/transformers/go.sum +20 -20
  121. package/shim/tsoptions/extra-shim.json +4 -4
  122. package/shim/tsoptions/go.mod +5 -5
  123. package/shim/tspath/extra-shim.json +4 -4
  124. package/shim/tspath/go.mod +5 -5
  125. package/shim/vfs/cachedvfs/extra-shim.json +4 -4
  126. package/shim/vfs/cachedvfs/go.mod +5 -5
  127. package/shim/vfs/extra-shim.json +4 -4
  128. package/shim/vfs/go.mod +5 -5
  129. package/shim/vfs/osvfs/extra-shim.json +4 -4
  130. package/shim/vfs/osvfs/go.mod +5 -5
  131. package/src/TtscService.ts +8 -20
  132. package/src/compiler/internal/captureProcessOutput.ts +96 -0
  133. package/src/compiler/internal/compileProjectInMemory.ts +7 -7
  134. package/src/compiler/internal/residentCheckProcess.ts +1 -21
  135. package/src/compiler/internal/residentTransformProcess.ts +0 -42
  136. package/src/compiler/internal/spawnNative.ts +39 -18
  137. package/src/compiler/internal/startResidentTransform.ts +1 -12
  138. package/src/launcher/internal/prepareExecution.ts +297 -26
  139. package/src/launcher/internal/registerRuntimeHooks.ts +11 -2
  140. package/src/launcher/internal/runTtscserver.ts +4 -9
  141. package/src/launcher/internal/runTtsx.ts +2 -78
  142. package/src/launcher/internal/runtimeHooks.ts +385 -82
  143. package/src/plugin/internal/buildSourcePlugin.ts +36 -3
  144. package/src/plugin/internal/descriptorProcessFailure.ts +34 -65
  145. package/src/plugin/internal/loadProjectPlugins.ts +78 -39
  146. package/tools/gen_shims/go.mod +14 -14
  147. package/tools/gen_shims/go.sum +12 -12
  148. package/tools/shim_audit/go.mod +10 -10
  149. package/tools/shim_audit/go.sum +8 -8
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Jeongho Nam
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jeongho Nam
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -8,8 +8,9 @@ A `typescript-go` toolchain for compiler-powered plugins and type-safe execution
8
8
 
9
9
  - **`ttsc`**: build, check, and transform.
10
10
  - **`ttsx`**: execute TypeScript with type checking.
11
- - [**`@ttsc/graph`**](https://github.com/samchon/ttsc/tree/master/packages/graph): MCP code graph that reduces agent token usage.
12
11
  - [**`@ttsc/lint`**](https://github.com/samchon/ttsc/tree/master/packages/lint): lint violations as compiler errors.
12
+ - [**`@ttsc/evidence`**](https://github.com/samchon/ttsc/tree/master/packages/evidence): 100% requirement coverage, or the build fails.
13
+ - [**`@ttsc/graph`**](https://github.com/samchon/ttsc/tree/master/packages/graph): compiler knowledge graph that cuts agent tokens by 92%.
13
14
  - **plugin support**: compiler-powered libraries, such as `typia`.
14
15
 
15
16
  ## Setup
@@ -35,94 +36,95 @@ That covers the CLI. The integrations each have a short guide:
35
36
  - [`@ttsc/metro`](https://ttsc.dev/docs/setup/metro): React Native and Expo.
36
37
  - [`@ttsc/vscode`](https://ttsc.dev/docs/setup/vscode): live editor diagnostics.
37
38
 
38
- ## Graph
39
-
40
- Your coding agent answers from the compiler, spending roughly 90% fewer tokens.
39
+ ## Lint
41
40
 
42
- `@ttsc/graph` is an MCP server that gives the agent a compiler-resolved graph of your project: what calls what, what a change would touch, and where to start reading. The agent asks the type checker instead of grepping and re-reading files.
41
+ Lint and format inside the type-check you already run. 720+ rules across 21 families, plus a formatter that reproduces Prettier's output.
43
42
 
44
- Register it with your MCP client. For Claude Code, a `.mcp.json` in the project root:
43
+ ```ts
44
+ // src/index.ts
45
+ var x: number = 3;
46
+ let y: number = 4;
47
+ const z: string = 5;
48
+ ```
45
49
 
46
50
  ```bash
47
- npm install -D ttsc @ttsc/graph typescript
48
- ```
51
+ $ npx ttsc --noEmit
52
+ src/index.ts:3:7 - error TS2322: Type 'number' is not assignable to type 'string'.
49
53
 
50
- ```json
51
- {
52
- "mcpServers": {
53
- "ttsc-graph": {
54
- "command": "npx",
55
- "args": ["-y", "@ttsc/graph"]
56
- }
57
- }
58
- }
59
- ```
54
+ 3 const z: string = 5;
55
+ ~
60
56
 
61
- On the agent-cost benchmark, Claude agents answer reading zero files, cutting tokens by roughly 90% and tool calls by 93% to 96%. The design and per-repository numbers are in the [Code Graph guide](https://ttsc.dev/docs/graph) and the [benchmark](https://ttsc.dev/docs/benchmark/graph).
57
+ src/index.ts:2:5 - error TS17397: [prefer-const] Use const instead of let.
62
58
 
63
- ![Median tokens on the shared onboarding question, lower is better](https://ttsc.dev/benchmark/svg/graph-common-codex-gpt-5.6-sol.svg)
59
+ 2 let y: number = 4;
60
+ ~~~~~~~~~~~~~
64
61
 
65
- ## Lint
62
+ src/index.ts:1:1 - error TS11966: [no-var] Unexpected var, use let or const instead.
66
63
 
67
- Lint and format at compiler speed: up to 800x faster than ESLint.
64
+ 1 var x: number = 3;
65
+ ~~~~~~~~~~~~~~~~~~
68
66
 
69
- `@ttsc/lint` replaces ESLint and Prettier with rules that run inside the type-check. It shares one AST pass with the compiler, so linting and formatting add almost nothing to the build you already run.
67
+ Found 3 errors in the same file, starting at: src/index.ts:3
68
+ ```
70
69
 
71
- Configuration is a single file. Each rule takes `"error"`, `"warning"`, or `"off"`, and the `format` block mirrors `.prettierrc`.
70
+ Type errors and lint violations arrive in one stream, so the CI step that already runs `ttsc --noEmit` gates lint with no second job and no second parse. On vscode's 6,093 files the rules take 73 ms inside that check, where ESLint spends 66.7 s as its own command.
72
71
 
73
- ```bash
74
- npm install -D @ttsc/lint
75
- ```
72
+ `npx ttsc fix` applies autofixes and formatting; `npx ttsc format` only formats. Rules and every `format` key are in the [Lint and Format guide](https://ttsc.dev/docs/lint).
76
73
 
77
- ```ts
78
- // lint.config.ts
79
- import type { ITtscLintConfig } from "@ttsc/lint";
80
-
81
- export default {
82
- rules: {
83
- "no-var": "error",
84
- "prefer-const": "error",
85
- "typescript/no-explicit-any": "warning",
86
- },
87
- format: {
88
- printWidth: 100,
89
- singleQuote: true,
90
- trailingComma: "all",
91
- },
92
- } satisfies ITtscLintConfig;
74
+ ## Evidence Graph
75
+
76
+ Your spec becomes a compile error, so requirement coverage is 100% or the build does not pass. An agent can still lie, but it cannot lie by omission.
77
+
78
+ ```tsx
79
+ /**
80
+ * @evidence docs/discount.md#coupon-stacking
81
+ * States the per-issuer stacking limit
82
+ * this section defines, in the buyer's words.
83
+ * @evidence POST:/orders/{orderId}/coupons
84
+ * Explains the rejection this endpoint returns
85
+ * for an over-stacked coupon set.
86
+ * @evidence {@link hooks.useCouponStacking} Renders the limit this hook resolves.
87
+ */
88
+ export function CouponStackingNotice(props: IProps): JSX.Element;
93
89
  ```
94
90
 
95
- Violations surface as compiler diagnostics, in the same stream as type errors, so the CI step that already runs `ttsc --noEmit` gates lint without a second job:
91
+ `@evidence <target> <reason>` names one unit of the spec and why this declaration answers for it. A target is a document section, an API operation, a database schema model, or a TypeScript symbol as an inline link.
96
92
 
97
- ```ts
98
- // src/index.ts
99
- var count = 3;
100
- let total = count;
93
+ ```bash
94
+ $ npx ttsc
95
+ error TS16411: [evidence/graph] Missing acknowledgement for 'docs/discount.md#coupon-stacking'
96
+ (Markdown H2 'Coupon Stacking' at docs/discount.md:3)
97
+ in Claim 1 reference 1 (markdown, symbols: h2, h3).
98
+
99
+ Use @evidence on a selected typescript host or @evidenceExclude on an eligible carrier.
100
+
101
+ Found 3 errors.
101
102
  ```
102
103
 
103
- ```text
104
- $ npx ttsc --noEmit
105
- src/index.ts:1:1 - error TS11966: [no-var] Unexpected var, use let or const instead.
104
+ Without those tags, the build fails once per obligation, because one reference never covers another. An AI coding agent has to clear them to finish, and clearing them means citing each target and writing down why its code answers for it.
106
105
 
107
- 1 var count = 3;
108
- ~~~
106
+ ![Coverage and token spend, Plain against Evidence](https://raw.githubusercontent.com/samchon/ttsc/gh-pages/benchmark/png/evidence-summary.png)
109
107
 
110
- src/index.ts:2:5 - error TS17397: [prefer-const] Use const instead of let.
108
+ ## Compiler Knowledge Graph
111
109
 
112
- 2 let total = count;
113
- ~~~~~
110
+ Your coding agent answers from the compiler instead of grepping and re-reading files.
114
111
 
115
- Found 2 errors in the same file, starting at: src/index.ts:1
112
+ ```json
113
+ {
114
+ "mcpServers": {
115
+ "ttsc-graph": {
116
+ "command": "npx",
117
+ "args": ["-y", "@ttsc/graph"]
118
+ }
119
+ }
120
+ }
116
121
  ```
117
122
 
118
- Clean the project in place:
123
+ One typed MCP tool over a graph the type checker resolved: what calls what, what a change would touch, where to start reading. Answers carry names, signatures, edges, and spans, never file bodies, so a large repository cannot inflate the response.
119
124
 
120
- ```bash
121
- npx ttsc fix # lint autofixes + format edits
122
- npx ttsc format # format edits only, never changes behavior
123
- ```
125
+ Across 64 measured question and model pairs, the median answer costs 92% fewer tokens and 95% fewer tool calls than the same agent with no MCP. The design and the comparators are in [`@ttsc/graph`](https://github.com/samchon/ttsc/tree/master/packages/graph).
124
126
 
125
- The rule catalog and every `format` key are in the [Lint & Format guide](https://ttsc.dev/docs/lint).
127
+ ![Median tokens on the shared onboarding question, lower is better](https://raw.githubusercontent.com/samchon/ttsc/gh-pages/benchmark/png/graph-common-codex-gpt-5.6-sol.png)
126
128
 
127
129
  ## Plugins
128
130
 
@@ -148,6 +150,7 @@ export const isStringArray = (() => {
148
150
  Utility plugins shipped in this repository:
149
151
 
150
152
  - [`@ttsc/banner`](https://github.com/samchon/ttsc/tree/master/packages/banner): adds `@packageDocumentation` JSDoc banners.
153
+ - [`@ttsc/evidence`](https://github.com/samchon/ttsc/tree/master/packages/evidence): turns a requirement into a compile error until code, tests, or docs acknowledge it by name.
151
154
  - [`@ttsc/graph`](https://github.com/samchon/ttsc/tree/master/packages/graph): MCP server exposing a checker-resolved code graph to coding agents.
152
155
  - [`@ttsc/lint`](https://github.com/samchon/ttsc/tree/master/packages/lint): lints and formats TypeScript source.
153
156
  - [`@ttsc/paths`](https://github.com/samchon/ttsc/tree/master/packages/paths): rewrites source path aliases so JS and declaration emit receive relative imports.
@@ -13,6 +13,7 @@ import (
13
13
  "slices"
14
14
  "sort"
15
15
  "strings"
16
+ "time"
16
17
 
17
18
  shimtsoptions "github.com/microsoft/typescript-go/shim/tsoptions"
18
19
  shimtspath "github.com/microsoft/typescript-go/shim/tspath"
@@ -45,7 +46,9 @@ const (
45
46
  serveModeUnchanged = "unchanged"
46
47
  // serveModeIncremental is edits applied onto the reused resident program.
47
48
  serveModeIncremental = "incremental"
48
- // serveModeRebuild is edits applied but the program could not be reused.
49
+ // serveModeRebuild is edits applied but graph projection requires a complete
50
+ // rebuild, either because the program could not be reused or because a
51
+ // declaration-file boundary has no authored source shard of its own.
49
52
  serveModeRebuild = "rebuild"
50
53
  // serveModeError is a request that produced no snapshot. It is a transport
51
54
  // mode, not a computation mode: it exists so mode is never absent, because a
@@ -71,10 +74,15 @@ var serveCapabilities = fullSnapshotCapabilities
71
74
 
72
75
  type serveRequest struct {
73
76
  ID int `json:"id"`
77
+ // GraphSnapshotVersion opts into the incremental shard protocol. Omitted
78
+ // requests retain the schema-v6 full-dump response for existing
79
+ // @ttsc/graph clients.
80
+ GraphSnapshotVersion int `json:"graphSnapshotVersion,omitempty"`
74
81
  }
75
82
 
76
83
  type serveResponse struct {
77
- Dump *graph.Dump `json:"dump,omitempty"`
84
+ Dump *graph.Dump `json:"dump,omitempty"`
85
+ Snapshot *serveGraphSnapshot `json:"snapshot,omitempty"`
78
86
  // Error is set when the request produced no snapshot; Mode is then
79
87
  // serveModeError.
80
88
  Error string `json:"error,omitempty"`
@@ -90,6 +98,8 @@ type serveResponse struct {
90
98
  Changed bool `json:"changed"`
91
99
  }
92
100
 
101
+ const graphPhaseTraceEnvironment = "SAMCHON_GRAPH_TTSC_PHASE_TRACE"
102
+
93
103
  // newServeResponse stamps the fields every response owes the client, so no exit
94
104
  // from the serve loop can forget one.
95
105
  func newServeResponse(id int) serveResponse {
@@ -124,13 +134,17 @@ type graphSession struct {
124
134
  // generation, captured from the same parse that produced configHashes and
125
135
  // rootFiles so the published evidence and the invalidation state can never
126
136
  // describe different loads.
127
- configDigests []graph.FileDigest
128
- roots []graph.RootFile
129
- initialized bool
130
- // pendingDumpMode remembers a generation whose state was captured but whose
131
- // dump failed. Until an input change lets the session rebuild, an unchanged
132
- // request must retry that dump instead of falsely confirming the older graph.
133
- pendingDumpMode string
137
+ configDigests []graph.FileDigest
138
+ roots []graph.RootFile
139
+ initialized bool
140
+ graphStore *serveGraphStore
141
+ requestProtocol int
142
+ requestProtocolSelected bool
143
+ // pending remembers a generation whose state was captured but whose selected
144
+ // projection failed. Until an input change lets the session rebuild, an
145
+ // unchanged request retries the same full or partial invalidation instead of
146
+ // falsely confirming the older client graph.
147
+ pending *graphChange
134
148
  }
135
149
 
136
150
  func newGraphSession(cwd, tsconfig string) (*graphSession, error) {
@@ -149,106 +163,187 @@ func (s *graphSession) Close() error {
149
163
  }
150
164
 
151
165
  func (s *graphSession) Snapshot() (*graph.Dump, string, bool, error) {
166
+ change, err := s.nextChange(false)
167
+ if err != nil {
168
+ return nil, "", false, err
169
+ }
170
+ if change == nil {
171
+ return nil, serveModeUnchanged, false, nil
172
+ }
173
+ dump, err := s.buildDump()
174
+ if err != nil {
175
+ s.pending = change
176
+ return nil, "", false, err
177
+ }
178
+ s.pending = nil
179
+ return &dump, change.mode, true, nil
180
+ }
181
+
182
+ type graphChange struct {
183
+ mode string
184
+ files []string
185
+ publicFiles []string
186
+ full bool
187
+ }
188
+
189
+ // nextChange advances only the resident compiler and its captured input state.
190
+ // Projection is deliberately outside this method so the legacy full-dump and
191
+ // incremental shard protocols share exactly one invalidation decision without
192
+ // forcing either representation onto the other.
193
+ func (s *graphSession) nextChange(trackPublicShape bool) (*graphChange, error) {
152
194
  if !s.initialized {
153
195
  // The captured compiler state is initialized even when its first dump is
154
196
  // not publishable. Mark the attempt before building so a later request can
155
197
  // observe a config/source edit that repairs the path error instead of
156
198
  // retrying the stale Program forever.
157
199
  s.initialized = true
158
- dump, err := s.buildDump()
159
- if err != nil {
160
- s.pendingDumpMode = serveModeInitial
161
- return nil, "", false, err
162
- }
163
- s.pendingDumpMode = ""
164
- return &dump, serveModeInitial, true, nil
200
+ return &graphChange{mode: serveModeInitial, full: true}, nil
165
201
  }
166
202
 
167
203
  configChanged, err := hashesChanged(s.configHashes)
168
204
  if err != nil {
169
- return nil, "", false, err
205
+ return nil, err
170
206
  }
171
207
  if configChanged {
172
208
  if err := s.reload(); err != nil {
173
- return nil, "", false, err
209
+ return nil, err
174
210
  }
175
- return s.changedSnapshot(serveModeReload)
211
+ return &graphChange{mode: serveModeReload, full: true}, nil
176
212
  }
177
213
 
178
214
  if diskStatesChanged(s.auxStates) {
179
215
  if err := s.reload(); err != nil {
180
- return nil, "", false, err
216
+ return nil, err
181
217
  }
182
- return s.changedSnapshot(serveModeReload)
218
+ return &graphChange{mode: serveModeReload, full: true}, nil
183
219
  }
184
220
 
185
221
  roots, err := projectRootFiles(s.compiler.Program(), true)
186
222
  if err != nil {
187
- return nil, "", false, err
223
+ return nil, err
188
224
  }
189
225
  if !slices.Equal(s.rootFiles, roots) {
190
226
  if err := s.reload(); err != nil {
191
- return nil, "", false, err
227
+ return nil, err
192
228
  }
193
- return s.changedSnapshot(serveModeReload)
229
+ return &graphChange{mode: serveModeReload, full: true}, nil
194
230
  }
195
231
 
196
232
  changed, deleted, err := changedSources(s.sourceHashes)
197
233
  if err != nil {
198
- return nil, "", false, err
234
+ return nil, err
199
235
  }
200
236
  if deleted {
201
237
  if err := s.reload(); err != nil {
202
- return nil, "", false, err
238
+ return nil, err
203
239
  }
204
- return s.changedSnapshot(serveModeReload)
240
+ return &graphChange{mode: serveModeReload, full: true}, nil
205
241
  }
206
242
  if len(changed) == 0 {
207
- if s.pendingDumpMode != "" {
208
- return s.changedSnapshot(s.pendingDumpMode)
243
+ if s.pending != nil {
244
+ return s.pending, nil
209
245
  }
210
- return nil, serveModeUnchanged, false, nil
246
+ return nil, nil
211
247
  }
212
248
  if s.compiler.Program().HasLinkedProgramPlugins() {
213
249
  if err := s.reload(); err != nil {
214
- return nil, "", false, err
250
+ return nil, err
215
251
  }
216
- return s.changedSnapshot(serveModeReload)
252
+ return &graphChange{mode: serveModeReload, full: true}, nil
217
253
  }
218
254
 
219
255
  mode := serveModeIncremental
256
+ full := false
220
257
  paths := make([]string, 0, len(changed))
221
258
  for path := range changed {
222
259
  paths = append(paths, path)
223
260
  }
224
261
  sort.Strings(paths)
262
+ oldShapes := make(map[string]string, len(paths))
263
+ for _, path := range paths {
264
+ source := s.compiler.Program().SourceFile(path)
265
+ if source == nil {
266
+ if err := s.reload(); err != nil {
267
+ return nil, err
268
+ }
269
+ return &graphChange{mode: serveModeReload, full: true}, nil
270
+ }
271
+ if source.IsDeclarationFile {
272
+ mode = serveModeRebuild
273
+ full = true
274
+ continue
275
+ }
276
+ if trackPublicShape {
277
+ shape, err := s.compiler.Program().DeclarationShapeDigest(source)
278
+ if err != nil {
279
+ return nil, err
280
+ }
281
+ oldShapes[path] = shape
282
+ }
283
+ }
225
284
  for _, path := range paths {
226
285
  if reused := s.compiler.Apply(path, changed[path]); !reused {
227
286
  mode = serveModeRebuild
287
+ full = true
228
288
  }
229
289
  current, exists := s.compiler.SourceText(path)
230
290
  expected := driver.ApplySourcePreambleToFile(path, changed[path], s.compiler.Program().SourcePreamble)
231
291
  if !exists || current != expected {
232
292
  if err := s.reload(); err != nil {
233
- return nil, "", false, err
293
+ return nil, err
234
294
  }
235
- return s.changedSnapshot(serveModeReload)
295
+ return &graphChange{mode: serveModeReload, full: true}, nil
296
+ }
297
+ source := s.compiler.Program().SourceFile(path)
298
+ if source == nil {
299
+ if err := s.reload(); err != nil {
300
+ return nil, err
301
+ }
302
+ return &graphChange{mode: serveModeReload, full: true}, nil
303
+ }
304
+ if source.IsDeclarationFile {
305
+ mode = serveModeRebuild
306
+ full = true
236
307
  }
237
308
  }
238
309
  if err := s.captureState(); err != nil {
239
- return nil, "", false, err
310
+ // The compiler already accepted the edit. Preserve a full retry marker so
311
+ // a transient hashing/input failure cannot make the next request compare
312
+ // public shape against the uncommitted compiler generation and publish an
313
+ // incomplete closure.
314
+ s.pending = &graphChange{mode: serveModeRebuild, full: true}
315
+ return nil, err
240
316
  }
241
- return s.changedSnapshot(mode)
242
- }
243
-
244
- func (s *graphSession) changedSnapshot(mode string) (*graph.Dump, string, bool, error) {
245
- dump, err := s.buildDump()
246
- if err != nil {
247
- s.pendingDumpMode = mode
248
- return nil, "", false, err
317
+ publicFiles := []string{}
318
+ if trackPublicShape && !full {
319
+ for _, path := range paths {
320
+ source := s.compiler.Program().SourceFile(path)
321
+ if source == nil {
322
+ return &graphChange{mode: serveModeRebuild, full: true}, nil
323
+ }
324
+ shape, err := s.compiler.Program().DeclarationShapeDigest(source)
325
+ if err != nil {
326
+ mode = serveModeRebuild
327
+ full = true
328
+ publicFiles = nil
329
+ break
330
+ }
331
+ if shape != oldShapes[path] {
332
+ publicFiles = append(publicFiles, path)
333
+ }
334
+ }
335
+ }
336
+ if s.pending != nil {
337
+ if s.pending.full {
338
+ mode = s.pending.mode
339
+ full = true
340
+ paths = nil
341
+ } else if !full {
342
+ paths = compactSortedStrings(append(paths, s.pending.files...))
343
+ publicFiles = compactSortedStrings(append(publicFiles, s.pending.publicFiles...))
344
+ }
249
345
  }
250
- s.pendingDumpMode = ""
251
- return &dump, mode, true, nil
346
+ return &graphChange{mode: mode, files: paths, publicFiles: publicFiles, full: full}, nil
252
347
  }
253
348
 
254
349
  func (s *graphSession) reload() error {
@@ -750,26 +845,86 @@ func serveSnapshots(input io.Reader, output io.Writer, cwd, tsconfig string) int
750
845
  fmt.Fprintf(stderr, "ttscgraph: unaddressable serve request: %v\n", err)
751
846
  return 1
752
847
  }
848
+ requestStarted := time.Now()
849
+ if request.GraphSnapshotVersion != 0 && request.GraphSnapshotVersion != graphSnapshotProtocolVersion {
850
+ if err := encodeServeResponseWithTrace(encoder, errorResponse(
851
+ request.ID,
852
+ fmt.Sprintf(
853
+ "ttscgraph: unsupported graph snapshot protocol v%d (supported: v%d)",
854
+ request.GraphSnapshotVersion,
855
+ graphSnapshotProtocolVersion,
856
+ ),
857
+ ), requestStarted, 0, 0, 0); err != nil {
858
+ fmt.Fprintf(stderr, "ttscgraph: write serve response: %v\n", err)
859
+ return 1
860
+ }
861
+ continue
862
+ }
863
+ var loadDuration time.Duration
753
864
  if session == nil {
865
+ loadStarted := time.Now()
754
866
  created, err := newGraphSession(cwd, tsconfig)
867
+ loadDuration = time.Since(loadStarted)
755
868
  if err != nil {
756
- _ = encoder.Encode(errorResponse(request.ID, err.Error()))
869
+ if err := encodeServeResponseWithTrace(
870
+ encoder,
871
+ errorResponse(request.ID, err.Error()),
872
+ requestStarted,
873
+ loadDuration,
874
+ 0,
875
+ 0,
876
+ ); err != nil {
877
+ fmt.Fprintf(stderr, "ttscgraph: write serve response: %v\n", err)
878
+ return 1
879
+ }
757
880
  continue
758
881
  }
759
882
  session = created
760
883
  }
761
- dump, mode, changed, err := session.Snapshot()
884
+ if session.requestProtocolSelected && session.requestProtocol != request.GraphSnapshotVersion {
885
+ if err := encodeServeResponseWithTrace(encoder, errorResponse(
886
+ request.ID,
887
+ "ttscgraph: graphSnapshotVersion cannot change within one resident session",
888
+ ), requestStarted, loadDuration, 0, 0); err != nil {
889
+ fmt.Fprintf(stderr, "ttscgraph: write serve response: %v\n", err)
890
+ return 1
891
+ }
892
+ continue
893
+ }
894
+ session.requestProtocol = request.GraphSnapshotVersion
895
+ session.requestProtocolSelected = true
896
+ var dump *graph.Dump
897
+ var snapshot *serveGraphSnapshot
898
+ var mode string
899
+ var changed bool
900
+ var semanticDuration time.Duration
901
+ var exportDuration time.Duration
902
+ var err error
903
+ if request.GraphSnapshotVersion == graphSnapshotProtocolVersion {
904
+ snapshot, mode, changed, semanticDuration, exportDuration, err = session.snapshotShardsWithTiming()
905
+ } else {
906
+ dump, mode, changed, err = session.Snapshot()
907
+ }
762
908
  response := newServeResponse(request.ID)
763
909
  response.Dump = dump
910
+ response.Snapshot = snapshot
764
911
  response.Mode = mode
765
912
  response.Changed = changed
766
913
  if err != nil {
767
914
  response.Error = err.Error()
768
915
  response.Dump = nil
916
+ response.Snapshot = nil
769
917
  response.Mode = serveModeError
770
918
  response.Changed = false
771
919
  }
772
- if err := encoder.Encode(response); err != nil {
920
+ if err := encodeServeResponseWithTrace(
921
+ encoder,
922
+ response,
923
+ requestStarted,
924
+ loadDuration,
925
+ semanticDuration,
926
+ exportDuration,
927
+ ); err != nil {
773
928
  fmt.Fprintf(stderr, "ttscgraph: write serve response: %v\n", err)
774
929
  return 1
775
930
  }
@@ -780,3 +935,64 @@ func serveSnapshots(input io.Reader, output io.Writer, cwd, tsconfig string) int
780
935
  }
781
936
  return 0
782
937
  }
938
+
939
+ // encodeServeResponseWithTrace accounts for every addressed response, including
940
+ // failures that occur before a graph session exists.
941
+ func encodeServeResponseWithTrace(
942
+ encoder *json.Encoder,
943
+ response serveResponse,
944
+ requestStarted time.Time,
945
+ loadDuration time.Duration,
946
+ semanticDuration time.Duration,
947
+ exportDuration time.Duration,
948
+ ) error {
949
+ encodeStarted := time.Now()
950
+ if err := encoder.Encode(response); err != nil {
951
+ return err
952
+ }
953
+ writeServePhaseTrace(
954
+ response.ID,
955
+ response.Mode,
956
+ loadDuration,
957
+ semanticDuration,
958
+ exportDuration,
959
+ time.Since(encodeStarted),
960
+ time.Since(requestStarted),
961
+ )
962
+ return nil
963
+ }
964
+
965
+ // writeServePhaseTrace emits opt-in timings without source paths or payloads.
966
+ func writeServePhaseTrace(
967
+ request int,
968
+ mode string,
969
+ load time.Duration,
970
+ semantic time.Duration,
971
+ export time.Duration,
972
+ encode time.Duration,
973
+ total time.Duration,
974
+ ) {
975
+ if os.Getenv(graphPhaseTraceEnvironment) != "1" {
976
+ return
977
+ }
978
+ phases := []struct {
979
+ name string
980
+ duration time.Duration
981
+ }{
982
+ {name: "native-load", duration: load},
983
+ {name: "semantic-refresh", duration: semantic},
984
+ {name: "shard-export", duration: export},
985
+ {name: "encode", duration: encode},
986
+ {name: "producer-total", duration: total},
987
+ }
988
+ for _, phase := range phases {
989
+ fmt.Fprintf(
990
+ stderr,
991
+ "@samchon/graph: ttscgraph-phase owner=producer request=%d mode=%s phase=%s durationMs=%.3f\n",
992
+ request,
993
+ mode,
994
+ phase.name,
995
+ float64(phase.duration)/float64(time.Millisecond),
996
+ )
997
+ }
998
+ }