@toolproof-core/schema 1.0.5 → 1.0.6

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.
@@ -0,0 +1,60 @@
1
+ declare const CONSTANTS: {
2
+ readonly IDENTIFIABLES: {
3
+ readonly PREFIXES: {
4
+ readonly BranchStepIdentity: "BRANCH_STEP-";
5
+ readonly ForStepIdentity: "FOR_STEP-";
6
+ readonly GoalIdentity: "GOAL-";
7
+ readonly JobIdentity: "JOB-";
8
+ readonly JobStepIdentity: "JOB_STEP-";
9
+ readonly ResourceIdentity: "RESOURCE-";
10
+ readonly ResourceRoleIdentity: "ROLE-";
11
+ readonly ResourceTypeIdentity: "TYPE-";
12
+ readonly RunnableStrategyIdentity: "RUNNABLE_STRATEGY-";
13
+ readonly StrategyRunIdentity: "STRATEGY_RUN-";
14
+ readonly StrategyThreadIdentity: "STRATEGY_THREAD-";
15
+ readonly WhileStepIdentity: "WHILE_STEP-";
16
+ };
17
+ readonly NAMES: {
18
+ readonly BranchStep: "BranchStep";
19
+ readonly ForStep: "ForStep";
20
+ readonly Goal: "Goal";
21
+ readonly Job: "Job";
22
+ readonly JobStep: "JobStep";
23
+ readonly Resource: "Resource";
24
+ readonly ResourceRole: "ResourceRole";
25
+ readonly ResourceType: "ResourceType";
26
+ readonly RunnableStrategy: "RunnableStrategy";
27
+ readonly StrategyRun: "StrategyRun";
28
+ readonly StrategyThread: "StrategyThread";
29
+ readonly WhileStep: "WhileStep";
30
+ };
31
+ };
32
+ readonly ENUMS: {
33
+ readonly ResourceKind: {
34
+ readonly missing: "missing";
35
+ readonly 'input-potential': "input-potential";
36
+ readonly 'output-potential': "output-potential";
37
+ readonly materialized: "materialized";
38
+ };
39
+ readonly RunEventKind: {
40
+ readonly graph_start: "graph_start";
41
+ readonly tick: "tick";
42
+ readonly interrupt: "interrupt";
43
+ readonly graph_end: "graph_end";
44
+ };
45
+ readonly RunnableStrategyStatus: {
46
+ readonly pending: "pending";
47
+ readonly running: "running";
48
+ readonly completed: "completed";
49
+ readonly failed: "failed";
50
+ readonly cancelled: "cancelled";
51
+ };
52
+ readonly StepKind: {
53
+ readonly job: "job";
54
+ readonly branch: "branch";
55
+ readonly while: "while";
56
+ readonly for: "for";
57
+ };
58
+ };
59
+ };
60
+ export default CONSTANTS;
@@ -0,0 +1,60 @@
1
+ const CONSTANTS = {
2
+ IDENTIFIABLES: {
3
+ PREFIXES: {
4
+ BranchStepIdentity: 'BRANCH_STEP-',
5
+ ForStepIdentity: 'FOR_STEP-',
6
+ GoalIdentity: 'GOAL-',
7
+ JobIdentity: 'JOB-',
8
+ JobStepIdentity: 'JOB_STEP-',
9
+ ResourceIdentity: 'RESOURCE-',
10
+ ResourceRoleIdentity: 'ROLE-',
11
+ ResourceTypeIdentity: 'TYPE-',
12
+ RunnableStrategyIdentity: 'RUNNABLE_STRATEGY-',
13
+ StrategyRunIdentity: 'STRATEGY_RUN-',
14
+ StrategyThreadIdentity: 'STRATEGY_THREAD-',
15
+ WhileStepIdentity: 'WHILE_STEP-',
16
+ },
17
+ NAMES: {
18
+ BranchStep: 'BranchStep',
19
+ ForStep: 'ForStep',
20
+ Goal: 'Goal',
21
+ Job: 'Job',
22
+ JobStep: 'JobStep',
23
+ Resource: 'Resource',
24
+ ResourceRole: 'ResourceRole',
25
+ ResourceType: 'ResourceType',
26
+ RunnableStrategy: 'RunnableStrategy',
27
+ StrategyRun: 'StrategyRun',
28
+ StrategyThread: 'StrategyThread',
29
+ WhileStep: 'WhileStep',
30
+ },
31
+ },
32
+ ENUMS: {
33
+ ResourceKind: {
34
+ missing: 'missing',
35
+ 'input-potential': 'input-potential',
36
+ 'output-potential': 'output-potential',
37
+ materialized: 'materialized',
38
+ },
39
+ RunEventKind: {
40
+ graph_start: 'graph_start',
41
+ tick: 'tick',
42
+ interrupt: 'interrupt',
43
+ graph_end: 'graph_end',
44
+ },
45
+ RunnableStrategyStatus: {
46
+ pending: 'pending',
47
+ running: 'running',
48
+ completed: 'completed',
49
+ failed: 'failed',
50
+ cancelled: 'cancelled',
51
+ },
52
+ StepKind: {
53
+ job: 'job',
54
+ branch: 'branch',
55
+ while: 'while',
56
+ for: 'for',
57
+ },
58
+ }
59
+ };
60
+ export default CONSTANTS;
@@ -1332,10 +1332,6 @@
1332
1332
  "properties": {
1333
1333
  "runnableStrategyHandle": {
1334
1334
  "$ref": "#/$defs/RunnableStrategyIdentity"
1335
- },
1336
- "runnableStrategy": {
1337
- "$comment": "Optional embedded snapshot; runnableStrategyHandle remains the canonical pointer.",
1338
- "$ref": "#/$defs/RunnableStrategy"
1339
1335
  }
1340
1336
  },
1341
1337
  "allOf": [
@@ -2034,10 +2034,6 @@
2034
2034
  "properties": {
2035
2035
  "runnableStrategyHandle": {
2036
2036
  "$ref": "#/$defs/RunnableStrategyIdentity"
2037
- },
2038
- "runnableStrategy": {
2039
- "$comment": "Optional embedded snapshot; runnableStrategyHandle remains the canonical pointer.",
2040
- "$ref": "#/$defs/RunnableStrategy"
2041
2037
  }
2042
2038
  },
2043
2039
  "allOf": [
@@ -977,10 +977,6 @@
977
977
  "properties": {
978
978
  "runnableStrategyHandle": {
979
979
  "$ref": "#/$defs/RunnableStrategyIdentity"
980
- },
981
- "runnableStrategy": {
982
- "$comment": "Optional embedded snapshot; runnableStrategyHandle remains the canonical pointer.",
983
- "$ref": "#/$defs/RunnableStrategy"
984
980
  }
985
981
  },
986
982
  "allOf": [
@@ -361,14 +361,7 @@ export type RunnableStrategy =
361
361
  export type RunnableStrategyUpdate =
362
362
  {
363
363
  runnableStrategyHandle: RunnableStrategyIdentity;
364
- runnableStrategy?: RunnableStrategy1;
365
364
  } & StrategyStateDelta;
366
- export type RunnableStrategy1 =
367
- {
368
- identity: RunnableStrategyIdentity;
369
- runnableStrategyContext: RunnableStrategyContext;
370
- } & StrategyThreadMapFacet1 &
371
- StrategyStateFacet1;
372
365
  /**
373
366
  * This interface was referenced by `Genesis`'s JSON-Schema
374
367
  * via the `definition` "RunEventUpdates".
package/dist/index.d.ts CHANGED
@@ -2,9 +2,10 @@ export { default as SchemaGenesis } from './generated/schemas/Genesis.js';
2
2
  export { default as SchemaJob } from './generated/schemas/standalone/Job.js';
3
3
  export { default as SchemaStrategyRun } from './generated/schemas/standalone/StrategyRun.js';
4
4
  export { default as ResourceTypeGenesis } from './generated/resources/Genesis.js';
5
+ export { default as CONSTANTS } from './generated/constants/constants.js';
5
6
  export type { Resource_Genesis as Resource_GenesisJson } from './generated/types/standalone/Resource_Genesis.js';
6
7
  export type { Resource_ResourceType as Resource_ResourceTypeJson } from './generated/types/standalone/Resource_ResourceType.js';
7
8
  export type { Resource_Job as Resource_JobJson } from './generated/types/standalone/Resource_Job.js';
8
9
  export type { Resource_RawStrategy as Resource_RawStrategyJson } from './generated/types/standalone/Resource_RawStrategy.js';
9
10
  export type { Resource_RunnableStrategy as Resource_RunnableStrategyJson } from './generated/types/standalone/Resource_RunnableStrategy.js';
10
- export type { DocumentationFacet as DocumentationFacetJson, NucleusFacet as NucleusFacetJson, ResourceTypeIdentity as ResourceTypeIdentityJson, ResourceType as ResourceTypeJson, ResourceRoleIdentity as ResourceRoleIdentityJson, ResourceRoleValue as ResourceRoleValueJson, Conditional as ConditionalJson, RoleMap as RoleMapJson, Roles as RolesJson, RoleBindings as RoleBindingsJson, ResourceIdentity as ResourceIdentityJson, JobStepIdentity as JobStepIdentityJson, BranchStepIdentity as BranchStepIdentityJson, WhileStepIdentity as WhileStepIdentityJson, ForStepIdentity as ForStepIdentityJson, JobStep as JobStepJson, BranchStep as BranchStepJson, WhileStep as WhileStepJson, ForStep as ForStepJson, StepKind as StepKindJson, StepIdentity as StepIdentityJson, Step as StepJson, CreationContext as CreationContextJson, ResourceMissing as ResourceMissingJson, Resource as ResourceJson, StrategyState as StrategyStateJson, StepsFacet as StepsFacetJson, RawStrategy as RawStrategyJson, RunnableStrategyIdentity as RunnableStrategyIdentityJson, RunnableStrategyStatus as RunnableStrategyStatusJson, RunnableStrategy as RunnableStrategyJson, RunnableStrategyUpdate as RunnableStrategyUpdateJson, JobIdentity as JobIdentityJson, Job as JobJson, JsonData as JsonDataJson, StrategyThreadIdentity as StrategyThreadIdentityJson, StrategyThreadMap as StrategyThreadMapJson, JobStepSocket as JobStepSocketJson, Timestamp as TimestampJson, Goal as GoalJson, } from './generated/types/types.js';
11
+ export type { DocumentationFacet as DocumentationFacetJson, NucleusFacet as NucleusFacetJson, ResourceTypeIdentity as ResourceTypeIdentityJson, ResourceType as ResourceTypeJson, ResourceRoleIdentity as ResourceRoleIdentityJson, ResourceRoleValue as ResourceRoleValueJson, Conditional as ConditionalJson, RoleMap as RoleMapJson, Roles as RolesJson, RoleBindings as RoleBindingsJson, ResourceIdentity as ResourceIdentityJson, JobStepIdentity as JobStepIdentityJson, BranchStepIdentity as BranchStepIdentityJson, WhileStepIdentity as WhileStepIdentityJson, ForStepIdentity as ForStepIdentityJson, JobStep as JobStepJson, BranchStep as BranchStepJson, WhileStep as WhileStepJson, ForStep as ForStepJson, StepKind as StepKindJson, StepIdentity as StepIdentityJson, Step as StepJson, CreationContext as CreationContextJson, ResourceMissing as ResourceMissingJson, Resource as ResourceJson, StrategyState as StrategyStateJson, StepsFacet as StepsFacetJson, RawStrategy as RawStrategyJson, RunnableStrategyIdentity as RunnableStrategyIdentityJson, RunnableStrategyStatus as RunnableStrategyStatusJson, RunnableStrategy as RunnableStrategyJson, RunnableStrategyUpdate as RunnableStrategyUpdateJson, JobIdentity as JobIdentityJson, Job as JobJson, JsonData as JsonDataJson, StrategyThreadIdentity as StrategyThreadIdentityJson, StrategyThreadMap as StrategyThreadMapJson, JobStepSocket as JobStepSocketJson, Timestamp as TimestampJson, StrategyRunIdentity as StrategyRunIdentityJson, GoalIdentity as GoalIdentityJson, Goal as GoalJson, } from './generated/types/types.js';
package/dist/index.js CHANGED
@@ -3,3 +3,4 @@ export { default as SchemaGenesis } from './generated/schemas/Genesis.js';
3
3
  export { default as SchemaJob } from './generated/schemas/standalone/Job.js';
4
4
  export { default as SchemaStrategyRun } from './generated/schemas/standalone/StrategyRun.js';
5
5
  export { default as ResourceTypeGenesis } from './generated/resources/Genesis.js';
6
+ export { default as CONSTANTS } from './generated/constants/constants.js';
@@ -12,6 +12,7 @@ export declare class SchemaConfig {
12
12
  private readonly sourceFile;
13
13
  private readonly normalizedDir;
14
14
  private readonly schemasDir;
15
+ private readonly constantsDir;
15
16
  private readonly resourcesDir;
16
17
  private readonly dependencyMapPath;
17
18
  private readonly typesSrcDir;
@@ -28,6 +29,8 @@ export declare class SchemaConfig {
28
29
  getNormalizedSourcePath(): string;
29
30
  getSchemasDir(): string;
30
31
  getSchemaPath(filename: string): string;
32
+ getConstantsDir(): string;
33
+ getConstantsPath(filename: string): string;
31
34
  getStandaloneSchemaDir(): string;
32
35
  getStandaloneSchemaPath(filename: string): string;
33
36
  getTypesSrcDir(): string;
@@ -22,6 +22,7 @@ export class SchemaConfig {
22
22
  this.sourceFile = getEnv('TP_SCHEMA_SOURCE_FILE', 'Genesis.json');
23
23
  this.normalizedDir = getEnv('TP_SCHEMA_NORMALIZED_DIR', 'src/generated/normalized');
24
24
  this.schemasDir = getEnv('TP_SCHEMA_SCHEMAS_DIR', 'src/generated/schemas');
25
+ this.constantsDir = getEnv('TP_SCHEMA_CONSTANTS_DIR', 'src/generated/constants');
25
26
  this.resourcesDir = getEnv('TP_SCHEMA_RESOURCES_DIR', 'src/generated/resources');
26
27
  this.dependencyMapPath = getEnv('TP_SCHEMA_DEPENDENCY_MAP_PATH', 'src/generated/dependencies/dependencyMap.json');
27
28
  this.typesSrcDir = getEnv('TP_SCHEMA_TYPES_SRC_DIR', 'src/generated/types');
@@ -66,6 +67,14 @@ export class SchemaConfig {
66
67
  getSchemaPath(filename) {
67
68
  return path.join(this.getSchemasDir(), filename);
68
69
  }
70
+ getConstantsDir() {
71
+ return path.isAbsolute(this.constantsDir)
72
+ ? this.constantsDir
73
+ : path.join(this.root, this.constantsDir);
74
+ }
75
+ getConstantsPath(filename) {
76
+ return path.join(this.getConstantsDir(), filename);
77
+ }
69
78
  getStandaloneSchemaDir() {
70
79
  return path.join(this.getSchemasDir(), 'standalone');
71
80
  }
@@ -0,0 +1,12 @@
1
+ export type GeneratedConstants = {
2
+ IDENTIFIABLES: {
3
+ PREFIXES: Record<string, string>;
4
+ NAMES: Record<string, string>;
5
+ };
6
+ ENUMS: Record<string, Record<string, string>>;
7
+ };
8
+ export declare function extractIdentityPrefixes(schema: unknown): Record<string, string>;
9
+ export declare function deriveIdentifiablesFromIdentityPrefixes(identityPrefixes: Record<string, string>): Record<string, string>;
10
+ export declare function extractEnums(schema: unknown): Record<string, Record<string, string>>;
11
+ export declare function extractConstants(schema: unknown): GeneratedConstants;
12
+ export declare function renderConstantsTs(constants: GeneratedConstants): string;
@@ -0,0 +1,179 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { getConfig } from './_lib/config.js';
4
+ // PURE: Attempt to derive a canonical identity prefix from a canonical identity regex pattern.
5
+ function deriveIdentityPrefixFromPattern(pattern) {
6
+ // Canonical (currently used across schemas): ^PREFIX-.+$
7
+ const match = /^\^([^$]+)\.\+\$$/.exec(pattern);
8
+ if (!match)
9
+ return undefined;
10
+ const prefix = match[1];
11
+ if (!prefix || /[`\n\r]/.test(prefix))
12
+ return undefined;
13
+ return prefix;
14
+ }
15
+ // PURE: Extract all identity prefixes from `$defs/*Identity` string-pattern definitions.
16
+ export function extractIdentityPrefixes(schema) {
17
+ if (!schema || typeof schema !== 'object')
18
+ return {};
19
+ const defs = schema.$defs;
20
+ if (!defs || typeof defs !== 'object')
21
+ return {};
22
+ const defEntries = Object.entries(defs);
23
+ defEntries.sort(([a], [b]) => a.localeCompare(b));
24
+ const out = {};
25
+ for (const [defName, defVal] of defEntries) {
26
+ if (!/Identity$/.test(defName))
27
+ continue;
28
+ if (!defVal || typeof defVal !== 'object' || Array.isArray(defVal))
29
+ continue;
30
+ const v = defVal;
31
+ if (v.type !== 'string')
32
+ continue;
33
+ if (typeof v.pattern !== 'string')
34
+ continue;
35
+ const prefix = deriveIdentityPrefixFromPattern(v.pattern);
36
+ if (!prefix)
37
+ continue;
38
+ out[defName] = prefix;
39
+ }
40
+ return out;
41
+ }
42
+ // PURE: Derive identifiables from extracted Identity def names.
43
+ // Example: { BranchStepIdentity: 'BRANCH_STEP-' } => { BranchStep: 'BranchStep' }
44
+ export function deriveIdentifiablesFromIdentityPrefixes(identityPrefixes) {
45
+ const keys = Object.keys(identityPrefixes).sort((a, b) => a.localeCompare(b));
46
+ const out = {};
47
+ for (const key of keys) {
48
+ if (!key.endsWith('Identity'))
49
+ continue;
50
+ const name = key.slice(0, -'Identity'.length);
51
+ if (!name)
52
+ continue;
53
+ out[name] = name;
54
+ }
55
+ return out;
56
+ }
57
+ // PURE: Extract all string enums from `$defs/*Kind|*Status` definitions.
58
+ // Shape: { StepKind: { job: 'job', ... }, ResourceKind: { 'input-potential': 'input-potential', ... } }
59
+ export function extractEnums(schema) {
60
+ if (!schema || typeof schema !== 'object')
61
+ return {};
62
+ const defs = schema.$defs;
63
+ if (!defs || typeof defs !== 'object')
64
+ return {};
65
+ const defEntries = Object.entries(defs);
66
+ defEntries.sort(([a], [b]) => a.localeCompare(b));
67
+ const out = {};
68
+ for (const [defName, defVal] of defEntries) {
69
+ if (!/(Kind|Status)$/.test(defName))
70
+ continue;
71
+ if (!defVal || typeof defVal !== 'object' || Array.isArray(defVal))
72
+ continue;
73
+ const v = defVal;
74
+ if (v.type !== 'string')
75
+ continue;
76
+ if (!Array.isArray(v.enum) || v.enum.length === 0)
77
+ continue;
78
+ if (v.enum.some((x) => typeof x !== 'string'))
79
+ continue;
80
+ const members = {};
81
+ for (const member of v.enum) {
82
+ members[member] = member;
83
+ }
84
+ out[defName] = members;
85
+ }
86
+ return out;
87
+ }
88
+ // PURE: Extract all generated constants from a parsed schema.
89
+ export function extractConstants(schema) {
90
+ const identityPrefixes = extractIdentityPrefixes(schema);
91
+ const identifiables = deriveIdentifiablesFromIdentityPrefixes(identityPrefixes);
92
+ const enums = extractEnums(schema);
93
+ return {
94
+ IDENTIFIABLES: {
95
+ PREFIXES: identityPrefixes,
96
+ NAMES: identifiables,
97
+ },
98
+ ENUMS: enums,
99
+ };
100
+ }
101
+ // PURE: Render helpers for TS keys/strings.
102
+ function escapeTsString(value) {
103
+ return value
104
+ .replace(/\\/g, '\\\\')
105
+ .replace(/\r/g, '\\r')
106
+ .replace(/\n/g, '\\n')
107
+ .replace(/\t/g, '\\t')
108
+ .replace(/'/g, "\\'");
109
+ }
110
+ function renderTsStringLiteral(value) {
111
+ return `'${escapeTsString(value)}'`;
112
+ }
113
+ function isValidTsIdentifier(key) {
114
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key);
115
+ }
116
+ function renderTsKey(key) {
117
+ return isValidTsIdentifier(key) ? key : renderTsStringLiteral(key);
118
+ }
119
+ // PURE: Render constants TypeScript source.
120
+ export function renderConstantsTs(constants) {
121
+ const prefixKeys = Object.keys(constants.IDENTIFIABLES.PREFIXES).sort((a, b) => a.localeCompare(b));
122
+ const nameKeys = Object.keys(constants.IDENTIFIABLES.NAMES).sort((a, b) => a.localeCompare(b));
123
+ const enumKeys = Object.keys(constants.ENUMS).sort((a, b) => a.localeCompare(b));
124
+ const lines = [];
125
+ lines.push('const CONSTANTS = {');
126
+ lines.push(' IDENTIFIABLES: {');
127
+ lines.push(' PREFIXES: {');
128
+ for (const key of prefixKeys) {
129
+ const value = constants.IDENTIFIABLES.PREFIXES[key] ?? '';
130
+ lines.push(` ${renderTsKey(key)}: ${renderTsStringLiteral(value)},`);
131
+ }
132
+ lines.push(' },');
133
+ lines.push(' NAMES: {');
134
+ for (const key of nameKeys) {
135
+ const value = constants.IDENTIFIABLES.NAMES[key] ?? '';
136
+ lines.push(` ${renderTsKey(key)}: ${renderTsStringLiteral(value)},`);
137
+ }
138
+ lines.push(' },');
139
+ lines.push(' },');
140
+ lines.push(' ENUMS: {');
141
+ for (const key of enumKeys) {
142
+ const members = constants.ENUMS[key] ?? {};
143
+ lines.push(` ${renderTsKey(key)}: {`);
144
+ for (const memberKey of Object.keys(members)) {
145
+ const value = members[memberKey] ?? '';
146
+ lines.push(` ${renderTsKey(memberKey)}: ${renderTsStringLiteral(value)},`);
147
+ }
148
+ lines.push(' },');
149
+ }
150
+ lines.push(' }');
151
+ lines.push('} as const;');
152
+ lines.push('');
153
+ lines.push('export default CONSTANTS;');
154
+ lines.push('');
155
+ return lines.join('\n');
156
+ }
157
+ // IMPURE: Script entrypoint (config + filesystem I/O + console + process exit code).
158
+ function main() {
159
+ try {
160
+ const config = getConfig();
161
+ const inPath = config.getSchemaPath('Genesis.json');
162
+ const outPath = config.getConstantsPath('constants.ts');
163
+ if (!fs.existsSync(inPath)) {
164
+ throw new Error(`Genesis schema not found at ${inPath}. Run extractSchemasFromResourceTypeShells first.`);
165
+ }
166
+ const raw = fs.readFileSync(inPath, 'utf8');
167
+ const doc = JSON.parse(raw);
168
+ const constants = extractConstants(doc);
169
+ const ts = renderConstantsTs(constants);
170
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
171
+ fs.writeFileSync(outPath, ts, 'utf8');
172
+ console.log(`Wrote constants to ${outPath}`);
173
+ }
174
+ catch (error) {
175
+ console.error(`Error generating constants: ${error?.message ?? error}`);
176
+ process.exitCode = 1;
177
+ }
178
+ }
179
+ main();
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,58 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { getConfig } from "./_lib/config.js";
4
+ // PURE: Validate + normalize a dependency map loaded from JSON.
5
+ function normalizeDependencyMap(value) {
6
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
7
+ throw new Error("Invalid dependencyMap.json: expected an object");
8
+ }
9
+ const input = value;
10
+ const out = {};
11
+ // Preserve insertion order from the parsed JSON object.
12
+ for (const [key, rawDeps] of Object.entries(input)) {
13
+ if (rawDeps == null) {
14
+ out[key] = [];
15
+ continue;
16
+ }
17
+ if (!Array.isArray(rawDeps)) {
18
+ throw new Error(`Invalid dependencyMap.json: value for ${key} must be an array`);
19
+ }
20
+ out[key] = rawDeps.filter((d) => typeof d === "string");
21
+ }
22
+ return out;
23
+ }
24
+ // PURE: Compute terminals (defs that are not depended-upon by any other def).
25
+ function computeTerminalsInKeyOrder(dependencyMap) {
26
+ const keys = Object.keys(dependencyMap);
27
+ const dependedUpon = new Set();
28
+ for (const key of keys) {
29
+ for (const dep of dependencyMap[key] ?? []) {
30
+ dependedUpon.add(dep);
31
+ }
32
+ }
33
+ // Preserve key order from dependencyMap.json.
34
+ return keys.filter((k) => !dependedUpon.has(k));
35
+ }
36
+ // IMPURE: Script entrypoint (config + filesystem I/O + console + process exit code).
37
+ function main() {
38
+ try {
39
+ const config = getConfig();
40
+ const inPath = config.getDependencyMapPath();
41
+ const outPath = path.join(path.dirname(inPath), "terminals.json");
42
+ if (!fs.existsSync(inPath)) {
43
+ throw new Error(`Dependency map not found at ${inPath}. Run generateDependencies first.`);
44
+ }
45
+ const raw = fs.readFileSync(inPath, "utf8");
46
+ const parsed = JSON.parse(raw);
47
+ const dependencyMap = normalizeDependencyMap(parsed);
48
+ const terminals = computeTerminalsInKeyOrder(dependencyMap);
49
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
50
+ fs.writeFileSync(outPath, JSON.stringify(terminals, null, 4), "utf8");
51
+ console.log(`Wrote terminals to ${outPath}`);
52
+ }
53
+ catch (error) {
54
+ console.error(`Error generating terminals: ${error?.message ?? error}`);
55
+ process.exitCode = 1;
56
+ }
57
+ }
58
+ main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toolproof-core/schema",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "JSON schemas and TypeScript types for ToolProof",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -39,7 +39,9 @@
39
39
  "wrapResourceTypesWithResourceShells": "node ./dist/scripts/wrapResourceTypesWithResourceShells.js",
40
40
  "generateSchemaShims": "node ./dist/scripts/generateSchemaShims.js",
41
41
  "generateDependencies": "node ./dist/scripts/generateDependencies.js",
42
+ "generateConstants": "node ./dist/scripts/generateConstants.js",
43
+ "generateTerminals": "node ./dist/scripts/generateTerminals.js",
42
44
  "generateResourceTypeGenesisType": "node ./dist/scripts/generateStandaloneType.js --name Genesis",
43
- "update": "rimraf /s /q dist && pnpm run build:scripts && pnpm run normalizeAnchorsToPointers && pnpm run extractSchemasFromResourceTypeShells && pnpm run generateStandaloneSchema -- --name Job && pnpm run generateStandaloneSchema -- --name ResourceType && pnpm run generateStandaloneSchema -- --name RawStrategy && pnpm run generateStandaloneSchema -- --name RunnableStrategy && pnpm run generateStandaloneSchema -- --name StrategyRun && pnpm run generateStandaloneSchema -- --name Goal && pnpm run wrapResourceTypesWithResourceShells && pnpm run generateSchemaShims && pnpm run generateTypes && pnpm run generateStandaloneType -- --name Job && pnpm run generateStandaloneType -- --name ResourceType && pnpm run generateStandaloneType -- --name RawStrategy && pnpm run generateStandaloneType -- --name RunnableStrategy && pnpm run generateResourceTypeGenesisType && pnpm run generateDependencies && pnpm run build"
45
+ "update": "rimraf /s /q dist && pnpm run build:scripts && pnpm run normalizeAnchorsToPointers && pnpm run extractSchemasFromResourceTypeShells && pnpm run generateConstants && pnpm run generateStandaloneSchema -- --name Job && pnpm run generateStandaloneSchema -- --name ResourceType && pnpm run generateStandaloneSchema -- --name RawStrategy && pnpm run generateStandaloneSchema -- --name RunnableStrategy && pnpm run generateStandaloneSchema -- --name StrategyRun && pnpm run generateStandaloneSchema -- --name Goal && pnpm run wrapResourceTypesWithResourceShells && pnpm run generateSchemaShims && pnpm run generateTypes && pnpm run generateStandaloneType -- --name Job && pnpm run generateStandaloneType -- --name ResourceType && pnpm run generateStandaloneType -- --name RawStrategy && pnpm run generateStandaloneType -- --name RunnableStrategy && pnpm run generateResourceTypeGenesisType && pnpm run generateDependencies && pnpm run generateTerminals && pnpm run build"
44
46
  }
45
47
  }
package/src/Genesis.json CHANGED
@@ -1403,10 +1403,6 @@
1403
1403
  "properties": {
1404
1404
  "runnableStrategyHandle": {
1405
1405
  "$ref": "#RunnableStrategyIdentity"
1406
- },
1407
- "runnableStrategy": {
1408
- "$comment": "Optional embedded snapshot; runnableStrategyHandle remains the canonical pointer.",
1409
- "$ref": "#RunnableStrategy"
1410
1406
  }
1411
1407
  },
1412
1408
  "allOf": [
@@ -0,0 +1,61 @@
1
+ const CONSTANTS = {
2
+ IDENTIFIABLES: {
3
+ PREFIXES: {
4
+ BranchStepIdentity: 'BRANCH_STEP-',
5
+ ForStepIdentity: 'FOR_STEP-',
6
+ GoalIdentity: 'GOAL-',
7
+ JobIdentity: 'JOB-',
8
+ JobStepIdentity: 'JOB_STEP-',
9
+ ResourceIdentity: 'RESOURCE-',
10
+ ResourceRoleIdentity: 'ROLE-',
11
+ ResourceTypeIdentity: 'TYPE-',
12
+ RunnableStrategyIdentity: 'RUNNABLE_STRATEGY-',
13
+ StrategyRunIdentity: 'STRATEGY_RUN-',
14
+ StrategyThreadIdentity: 'STRATEGY_THREAD-',
15
+ WhileStepIdentity: 'WHILE_STEP-',
16
+ },
17
+ NAMES: {
18
+ BranchStep: 'BranchStep',
19
+ ForStep: 'ForStep',
20
+ Goal: 'Goal',
21
+ Job: 'Job',
22
+ JobStep: 'JobStep',
23
+ Resource: 'Resource',
24
+ ResourceRole: 'ResourceRole',
25
+ ResourceType: 'ResourceType',
26
+ RunnableStrategy: 'RunnableStrategy',
27
+ StrategyRun: 'StrategyRun',
28
+ StrategyThread: 'StrategyThread',
29
+ WhileStep: 'WhileStep',
30
+ },
31
+ },
32
+ ENUMS: {
33
+ ResourceKind: {
34
+ missing: 'missing',
35
+ 'input-potential': 'input-potential',
36
+ 'output-potential': 'output-potential',
37
+ materialized: 'materialized',
38
+ },
39
+ RunEventKind: {
40
+ graph_start: 'graph_start',
41
+ tick: 'tick',
42
+ interrupt: 'interrupt',
43
+ graph_end: 'graph_end',
44
+ },
45
+ RunnableStrategyStatus: {
46
+ pending: 'pending',
47
+ running: 'running',
48
+ completed: 'completed',
49
+ failed: 'failed',
50
+ cancelled: 'cancelled',
51
+ },
52
+ StepKind: {
53
+ job: 'job',
54
+ branch: 'branch',
55
+ while: 'while',
56
+ for: 'for',
57
+ },
58
+ }
59
+ } as const;
60
+
61
+ export default CONSTANTS;
@@ -218,7 +218,6 @@
218
218
  ],
219
219
  "RunnableStrategyUpdate": [
220
220
  "RunnableStrategyIdentity",
221
- "RunnableStrategy",
222
221
  "StrategyStateDelta"
223
222
  ],
224
223
  "RunEventCounters": [],
@@ -0,0 +1,12 @@
1
+ [
2
+ "ResourceType",
3
+ "Error",
4
+ "Job",
5
+ "ShellMaterialized",
6
+ "RawStrategy",
7
+ "RunnableStrategyUpdate",
8
+ "StrategyRun",
9
+ "Goal",
10
+ "Natural",
11
+ "Boolean"
12
+ ]
@@ -1332,10 +1332,6 @@
1332
1332
  "properties": {
1333
1333
  "runnableStrategyHandle": {
1334
1334
  "$ref": "#/$defs/RunnableStrategyIdentity"
1335
- },
1336
- "runnableStrategy": {
1337
- "$comment": "Optional embedded snapshot; runnableStrategyHandle remains the canonical pointer.",
1338
- "$ref": "#/$defs/RunnableStrategy"
1339
1335
  }
1340
1336
  },
1341
1337
  "allOf": [
@@ -2034,10 +2034,6 @@
2034
2034
  "properties": {
2035
2035
  "runnableStrategyHandle": {
2036
2036
  "$ref": "#/$defs/RunnableStrategyIdentity"
2037
- },
2038
- "runnableStrategy": {
2039
- "$comment": "Optional embedded snapshot; runnableStrategyHandle remains the canonical pointer.",
2040
- "$ref": "#/$defs/RunnableStrategy"
2041
2037
  }
2042
2038
  },
2043
2039
  "allOf": [
@@ -977,10 +977,6 @@
977
977
  "properties": {
978
978
  "runnableStrategyHandle": {
979
979
  "$ref": "#/$defs/RunnableStrategyIdentity"
980
- },
981
- "runnableStrategy": {
982
- "$comment": "Optional embedded snapshot; runnableStrategyHandle remains the canonical pointer.",
983
- "$ref": "#/$defs/RunnableStrategy"
984
980
  }
985
981
  },
986
982
  "allOf": [
@@ -361,14 +361,7 @@ export type RunnableStrategy =
361
361
  export type RunnableStrategyUpdate =
362
362
  {
363
363
  runnableStrategyHandle: RunnableStrategyIdentity;
364
- runnableStrategy?: RunnableStrategy1;
365
364
  } & StrategyStateDelta;
366
- export type RunnableStrategy1 =
367
- {
368
- identity: RunnableStrategyIdentity;
369
- runnableStrategyContext: RunnableStrategyContext;
370
- } & StrategyThreadMapFacet1 &
371
- StrategyStateFacet1;
372
365
  /**
373
366
  * This interface was referenced by `Genesis`'s JSON-Schema
374
367
  * via the `definition` "RunEventUpdates".
package/src/index.ts CHANGED
@@ -3,6 +3,7 @@ export { default as SchemaGenesis } from './generated/schemas/Genesis.js';
3
3
  export { default as SchemaJob } from './generated/schemas/standalone/Job.js';
4
4
  export { default as SchemaStrategyRun } from './generated/schemas/standalone/StrategyRun.js';
5
5
  export { default as ResourceTypeGenesis } from './generated/resources/Genesis.js';
6
+ export { default as CONSTANTS } from './generated/constants/constants.js';
6
7
 
7
8
 
8
9
  export type {
@@ -62,5 +63,7 @@ export type {
62
63
  StrategyThreadMap as StrategyThreadMapJson,
63
64
  JobStepSocket as JobStepSocketJson,
64
65
  Timestamp as TimestampJson,
66
+ StrategyRunIdentity as StrategyRunIdentityJson,
67
+ GoalIdentity as GoalIdentityJson,
65
68
  Goal as GoalJson,
66
- } from './generated/types/types.js';
69
+ } from './generated/types/types.js';
@@ -24,6 +24,7 @@ export class SchemaConfig {
24
24
  private readonly sourceFile: string;
25
25
  private readonly normalizedDir: string;
26
26
  private readonly schemasDir: string;
27
+ private readonly constantsDir: string;
27
28
  private readonly resourcesDir: string;
28
29
  private readonly dependencyMapPath: string;
29
30
  private readonly typesSrcDir: string;
@@ -40,6 +41,7 @@ export class SchemaConfig {
40
41
  this.sourceFile = getEnv('TP_SCHEMA_SOURCE_FILE', 'Genesis.json');
41
42
  this.normalizedDir = getEnv('TP_SCHEMA_NORMALIZED_DIR', 'src/generated/normalized');
42
43
  this.schemasDir = getEnv('TP_SCHEMA_SCHEMAS_DIR', 'src/generated/schemas');
44
+ this.constantsDir = getEnv('TP_SCHEMA_CONSTANTS_DIR', 'src/generated/constants');
43
45
  this.resourcesDir = getEnv('TP_SCHEMA_RESOURCES_DIR', 'src/generated/resources');
44
46
  this.dependencyMapPath = getEnv('TP_SCHEMA_DEPENDENCY_MAP_PATH', 'src/generated/dependencies/dependencyMap.json');
45
47
  this.typesSrcDir = getEnv('TP_SCHEMA_TYPES_SRC_DIR', 'src/generated/types');
@@ -94,6 +96,16 @@ export class SchemaConfig {
94
96
  return path.join(this.getSchemasDir(), filename);
95
97
  }
96
98
 
99
+ getConstantsDir(): string {
100
+ return path.isAbsolute(this.constantsDir)
101
+ ? this.constantsDir
102
+ : path.join(this.root, this.constantsDir);
103
+ }
104
+
105
+ getConstantsPath(filename: string): string {
106
+ return path.join(this.getConstantsDir(), filename);
107
+ }
108
+
97
109
  getStandaloneSchemaDir(): string {
98
110
  return path.join(this.getSchemasDir(), 'standalone');
99
111
  }
@@ -0,0 +1,217 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { getConfig } from './_lib/config.js';
4
+
5
+ type JSONValue = null | boolean | number | string | JSONValue[] | { [k: string]: JSONValue };
6
+
7
+ export type GeneratedConstants = {
8
+ IDENTIFIABLES: {
9
+ PREFIXES: Record<string, string>;
10
+ NAMES: Record<string, string>;
11
+ };
12
+ ENUMS: Record<string, Record<string, string>>;
13
+ };
14
+
15
+ // PURE: Attempt to derive a canonical identity prefix from a canonical identity regex pattern.
16
+ function deriveIdentityPrefixFromPattern(pattern: string): string | undefined {
17
+ // Canonical (currently used across schemas): ^PREFIX-.+$
18
+ const match = /^\^([^$]+)\.\+\$$/.exec(pattern);
19
+ if (!match) return undefined;
20
+
21
+ const prefix = match[1];
22
+ if (!prefix || /[`\n\r]/.test(prefix)) return undefined;
23
+
24
+ return prefix;
25
+ }
26
+
27
+ // PURE: Extract all identity prefixes from `$defs/*Identity` string-pattern definitions.
28
+ export function extractIdentityPrefixes(schema: unknown): Record<string, string> {
29
+ if (!schema || typeof schema !== 'object') return {};
30
+
31
+ const defs = (schema as any).$defs;
32
+ if (!defs || typeof defs !== 'object') return {};
33
+
34
+ const defEntries = Object.entries(defs as Record<string, unknown>);
35
+ defEntries.sort(([a], [b]) => a.localeCompare(b));
36
+
37
+ const out: Record<string, string> = {};
38
+
39
+ for (const [defName, defVal] of defEntries) {
40
+ if (!/Identity$/.test(defName)) continue;
41
+ if (!defVal || typeof defVal !== 'object' || Array.isArray(defVal)) continue;
42
+
43
+ const v: any = defVal;
44
+ if (v.type !== 'string') continue;
45
+ if (typeof v.pattern !== 'string') continue;
46
+
47
+ const prefix = deriveIdentityPrefixFromPattern(v.pattern);
48
+ if (!prefix) continue;
49
+
50
+ out[defName] = prefix;
51
+ }
52
+
53
+ return out;
54
+ }
55
+
56
+ // PURE: Derive identifiables from extracted Identity def names.
57
+ // Example: { BranchStepIdentity: 'BRANCH_STEP-' } => { BranchStep: 'BranchStep' }
58
+ export function deriveIdentifiablesFromIdentityPrefixes(identityPrefixes: Record<string, string>): Record<string, string> {
59
+ const keys = Object.keys(identityPrefixes).sort((a, b) => a.localeCompare(b));
60
+ const out: Record<string, string> = {};
61
+
62
+ for (const key of keys) {
63
+ if (!key.endsWith('Identity')) continue;
64
+ const name = key.slice(0, -'Identity'.length);
65
+ if (!name) continue;
66
+ out[name] = name;
67
+ }
68
+
69
+ return out;
70
+ }
71
+
72
+ // PURE: Extract all string enums from `$defs/*Kind|*Status` definitions.
73
+ // Shape: { StepKind: { job: 'job', ... }, ResourceKind: { 'input-potential': 'input-potential', ... } }
74
+ export function extractEnums(schema: unknown): Record<string, Record<string, string>> {
75
+ if (!schema || typeof schema !== 'object') return {};
76
+
77
+ const defs = (schema as any).$defs;
78
+ if (!defs || typeof defs !== 'object') return {};
79
+
80
+ const defEntries = Object.entries(defs as Record<string, unknown>);
81
+ defEntries.sort(([a], [b]) => a.localeCompare(b));
82
+
83
+ const out: Record<string, Record<string, string>> = {};
84
+
85
+ for (const [defName, defVal] of defEntries) {
86
+ if (!/(Kind|Status)$/.test(defName)) continue;
87
+ if (!defVal || typeof defVal !== 'object' || Array.isArray(defVal)) continue;
88
+
89
+ const v: any = defVal;
90
+ if (v.type !== 'string') continue;
91
+ if (!Array.isArray(v.enum) || v.enum.length === 0) continue;
92
+ if (v.enum.some((x: unknown) => typeof x !== 'string')) continue;
93
+
94
+ const members: Record<string, string> = {};
95
+ for (const member of v.enum as readonly string[]) {
96
+ members[member] = member;
97
+ }
98
+
99
+ out[defName] = members;
100
+ }
101
+
102
+ return out;
103
+ }
104
+
105
+ // PURE: Extract all generated constants from a parsed schema.
106
+ export function extractConstants(schema: unknown): GeneratedConstants {
107
+ const identityPrefixes = extractIdentityPrefixes(schema);
108
+ const identifiables = deriveIdentifiablesFromIdentityPrefixes(identityPrefixes);
109
+ const enums = extractEnums(schema);
110
+ return {
111
+ IDENTIFIABLES: {
112
+ PREFIXES: identityPrefixes,
113
+ NAMES: identifiables,
114
+ },
115
+ ENUMS: enums,
116
+ };
117
+ }
118
+
119
+ // PURE: Render helpers for TS keys/strings.
120
+ function escapeTsString(value: string): string {
121
+ return value
122
+ .replace(/\\/g, '\\\\')
123
+ .replace(/\r/g, '\\r')
124
+ .replace(/\n/g, '\\n')
125
+ .replace(/\t/g, '\\t')
126
+ .replace(/'/g, "\\'");
127
+ }
128
+
129
+ function renderTsStringLiteral(value: string): string {
130
+ return `'${escapeTsString(value)}'`;
131
+ }
132
+
133
+ function isValidTsIdentifier(key: string): boolean {
134
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key);
135
+ }
136
+
137
+ function renderTsKey(key: string): string {
138
+ return isValidTsIdentifier(key) ? key : renderTsStringLiteral(key);
139
+ }
140
+
141
+ // PURE: Render constants TypeScript source.
142
+ export function renderConstantsTs(constants: GeneratedConstants): string {
143
+ const prefixKeys = Object.keys(constants.IDENTIFIABLES.PREFIXES).sort((a, b) => a.localeCompare(b));
144
+ const nameKeys = Object.keys(constants.IDENTIFIABLES.NAMES).sort((a, b) => a.localeCompare(b));
145
+ const enumKeys = Object.keys(constants.ENUMS).sort((a, b) => a.localeCompare(b));
146
+
147
+ const lines: string[] = [];
148
+ lines.push('const CONSTANTS = {');
149
+ lines.push(' IDENTIFIABLES: {');
150
+ lines.push(' PREFIXES: {');
151
+
152
+ for (const key of prefixKeys) {
153
+ const value = constants.IDENTIFIABLES.PREFIXES[key] ?? '';
154
+ lines.push(` ${renderTsKey(key)}: ${renderTsStringLiteral(value)},`);
155
+ }
156
+
157
+ lines.push(' },');
158
+ lines.push(' NAMES: {');
159
+
160
+ for (const key of nameKeys) {
161
+ const value = constants.IDENTIFIABLES.NAMES[key] ?? '';
162
+ lines.push(` ${renderTsKey(key)}: ${renderTsStringLiteral(value)},`);
163
+ }
164
+
165
+ lines.push(' },');
166
+ lines.push(' },');
167
+ lines.push(' ENUMS: {');
168
+
169
+ for (const key of enumKeys) {
170
+ const members = constants.ENUMS[key] ?? {};
171
+ lines.push(` ${renderTsKey(key)}: {`);
172
+
173
+ for (const memberKey of Object.keys(members)) {
174
+ const value = members[memberKey] ?? '';
175
+ lines.push(` ${renderTsKey(memberKey)}: ${renderTsStringLiteral(value)},`);
176
+ }
177
+
178
+ lines.push(' },');
179
+ }
180
+
181
+ lines.push(' }');
182
+ lines.push('} as const;');
183
+ lines.push('');
184
+ lines.push('export default CONSTANTS;');
185
+ lines.push('');
186
+
187
+ return lines.join('\n');
188
+ }
189
+
190
+ // IMPURE: Script entrypoint (config + filesystem I/O + console + process exit code).
191
+ function main() {
192
+ try {
193
+ const config = getConfig();
194
+
195
+ const inPath = config.getSchemaPath('Genesis.json');
196
+ const outPath = config.getConstantsPath('constants.ts');
197
+
198
+ if (!fs.existsSync(inPath)) {
199
+ throw new Error(`Genesis schema not found at ${inPath}. Run extractSchemasFromResourceTypeShells first.`);
200
+ }
201
+
202
+ const raw = fs.readFileSync(inPath, 'utf8');
203
+ const doc: JSONValue = JSON.parse(raw);
204
+
205
+ const constants = extractConstants(doc);
206
+ const ts = renderConstantsTs(constants);
207
+
208
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
209
+ fs.writeFileSync(outPath, ts, 'utf8');
210
+ console.log(`Wrote constants to ${outPath}`);
211
+ } catch (error: any) {
212
+ console.error(`Error generating constants: ${error?.message ?? error}`);
213
+ process.exitCode = 1;
214
+ }
215
+ }
216
+
217
+ main();
@@ -0,0 +1,73 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { getConfig } from "./_lib/config.js";
4
+
5
+ type DependencyMap = Record<string, string[]>;
6
+
7
+ // PURE: Validate + normalize a dependency map loaded from JSON.
8
+ function normalizeDependencyMap(value: unknown): DependencyMap {
9
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
10
+ throw new Error("Invalid dependencyMap.json: expected an object");
11
+ }
12
+
13
+ const input = value as Record<string, unknown>;
14
+ const out: DependencyMap = {};
15
+
16
+ // Preserve insertion order from the parsed JSON object.
17
+ for (const [key, rawDeps] of Object.entries(input)) {
18
+ if (rawDeps == null) {
19
+ out[key] = [];
20
+ continue;
21
+ }
22
+ if (!Array.isArray(rawDeps)) {
23
+ throw new Error(`Invalid dependencyMap.json: value for ${key} must be an array`);
24
+ }
25
+ out[key] = rawDeps.filter((d): d is string => typeof d === "string");
26
+ }
27
+
28
+ return out;
29
+ }
30
+
31
+ // PURE: Compute terminals (defs that are not depended-upon by any other def).
32
+ function computeTerminalsInKeyOrder(dependencyMap: DependencyMap): string[] {
33
+ const keys = Object.keys(dependencyMap);
34
+ const dependedUpon = new Set<string>();
35
+
36
+ for (const key of keys) {
37
+ for (const dep of dependencyMap[key] ?? []) {
38
+ dependedUpon.add(dep);
39
+ }
40
+ }
41
+
42
+ // Preserve key order from dependencyMap.json.
43
+ return keys.filter((k) => !dependedUpon.has(k));
44
+ }
45
+
46
+ // IMPURE: Script entrypoint (config + filesystem I/O + console + process exit code).
47
+ function main() {
48
+ try {
49
+ const config = getConfig();
50
+
51
+ const inPath = config.getDependencyMapPath();
52
+ const outPath = path.join(path.dirname(inPath), "terminals.json");
53
+
54
+ if (!fs.existsSync(inPath)) {
55
+ throw new Error(`Dependency map not found at ${inPath}. Run generateDependencies first.`);
56
+ }
57
+
58
+ const raw = fs.readFileSync(inPath, "utf8");
59
+ const parsed = JSON.parse(raw) as unknown;
60
+
61
+ const dependencyMap = normalizeDependencyMap(parsed);
62
+ const terminals = computeTerminalsInKeyOrder(dependencyMap);
63
+
64
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
65
+ fs.writeFileSync(outPath, JSON.stringify(terminals, null, 4), "utf8");
66
+ console.log(`Wrote terminals to ${outPath}`);
67
+ } catch (error: any) {
68
+ console.error(`Error generating terminals: ${error?.message ?? error}`);
69
+ process.exitCode = 1;
70
+ }
71
+ }
72
+
73
+ main();