@usine/config 0.0.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +50 -2
  2. package/dist/agent.d.ts +23 -0
  3. package/dist/agent.d.ts.map +1 -0
  4. package/dist/agent.js +22 -0
  5. package/dist/agent.js.map +1 -0
  6. package/dist/board.d.ts +31 -0
  7. package/dist/board.d.ts.map +1 -0
  8. package/dist/board.js +32 -0
  9. package/dist/board.js.map +1 -0
  10. package/dist/checks.d.ts +3 -0
  11. package/dist/checks.d.ts.map +1 -0
  12. package/dist/checks.js +3 -0
  13. package/dist/checks.js.map +1 -0
  14. package/dist/factory.d.ts +86 -0
  15. package/dist/factory.d.ts.map +1 -0
  16. package/dist/factory.js +87 -0
  17. package/dist/factory.js.map +1 -0
  18. package/dist/file.d.ts +27 -0
  19. package/dist/file.d.ts.map +1 -0
  20. package/dist/file.js +29 -0
  21. package/dist/file.js.map +1 -0
  22. package/dist/frontmatter.d.ts +10 -0
  23. package/dist/frontmatter.d.ts.map +1 -0
  24. package/dist/frontmatter.js +51 -0
  25. package/dist/frontmatter.js.map +1 -0
  26. package/dist/instance/diagnostics.d.ts +19 -0
  27. package/dist/instance/diagnostics.d.ts.map +1 -0
  28. package/dist/instance/diagnostics.js +22 -0
  29. package/dist/instance/diagnostics.js.map +1 -0
  30. package/dist/instance/instance.d.ts +17 -0
  31. package/dist/instance/instance.d.ts.map +1 -0
  32. package/dist/instance/instance.js +21 -0
  33. package/dist/instance/instance.js.map +1 -0
  34. package/dist/instance/references.d.ts +21 -0
  35. package/dist/instance/references.d.ts.map +1 -0
  36. package/dist/instance/references.js +38 -0
  37. package/dist/instance/references.js.map +1 -0
  38. package/dist/instance-folder.d.ts +14 -0
  39. package/dist/instance-folder.d.ts.map +1 -0
  40. package/dist/instance-folder.js +101 -0
  41. package/dist/instance-folder.js.map +1 -0
  42. package/dist/label.d.ts +22 -0
  43. package/dist/label.d.ts.map +1 -0
  44. package/dist/label.js +25 -0
  45. package/dist/label.js.map +1 -0
  46. package/dist/loop.d.ts +42 -0
  47. package/dist/loop.d.ts.map +1 -0
  48. package/dist/loop.js +36 -0
  49. package/dist/loop.js.map +1 -0
  50. package/dist/parser.d.ts +25 -0
  51. package/dist/parser.d.ts.map +1 -0
  52. package/dist/parser.js +204 -0
  53. package/dist/parser.js.map +1 -0
  54. package/dist/payload-slices.d.ts +17 -0
  55. package/dist/payload-slices.d.ts.map +1 -0
  56. package/dist/payload-slices.js +218 -0
  57. package/dist/payload-slices.js.map +1 -0
  58. package/dist/selector.d.ts +27 -0
  59. package/dist/selector.d.ts.map +1 -0
  60. package/dist/selector.js +29 -0
  61. package/dist/selector.js.map +1 -0
  62. package/dist/trigger.d.ts +37 -0
  63. package/dist/trigger.d.ts.map +1 -0
  64. package/dist/trigger.js +34 -0
  65. package/dist/trigger.js.map +1 -0
  66. package/dist/yaml/document.d.ts +9 -0
  67. package/dist/yaml/document.d.ts.map +1 -0
  68. package/dist/yaml/document.js +38 -0
  69. package/dist/yaml/document.js.map +1 -0
  70. package/dist/yaml/writer.d.ts +16 -0
  71. package/dist/yaml/writer.d.ts.map +1 -0
  72. package/dist/yaml/writer.js +33 -0
  73. package/dist/yaml/writer.js.map +1 -0
  74. package/package.json +64 -5
  75. package/dist/index.d.ts +0 -10
  76. package/dist/index.d.ts.map +0 -1
  77. package/dist/index.js +0 -10
  78. package/dist/index.js.map +0 -1
@@ -0,0 +1,21 @@
1
+ import { Option, Schema } from "effect";
2
+ import { UsineConfig } from "../factory.js";
3
+ import { LoopFile, QuarantinedLoop, loopPath } from "../loop.js";
4
+ import { AgentFile, InvalidAgent } from "../agent.js";
5
+ import { ConfigWarning } from "./diagnostics.js";
6
+ import { referenceIndex, loopReferenceFailure } from "./references.js";
7
+ /** One consistent snapshot of an instance folder. Construction fails if a loop references a name the instance does not have. */
8
+ export class LoadedInstance extends Schema.Class("@usine/config/LoadedInstance")(Schema.Struct({
9
+ config: UsineConfig,
10
+ agents: Schema.Array(AgentFile),
11
+ invalidAgents: Schema.Array(InvalidAgent),
12
+ loops: Schema.Array(LoopFile),
13
+ quarantinedLoops: Schema.Array(QuarantinedLoop),
14
+ warnings: Schema.Array(ConfigWarning),
15
+ }).check(Schema.makeFilter(({ agents, config, loops }) => {
16
+ const references = referenceIndex(config, agents);
17
+ return loops.every((loop) => Option.isNone(loopReferenceFailure(loopPath(loop.name), loop, references)));
18
+ }, { message: "Expected every loop to reference the loaded roster and registry" }))) {
19
+ }
20
+ export { UndeclaredLaneLabelWarning, UndeclaredTriggerLabelWarning, ConfigWarning, } from "./diagnostics.js";
21
+ //# sourceMappingURL=instance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instance.js","sourceRoot":"","sources":["../../src/instance/instance.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEvE,gIAAgI;AAChI,MAAM,OAAO,cAAe,SAAQ,MAAM,CAAC,KAAK,CAAiB,8BAA8B,CAAC,CAC9F,MAAM,CAAC,MAAM,CAAC;IACZ,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC;IAC/B,aAAa,EAAE,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC;IACzC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC;IAC7B,gBAAgB,EAAE,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC;IAC/C,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC;CACtC,CAAC,CAAC,KAAK,CACN,MAAM,CAAC,UAAU,CACf,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE;IAC5B,MAAM,UAAU,GAAG,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClD,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAC1B,MAAM,CAAC,MAAM,CAAC,oBAAoB,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAC3E,CAAC;AACJ,CAAC,EACD,EAAE,OAAO,EAAE,iEAAiE,EAAE,CAC/E,CACF,CACF;CAAG;AAEJ,OAAO,EACL,0BAA0B,EAC1B,6BAA6B,EAC7B,aAAa,GACd,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { AgentName } from "@usine/core/agent";
2
+ import { RepositoryName } from "@usine/core/repository";
3
+ import { Option } from "effect";
4
+ import { InstancePath, ConfigFileError } from "../file.ts";
5
+ import { RegistryTag, UsineConfig } from "../factory.ts";
6
+ import { LoopFile } from "../loop.ts";
7
+ import type { AgentFile } from "../agent.ts";
8
+ /** The agent and selector names a loop may reference in one loaded instance. */
9
+ export interface ReferenceIndex {
10
+ readonly agentNames: ReadonlySet<AgentName>;
11
+ readonly registryTags: ReadonlySet<RegistryTag>;
12
+ readonly repoNames: ReadonlySet<RepositoryName>;
13
+ }
14
+ export declare const referenceIndex: (config: UsineConfig, agents: ReadonlyArray<AgentFile>) => ReferenceIndex;
15
+ /**
16
+ * Returns the reference error that quarantines this loop, if it names an agent
17
+ * or selector entry the instance does not have. The folder loader reports it
18
+ * per file and LoadedInstance refuses to construct with it.
19
+ */
20
+ export declare const loopReferenceFailure: (file: InstancePath, loop: LoopFile, references: ReferenceIndex) => Option.Option<ConfigFileError>;
21
+ //# sourceMappingURL=references.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"references.d.ts","sourceRoot":"","sources":["../../src/instance/references.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,gFAAgF;AAChF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;IAC5C,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC,WAAW,CAAC,CAAC;IAChD,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,cAAc,CAAC,CAAC;CACjD;AAED,eAAO,MAAM,cAAc,WACjB,WAAW,UACX,aAAa,CAAC,SAAS,CAAC,KAC/B,cAID,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,SACzB,YAAY,QACZ,QAAQ,cACF,cAAc,KACzB,MAAM,CAAC,MAAM,CAAC,eAAe,CA4B/B,CAAC"}
@@ -0,0 +1,38 @@
1
+ import { AgentName } from "@usine/core/agent";
2
+ import { RepositoryName } from "@usine/core/repository";
3
+ import { Option } from "effect";
4
+ import { InstancePath, ConfigFileError } from "../file.js";
5
+ import { RegistryTag, UsineConfig } from "../factory.js";
6
+ import { LoopFile } from "../loop.js";
7
+ export const referenceIndex = (config, agents) => ({
8
+ agentNames: new Set(agents.map(({ name }) => name)),
9
+ registryTags: new Set(config.repos.flatMap(({ tags }) => tags)),
10
+ repoNames: new Set(config.repos.map(({ name }) => name)),
11
+ });
12
+ /**
13
+ * Returns the reference error that quarantines this loop, if it names an agent
14
+ * or selector entry the instance does not have. The folder loader reports it
15
+ * per file and LoadedInstance refuses to construct with it.
16
+ */
17
+ export const loopReferenceFailure = (file, loop, references) => {
18
+ const referenceError = (message) => Option.some(new ConfigFileError({ file, stage: "reference", expression: Option.none(), message }));
19
+ if (!references.agentNames.has(loop.agent)) {
20
+ return referenceError(`Unknown agent: ${loop.agent}`);
21
+ }
22
+ if (Option.isNone(loop.selector))
23
+ return Option.none();
24
+ if (Option.isSome(loop.selector.value.repos)) {
25
+ const missing = loop.selector.value.repos.value.filter((name) => !references.repoNames.has(name));
26
+ if (missing.length > 0) {
27
+ return referenceError(`Unknown selector repos: ${missing.join(", ")}`);
28
+ }
29
+ }
30
+ if (Option.isSome(loop.selector.value.tags)) {
31
+ const missing = loop.selector.value.tags.value.filter((tag) => !references.registryTags.has(tag));
32
+ if (missing.length > 0) {
33
+ return referenceError(`Unknown selector tags: ${missing.join(", ")}`);
34
+ }
35
+ }
36
+ return Option.none();
37
+ };
38
+ //# sourceMappingURL=references.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"references.js","sourceRoot":"","sources":["../../src/instance/references.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAUtC,MAAM,CAAC,MAAM,cAAc,GAAG,CAC5B,MAAmB,EACnB,MAAgC,EAChB,EAAE,CAAC,CAAC;IACpB,UAAU,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;IACnD,YAAY,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;IAC/D,SAAS,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;CACzD,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAClC,IAAkB,EAClB,IAAc,EACd,UAA0B,EACM,EAAE;IAClC,MAAM,cAAc,GAAG,CAAC,OAAe,EAAkC,EAAE,CACzE,MAAM,CAAC,IAAI,CACT,IAAI,eAAe,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,CACtF,CAAC;IACJ,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3C,OAAO,cAAc,CAAC,kBAAkB,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IACxD,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC;QAAE,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;IACvD,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CACpD,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAC1C,CAAC;QACF,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,OAAO,cAAc,CAAC,2BAA2B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACzE,CAAC;IACH,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5C,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CACnD,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAC3C,CAAC;QACF,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,OAAO,cAAc,CAAC,0BAA0B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;AACvB,CAAC,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { InstanceDirectory } from "@usine/core/instance";
2
+ import { Context, Effect, FileSystem, Layer, Path } from "effect";
3
+ import { FactoryConfigError } from "./file.ts";
4
+ import { LoadedInstance } from "./instance/instance.ts";
5
+ import { ConfigParser } from "./parser.ts";
6
+ declare const InstanceFolder_base: Context.ServiceClass<InstanceFolder, "@usine/config/InstanceFolder", {
7
+ readonly load: (root: InstanceDirectory) => Effect.Effect<LoadedInstance, FactoryConfigError>;
8
+ }>;
9
+ /** Loads an instance folder; an invalid `usine.yaml` fails the load, an invalid loop is quarantined and its siblings still load. */
10
+ export declare class InstanceFolder extends InstanceFolder_base {
11
+ static readonly layer: Layer.Layer<InstanceFolder, never, ConfigParser | FileSystem.FileSystem | Path.Path>;
12
+ }
13
+ export {};
14
+ //# sourceMappingURL=instance-folder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instance-folder.d.ts","sourceRoot":"","sources":["../src/instance-folder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAU,IAAI,EAAU,MAAM,QAAQ,CAAC;AAElF,OAAO,EAAmB,kBAAkB,EAAgB,MAAM,WAAW,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAGxD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;;mBAoBxB,CAAC,IAAI,EAAE,iBAAiB,KAAK,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,kBAAkB,CAAC;;AAJjG,oIAAoI;AACpI,qBAAa,cAAe,SAAQ,mBAKD;IACjC,MAAM,CAAC,QAAQ,CAAC,KAAK,uFA0JnB;CACH"}
@@ -0,0 +1,101 @@
1
+ import { InstanceDirectory } from "@usine/core/instance";
2
+ import { Context, Effect, FileSystem, Layer, Option, Path, Result } from "effect";
3
+ import { InvalidAgent } from "./agent.js";
4
+ import { ConfigFileError, FactoryConfigError, InstancePath } from "./file.js";
5
+ import { LoadedInstance } from "./instance/instance.js";
6
+ import { QuarantinedLoop } from "./loop.js";
7
+ import { loopReferenceFailure, referenceIndex } from "./instance/references.js";
8
+ import { ConfigParser } from "./parser.js";
9
+ const FILE_CONCURRENCY = 16;
10
+ const factoryError = (error) => new FactoryConfigError({
11
+ file: error.file,
12
+ stage: error.stage,
13
+ message: error.message,
14
+ });
15
+ /** Loads an instance folder; an invalid `usine.yaml` fails the load, an invalid loop is quarantined and its siblings still load. */
16
+ export class InstanceFolder extends Context.Service()("@usine/config/InstanceFolder") {
17
+ static layer = Layer.effect(InstanceFolder, Effect.gen(function* () {
18
+ const fileSystem = yield* FileSystem.FileSystem;
19
+ const path = yield* Path.Path;
20
+ const parser = yield* ConfigParser;
21
+ const readFile = Effect.fn("InstanceFolder.readFile")((absolutePath, file) => fileSystem.readFileString(absolutePath).pipe(Effect.mapError((error) => new ConfigFileError({
22
+ file,
23
+ stage: "read",
24
+ expression: Option.none(),
25
+ message: error.message,
26
+ }))));
27
+ const markdownFiles = Effect.fn("InstanceFolder.markdownFiles")((root, directory) => fileSystem.readDirectory(path.join(root, directory)).pipe(Effect.map((files) => files.filter((file) => file.endsWith(".md")).sort()), Effect.mapError((error) => new FactoryConfigError({
28
+ file: InstancePath.make(directory),
29
+ stage: "read",
30
+ message: error.message,
31
+ }))));
32
+ const loadMarkdownFiles = (root, directory, parseFile) => Effect.gen(function* () {
33
+ const files = yield* markdownFiles(root, directory);
34
+ return yield* Effect.forEach(files, (name) => {
35
+ const file = InstancePath.make(`${directory}/${name}`);
36
+ return readFile(path.join(root, file), file).pipe(Effect.flatMap((source) => parseFile(file, source)), Effect.result, Effect.map((result) => ({ file, result })));
37
+ }, { concurrency: FILE_CONCURRENCY });
38
+ });
39
+ return {
40
+ load: Effect.fn("InstanceFolder.load")((root) => Effect.gen(function* () {
41
+ const configFile = InstancePath.make("usine.yaml");
42
+ const configSource = yield* readFile(path.join(root, configFile), configFile).pipe(Effect.mapError(factoryError));
43
+ const config = yield* parser
44
+ .parseUsineYaml(configFile, configSource)
45
+ .pipe(Effect.mapError(factoryError));
46
+ const parsed = yield* Effect.all({
47
+ agents: loadMarkdownFiles(root, "agents", parser.parseAgentFile),
48
+ loops: loadMarkdownFiles(root, "loops", parser.parseLoopFile),
49
+ }, { concurrency: 2 });
50
+ const agents = [];
51
+ const invalidAgents = [];
52
+ for (const entry of parsed.agents) {
53
+ if (Result.isSuccess(entry.result))
54
+ agents.push(entry.result.success);
55
+ else {
56
+ invalidAgents.push(new InvalidAgent({ file: entry.file, error: entry.result.failure }));
57
+ }
58
+ }
59
+ const references = referenceIndex(config, agents);
60
+ const loops = [];
61
+ const quarantinedLoops = [];
62
+ for (const entry of parsed.loops) {
63
+ if (Result.isFailure(entry.result)) {
64
+ quarantinedLoops.push(new QuarantinedLoop({ file: entry.file, error: entry.result.failure }));
65
+ continue;
66
+ }
67
+ const referenceFailure = loopReferenceFailure(entry.file, entry.result.success, references);
68
+ if (Option.isSome(referenceFailure)) {
69
+ quarantinedLoops.push(new QuarantinedLoop({ file: entry.file, error: referenceFailure.value }));
70
+ }
71
+ else
72
+ loops.push(entry.result.success);
73
+ }
74
+ const warnings = yield* parser.configWarnings(config, loops).pipe(Effect.catch((error) => Effect.logError("Config warning analysis failed; loaded the valid instance without warnings", {
75
+ errorTag: error._tag,
76
+ expression: error.source,
77
+ message: error.message,
78
+ }).pipe(Effect.as([]))));
79
+ yield* Effect.forEach(warnings, (warning) => warning._tag === "UndeclaredLaneLabelWarning"
80
+ ? Effect.logWarning("Board Lane references an undeclared GitHub label", {
81
+ repository: warning.repository,
82
+ lane: warning.lane,
83
+ label: warning.label,
84
+ })
85
+ : Effect.logWarning("Loop trigger references an undeclared GitHub label", {
86
+ loop: warning.loop,
87
+ label: warning.label,
88
+ }), { discard: true });
89
+ return new LoadedInstance({
90
+ config,
91
+ agents,
92
+ invalidAgents,
93
+ loops,
94
+ quarantinedLoops,
95
+ warnings,
96
+ });
97
+ })),
98
+ };
99
+ }));
100
+ }
101
+ //# sourceMappingURL=instance-folder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instance-folder.js","sourceRoot":"","sources":["../src/instance-folder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { Schema } from "effect";
2
+ /**
3
+ * GitHub trims label names and caps them at 50 characters. A name outside that
4
+ * contract never matches the catalog and would be re-created every cycle.
5
+ */
6
+ export declare const GitHubLabelName: Schema.brand<Schema.String, "GitHubLabelName">;
7
+ export type GitHubLabelName = typeof GitHubLabelName.Type;
8
+ /** Compare label names on this key: GitHub matches them case-insensitively. */
9
+ export declare const canonicalLabelName: (name: string) => string;
10
+ /** Six hex digits without a leading hash, as GitHub stores label colors. */
11
+ export declare const GitHubLabelColor: Schema.brand<Schema.String, "GitHubLabelColor">;
12
+ export type GitHubLabelColor = typeof GitHubLabelColor.Type;
13
+ declare const DeclaredLabel_base: Schema.Class<DeclaredLabel, Schema.Struct<{
14
+ readonly name: Schema.brand<Schema.String, "GitHubLabelName">;
15
+ readonly color: Schema.OptionFromOptionalKey<Schema.brand<Schema.String, "GitHubLabelColor">>;
16
+ readonly description: Schema.OptionFromOptionalKey<Schema.String>;
17
+ }>, {}>;
18
+ /** A label the factory creates in every managed repository. */
19
+ export declare class DeclaredLabel extends DeclaredLabel_base {
20
+ }
21
+ export {};
22
+ //# sourceMappingURL=label.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"label.d.ts","sourceRoot":"","sources":["../src/label.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC;;;GAGG;AACH,eAAO,MAAM,eAAe,gDAKW,CAAC;AAExC,MAAM,MAAM,eAAe,GAAG,OAAO,eAAe,CAAC,IAAI,CAAC;AAE1D,+EAA+E;AAC/E,eAAO,MAAM,kBAAkB,SAAU,MAAM,KAAG,MAA4B,CAAC;AAE/E,4EAA4E;AAC5E,eAAO,MAAM,gBAAgB,iDAIW,CAAC;AAEzC,MAAM,MAAM,gBAAgB,GAAG,OAAO,gBAAgB,CAAC,IAAI,CAAC;;;;;;AAQ5D,+DAA+D;AAC/D,qBAAa,aAAc,SAAQ,kBAIjC;CAAG"}
package/dist/label.js ADDED
@@ -0,0 +1,25 @@
1
+ import { Schema } from "effect";
2
+ /**
3
+ * GitHub trims label names and caps them at 50 characters. A name outside that
4
+ * contract never matches the catalog and would be re-created every cycle.
5
+ */
6
+ export const GitHubLabelName = Schema.String.check(Schema.isPattern(/^\S(?:.*\S)?$/u, {
7
+ message: "Expected a GitHub label name without surrounding whitespace",
8
+ }), Schema.isMaxLength(50, { message: "Expected a GitHub label name of at most 50 characters" })).pipe(Schema.brand("GitHubLabelName"));
9
+ /** Compare label names on this key: GitHub matches them case-insensitively. */
10
+ export const canonicalLabelName = (name) => name.toLowerCase();
11
+ /** Six hex digits without a leading hash, as GitHub stores label colors. */
12
+ export const GitHubLabelColor = Schema.String.check(Schema.isPattern(/^[0-9a-fA-F]{6}$/u, {
13
+ message: "Expected a six-digit hex GitHub label color without #",
14
+ })).pipe(Schema.brand("GitHubLabelColor"));
15
+ const GitHubLabelDescription = Schema.String.check(Schema.isMaxLength(100, {
16
+ message: "Expected a GitHub label description of at most 100 characters",
17
+ }));
18
+ /** A label the factory creates in every managed repository. */
19
+ export class DeclaredLabel extends Schema.Class("@usine/config/DeclaredLabel")({
20
+ name: GitHubLabelName,
21
+ color: Schema.OptionFromOptionalKey(GitHubLabelColor),
22
+ description: Schema.OptionFromOptionalKey(GitHubLabelDescription),
23
+ }) {
24
+ }
25
+ //# sourceMappingURL=label.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"label.js","sourceRoot":"","sources":["../src/label.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAChD,MAAM,CAAC,SAAS,CAAC,gBAAgB,EAAE;IACjC,OAAO,EAAE,6DAA6D;CACvE,CAAC,EACF,MAAM,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,uDAAuD,EAAE,CAAC,CAC7F,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC;AAIxC,+EAA+E;AAC/E,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;AAE/E,4EAA4E;AAC5E,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CACjD,MAAM,CAAC,SAAS,CAAC,mBAAmB,EAAE;IACpC,OAAO,EAAE,uDAAuD;CACjE,CAAC,CACH,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC,CAAC;AAIzC,MAAM,sBAAsB,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAChD,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE;IACtB,OAAO,EAAE,+DAA+D;CACzE,CAAC,CACH,CAAC;AAEF,+DAA+D;AAC/D,MAAM,OAAO,aAAc,SAAQ,MAAM,CAAC,KAAK,CAAgB,6BAA6B,CAAC,CAAC;IAC5F,IAAI,EAAE,eAAe;IACrB,KAAK,EAAE,MAAM,CAAC,qBAAqB,CAAC,gBAAgB,CAAC;IACrD,WAAW,EAAE,MAAM,CAAC,qBAAqB,CAAC,sBAAsB,CAAC;CAClE,CAAC;CAAG"}
package/dist/loop.d.ts ADDED
@@ -0,0 +1,42 @@
1
+ import { LoopName } from "@usine/core/loop";
2
+ import { Schema } from "effect";
3
+ import { InstancePath, ConfigFileError } from "./file.ts";
4
+ /** The Loop's Markdown task template, with CEL expressions inside `{{ ... }}`. */
5
+ export declare const PromptTemplate: Schema.brand<Schema.String, "PromptTemplate">;
6
+ export type PromptTemplate = typeof PromptTemplate.Type;
7
+ declare const LoopFile_base: Schema.Class<LoopFile, Schema.Struct<{
8
+ readonly name: Schema.brand<Schema.NonEmptyString, "LoopName">;
9
+ readonly trigger: Schema.Union<readonly [typeof import("./trigger.ts").IssueTrigger, typeof import("./trigger.ts").PullRequestTrigger, typeof import("./trigger.ts").ScheduleTrigger]>;
10
+ readonly selector: Schema.Option<Schema.decodeTo<Schema.declareConstructor<import("./selector.ts").Selector, {
11
+ readonly repos?: readonly string[];
12
+ readonly tags?: readonly string[];
13
+ }, readonly [Schema.Struct<{
14
+ readonly repos: Schema.OptionFromOptionalKey<Schema.$Array<Schema.brand<Schema.String, "RepositoryName">>>;
15
+ readonly tags: Schema.OptionFromOptionalKey<Schema.$Array<Schema.brand<Schema.String, "RegistryTag">>>;
16
+ }>], {
17
+ readonly repos: Schema.OptionIso<Schema.toType<Schema.$Array<Schema.brand<Schema.String, "RepositoryName">>>>;
18
+ readonly tags: Schema.OptionIso<Schema.toType<Schema.$Array<Schema.brand<Schema.String, "RegistryTag">>>>;
19
+ }>, Schema.Struct<{
20
+ readonly repos: Schema.OptionFromOptionalKey<Schema.$Array<Schema.brand<Schema.String, "RepositoryName">>>;
21
+ readonly tags: Schema.OptionFromOptionalKey<Schema.$Array<Schema.brand<Schema.String, "RegistryTag">>>;
22
+ }>, never, never>>;
23
+ readonly approval: Schema.Literals<readonly ["none", "required"]>;
24
+ readonly maxCycles: Schema.withConstructorDefault<Schema.withDecodingDefaultKey<Schema.brand<Schema.Int, "CycleLimit">, never>>;
25
+ readonly step: Schema.brand<Schema.NonEmptyString, "StepName">;
26
+ readonly agent: Schema.brand<Schema.String, "AgentName">;
27
+ readonly body: Schema.brand<Schema.String, "PromptTemplate">;
28
+ }>, {}>;
29
+ /** A Loop parsed from `loops/<name>.md`; the filename defines its identity. */
30
+ export declare class LoopFile extends LoopFile_base {
31
+ }
32
+ declare const QuarantinedLoop_base: Schema.Class<QuarantinedLoop, Schema.Struct<{
33
+ readonly file: Schema.brand<Schema.NonEmptyString, "InstancePath">;
34
+ readonly error: typeof ConfigFileError;
35
+ }>, {}>;
36
+ /** A Loop excluded from execution because its file could not be loaded or its references resolved. */
37
+ export declare class QuarantinedLoop extends QuarantinedLoop_base {
38
+ }
39
+ /** The file a loop name refers to. */
40
+ export declare const loopPath: (name: LoopName) => InstancePath;
41
+ export {};
42
+ //# sourceMappingURL=loop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../src/loop.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAuB,MAAM,kBAAkB,CAAC;AAEjE,OAAO,EAAU,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAW1D,kFAAkF;AAClF,eAAO,MAAM,cAAc,+CAE1B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;AAExD,+EAA+E;AAC/E,qBAAa,QAAS,SAAQ,aAY5B;CAAG;;;;;AAEL,sGAAsG;AACtG,qBAAa,eAAgB,SAAQ,oBAKpC;CAAG;AAEJ,sCAAsC;AACtC,eAAO,MAAM,QAAQ,SAAU,QAAQ,KAAG,YAAqD,CAAC"}
package/dist/loop.js ADDED
@@ -0,0 +1,36 @@
1
+ import { AgentName } from "@usine/core/agent";
2
+ import { CycleLimit, DEFAULT_CYCLE_LIMIT } from "@usine/core/cycle-budget";
3
+ import { LoopName, ApprovalRequirement } from "@usine/core/loop";
4
+ import { StepName } from "@usine/core/step";
5
+ import { Effect, Schema } from "effect";
6
+ import { InstancePath, ConfigFileError } from "./file.js";
7
+ import { nonBlankString } from "./checks.js";
8
+ import { ValidSelector } from "./selector.js";
9
+ import { Trigger } from "./trigger.js";
10
+ const KebabCase = Schema.isPattern(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u, {
11
+ message: "Expected a kebab-case filename identity",
12
+ });
13
+ const LoopFileName = LoopName.check(KebabCase);
14
+ /** The Loop's Markdown task template, with CEL expressions inside `{{ ... }}`. */
15
+ export const PromptTemplate = nonBlankString("Expected a non-blank Markdown prompt").pipe(Schema.brand("PromptTemplate"));
16
+ /** A Loop parsed from `loops/<name>.md`; the filename defines its identity. */
17
+ export class LoopFile extends Schema.Class("@usine/config/LoopFile")({
18
+ name: LoopFileName,
19
+ trigger: Trigger,
20
+ selector: Schema.Option(ValidSelector),
21
+ approval: ApprovalRequirement,
22
+ maxCycles: CycleLimit.pipe(Schema.withDecodingDefaultKey(Effect.succeed(DEFAULT_CYCLE_LIMIT)), Schema.withConstructorDefault(Effect.succeed(DEFAULT_CYCLE_LIMIT))),
23
+ step: StepName,
24
+ agent: AgentName,
25
+ body: PromptTemplate,
26
+ }) {
27
+ }
28
+ /** A Loop excluded from execution because its file could not be loaded or its references resolved. */
29
+ export class QuarantinedLoop extends Schema.Class("@usine/config/QuarantinedLoop")({
30
+ file: InstancePath,
31
+ error: ConfigFileError,
32
+ }) {
33
+ }
34
+ /** The file a loop name refers to. */
35
+ export const loopPath = (name) => InstancePath.make(`loops/${name}.md`);
36
+ //# sourceMappingURL=loop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop.js","sourceRoot":"","sources":["../src/loop.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC3E,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC,6BAA6B,EAAE;IAChE,OAAO,EAAE,yCAAyC;CACnD,CAAC,CAAC;AAEH,MAAM,YAAY,GAAG,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;AAE/C,kFAAkF;AAClF,MAAM,CAAC,MAAM,cAAc,GAAG,cAAc,CAAC,sCAAsC,CAAC,CAAC,IAAI,CACvF,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAC/B,CAAC;AAIF,+EAA+E;AAC/E,MAAM,OAAO,QAAS,SAAQ,MAAM,CAAC,KAAK,CAAW,wBAAwB,CAAC,CAAC;IAC7E,IAAI,EAAE,YAAY;IAClB,OAAO,EAAE,OAAO;IAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC;IACtC,QAAQ,EAAE,mBAAmB;IAC7B,SAAS,EAAE,UAAU,CAAC,IAAI,CACxB,MAAM,CAAC,sBAAsB,CAAC,MAAM,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC,EAClE,MAAM,CAAC,sBAAsB,CAAC,MAAM,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC,CACnE;IACD,IAAI,EAAE,QAAQ;IACd,KAAK,EAAE,SAAS;IAChB,IAAI,EAAE,cAAc;CACrB,CAAC;CAAG;AAEL,sGAAsG;AACtG,MAAM,OAAO,eAAgB,SAAQ,MAAM,CAAC,KAAK,CAAkB,+BAA+B,CAAC,CACjG;IACE,IAAI,EAAE,YAAY;IAClB,KAAK,EAAE,eAAe;CACvB,CACF;CAAG;AAEJ,sCAAsC;AACtC,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,IAAc,EAAgB,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { ExpressionEngine, type ExpressionCheckError } from "@usine/expression/expression-engine";
2
+ import { Context, Effect, Layer, Schema } from "effect";
3
+ import { AgentFile } from "./agent.ts";
4
+ import { ConfigFileError, InstancePath } from "./file.ts";
5
+ import { type ConfigWarning } from "./instance/diagnostics.ts";
6
+ import { LoopFile } from "./loop.ts";
7
+ import { UsineConfig } from "./factory.ts";
8
+ declare const ConfigParser_base: Context.ServiceClass<ConfigParser, "@usine/config/ConfigParser", {
9
+ /** Reads registry and factory settings from `source`; `file` identifies the source in diagnostics. */
10
+ readonly parseUsineYaml: (file: InstancePath, source: string) => Effect.Effect<UsineConfig, ConfigFileError>;
11
+ /** Reads an Agent's brain and standing instructions; `file` supplies its filename-derived identity. */
12
+ readonly parseAgentFile: (file: InstancePath, source: string) => Effect.Effect<AgentFile, ConfigFileError>;
13
+ /** Reads a Loop and checks its selection and prompt expressions; `file` supplies its identity. */
14
+ readonly parseLoopFile: (file: InstancePath, source: string) => Effect.Effect<LoopFile, ConfigFileError>;
15
+ /** Reports undeclared labels used by Board lanes or Loop conditions without rejecting otherwise valid configuration. */
16
+ readonly configWarnings: (config: UsineConfig, loops: ReadonlyArray<LoopFile>) => Effect.Effect<ReadonlyArray<ConfigWarning>, ExpressionCheckError>;
17
+ /** Renders the Loop's `{{ ... }}` expressions using `context.item` for the trigger payload and `context.repo` for the repository. */
18
+ readonly renderPrompt: (loop: LoopFile, context: Schema.JsonObject) => Effect.Effect<string, ConfigFileError>;
19
+ }>;
20
+ /** Parses supplied instance-file contents and checks the expressions that select Items and render task prompts. */
21
+ export declare class ConfigParser extends ConfigParser_base {
22
+ static readonly layer: Layer.Layer<ConfigParser, never, ExpressionEngine>;
23
+ }
24
+ export {};
25
+ //# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,gBAAgB,EAEhB,KAAK,oBAAoB,EAG1B,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAU,MAAM,EAAe,MAAM,QAAQ,CAAC;AAE7E,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EACL,KAAK,aAAa,EAGnB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,QAAQ,EAA4B,MAAM,WAAW,CAAC;AAG/D,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;;IA+QvC,sGAAsG;6BAC7E,CACvB,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,MAAM,KACX,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,eAAe,CAAC;IAChD,uGAAuG;6BAC9E,CACvB,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,MAAM,KACX,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,eAAe,CAAC;IAC9C,kGAAkG;4BAC1E,CACtB,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,MAAM,KACX,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,eAAe,CAAC;IAC7C,wHAAwH;6BAC/F,CACvB,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,aAAa,CAAC,QAAQ,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,aAAa,CAAC,EAAE,oBAAoB,CAAC;IACtE,qIAAqI;2BAC9G,CACrB,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,MAAM,CAAC,UAAU,KACvB,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC;;AA5B/C,mHAAmH;AACnH,qBAAa,YAAa,SAAQ,iBA6BD;IAC/B,MAAM,CAAC,QAAQ,CAAC,KAAK,qDAcnB;CACH"}
package/dist/parser.js ADDED
@@ -0,0 +1,204 @@
1
+ import { StepName } from "@usine/core/step";
2
+ import { ApprovalRequirement, LoopName } from "@usine/core/loop";
3
+ import { ExpressionEngine, Source, } from "@usine/expression/expression-engine";
4
+ import { Context, Effect, Layer, Option, Schema, SchemaIssue } from "effect";
5
+ import { splitMarkdown } from "./frontmatter.js";
6
+ import { AgentFile } from "./agent.js";
7
+ import { canonicalLabelName, GitHubLabelName } from "./label.js";
8
+ import { ConfigFileError, InstancePath } from "./file.js";
9
+ import { UndeclaredLaneLabelWarning, UndeclaredTriggerLabelWarning, } from "./instance/diagnostics.js";
10
+ import { LoopFile, loopPath, PromptTemplate } from "./loop.js";
11
+ import { AgentPersona, AgentName } from "@usine/core/agent";
12
+ import { Trigger } from "./trigger.js";
13
+ import { UsineConfig } from "./factory.js";
14
+ import { ValidSelector } from "./selector.js";
15
+ import { Effort, Harness, ModelId } from "@usine/core/agent/brain";
16
+ import { environmentFor } from "./payload-slices.js";
17
+ import { parseYamlDocument } from "./yaml/document.js";
18
+ const formatIssue = SchemaIssue.makeFormatterDefault();
19
+ class LoopFrontmatter extends Schema.Class("@usine/config/LoopFrontmatter")({
20
+ trigger: Trigger,
21
+ selector: Schema.OptionFromOptionalKey(ValidSelector),
22
+ approval: ApprovalRequirement.pipe(Schema.withDecodingDefaultKey(Effect.succeed("none"))),
23
+ step: StepName.pipe(Schema.withDecodingDefaultKey(Effect.succeed("run"))),
24
+ agent: AgentName,
25
+ maxCycles: LoopFile.fields.maxCycles,
26
+ }) {
27
+ }
28
+ const AuthoredLoopFrontmatter = LoopFrontmatter.pipe(Schema.encodeKeys({ maxCycles: "max_cycles" }));
29
+ class AgentFrontmatter extends Schema.Class("@usine/config/AgentFrontmatter")({
30
+ harness: Harness,
31
+ model: ModelId,
32
+ effort: Effort,
33
+ parent_model: Schema.OptionFromOptionalKey(ModelId),
34
+ parent_effort: Schema.OptionFromOptionalKey(Effort),
35
+ }) {
36
+ }
37
+ const schemaError = (file, error) => new ConfigFileError({
38
+ file,
39
+ stage: "schema",
40
+ expression: Option.none(),
41
+ message: formatIssue(error.issue),
42
+ });
43
+ const decodeJson = Schema.decodeUnknownEffect(Schema.Json);
44
+ const parseYaml = (file, source) => Effect.gen(function* () {
45
+ const document = yield* parseYamlDocument(file, source);
46
+ return yield* decodeJson(document.toJS()).pipe(Effect.mapError((error) => new ConfigFileError({
47
+ file,
48
+ stage: "yaml",
49
+ expression: Option.none(),
50
+ message: formatIssue(error.issue),
51
+ })));
52
+ });
53
+ const expressionError = (file, error) => new ConfigFileError({
54
+ file,
55
+ stage: "expression",
56
+ expression: Option.some(error.source),
57
+ message: error.message,
58
+ });
59
+ const markdownName = (file) => {
60
+ const basename = file.slice(file.lastIndexOf("/") + 1);
61
+ if (!basename.endsWith(".md")) {
62
+ return new ConfigFileError({
63
+ file,
64
+ stage: "schema",
65
+ expression: Option.none(),
66
+ message: "Expected a .md filename",
67
+ });
68
+ }
69
+ return Effect.succeed(basename.slice(0, -3));
70
+ };
71
+ const loopName = (file) => markdownName(file).pipe(Effect.flatMap((name) => Schema.decodeUnknownEffect(LoopFile.fields.name)(name).pipe(Effect.mapError((error) => schemaError(file, error)))));
72
+ const agentName = (file) => markdownName(file).pipe(Effect.flatMap((name) => Schema.decodeUnknownEffect(AgentName)(name).pipe(Effect.mapError((error) => schemaError(file, error)))));
73
+ const interpolations = (file, body) => Effect.gen(function* () {
74
+ const found = [];
75
+ let cursor = 0;
76
+ while (cursor < body.length) {
77
+ const start = body.indexOf("{{", cursor);
78
+ if (start < 0)
79
+ break;
80
+ const end = body.indexOf("}}", start + 2);
81
+ if (end < 0) {
82
+ return yield* new ConfigFileError({
83
+ file,
84
+ stage: "expression",
85
+ expression: Option.none(),
86
+ message: `Unclosed prompt interpolation at character ${start}`,
87
+ });
88
+ }
89
+ const expression = body.slice(start + 2, end).trim();
90
+ const parsed = yield* Schema.decodeUnknownEffect(Source)(expression).pipe(Effect.mapError((error) => schemaError(file, error)));
91
+ found.push({ source: parsed, start, end: end + 2 });
92
+ cursor = end + 2;
93
+ }
94
+ return found;
95
+ });
96
+ const render = (engine, loop, context) => {
97
+ const file = loopPath(loop.name);
98
+ const environment = environmentFor(loop.trigger.on);
99
+ return Effect.gen(function* () {
100
+ const expressions = yield* interpolations(file, loop.body);
101
+ const rendered = yield* Effect.forEach(expressions, (expression) => engine.evaluateScalar(expression.source, environment, context).pipe(Effect.mapError((error) => expressionError(file, error)), Effect.map((value) => ({ expression, value }))));
102
+ let output = "";
103
+ let cursor = 0;
104
+ for (const { expression, value } of rendered) {
105
+ output += loop.body.slice(cursor, expression.start);
106
+ output += String(value);
107
+ cursor = expression.end;
108
+ }
109
+ return output + loop.body.slice(cursor);
110
+ });
111
+ };
112
+ const parseUsineYaml = Effect.fn("ConfigParser.parseUsineYaml")((file, source) => Effect.gen(function* () {
113
+ const yaml = yield* parseYaml(file, source);
114
+ return yield* Schema.decodeUnknownEffect(UsineConfig)(yaml, {
115
+ onExcessProperty: "error",
116
+ }).pipe(Effect.mapError((error) => schemaError(file, error)));
117
+ }));
118
+ const parseAgentFile = Effect.fn("ConfigParser.parseAgentFile")((file, source) => Effect.gen(function* () {
119
+ const name = yield* agentName(file);
120
+ const markdown = yield* splitMarkdown(file, source);
121
+ const yaml = yield* parseYaml(file, markdown.yaml);
122
+ const frontmatter = yield* Schema.decodeUnknownEffect(AgentFrontmatter)(yaml, {
123
+ onExcessProperty: "error",
124
+ }).pipe(Effect.mapError((error) => schemaError(file, error)));
125
+ const persona = yield* Schema.decodeUnknownEffect(AgentPersona)(markdown.body).pipe(Effect.mapError((error) => schemaError(file, error)));
126
+ return new AgentFile({
127
+ name,
128
+ harness: frontmatter.harness,
129
+ model: frontmatter.model,
130
+ effort: frontmatter.effort,
131
+ parentModel: frontmatter.parent_model,
132
+ parentEffort: frontmatter.parent_effort,
133
+ persona,
134
+ });
135
+ }));
136
+ const makeParseLoopFile = (engine) => Effect.fn("ConfigParser.parseLoopFile")((file, source) => Effect.gen(function* () {
137
+ const name = yield* loopName(file);
138
+ const markdown = yield* splitMarkdown(file, source);
139
+ const yaml = yield* parseYaml(file, markdown.yaml);
140
+ const frontmatter = yield* Schema.decodeUnknownEffect(AuthoredLoopFrontmatter)(yaml, {
141
+ onExcessProperty: "error",
142
+ }).pipe(Effect.mapError((error) => schemaError(file, error)));
143
+ const body = yield* Schema.decodeUnknownEffect(PromptTemplate)(markdown.body).pipe(Effect.mapError((error) => schemaError(file, error)));
144
+ const environment = environmentFor(frontmatter.trigger.on);
145
+ yield* engine
146
+ .checkBoolean(frontmatter.trigger.if, environment)
147
+ .pipe(Effect.mapError((error) => expressionError(file, error)));
148
+ const promptExpressions = yield* interpolations(file, body);
149
+ yield* Effect.forEach(promptExpressions, ({ source: expression }) => engine
150
+ .checkScalar(expression, environment)
151
+ .pipe(Effect.mapError((error) => expressionError(file, error))), { discard: true });
152
+ return new LoopFile({
153
+ name,
154
+ trigger: frontmatter.trigger,
155
+ selector: frontmatter.selector,
156
+ approval: frontmatter.approval,
157
+ maxCycles: frontmatter.maxCycles,
158
+ step: frontmatter.step,
159
+ agent: frontmatter.agent,
160
+ body,
161
+ });
162
+ }));
163
+ const decodeLabelName = Schema.decodeUnknownOption(GitHubLabelName);
164
+ const makeConfigWarnings = (engine) => Effect.fn("ConfigParser.configWarnings")(function* (config, loops) {
165
+ const declared = new Set(config.labels.map(({ name }) => canonicalLabelName(name)));
166
+ const warnings = [];
167
+ for (const repository of config.repos) {
168
+ for (const lane of repository.lanes) {
169
+ for (const source of lane.sources) {
170
+ if ("label" in source && !declared.has(canonicalLabelName(source.label))) {
171
+ warnings.push(new UndeclaredLaneLabelWarning({
172
+ repository: repository.name,
173
+ lane: lane.name,
174
+ label: source.label,
175
+ }));
176
+ }
177
+ }
178
+ }
179
+ }
180
+ for (const loop of loops) {
181
+ const literals = yield* engine.relatedStringLiterals(loop.trigger.if, ["item", "labels"]);
182
+ for (const literal of literals) {
183
+ const label = decodeLabelName(literal);
184
+ if (Option.isSome(label) && !declared.has(canonicalLabelName(label.value))) {
185
+ warnings.push(new UndeclaredTriggerLabelWarning({ loop: loop.name, label: label.value }));
186
+ }
187
+ }
188
+ }
189
+ return warnings;
190
+ });
191
+ /** Parses supplied instance-file contents and checks the expressions that select Items and render task prompts. */
192
+ export class ConfigParser extends Context.Service()("@usine/config/ConfigParser") {
193
+ static layer = Layer.effect(ConfigParser, Effect.gen(function* () {
194
+ const engine = yield* ExpressionEngine;
195
+ return {
196
+ configWarnings: makeConfigWarnings(engine),
197
+ parseUsineYaml,
198
+ parseAgentFile,
199
+ parseLoopFile: makeParseLoopFile(engine),
200
+ renderPrompt: Effect.fn("ConfigParser.renderPrompt")((loop, context) => render(engine, loop, context)),
201
+ };
202
+ }));
203
+ }
204
+ //# sourceMappingURL=parser.js.map