@llm4ts/runner 0.17.0 → 0.18.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/dist/Kits.d.ts ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * A kit is a directory bundling everything stack-specific the engine flows
3
+ * consume: `packs/<name>/pack.md` manifests, the `scaffolds/` they point at,
4
+ * a `patterns/` deck of translation cards, optional `flows/` of its own, and
5
+ * a `README.md` whose first paragraph is the kit's description (ADR 0014).
6
+ * Kits are discovered in the same three tiers as flows — the project's
7
+ * `.llm4ts/kits/`, the global `~/.config/llm4ts/kits/`, and the built-in
8
+ * kits shipped beside the engine flows — with project > global > builtin
9
+ * precedence by kit name.
10
+ */
11
+ export type KitTier = "project" | "global" | "builtin";
12
+ export interface KitTierPaths {
13
+ readonly project?: string;
14
+ readonly global?: string;
15
+ readonly builtin?: string;
16
+ }
17
+ export interface DiscoveredKit {
18
+ readonly name: string;
19
+ /** Absolute kit directory. */
20
+ readonly root: string;
21
+ readonly tier: KitTier;
22
+ readonly description?: string;
23
+ /** Pack names: every `packs/<name>/pack.md`. */
24
+ readonly packs: ReadonlyArray<string>;
25
+ /** Flow names: every `flows/<name>.ts|js` (not `lib/` or `test/`). */
26
+ readonly flows: ReadonlyArray<string>;
27
+ readonly shadows: ReadonlyArray<KitTier>;
28
+ }
29
+ export declare const kitTierPaths: (options: {
30
+ readonly cwd: string;
31
+ readonly homeDir: string;
32
+ readonly environment?: Readonly<Record<string, string | undefined>>;
33
+ readonly builtinDir?: string;
34
+ }) => KitTierPaths;
35
+ /**
36
+ * The built-in kits directory for a flow script: the `kits/` directory that
37
+ * sits beside the engine `flows/` — in a checkout `<repo>/kits`, in the
38
+ * published shell `<shell>/kits` — found by walking up from the flow's own
39
+ * directory. A kit's own flows live two levels deeper and resolve the same
40
+ * way. `undefined` when no `kits/` directory is within reach.
41
+ */
42
+ export declare const builtinKitsDir: (flowDir: string) => string | undefined;
43
+ /** The first non-empty line after the README's `# ` title, without markup. */
44
+ export declare const parseKitDescription: (readme: string) => string | undefined;
45
+ /**
46
+ * Discovers kits across the tiers. A directory is a kit when it holds at
47
+ * least one pack or one flow; missing tier directories contribute nothing.
48
+ */
49
+ export declare const discoverKits: (paths: KitTierPaths) => ReadonlyArray<DiscoveredKit>;
50
+ export type PackLookup = {
51
+ readonly _tag: "Found";
52
+ readonly kit: DiscoveredKit;
53
+ readonly pack: string;
54
+ } | {
55
+ readonly _tag: "Ambiguous";
56
+ readonly candidates: ReadonlyArray<string>;
57
+ } | {
58
+ readonly _tag: "Absent";
59
+ };
60
+ /**
61
+ * Resolves a pack name across discovered kits. `kit/pack` names one kit;
62
+ * a bare name is searched tier by tier (project, then global, then builtin)
63
+ * and is ambiguous only when two kits of the SAME tier both ship it — a
64
+ * project kit shadows a built-in one, exactly as flows do.
65
+ */
66
+ export declare const findPack: (reference: string, kits: ReadonlyArray<DiscoveredKit>) => PackLookup;
67
+ //# sourceMappingURL=Kits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Kits.d.ts","sourceRoot":"","sources":["../src/Kits.ts"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AACH,MAAM,MAAM,OAAO,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAA;AAEtD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,8BAA8B;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,gDAAgD;IAChD,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IACrC,sEAAsE;IACtE,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IACrC,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;CACzC;AAID,eAAO,MAAM,YAAY,GAAI,SAAS;IACpC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAA;IACnE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAC7B,KAAG,YAWH,CAAA;AAUD;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GAAI,SAAS,MAAM,KAAG,MAAM,GAAG,SAczD,CAAA;AAED,8EAA8E;AAC9E,eAAO,MAAM,mBAAmB,GAAI,QAAQ,MAAM,KAAG,MAAM,GAAG,SAU7D,CAAA;AAyCD;;;GAGG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,YAAY,KAAG,aAAa,CAAC,aAAa,CAuB7E,CAAA;AAED,MAAM,MAAM,UAAU,GAClB;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9E;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CAAE,GAC1E;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAA;AAE/B;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,GAAI,WAAW,MAAM,EAAE,MAAM,aAAa,CAAC,aAAa,CAAC,KAAG,UAoBhF,CAAA"}
package/dist/Kits.js ADDED
@@ -0,0 +1,143 @@
1
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+ const tierOrder = ["project", "global", "builtin"];
4
+ export const kitTierPaths = (options) => {
5
+ const environment = options.environment ?? process.env;
6
+ const configHome = environment.XDG_CONFIG_HOME !== undefined && environment.XDG_CONFIG_HOME.length > 0
7
+ ? environment.XDG_CONFIG_HOME
8
+ : join(options.homeDir, ".config");
9
+ return {
10
+ project: join(options.cwd, ".llm4ts", "kits"),
11
+ global: join(configHome, "llm4ts", "kits"),
12
+ ...(options.builtinDir === undefined ? {} : { builtin: options.builtinDir })
13
+ };
14
+ };
15
+ const isDirectory = (path) => {
16
+ try {
17
+ return statSync(path).isDirectory();
18
+ }
19
+ catch {
20
+ return false;
21
+ }
22
+ };
23
+ /**
24
+ * The built-in kits directory for a flow script: the `kits/` directory that
25
+ * sits beside the engine `flows/` — in a checkout `<repo>/kits`, in the
26
+ * published shell `<shell>/kits` — found by walking up from the flow's own
27
+ * directory. A kit's own flows live two levels deeper and resolve the same
28
+ * way. `undefined` when no `kits/` directory is within reach.
29
+ */
30
+ export const builtinKitsDir = (flowDir) => {
31
+ let directory = resolve(flowDir);
32
+ for (let depth = 0; depth < 4; depth += 1) {
33
+ const candidate = join(directory, "kits");
34
+ if (isDirectory(candidate)) {
35
+ return candidate;
36
+ }
37
+ const parent = dirname(directory);
38
+ if (parent === directory) {
39
+ break;
40
+ }
41
+ directory = parent;
42
+ }
43
+ return undefined;
44
+ };
45
+ /** The first non-empty line after the README's `# ` title, without markup. */
46
+ export const parseKitDescription = (readme) => {
47
+ const lines = readme.split(/\r?\n/);
48
+ const start = lines.findIndex((line) => line.startsWith("# "));
49
+ for (const line of lines.slice(start + 1)) {
50
+ const trimmed = line.trim();
51
+ if (trimmed.length > 0 && !trimmed.startsWith("#")) {
52
+ return trimmed;
53
+ }
54
+ }
55
+ return undefined;
56
+ };
57
+ const listDirectories = (path) => isDirectory(path)
58
+ ? readdirSync(path)
59
+ .filter((entry) => isDirectory(join(path, entry)))
60
+ .sort()
61
+ : [];
62
+ const readKit = (name, root, tier) => {
63
+ const packs = listDirectories(join(root, "packs")).filter((pack) => existsSync(join(root, "packs", pack, "pack.md")));
64
+ const flowsDir = join(root, "flows");
65
+ const flows = isDirectory(flowsDir)
66
+ ? readdirSync(flowsDir)
67
+ .filter((entry) => entry.endsWith(".ts") || entry.endsWith(".js"))
68
+ .map((entry) => entry.replace(/\.(ts|js)$/, ""))
69
+ .sort()
70
+ : [];
71
+ if (packs.length === 0 && flows.length === 0) {
72
+ return undefined;
73
+ }
74
+ const readme = join(root, "README.md");
75
+ const description = existsSync(readme)
76
+ ? parseKitDescription(readFileSync(readme, "utf8"))
77
+ : undefined;
78
+ return {
79
+ name,
80
+ root,
81
+ tier,
82
+ ...(description === undefined ? {} : { description }),
83
+ packs,
84
+ flows
85
+ };
86
+ };
87
+ /**
88
+ * Discovers kits across the tiers. A directory is a kit when it holds at
89
+ * least one pack or one flow; missing tier directories contribute nothing.
90
+ */
91
+ export const discoverKits = (paths) => {
92
+ const byName = new Map();
93
+ for (const tier of tierOrder) {
94
+ const directory = paths[tier];
95
+ if (directory === undefined) {
96
+ continue;
97
+ }
98
+ for (const name of listDirectories(directory)) {
99
+ const kit = readKit(name, join(resolve(directory), name), tier);
100
+ if (kit === undefined) {
101
+ continue;
102
+ }
103
+ const existing = byName.get(name);
104
+ if (existing === undefined) {
105
+ byName.set(name, { kit, shadows: [] });
106
+ }
107
+ else {
108
+ existing.shadows.push(tier);
109
+ }
110
+ }
111
+ }
112
+ return [...byName.values()]
113
+ .map(({ kit, shadows }) => ({ ...kit, shadows }))
114
+ .sort((left, right) => left.name.localeCompare(right.name));
115
+ };
116
+ /**
117
+ * Resolves a pack name across discovered kits. `kit/pack` names one kit;
118
+ * a bare name is searched tier by tier (project, then global, then builtin)
119
+ * and is ambiguous only when two kits of the SAME tier both ship it — a
120
+ * project kit shadows a built-in one, exactly as flows do.
121
+ */
122
+ export const findPack = (reference, kits) => {
123
+ const slash = reference.indexOf("/");
124
+ if (slash > 0 && reference.indexOf("/", slash + 1) < 0) {
125
+ const kitName = reference.slice(0, slash);
126
+ const pack = reference.slice(slash + 1);
127
+ const kit = kits.find((candidate) => candidate.name === kitName);
128
+ return kit !== undefined && kit.packs.includes(pack)
129
+ ? { _tag: "Found", kit, pack }
130
+ : { _tag: "Absent" };
131
+ }
132
+ for (const tier of tierOrder) {
133
+ const holders = kits.filter((kit) => kit.tier === tier && kit.packs.includes(reference));
134
+ if (holders.length === 1) {
135
+ return { _tag: "Found", kit: holders[0], pack: reference };
136
+ }
137
+ if (holders.length > 1) {
138
+ return { _tag: "Ambiguous", candidates: holders.map((kit) => `${kit.name}/${reference}`) };
139
+ }
140
+ }
141
+ return { _tag: "Absent" };
142
+ };
143
+ //# sourceMappingURL=Kits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Kits.js","sourceRoot":"","sources":["../src/Kits.ts"],"names":[],"mappings":"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"}
package/dist/Packs.d.ts CHANGED
@@ -4,51 +4,76 @@ import type { PlanParseError } from "@llm4ts/flow/FlowError";
4
4
  import { type Pack } from "@llm4ts/flow/Pack";
5
5
  import { type PatternCard } from "@llm4ts/flow/Patterns";
6
6
  import type { WorkspaceError, WorkspaceShape } from "@llm4ts/flow/Workspace";
7
+ import { type DiscoveredKit, type KitTierPaths } from "./Kits.ts";
7
8
  declare const PackNotFound_base: Schema.Class<PackNotFound, Schema.TaggedStruct<"PackNotFound", {
8
9
  readonly message: Schema.String;
9
10
  }>, import("effect/Cause").YieldableError>;
10
11
  /**
11
12
  * Pack location for the modernization flows. `LLM4TS_PACK` names the pack
12
- * (default `packs/cobol-springboot`) and is resolved against the launch
13
- * directory first — a pack the user keeps next to their work wins — then
14
- * against the flow script's own directory, where the published shell ships
15
- * the built-in packs. An absolute `LLM4TS_PACK` is used as-is. The fallback
16
- * is what lets `llm4ts run modernize-survey --repo <estate>` work from any
17
- * directory instead of only from a checkout with `packs/` at its cwd.
13
+ * (default `cobol-springboot`) in one of three forms:
14
+ *
15
+ * - a bare name (`cobol-springboot`) or `kit/pack` (`mainframe-java/cobol-springboot`),
16
+ * resolved across the kits discovered in the project, global, and built-in
17
+ * tiers (ADR 0014);
18
+ * - a path relative to the launch directory whose directory holds `pack.md`
19
+ * (`packs/my-pack`) — a pack still being written, not yet in a kit;
20
+ * - an absolute path to such a directory.
21
+ *
22
+ * The built-in tier is what lets `llm4ts run modernize-survey --repo <estate>`
23
+ * work from any directory instead of only from a checkout.
18
24
  */
19
25
  export declare class PackNotFound extends PackNotFound_base {
20
26
  }
21
27
  export interface LocatedPack {
22
- /** Directory the pack workspace is rooted at. */
28
+ /** Directory the pack workspace is rooted at: the kit, or the pack itself. */
23
29
  readonly root: string;
24
- /** Pack directory relative to `root` (`"."` when the pack path is absolute). */
30
+ /** Pack directory relative to `root` (`"."` for a path-form pack). */
25
31
  readonly dir: string;
32
+ /** The kit the pack came from; absent for a path-form pack. */
33
+ readonly kit?: DiscoveredKit;
26
34
  }
27
- /** The first candidate root whose `packDir` holds a `pack.md` manifest. */
28
- export declare const locatePack: (packDir: string, roots: ReadonlyArray<string>) => LocatedPack | undefined;
35
+ export type PackLocation = ({
36
+ readonly _tag: "Located";
37
+ } & LocatedPack) | {
38
+ readonly _tag: "Ambiguous";
39
+ readonly candidates: ReadonlyArray<string>;
40
+ } | {
41
+ readonly _tag: "Absent";
42
+ };
43
+ /** Resolves a pack reference: absolute path, launch-relative path, then kits. */
44
+ export declare const locatePack: (reference: string, options: {
45
+ readonly launchDir: string;
46
+ readonly kits: ReadonlyArray<DiscoveredKit>;
47
+ }) => PackLocation;
29
48
  export interface OpenedPack {
30
49
  readonly pack: Pack;
31
50
  /**
32
- * Workspace rooted where the pack was found. Pack-relative paths prompts,
33
- * `<pack>/patterns`, `lessons.md`, the scaffoldmust resolve through this
34
- * workspace, not the launch directory's.
51
+ * Workspace rooted where the pack was found the kit for a kit pack, the
52
+ * pack directory itself otherwise. Pack-relative pathsprompts,
53
+ * `<pack>/patterns`, `lessons.md`, the scaffold — must resolve through
54
+ * this workspace, not the launch directory's.
35
55
  */
36
56
  readonly workspace: WorkspaceShape;
37
57
  /** Pack directory within `workspace`. */
38
58
  readonly dir: string;
59
+ /** The kit the pack belongs to, when it came from one. */
60
+ readonly kit?: DiscoveredKit;
39
61
  }
62
+ export declare const defaultPackName = "cobol-springboot";
40
63
  export declare const openPack: (options: {
41
64
  readonly environment: Readonly<Record<string, string | undefined>>;
42
65
  /** The directory the flow was launched from (`resolveFlowInput().workspace`). */
43
66
  readonly launchDir: string;
44
67
  /** The directory holding the flow script (`import.meta.dirname`). */
45
68
  readonly flowDir: string;
69
+ /** Kit tiers to search; defaults to the standard tiers around `launchDir` and `flowDir`. */
70
+ readonly kits?: KitTierPaths;
46
71
  }) => Effect.Effect<OpenedPack, PlanParseError | WorkspaceError | PackNotFound, never>;
47
72
  /**
48
- * The universal pattern cards: the first candidate root with a `patterns/`
49
- * directory contributes them, and no root having one is an empty set —
50
- * patterns are optional everywhere.
73
+ * The pattern cards of the kit the opened pack belongs to (`<kit>/patterns`);
74
+ * a path-form pack, or a kit without a deck, contributes none. Pack-local
75
+ * cards (`<pack>/patterns`) are loaded separately by the flows.
51
76
  */
52
- export declare const loadUniversalPatternCards: (roots: readonly string[]) => Effect.Effect<readonly PatternCard[], WorkspaceError, never>;
77
+ export declare const loadKitPatternCards: (opened: OpenedPack) => Effect.Effect<readonly PatternCard[], WorkspaceError, never>;
53
78
  export {};
54
79
  //# sourceMappingURL=Packs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Packs.d.ts","sourceRoot":"","sources":["../src/Packs.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAC5D,OAAO,EAAY,KAAK,IAAI,EAAE,MAAM,mBAAmB,CAAA;AACvD,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAC1E,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;;;;AAG5E;;;;;;;;GAQG;AAEH,qBAAa,YAAa,SAAQ,iBAEhC;CAAG;AAEL,MAAM,WAAW,WAAW;IAC1B,iDAAiD;IACjD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,gFAAgF;IAChF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CACrB;AAED,2EAA2E;AAC3E,eAAO,MAAM,UAAU,GACrB,SAAS,MAAM,EACf,OAAO,aAAa,CAAC,MAAM,CAAC,KAC3B,WAAW,GAAG,SAWhB,CAAA;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;IACnB;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAA;IAClC,yCAAyC;IACzC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CACrB;AAED,eAAO,MAAM,QAAQ;0BACG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAClE,iFAAiF;wBAC7D,MAAM;IAC1B,qEAAqE;sBACnD,MAAM;sFAexB,CAAA;AAEF;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,4FAapC,CAAA"}
1
+ {"version":3,"file":"Packs.d.ts","sourceRoot":"","sources":["../src/Packs.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAC5D,OAAO,EAAY,KAAK,IAAI,EAAE,MAAM,mBAAmB,CAAA;AACvD,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAC1E,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAC5E,OAAO,EAKL,KAAK,aAAa,EAClB,KAAK,YAAY,EAClB,MAAM,WAAW,CAAA;;;;AAGlB;;;;;;;;;;;;;GAaG;AAEH,qBAAa,YAAa,SAAQ,iBAEhC;CAAG;AAEL,MAAM,WAAW,WAAW;IAC1B,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,sEAAsE;IACtE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,+DAA+D;IAC/D,QAAQ,CAAC,GAAG,CAAC,EAAE,aAAa,CAAA;CAC7B;AAED,MAAM,MAAM,YAAY,GACpB,CAAC;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAAG,WAAW,CAAC,GAC5C;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CAAE,GAC1E;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAA;AAI/B,iFAAiF;AACjF,eAAO,MAAM,UAAU,GACrB,WAAW,MAAM,EACjB,SAAS;IAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,aAAa,CAAC,CAAA;CAAE,KACnF,YAwBF,CAAA;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;IACnB;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAA;IAClC,yCAAyC;IACzC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,0DAA0D;IAC1D,QAAQ,CAAC,GAAG,CAAC,EAAE,aAAa,CAAA;CAC7B;AAED,eAAO,MAAM,eAAe,qBAAqB,CAAA;AAEjD,eAAO,MAAM,QAAQ;0BACG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAClE,iFAAiF;wBAC7D,MAAM;IAC1B,qEAAqE;sBACnD,MAAM;IACxB,4FAA4F;oBAC5E,YAAY;sFAuC5B,CAAA;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,sFAO9B,CAAA"}
package/dist/Packs.js CHANGED
@@ -1,63 +1,103 @@
1
1
  import { existsSync } from "node:fs";
2
+ import { homedir } from "node:os";
2
3
  import { isAbsolute, join, resolve } from "node:path";
3
4
  import * as Effect from "effect/Effect";
4
5
  import * as Schema from "effect/Schema";
5
6
  import { loadPack } from "@llm4ts/flow/Pack";
6
7
  import { loadPatternCards } from "@llm4ts/flow/Patterns";
8
+ import { builtinKitsDir, discoverKits, findPack, kitTierPaths } from "./Kits.js";
7
9
  import { makeNodeWorkspace } from "./NodeWorkspace.js";
8
10
  /**
9
11
  * Pack location for the modernization flows. `LLM4TS_PACK` names the pack
10
- * (default `packs/cobol-springboot`) and is resolved against the launch
11
- * directory first — a pack the user keeps next to their work wins — then
12
- * against the flow script's own directory, where the published shell ships
13
- * the built-in packs. An absolute `LLM4TS_PACK` is used as-is. The fallback
14
- * is what lets `llm4ts run modernize-survey --repo <estate>` work from any
15
- * directory instead of only from a checkout with `packs/` at its cwd.
12
+ * (default `cobol-springboot`) in one of three forms:
13
+ *
14
+ * - a bare name (`cobol-springboot`) or `kit/pack` (`mainframe-java/cobol-springboot`),
15
+ * resolved across the kits discovered in the project, global, and built-in
16
+ * tiers (ADR 0014);
17
+ * - a path relative to the launch directory whose directory holds `pack.md`
18
+ * (`packs/my-pack`) — a pack still being written, not yet in a kit;
19
+ * - an absolute path to such a directory.
20
+ *
21
+ * The built-in tier is what lets `llm4ts run modernize-survey --repo <estate>`
22
+ * work from any directory instead of only from a checkout.
16
23
  */
17
24
  export class PackNotFound extends Schema.TaggedErrorClass()("PackNotFound", {
18
25
  message: Schema.String
19
26
  }) {
20
27
  }
21
- /** The first candidate root whose `packDir` holds a `pack.md` manifest. */
22
- export const locatePack = (packDir, roots) => {
23
- if (isAbsolute(packDir)) {
24
- return existsSync(join(packDir, "pack.md")) ? { root: packDir, dir: "." } : undefined;
28
+ const hasManifest = (directory) => existsSync(join(directory, "pack.md"));
29
+ /** Resolves a pack reference: absolute path, launch-relative path, then kits. */
30
+ export const locatePack = (reference, options) => {
31
+ if (isAbsolute(reference)) {
32
+ return hasManifest(reference)
33
+ ? { _tag: "Located", root: reference, dir: "." }
34
+ : { _tag: "Absent" };
25
35
  }
26
- for (const root of roots) {
27
- const resolved = resolve(root);
28
- if (existsSync(join(resolved, packDir, "pack.md"))) {
29
- return { root: resolved, dir: packDir };
30
- }
36
+ const launchDir = resolve(options.launchDir);
37
+ if (reference.includes("/") && hasManifest(join(launchDir, reference))) {
38
+ return { _tag: "Located", root: launchDir, dir: reference };
39
+ }
40
+ const lookup = findPack(reference, options.kits);
41
+ switch (lookup._tag) {
42
+ case "Found":
43
+ return {
44
+ _tag: "Located",
45
+ root: lookup.kit.root,
46
+ dir: join("packs", lookup.pack),
47
+ kit: lookup.kit
48
+ };
49
+ case "Ambiguous":
50
+ return lookup;
51
+ case "Absent":
52
+ return lookup;
31
53
  }
32
- return undefined;
33
54
  };
55
+ export const defaultPackName = "cobol-springboot";
34
56
  export const openPack = Effect.fn("@llm4ts/runner/Packs.openPack")(function* (options) {
35
- const packDir = options.environment.LLM4TS_PACK ?? "packs/cobol-springboot";
36
- const located = locatePack(packDir, [options.launchDir, options.flowDir]);
37
- if (located === undefined) {
57
+ const reference = options.environment.LLM4TS_PACK?.trim() || defaultPackName;
58
+ const builtinDir = builtinKitsDir(options.flowDir);
59
+ const tiers = options.kits ??
60
+ kitTierPaths({
61
+ cwd: options.launchDir,
62
+ homeDir: homedir(),
63
+ environment: options.environment,
64
+ ...(builtinDir === undefined ? {} : { builtinDir })
65
+ });
66
+ const kits = discoverKits(tiers);
67
+ const location = locatePack(reference, { launchDir: options.launchDir, kits });
68
+ if (location._tag === "Ambiguous") {
69
+ return yield* PackNotFound.make({
70
+ message: `pack '${reference}' is shipped by more than one kit — ` +
71
+ `name one of: ${location.candidates.join(", ")}`
72
+ });
73
+ }
74
+ if (location._tag === "Absent") {
75
+ const known = kits.flatMap((kit) => kit.packs.map((pack) => `${kit.name}/${pack}`));
38
76
  return yield* PackNotFound.make({
39
- message: `pack '${packDir}' not found: no ${join(packDir, "pack.md")} under ` +
40
- `${resolve(options.launchDir)} or ${resolve(options.flowDir)}` +
41
- "set LLM4TS_PACK or launch from a directory containing the pack"
77
+ message: `pack '${reference}' not found: no kit ships it` +
78
+ (known.length === 0 ? "" : ` (known packs: ${known.join(", ")})`) +
79
+ ` and no ${join(reference, "pack.md")} exists under ${resolve(options.launchDir)} ` +
80
+ "set LLM4TS_PACK to a kit pack name, a kit/pack name, or a directory holding pack.md"
42
81
  });
43
82
  }
44
- const workspace = yield* makeNodeWorkspace(located.root);
45
- const pack = yield* loadPack(workspace, located.dir);
46
- return { pack, workspace, dir: located.dir };
83
+ const workspace = yield* makeNodeWorkspace(location.root);
84
+ const pack = yield* loadPack(workspace, location.dir);
85
+ return {
86
+ pack,
87
+ workspace,
88
+ dir: location.dir,
89
+ ...(location.kit === undefined ? {} : { kit: location.kit })
90
+ };
47
91
  });
48
92
  /**
49
- * The universal pattern cards: the first candidate root with a `patterns/`
50
- * directory contributes them, and no root having one is an empty set —
51
- * patterns are optional everywhere.
93
+ * The pattern cards of the kit the opened pack belongs to (`<kit>/patterns`);
94
+ * a path-form pack, or a kit without a deck, contributes none. Pack-local
95
+ * cards (`<pack>/patterns`) are loaded separately by the flows.
52
96
  */
53
- export const loadUniversalPatternCards = Effect.fn("@llm4ts/runner/Packs.loadUniversalPatternCards")(function* (roots) {
54
- const root = roots
55
- .map((candidate) => resolve(candidate))
56
- .find((candidate) => existsSync(join(candidate, "patterns")));
57
- if (root === undefined) {
97
+ export const loadKitPatternCards = Effect.fn("@llm4ts/runner/Packs.loadKitPatternCards")(function* (opened) {
98
+ if (opened.kit === undefined || !existsSync(join(opened.kit.root, "patterns"))) {
58
99
  return [];
59
100
  }
60
- const workspace = yield* makeNodeWorkspace(root);
61
- return yield* loadPatternCards(workspace, "patterns");
101
+ return yield* loadPatternCards(opened.workspace, "patterns");
62
102
  });
63
103
  //# sourceMappingURL=Packs.js.map
package/dist/Packs.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"Packs.js","sourceRoot":"","sources":["../src/Packs.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Packs.js","sourceRoot":"","sources":["../src/Packs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The flow author's entry point: everything a flow script under
3
+ * `.llm4ts/flows/` reaches for, re-exported from the runner and flow
4
+ * subpaths so a script needs one import line. Nothing here is new API — the
5
+ * subpath exports stay the contract for everything else, and the list is
6
+ * exactly what the shipped flows use.
7
+ */
8
+ export { runEmbedded, runFlowMain, runNode } from "./FlowRunner.ts";
9
+ export type { FlowRunnerOptions } from "./FlowRunner.ts";
10
+ export { resolveFlowInput, ScriptUsage } from "./FlowArgs.ts";
11
+ export type { FlowInput } from "./FlowArgs.ts";
12
+ export { apiConnectorFromEnvironment, asReadOnly, coderFor, coderFromEnv, mock, withModel, withTurnLimit } from "./Connectors.ts";
13
+ export { loadKitPatternCards, openPack, PackNotFound } from "./Packs.ts";
14
+ export type { OpenedPack } from "./Packs.ts";
15
+ export { nodePlainFileStore } from "./NodePlainFileStore.ts";
16
+ export { makeNodeWorkspace } from "./NodeWorkspace.ts";
17
+ export { nodeProcessExecutor } from "./NodeProcessExecutor.ts";
18
+ export { reviewFingerprint } from "./ReviewFingerprint.ts";
19
+ export { completeAndPublish, implementPlanFlow } from "@llm4ts/flow/Flow";
20
+ export type { ImplementPlanOptions } from "@llm4ts/flow/Flow";
21
+ export type { FlowContextShape } from "@llm4ts/flow/FlowContext";
22
+ export { implementTaskLoop, stage } from "@llm4ts/flow/PlanExecution";
23
+ export { lintCommand, mergeReviewResults, minimalReviewers, reviewAndFixLoop, ReviewResult } from "@llm4ts/flow/Review";
24
+ export { Reviewer } from "@llm4ts/flow/Reviewer";
25
+ export { makeChat } from "@llm4ts/flow/Chat";
26
+ export { makePlanStore } from "@llm4ts/flow/Persistence";
27
+ export { defaultPlanInstructions, planFrom, writeBrief } from "@llm4ts/flow/Planner";
28
+ export { defaultPlanPath, parsePlan, Plan } from "@llm4ts/flow/Plan";
29
+ export { AssistantMessage, Info } from "@llm4ts/flow/FlowEvents";
30
+ export { FlowAborted, FlowLlmError } from "@llm4ts/flow/FlowError";
31
+ export { loadPack } from "@llm4ts/flow/Pack";
32
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AACnE,YAAY,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACxD,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC7D,YAAY,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,EACL,2BAA2B,EAC3B,UAAU,EACV,QAAQ,EACR,YAAY,EACZ,IAAI,EACJ,SAAS,EACT,aAAa,EACd,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACxE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAC5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAE1D,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AACzE,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAC7D,YAAY,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAChE,OAAO,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EACL,WAAW,EACX,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACb,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AACxD,OAAO,EAAE,uBAAuB,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACpF,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAA;AACpE,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAA;AAChE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAClE,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The flow author's entry point: everything a flow script under
3
+ * `.llm4ts/flows/` reaches for, re-exported from the runner and flow
4
+ * subpaths so a script needs one import line. Nothing here is new API — the
5
+ * subpath exports stay the contract for everything else, and the list is
6
+ * exactly what the shipped flows use.
7
+ */
8
+ export { runEmbedded, runFlowMain, runNode } from "./FlowRunner.js";
9
+ export { resolveFlowInput, ScriptUsage } from "./FlowArgs.js";
10
+ export { apiConnectorFromEnvironment, asReadOnly, coderFor, coderFromEnv, mock, withModel, withTurnLimit } from "./Connectors.js";
11
+ export { loadKitPatternCards, openPack, PackNotFound } from "./Packs.js";
12
+ export { nodePlainFileStore } from "./NodePlainFileStore.js";
13
+ export { makeNodeWorkspace } from "./NodeWorkspace.js";
14
+ export { nodeProcessExecutor } from "./NodeProcessExecutor.js";
15
+ export { reviewFingerprint } from "./ReviewFingerprint.js";
16
+ export { completeAndPublish, implementPlanFlow } from "@llm4ts/flow/Flow";
17
+ export { implementTaskLoop, stage } from "@llm4ts/flow/PlanExecution";
18
+ export { lintCommand, mergeReviewResults, minimalReviewers, reviewAndFixLoop, ReviewResult } from "@llm4ts/flow/Review";
19
+ export { Reviewer } from "@llm4ts/flow/Reviewer";
20
+ export { makeChat } from "@llm4ts/flow/Chat";
21
+ export { makePlanStore } from "@llm4ts/flow/Persistence";
22
+ export { defaultPlanInstructions, planFrom, writeBrief } from "@llm4ts/flow/Planner";
23
+ export { defaultPlanPath, parsePlan, Plan } from "@llm4ts/flow/Plan";
24
+ export { AssistantMessage, Info } from "@llm4ts/flow/FlowEvents";
25
+ export { FlowAborted, FlowLlmError } from "@llm4ts/flow/FlowError";
26
+ export { loadPack } from "@llm4ts/flow/Pack";
27
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAEnE,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAE7D,OAAO,EACL,2BAA2B,EAC3B,UAAU,EACV,QAAQ,EACR,YAAY,EACZ,IAAI,EACJ,SAAS,EACT,aAAa,EACd,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAExE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAE1D,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAGzE,OAAO,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EACL,WAAW,EACX,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACb,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AACxD,OAAO,EAAE,uBAAuB,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACpF,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAA;AACpE,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAA;AAChE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAClE,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llm4ts/runner",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "Node runner, CLI, terminal, and MCP composition for llm4ts",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -13,12 +13,14 @@
13
13
  "src"
14
14
  ],
15
15
  "exports": {
16
+ ".": "./dist/index.js",
16
17
  "./Cli": "./dist/Cli.js",
17
18
  "./CoderPolicy": "./dist/CoderPolicy.js",
18
19
  "./Connectors": "./dist/Connectors.js",
19
20
  "./Doctor": "./dist/Doctor.js",
20
21
  "./FlowArgs": "./dist/FlowArgs.js",
21
22
  "./FlowRunner": "./dist/FlowRunner.js",
23
+ "./Kits": "./dist/Kits.js",
22
24
  "./McpStdio": "./dist/McpStdio.js",
23
25
  "./NodeHttpClient": "./dist/NodeHttpClient.js",
24
26
  "./NodeGeminiCliExecutor": "./dist/NodeGeminiCliExecutor.js",
@@ -57,8 +59,8 @@
57
59
  "typescript"
58
60
  ],
59
61
  "dependencies": {
60
- "@llm4ts/flow": "0.17.0",
61
- "@llm4ts/core": "0.17.0"
62
+ "@llm4ts/core": "0.18.0",
63
+ "@llm4ts/flow": "0.18.0"
62
64
  },
63
65
  "peerDependencies": {
64
66
  "effect": "4.0.0-beta.102"
package/src/Kits.ts ADDED
@@ -0,0 +1,198 @@
1
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"
2
+ import { dirname, join, resolve } from "node:path"
3
+
4
+ /**
5
+ * A kit is a directory bundling everything stack-specific the engine flows
6
+ * consume: `packs/<name>/pack.md` manifests, the `scaffolds/` they point at,
7
+ * a `patterns/` deck of translation cards, optional `flows/` of its own, and
8
+ * a `README.md` whose first paragraph is the kit's description (ADR 0014).
9
+ * Kits are discovered in the same three tiers as flows — the project's
10
+ * `.llm4ts/kits/`, the global `~/.config/llm4ts/kits/`, and the built-in
11
+ * kits shipped beside the engine flows — with project > global > builtin
12
+ * precedence by kit name.
13
+ */
14
+ export type KitTier = "project" | "global" | "builtin"
15
+
16
+ export interface KitTierPaths {
17
+ readonly project?: string
18
+ readonly global?: string
19
+ readonly builtin?: string
20
+ }
21
+
22
+ export interface DiscoveredKit {
23
+ readonly name: string
24
+ /** Absolute kit directory. */
25
+ readonly root: string
26
+ readonly tier: KitTier
27
+ readonly description?: string
28
+ /** Pack names: every `packs/<name>/pack.md`. */
29
+ readonly packs: ReadonlyArray<string>
30
+ /** Flow names: every `flows/<name>.ts|js` (not `lib/` or `test/`). */
31
+ readonly flows: ReadonlyArray<string>
32
+ readonly shadows: ReadonlyArray<KitTier>
33
+ }
34
+
35
+ const tierOrder: ReadonlyArray<KitTier> = ["project", "global", "builtin"]
36
+
37
+ export const kitTierPaths = (options: {
38
+ readonly cwd: string
39
+ readonly homeDir: string
40
+ readonly environment?: Readonly<Record<string, string | undefined>>
41
+ readonly builtinDir?: string
42
+ }): KitTierPaths => {
43
+ const environment = options.environment ?? process.env
44
+ const configHome =
45
+ environment.XDG_CONFIG_HOME !== undefined && environment.XDG_CONFIG_HOME.length > 0
46
+ ? environment.XDG_CONFIG_HOME
47
+ : join(options.homeDir, ".config")
48
+ return {
49
+ project: join(options.cwd, ".llm4ts", "kits"),
50
+ global: join(configHome, "llm4ts", "kits"),
51
+ ...(options.builtinDir === undefined ? {} : { builtin: options.builtinDir })
52
+ }
53
+ }
54
+
55
+ const isDirectory = (path: string): boolean => {
56
+ try {
57
+ return statSync(path).isDirectory()
58
+ } catch {
59
+ return false
60
+ }
61
+ }
62
+
63
+ /**
64
+ * The built-in kits directory for a flow script: the `kits/` directory that
65
+ * sits beside the engine `flows/` — in a checkout `<repo>/kits`, in the
66
+ * published shell `<shell>/kits` — found by walking up from the flow's own
67
+ * directory. A kit's own flows live two levels deeper and resolve the same
68
+ * way. `undefined` when no `kits/` directory is within reach.
69
+ */
70
+ export const builtinKitsDir = (flowDir: string): string | undefined => {
71
+ let directory = resolve(flowDir)
72
+ for (let depth = 0; depth < 4; depth += 1) {
73
+ const candidate = join(directory, "kits")
74
+ if (isDirectory(candidate)) {
75
+ return candidate
76
+ }
77
+ const parent = dirname(directory)
78
+ if (parent === directory) {
79
+ break
80
+ }
81
+ directory = parent
82
+ }
83
+ return undefined
84
+ }
85
+
86
+ /** The first non-empty line after the README's `# ` title, without markup. */
87
+ export const parseKitDescription = (readme: string): string | undefined => {
88
+ const lines = readme.split(/\r?\n/)
89
+ const start = lines.findIndex((line) => line.startsWith("# "))
90
+ for (const line of lines.slice(start + 1)) {
91
+ const trimmed = line.trim()
92
+ if (trimmed.length > 0 && !trimmed.startsWith("#")) {
93
+ return trimmed
94
+ }
95
+ }
96
+ return undefined
97
+ }
98
+
99
+ const listDirectories = (path: string): ReadonlyArray<string> =>
100
+ isDirectory(path)
101
+ ? readdirSync(path)
102
+ .filter((entry) => isDirectory(join(path, entry)))
103
+ .sort()
104
+ : []
105
+
106
+ const readKit = (
107
+ name: string,
108
+ root: string,
109
+ tier: KitTier
110
+ ): Omit<DiscoveredKit, "shadows"> | undefined => {
111
+ const packs = listDirectories(join(root, "packs")).filter((pack) =>
112
+ existsSync(join(root, "packs", pack, "pack.md"))
113
+ )
114
+ const flowsDir = join(root, "flows")
115
+ const flows = isDirectory(flowsDir)
116
+ ? readdirSync(flowsDir)
117
+ .filter((entry) => entry.endsWith(".ts") || entry.endsWith(".js"))
118
+ .map((entry) => entry.replace(/\.(ts|js)$/, ""))
119
+ .sort()
120
+ : []
121
+ if (packs.length === 0 && flows.length === 0) {
122
+ return undefined
123
+ }
124
+ const readme = join(root, "README.md")
125
+ const description = existsSync(readme)
126
+ ? parseKitDescription(readFileSync(readme, "utf8"))
127
+ : undefined
128
+ return {
129
+ name,
130
+ root,
131
+ tier,
132
+ ...(description === undefined ? {} : { description }),
133
+ packs,
134
+ flows
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Discovers kits across the tiers. A directory is a kit when it holds at
140
+ * least one pack or one flow; missing tier directories contribute nothing.
141
+ */
142
+ export const discoverKits = (paths: KitTierPaths): ReadonlyArray<DiscoveredKit> => {
143
+ const byName = new Map<string, { kit: Omit<DiscoveredKit, "shadows">; shadows: Array<KitTier> }>()
144
+ for (const tier of tierOrder) {
145
+ const directory = paths[tier]
146
+ if (directory === undefined) {
147
+ continue
148
+ }
149
+ for (const name of listDirectories(directory)) {
150
+ const kit = readKit(name, join(resolve(directory), name), tier)
151
+ if (kit === undefined) {
152
+ continue
153
+ }
154
+ const existing = byName.get(name)
155
+ if (existing === undefined) {
156
+ byName.set(name, { kit, shadows: [] })
157
+ } else {
158
+ existing.shadows.push(tier)
159
+ }
160
+ }
161
+ }
162
+ return [...byName.values()]
163
+ .map(({ kit, shadows }): DiscoveredKit => ({ ...kit, shadows }))
164
+ .sort((left, right) => left.name.localeCompare(right.name))
165
+ }
166
+
167
+ export type PackLookup =
168
+ | { readonly _tag: "Found"; readonly kit: DiscoveredKit; readonly pack: string }
169
+ | { readonly _tag: "Ambiguous"; readonly candidates: ReadonlyArray<string> }
170
+ | { readonly _tag: "Absent" }
171
+
172
+ /**
173
+ * Resolves a pack name across discovered kits. `kit/pack` names one kit;
174
+ * a bare name is searched tier by tier (project, then global, then builtin)
175
+ * and is ambiguous only when two kits of the SAME tier both ship it — a
176
+ * project kit shadows a built-in one, exactly as flows do.
177
+ */
178
+ export const findPack = (reference: string, kits: ReadonlyArray<DiscoveredKit>): PackLookup => {
179
+ const slash = reference.indexOf("/")
180
+ if (slash > 0 && reference.indexOf("/", slash + 1) < 0) {
181
+ const kitName = reference.slice(0, slash)
182
+ const pack = reference.slice(slash + 1)
183
+ const kit = kits.find((candidate) => candidate.name === kitName)
184
+ return kit !== undefined && kit.packs.includes(pack)
185
+ ? { _tag: "Found", kit, pack }
186
+ : { _tag: "Absent" }
187
+ }
188
+ for (const tier of tierOrder) {
189
+ const holders = kits.filter((kit) => kit.tier === tier && kit.packs.includes(reference))
190
+ if (holders.length === 1) {
191
+ return { _tag: "Found", kit: holders[0]!, pack: reference }
192
+ }
193
+ if (holders.length > 1) {
194
+ return { _tag: "Ambiguous", candidates: holders.map((kit) => `${kit.name}/${reference}`) }
195
+ }
196
+ }
197
+ return { _tag: "Absent" }
198
+ }
package/src/Packs.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { existsSync } from "node:fs"
2
+ import { homedir } from "node:os"
2
3
  import { isAbsolute, join, resolve } from "node:path"
3
4
  import * as Effect from "effect/Effect"
4
5
  import * as Schema from "effect/Schema"
@@ -6,16 +7,29 @@ import type { PlanParseError } from "@llm4ts/flow/FlowError"
6
7
  import { loadPack, type Pack } from "@llm4ts/flow/Pack"
7
8
  import { loadPatternCards, type PatternCard } from "@llm4ts/flow/Patterns"
8
9
  import type { WorkspaceError, WorkspaceShape } from "@llm4ts/flow/Workspace"
10
+ import {
11
+ builtinKitsDir,
12
+ discoverKits,
13
+ findPack,
14
+ kitTierPaths,
15
+ type DiscoveredKit,
16
+ type KitTierPaths
17
+ } from "./Kits.ts"
9
18
  import { makeNodeWorkspace } from "./NodeWorkspace.ts"
10
19
 
11
20
  /**
12
21
  * Pack location for the modernization flows. `LLM4TS_PACK` names the pack
13
- * (default `packs/cobol-springboot`) and is resolved against the launch
14
- * directory first — a pack the user keeps next to their work wins — then
15
- * against the flow script's own directory, where the published shell ships
16
- * the built-in packs. An absolute `LLM4TS_PACK` is used as-is. The fallback
17
- * is what lets `llm4ts run modernize-survey --repo <estate>` work from any
18
- * directory instead of only from a checkout with `packs/` at its cwd.
22
+ * (default `cobol-springboot`) in one of three forms:
23
+ *
24
+ * - a bare name (`cobol-springboot`) or `kit/pack` (`mainframe-java/cobol-springboot`),
25
+ * resolved across the kits discovered in the project, global, and built-in
26
+ * tiers (ADR 0014);
27
+ * - a path relative to the launch directory whose directory holds `pack.md`
28
+ * (`packs/my-pack`) — a pack still being written, not yet in a kit;
29
+ * - an absolute path to such a directory.
30
+ *
31
+ * The built-in tier is what lets `llm4ts run modernize-survey --repo <estate>`
32
+ * work from any directory instead of only from a checkout.
19
33
  */
20
34
 
21
35
  export class PackNotFound extends Schema.TaggedErrorClass<PackNotFound>()("PackNotFound", {
@@ -23,79 +37,126 @@ export class PackNotFound extends Schema.TaggedErrorClass<PackNotFound>()("PackN
23
37
  }) {}
24
38
 
25
39
  export interface LocatedPack {
26
- /** Directory the pack workspace is rooted at. */
40
+ /** Directory the pack workspace is rooted at: the kit, or the pack itself. */
27
41
  readonly root: string
28
- /** Pack directory relative to `root` (`"."` when the pack path is absolute). */
42
+ /** Pack directory relative to `root` (`"."` for a path-form pack). */
29
43
  readonly dir: string
44
+ /** The kit the pack came from; absent for a path-form pack. */
45
+ readonly kit?: DiscoveredKit
30
46
  }
31
47
 
32
- /** The first candidate root whose `packDir` holds a `pack.md` manifest. */
48
+ export type PackLocation =
49
+ | ({ readonly _tag: "Located" } & LocatedPack)
50
+ | { readonly _tag: "Ambiguous"; readonly candidates: ReadonlyArray<string> }
51
+ | { readonly _tag: "Absent" }
52
+
53
+ const hasManifest = (directory: string): boolean => existsSync(join(directory, "pack.md"))
54
+
55
+ /** Resolves a pack reference: absolute path, launch-relative path, then kits. */
33
56
  export const locatePack = (
34
- packDir: string,
35
- roots: ReadonlyArray<string>
36
- ): LocatedPack | undefined => {
37
- if (isAbsolute(packDir)) {
38
- return existsSync(join(packDir, "pack.md")) ? { root: packDir, dir: "." } : undefined
57
+ reference: string,
58
+ options: { readonly launchDir: string; readonly kits: ReadonlyArray<DiscoveredKit> }
59
+ ): PackLocation => {
60
+ if (isAbsolute(reference)) {
61
+ return hasManifest(reference)
62
+ ? { _tag: "Located", root: reference, dir: "." }
63
+ : { _tag: "Absent" }
64
+ }
65
+ const launchDir = resolve(options.launchDir)
66
+ if (reference.includes("/") && hasManifest(join(launchDir, reference))) {
67
+ return { _tag: "Located", root: launchDir, dir: reference }
39
68
  }
40
- for (const root of roots) {
41
- const resolved = resolve(root)
42
- if (existsSync(join(resolved, packDir, "pack.md"))) {
43
- return { root: resolved, dir: packDir }
44
- }
69
+ const lookup = findPack(reference, options.kits)
70
+ switch (lookup._tag) {
71
+ case "Found":
72
+ return {
73
+ _tag: "Located",
74
+ root: lookup.kit.root,
75
+ dir: join("packs", lookup.pack),
76
+ kit: lookup.kit
77
+ }
78
+ case "Ambiguous":
79
+ return lookup
80
+ case "Absent":
81
+ return lookup
45
82
  }
46
- return undefined
47
83
  }
48
84
 
49
85
  export interface OpenedPack {
50
86
  readonly pack: Pack
51
87
  /**
52
- * Workspace rooted where the pack was found. Pack-relative paths prompts,
53
- * `<pack>/patterns`, `lessons.md`, the scaffoldmust resolve through this
54
- * workspace, not the launch directory's.
88
+ * Workspace rooted where the pack was found the kit for a kit pack, the
89
+ * pack directory itself otherwise. Pack-relative pathsprompts,
90
+ * `<pack>/patterns`, `lessons.md`, the scaffold — must resolve through
91
+ * this workspace, not the launch directory's.
55
92
  */
56
93
  readonly workspace: WorkspaceShape
57
94
  /** Pack directory within `workspace`. */
58
95
  readonly dir: string
96
+ /** The kit the pack belongs to, when it came from one. */
97
+ readonly kit?: DiscoveredKit
59
98
  }
60
99
 
100
+ export const defaultPackName = "cobol-springboot"
101
+
61
102
  export const openPack = Effect.fn("@llm4ts/runner/Packs.openPack")(function* (options: {
62
103
  readonly environment: Readonly<Record<string, string | undefined>>
63
104
  /** The directory the flow was launched from (`resolveFlowInput().workspace`). */
64
105
  readonly launchDir: string
65
106
  /** The directory holding the flow script (`import.meta.dirname`). */
66
107
  readonly flowDir: string
108
+ /** Kit tiers to search; defaults to the standard tiers around `launchDir` and `flowDir`. */
109
+ readonly kits?: KitTierPaths
67
110
  }): Effect.fn.Return<OpenedPack, PackNotFound | WorkspaceError | PlanParseError> {
68
- const packDir = options.environment.LLM4TS_PACK ?? "packs/cobol-springboot"
69
- const located = locatePack(packDir, [options.launchDir, options.flowDir])
70
- if (located === undefined) {
111
+ const reference = options.environment.LLM4TS_PACK?.trim() || defaultPackName
112
+ const builtinDir = builtinKitsDir(options.flowDir)
113
+ const tiers =
114
+ options.kits ??
115
+ kitTierPaths({
116
+ cwd: options.launchDir,
117
+ homeDir: homedir(),
118
+ environment: options.environment,
119
+ ...(builtinDir === undefined ? {} : { builtinDir })
120
+ })
121
+ const kits = discoverKits(tiers)
122
+ const location = locatePack(reference, { launchDir: options.launchDir, kits })
123
+ if (location._tag === "Ambiguous") {
71
124
  return yield* PackNotFound.make({
72
125
  message:
73
- `pack '${packDir}' not found: no ${join(packDir, "pack.md")} under ` +
74
- `${resolve(options.launchDir)} or ${resolve(options.flowDir)}` +
75
- "set LLM4TS_PACK or launch from a directory containing the pack"
126
+ `pack '${reference}' is shipped by more than one kit — ` +
127
+ `name one of: ${location.candidates.join(", ")}`
76
128
  })
77
129
  }
78
- const workspace = yield* makeNodeWorkspace(located.root)
79
- const pack = yield* loadPack(workspace, located.dir)
80
- return { pack, workspace, dir: located.dir }
130
+ if (location._tag === "Absent") {
131
+ const known = kits.flatMap((kit) => kit.packs.map((pack) => `${kit.name}/${pack}`))
132
+ return yield* PackNotFound.make({
133
+ message:
134
+ `pack '${reference}' not found: no kit ships it` +
135
+ (known.length === 0 ? "" : ` (known packs: ${known.join(", ")})`) +
136
+ ` and no ${join(reference, "pack.md")} exists under ${resolve(options.launchDir)} — ` +
137
+ "set LLM4TS_PACK to a kit pack name, a kit/pack name, or a directory holding pack.md"
138
+ })
139
+ }
140
+ const workspace = yield* makeNodeWorkspace(location.root)
141
+ const pack = yield* loadPack(workspace, location.dir)
142
+ return {
143
+ pack,
144
+ workspace,
145
+ dir: location.dir,
146
+ ...(location.kit === undefined ? {} : { kit: location.kit })
147
+ }
81
148
  })
82
149
 
83
150
  /**
84
- * The universal pattern cards: the first candidate root with a `patterns/`
85
- * directory contributes them, and no root having one is an empty set —
86
- * patterns are optional everywhere.
151
+ * The pattern cards of the kit the opened pack belongs to (`<kit>/patterns`);
152
+ * a path-form pack, or a kit without a deck, contributes none. Pack-local
153
+ * cards (`<pack>/patterns`) are loaded separately by the flows.
87
154
  */
88
- export const loadUniversalPatternCards = Effect.fn(
89
- "@llm4ts/runner/Packs.loadUniversalPatternCards"
90
- )(function* (
91
- roots: ReadonlyArray<string>
155
+ export const loadKitPatternCards = Effect.fn("@llm4ts/runner/Packs.loadKitPatternCards")(function* (
156
+ opened: OpenedPack
92
157
  ): Effect.fn.Return<ReadonlyArray<PatternCard>, WorkspaceError> {
93
- const root = roots
94
- .map((candidate) => resolve(candidate))
95
- .find((candidate) => existsSync(join(candidate, "patterns")))
96
- if (root === undefined) {
158
+ if (opened.kit === undefined || !existsSync(join(opened.kit.root, "patterns"))) {
97
159
  return []
98
160
  }
99
- const workspace = yield* makeNodeWorkspace(root)
100
- return yield* loadPatternCards(workspace, "patterns")
161
+ return yield* loadPatternCards(opened.workspace, "patterns")
101
162
  })
package/src/index.ts ADDED
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The flow author's entry point: everything a flow script under
3
+ * `.llm4ts/flows/` reaches for, re-exported from the runner and flow
4
+ * subpaths so a script needs one import line. Nothing here is new API — the
5
+ * subpath exports stay the contract for everything else, and the list is
6
+ * exactly what the shipped flows use.
7
+ */
8
+ export { runEmbedded, runFlowMain, runNode } from "./FlowRunner.ts"
9
+ export type { FlowRunnerOptions } from "./FlowRunner.ts"
10
+ export { resolveFlowInput, ScriptUsage } from "./FlowArgs.ts"
11
+ export type { FlowInput } from "./FlowArgs.ts"
12
+ export {
13
+ apiConnectorFromEnvironment,
14
+ asReadOnly,
15
+ coderFor,
16
+ coderFromEnv,
17
+ mock,
18
+ withModel,
19
+ withTurnLimit
20
+ } from "./Connectors.ts"
21
+ export { loadKitPatternCards, openPack, PackNotFound } from "./Packs.ts"
22
+ export type { OpenedPack } from "./Packs.ts"
23
+ export { nodePlainFileStore } from "./NodePlainFileStore.ts"
24
+ export { makeNodeWorkspace } from "./NodeWorkspace.ts"
25
+ export { nodeProcessExecutor } from "./NodeProcessExecutor.ts"
26
+ export { reviewFingerprint } from "./ReviewFingerprint.ts"
27
+
28
+ export { completeAndPublish, implementPlanFlow } from "@llm4ts/flow/Flow"
29
+ export type { ImplementPlanOptions } from "@llm4ts/flow/Flow"
30
+ export type { FlowContextShape } from "@llm4ts/flow/FlowContext"
31
+ export { implementTaskLoop, stage } from "@llm4ts/flow/PlanExecution"
32
+ export {
33
+ lintCommand,
34
+ mergeReviewResults,
35
+ minimalReviewers,
36
+ reviewAndFixLoop,
37
+ ReviewResult
38
+ } from "@llm4ts/flow/Review"
39
+ export { Reviewer } from "@llm4ts/flow/Reviewer"
40
+ export { makeChat } from "@llm4ts/flow/Chat"
41
+ export { makePlanStore } from "@llm4ts/flow/Persistence"
42
+ export { defaultPlanInstructions, planFrom, writeBrief } from "@llm4ts/flow/Planner"
43
+ export { defaultPlanPath, parsePlan, Plan } from "@llm4ts/flow/Plan"
44
+ export { AssistantMessage, Info } from "@llm4ts/flow/FlowEvents"
45
+ export { FlowAborted, FlowLlmError } from "@llm4ts/flow/FlowError"
46
+ export { loadPack } from "@llm4ts/flow/Pack"