@pmelab/gtd 60.1.0 → 61.0.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 +7 -0
- package/dist/chunks/{testkit-BhTey1Do.mjs → testkit-C9sq0x07.mjs} +1695 -1454
- package/dist/chunks/{wire-DZ8R1RU9.mjs → wire-CPNSm_9N.mjs} +27 -12
- package/dist/gtd.bundle.mjs +1666 -1449
- package/dist/gtd.core.mjs +86 -86
- package/dist/testkit/gherkin.mjs +1 -1
- package/dist/testkit.mjs +1 -1
- package/dist/wire.mjs +2 -2
- package/package.json +1 -1
- package/src/cli/History.test.ts +10 -1
- package/src/cli/Metrics.test.ts +10 -1
- package/src/cli/Program.test.ts +30 -19
- package/src/cli/Program.ts +4 -1
- package/src/cli/Strays.ts +3 -2
- package/src/edge/Edge.test.ts +78 -25
- package/src/edge/Edge.ts +161 -28
- package/src/emit/GitScript.ts +21 -3
- package/src/emit/index.ts +3 -0
- package/src/gherkin/messages.test.ts +1 -0
- package/src/gherkin/module-config.feature +1 -0
- package/src/gherkin/more-steps.feature +1 -0
- package/src/gherkin/testkit.feature +2 -1
- package/src/install/Install.ts +9 -2
- package/src/replay/Format.test.ts +35 -8
- package/src/replay/Format.ts +7 -4
- package/src/replay/Replay.test.ts +54 -0
- package/src/replay/Replay.ts +61 -17
- package/src/replay/Trailers.test.ts +12 -12
- package/src/replay/Trailers.ts +35 -20
- package/src/replay/Tree.ts +11 -0
- package/src/replay/index.ts +3 -0
- package/src/step/LandStep.ts +1 -0
- package/src/step/RepoSnapshot.ts +2 -0
- package/src/step/planStart.test.ts +2 -2
- package/src/step/planStep.test.ts +4 -4
- package/src/step/planStep.ts +10 -8
- package/src/testkit/TestRepo.test.ts +4 -1
- package/src/testkit/TestRepo.ts +3 -1
- package/src/twins/EmittedScriptRecognizer.ts +32 -1
- package/src/twins/GitTiers.ts +23 -0
- package/src/twins/InMemRepo.ts +14 -0
- package/src/twins/PinBeats.ts +33 -0
- package/src/twins/index.ts +1 -0
- package/src/wire/BeatDocument.ts +26 -5
- package/src/wire/Demand.ts +16 -6
- package/src/wire/constants.ts +3 -0
- package/src/wire/types.ts +4 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Effect } from "effect"
|
|
2
|
+
import { runCli } from "../cli/index.js"
|
|
3
|
+
import type { PinMark } from "../wire/index.js"
|
|
4
|
+
import { makeCapturingCliIo } from "./cliIo.js"
|
|
5
|
+
import { applyEmittedScript } from "./EmittedScriptRecognizer.js"
|
|
6
|
+
import type { InMemRepo } from "./InMemRepo.js"
|
|
7
|
+
|
|
8
|
+
const gtd = async (repo: InMemRepo, ...args: string[]) => {
|
|
9
|
+
const { io, result } = makeCapturingCliIo(repo)
|
|
10
|
+
await Effect.runPromise(runCli(["node", "gtd.js", ...args], io))
|
|
11
|
+
return result()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const apply = (repo: InMemRepo, script: string): void => {
|
|
15
|
+
const outcome = applyEmittedScript(repo, new Map(), script)
|
|
16
|
+
if (!outcome.ok) throw new Error(outcome.error)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Takes the pending pin or unpin beat — `mark` alone when given — the way a
|
|
21
|
+
* driver does: its script, then its landing. Whether it took one.
|
|
22
|
+
*/
|
|
23
|
+
export const takePinBeat = async (repo: InMemRepo, mark?: PinMark): Promise<boolean> => {
|
|
24
|
+
const next = await gtd(repo, "next", "--json")
|
|
25
|
+
if (next.exitCode !== 0) return false
|
|
26
|
+
const beat = JSON.parse(next.stdout) as { readonly pin?: PinMark; readonly content: string }
|
|
27
|
+
if (beat.pin === undefined || (mark !== undefined && beat.pin !== mark)) return false
|
|
28
|
+
apply(repo, beat.content)
|
|
29
|
+
const land = await gtd(repo, "land", "--json")
|
|
30
|
+
if (land.exitCode !== 0) throw new Error(`gtd land exited ${land.exitCode}: ${land.stderr}`)
|
|
31
|
+
apply(repo, (JSON.parse(land.stdout) as { readonly script: string }).script)
|
|
32
|
+
return true
|
|
33
|
+
}
|
package/src/twins/index.ts
CHANGED
|
@@ -11,3 +11,4 @@ export { applyFlowScript } from "./FlowScripts.js"
|
|
|
11
11
|
export { gitTiers, runGitServiceContract, runGitScriptContract, type GitTier } from "./GitTiers.js"
|
|
12
12
|
export { strictGitOperations } from "./StrictGitOperations.js"
|
|
13
13
|
export { fakeGitOperations } from "./FakeGitOperations.js"
|
|
14
|
+
export { takePinBeat } from "./PinBeats.js"
|
package/src/wire/BeatDocument.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import type { BeatKind, Demand, DemandSession } from "./Demand.js"
|
|
2
2
|
import type { BeatStatus, NextMatch, StatusChange } from "./BeatStatus.js"
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
Actor,
|
|
5
|
+
ApprovalEntry,
|
|
6
|
+
ModelCost,
|
|
7
|
+
PinMark,
|
|
8
|
+
StateMode,
|
|
9
|
+
StateName,
|
|
10
|
+
StepAccess,
|
|
11
|
+
} from "./types.js"
|
|
4
12
|
import { WIRE_SCHEMA } from "./constants.js"
|
|
5
13
|
|
|
6
14
|
/** `gtd next --json`'s `next` key — `null` when landing would commit nothing. */
|
|
@@ -27,20 +35,30 @@ const dispatchFieldsOf = (
|
|
|
27
35
|
readonly validate: string | undefined
|
|
28
36
|
readonly skills: readonly string[] | undefined
|
|
29
37
|
readonly access: StepAccess | undefined
|
|
38
|
+
readonly pin: PinMark | undefined
|
|
30
39
|
} => {
|
|
40
|
+
const none = {
|
|
41
|
+
session: undefined,
|
|
42
|
+
validate: undefined,
|
|
43
|
+
skills: undefined,
|
|
44
|
+
access: undefined,
|
|
45
|
+
pin: undefined,
|
|
46
|
+
}
|
|
31
47
|
switch (demand.kind) {
|
|
32
48
|
case "prompt":
|
|
33
49
|
return {
|
|
50
|
+
...none,
|
|
34
51
|
session: demand.session,
|
|
35
52
|
validate: demand.validate,
|
|
36
53
|
skills: demand.skills,
|
|
37
54
|
access: demand.access,
|
|
38
55
|
}
|
|
56
|
+
case "script":
|
|
57
|
+
return { ...none, pin: demand.pin }
|
|
39
58
|
case "capture":
|
|
40
59
|
case "message":
|
|
41
|
-
case "script":
|
|
42
60
|
case "stalled":
|
|
43
|
-
return
|
|
61
|
+
return none
|
|
44
62
|
default:
|
|
45
63
|
return assertNeverDemand(demand)
|
|
46
64
|
}
|
|
@@ -57,6 +75,8 @@ export interface BeatDocument {
|
|
|
57
75
|
readonly model: string | undefined
|
|
58
76
|
readonly system: string | undefined
|
|
59
77
|
readonly validate: string | undefined
|
|
78
|
+
/** On a pin or unpin beat only: what its script does to `.gtd/process.gtdpin`. */
|
|
79
|
+
readonly pin: PinMark | undefined
|
|
60
80
|
readonly log: string
|
|
61
81
|
readonly state: StateName
|
|
62
82
|
readonly actor: Actor
|
|
@@ -76,13 +96,13 @@ export interface BeatDocument {
|
|
|
76
96
|
}
|
|
77
97
|
|
|
78
98
|
/**
|
|
79
|
-
* Flatten a `Demand` plus its `BeatStatus` into the single
|
|
99
|
+
* Flatten a `Demand` plus its `BeatStatus` into the single 26-key document
|
|
80
100
|
* `gtd next --json` emits — the ONLY place the two are joined. `cost`/
|
|
81
101
|
* `costByModel` are omitted together, exactly when no cost was recorded
|
|
82
102
|
* (`cost <= 0`).
|
|
83
103
|
*/
|
|
84
104
|
export const beatDocument = (demand: Demand, status: BeatStatus): BeatDocument => {
|
|
85
|
-
const { session, validate, skills, access } = dispatchFieldsOf(demand)
|
|
105
|
+
const { session, validate, skills, access, pin } = dispatchFieldsOf(demand)
|
|
86
106
|
const hasCost = status.cost > 0
|
|
87
107
|
return {
|
|
88
108
|
schema: WIRE_SCHEMA,
|
|
@@ -94,6 +114,7 @@ export const beatDocument = (demand: Demand, status: BeatStatus): BeatDocument =
|
|
|
94
114
|
model: status.model,
|
|
95
115
|
system: status.system,
|
|
96
116
|
validate,
|
|
117
|
+
pin,
|
|
97
118
|
log: status.log,
|
|
98
119
|
state: status.state,
|
|
99
120
|
actor: status.actor,
|
package/src/wire/Demand.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
Actor,
|
|
3
|
+
ContentKind,
|
|
4
|
+
PinMark,
|
|
5
|
+
RenderedDemandSource,
|
|
6
|
+
StateName,
|
|
7
|
+
StepAccess,
|
|
8
|
+
} from "./types.js"
|
|
2
9
|
|
|
3
10
|
/** The whole beat vocabulary a driver acts on — what to DO with a rest, not merely whether dispatching it is safe. */
|
|
4
11
|
export type BeatKind = "capture" | "message" | "script" | "prompt" | "stalled"
|
|
@@ -72,7 +79,7 @@ export interface DemandSession {
|
|
|
72
79
|
export type Demand =
|
|
73
80
|
| { readonly kind: "capture"; readonly content: string }
|
|
74
81
|
| { readonly kind: "message"; readonly content: string }
|
|
75
|
-
| { readonly kind: "script"; readonly content: string }
|
|
82
|
+
| { readonly kind: "script"; readonly content: string; readonly pin?: PinMark }
|
|
76
83
|
| { readonly kind: "stalled"; readonly content: string }
|
|
77
84
|
| {
|
|
78
85
|
readonly kind: "prompt"
|
|
@@ -91,7 +98,10 @@ export type Demand =
|
|
|
91
98
|
* any other kind.
|
|
92
99
|
*/
|
|
93
100
|
export const demandOf = (input: {
|
|
94
|
-
readonly rendered: Pick<
|
|
101
|
+
readonly rendered: Pick<
|
|
102
|
+
RenderedDemandSource,
|
|
103
|
+
"actor" | "content" | "state" | "skills" | "access" | "pin"
|
|
104
|
+
>
|
|
95
105
|
readonly kind: BeatKind
|
|
96
106
|
readonly session?: DemandSession
|
|
97
107
|
readonly validate?: string
|
|
@@ -102,7 +112,7 @@ export const demandOf = (input: {
|
|
|
102
112
|
const skills =
|
|
103
113
|
rendered.skills !== undefined && rendered.skills.length > 0 ? rendered.skills : undefined
|
|
104
114
|
const access = rendered.access ?? { read: null, write: null }
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
115
|
+
if (kind === "prompt") return { kind, content, session, validate, skills, access }
|
|
116
|
+
if (kind === "script" && rendered.pin !== undefined) return { kind, content, pin: rendered.pin }
|
|
117
|
+
return { kind, content }
|
|
108
118
|
}
|
package/src/wire/constants.ts
CHANGED
|
@@ -20,3 +20,6 @@ export const renderFormat = (fmt: string, ...args: readonly string[]): string =>
|
|
|
20
20
|
/** The first line of the landing refusal for a turn that wrote outside its `write` access — a stable marker drivers and tests grep for. */
|
|
21
21
|
export const ACCESS_REFUSAL =
|
|
22
22
|
"gtd land: this turn wrote outside its write access — revert these paths, then run `gtd land` again:"
|
|
23
|
+
|
|
24
|
+
/** Where a process keeps its pin: the engine and workflow it runs on, as `gtd bundle` prints them. */
|
|
25
|
+
export const PIN_PATH = ".gtd/process.gtdpin"
|
package/src/wire/types.ts
CHANGED
|
@@ -10,6 +10,9 @@ export type StateName = string
|
|
|
10
10
|
/** The three content kinds a state can carry — exactly one per state. */
|
|
11
11
|
export type ContentKind = "script" | "prompt" | "message"
|
|
12
12
|
|
|
13
|
+
/** What a pin or unpin beat does to the pin; a driver may do it its own way instead of running the script. */
|
|
14
|
+
export type PinMark = "add" | "remove"
|
|
15
|
+
|
|
13
16
|
/** The name of a steering-file mode. Not a closed vocabulary: the valid set derives from the active definition (`BUILT_IN_MODES` plus whatever `modes:` declares). */
|
|
14
17
|
export type StateMode = string
|
|
15
18
|
|
|
@@ -51,4 +54,5 @@ export interface RenderedDemandSource {
|
|
|
51
54
|
readonly judge?: string
|
|
52
55
|
readonly skills?: readonly string[]
|
|
53
56
|
readonly access?: StepAccess
|
|
57
|
+
readonly pin?: PinMark
|
|
54
58
|
}
|