@sublang/slc 0.1.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.
- package/LICENSE +201 -0
- package/README.md +192 -0
- package/dist/app.d.ts +85 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +233 -0
- package/dist/app.js.map +1 -0
- package/dist/artifacts.d.ts +61 -0
- package/dist/artifacts.d.ts.map +1 -0
- package/dist/artifacts.js +97 -0
- package/dist/artifacts.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +26 -0
- package/dist/cli.js.map +1 -0
- package/dist/cligent-agent.d.ts +11 -0
- package/dist/cligent-agent.d.ts.map +1 -0
- package/dist/cligent-agent.js +66 -0
- package/dist/cligent-agent.js.map +1 -0
- package/dist/compiled-executor.d.ts +42 -0
- package/dist/compiled-executor.d.ts.map +1 -0
- package/dist/compiled-executor.js +433 -0
- package/dist/compiled-executor.js.map +1 -0
- package/dist/config-file.d.ts +52 -0
- package/dist/config-file.d.ts.map +1 -0
- package/dist/config-file.js +159 -0
- package/dist/config-file.js.map +1 -0
- package/dist/config.d.ts +82 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +161 -0
- package/dist/config.js.map +1 -0
- package/dist/entry-module.d.ts +22 -0
- package/dist/entry-module.d.ts.map +1 -0
- package/dist/entry-module.js +216 -0
- package/dist/entry-module.js.map +1 -0
- package/dist/execution.d.ts +71 -0
- package/dist/execution.d.ts.map +1 -0
- package/dist/execution.js +207 -0
- package/dist/execution.js.map +1 -0
- package/dist/file-capability.d.ts +46 -0
- package/dist/file-capability.d.ts.map +1 -0
- package/dist/file-capability.js +234 -0
- package/dist/file-capability.js.map +1 -0
- package/dist/file-grants.d.ts +79 -0
- package/dist/file-grants.d.ts.map +1 -0
- package/dist/file-grants.js +133 -0
- package/dist/file-grants.js.map +1 -0
- package/dist/hash.d.ts +14 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +34 -0
- package/dist/hash.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/interpreter.d.ts +48 -0
- package/dist/interpreter.d.ts.map +1 -0
- package/dist/interpreter.js +118 -0
- package/dist/interpreter.js.map +1 -0
- package/dist/invocation.d.ts +64 -0
- package/dist/invocation.d.ts.map +1 -0
- package/dist/invocation.js +148 -0
- package/dist/invocation.js.map +1 -0
- package/dist/link.d.ts +63 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +172 -0
- package/dist/link.js.map +1 -0
- package/dist/markdown.d.ts +12 -0
- package/dist/markdown.d.ts.map +1 -0
- package/dist/markdown.js +63 -0
- package/dist/markdown.js.map +1 -0
- package/dist/normalize.md +68 -0
- package/dist/phase-runner.d.ts +64 -0
- package/dist/phase-runner.d.ts.map +1 -0
- package/dist/phase-runner.js +322 -0
- package/dist/phase-runner.js.map +1 -0
- package/dist/phase.d.ts +62 -0
- package/dist/phase.d.ts.map +1 -0
- package/dist/phase.js +111 -0
- package/dist/phase.js.map +1 -0
- package/dist/pin-closure.d.ts +19 -0
- package/dist/pin-closure.d.ts.map +1 -0
- package/dist/pin-closure.js +110 -0
- package/dist/pin-closure.js.map +1 -0
- package/dist/pin-currency.d.ts +55 -0
- package/dist/pin-currency.d.ts.map +1 -0
- package/dist/pin-currency.js +435 -0
- package/dist/pin-currency.js.map +1 -0
- package/dist/pin-generate.d.ts +46 -0
- package/dist/pin-generate.d.ts.map +1 -0
- package/dist/pin-generate.js +131 -0
- package/dist/pin-generate.js.map +1 -0
- package/dist/pin-paths.d.ts +11 -0
- package/dist/pin-paths.d.ts.map +1 -0
- package/dist/pin-paths.js +108 -0
- package/dist/pin-paths.js.map +1 -0
- package/dist/pins.d.ts +84 -0
- package/dist/pins.d.ts.map +1 -0
- package/dist/pins.js +265 -0
- package/dist/pins.js.map +1 -0
- package/dist/pipeline.d.ts +61 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +147 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/playbook-contract.d.ts +194 -0
- package/dist/playbook-contract.d.ts.map +1 -0
- package/dist/playbook-contract.js +180 -0
- package/dist/playbook-contract.js.map +1 -0
- package/dist/playbook-ports.d.ts +53 -0
- package/dist/playbook-ports.d.ts.map +1 -0
- package/dist/playbook-ports.js +217 -0
- package/dist/playbook-ports.js.map +1 -0
- package/dist/resolver.d.ts +68 -0
- package/dist/resolver.d.ts.map +1 -0
- package/dist/resolver.js +133 -0
- package/dist/resolver.js.map +1 -0
- package/dist/runner.d.ts +45 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +506 -0
- package/dist/runner.js.map +1 -0
- package/dist/runtime-package.d.ts +7 -0
- package/dist/runtime-package.d.ts.map +1 -0
- package/dist/runtime-package.js +132 -0
- package/dist/runtime-package.js.map +1 -0
- package/dist/slc.config.template.yaml +5 -0
- package/dist/verify-coverage.d.ts +99 -0
- package/dist/verify-coverage.d.ts.map +1 -0
- package/dist/verify-coverage.js +1949 -0
- package/dist/verify-coverage.js.map +1 -0
- package/dist/verify-support.d.ts +10 -0
- package/dist/verify-support.d.ts.map +1 -0
- package/dist/verify-support.js +52 -0
- package/dist/verify-support.js.map +1 -0
- package/dist/verify.d.ts +393 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +1790 -0
- package/dist/verify.js.map +1 -0
- package/package.json +76 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
|
|
3
|
+
/**
|
|
4
|
+
* SLC phase-runner facade for compiled `playbook` artifacts (PHEXEC-23,
|
|
5
|
+
* PHEXEC-24; DR-005).
|
|
6
|
+
*
|
|
7
|
+
* A compiled `playbook` artifact default-exports a `PlaybookRuntimeFactory`
|
|
8
|
+
* (`createPlaybookRuntime`). `slc` drives it host-side — `init` with the
|
|
9
|
+
* DR-010 session/legacy compatibility view over a `PlaybookPorts` adapter, one
|
|
10
|
+
* `handleBossInput` turn seeded from the {@link PhaseInput}, then `dispose` —
|
|
11
|
+
* and derives a {@link PhaseResult}
|
|
12
|
+
* (`ok`/`blocked`/`error`) that {@link mapPhaseResult} maps onto the DR-003
|
|
13
|
+
* protocol: `ok` proceeds to the generic checks, `blocked` is the BLOCKED
|
|
14
|
+
* outcome, and `error` stops the pipeline like a failed generic check.
|
|
15
|
+
*
|
|
16
|
+
* The non-interactive driving lives in the compiled executor; this module owns
|
|
17
|
+
* the shared facade types, the static `playbook`-format recognition the
|
|
18
|
+
* pin-currency validator uses, the seeding of a phase request into the
|
|
19
|
+
* runtime's single Boss turn (PHEXEC-29), and the host workspace contract a
|
|
20
|
+
* transformation-performing direct Captain call carries (PHEXEC-34). See
|
|
21
|
+
* specs/dev/phase-execution.md.
|
|
22
|
+
*/
|
|
23
|
+
import ts from 'typescript';
|
|
24
|
+
/**
|
|
25
|
+
* Maps a compiled phase's {@link PhaseResult} onto the DR-003 execution-boundary
|
|
26
|
+
* outcome consumed by `runPhase` (PHEXEC-24).
|
|
27
|
+
*
|
|
28
|
+
* The facade result (owned by the DR-005 artifact contract) and the executor
|
|
29
|
+
* result (owned by the DR-003 boundary) are distinct types, so the compiled
|
|
30
|
+
* executor crosses the boundary through this seam rather than casting: `ok`
|
|
31
|
+
* proceeds to the generic checks, `blocked` is the BLOCKED outcome, and `error`
|
|
32
|
+
* stops the pipeline like a failed generic check, with diagnostics surfaced for
|
|
33
|
+
* every status.
|
|
34
|
+
*/
|
|
35
|
+
export function mapPhaseResult(result) {
|
|
36
|
+
return { status: result.status, diagnostics: result.diagnostics };
|
|
37
|
+
}
|
|
38
|
+
const PLAYBOOK_FACTORY = 'createPlaybookRuntime';
|
|
39
|
+
/**
|
|
40
|
+
* Reports whether a compiled artifact's source resolves to the linked `playbook`
|
|
41
|
+
* format: a module exposing a `createPlaybookRuntime` default export (DR-005).
|
|
42
|
+
* This is the static byte-level recognition the pin-currency validator uses; the
|
|
43
|
+
* loader confirms the contract at run time.
|
|
44
|
+
*/
|
|
45
|
+
export function resolvesToPlaybook(source) {
|
|
46
|
+
const file = ts.createSourceFile('artifact.playbook.ts', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
47
|
+
const diagnostics = file.parseDiagnostics;
|
|
48
|
+
if (diagnostics?.some((item) => item.category === ts.DiagnosticCategory.Error)) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
if (!usesErasableTypeScript(file) || defaultExportCount(file) !== 1) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
if (factoryValueBindingCount(file, PLAYBOOK_FACTORY) !== 1)
|
|
55
|
+
return false;
|
|
56
|
+
const hasFactoryBinding = factoryBindingIsCallable(file, PLAYBOOK_FACTORY);
|
|
57
|
+
for (const statement of file.statements) {
|
|
58
|
+
if (ts.isFunctionDeclaration(statement) &&
|
|
59
|
+
statement.name?.text === PLAYBOOK_FACTORY &&
|
|
60
|
+
statement.body !== undefined &&
|
|
61
|
+
hasModifier(statement, ts.SyntaxKind.ExportKeyword) &&
|
|
62
|
+
hasModifier(statement, ts.SyntaxKind.DefaultKeyword) &&
|
|
63
|
+
isSynchronousFunction(statement)) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
if (ts.isExportAssignment(statement) &&
|
|
67
|
+
!statement.isExportEquals &&
|
|
68
|
+
ts.isIdentifier(unwrapExpression(statement.expression)) &&
|
|
69
|
+
(unwrapExpression(statement.expression).text ===
|
|
70
|
+
PLAYBOOK_FACTORY ||
|
|
71
|
+
// `const factory: PlaybookRuntimeFactory<…> = createPlaybookRuntime;
|
|
72
|
+
// export default factory;` — a typed alias of the factory is the
|
|
73
|
+
// factory; linkers use it to state contract conformance in erasable
|
|
74
|
+
// TypeScript.
|
|
75
|
+
aliasesFactory(file, unwrapExpression(statement.expression).text)) &&
|
|
76
|
+
hasFactoryBinding) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
if (ts.isExportDeclaration(statement) &&
|
|
80
|
+
statement.exportClause !== undefined) {
|
|
81
|
+
if (ts.isNamedExports(statement.exportClause) &&
|
|
82
|
+
statement.exportClause.elements.some((element) => !statement.isTypeOnly &&
|
|
83
|
+
!element.isTypeOnly &&
|
|
84
|
+
element.name.text === 'default' &&
|
|
85
|
+
(element.propertyName?.text ?? element.name.text) ===
|
|
86
|
+
PLAYBOOK_FACTORY) &&
|
|
87
|
+
statement.moduleSpecifier === undefined &&
|
|
88
|
+
hasFactoryBinding) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
/** True when `name` is a top-level `const <name> = createPlaybookRuntime;`. */
|
|
96
|
+
function aliasesFactory(file, name) {
|
|
97
|
+
for (const statement of file.statements) {
|
|
98
|
+
if (!ts.isVariableStatement(statement))
|
|
99
|
+
continue;
|
|
100
|
+
if ((statement.declarationList.flags & ts.NodeFlags.Const) === 0 ||
|
|
101
|
+
hasModifier(statement, ts.SyntaxKind.DeclareKeyword)) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
105
|
+
if (ts.isIdentifier(declaration.name) &&
|
|
106
|
+
declaration.name.text === name &&
|
|
107
|
+
declaration.initializer !== undefined &&
|
|
108
|
+
ts.isIdentifier(unwrapExpression(declaration.initializer)) &&
|
|
109
|
+
unwrapExpression(declaration.initializer).text ===
|
|
110
|
+
PLAYBOOK_FACTORY) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
function usesErasableTypeScript(file) {
|
|
118
|
+
let erasable = true;
|
|
119
|
+
const visit = (node) => {
|
|
120
|
+
if (ts.isEnumDeclaration(node) ||
|
|
121
|
+
ts.isModuleDeclaration(node) ||
|
|
122
|
+
ts.isImportEqualsDeclaration(node) ||
|
|
123
|
+
(ts.isExportAssignment(node) && node.isExportEquals) ||
|
|
124
|
+
(ts.isParameter(node) &&
|
|
125
|
+
ts.isParameterPropertyDeclaration(node, node.parent))) {
|
|
126
|
+
erasable = false;
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
ts.forEachChild(node, visit);
|
|
130
|
+
};
|
|
131
|
+
visit(file);
|
|
132
|
+
return erasable;
|
|
133
|
+
}
|
|
134
|
+
function defaultExportCount(file) {
|
|
135
|
+
let count = 0;
|
|
136
|
+
for (const statement of file.statements) {
|
|
137
|
+
if ((ts.isFunctionDeclaration(statement) ||
|
|
138
|
+
ts.isClassDeclaration(statement)) &&
|
|
139
|
+
hasModifier(statement, ts.SyntaxKind.DefaultKeyword)) {
|
|
140
|
+
count++;
|
|
141
|
+
}
|
|
142
|
+
else if (ts.isExportAssignment(statement) && !statement.isExportEquals) {
|
|
143
|
+
count++;
|
|
144
|
+
}
|
|
145
|
+
else if (ts.isExportDeclaration(statement) &&
|
|
146
|
+
statement.exportClause !== undefined &&
|
|
147
|
+
ts.isNamedExports(statement.exportClause)) {
|
|
148
|
+
count += statement.exportClause.elements.filter((element) => !statement.isTypeOnly &&
|
|
149
|
+
!element.isTypeOnly &&
|
|
150
|
+
element.name.text === 'default').length;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return count;
|
|
154
|
+
}
|
|
155
|
+
function factoryValueBindingCount(file, name) {
|
|
156
|
+
let count = 0;
|
|
157
|
+
for (const statement of file.statements) {
|
|
158
|
+
if (ts.isFunctionDeclaration(statement) &&
|
|
159
|
+
statement.name?.text === name &&
|
|
160
|
+
statement.body !== undefined) {
|
|
161
|
+
count++;
|
|
162
|
+
}
|
|
163
|
+
if (ts.isVariableStatement(statement)) {
|
|
164
|
+
count += statement.declarationList.declarations.filter((declaration) => ts.isIdentifier(declaration.name) &&
|
|
165
|
+
declaration.name.text === name &&
|
|
166
|
+
declaration.initializer !== undefined).length;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return count;
|
|
170
|
+
}
|
|
171
|
+
function hasModifier(node, kind) {
|
|
172
|
+
return (ts.canHaveModifiers(node) &&
|
|
173
|
+
ts.getModifiers(node)?.some((modifier) => modifier.kind === kind) === true);
|
|
174
|
+
}
|
|
175
|
+
function factoryBindingIsCallable(file, name, seen = new Set()) {
|
|
176
|
+
if (seen.has(name))
|
|
177
|
+
return false;
|
|
178
|
+
const nextSeen = new Set(seen).add(name);
|
|
179
|
+
for (const statement of file.statements) {
|
|
180
|
+
if (ts.isFunctionDeclaration(statement) &&
|
|
181
|
+
statement.name?.text === name &&
|
|
182
|
+
statement.body !== undefined &&
|
|
183
|
+
isSynchronousFunction(statement)) {
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
if (ts.isVariableStatement(statement)) {
|
|
187
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
188
|
+
if (ts.isIdentifier(declaration.name) &&
|
|
189
|
+
declaration.name.text === name &&
|
|
190
|
+
declaration.initializer !== undefined &&
|
|
191
|
+
callableExpression(file, declaration.initializer, nextSeen)) {
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
function callableExpression(file, expression, seen) {
|
|
200
|
+
const value = unwrapExpression(expression);
|
|
201
|
+
if ((ts.isArrowFunction(value) || ts.isFunctionExpression(value)) &&
|
|
202
|
+
isSynchronousFunction(value)) {
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
if (ts.isIdentifier(value)) {
|
|
206
|
+
return factoryBindingIsCallable(file, value.text, seen);
|
|
207
|
+
}
|
|
208
|
+
// A DR-019 thin module binds the factory to a shared-engine factory call —
|
|
209
|
+
// `createXStatePlaybookRuntime(machine, spec)`. Static recognition accepts
|
|
210
|
+
// the call exactly when its callee is a non-type named import from the
|
|
211
|
+
// shared engine module; the loader confirms the returned factory's
|
|
212
|
+
// callability at run time (DR-017).
|
|
213
|
+
if (ts.isCallExpression(value)) {
|
|
214
|
+
const callee = unwrapExpression(value.expression);
|
|
215
|
+
if (ts.isIdentifier(callee)) {
|
|
216
|
+
return importedFromSharedEngine(file, callee.text);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
const SHARED_ENGINE_MODULE = '@sublang/playbook/xstate-runtime';
|
|
222
|
+
/** True when `name` locally binds the shared engine's runtime factory export. */
|
|
223
|
+
function importedFromSharedEngine(file, name) {
|
|
224
|
+
for (const statement of file.statements) {
|
|
225
|
+
if (!ts.isImportDeclaration(statement))
|
|
226
|
+
continue;
|
|
227
|
+
if (!ts.isStringLiteral(statement.moduleSpecifier) ||
|
|
228
|
+
statement.moduleSpecifier.text !== SHARED_ENGINE_MODULE) {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const clause = statement.importClause;
|
|
232
|
+
if (clause === undefined || clause.phaseModifier !== undefined)
|
|
233
|
+
continue;
|
|
234
|
+
const bindings = clause.namedBindings;
|
|
235
|
+
if (bindings === undefined || !ts.isNamedImports(bindings))
|
|
236
|
+
continue;
|
|
237
|
+
for (const element of bindings.elements) {
|
|
238
|
+
if (!element.isTypeOnly &&
|
|
239
|
+
element.name.text === name &&
|
|
240
|
+
(element.propertyName?.text ?? element.name.text) ===
|
|
241
|
+
'createXStatePlaybookRuntime') {
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
function isSynchronousFunction(node) {
|
|
249
|
+
return (!hasModifier(node, ts.SyntaxKind.AsyncKeyword) &&
|
|
250
|
+
(!('asteriskToken' in node) || node.asteriskToken === undefined));
|
|
251
|
+
}
|
|
252
|
+
function unwrapExpression(expression) {
|
|
253
|
+
let current = expression;
|
|
254
|
+
while (ts.isParenthesizedExpression(current) ||
|
|
255
|
+
ts.isTypeAssertionExpression(current) ||
|
|
256
|
+
ts.isAsExpression(current) ||
|
|
257
|
+
ts.isSatisfiesExpression(current) ||
|
|
258
|
+
ts.isNonNullExpression(current)) {
|
|
259
|
+
current = current.expression;
|
|
260
|
+
}
|
|
261
|
+
return current;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Seeds a phase request into the single non-interactive Boss turn `slc` hands
|
|
265
|
+
* the runtime through `handleBossInput` (PHEXEC-29; DR-005).
|
|
266
|
+
*
|
|
267
|
+
* The settled SLC-to-runtime seeding contract: one Boss turn whose text states
|
|
268
|
+
* the request kind in prose — so any compiled playbook's judge-backed classifier
|
|
269
|
+
* can route it — and carries the full request as a single-line JSON object
|
|
270
|
+
* introduced by `Request: `, with workspace paths already resolved to absolute
|
|
271
|
+
* host paths, so a runtime (or a deterministic fixture) recovers the exact
|
|
272
|
+
* `PhaseInput` without host-specific parsing.
|
|
273
|
+
*/
|
|
274
|
+
export function seedPhaseTurn(input) {
|
|
275
|
+
const directive = input.kind === 'compile'
|
|
276
|
+
? 'Perform this compile phase non-interactively: transform the source into the target artifact, then stop.'
|
|
277
|
+
: 'Perform this link phase non-interactively: link the object artifacts against the link target into the linked artifact, then stop.';
|
|
278
|
+
return `${directive}\nRequest: ${JSON.stringify(input)}`;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Composes the host workspace contract a transformation-performing direct
|
|
282
|
+
* Captain call carries (PHEXEC-34; DR-005, DR-012).
|
|
283
|
+
*
|
|
284
|
+
* A linked `playbook` artifact composes host-agnostic Captain prompts — the
|
|
285
|
+
* GEARS-derived domain body plus typed runtime evidence — and is forbidden from
|
|
286
|
+
* appending workspace specifics, which only the host owns. Mirroring the
|
|
287
|
+
* interpreted agent contract (PHEXEC-14), the host therefore supplies the
|
|
288
|
+
* request's absolute workspace paths and write-scope rules at its transport
|
|
289
|
+
* boundary, so the acting Captain writes exactly the declared target instead
|
|
290
|
+
* of merely emitting the artifact in its reply. Routing-only Captain calls
|
|
291
|
+
* (explicitly empty `allowedTools`) and hidden judge calls never carry it.
|
|
292
|
+
*/
|
|
293
|
+
export function composeWorkspaceContract(input) {
|
|
294
|
+
const target = input.kind === 'compile' ? input.target : input.linked;
|
|
295
|
+
const inputs = input.kind === 'compile'
|
|
296
|
+
? [`source to read: ${input.source}`]
|
|
297
|
+
: [
|
|
298
|
+
`object artifacts to read, in order: ${input.objects.join(', ')}`,
|
|
299
|
+
`link target module: ${input.linkTarget}`,
|
|
300
|
+
`options: ${formatWorkspaceOptions(input.options)}`,
|
|
301
|
+
];
|
|
302
|
+
return [
|
|
303
|
+
'Host workspace (SubLang Compiler):',
|
|
304
|
+
'You are performing the work above non-interactively as one compiled phase of the SubLang Compiler (slc), against these workspace files:',
|
|
305
|
+
...inputs.map((line) => `- ${line}`),
|
|
306
|
+
`- artifact to write: ${target}`,
|
|
307
|
+
'',
|
|
308
|
+
'Workspace contract — you must:',
|
|
309
|
+
`- write only ${target}, creating or overwriting exactly that file; emitting the artifact solely in your reply does not produce it;`,
|
|
310
|
+
'- not edit the sources, the phase or link definition, specs, link targets, object artifacts, or any other file;',
|
|
311
|
+
'- not commit or otherwise touch version control;',
|
|
312
|
+
'- produce a complete artifact, not a sketch or placeholder;',
|
|
313
|
+
'- verify the produced artifact before finishing, then reply with a concise summary of what you produced and any ambiguity you resolved.',
|
|
314
|
+
].join('\n');
|
|
315
|
+
}
|
|
316
|
+
function formatWorkspaceOptions(options) {
|
|
317
|
+
const entries = Object.entries(options);
|
|
318
|
+
if (entries.length === 0)
|
|
319
|
+
return '(none)';
|
|
320
|
+
return entries.map(([name, value]) => `${name}=${value}`).join(', ');
|
|
321
|
+
}
|
|
322
|
+
//# sourceMappingURL=phase-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phase-runner.js","sourceRoot":"","sources":["../src/phase-runner.ts"],"names":[],"mappings":"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"}
|
package/dist/phase.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** A format token bound to its canonical file extension. */
|
|
2
|
+
export interface FormatDecl {
|
|
3
|
+
/** Short kebab-case language identifier, e.g. `text`, `gears`, `fsm`. */
|
|
4
|
+
format: string;
|
|
5
|
+
/** Canonical extension including the leading dot, e.g. `.md`, `.ts`. */
|
|
6
|
+
ext: string;
|
|
7
|
+
}
|
|
8
|
+
/** A loaded ordinary compile phase or format-preserving pass phase. */
|
|
9
|
+
export interface Phase {
|
|
10
|
+
/** Phase basename without the `.md` suffix, e.g. `text2gears` or `optimize`. */
|
|
11
|
+
name: string;
|
|
12
|
+
/** Declared source role. */
|
|
13
|
+
source: FormatDecl;
|
|
14
|
+
/** Declared target role. */
|
|
15
|
+
target: FormatDecl;
|
|
16
|
+
/**
|
|
17
|
+
* True for a format-preserving pass phase (source format equals target
|
|
18
|
+
* format), which sits outside the linear chain and runs only when a compile
|
|
19
|
+
* opts in (DR-013).
|
|
20
|
+
*/
|
|
21
|
+
pass: boolean;
|
|
22
|
+
}
|
|
23
|
+
/** Machine-readable reason a `## Formats` table was refused. */
|
|
24
|
+
export type FormatsErrorCode = 'missing-formats' | 'malformed-formats' | 'missing-role';
|
|
25
|
+
/** Machine-readable reason a phase was refused. */
|
|
26
|
+
export type PhaseErrorCode = FormatsErrorCode | 'filename-mismatch' | 'extension-conflict';
|
|
27
|
+
/** Raised when a phase definition is refused under DR-001. */
|
|
28
|
+
export declare class PhaseError extends Error {
|
|
29
|
+
readonly code: PhaseErrorCode;
|
|
30
|
+
constructor(code: PhaseErrorCode, message: string);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Reads the authoritative `## Formats` table into its source and target
|
|
34
|
+
* declarations (PIPE-1), shared by the phase and link loaders.
|
|
35
|
+
*
|
|
36
|
+
* Refusals are reported through `fail` so each loader can raise its own error
|
|
37
|
+
* type; `fail` must not return.
|
|
38
|
+
*/
|
|
39
|
+
export declare function readFormats(content: string, fail: (code: FormatsErrorCode, message: string) => never): {
|
|
40
|
+
source: FormatDecl;
|
|
41
|
+
target: FormatDecl;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Parses and validates a phase definition from its filename and content.
|
|
45
|
+
*
|
|
46
|
+
* @throws {PhaseError} when the `## Formats` table is missing or malformed, or
|
|
47
|
+
* when the filename does not match the declared tokens (PIPE-1, PIPE-2).
|
|
48
|
+
*/
|
|
49
|
+
export declare function parsePhase(file: {
|
|
50
|
+
name: string;
|
|
51
|
+
content: string;
|
|
52
|
+
}): Phase;
|
|
53
|
+
/** Reads a phase definition file from disk and parses it (PIPE-1, PIPE-2). */
|
|
54
|
+
export declare function loadPhaseFile(path: string): Promise<Phase>;
|
|
55
|
+
/**
|
|
56
|
+
* Refuses a set of phases that declare conflicting extensions for the same
|
|
57
|
+
* format token (PIPE-3).
|
|
58
|
+
*
|
|
59
|
+
* @throws {PhaseError} with code `extension-conflict` on the first conflict.
|
|
60
|
+
*/
|
|
61
|
+
export declare function checkExtensionConsistency(phases: readonly Phase[]): void;
|
|
62
|
+
//# sourceMappingURL=phase.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phase.d.ts","sourceRoot":"","sources":["../src/phase.ts"],"names":[],"mappings":"AAkBA,4DAA4D;AAC5D,MAAM,WAAW,UAAU;IACzB,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,GAAG,EAAE,MAAM,CAAC;CACb;AAED,uEAAuE;AACvE,MAAM,WAAW,KAAK;IACpB,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,MAAM,EAAE,UAAU,CAAC;IACnB,4BAA4B;IAC5B,MAAM,EAAE,UAAU,CAAC;IACnB;;;;OAIG;IACH,IAAI,EAAE,OAAO,CAAC;CACf;AAED,gEAAgE;AAChE,MAAM,MAAM,gBAAgB,GACxB,iBAAiB,GACjB,mBAAmB,GACnB,cAAc,CAAC;AAEnB,mDAAmD;AACnD,MAAM,MAAM,cAAc,GACtB,gBAAgB,GAChB,mBAAmB,GACnB,oBAAoB,CAAC;AAEzB,8DAA8D;AAC9D,qBAAa,UAAW,SAAQ,KAAK;IACnC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;gBAElB,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM;CAKlD;AAKD;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,CAAC,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,KAAK,KAAK,GACvD;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAAE,CAwC5C;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,KAAK,CAmBzE;AAED,8EAA8E;AAC9E,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAGhE;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,GAAG,IAAI,CAgBxE"}
|
package/dist/phase.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
|
|
3
|
+
/**
|
|
4
|
+
* Phase model and `## Formats` parsing (DR-001).
|
|
5
|
+
*
|
|
6
|
+
* Implements PIPE-1 (read the authoritative `## Formats` table), PIPE-2 (refuse
|
|
7
|
+
* a phase whose `<source-format>2<target-format>.md` filename does not match its
|
|
8
|
+
* table), and PIPE-3 (refuse phases declaring conflicting extensions for the
|
|
9
|
+
* same format token). The `## Formats` reader is shared with the link loader.
|
|
10
|
+
* See specs/dev/pipeline.md.
|
|
11
|
+
*/
|
|
12
|
+
import { readFile } from 'node:fs/promises';
|
|
13
|
+
import { basename } from 'node:path';
|
|
14
|
+
import { findSection, parseTable } from './markdown.js';
|
|
15
|
+
/** Raised when a phase definition is refused under DR-001. */
|
|
16
|
+
export class PhaseError extends Error {
|
|
17
|
+
code;
|
|
18
|
+
constructor(code, message) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = 'PhaseError';
|
|
21
|
+
this.code = code;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const FORMAT_TOKEN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
25
|
+
const EXTENSION = /^\.[A-Za-z0-9]+$/;
|
|
26
|
+
/**
|
|
27
|
+
* Reads the authoritative `## Formats` table into its source and target
|
|
28
|
+
* declarations (PIPE-1), shared by the phase and link loaders.
|
|
29
|
+
*
|
|
30
|
+
* Refusals are reported through `fail` so each loader can raise its own error
|
|
31
|
+
* type; `fail` must not return.
|
|
32
|
+
*/
|
|
33
|
+
export function readFormats(content, fail) {
|
|
34
|
+
const section = findSection(content, 'Formats');
|
|
35
|
+
if (section === null) {
|
|
36
|
+
fail('missing-formats', 'missing a ## Formats section');
|
|
37
|
+
}
|
|
38
|
+
const decls = new Map();
|
|
39
|
+
for (const cells of parseTable(section)) {
|
|
40
|
+
if (cells.length < 3)
|
|
41
|
+
continue;
|
|
42
|
+
const role = cells[0].toLowerCase();
|
|
43
|
+
if (role !== 'source' && role !== 'target')
|
|
44
|
+
continue;
|
|
45
|
+
const [, format, ext] = cells;
|
|
46
|
+
if (!FORMAT_TOKEN.test(format)) {
|
|
47
|
+
fail('malformed-formats', `${role} format token "${format}" is not a kebab-case identifier`);
|
|
48
|
+
}
|
|
49
|
+
if (!EXTENSION.test(ext)) {
|
|
50
|
+
fail('malformed-formats', `${role} extension "${ext}" is not a canonical extension (e.g. ".md")`);
|
|
51
|
+
}
|
|
52
|
+
if (decls.has(role)) {
|
|
53
|
+
fail('malformed-formats', `## Formats declares ${role} more than once`);
|
|
54
|
+
}
|
|
55
|
+
decls.set(role, { format, ext });
|
|
56
|
+
}
|
|
57
|
+
const source = decls.get('source');
|
|
58
|
+
const target = decls.get('target');
|
|
59
|
+
if (source === undefined || target === undefined) {
|
|
60
|
+
fail('missing-role', `## Formats is missing a ${source === undefined ? 'source' : 'target'} row`);
|
|
61
|
+
}
|
|
62
|
+
return { source, target };
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Parses and validates a phase definition from its filename and content.
|
|
66
|
+
*
|
|
67
|
+
* @throws {PhaseError} when the `## Formats` table is missing or malformed, or
|
|
68
|
+
* when the filename does not match the declared tokens (PIPE-1, PIPE-2).
|
|
69
|
+
*/
|
|
70
|
+
export function parsePhase(file) {
|
|
71
|
+
const { source, target } = readFormats(file.content, (code, message) => {
|
|
72
|
+
throw new PhaseError(code, message);
|
|
73
|
+
});
|
|
74
|
+
// A format-preserving pass phase is named freely — its filename is the pass
|
|
75
|
+
// name (DR-013); the `<source>2<target>.md` rule identifies transform phases.
|
|
76
|
+
const pass = source.format === target.format;
|
|
77
|
+
if (!pass) {
|
|
78
|
+
const expected = `${source.format}2${target.format}.md`;
|
|
79
|
+
if (file.name !== expected) {
|
|
80
|
+
throw new PhaseError('filename-mismatch', `phase filename "${file.name}" does not match its ## Formats tokens (expected "${expected}")`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return { name: file.name.slice(0, -'.md'.length), source, target, pass };
|
|
84
|
+
}
|
|
85
|
+
/** Reads a phase definition file from disk and parses it (PIPE-1, PIPE-2). */
|
|
86
|
+
export async function loadPhaseFile(path) {
|
|
87
|
+
const content = await readFile(path, 'utf8');
|
|
88
|
+
return parsePhase({ name: basename(path), content });
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Refuses a set of phases that declare conflicting extensions for the same
|
|
92
|
+
* format token (PIPE-3).
|
|
93
|
+
*
|
|
94
|
+
* @throws {PhaseError} with code `extension-conflict` on the first conflict.
|
|
95
|
+
*/
|
|
96
|
+
export function checkExtensionConsistency(phases) {
|
|
97
|
+
const seen = new Map();
|
|
98
|
+
for (const phase of phases) {
|
|
99
|
+
for (const decl of [phase.source, phase.target]) {
|
|
100
|
+
const prior = seen.get(decl.format);
|
|
101
|
+
if (prior === undefined) {
|
|
102
|
+
seen.set(decl.format, { ext: decl.ext, phase: phase.name });
|
|
103
|
+
}
|
|
104
|
+
else if (prior.ext !== decl.ext) {
|
|
105
|
+
throw new PhaseError('extension-conflict', `format "${decl.format}" declares conflicting extensions: ` +
|
|
106
|
+
`"${prior.ext}" (in ${prior.phase}) vs "${decl.ext}" (in ${phase.name})`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=phase.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phase.js","sourceRoot":"","sources":["../src/phase.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PinRecord } from './pins.js';
|
|
2
|
+
/** Extracts the inline-code paths cited by a `## Pin Inputs` section (DR-007). */
|
|
3
|
+
export declare function parsePinInputs(content: string): string[];
|
|
4
|
+
/**
|
|
5
|
+
* Derives the semantic-input closure of `definitionPath` as a set of resolved
|
|
6
|
+
* absolute paths: the definition plus every transitively cited local file
|
|
7
|
+
* (PIN-2, PIN-4).
|
|
8
|
+
*
|
|
9
|
+
* @throws {import('./pins.js').PinError} when a cited path is absolute or escapes
|
|
10
|
+
* the recorded boundary (PIN-5).
|
|
11
|
+
*/
|
|
12
|
+
export declare function deriveClosure(pipelineDir: string, boundary: string, definitionPath: string): Promise<Set<string>>;
|
|
13
|
+
/**
|
|
14
|
+
* Reports whether the closure derived from `record.definition` matches the
|
|
15
|
+
* recorded definition plus semanticInputs, compared as sets of resolved paths
|
|
16
|
+
* (PIN-4).
|
|
17
|
+
*/
|
|
18
|
+
export declare function closureMatchesRecord(pipelineDir: string, boundary: string, record: PinRecord): Promise<boolean>;
|
|
19
|
+
//# sourceMappingURL=pin-closure.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pin-closure.d.ts","sourceRoot":"","sources":["../src/pin-closure.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAK3C,kFAAkF;AAClF,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAYxD;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAoCtB;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,SAAS,GAChB,OAAO,CAAC,OAAO,CAAC,CAqBlB"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
|
|
3
|
+
/**
|
|
4
|
+
* Semantic-input closure derivation for the pin-currency validator (PIN-2, PIN-4;
|
|
5
|
+
* DR-007).
|
|
6
|
+
*
|
|
7
|
+
* A pinned phase's semantic-input closure is the definition file plus every local
|
|
8
|
+
* file transitively cited by a `## Pin Inputs` section. A `## Pin Inputs` section
|
|
9
|
+
* cites each input as an inline-code path (for example `` `reference/gears.md` ``)
|
|
10
|
+
* expressed relative to the pipeline directory — the same coordinate system as
|
|
11
|
+
* the recorded pin paths. Derivation recurses only into Markdown inputs that
|
|
12
|
+
* declare their own `## Pin Inputs`; a non-Markdown input, or a Markdown input
|
|
13
|
+
* without the section, is a closure member but terminates the transitive walk.
|
|
14
|
+
* The derived closure is compared — as a set of resolved paths — to the recorded
|
|
15
|
+
* definition plus semanticInputs; any difference is a stale verdict (PIN-4). See
|
|
16
|
+
* specs/dev/pinning.md.
|
|
17
|
+
*/
|
|
18
|
+
import { readFile } from 'node:fs/promises';
|
|
19
|
+
import { findSection } from './markdown.js';
|
|
20
|
+
import { resolvePinPath } from './pin-paths.js';
|
|
21
|
+
const PIN_INPUT_FIELD = 'pin input path';
|
|
22
|
+
const INLINE_CODE = /`([^`]+)`/g;
|
|
23
|
+
/** Extracts the inline-code paths cited by a `## Pin Inputs` section (DR-007). */
|
|
24
|
+
export function parsePinInputs(content) {
|
|
25
|
+
const lines = findSection(content, 'Pin Inputs');
|
|
26
|
+
if (lines === null) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
const paths = [];
|
|
30
|
+
for (const line of lines) {
|
|
31
|
+
for (const match of line.matchAll(INLINE_CODE)) {
|
|
32
|
+
paths.push(match[1].trim());
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return paths;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Derives the semantic-input closure of `definitionPath` as a set of resolved
|
|
39
|
+
* absolute paths: the definition plus every transitively cited local file
|
|
40
|
+
* (PIN-2, PIN-4).
|
|
41
|
+
*
|
|
42
|
+
* @throws {import('./pins.js').PinError} when a cited path is absolute or escapes
|
|
43
|
+
* the recorded boundary (PIN-5).
|
|
44
|
+
*/
|
|
45
|
+
export async function deriveClosure(pipelineDir, boundary, definitionPath) {
|
|
46
|
+
const closure = new Set();
|
|
47
|
+
const seen = new Set();
|
|
48
|
+
const queue = [definitionPath];
|
|
49
|
+
while (queue.length > 0) {
|
|
50
|
+
const rel = queue.shift();
|
|
51
|
+
if (seen.has(rel)) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
seen.add(rel);
|
|
55
|
+
const resolved = resolvePinPath(pipelineDir, boundary, rel, PIN_INPUT_FIELD);
|
|
56
|
+
closure.add(resolved);
|
|
57
|
+
// Recurse only into Markdown inputs that declare their own ## Pin Inputs;
|
|
58
|
+
// non-Markdown and sectionless inputs are members but terminate the walk.
|
|
59
|
+
if (!isMarkdown(rel)) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const content = await readIfPresent(resolved);
|
|
63
|
+
if (content === null) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
for (const cited of parsePinInputs(content)) {
|
|
67
|
+
if (!seen.has(cited)) {
|
|
68
|
+
queue.push(cited);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return closure;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Reports whether the closure derived from `record.definition` matches the
|
|
76
|
+
* recorded definition plus semanticInputs, compared as sets of resolved paths
|
|
77
|
+
* (PIN-4).
|
|
78
|
+
*/
|
|
79
|
+
export async function closureMatchesRecord(pipelineDir, boundary, record) {
|
|
80
|
+
const derived = await deriveClosure(pipelineDir, boundary, record.definition.path);
|
|
81
|
+
const recorded = new Set();
|
|
82
|
+
recorded.add(resolvePinPath(pipelineDir, boundary, record.definition.path, 'definition.path'));
|
|
83
|
+
for (const input of record.semanticInputs) {
|
|
84
|
+
recorded.add(resolvePinPath(pipelineDir, boundary, input.path, 'semanticInputs.path'));
|
|
85
|
+
}
|
|
86
|
+
return setsEqual(derived, recorded);
|
|
87
|
+
}
|
|
88
|
+
function isMarkdown(path) {
|
|
89
|
+
return path.toLowerCase().endsWith('.md');
|
|
90
|
+
}
|
|
91
|
+
async function readIfPresent(absolutePath) {
|
|
92
|
+
try {
|
|
93
|
+
return await readFile(absolutePath, 'utf8');
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function setsEqual(a, b) {
|
|
100
|
+
if (a.size !== b.size) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
for (const value of a) {
|
|
104
|
+
if (!b.has(value)) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=pin-closure.js.map
|