@pmelab/gtd 58.0.0 → 58.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 +3 -2
- package/dist/chunks/{Schema-C6fLlQzx.mjs → Schema-B3o6eRNU.mjs} +637 -3957
- package/dist/chunks/{reserved-BzAFXuz_.mjs → reserved-lMYCaapY.mjs} +1 -1
- package/dist/chunks/{review-CVqRS0mc.mjs → review-Bfaror38.mjs} +2 -2
- package/dist/chunks/{tail-JQzB21Q3.mjs → tail-Bd7L940p.mjs} +1 -1
- package/dist/chunks/testkit-D1-auYau.mjs +10362 -0
- package/dist/gtd.bundle.mjs +8 -8
- package/dist/testkit/gherkin.mjs +255 -0
- package/dist/testkit.mjs +2 -15461
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +3 -3
- package/dist/workflows/fix.mjs +2 -2
- package/dist/workflows/review.mjs +3 -3
- package/dist/workflows/rollout.mjs +1 -1
- package/package.json +8 -1
- package/src/cli/Cli.test.ts +1 -1
- package/src/cli/History.test.ts +1 -1
- package/src/cli/Metrics.test.ts +1 -1
- package/src/cli/Program.test.ts +1 -1
- package/src/cli/Strays.test.ts +1 -1
- package/src/edge/Edge.test.ts +1 -1
- package/src/emit/Emit.ts +1 -1
- package/src/emit/GitScript.ts +1 -1
- package/src/emit/OutcomeScript.ts +1 -1
- package/src/{testkit/steps.ts → gherkin/index.ts} +20 -3
- package/src/gherkin/module-config.feature +24 -0
- package/src/gherkin/tiny.fixture.ts +8 -0
- package/src/platform/Git.test.ts +2 -2
- package/src/platform/RetainedHistory.test.ts +2 -2
- package/src/platform/Workspace.test.ts +2 -2
- package/src/platform/WorktreeState.test.ts +1 -1
- package/src/step/planStart.test.ts +1 -1
- package/src/testkit/TestRepo.ts +7 -4
- package/src/testkit/index.ts +15 -9
- package/src/{testkit → twins}/Layers.ts +2 -2
- package/src/twins/index.ts +8 -0
- package/src/workflow/discovery.test.ts +1 -1
- package/src/workflow/discovery.ts +3 -3
- package/src/workflow/load.ts +3 -3
- /package/src/{testkit → gherkin}/testkit.feature +0 -0
- /package/src/{testkit → twins}/EmittedScriptRecognizer.test.ts +0 -0
- /package/src/{testkit → twins}/EmittedScriptRecognizer.ts +0 -0
- /package/src/{testkit → twins}/EmittedScripts.test.ts +0 -0
- /package/src/{testkit → twins}/FakeGitOperations.ts +0 -0
- /package/src/{testkit → twins}/FlowScripts.test.ts +0 -0
- /package/src/{testkit → twins}/FlowScripts.ts +0 -0
- /package/src/{testkit → twins}/GitTiers.test.ts +0 -0
- /package/src/{testkit → twins}/GitTiers.ts +0 -0
- /package/src/{testkit → twins}/InMemRepo.ts +0 -0
- /package/src/{testkit → twins}/Layers.test.ts +0 -0
- /package/src/{testkit → twins}/StrictGitOperations.ts +0 -0
- /package/src/{testkit → twins}/cliIo.ts +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { agent, changes, glob, read, refuse, scope, start, vars } from "../flows.mjs";
|
|
2
|
-
import { It as registryEntry, Lt as requireFlag, Pt as flagReferences, St as envDefaults, a as flagShipped, i as skills, n as summary, r as access, t as steering, x as coder, xt as defaults } from "../chunks/reserved-
|
|
3
|
-
import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-
|
|
2
|
+
import { It as registryEntry, Lt as requireFlag, Pt as flagReferences, St as envDefaults, a as flagShipped, i as skills, n as summary, r as access, t as steering, x as coder, xt as defaults } from "../chunks/reserved-lMYCaapY.mjs";
|
|
3
|
+
import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-Bd7L940p.mjs";
|
|
4
4
|
//#region src/workflows/cleanup/cleanup.ts
|
|
5
5
|
const removingPrompt = (name, registry) => `Clean up the release flag "${name}", which is default-on: delete its entry in ${registry}, and remove every \`GTD_FLAG_${name.toUpperCase()}\` check from the shipped sources. Keep the flag-on path; delete code only the off path reached, including tests that pin the off path.`;
|
|
6
6
|
const cleanupFlow = async () => {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { agent, answered, applyScript, changes, changesSince, check, codeChanges, env, hasThreadFor, head, human, judge, judgeDiffShape, kept, mark, matches, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireThreadsClosed, restoreScript, revertScript, run, scope, start, vars, wrote } from "../flows.mjs";
|
|
2
2
|
import { lensEntriesOf } from "../workflow-kit.mjs";
|
|
3
|
-
import { B as designSystem, Ct as testFileGlobs, D as planner, F as clearCriteriaMessage, I as clearCriteriaQuestion, J as implementBuildingPrompt, L as criterionLine, M as buildReviewCollectingPrompt, P as builderSystem, Q as naiveTryMessage, R as designGateAnswerMessage, S as collecting, St as envDefaults, V as designTriagePrompt, X as naiveApplyFailure, Y as implementFixPrompt, Z as naivePassPrompt, _ as REQUIREMENTS, _t as unwindFailedMessage, at as refactorFixPrompt, d as healthy, et as normaliseCriteriaScript, f as FEEDBACK, g as REFACTOR_NOTE, gt as startGateBlockedMessage, h as REFACTOR, ht as seamSteerMessage, i as skills, it as refactorDroppedRefusal, j as agentWithSkills, l as gate, mt as seamByHandMessage, n as summary, o as naiveTestOn, ot as refactorNotePrompt, p as NAIVE_TEST, pt as reviewerSystem, q as idleMessage, r as access, rt as refactorBuildingPrompt, s as shipped, st as refactorRules, t as steering, vt as unwindFailure, w as fixNits, x as coder, xt as defaults, y as answerReviewQuestions, z as designImpactPrompt } from "../chunks/reserved-
|
|
4
|
-
import { a as refusePendingHandOff } from "../chunks/tail-
|
|
5
|
-
import { n as isCode$1, r as reUnwind, t as buildTail } from "../chunks/review-
|
|
3
|
+
import { B as designSystem, Ct as testFileGlobs, D as planner, F as clearCriteriaMessage, I as clearCriteriaQuestion, J as implementBuildingPrompt, L as criterionLine, M as buildReviewCollectingPrompt, P as builderSystem, Q as naiveTryMessage, R as designGateAnswerMessage, S as collecting, St as envDefaults, V as designTriagePrompt, X as naiveApplyFailure, Y as implementFixPrompt, Z as naivePassPrompt, _ as REQUIREMENTS, _t as unwindFailedMessage, at as refactorFixPrompt, d as healthy, et as normaliseCriteriaScript, f as FEEDBACK, g as REFACTOR_NOTE, gt as startGateBlockedMessage, h as REFACTOR, ht as seamSteerMessage, i as skills, it as refactorDroppedRefusal, j as agentWithSkills, l as gate, mt as seamByHandMessage, n as summary, o as naiveTestOn, ot as refactorNotePrompt, p as NAIVE_TEST, pt as reviewerSystem, q as idleMessage, r as access, rt as refactorBuildingPrompt, s as shipped, st as refactorRules, t as steering, vt as unwindFailure, w as fixNits, x as coder, xt as defaults, y as answerReviewQuestions, z as designImpactPrompt } from "../chunks/reserved-lMYCaapY.mjs";
|
|
4
|
+
import { a as refusePendingHandOff } from "../chunks/tail-Bd7L940p.mjs";
|
|
5
|
+
import { n as isCode$1, r as reUnwind, t as buildTail } from "../chunks/review-Bfaror38.mjs";
|
|
6
6
|
import { posix } from "node:path";
|
|
7
7
|
//#region src/workflows/shared/fences.ts
|
|
8
8
|
const markFenced = (text) => {
|
package/dist/workflows/fix.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { read, refuse, removeScript, run, scope, start } from "../flows.mjs";
|
|
2
2
|
import { b as reviewNotes, x as reviewRisks } from "../chunks/steering-CCNvngSH.mjs";
|
|
3
|
-
import { G as fixReviewNotesPrompt, St as envDefaults, W as finisherSystem, c as baseline, i as skills, j as agentWithSkills, n as summary, r as access, s as shipped, t as steering, v as REVIEW, x as coder, xt as defaults } from "../chunks/reserved-
|
|
4
|
-
import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-
|
|
3
|
+
import { G as fixReviewNotesPrompt, St as envDefaults, W as finisherSystem, c as baseline, i as skills, j as agentWithSkills, n as summary, r as access, s as shipped, t as steering, v as REVIEW, x as coder, xt as defaults } from "../chunks/reserved-lMYCaapY.mjs";
|
|
4
|
+
import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-Bd7L940p.mjs";
|
|
5
5
|
//#region src/workflows/fix/fix.ts
|
|
6
6
|
/**
|
|
7
7
|
* Fix every note a human left in a handed-off `.gtd/REVIEW.md` and every
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { read, refuse, removeScript, run, start } from "../flows.mjs";
|
|
2
|
-
import { St as envDefaults, W as finisherSystem, _ as REQUIREMENTS, dt as reviewGateBlockedMessage, i as skills, j as agentWithSkills, l as gate, n as summary, r as access, s as shipped, t as steering, ut as reviewFeedbackFixPrompt, x as coder, xt as defaults } from "../chunks/reserved-
|
|
3
|
-
import { a as refusePendingHandOff } from "../chunks/tail-
|
|
4
|
-
import { r as reUnwind, t as buildTail } from "../chunks/review-
|
|
2
|
+
import { St as envDefaults, W as finisherSystem, _ as REQUIREMENTS, dt as reviewGateBlockedMessage, i as skills, j as agentWithSkills, l as gate, n as summary, r as access, s as shipped, t as steering, ut as reviewFeedbackFixPrompt, x as coder, xt as defaults } from "../chunks/reserved-lMYCaapY.mjs";
|
|
3
|
+
import { a as refusePendingHandOff } from "../chunks/tail-Bd7L940p.mjs";
|
|
4
|
+
import { r as reUnwind, t as buildTail } from "../chunks/review-Bfaror38.mjs";
|
|
5
5
|
//#region src/workflows/review/review.ts
|
|
6
6
|
const fixFeedback = async () => {
|
|
7
7
|
await agentWithSkills("feedback.fixing", reviewFeedbackFixPrompt(), {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { agent, changesSince, glob, head, mark, read, refuse, removeScript, run, scope, vars } from "../flows.mjs";
|
|
2
|
-
import { Ft as flipProblems, Lt as requireFlag, Nt as flagMentions, St as envDefaults, a as flagShipped, f as FEEDBACK, i as skills, l as gate, n as summary, r as access, t as steering, x as coder, xt as defaults } from "../chunks/reserved-
|
|
2
|
+
import { Ft as flipProblems, Lt as requireFlag, Nt as flagMentions, St as envDefaults, a as flagShipped, f as FEEDBACK, i as skills, l as gate, n as summary, r as access, t as steering, x as coder, xt as defaults } from "../chunks/reserved-lMYCaapY.mjs";
|
|
3
3
|
//#region src/workflows/rollout/rollout.ts
|
|
4
4
|
const flippingPrompt = (name, registry) => `Roll out the release flag "${name}": in ${registry}, set \`defaultOn: true\` on its entry. Change nothing else — no other entry, no other field, no other file, no comments.`;
|
|
5
5
|
const suiteBlockedMessage = () => "The suite is red after the rollout. Fix it by hand — a rollout may only touch the flag's registry entry — then land.";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pmelab/gtd",
|
|
3
|
-
"version": "58.
|
|
3
|
+
"version": "58.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Git-aware CLI that emits the next prompt for an autonomous coding agent based on the current repository state",
|
|
6
6
|
"bin": {
|
|
@@ -66,6 +66,11 @@
|
|
|
66
66
|
"types": "./src/testkit/index.ts",
|
|
67
67
|
"source": "./src/testkit/index.ts",
|
|
68
68
|
"default": "./dist/testkit.mjs"
|
|
69
|
+
},
|
|
70
|
+
"./testkit/gherkin": {
|
|
71
|
+
"types": "./src/gherkin/index.ts",
|
|
72
|
+
"source": "./src/gherkin/index.ts",
|
|
73
|
+
"default": "./dist/testkit/gherkin.mjs"
|
|
69
74
|
}
|
|
70
75
|
},
|
|
71
76
|
"files": [
|
|
@@ -76,6 +81,8 @@
|
|
|
76
81
|
"src/wire/",
|
|
77
82
|
"src/steering/",
|
|
78
83
|
"src/testkit/",
|
|
84
|
+
"src/twins/",
|
|
85
|
+
"src/gherkin/",
|
|
79
86
|
"src/cli/",
|
|
80
87
|
"src/edge/",
|
|
81
88
|
"src/emit/",
|
package/src/cli/Cli.test.ts
CHANGED
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
import { EXIT_CODES, EXIT_RUNTIME_ERROR, EXIT_USAGE_ERROR } from "../wire/index.js"
|
|
26
26
|
import { Host } from "../platform/index.js"
|
|
27
27
|
import { type CommandRequirements } from "./Cli.js"
|
|
28
|
-
import { InMemRepo, makeCapturingCliIo, testLayers } from "../
|
|
28
|
+
import { InMemRepo, makeCapturingCliIo, testLayers } from "../twins/index.js"
|
|
29
29
|
import { CONFIG_SCAFFOLD } from "../install/index.js"
|
|
30
30
|
|
|
31
31
|
const FLAG_NAMES = [
|
package/src/cli/History.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Effect, Exit } from "effect"
|
|
2
2
|
import { describe, expect, it } from "vitest"
|
|
3
|
-
import { applyEmittedScript, InMemRepo, makeCapturingCliIo, testLayers } from "../
|
|
3
|
+
import { applyEmittedScript, InMemRepo, makeCapturingCliIo, testLayers } from "../twins/index.js"
|
|
4
4
|
import { runCli } from "./index.js"
|
|
5
5
|
import { historyEntries, resolveHistoryRef, type HistoryEntry } from "./History.js"
|
|
6
6
|
|
package/src/cli/Metrics.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Effect } from "effect"
|
|
2
2
|
import { describe, expect, it } from "vitest"
|
|
3
|
-
import { applyEmittedScript, InMemRepo, makeCapturingCliIo, testLayers } from "../
|
|
3
|
+
import { applyEmittedScript, InMemRepo, makeCapturingCliIo, testLayers } from "../twins/index.js"
|
|
4
4
|
import { runCli } from "./index.js"
|
|
5
5
|
import { processMetrics, readProcessMetrics, totalMetrics, type MetricCommit } from "./Metrics.js"
|
|
6
6
|
|
package/src/cli/Program.test.ts
CHANGED
|
@@ -18,7 +18,7 @@ vi.mock("node:os", async (importOriginal) => ({
|
|
|
18
18
|
import { runCli, type Command } from "./index.js"
|
|
19
19
|
import { EXIT_USAGE_ERROR, stallDiagnosis, noopText, WIRE_SCHEMA } from "../wire/index.js"
|
|
20
20
|
import { needsOf, runCommand, SelectorUsageError } from "./Program.js"
|
|
21
|
-
import { InMemRepo, makeCapturingCliIo, testLayers, applyEmittedScript } from "../
|
|
21
|
+
import { InMemRepo, makeCapturingCliIo, testLayers, applyEmittedScript } from "../twins/index.js"
|
|
22
22
|
import {
|
|
23
23
|
commitAll,
|
|
24
24
|
DID_NOT_RUN_COMMENT,
|
package/src/cli/Strays.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Effect } from "effect"
|
|
2
2
|
import { describe, expect, it } from "vitest"
|
|
3
3
|
import { EXIT_OK, EXIT_RUNTIME_ERROR, EXIT_STRAYS_FOUND } from "../wire/index.js"
|
|
4
|
-
import { InMemRepo, makeCapturingCliIo } from "../
|
|
4
|
+
import { InMemRepo, makeCapturingCliIo } from "../twins/index.js"
|
|
5
5
|
import { runCli } from "./index.js"
|
|
6
6
|
|
|
7
7
|
const seed = (): InMemRepo => {
|
package/src/edge/Edge.test.ts
CHANGED
package/src/emit/Emit.ts
CHANGED
|
@@ -110,7 +110,7 @@ export const emitScripts = (
|
|
|
110
110
|
optional: assembleScript(optional),
|
|
111
111
|
})
|
|
112
112
|
|
|
113
|
-
/** Exported so `src/
|
|
113
|
+
/** Exported so `src/twins/EmittedScriptRecognizer.ts` can recognize these by exact string comparison. `printf`, not `echo`, avoids shell-specific backslash-escape quirks. */
|
|
114
114
|
export const DID_NOT_RUN_COMMENT =
|
|
115
115
|
"# gtd emitted this and did NOT run it — pipe it into `sh` to land the turn"
|
|
116
116
|
|
package/src/emit/GitScript.ts
CHANGED
|
@@ -130,7 +130,7 @@ export const fetchHistoryRefs = (remote: string): string =>
|
|
|
130
130
|
// No test asserts the two stay byte-identical; a drift between them is a
|
|
131
131
|
// review-time risk this boundary accepts.
|
|
132
132
|
|
|
133
|
-
/** Marks an emitted block as print-only — mirrors `OutcomeScript.ts`'s `OUTCOME_MARKER` verbatim, so `src/
|
|
133
|
+
/** Marks an emitted block as print-only — mirrors `OutcomeScript.ts`'s `OUTCOME_MARKER` verbatim, so `src/twins/EmittedScriptRecognizer.ts` recognizes either path's output identically. */
|
|
134
134
|
const OUTCOME_MARKER = "# gtd: outcome (print-only)"
|
|
135
135
|
|
|
136
136
|
const printfLine = (fmt: string, args: readonly string[]): string =>
|
|
@@ -19,7 +19,7 @@ const printfLine = (fmt: string, args: readonly string[]): string =>
|
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Marks an emitted block as print-only. Load-bearing beyond documentation:
|
|
22
|
-
* `src/
|
|
22
|
+
* `src/twins/EmittedScriptRecognizer.ts` recognizes an outcome block by
|
|
23
23
|
* this line, and a bare `printf` would be ambiguous with a workflow
|
|
24
24
|
* `script:` command that happens to start the same way.
|
|
25
25
|
*/
|
|
@@ -11,7 +11,15 @@ import {
|
|
|
11
11
|
} from "quickpickle"
|
|
12
12
|
import assert from "node:assert"
|
|
13
13
|
import type { TestContext } from "vitest"
|
|
14
|
-
import {
|
|
14
|
+
import { resolve } from "node:path"
|
|
15
|
+
import { pathToFileURL } from "node:url"
|
|
16
|
+
import {
|
|
17
|
+
runsItsScript,
|
|
18
|
+
testRepo,
|
|
19
|
+
type Actor,
|
|
20
|
+
type TestRepo,
|
|
21
|
+
type WorkflowModule,
|
|
22
|
+
} from "../testkit/index.js"
|
|
15
23
|
|
|
16
24
|
export class TestkitWorld extends QuickPickleWorld {
|
|
17
25
|
// Re-declared: under `nodenext` a subclass loses the base's members from TS's view.
|
|
@@ -21,7 +29,7 @@ export class TestkitWorld extends QuickPickleWorld {
|
|
|
21
29
|
super(context, info)
|
|
22
30
|
}
|
|
23
31
|
|
|
24
|
-
private gtdConfig:
|
|
32
|
+
private gtdConfig: WorkflowModule | undefined = undefined
|
|
25
33
|
private env: Record<string, string> = {}
|
|
26
34
|
/** Per step, what its scripted actor does, in the order the scenario declared it. */
|
|
27
35
|
actions = new Map<string, Actor[]>()
|
|
@@ -33,7 +41,7 @@ export class TestkitWorld extends QuickPickleWorld {
|
|
|
33
41
|
// The repository is created on first use and keeps the config and
|
|
34
42
|
// environment it was created with, so a later change to either is refused
|
|
35
43
|
// rather than silently ignored.
|
|
36
|
-
configure(config:
|
|
44
|
+
configure(config: WorkflowModule): void {
|
|
37
45
|
this.assertNotCreated("a gtd config")
|
|
38
46
|
this.gtdConfig = config
|
|
39
47
|
}
|
|
@@ -41,6 +49,8 @@ export class TestkitWorld extends QuickPickleWorld {
|
|
|
41
49
|
extendConfig(tail: string): void {
|
|
42
50
|
this.assertNotCreated("a gtd config")
|
|
43
51
|
if (this.gtdConfig === undefined) throw new Error("no gtd config to extend")
|
|
52
|
+
if (typeof this.gtdConfig !== "string")
|
|
53
|
+
throw new Error("a gtd config from a module cannot be extended")
|
|
44
54
|
this.gtdConfig += `\n${tail}`
|
|
45
55
|
}
|
|
46
56
|
|
|
@@ -108,6 +118,13 @@ Given("a gtd config with:", (world: TestkitWorld, source: string) => {
|
|
|
108
118
|
world.configure(withNewline(source))
|
|
109
119
|
})
|
|
110
120
|
|
|
121
|
+
// Imported by the test runner itself, from the working directory: the module and
|
|
122
|
+
// the engine share one `@pmelab/gtd/flows`, and a config source string could
|
|
123
|
+
// import only files written into the in-memory repository.
|
|
124
|
+
Given("a gtd config from the module {string}", async (world: TestkitWorld, path: string) => {
|
|
125
|
+
world.configure({ ...(await import(pathToFileURL(resolve(path)).href)) })
|
|
126
|
+
})
|
|
127
|
+
|
|
111
128
|
Given("the gtd config ends with:", (world: TestkitWorld, source: string) => {
|
|
112
129
|
world.extendConfig(withNewline(source))
|
|
113
130
|
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
@inmem
|
|
2
|
+
Feature: a scenario runs a workflow module from disk
|
|
3
|
+
|
|
4
|
+
The test runner imports the module, so it shares the engine's runtime and
|
|
5
|
+
may import anything on disk. A config written as source can import only
|
|
6
|
+
files inside the in-memory repository.
|
|
7
|
+
|
|
8
|
+
Scenario: the module's default export starts on a change in the tree
|
|
9
|
+
Given a gtd config from the module "src/gherkin/tiny.fixture.ts"
|
|
10
|
+
And a file "IDEA.md" with:
|
|
11
|
+
"""
|
|
12
|
+
a calculator
|
|
13
|
+
"""
|
|
14
|
+
And step "plan" writes "PLAN.md" with:
|
|
15
|
+
"""
|
|
16
|
+
add two numbers
|
|
17
|
+
"""
|
|
18
|
+
When the process is driven
|
|
19
|
+
Then the process rests at "sign-off"
|
|
20
|
+
And the commit subjects end with:
|
|
21
|
+
"""
|
|
22
|
+
gtd(agent): plan → sign-off
|
|
23
|
+
"""
|
|
24
|
+
|
package/src/platform/Git.test.ts
CHANGED
|
@@ -7,11 +7,11 @@ import { Cause, Effect, Exit, Option } from "effect"
|
|
|
7
7
|
import { describe, expect, it } from "vitest"
|
|
8
8
|
import { GitService, InvalidRefError, resolvedRefOrCorrupted } from "./Git.js"
|
|
9
9
|
import { Host } from "./index.js"
|
|
10
|
-
import { gitTiers, runGitServiceContract, runGitScriptContract } from "../
|
|
10
|
+
import { gitTiers, runGitServiceContract, runGitScriptContract } from "../twins/index.js"
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* The `GitOperations` contract, run identically against both the Live and
|
|
14
|
-
* InMemory tiers (`src/
|
|
14
|
+
* InMemory tiers (`src/twins/GitTiers.ts`) — 20 operations × 2 tiers.
|
|
15
15
|
*/
|
|
16
16
|
for (const makeTier of gitTiers) {
|
|
17
17
|
const name = makeTier().name
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Effect, Option } from "effect"
|
|
2
2
|
import { describe, expect, it, vi } from "vitest"
|
|
3
|
-
import { strictGitOperations as stubGit } from "../
|
|
3
|
+
import { strictGitOperations as stubGit } from "../twins/index.js"
|
|
4
4
|
import {
|
|
5
5
|
clearRetainedHistory,
|
|
6
6
|
HISTORY_REF,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Unit coverage for the retention edge module (`src/platform/RetainedHistory.ts`),
|
|
16
|
-
* using `strictGitOperations` (`src/
|
|
16
|
+
* using `strictGitOperations` (`src/twins/StrictGitOperations.ts`) as its
|
|
17
17
|
* `GitOperations` double — a Proxy failing on any method a test didn't
|
|
18
18
|
* override, so an unexpected call fails loudly instead of silently succeeding.
|
|
19
19
|
*/
|
|
@@ -15,7 +15,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"
|
|
|
15
15
|
import { Effect, Layer } from "effect"
|
|
16
16
|
import { NodeContext } from "@effect/platform-node"
|
|
17
17
|
import { GitService, Host, Workspace, type WorkspaceOps } from "./index.js"
|
|
18
|
-
import { InMemRepo, makeInMemoryWorkspaceOps } from "../
|
|
18
|
+
import { InMemRepo, makeInMemoryWorkspaceOps } from "../twins/index.js"
|
|
19
19
|
|
|
20
20
|
// `execFileSync` is a named export of the built-in `node:child_process` ESM
|
|
21
21
|
// module — not configurable, so `vi.spyOn` can't wrap it directly. `vi.mock`
|
|
@@ -31,7 +31,7 @@ vi.mock("node:child_process", async (importOriginal) => {
|
|
|
31
31
|
})
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* One `Workspace` tier, in the shape `src/
|
|
34
|
+
* One `Workspace` tier, in the shape `src/twins/GitTiers.ts` establishes
|
|
35
35
|
* for `GitOperations`: a `root`, a way to seed working-tree/committed
|
|
36
36
|
* content, and `provide` to run an Effect against this tier's `Workspace`.
|
|
37
37
|
*/
|
|
@@ -16,7 +16,7 @@ import { NodeContext } from "@effect/platform-node"
|
|
|
16
16
|
import { describe, expect, it } from "vitest"
|
|
17
17
|
import { loopLogPath, worktreeGitDir } from "./WorktreeState.js"
|
|
18
18
|
import { GitService, Host, Workspace } from "./index.js"
|
|
19
|
-
import { InMemRepo, testLayers } from "../
|
|
19
|
+
import { InMemRepo, testLayers } from "../twins/index.js"
|
|
20
20
|
|
|
21
21
|
const provide = <A>(
|
|
22
22
|
eff: Effect.Effect<A, never, Host | Workspace>,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Effect } from "effect"
|
|
2
2
|
import { describe, expect, it } from "vitest"
|
|
3
3
|
import { planStart, type StartOutcome } from "./planStart.js"
|
|
4
|
-
import { InMemRepo, testLayers } from "../
|
|
4
|
+
import { InMemRepo, testLayers } from "../twins/index.js"
|
|
5
5
|
import { ConfigService } from "../workflow/index.js"
|
|
6
6
|
|
|
7
7
|
const WORKFLOW = `import { agent, human } from "@pmelab/gtd/flows"
|
package/src/testkit/TestRepo.ts
CHANGED
|
@@ -2,10 +2,13 @@ import { Effect } from "effect"
|
|
|
2
2
|
import { PassThrough } from "node:stream"
|
|
3
3
|
import { runCli } from "../cli/index.js"
|
|
4
4
|
import { EXIT_OK, type BeatDocument } from "../wire/index.js"
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
import {
|
|
6
|
+
applyEmittedScript,
|
|
7
|
+
applyFlowScript,
|
|
8
|
+
InMemRepo,
|
|
9
|
+
makeCapturingCliIo,
|
|
10
|
+
type CapturedCliResult,
|
|
11
|
+
} from "../twins/index.js"
|
|
9
12
|
|
|
10
13
|
/**
|
|
11
14
|
* The repository's `gtd.config.ts`: its source, or the module itself. A module
|
package/src/testkit/index.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
// The public testkit: runs any workflow against the real engine in an in-memory
|
|
2
|
+
// repository. It imports no test runner, so any runner can use it; the
|
|
3
|
+
// gherkin steps are the opt-in \`@pmelab/gtd/testkit/gherkin\`.
|
|
4
|
+
export {
|
|
5
|
+
runsItsScript,
|
|
6
|
+
testRepo,
|
|
7
|
+
type Actor,
|
|
8
|
+
type Actors,
|
|
9
|
+
type DriveOptions,
|
|
10
|
+
type TestRepo,
|
|
11
|
+
type TestRepoOptions,
|
|
12
|
+
type WorkflowModule,
|
|
13
|
+
} from "./TestRepo.js"
|
|
14
|
+
export { InMemRepo, type CapturedCliResult } from "../twins/index.js"
|
|
15
|
+
export type { TestkitWorld } from "../gherkin/index.js"
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// gtd itself spawns no subprocess at all any more (`gtd renders; the driver
|
|
5
5
|
// executes`) — a mode's `format:`/`validate:` command is emitted text, never
|
|
6
6
|
// run in-process. `ScriptedCommand` below still exists for the world's own
|
|
7
|
-
// fake-shell interpreter (`src/
|
|
7
|
+
// fake-shell interpreter (`src/twins/EmittedScriptRecognizer.ts`), which
|
|
8
8
|
// simulates a driver running gtd's emitted scripts against the in-memory
|
|
9
9
|
// worktree — a wholly separate mechanism from any gtd-internal layer.
|
|
10
10
|
|
|
@@ -193,7 +193,7 @@ export interface TestWorldOptions {
|
|
|
193
193
|
* Effect test has no `--verbose` flag of its own to gate on.
|
|
194
194
|
*/
|
|
195
195
|
readonly narrate?: (line: string) => void
|
|
196
|
-
/** 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/
|
|
196
|
+
/** 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. */
|
|
197
197
|
readonly verbose?: boolean
|
|
198
198
|
/** Real or fake driver and tool commands — each one left out fails when called. */
|
|
199
199
|
readonly tools?: Partial<ToolCommands["Type"]>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { InMemRepo } from "./InMemRepo.js"
|
|
2
|
+
export { makeInMemoryWorkspaceOps, testLayers, type ScriptedCommand } from "./Layers.js"
|
|
3
|
+
export { makeCapturingCliIo, type CapturedCliResult } from "./cliIo.js"
|
|
4
|
+
export { applyEmittedScript } from "./EmittedScriptRecognizer.js"
|
|
5
|
+
export { applyFlowScript } from "./FlowScripts.js"
|
|
6
|
+
export { gitTiers, runGitServiceContract, runGitScriptContract, type GitTier } from "./GitTiers.js"
|
|
7
|
+
export { strictGitOperations } from "./StrictGitOperations.js"
|
|
8
|
+
export { fakeGitOperations } from "./FakeGitOperations.js"
|
|
@@ -6,7 +6,7 @@ import { NodeContext } from "@effect/platform-node"
|
|
|
6
6
|
import { Effect, Layer } from "effect"
|
|
7
7
|
import { afterEach, describe, expect, it } from "vitest"
|
|
8
8
|
import { GitService, Host, Narrator, Workspace } from "../platform/index.js"
|
|
9
|
-
import { InMemRepo, testLayers } from "../
|
|
9
|
+
import { InMemRepo, testLayers } from "../twins/index.js"
|
|
10
10
|
import { ConfigDiscovery, ConfigService } from "./index.js"
|
|
11
11
|
|
|
12
12
|
// A config and the local modules it imports load the same from disk, where
|
|
@@ -38,7 +38,7 @@ export const WORKFLOW_MODULE = "gtd.config.ts"
|
|
|
38
38
|
* The file names gtd read repository settings from before `gtd.config.ts` took
|
|
39
39
|
* them over. Still looked for, only to refuse them: a leftover one would
|
|
40
40
|
* otherwise be silently ignored. Shared with the in-memory counterpart in
|
|
41
|
-
* `src/
|
|
41
|
+
* `src/twins/Layers.ts`, so both tiers refuse the same names.
|
|
42
42
|
*/
|
|
43
43
|
export const LEGACY_RC_FILES = [
|
|
44
44
|
".gtdrc",
|
|
@@ -71,7 +71,7 @@ const legacyRcFileAt = (path: string): LegacyRcFile | undefined => {
|
|
|
71
71
|
* the user's home dir (inclusive, when it is an ancestor) or after reaching the
|
|
72
72
|
* filesystem root — whichever comes first. Returned innermost→outermost.
|
|
73
73
|
* Exported (via the barrel) for the same reason `SEARCH_PLACES` is — one
|
|
74
|
-
* shared implementation, not a second copy in `src/
|
|
74
|
+
* shared implementation, not a second copy in `src/twins/Layers.ts` that
|
|
75
75
|
* could silently diverge.
|
|
76
76
|
*/
|
|
77
77
|
export const walkUp = (from: string, home: string): ReadonlyArray<string> => {
|
|
@@ -115,7 +115,7 @@ const workflowModule = (
|
|
|
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
|
-
* cannot back it. `src/
|
|
118
|
+
* cannot back it. `src/twins/Layers.ts` provides the in-memory
|
|
119
119
|
* counterpart instead, mirroring how `Workspace`/`Host`/`GitService` already
|
|
120
120
|
* each have a production and a fake layer behind one Tag.
|
|
121
121
|
*/
|
package/src/workflow/load.ts
CHANGED
|
@@ -357,14 +357,14 @@ const shippedModules = (): Promise<ShippedModules> =>
|
|
|
357
357
|
return { modules, missing }
|
|
358
358
|
})())
|
|
359
359
|
|
|
360
|
-
/** The file each subpath a config may import serves: all but `flows`, the engine's own, and
|
|
360
|
+
/** The file each subpath a config may import serves: all but `flows`, the engine's own, and the testkit's. */
|
|
361
361
|
const subpathFiles = (condition: string): ReadonlyMap<string, string> => {
|
|
362
362
|
const { root, pkg } = ownPackage()
|
|
363
363
|
const files = new Map<string, string>()
|
|
364
364
|
for (const [subpath, target] of Object.entries(pkg.exports)) {
|
|
365
365
|
const file = typeof target === "string" ? target : target[condition]
|
|
366
|
-
//
|
|
367
|
-
if (subpath === "./flows" || subpath
|
|
366
|
+
// A config runs no tests, and the gherkin steps import the consumer's test runner.
|
|
367
|
+
if (subpath === "./flows" || subpath.startsWith("./testkit") || file === undefined) continue
|
|
368
368
|
files.set(`${PACKAGE}/${subpath.replace(/^\.\//, "")}`, join(root, file))
|
|
369
369
|
}
|
|
370
370
|
return files
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|