@pmelab/gtd 59.0.1 → 60.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +48 -32
- package/claude/hooks/doors.ts +8 -4
- package/dist/chunks/flows-DENt0uWV.mjs +707 -0
- package/dist/chunks/{steering-CCNvngSH.mjs → steering-DF_BMadH.mjs} +292 -289
- package/dist/chunks/testkit-BhTey1Do.mjs +30517 -0
- package/dist/chunks/{wire-BTUhRGXj.mjs → wire-DZ8R1RU9.mjs} +8 -1
- package/dist/flows.mjs +2 -676
- package/dist/gtd.bundle.mjs +11285 -19864
- package/dist/gtd.core.mjs +593 -0
- package/dist/steering.mjs +2 -2
- package/dist/testkit/gherkin.mjs +246 -37
- package/dist/testkit.mjs +3 -2
- package/dist/web/main.js +234 -232
- package/dist/wire.mjs +2 -2
- package/dist/workflow-kit.mjs +3 -3
- package/package.json +7 -7
- package/skills/authoring/SKILL.md +66 -60
- package/src/cli/Cli.test.ts +185 -184
- package/src/cli/Cli.ts +219 -224
- package/src/cli/History.ts +9 -4
- package/src/cli/Judges.ts +1 -1
- package/src/cli/Output.test.ts +35 -0
- package/src/cli/Output.ts +34 -3
- package/src/cli/Program.test.ts +78 -84
- package/src/cli/Program.ts +192 -128
- package/src/cli/ToolCommands.ts +2 -2
- package/src/edge/Edge.test.ts +72 -62
- package/src/edge/Edge.ts +6 -6
- package/src/flows/factory.test.ts +14 -0
- package/src/flows/factory.ts +22 -5
- package/src/flows/helpers.test.ts +35 -0
- package/src/flows/helpers.ts +19 -10
- package/src/flows/index.test.ts +66 -0
- package/src/flows/index.ts +109 -5
- package/src/flows/internal.ts +7 -0
- package/src/flows/runtime.test.ts +192 -1
- package/src/flows/runtime.ts +189 -55
- package/src/gherkin/git-control.feature +6 -6
- package/src/gherkin/index.test.ts +127 -0
- package/src/gherkin/index.ts +370 -51
- package/src/gherkin/messages.test.ts +38 -4
- package/src/gherkin/messages.ts +21 -4
- package/src/gherkin/more-steps.feature +170 -0
- package/src/gherkin/scenario-end.feature +101 -0
- package/src/gherkin/testkit.feature +83 -7
- package/src/install/Install.test.ts +7 -7
- package/src/install/Install.ts +8 -8
- package/src/install/Version.ts +9 -3
- package/src/install/index.ts +1 -1
- package/src/platform/CommandRunner.ts +13 -11
- package/src/platform/Commentary.ts +2 -2
- package/src/platform/Git.test.ts +4 -5
- package/src/platform/Git.ts +42 -38
- package/src/platform/Host.ts +1 -1
- package/src/platform/NodeServices.ts +11 -0
- package/src/platform/Workspace.test.ts +4 -5
- package/src/platform/Workspace.ts +3 -3
- package/src/platform/WorktreeState.test.ts +2 -3
- package/src/platform/WorktreeState.ts +1 -1
- package/src/platform/index.ts +2 -1
- package/src/replay/Replay.test.ts +13 -2
- package/src/replay/Replay.ts +19 -30
- package/src/replay/judgeDiffShape.test.ts +1 -1
- package/src/steering/Footnotes.test.ts +1 -1
- package/src/steering/SteeringFormats.test.ts +1 -1
- package/src/steering/freeform.test.ts +1 -1
- package/src/steering/index.test.ts +27 -1
- package/src/steering/index.ts +6 -104
- package/src/steering/internal.ts +11 -0
- package/src/steering/qa.test.ts +7 -2
- package/src/steering/reads.test.ts +42 -0
- package/src/steering/reads.ts +43 -0
- package/src/steering/registry.ts +93 -0
- package/src/steering/review.test.ts +2 -2
- package/src/step/planStart.test.ts +18 -16
- package/src/step/planStart.ts +12 -12
- package/src/testkit/TestRepo.test.ts +245 -7
- package/src/testkit/TestRepo.ts +271 -40
- package/src/testkit/imports.test.ts +58 -0
- package/src/testkit/imports.ts +102 -0
- package/src/testkit/index.test.ts +28 -0
- package/src/testkit/index.ts +14 -2
- package/src/twins/EmittedScriptRecognizer.ts +2 -6
- package/src/twins/EmittedScripts.test.ts +5 -3
- package/src/twins/GitTiers.ts +6 -7
- package/src/twins/InMemRepo.ts +1 -0
- package/src/twins/Layers.test.ts +1 -1
- package/src/twins/Layers.ts +10 -3
- package/src/twins/cliIo.ts +9 -4
- package/src/wire/Script.ts +11 -0
- package/src/wire/WireSchema.test.ts +3 -0
- package/src/wire/golden/1/script.json +4 -0
- package/src/wire/index.ts +1 -0
- package/src/workflow/SteeringFormats.test.ts +1 -1
- package/src/workflow/compile.ts +2 -2
- package/src/workflow/discovery.test.ts +27 -10
- package/src/workflow/discovery.ts +5 -6
- package/src/workflow/index.ts +1 -0
- package/src/workflow/load.test.ts +316 -119
- package/src/workflow/load.ts +478 -305
- package/src/workflow/pin.test.ts +159 -0
- package/src/workflow/pin.ts +72 -0
- package/src/workflow/vars.ts +3 -3
- package/src/workflow-kit/index.test.ts +25 -0
- package/src/workflow-kit/index.ts +37 -4
- package/src/workflow-kit/review.ts +2 -2
- package/dist/chunks/testkit-07Z_ENch.mjs +0 -32175
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
formatFinding,
|
|
31
31
|
steeringFormatFor,
|
|
32
32
|
type SteeringFormat,
|
|
33
|
-
} from "../steering/
|
|
33
|
+
} from "../steering/internal.js"
|
|
34
34
|
import { refNameFault } from "../replay/index.js"
|
|
35
35
|
import { renderFormat } from "../wire/index.js"
|
|
36
36
|
import { applyFlowScript, rewritten } from "./FlowScripts.js"
|
|
@@ -518,12 +518,8 @@ const recognizeGtdCheck = (repo: InMemRepo, block: string): BlockOutcome | undef
|
|
|
518
518
|
const known = BUILT_IN_MODE_NAMES.join(", ")
|
|
519
519
|
return { kind: "failed", error: `gtd check: unknown mode "${mode}" — known modes: ${known}` }
|
|
520
520
|
}
|
|
521
|
-
// An ABSENT file has nothing to check and exits 0 — `runCheckCommand`'s own
|
|
522
|
-
// documented behavior. Validating `""` instead would report every "missing
|
|
523
|
-
// header" finding the format has, turning "the reviewer deleted the file"
|
|
524
|
-
// (a case the review-doc guard owns) into a script failure.
|
|
525
521
|
const content = repo.readFile(file)
|
|
526
|
-
if (content === undefined) return { kind: "
|
|
522
|
+
if (content === undefined) return { kind: "failed", error: `gtd check: no such file: ${file}` }
|
|
527
523
|
const findings = format.validate(content).map((finding) => formatFinding(file, finding))
|
|
528
524
|
if (findings.length === 0) return { kind: "noop" }
|
|
529
525
|
return {
|
|
@@ -140,8 +140,10 @@ for (const makeTier of gitTiers) {
|
|
|
140
140
|
})
|
|
141
141
|
|
|
142
142
|
describe("gtd check", () => {
|
|
143
|
-
it("
|
|
144
|
-
|
|
143
|
+
it("refuses an absent file, and passes a valid one", () => {
|
|
144
|
+
const absent = run(script(`gtd check qa '${FILE}'`))
|
|
145
|
+
expect(absent).toMatchObject({ ok: false, status: 1 })
|
|
146
|
+
expect(absent.output).toContain(`gtd check: no such file: ${FILE}`)
|
|
145
147
|
t.seed.writeFile(FILE, "## Open Questions\n")
|
|
146
148
|
expect(run(script(`gtd check qa '${FILE}'`))).toEqual({ ok: true, status: 0, output: "" })
|
|
147
149
|
})
|
|
@@ -178,7 +180,7 @@ for (const makeTier of gitTiers) {
|
|
|
178
180
|
// As a landing renders it: under `set -eu`, which a failing update-ref relies on to stop.
|
|
179
181
|
const keepTurn = (ref: string, message: string) =>
|
|
180
182
|
ScriptSurface.render([{ kind: "gitWrite", write: { kind: "keepTurn", ref, message } }])
|
|
181
|
-
const git = <A>(f: (g: GitService["
|
|
183
|
+
const git = <A>(f: (g: GitService["Service"]) => Effect.Effect<A, Error>) =>
|
|
182
184
|
t.provide(Effect.flatMap(GitService, f))
|
|
183
185
|
const keptTip = async () => {
|
|
184
186
|
const marker = await git((g) => g.lastCommitMessage())
|
package/src/twins/GitTiers.ts
CHANGED
|
@@ -18,8 +18,7 @@ import { execFileSync, execSync, spawnSync } from "node:child_process"
|
|
|
18
18
|
// `TEST_DOUBLE_SENTINEL` check.
|
|
19
19
|
import { afterEach, beforeEach, describe, expect, it } from "vitest"
|
|
20
20
|
import { Effect, Exit, Option } from "effect"
|
|
21
|
-
import {
|
|
22
|
-
import { Narrator, GitService, Host, type GitOperations } from "../platform/index.js"
|
|
21
|
+
import { Narrator, GitService, Host, type GitOperations, NodeServices } from "../platform/index.js"
|
|
23
22
|
import {
|
|
24
23
|
commitAll,
|
|
25
24
|
commitAsIs,
|
|
@@ -165,7 +164,7 @@ const makeLiveTier = (initialCommit = true): GitTier => {
|
|
|
165
164
|
eff.pipe(
|
|
166
165
|
Effect.provide(GitService.Live),
|
|
167
166
|
Effect.provide(Host.layer({ root, home: root, env: {} })),
|
|
168
|
-
Effect.provide(
|
|
167
|
+
Effect.provide(NodeServices.layer),
|
|
169
168
|
Effect.provide(noopNarratorLayer),
|
|
170
169
|
),
|
|
171
170
|
)
|
|
@@ -177,7 +176,7 @@ const makeLiveTier = (initialCommit = true): GitTier => {
|
|
|
177
176
|
eff.pipe(
|
|
178
177
|
Effect.provide(GitService.Live),
|
|
179
178
|
Effect.provide(Host.layer({ root, home: root, env: {} })),
|
|
180
|
-
Effect.provide(
|
|
179
|
+
Effect.provide(NodeServices.layer),
|
|
181
180
|
Effect.provide(noopNarratorLayer),
|
|
182
181
|
),
|
|
183
182
|
)
|
|
@@ -1266,7 +1265,7 @@ export const runGitServiceContract = (makeTier: () => GitTier): void => {
|
|
|
1266
1265
|
Effect.flatMap(GitService, (g) => g.gitDir()).pipe(
|
|
1267
1266
|
Effect.provide(GitService.Live),
|
|
1268
1267
|
Effect.provide(Host.layer({ root: siblingDir, home: siblingDir, env: {} })),
|
|
1269
|
-
Effect.provide(
|
|
1268
|
+
Effect.provide(NodeServices.layer),
|
|
1270
1269
|
),
|
|
1271
1270
|
)
|
|
1272
1271
|
expect(siblingGitDir).not.toBe(mainGitDir)
|
|
@@ -1297,14 +1296,14 @@ export const runGitServiceContract = (makeTier: () => GitTier): void => {
|
|
|
1297
1296
|
Effect.flatMap(GitService, (g) => g.gitCommonDir()).pipe(
|
|
1298
1297
|
Effect.provide(GitService.Live),
|
|
1299
1298
|
Effect.provide(Host.layer({ root: siblingDir, home: siblingDir, env: {} })),
|
|
1300
|
-
Effect.provide(
|
|
1299
|
+
Effect.provide(NodeServices.layer),
|
|
1301
1300
|
),
|
|
1302
1301
|
)
|
|
1303
1302
|
const siblingGitDir = await Effect.runPromise(
|
|
1304
1303
|
Effect.flatMap(GitService, (g) => g.gitDir()).pipe(
|
|
1305
1304
|
Effect.provide(GitService.Live),
|
|
1306
1305
|
Effect.provide(Host.layer({ root: siblingDir, home: siblingDir, env: {} })),
|
|
1307
|
-
Effect.provide(
|
|
1306
|
+
Effect.provide(NodeServices.layer),
|
|
1308
1307
|
),
|
|
1309
1308
|
)
|
|
1310
1309
|
expect(siblingCommonDir).toBe(mainCommonDir)
|
package/src/twins/InMemRepo.ts
CHANGED
|
@@ -30,6 +30,7 @@ function makeHash(message: string, parent: string | null, tree: Map<string, stri
|
|
|
30
30
|
export class InMemRepo {
|
|
31
31
|
// Data-only marker read by no code path: embeds the sentinel in every
|
|
32
32
|
// instance so a leaked object still carries it.
|
|
33
|
+
// fallow-ignore-next-line unused-class-member
|
|
33
34
|
readonly testDouble = TEST_DOUBLE_SENTINEL
|
|
34
35
|
|
|
35
36
|
private commits: Map<string, Commit> = new Map()
|
package/src/twins/Layers.test.ts
CHANGED
|
@@ -23,7 +23,7 @@ const seed = (repo: InMemRepo, dir: string, name: string, content: string): void
|
|
|
23
23
|
|
|
24
24
|
const runDiscovery = <A>(
|
|
25
25
|
repo: InMemRepo,
|
|
26
|
-
f: (d: ConfigDiscovery["
|
|
26
|
+
f: (d: ConfigDiscovery["Service"]) => Effect.Effect<A, Error>,
|
|
27
27
|
) =>
|
|
28
28
|
Effect.runPromise(
|
|
29
29
|
Effect.gen(function* () {
|
package/src/twins/Layers.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
import {
|
|
22
22
|
ConfigDiscovery,
|
|
23
23
|
ConfigService,
|
|
24
|
+
EngineCore,
|
|
24
25
|
LEGACY_RC_FILES,
|
|
25
26
|
legacyRcDirs,
|
|
26
27
|
WORKFLOW_MODULE,
|
|
@@ -30,7 +31,7 @@ import { fakeGitOperations } from "./FakeGitOperations.js"
|
|
|
30
31
|
import { InMemRepo } from "./InMemRepo.js"
|
|
31
32
|
|
|
32
33
|
// Read off the CLI's own port rather than imported from the composition root.
|
|
33
|
-
type CommandRequirements = Layer.
|
|
34
|
+
type CommandRequirements = Layer.Success<ReturnType<CliIo["layers"]>>
|
|
34
35
|
import { ToolCommands, type CliIo } from "../cli/index.js"
|
|
35
36
|
|
|
36
37
|
const toError = (e: unknown): Error => (e instanceof Error ? e : new Error(String(e)))
|
|
@@ -167,6 +168,11 @@ const makeInMemoryConfigDiscovery = (
|
|
|
167
168
|
})
|
|
168
169
|
}
|
|
169
170
|
|
|
171
|
+
/** The in-memory counterpart to the shipped `gtd.core.mjs`: an ES module that needs nothing but Node. */
|
|
172
|
+
const IN_MEMORY_ENGINE = `#!/usr/bin/env node
|
|
173
|
+
import "node:process"
|
|
174
|
+
`
|
|
175
|
+
|
|
170
176
|
/** One scripted `bash` command's canned behavior, keyed by the RENDERED command string a scenario's `Given` step declares. */
|
|
171
177
|
export type ScriptedCommand =
|
|
172
178
|
| {
|
|
@@ -207,13 +213,13 @@ export interface TestWorldOptions {
|
|
|
207
213
|
/** Gates `narrate` above — defaults to `true` (fires whenever a sink is given) since these tests aren't exercising `Cli.ts`'s own `--verbose` gate. `src/twins/cliIo.ts` overrides this with the real decoded flag. */
|
|
208
214
|
readonly verbose?: boolean
|
|
209
215
|
/** Real or fake driver and tool commands — each one left out fails when called. */
|
|
210
|
-
readonly tools?: Partial<ToolCommands["
|
|
216
|
+
readonly tools?: Partial<ToolCommands["Service"]>
|
|
211
217
|
}
|
|
212
218
|
|
|
213
219
|
const missingTool = (name: string) => () =>
|
|
214
220
|
Effect.fail(new Error(`gtd ${name}: ToolCommands has no test double wired into testLayers()`))
|
|
215
221
|
|
|
216
|
-
const missingToolCommands: ToolCommands["
|
|
222
|
+
const missingToolCommands: ToolCommands["Service"] = {
|
|
217
223
|
ui: missingTool("ui"),
|
|
218
224
|
lsp: missingTool("lsp"),
|
|
219
225
|
judge: () =>
|
|
@@ -264,6 +270,7 @@ export function testLayers(
|
|
|
264
270
|
}),
|
|
265
271
|
ConfigService.Live,
|
|
266
272
|
makeInMemoryConfigDiscovery(repo, root),
|
|
273
|
+
Layer.succeed(EngineCore, { source: Effect.succeed(IN_MEMORY_ENGINE) }),
|
|
267
274
|
Narrator.layer(opts.narrate ?? (() => {}), opts.verbose ?? true),
|
|
268
275
|
// Drivers and tools are unit-tested in their own folders; a call reaching
|
|
269
276
|
// one of these is a test gap, not silence. A test opts into one via `tools`.
|
package/src/twins/cliIo.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { testLayers } from "./Layers.js"
|
|
|
4
4
|
|
|
5
5
|
export interface CapturedCliResult {
|
|
6
6
|
readonly stdout: string
|
|
7
|
+
/** stdout as written, for a binary artifact `stdout` would decode. */
|
|
8
|
+
readonly stdoutBytes: Uint8Array
|
|
7
9
|
readonly stderr: string
|
|
8
10
|
readonly exitCode: number
|
|
9
11
|
}
|
|
@@ -11,14 +13,14 @@ export interface CapturedCliResult {
|
|
|
11
13
|
export const makeCapturingCliIo = (
|
|
12
14
|
repo: InMemRepo,
|
|
13
15
|
env: Readonly<Record<string, string | undefined>> = {},
|
|
14
|
-
tools: Partial<ToolCommands["
|
|
16
|
+
tools: Partial<ToolCommands["Service"]> = {},
|
|
15
17
|
): { readonly io: CliIo; readonly result: () => CapturedCliResult } => {
|
|
16
|
-
let stdout =
|
|
18
|
+
let stdout: Buffer = Buffer.alloc(0)
|
|
17
19
|
let stderr = ""
|
|
18
20
|
let exitCode = 0
|
|
19
21
|
const io: CliIo = {
|
|
20
22
|
stdout: (chunk) => {
|
|
21
|
-
stdout
|
|
23
|
+
stdout = Buffer.concat([stdout, Buffer.from(chunk)])
|
|
22
24
|
},
|
|
23
25
|
stderr: (chunk) => {
|
|
24
26
|
stderr += chunk
|
|
@@ -32,5 +34,8 @@ export const makeCapturingCliIo = (
|
|
|
32
34
|
layers: (verbose) =>
|
|
33
35
|
testLayers(repo, { env, narrate: (line) => (stderr += line), verbose, tools }),
|
|
34
36
|
}
|
|
35
|
-
return {
|
|
37
|
+
return {
|
|
38
|
+
io,
|
|
39
|
+
result: () => ({ stdout: stdout.toString("utf8"), stdoutBytes: stdout, stderr, exitCode }),
|
|
40
|
+
}
|
|
36
41
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { WIRE_SCHEMA } from "./constants.js"
|
|
2
|
+
|
|
3
|
+
/** `--json` of every command that prints a script and nothing else to decide on: abandon, restore, start, door, validate, history fetch. */
|
|
4
|
+
export interface ScriptFields {
|
|
5
|
+
readonly schema: number
|
|
6
|
+
readonly script: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const scriptFields = (script: string): ScriptFields => ({ schema: WIRE_SCHEMA, script })
|
|
10
|
+
|
|
11
|
+
export const renderScriptJson = (fields: ScriptFields): string => JSON.stringify(fields) + "\n"
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
landFields,
|
|
10
10
|
renderBeatJson,
|
|
11
11
|
renderLandJson,
|
|
12
|
+
renderScriptJson,
|
|
13
|
+
scriptFields,
|
|
12
14
|
statusOf,
|
|
13
15
|
WIRE_SCHEMA,
|
|
14
16
|
type BeatKind,
|
|
@@ -116,6 +118,7 @@ const documents: Readonly<Record<string, string>> = {
|
|
|
116
118
|
}),
|
|
117
119
|
),
|
|
118
120
|
"judge.json": JUDGE,
|
|
121
|
+
"script.json": renderScriptJson(scriptFields("git reset --mixed ...\n")),
|
|
119
122
|
}
|
|
120
123
|
|
|
121
124
|
describe(`wire schema ${WIRE_SCHEMA}`, () => {
|
package/src/wire/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from "./BeatStatus.js"
|
|
|
6
6
|
export * from "./BeatDocument.js"
|
|
7
7
|
export * from "./BeatPlain.js"
|
|
8
8
|
export * from "./Land.js"
|
|
9
|
+
export * from "./Script.js"
|
|
9
10
|
export * from "./Judge.js"
|
|
10
11
|
export { selectPath } from "./Select.js"
|
|
11
12
|
export { resolveSession } from "./Sessions.js"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest"
|
|
2
2
|
import fc from "fast-check"
|
|
3
3
|
import { isSeededValidateCommand, seededValidateCommand } from "./SteeringFormats.js"
|
|
4
|
-
import { BUILT_IN_MODE_NAMES } from "../steering/
|
|
4
|
+
import { BUILT_IN_MODE_NAMES } from "../steering/internal.js"
|
|
5
5
|
|
|
6
6
|
describe("seededValidateCommand / isSeededValidateCommand", () => {
|
|
7
7
|
it("recognizes its own seeded command for every built-in mode", () => {
|
package/src/workflow/compile.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { seededValidateCommand } from "./SteeringFormats.js"
|
|
2
|
-
import { BUILT_IN_MODE_NAMES } from "../steering/
|
|
2
|
+
import { BUILT_IN_MODE_NAMES } from "../steering/internal.js"
|
|
3
3
|
import type { ModeDef } from "./Workflow.js"
|
|
4
|
-
import { configProblems, type GtdConfig, type UiSettings } from "../flows/
|
|
4
|
+
import { configProblems, type GtdConfig, type UiSettings } from "../flows/internal.js"
|
|
5
5
|
import type { Diagnostic } from "./Diagnostic.js"
|
|
6
6
|
|
|
7
7
|
export interface CompiledConfig {
|
|
@@ -2,15 +2,14 @@ import { execSync } from "node:child_process"
|
|
|
2
2
|
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs"
|
|
3
3
|
import { tmpdir } from "node:os"
|
|
4
4
|
import { dirname, join } from "node:path"
|
|
5
|
-
import { NodeContext } from "@effect/platform-node"
|
|
6
5
|
import { Effect, Layer } from "effect"
|
|
7
6
|
import { afterEach, describe, expect, it } from "vitest"
|
|
8
|
-
import { GitService, Host, Narrator, Workspace } from "../platform/index.js"
|
|
7
|
+
import { GitService, Host, Narrator, Workspace, NodeServices } from "../platform/index.js"
|
|
9
8
|
import { InMemRepo, testLayers } from "../twins/index.js"
|
|
10
9
|
import { ConfigDiscovery, ConfigService } from "./index.js"
|
|
11
10
|
|
|
12
11
|
// A config and the local modules it imports load the same from disk, where
|
|
13
|
-
//
|
|
12
|
+
// rolldown resolves them, and from an in-memory discovery, where the loader
|
|
14
13
|
// resolves them itself — the same workflows, the same fingerprint, the same
|
|
15
14
|
// failure for a module that is not there.
|
|
16
15
|
type Files = Readonly<Record<string, string>>
|
|
@@ -22,15 +21,15 @@ interface Tier {
|
|
|
22
21
|
|
|
23
22
|
const config = (imports: string): string =>
|
|
24
23
|
[
|
|
25
|
-
'import { agent, human } from "@pmelab/gtd/flows"',
|
|
24
|
+
'import { agent, human, workflow } from "@pmelab/gtd/flows"',
|
|
26
25
|
imports,
|
|
27
26
|
"export default async () => {",
|
|
28
27
|
' await human("idle", { message: "go" })',
|
|
29
28
|
' await agent("working", brief)',
|
|
30
29
|
"}",
|
|
31
|
-
"export const named = async () => {",
|
|
30
|
+
"export const named = workflow(async () => {",
|
|
32
31
|
' await agent("working", brief)',
|
|
33
|
-
"}",
|
|
32
|
+
"})",
|
|
34
33
|
"",
|
|
35
34
|
].join("\n")
|
|
36
35
|
|
|
@@ -63,7 +62,7 @@ const live = (): Tier => {
|
|
|
63
62
|
writeFileSync(join(root, path), content)
|
|
64
63
|
}
|
|
65
64
|
const host = Host.layer({ root, home: root, env: {} })
|
|
66
|
-
const git = GitService.Live.pipe(Layer.provide(Layer.merge(host,
|
|
65
|
+
const git = GitService.Live.pipe(Layer.provide(Layer.merge(host, NodeServices.layer)))
|
|
67
66
|
const workspace = Workspace.Live.pipe(Layer.provide(Layer.merge(host, git)))
|
|
68
67
|
return loadWith(
|
|
69
68
|
Layer.mergeAll(
|
|
@@ -121,7 +120,24 @@ for (const make of [live, inMemory]) {
|
|
|
121
120
|
})
|
|
122
121
|
})
|
|
123
122
|
|
|
124
|
-
it("
|
|
123
|
+
it("imports a local module and a gtd subpath dynamically as the flow runs", async () => {
|
|
124
|
+
t = make()
|
|
125
|
+
const loaded = await t.load({
|
|
126
|
+
"gtd.config.ts": [
|
|
127
|
+
'import { human } from "@pmelab/gtd/flows"',
|
|
128
|
+
"export default async () => {",
|
|
129
|
+
' const { WIRE_SCHEMA } = await import("@pmelab/gtd/wire")',
|
|
130
|
+
' const { first } = await import("./first.js")',
|
|
131
|
+
" await human(`${first}-${WIRE_SCHEMA}`)",
|
|
132
|
+
"}",
|
|
133
|
+
"",
|
|
134
|
+
].join("\n"),
|
|
135
|
+
"first.ts": 'export const first = "idle"\n',
|
|
136
|
+
})
|
|
137
|
+
expect(Object.keys(loaded.fingerprint).sort()).toEqual(["first.ts", "gtd.config.ts"])
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it("leaves a specifier in a string alone, and runs a skipped require never", async () => {
|
|
125
141
|
t = make()
|
|
126
142
|
const loaded = await t.load({
|
|
127
143
|
"gtd.config.ts": config(
|
|
@@ -134,14 +150,15 @@ for (const make of [live, inMemory]) {
|
|
|
134
150
|
"optional.ts": 'throw new Error("optional ran")\n',
|
|
135
151
|
})
|
|
136
152
|
expect(loaded.names).toEqual(["named"])
|
|
137
|
-
|
|
153
|
+
// Bundled, so watched, though it never runs.
|
|
154
|
+
expect(Object.keys(loaded.fingerprint).sort()).toEqual(["gtd.config.ts", "optional.ts"])
|
|
138
155
|
})
|
|
139
156
|
|
|
140
157
|
it("fails on an import that is not there, naming it", async () => {
|
|
141
158
|
t = make()
|
|
142
159
|
await expect(
|
|
143
160
|
t.load({ "gtd.config.ts": config('import { brief } from "./missing.js"') }),
|
|
144
|
-
).rejects.toThrow(/
|
|
161
|
+
).rejects.toThrow(/Could not resolve '\.\/missing\.js'/)
|
|
145
162
|
})
|
|
146
163
|
})
|
|
147
164
|
}
|
|
@@ -110,18 +110,17 @@ const workflowModule = (
|
|
|
110
110
|
})
|
|
111
111
|
|
|
112
112
|
/**
|
|
113
|
-
* The discovery seam — a `Context.
|
|
113
|
+
* The discovery seam — a `Context.Service`, not part of `WorkflowFiles` (that
|
|
114
114
|
* port is a plain sync record for `compileWorkflow`'s own content-file-ref
|
|
115
115
|
* reads; discovery is effectful and environment-dependent: `.Live` reads
|
|
116
116
|
* REAL disk, so an `@inmem` scenario's fake
|
|
117
117
|
* `Workspace` — a pure in-memory `Map`, no real files anywhere on disk —
|
|
118
118
|
* cannot back it. `src/twins/Layers.ts` provides the in-memory
|
|
119
119
|
* counterpart instead, mirroring how `Workspace`/`Host`/`GitService` already
|
|
120
|
-
* each have a production and a fake layer behind one
|
|
120
|
+
* each have a production and a fake layer behind one service.
|
|
121
121
|
*/
|
|
122
|
-
export class ConfigDiscovery extends Context.
|
|
123
|
-
ConfigDiscovery,
|
|
124
|
-
|
|
125
|
-
>() {
|
|
122
|
+
export class ConfigDiscovery extends Context.Service<ConfigDiscovery, ConfigDiscoveryOps>()(
|
|
123
|
+
"ConfigDiscovery",
|
|
124
|
+
) {
|
|
126
125
|
static Live = Layer.succeed(ConfigDiscovery, { legacyRcFiles, workflowModule })
|
|
127
126
|
}
|
package/src/workflow/index.ts
CHANGED