Skip to content

Commit 68b2123

Browse files
committed
sdk + skills
1 parent 6440563 commit 68b2123

72 files changed

Lines changed: 6176 additions & 5725 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ This file defines the global rules for AI agents working in this repository.
5252
### Likes
5353

5454
- Use `ManagedCode.Tps` as the namespace, project, and solution prefix for .NET artifacts.
55+
- Keep the `.NET` runtime as the canonical TPS implementation and module layout that other runtimes mirror where practical.
5556
- Keep repo-local agent skills under `.codex/skills/`.
5657
- Keep architecture and workflow guidance durable and versioned in the repository.
5758
- Keep all active TPS runtimes feature-aligned: each runtime must expose spec constants, validation APIs, compiler APIs, and player APIs.
@@ -174,7 +175,7 @@ List only the skills this repository should actively use.
174175
- Use `.editorconfig`, project files, and checked-in docs as the durable source of tooling truth.
175176
- Keep names, namespaces, and future .NET projects under the `ManagedCode.Tps` prefix unless a documented exception is approved.
176177
- Every TPS runtime library must publish a clear list of spec constants for keywords, tags, emotions, metadata keys, and other validation-critical terms.
177-
- Do not introduce magic string literals or repeated catalog literals for TPS spec terms, statuses, diagnostic codes, emotion names, palette keys, or similar runtime-contract data; define and reuse named constants.
178+
- Do not introduce magic string literals, repeated catalog literals, or inline public catalog numbers for TPS spec terms, statuses, diagnostic codes, emotion names, palette keys, recommended WPM values, rhythm ranges, or similar runtime-contract data; define and reuse named constants or named catalog values.
178179
- Every TPS runtime library must include TPS format validation that reports actionable diagnostics for invalid structure, unknown tags, malformed attributes, and other authoring errors.
179180
- Keep each runtime in its own language folder under `SDK/`, and keep SDK design/testing/ADR documentation under `SDK/docs/`.
180181
- Design CI so the active runtime set is extensible beyond JavaScript, TypeScript, and C#, with future language additions enabled by configuration instead of ad hoc workflow rewrites.

SDK/README.md

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,17 @@ This folder is where TPS runtime implementations live. The goal is parity across
2121
- TPS compilation into a JSON-friendly state machine
2222
- player/runtime APIs for both deterministic sampling and live timed playback
2323

24-
The TypeScript runtime is the canonical implementation. The JavaScript runtime is the consumer-facing built artifact of that source. The .NET, Flutter, Swift, and Java runtimes are independent implementations that are kept aligned through shared fixtures and parity tests.
24+
The `.NET` runtime is the canonical TPS implementation and source layout. TypeScript mirrors the same runtime contract as the canonical source runtime for web-focused development, and the JavaScript runtime is the consumer-facing built artifact of that TypeScript source. Flutter, Swift, and Java are independent implementations kept aligned through shared fixtures and parity tests.
2525

2626
Compiled TPS output is meant to be portable. The active runtimes treat the compiled state machine as the shared transport format for `compile -> json -> restore -> play` flows.
2727

2828
The root [README.md](/Users/ksemenenko/Developer/TPS/README.md) is the canonical format specification. This SDK README documents the runtime contract that is implemented today.
2929

3030
## Workspace Layout
3131

32-
- `ts/`: canonical TypeScript implementation
32+
- `ts/`: TypeScript runtime that mirrors the canonical .NET contract for web-oriented consumers
3333
- `js/`: generated JavaScript runtime, Node tests, and package metadata
34-
- `dotnet/`: .NET runtime, solution, and xUnit tests
34+
- `dotnet/`: canonical .NET runtime, solution, and xUnit tests
3535
- `flutter/`: Dart runtime for Flutter hosts
3636
- `swift/`: Swift runtime package
3737
- `java/`: Java runtime package
@@ -43,9 +43,9 @@ The root [README.md](/Users/ksemenenko/Developer/TPS/README.md) is the canonical
4343

4444
| Folder | Purpose | Edit Here When | Main Commands |
4545
|--------|---------|----------------|---------------|
46-
| `SDK/ts` | canonical runtime source | changing TPS behavior or runtime contract | `npm --prefix SDK/js run build:tps`, `npm --prefix SDK/js run coverage:typescript` |
46+
| `SDK/ts` | TypeScript mirror of the canonical runtime contract | changing TPS behavior for TS/JS consumers | `npm --prefix SDK/js run build:tps`, `npm --prefix SDK/js run coverage:typescript` |
4747
| `SDK/js` | JavaScript package and Node validation | changing JS packaging or JS-specific tests | `npm --prefix SDK/js run test:js`, `npm --prefix SDK/js run coverage:js` |
48-
| `SDK/dotnet` | C# runtime and tests | changing .NET API or .NET behavior | `dotnet build SDK/dotnet/ManagedCode.Tps.slnx -warnaserror --no-restore`, `dotnet test SDK/dotnet/ManagedCode.Tps.slnx --no-restore` |
48+
| `SDK/dotnet` | canonical C# runtime and tests | changing the shared runtime contract, .NET API, or canonical behavior | `dotnet build SDK/dotnet/ManagedCode.Tps.slnx -warnaserror --no-restore`, `dotnet test SDK/dotnet/ManagedCode.Tps.slnx --no-restore` |
4949
| `SDK/flutter` | Dart runtime for Flutter hosts | changing Flutter/Dart behavior or tests | `cd SDK/flutter && dart pub get && ./coverage.sh` |
5050
| `SDK/swift` | Swift runtime package | changing Apple-platform runtime behavior or tests | `cd SDK/swift && ./coverage.sh` |
5151
| `SDK/java` | Java runtime package | changing Java behavior or tests | `cd SDK/java && ./coverage.sh` |
@@ -73,9 +73,7 @@ Across the active runtimes, the shared contract today includes:
7373
- timed playback via `TpsPlaybackSession`
7474
- compile-and-play embedding via `TpsStandalonePlayer`
7575

76-
Archetype parsing, inheritance, and recommended-WPM defaults are part of the current parity contract.
77-
78-
The advisory archetype-profile mismatch warnings and rhythm-analysis warnings described in the root spec are format-level guidance and are not yet enforced uniformly across every runtime.
76+
Archetype parsing, inheritance, recommended-WPM defaults, advisory archetype-profile mismatch warnings, and rhythm-analysis warnings are part of the current active-runtime contract and are covered by shared fixtures.
7977

8078
## Compiled Model
8179

SDK/dotnet/README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Package identity: `ManagedCode.Tps`
99

1010
## What This Project Is
1111

12-
`SDK/dotnet` is the C# implementation of the TPS runtime. It provides the same functional surface as the active TypeScript/JavaScript SDKs, but as a native .NET library under the `ManagedCode.Tps` namespace.
12+
`SDK/dotnet` is the canonical C# implementation of the TPS runtime. It defines the reference module layout and runtime behavior that the other SDKs mirror where practical, while still shipping as a native .NET library under the `ManagedCode.Tps` namespace.
1313

1414
This is the project to change when the .NET API, serialization behavior, or .NET runtime semantics need to evolve.
1515

@@ -35,6 +35,13 @@ This is the project to change when the .NET API, serialization behavior, or .NET
3535
## Project Layout
3636

3737
- `src/ManagedCode.Tps/`: runtime implementation
38+
- `src/ManagedCode.Tps/TpsSpec*.cs`: public constants, tags, palettes, and archetype catalogs
39+
- `src/ManagedCode.Tps/Models/*Contracts.cs`: public JSON/runtime models
40+
- `src/ManagedCode.Tps/Internal/TpsParser*.cs`: parser pipeline split by responsibility
41+
- `src/ManagedCode.Tps/Internal/TpsContentCompiler*.cs`: compiler pipeline split by responsibility
42+
- `src/ManagedCode.Tps/Internal/TpsArchetypeAnalyzer*.cs`: advisory archetype diagnostics
43+
- `src/ManagedCode.Tps/TpsRuntime*.cs`: compile pipeline and state-machine assembly
44+
- `src/ManagedCode.Tps/TpsPlaybackSession*.cs`: playback transport, loop, and event dispatch
3845
- `tests/ManagedCode.Tps.Tests/`: xUnit coverage and parity tests
3946

4047
## Technical Scope
@@ -52,7 +59,7 @@ This is the project to change when the .NET API, serialization behavior, or .NET
5259
## How To Work With This Project
5360

5461
1. Update `src/ManagedCode.Tps/` for runtime or API changes.
55-
2. Keep behavior aligned with the active TPS contract used by the TS/JS SDKs.
62+
2. Treat this runtime as the canonical contract and keep the other SDKs aligned with behavior changes made here.
5663
3. Run build, tests, and coverage checks after changes.
5764
4. Keep example snapshot parity with the shared fixtures under `SDK/fixtures/examples`.
5865
5. Keep the canonical compiled JSON transport fixture under `SDK/fixtures/transport` aligned with the runtime serializer.

SDK/dotnet/src/ManagedCode.Tps/AGENTS.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,8 @@ For this .NET project:
6767
- Do not place test helpers or test-only code in this project.
6868
- Prefer small, composable types over large utility buckets.
6969
- Do not add new TPS catalog literals inline when the value is part of the public runtime contract; expose and reuse named constants instead.
70+
- Treat the .NET runtime module layout as the canonical source structure that the other SDKs should mirror where practical.
7071

7172
## Exception Record
7273

73-
- Size exception:
74-
- scope: `TpsPlaybackSession.cs`, `Internal/TpsContentCompiler.cs`, `Internal/TpsParser.cs`, `Models/Contracts.cs`
75-
- reason: these files still concentrate the parity-first .NET implementation of the runtime contract
76-
- removal plan: split playback transition/publish code, parser header/front-matter code, content-scope handling, and transport contracts into smaller cohesive types
74+
- No active size exceptions in `src/ManagedCode.Tps`.
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
using ManagedCode.Tps.Models;
2+
3+
namespace ManagedCode.Tps.Internal;
4+
5+
internal static partial class CompiledScriptValidator
6+
{
7+
private static void ValidateWords(IReadOnlyList<CompiledWord> words, ISet<string> wordIds)
8+
{
9+
CompiledWord? previousWord = null;
10+
for (var index = 0; index < words.Count; index++)
11+
{
12+
var word = words[index];
13+
ValidateIdentifier(word.Id, "word", nameof(words), wordIds);
14+
15+
if (word.Index != index)
16+
{
17+
throw new ArgumentException("Compiled TPS words must have sequential indexes that match their order.", nameof(words));
18+
}
19+
20+
if (string.IsNullOrWhiteSpace(word.SegmentId) || string.IsNullOrWhiteSpace(word.BlockId))
21+
{
22+
throw new ArgumentException("Compiled TPS words must reference a segment and block.", nameof(words));
23+
}
24+
25+
if (string.Equals(word.Kind, TpsSpec.WordKinds.Word, StringComparison.Ordinal) && string.IsNullOrWhiteSpace(word.PhraseId))
26+
{
27+
throw new ArgumentException("Compiled TPS spoken words must reference a phrase.", nameof(words));
28+
}
29+
30+
if (word.StartMs < 0 || word.EndMs < word.StartMs)
31+
{
32+
throw new ArgumentException("Compiled TPS words must define a non-negative time range.", nameof(words));
33+
}
34+
35+
if (word.EndMs - word.StartMs != word.DisplayDurationMs)
36+
{
37+
throw new ArgumentException("Compiled TPS words must keep display duration aligned with their start and end timestamps.", nameof(words));
38+
}
39+
40+
if (previousWord is not null && word.StartMs != previousWord.EndMs)
41+
{
42+
throw new ArgumentException("Compiled TPS words must form a contiguous timeline.", nameof(words));
43+
}
44+
45+
previousWord = word;
46+
}
47+
}
48+
49+
private static void ValidateTimeRange(
50+
string scope,
51+
int startWordIndex,
52+
int endWordIndex,
53+
int startMs,
54+
int endMs,
55+
int wordCount,
56+
string parameterName)
57+
{
58+
if (startWordIndex < 0 || endWordIndex < startWordIndex || startMs < 0 || endMs < startMs)
59+
{
60+
throw new ArgumentException($"Compiled TPS {scope} ranges must be non-negative and ordered.", parameterName);
61+
}
62+
63+
if (wordCount == 0)
64+
{
65+
if (startWordIndex != 0 || endWordIndex != 0 || startMs != 0 || endMs != 0)
66+
{
67+
throw new ArgumentException($"Compiled TPS empty {scope} ranges must stay at zero.", parameterName);
68+
}
69+
70+
return;
71+
}
72+
73+
if (startWordIndex >= wordCount || endWordIndex >= wordCount)
74+
{
75+
throw new ArgumentException($"Compiled TPS {scope} ranges must reference words inside the canonical timeline.", parameterName);
76+
}
77+
}
78+
79+
private static void ValidateIdentifier(string id, string scope, string parameterName, ISet<string> seen)
80+
{
81+
if (string.IsNullOrWhiteSpace(id))
82+
{
83+
throw new ArgumentException($"Compiled TPS {scope} identifiers cannot be empty.", parameterName);
84+
}
85+
86+
if (!seen.Add(id))
87+
{
88+
throw new ArgumentException($"Compiled TPS {scope} identifiers must be unique.", parameterName);
89+
}
90+
}
91+
92+
private static void ValidateWordReferences(
93+
IReadOnlyList<CompiledWord> words,
94+
IReadOnlySet<string> segmentIds,
95+
IReadOnlySet<string> blockIds,
96+
IReadOnlySet<string> phraseIds)
97+
{
98+
foreach (var word in words)
99+
{
100+
if (!segmentIds.Contains(word.SegmentId))
101+
{
102+
throw new ArgumentException($"Compiled TPS word '{word.Id}' references an unknown segment '{word.SegmentId}'.", nameof(words));
103+
}
104+
105+
if (!blockIds.Contains(word.BlockId))
106+
{
107+
throw new ArgumentException($"Compiled TPS word '{word.Id}' references an unknown block '{word.BlockId}'.", nameof(words));
108+
}
109+
110+
if (!string.IsNullOrWhiteSpace(word.PhraseId) && !phraseIds.Contains(word.PhraseId))
111+
{
112+
throw new ArgumentException($"Compiled TPS word '{word.Id}' references an unknown phrase '{word.PhraseId}'.", nameof(words));
113+
}
114+
}
115+
}
116+
117+
private static void ValidateCanonicalScopeWords(
118+
string scope,
119+
string ownerId,
120+
IReadOnlyList<CompiledWord> scopeWords,
121+
int startWordIndex,
122+
int endWordIndex,
123+
int startMs,
124+
int endMs,
125+
IReadOnlyList<CompiledWord> canonicalWords,
126+
string parameterName,
127+
string expectedSegmentId,
128+
string? expectedBlockId = null,
129+
string? expectedPhraseId = null)
130+
{
131+
if (canonicalWords.Count == 0)
132+
{
133+
if (scopeWords.Count != 0)
134+
{
135+
throw new ArgumentException($"Compiled TPS {scope} '{ownerId}' cannot reference words when the canonical timeline is empty.", parameterName);
136+
}
137+
138+
return;
139+
}
140+
141+
if (scopeWords.Count == 0)
142+
{
143+
if (startWordIndex != 0 || endWordIndex != 0 || startMs != 0 || endMs != 0)
144+
{
145+
throw new ArgumentException($"Compiled TPS empty {scope} '{ownerId}' ranges must stay at zero.", parameterName);
146+
}
147+
148+
return;
149+
}
150+
151+
var expectedWordCount = endWordIndex - startWordIndex + 1;
152+
if (scopeWords.Count != expectedWordCount)
153+
{
154+
throw new ArgumentException($"Compiled TPS {scope} '{ownerId}' words must match the canonical range they claim to cover.", parameterName);
155+
}
156+
157+
if (startMs != canonicalWords[startWordIndex].StartMs || endMs != canonicalWords[endWordIndex].EndMs)
158+
{
159+
throw new ArgumentException($"Compiled TPS {scope} '{ownerId}' timestamps must match the canonical word range they claim to cover.", parameterName);
160+
}
161+
162+
for (var offset = 0; offset < scopeWords.Count; offset++)
163+
{
164+
var expectedWord = canonicalWords[startWordIndex + offset];
165+
var actualWord = scopeWords[offset];
166+
167+
if (!string.Equals(actualWord.Id, expectedWord.Id, StringComparison.Ordinal) ||
168+
actualWord.Index != expectedWord.Index ||
169+
actualWord.StartMs != expectedWord.StartMs ||
170+
actualWord.EndMs != expectedWord.EndMs)
171+
{
172+
throw new ArgumentException($"Compiled TPS {scope} '{ownerId}' words must stay aligned with the canonical word timeline.", parameterName);
173+
}
174+
175+
if (!string.Equals(actualWord.SegmentId, expectedSegmentId, StringComparison.Ordinal))
176+
{
177+
throw new ArgumentException($"Compiled TPS {scope} '{ownerId}' words must reference segment '{expectedSegmentId}'.", parameterName);
178+
}
179+
180+
if (expectedBlockId is not null && !string.Equals(actualWord.BlockId, expectedBlockId, StringComparison.Ordinal))
181+
{
182+
throw new ArgumentException($"Compiled TPS {scope} '{ownerId}' words must reference block '{expectedBlockId}'.", parameterName);
183+
}
184+
185+
if (expectedPhraseId is not null && !string.Equals(actualWord.PhraseId, expectedPhraseId, StringComparison.Ordinal))
186+
{
187+
throw new ArgumentException($"Compiled TPS {scope} '{ownerId}' words must reference phrase '{expectedPhraseId}'.", parameterName);
188+
}
189+
}
190+
}
191+
}

0 commit comments

Comments
 (0)