@pmelab/gtd 62.0.0 → 62.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 +10 -8
- package/dist/chunks/{testkit-XQeYCNMn.mjs → testkit-CPzWnl3P.mjs} +56 -2
- package/dist/gtd.bundle.mjs +55 -0
- package/dist/gtd.core.mjs +76 -73
- package/dist/testkit/gherkin.mjs +1 -1
- package/dist/testkit.mjs +1 -1
- package/package.json +1 -1
- package/src/cli/Cli.test.ts +2 -0
- package/src/cli/Cli.ts +12 -0
- package/src/cli/Launch.ts +1 -0
- package/src/cli/Program.test.ts +3 -1
- package/src/cli/Program.ts +41 -0
- package/src/emit/GitScript.ts +7 -0
- package/src/emit/index.ts +1 -0
- package/src/twins/EmittedScriptRecognizer.ts +3 -2
package/dist/testkit/gherkin.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as runsItsScript, r as testRepo } from "../chunks/testkit-
|
|
1
|
+
import { n as runsItsScript, r as testRepo } from "../chunks/testkit-CPzWnl3P.mjs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
4
|
import { After, AfterAll, Given, QuickPickleWorld, Then, When, setWorldConstructor } from "quickpickle";
|
package/dist/testkit.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { H as installContext } from "./chunks/flows-DENt0uWV.mjs";
|
|
2
|
-
import { a as verdictWhen, i as verdict, n as runsItsScript, o as whenPrompted, r as testRepo, t as inTurn } from "./chunks/testkit-
|
|
2
|
+
import { a as verdictWhen, i as verdict, n as runsItsScript, o as whenPrompted, r as testRepo, t as inTurn } from "./chunks/testkit-CPzWnl3P.mjs";
|
|
3
3
|
export { inTurn, installContext, runsItsScript, testRepo, verdict, verdictWhen, whenPrompted };
|
package/package.json
CHANGED
package/src/cli/Cli.test.ts
CHANGED
|
@@ -114,6 +114,7 @@ describe("parseArgv — scope", () => {
|
|
|
114
114
|
["validate", "--json"],
|
|
115
115
|
["abandon", "--json"],
|
|
116
116
|
["restore", "--json"],
|
|
117
|
+
["repin", "--json"],
|
|
117
118
|
["start", "--json"],
|
|
118
119
|
["start", "fix", "--json"],
|
|
119
120
|
["history", "fetch", "--json"],
|
|
@@ -1116,6 +1117,7 @@ describe("standaloneKinds / needsOf", () => {
|
|
|
1116
1117
|
"start",
|
|
1117
1118
|
"abandon",
|
|
1118
1119
|
"restore",
|
|
1120
|
+
"repin",
|
|
1119
1121
|
"next",
|
|
1120
1122
|
"validate",
|
|
1121
1123
|
"ui",
|
package/src/cli/Cli.ts
CHANGED
|
@@ -131,6 +131,7 @@ const SCRIPT_KINDS: ReadonlySet<Command["kind"]> = new Set<Command["kind"]>([
|
|
|
131
131
|
"start",
|
|
132
132
|
"abandon",
|
|
133
133
|
"restore",
|
|
134
|
+
"repin",
|
|
134
135
|
"validate",
|
|
135
136
|
"historyFetch",
|
|
136
137
|
])
|
|
@@ -448,6 +449,16 @@ With no <name>, list every workflow and door it can start. An unknown <name>, th
|
|
|
448
449
|
summary: "Bring back the last abandoned process",
|
|
449
450
|
details: `Bring back the process the last \`gtd abandon\` stopped: the printed script resets HEAD to that process's last commit. Refuses on a dirty working tree, when nothing was abandoned, or when commits made since would be lost.`,
|
|
450
451
|
},
|
|
452
|
+
{
|
|
453
|
+
token: "repin",
|
|
454
|
+
kind: "repin",
|
|
455
|
+
arity: "none",
|
|
456
|
+
group: "Start and stop",
|
|
457
|
+
summary: "Pin the process to the live gtd and workflow",
|
|
458
|
+
details: `Switch the process underway to the live engine and workflow, the way out when the workflow needs a fix mid-process: the printed script runs the pin beat again, writing \`gtd bundle\`'s output to .gtd/process.gtdpin and committing that file alone. Always runs on the gtd on PATH, never on the pin. Refuses when no process is underway, when the next beat is a pin or unpin beat already, or when the live workflow does not replay the process.
|
|
459
|
+
|
|
460
|
+
s=$(gtd repin --json=script) && sh -c "$s"`,
|
|
461
|
+
},
|
|
451
462
|
{
|
|
452
463
|
token: "summary",
|
|
453
464
|
kind: "summary",
|
|
@@ -1306,6 +1317,7 @@ export const parseArgv = (argv: readonly string[]): CliPlan => {
|
|
|
1306
1317
|
| "lsp"
|
|
1307
1318
|
| "abandon"
|
|
1308
1319
|
| "restore"
|
|
1320
|
+
| "repin"
|
|
1309
1321
|
| "next"
|
|
1310
1322
|
| "validate"
|
|
1311
1323
|
| "guide"
|
package/src/cli/Launch.ts
CHANGED
package/src/cli/Program.test.ts
CHANGED
|
@@ -1717,6 +1717,7 @@ describe("runCommand — refuses in a repository with no commits", () => {
|
|
|
1717
1717
|
start: { kind: "start", name: "fix", args: [], vars: {} },
|
|
1718
1718
|
abandon: { kind: "abandon" },
|
|
1719
1719
|
restore: { kind: "restore" },
|
|
1720
|
+
repin: { kind: "repin" },
|
|
1720
1721
|
next: { kind: "next" },
|
|
1721
1722
|
validate: { kind: "validate" },
|
|
1722
1723
|
check: { kind: "check", mode: "qa", file: ".gtd/TODO.md" },
|
|
@@ -1743,7 +1744,7 @@ describe("runCommand — refuses in a repository with no commits", () => {
|
|
|
1743
1744
|
const NO_COMMITS_MESSAGE =
|
|
1744
1745
|
"gtd: the repository has no commits yet — make an initial commit, then run gtd again"
|
|
1745
1746
|
|
|
1746
|
-
it("derives exactly the
|
|
1747
|
+
it("derives exactly the eighteen non-standalone kinds — a canary for the table-driven cases below", () => {
|
|
1747
1748
|
expect(stateKinds.sort()).toEqual(
|
|
1748
1749
|
[
|
|
1749
1750
|
"abandon",
|
|
@@ -1753,6 +1754,7 @@ describe("runCommand — refuses in a repository with no commits", () => {
|
|
|
1753
1754
|
"land",
|
|
1754
1755
|
"next",
|
|
1755
1756
|
"restore",
|
|
1757
|
+
"repin",
|
|
1756
1758
|
"summary",
|
|
1757
1759
|
"validate",
|
|
1758
1760
|
"ui",
|
package/src/cli/Program.ts
CHANGED
|
@@ -65,6 +65,7 @@ import {
|
|
|
65
65
|
summaryRun,
|
|
66
66
|
type RenderedRest,
|
|
67
67
|
} from "../edge/index.js"
|
|
68
|
+
import { formatCommitMessage, PIN_STATES } from "../replay/index.js"
|
|
68
69
|
import { planStart, planStep as planStepPure, type JudgeVerdict } from "../step/index.js"
|
|
69
70
|
import {
|
|
70
71
|
BUILT_IN_MODE_NAMES,
|
|
@@ -82,6 +83,9 @@ import {
|
|
|
82
83
|
hardResetTo,
|
|
83
84
|
mixedResetTo,
|
|
84
85
|
unpinScript,
|
|
86
|
+
repinScript,
|
|
87
|
+
commitPath,
|
|
88
|
+
transitionOutcome,
|
|
85
89
|
ScriptSurface,
|
|
86
90
|
updateRef,
|
|
87
91
|
type RunnableScript,
|
|
@@ -154,6 +158,7 @@ export type Command =
|
|
|
154
158
|
}
|
|
155
159
|
| { readonly kind: "abandon" }
|
|
156
160
|
| { readonly kind: "restore" }
|
|
161
|
+
| { readonly kind: "repin" }
|
|
157
162
|
| { readonly kind: "next" }
|
|
158
163
|
| { readonly kind: "validate" }
|
|
159
164
|
| {
|
|
@@ -862,6 +867,40 @@ const runRestoreCommand = (
|
|
|
862
867
|
yield* writeScript(json, out, combinedScript(required, ""))
|
|
863
868
|
})
|
|
864
869
|
|
|
870
|
+
/**
|
|
871
|
+
* `gtd repin`: the pin beat's script and landing, on demand. It runs live,
|
|
872
|
+
* so reading the rest first refuses a live workflow that cannot replay the
|
|
873
|
+
* process before any pin is written.
|
|
874
|
+
*/
|
|
875
|
+
const runRepinCommand = (
|
|
876
|
+
json: JsonMode,
|
|
877
|
+
out: ArtifactOut,
|
|
878
|
+
): Effect.Effect<void, Error, CommandRequirements> =>
|
|
879
|
+
Effect.gen(function* () {
|
|
880
|
+
const rest = yield* currentRest
|
|
881
|
+
const refusal =
|
|
882
|
+
rest.pin === "add"
|
|
883
|
+
? "the next beat pins the process already — run it (`gtd next --json=content`), then land"
|
|
884
|
+
: rest.pin === "remove"
|
|
885
|
+
? "the flow has ended — its unpin beat is next, so there is nothing to repin"
|
|
886
|
+
: noProcessUnderway(rest)
|
|
887
|
+
? "no gtd process is underway — nothing to repin"
|
|
888
|
+
: undefined
|
|
889
|
+
if (refusal !== undefined) return yield* Effect.fail(new Error(`gtd repin: ${refusal}`))
|
|
890
|
+
const message = formatCommitMessage({
|
|
891
|
+
actor: "check",
|
|
892
|
+
from: PIN_STATES.add,
|
|
893
|
+
to: rest.step.name,
|
|
894
|
+
pin: "add",
|
|
895
|
+
})
|
|
896
|
+
const steps: EmitStep[] = [
|
|
897
|
+
{ kind: "gitWrite", command: repinScript(PIN_PATH) },
|
|
898
|
+
{ kind: "gitWrite", command: commitPath(PIN_PATH, message) },
|
|
899
|
+
{ kind: "outcome", command: transitionOutcome(PIN_STATES.add, rest.step.name) },
|
|
900
|
+
]
|
|
901
|
+
yield* writeScript(json, out, combinedScript(emitScripts(steps).required, ""))
|
|
902
|
+
})
|
|
903
|
+
|
|
865
904
|
/**
|
|
866
905
|
* The mode's own validate command for `file`: a declared `validate:` command
|
|
867
906
|
* verbatim; a `"builtin"` validator or no mode at all names the leaf
|
|
@@ -1339,6 +1378,8 @@ const dispatchVoidCommand = (
|
|
|
1339
1378
|
return runAbandonCommand(json, out)
|
|
1340
1379
|
case "restore":
|
|
1341
1380
|
return runRestoreCommand(json, out)
|
|
1381
|
+
case "repin":
|
|
1382
|
+
return runRepinCommand(json, out)
|
|
1342
1383
|
case "next":
|
|
1343
1384
|
return runNextCommand(json, out)
|
|
1344
1385
|
case "validate":
|
package/src/emit/GitScript.ts
CHANGED
|
@@ -44,6 +44,13 @@ export const pinScript = (path: string): string => {
|
|
|
44
44
|
return `${mkdir}gtd bundle >${p} || { rm -f -- ${p}; exit 1; }`
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/** `gtd repin`'s pin write: a failed `gtd bundle` leaves the committed pin in place. */
|
|
48
|
+
export const repinScript = (path: string): string => {
|
|
49
|
+
const p = shellQuote(path)
|
|
50
|
+
const next = shellQuote(`${path}.new`)
|
|
51
|
+
return `gtd bundle >${next} || { rm -f -- ${next}; exit 1; }\nmv -f -- ${next} ${p}`
|
|
52
|
+
}
|
|
53
|
+
|
|
47
54
|
/** An unpin beat's script, and `gtd abandon`'s removal of the pin. */
|
|
48
55
|
export const unpinScript = (path: string): string => `rm -f -- ${shellQuote(path)}`
|
|
49
56
|
|
package/src/emit/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
modeContradictionSkipNotice,
|
|
27
27
|
OUTCOME_MARKER,
|
|
28
28
|
pinScript,
|
|
29
|
+
repinScript,
|
|
29
30
|
unpinScript,
|
|
30
31
|
} from "../emit/index.js"
|
|
31
32
|
import {
|
|
@@ -272,9 +273,9 @@ const recognizeGitBuilders = (repo: InMemRepo, block: string): BlockOutcome | un
|
|
|
272
273
|
/** What the in-memory `gtd bundle` prints: a pin header, and no engine behind it. */
|
|
273
274
|
const IN_MEMORY_PIN = "gtdpin 1 gtd in-memory\n"
|
|
274
275
|
|
|
275
|
-
/** A pin or unpin beat's script. */
|
|
276
|
+
/** A pin or unpin beat's script, or `gtd repin`'s. */
|
|
276
277
|
const recognizePinScripts = (repo: InMemRepo, block: string): BlockOutcome | undefined => {
|
|
277
|
-
if (block === pinScript(PIN_PATH)) {
|
|
278
|
+
if (block === pinScript(PIN_PATH) || block === repinScript(PIN_PATH)) {
|
|
278
279
|
repo.writeFile(PIN_PATH, IN_MEMORY_PIN)
|
|
279
280
|
return { kind: "applied" }
|
|
280
281
|
}
|