@pmelab/gtd 27.6.0 → 27.7.1
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 +25 -14
- package/claude/hooks/assist.ts +35 -2
- package/claude/hooks/delegate.ts +22 -0
- package/claude/hooks/handoff.ts +13 -4
- package/claude/hooks/jobs.ts +67 -0
- package/claude/hooks/paste.ts +17 -0
- package/claude/hooks/register.tsx +126 -65
- package/claude/hooks/ship.ts +7 -2
- package/claude/hooks/tools.ts +32 -0
- package/claude/hooks/wording.ts +11 -0
- package/dist/chunks/{reserved-Df8GeSHw.mjs → reserved-UdSuHDUJ.mjs} +2 -2
- package/dist/chunks/{tail-DmIfEAI4.mjs → tail-fj60rQ3y.mjs} +1 -1
- package/dist/gtd.bundle.mjs +3 -3
- package/dist/testkit.mjs +30 -18
- 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 +2 -2
- package/dist/workflows/rollout.mjs +1 -1
- package/dist/workflows.mjs +3 -3
- package/package.json +1 -1
- package/src/cli/Cli.ts +2 -2
- package/src/testkit/EmittedScriptRecognizer.test.ts +41 -0
- package/src/testkit/EmittedScriptRecognizer.ts +16 -7
- package/src/testkit/TestRepo.test.ts +79 -1
- package/src/testkit/TestRepo.ts +17 -6
- package/src/testkit/steps.ts +44 -1
- package/src/testkit/testkit.feature +35 -0
- package/src/workflows/cleanup/cleanup-path.feature +94 -0
- package/src/workflows/cleanup/cleanup.feature +0 -29
- package/src/workflows/feature/fast-path.feature +114 -0
- package/src/workflows/feature/one-step-land.feature +108 -0
- package/src/workflows/feature/refactor-first.feature +137 -0
- package/src/workflows/feature/review.test.ts +9 -3
- package/src/workflows/feature/review.ts +3 -3
- package/src/workflows/feature/risk-tier.feature +0 -63
- package/src/workflows/feature/seam-rejection.feature +146 -0
- package/src/workflows/feature/seam.feature +0 -67
- package/src/workflows/shared/steps.ts +1 -2
- package/src/workflows/shared/tail.test.ts +18 -9
- package/src/workflows/shared/workflows.steps.ts +5 -4
package/claude/hooks/wording.ts
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import type { Stop } from "../types"
|
|
4
4
|
import type { Beat } from "./drive"
|
|
5
|
+
import type { Shipped } from "./ship"
|
|
6
|
+
import { JOB_NAME } from "./tools"
|
|
7
|
+
import type { JobName } from "./tools"
|
|
5
8
|
|
|
6
9
|
const step = (s: { label?: string; state?: string }) => s.label ?? s.state ?? "the next step"
|
|
7
10
|
|
|
@@ -53,3 +56,11 @@ export function pushText(stop: Stop, repo: string, url?: string) {
|
|
|
53
56
|
if (stop.kind !== "gate") return at
|
|
54
57
|
return url ? `${at}. Open: ${url}` : at
|
|
55
58
|
}
|
|
59
|
+
|
|
60
|
+
const capitalised = (s: string) => s[0]!.toUpperCase() + s.slice(1)
|
|
61
|
+
|
|
62
|
+
export const jobHeadline = (job: JobName, outcome: Shipped) =>
|
|
63
|
+
`${outcome.ok ? "✔" : "✘"} ${capitalised(JOB_NAME[job])} ${outcome.ok ? "finished" : "failed"}`
|
|
64
|
+
|
|
65
|
+
export const jobPushText = (job: JobName, outcome: Shipped, repo: string) =>
|
|
66
|
+
`${repo}: ${JOB_NAME[job]} ${outcome.ok ? "finished" : "failed"}. ${outcome.text}`.trim()
|
|
@@ -1234,13 +1234,13 @@ const fixRisks = (notes) => agentWithSkills("review.fix.risks.fixing", buildRevi
|
|
|
1234
1234
|
system: reviewerSystem(),
|
|
1235
1235
|
allowEmpty: true
|
|
1236
1236
|
});
|
|
1237
|
-
const awaitReview = (base, message = buildReviewAwaitReviewMessage(base)
|
|
1237
|
+
const awaitReview = (base, message = buildReviewAwaitReviewMessage(base)) => human("review.await-review", {
|
|
1238
1238
|
message,
|
|
1239
1239
|
label: "Awaiting your review",
|
|
1240
1240
|
file: REVIEW,
|
|
1241
1241
|
mode: "review",
|
|
1242
1242
|
base,
|
|
1243
|
-
|
|
1243
|
+
acceptClean: true
|
|
1244
1244
|
});
|
|
1245
1245
|
/** The round at commit `round` left no `.gtd/REVIEW.md`. */
|
|
1246
1246
|
const reviewMissing = (round) => human("review.review-missing", {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { read, refuse, removeScript, run, scope, showScript, vars } from "../flows.mjs";
|
|
2
2
|
import { fixFindings, lensesOf, qualityLap as qualityLap$1, reviewToHandOff } from "../workflow-kit.mjs";
|
|
3
|
-
import { C as fixRisks, D as reviewing, E as reviewQuality, G as handOffAwaitReviewMessage, S as fixQuality, T as reviewMissing, f as guarded, g as QUALITY, p as healthy, u as escalation, v as REVIEW, x as fixCheck, y as awaitReview } from "./reserved-
|
|
3
|
+
import { C as fixRisks, D as reviewing, E as reviewQuality, G as handOffAwaitReviewMessage, S as fixQuality, T as reviewMissing, f as guarded, g as QUALITY, p as healthy, u as escalation, v as REVIEW, x as fixCheck, y as awaitReview } from "./reserved-UdSuHDUJ.mjs";
|
|
4
4
|
//#region src/workflows/shared/tail.ts
|
|
5
5
|
/** The reviewer's own `.gtd/REVIEW.md`, kept beside the human's notes so `fix` can tell them apart. */
|
|
6
6
|
const REVIEW_ORIG = ".gtd/REVIEW.orig.md";
|
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -41695,7 +41695,7 @@ const FLAGS = [
|
|
|
41695
41695
|
help: [
|
|
41696
41696
|
"(gtd land, with --cost) tag that cost's model",
|
|
41697
41697
|
"(gtd judge run, llm answerer) the claude model; omitted:",
|
|
41698
|
-
"GTD_JUDGE_MODEL, then judge.json, then
|
|
41698
|
+
"GTD_JUDGE_MODEL, then judge.json, then sonnet"
|
|
41699
41699
|
]
|
|
41700
41700
|
},
|
|
41701
41701
|
{
|
|
@@ -42088,7 +42088,7 @@ const COMMAND_ROWS = [
|
|
|
42088
42088
|
"--answers <path> or GTD_JUDGE_ANSWERS; questions the file",
|
|
42089
42089
|
"does not cover are left out. --provider jev asks TypeSafe",
|
|
42090
42090
|
"(TYPESAFE_API_KEY). --provider llm asks the `claude` CLI",
|
|
42091
|
-
"on PATH (Claude Code only), model
|
|
42091
|
+
"on PATH (Claude Code only), model sonnet unless --model.",
|
|
42092
42092
|
"No --provider: GTD_JUDGE_PROVIDER, then ~/.config/gtd/",
|
|
42093
42093
|
"judge.json, else jev when TYPESAFE_API_KEY is set and no",
|
|
42094
42094
|
"model is given, else llm; --model likewise falls back to",
|
|
@@ -58805,7 +58805,7 @@ var DEFAULT_MODEL, TIMEOUT_MS, SYSTEM_PROMPT, fail$1, unreadable, levelsOf, opti
|
|
|
58805
58805
|
var init_llm = __esmMin((() => {
|
|
58806
58806
|
init_criteria();
|
|
58807
58807
|
init_io();
|
|
58808
|
-
DEFAULT_MODEL = "
|
|
58808
|
+
DEFAULT_MODEL = "sonnet";
|
|
58809
58809
|
TIMEOUT_MS = 18e4;
|
|
58810
58810
|
SYSTEM_PROMPT = "You are a strict, impartial judge. Answer each question from the supplied evidence alone.\nReply only with the structured answer; never use tools or ask questions.";
|
|
58811
58811
|
fail$1 = (message) => {
|
package/dist/testkit.mjs
CHANGED
|
@@ -10825,7 +10825,7 @@ const FLAGS = [
|
|
|
10825
10825
|
help: [
|
|
10826
10826
|
"(gtd land, with --cost) tag that cost's model",
|
|
10827
10827
|
"(gtd judge run, llm answerer) the claude model; omitted:",
|
|
10828
|
-
"GTD_JUDGE_MODEL, then judge.json, then
|
|
10828
|
+
"GTD_JUDGE_MODEL, then judge.json, then sonnet"
|
|
10829
10829
|
]
|
|
10830
10830
|
},
|
|
10831
10831
|
{
|
|
@@ -11218,7 +11218,7 @@ const COMMAND_ROWS = [
|
|
|
11218
11218
|
"--answers <path> or GTD_JUDGE_ANSWERS; questions the file",
|
|
11219
11219
|
"does not cover are left out. --provider jev asks TypeSafe",
|
|
11220
11220
|
"(TYPESAFE_API_KEY). --provider llm asks the `claude` CLI",
|
|
11221
|
-
"on PATH (Claude Code only), model
|
|
11221
|
+
"on PATH (Claude Code only), model sonnet unless --model.",
|
|
11222
11222
|
"No --provider: GTD_JUDGE_PROVIDER, then ~/.config/gtd/",
|
|
11223
11223
|
"judge.json, else jev when TYPESAFE_API_KEY is set and no",
|
|
11224
11224
|
"model is given, else llm; --model likewise falls back to",
|
|
@@ -13076,18 +13076,22 @@ const recognizeGtdUncheck = (repo, block) => {
|
|
|
13076
13076
|
* definition) keeps the `@inmem` tier green.
|
|
13077
13077
|
*/
|
|
13078
13078
|
const recognizeDidNotRunComment = (block) => block === "# gtd emitted this and did NOT run it — pipe it into `sh` to land the turn" ? { kind: "noop" } : void 0;
|
|
13079
|
-
/** `combinedScript`'s optional half.
|
|
13079
|
+
/** `combinedScript`'s optional half. Reports `noop` whatever the inner result, mirroring bash's `( … ) || <warning>`. Exception: under `git: false` any unrecognized inner block fails the outer script (git or not), so the in-memory twin knowingly diverges from `sh` rather than run git unseen. */
|
|
13080
13080
|
const PRESENTATION_FAILURE_TEXT = `${extractQuotedTokens(PRESENTATION_FAILURE_WARNING)[0].replace(/\\n$/, "")}\n`;
|
|
13081
13081
|
const PRESENTATION_SUBSHELL_PREFIX = `${PRESENTATION_ONLY_COMMENT}\n(\n`;
|
|
13082
13082
|
const PRESENTATION_SUBSHELL_SUFFIX = `\n) || ${PRESENTATION_FAILURE_WARNING}`;
|
|
13083
|
-
const recognizePresentationSubshell = (repo, commands, block) => {
|
|
13083
|
+
const recognizePresentationSubshell = (repo, commands, block, git) => {
|
|
13084
13084
|
if (!block.startsWith(PRESENTATION_SUBSHELL_PREFIX) || !block.endsWith(PRESENTATION_SUBSHELL_SUFFIX)) return;
|
|
13085
13085
|
const inner = block.slice(PRESENTATION_SUBSHELL_PREFIX.length, block.length - PRESENTATION_SUBSHELL_SUFFIX.length);
|
|
13086
|
-
const result = applyEmittedScript(repo, commands, inner);
|
|
13086
|
+
const result = applyEmittedScript(repo, commands, inner, { git });
|
|
13087
13087
|
if (result.ok) return {
|
|
13088
13088
|
kind: "noop",
|
|
13089
13089
|
output: result.output
|
|
13090
13090
|
};
|
|
13091
|
+
if (!git && result.error?.startsWith(UNRECOGNIZED_PREFIX)) return {
|
|
13092
|
+
kind: "failed",
|
|
13093
|
+
error: `${result.output}${result.error}`
|
|
13094
|
+
};
|
|
13091
13095
|
return {
|
|
13092
13096
|
kind: "noop",
|
|
13093
13097
|
output: `${result.output}${PRESENTATION_FAILURE_TEXT}`
|
|
@@ -13194,17 +13198,17 @@ const recognizeWriteLines = (repo, block) => {
|
|
|
13194
13198
|
return { kind: "applied" };
|
|
13195
13199
|
};
|
|
13196
13200
|
/** Every recognizer `applyEmittedScript` tries, first match wins — `recognizeScriptedCommand` stays last since it's the exact-hit fallback for anything the rest don't claim. */
|
|
13197
|
-
const recognizersFor = (repo, commands) => [
|
|
13201
|
+
const recognizersFor = (repo, commands, git) => [
|
|
13198
13202
|
(block) => recognizeSetFlags(block),
|
|
13199
13203
|
(block) => recognizeDidNotRunComment(block),
|
|
13200
13204
|
(block) => recognizePrecondition(repo, block),
|
|
13201
13205
|
(block) => recognizeFileExistsGuard(repo, block),
|
|
13202
13206
|
(block) => recognizeBinaryGuard(commands, block),
|
|
13203
|
-
(block) => recognizeGitBuilders(repo, block),
|
|
13207
|
+
(block) => git ? recognizeGitBuilders(repo, block) : void 0,
|
|
13204
13208
|
(block) => recognizeDeferNotes(repo, block),
|
|
13205
13209
|
(block) => recognizeWriteLines(repo, block),
|
|
13206
13210
|
(block) => recognizeFailurePromptWrapper(repo, commands, block),
|
|
13207
|
-
(block) => recognizePresentationSubshell(repo, commands, block),
|
|
13211
|
+
(block) => recognizePresentationSubshell(repo, commands, block, git),
|
|
13208
13212
|
(block) => recognizeModeContradictionSkipNotice(block),
|
|
13209
13213
|
(block) => recognizeModeContradictionCheck(repo, commands, block),
|
|
13210
13214
|
(block) => recognizeGtdCheck(repo, block),
|
|
@@ -13227,7 +13231,7 @@ const recognizeBlock = (recognizers, block) => {
|
|
|
13227
13231
|
* non-zero scripted-command exit — exactly like `set -euo pipefail` stops a
|
|
13228
13232
|
* real script. No block after the failing one is applied.
|
|
13229
13233
|
*/
|
|
13230
|
-
const applyEmittedScript = (repo, commands, script) => {
|
|
13234
|
+
const applyEmittedScript = (repo, commands, script, { git = true } = {}) => {
|
|
13231
13235
|
const flow = applyFlowScript(repo, commands, script);
|
|
13232
13236
|
if (flow !== void 0) return flow.ok ? {
|
|
13233
13237
|
ok: true,
|
|
@@ -13239,10 +13243,10 @@ const applyEmittedScript = (repo, commands, script) => {
|
|
|
13239
13243
|
output: flow.output,
|
|
13240
13244
|
error: flow.error ?? ""
|
|
13241
13245
|
};
|
|
13242
|
-
return script.startsWith("#!") ? unrecognized(script) : applyBlocks(repo, commands, script);
|
|
13246
|
+
return script.startsWith("#!") ? unrecognized(script) : applyBlocks(repo, commands, script, git);
|
|
13243
13247
|
};
|
|
13244
|
-
const applyBlocks = (repo, commands, script) => {
|
|
13245
|
-
const recognizers = recognizersFor(repo, commands);
|
|
13248
|
+
const applyBlocks = (repo, commands, script, git) => {
|
|
13249
|
+
const recognizers = recognizersFor(repo, commands, git);
|
|
13246
13250
|
let output = "";
|
|
13247
13251
|
for (const block of splitBlocks(script)) {
|
|
13248
13252
|
const outcome = recognizeBlock(recognizers, block);
|
|
@@ -13266,11 +13270,12 @@ const failedAt = (outcome, output) => {
|
|
|
13266
13270
|
error: outcome.error
|
|
13267
13271
|
};
|
|
13268
13272
|
};
|
|
13273
|
+
const UNRECOGNIZED_PREFIX = "unrecognized script block: ";
|
|
13269
13274
|
const unrecognized = (block, output = "") => ({
|
|
13270
13275
|
ok: false,
|
|
13271
13276
|
status: 1,
|
|
13272
13277
|
output,
|
|
13273
|
-
error:
|
|
13278
|
+
error: `${UNRECOGNIZED_PREFIX}${block.split("\n")[0]}`
|
|
13274
13279
|
});
|
|
13275
13280
|
//#endregion
|
|
13276
13281
|
//#region src/testkit/TestRepo.ts
|
|
@@ -13322,10 +13327,17 @@ const LINES = [
|
|
|
13322
13327
|
repo.writeFile(dest, content);
|
|
13323
13328
|
}]
|
|
13324
13329
|
];
|
|
13325
|
-
const
|
|
13326
|
-
|
|
13327
|
-
|
|
13328
|
-
|
|
13330
|
+
const knownLine = (line) => LINES.find(([pattern]) => pattern.test(line));
|
|
13331
|
+
const cannotRun = (error) => /* @__PURE__ */ new Error(`testkit: cannot run script in memory — give the step an actor instead\n${error}`);
|
|
13332
|
+
const runLines = (repo, script) => {
|
|
13333
|
+
const lines = script.split("\n").map((line) => line.trim());
|
|
13334
|
+
const known = lines.map(knownLine);
|
|
13335
|
+
if (known.every(Boolean)) {
|
|
13336
|
+
known.forEach((k, i) => k[1](repo, words(lines[i])));
|
|
13337
|
+
return;
|
|
13338
|
+
}
|
|
13339
|
+
const applied = applyEmittedScript(repo, /* @__PURE__ */ new Map(), script, { git: false });
|
|
13340
|
+
if (!applied.ok) throw cannotRun(applied.error ?? "");
|
|
13329
13341
|
};
|
|
13330
13342
|
const failed = (what, result) => /* @__PURE__ */ new Error(`${what} exited ${result.exitCode}\n${result.stderr}${result.stdout}`);
|
|
13331
13343
|
const testRepo = (options) => {
|
|
@@ -13407,7 +13419,7 @@ const testRepo = (options) => {
|
|
|
13407
13419
|
show: (ref, path) => repo.fileAtRef(ref, path) ?? void 0,
|
|
13408
13420
|
runScript: (script) => {
|
|
13409
13421
|
const flow = applyFlowScript(repo, /* @__PURE__ */ new Map(), script);
|
|
13410
|
-
if (flow === void 0)
|
|
13422
|
+
if (flow === void 0) runLines(repo, script);
|
|
13411
13423
|
else if (!flow.ok) throw new Error(`testkit: ${flow.error ?? ""}`);
|
|
13412
13424
|
},
|
|
13413
13425
|
gtd,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { agent, changes, glob, read, refuse, scope, start, vars } from "../flows.mjs";
|
|
2
|
-
import { a as flagShipped, b as coder, c as envDefaults, i as skills, n as summary, r as access, s as defaults, t as steering } from "../chunks/reserved-
|
|
3
|
-
import { i as refusePendingHandOff, r as handOffTail } from "../chunks/tail-
|
|
2
|
+
import { a as flagShipped, b as coder, c as envDefaults, i as skills, n as summary, r as access, s as defaults, t as steering } from "../chunks/reserved-UdSuHDUJ.mjs";
|
|
3
|
+
import { i as refusePendingHandOff, r as handOffTail } from "../chunks/tail-fj60rQ3y.mjs";
|
|
4
4
|
import { i as requireFlag, r as registryEntry, t as flagReferences } from "../chunks/registry-CP3YuV9j.mjs";
|
|
5
5
|
//#region src/workflows/cleanup/cleanup.ts
|
|
6
6
|
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.`;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { answered, changes, changesSince, glob, hasThreadFor, head, human, judge, judgeDiffShape, kept, mark, numeric, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireRevert, requireThreadsClosed, restoreScript, revertScript, run, scope, seamGate, start, vars } from "../flows.mjs";
|
|
2
2
|
import { review as review$1 } from "../workflow-kit.mjs";
|
|
3
|
-
import { $ as refactorFixPrompt, A as architectureAuthorPrompt, B as designGateAnswerMessage, C as fixRisks, D as reviewing, F as buildReviewCollectingPrompt, H as designTriagePrompt, I as buildReviewFixNitsPrompt, J as implementFixPrompt, K as idleMessage, L as buildReviewTriageMessage, M as architectureGateAnswerMessage, N as buildReviewAnswerQuestionsPrompt, O as agentWithSkills, P as buildReviewAwaitAfterDeferMessage, Q as refactorBuildingPrompt, R as builderSystem, T as reviewMissing, V as designSystem, X as packagesItemBuildingPrompt, Y as naivePassPrompt, Z as packagesItemFixSuitePrompt, _ as REQUIREMENTS, at as seamByHandMessage, b as coder, c as envDefaults, ct as unwindFailedMessage, d as gate, et as reviewEditNotesCapture, h as FEEDBACK, i as skills, it as reviewerSystem, j as architectureDecomposePrompt, k as architectSystem, lt as unwindFailure, m as ARCHITECTURE, n as summary, o as shipped, ot as seamSteerMessage, p as healthy, q as implementBuildingPrompt, r as access, rt as reviewNotesCapture, s as defaults, st as startGateBlockedMessage, t as steering, tt as reviewEditsCapture, v as REVIEW, w as planner, x as fixCheck, y as awaitReview, z as deferNotesScript } from "../chunks/reserved-
|
|
4
|
-
import { a as tail, i as refusePendingHandOff, n as closeReview } from "../chunks/tail-
|
|
3
|
+
import { $ as refactorFixPrompt, A as architectureAuthorPrompt, B as designGateAnswerMessage, C as fixRisks, D as reviewing, F as buildReviewCollectingPrompt, H as designTriagePrompt, I as buildReviewFixNitsPrompt, J as implementFixPrompt, K as idleMessage, L as buildReviewTriageMessage, M as architectureGateAnswerMessage, N as buildReviewAnswerQuestionsPrompt, O as agentWithSkills, P as buildReviewAwaitAfterDeferMessage, Q as refactorBuildingPrompt, R as builderSystem, T as reviewMissing, V as designSystem, X as packagesItemBuildingPrompt, Y as naivePassPrompt, Z as packagesItemFixSuitePrompt, _ as REQUIREMENTS, at as seamByHandMessage, b as coder, c as envDefaults, ct as unwindFailedMessage, d as gate, et as reviewEditNotesCapture, h as FEEDBACK, i as skills, it as reviewerSystem, j as architectureDecomposePrompt, k as architectSystem, lt as unwindFailure, m as ARCHITECTURE, n as summary, o as shipped, ot as seamSteerMessage, p as healthy, q as implementBuildingPrompt, r as access, rt as reviewNotesCapture, s as defaults, st as startGateBlockedMessage, t as steering, tt as reviewEditsCapture, v as REVIEW, w as planner, x as fixCheck, y as awaitReview, z as deferNotesScript } from "../chunks/reserved-UdSuHDUJ.mjs";
|
|
4
|
+
import { a as tail, i as refusePendingHandOff, n as closeReview } from "../chunks/tail-fj60rQ3y.mjs";
|
|
5
5
|
//#region src/workflows/feature/tier.ts
|
|
6
6
|
/** GTD_FLAG_NAIVE_PASS, GTD_FLAG_RISK_TIER: both pinned at process start, so a mid-process flip never moves a process. */
|
|
7
7
|
const riskTierOn = () => vars.flag_naive_pass === "1" && vars.flag_risk_tier === "1";
|
|
@@ -355,7 +355,7 @@ const review = (base, escalations = { rounds: 0 }) => {
|
|
|
355
355
|
awaitReview: (b) => {
|
|
356
356
|
const notes = deferred;
|
|
357
357
|
deferred = [];
|
|
358
|
-
return notes.length > 0 ? awaitReview(b, buildReviewAwaitAfterDeferMessage(b, notes)
|
|
358
|
+
return notes.length > 0 ? awaitReview(b, buildReviewAwaitAfterDeferMessage(b, notes)) : awaitReview(b);
|
|
359
359
|
},
|
|
360
360
|
reviewMissing,
|
|
361
361
|
collecting,
|
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 { d as reviewNotes, f as reviewRisks } from "../chunks/steering-DVILanVf.mjs";
|
|
3
|
-
import { O as agentWithSkills, U as finisherSystem, W as fixReviewNotesPrompt, b as coder, c as envDefaults, i as skills, l as baseline, n as summary, o as shipped, r as access, s as defaults, t as steering, v as REVIEW } from "../chunks/reserved-
|
|
4
|
-
import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-
|
|
3
|
+
import { O as agentWithSkills, U as finisherSystem, W as fixReviewNotesPrompt, b as coder, c as envDefaults, i as skills, l as baseline, n as summary, o as shipped, r as access, s as defaults, t as steering, v as REVIEW } from "../chunks/reserved-UdSuHDUJ.mjs";
|
|
4
|
+
import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-fj60rQ3y.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,6 +1,6 @@
|
|
|
1
1
|
import { start } from "../flows.mjs";
|
|
2
|
-
import { c as envDefaults, d as gate, i as skills, n as summary, nt as reviewGateBlockedMessage, o as shipped, r as access, s as defaults, t as steering } from "../chunks/reserved-
|
|
3
|
-
import { i as refusePendingHandOff, r as handOffTail } from "../chunks/tail-
|
|
2
|
+
import { c as envDefaults, d as gate, i as skills, n as summary, nt as reviewGateBlockedMessage, o as shipped, r as access, s as defaults, t as steering } from "../chunks/reserved-UdSuHDUJ.mjs";
|
|
3
|
+
import { i as refusePendingHandOff, r as handOffTail } from "../chunks/tail-fj60rQ3y.mjs";
|
|
4
4
|
//#region src/workflows/review/review.ts
|
|
5
5
|
/**
|
|
6
6
|
* Pure review of everything since `reviewBase`: a green gate, the quality
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { agent, changesSince, head, read, refuse, scope, vars } from "../flows.mjs";
|
|
2
|
-
import { a as flagShipped, b as coder, c as envDefaults, d as gate, i as skills, n as summary, r as access, s as defaults, t as steering } from "../chunks/reserved-
|
|
2
|
+
import { a as flagShipped, b as coder, c as envDefaults, d as gate, i as skills, n as summary, r as access, s as defaults, t as steering } from "../chunks/reserved-UdSuHDUJ.mjs";
|
|
3
3
|
import { i as requireFlag, n as flipProblems } from "../chunks/registry-CP3YuV9j.mjs";
|
|
4
4
|
//#region src/workflows/rollout/rollout.ts
|
|
5
5
|
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.`;
|
package/dist/workflows.mjs
CHANGED
|
@@ -17995,13 +17995,13 @@ const fixRisks = (notes) => agentWithSkills("review.fix.risks.fixing", buildRevi
|
|
|
17995
17995
|
system: reviewerSystem(),
|
|
17996
17996
|
allowEmpty: true
|
|
17997
17997
|
});
|
|
17998
|
-
const awaitReview = (base, message = buildReviewAwaitReviewMessage(base)
|
|
17998
|
+
const awaitReview = (base, message = buildReviewAwaitReviewMessage(base)) => human("review.await-review", {
|
|
17999
17999
|
message,
|
|
18000
18000
|
label: "Awaiting your review",
|
|
18001
18001
|
file: REVIEW,
|
|
18002
18002
|
mode: "review",
|
|
18003
18003
|
base,
|
|
18004
|
-
|
|
18004
|
+
acceptClean: true
|
|
18005
18005
|
});
|
|
18006
18006
|
/** The round at commit `round` left no `.gtd/REVIEW.md`. */
|
|
18007
18007
|
const reviewMissing = (round) => human("review.review-missing", {
|
|
@@ -37911,7 +37911,7 @@ const review$1 = (base, escalations = { rounds: 0 }) => {
|
|
|
37911
37911
|
awaitReview: (b) => {
|
|
37912
37912
|
const notes = deferred;
|
|
37913
37913
|
deferred = [];
|
|
37914
|
-
return notes.length > 0 ? awaitReview(b, buildReviewAwaitAfterDeferMessage(b, notes)
|
|
37914
|
+
return notes.length > 0 ? awaitReview(b, buildReviewAwaitAfterDeferMessage(b, notes)) : awaitReview(b);
|
|
37915
37915
|
},
|
|
37916
37916
|
reviewMissing,
|
|
37917
37917
|
collecting,
|
package/package.json
CHANGED
package/src/cli/Cli.ts
CHANGED
|
@@ -241,7 +241,7 @@ const FLAGS: readonly FlagRow[] = [
|
|
|
241
241
|
help: [
|
|
242
242
|
"(gtd land, with --cost) tag that cost's model",
|
|
243
243
|
"(gtd judge run, llm answerer) the claude model; omitted:",
|
|
244
|
-
"GTD_JUDGE_MODEL, then judge.json, then
|
|
244
|
+
"GTD_JUDGE_MODEL, then judge.json, then sonnet",
|
|
245
245
|
],
|
|
246
246
|
},
|
|
247
247
|
{
|
|
@@ -673,7 +673,7 @@ const COMMAND_ROWS: readonly CommandRow[] = [
|
|
|
673
673
|
"--answers <path> or GTD_JUDGE_ANSWERS; questions the file",
|
|
674
674
|
"does not cover are left out. --provider jev asks TypeSafe",
|
|
675
675
|
"(TYPESAFE_API_KEY). --provider llm asks the `claude` CLI",
|
|
676
|
-
"on PATH (Claude Code only), model
|
|
676
|
+
"on PATH (Claude Code only), model sonnet unless --model.",
|
|
677
677
|
"No --provider: GTD_JUDGE_PROVIDER, then ~/.config/gtd/",
|
|
678
678
|
"judge.json, else jev when TYPESAFE_API_KEY is set and no",
|
|
679
679
|
"model is given, else llm; --model likewise falls back to",
|
|
@@ -630,6 +630,18 @@ describe("applyEmittedScript — Emit.ts's combinedScript (the whole stdout of a
|
|
|
630
630
|
expect(repo.lastCommitMessage()).toBe("gtd(human): idle")
|
|
631
631
|
})
|
|
632
632
|
|
|
633
|
+
it("fails an unrecognized optional block under git: false", () => {
|
|
634
|
+
const repo = new InMemRepo()
|
|
635
|
+
const result = applyEmittedScript(
|
|
636
|
+
repo,
|
|
637
|
+
NO_COMMANDS,
|
|
638
|
+
combinedScript("set -eu", "totally-unrecognized-command"),
|
|
639
|
+
{ git: false },
|
|
640
|
+
)
|
|
641
|
+
expect(result.ok).toBe(false)
|
|
642
|
+
expect(result.error).toContain("totally-unrecognized-command")
|
|
643
|
+
})
|
|
644
|
+
|
|
633
645
|
it("still fails loudly on an unrecognized block outside the presentation-only subshell", () => {
|
|
634
646
|
const repo = new InMemRepo()
|
|
635
647
|
const script = `${combinedScript("totally-unrecognized-required-command", "")}`
|
|
@@ -691,3 +703,32 @@ describe("applyEmittedScript — commitClean", () => {
|
|
|
691
703
|
expect(snapshot(repo)).toEqual(before)
|
|
692
704
|
})
|
|
693
705
|
})
|
|
706
|
+
|
|
707
|
+
describe("applyEmittedScript — git: false reaches through the presentation subshell", () => {
|
|
708
|
+
it("refuses a git builder wrapped in combinedScript and changes nothing", () => {
|
|
709
|
+
const repo = new InMemRepo()
|
|
710
|
+
repo.writeFile("a.txt", "1")
|
|
711
|
+
const before = snapshot(repo)
|
|
712
|
+
const result = applyEmittedScript(
|
|
713
|
+
repo,
|
|
714
|
+
NO_COMMANDS,
|
|
715
|
+
combinedScript("set -eu", commitAll("chore: sneaky")),
|
|
716
|
+
{ git: false },
|
|
717
|
+
)
|
|
718
|
+
expect(result.ok).toBe(false)
|
|
719
|
+
expect(result.error).toMatch(/unrecognized script block/)
|
|
720
|
+
expect(snapshot(repo)).toEqual(before)
|
|
721
|
+
})
|
|
722
|
+
|
|
723
|
+
it("still applies a non-git write-lines optional half", () => {
|
|
724
|
+
const repo = new InMemRepo()
|
|
725
|
+
const result = applyEmittedScript(
|
|
726
|
+
repo,
|
|
727
|
+
NO_COMMANDS,
|
|
728
|
+
combinedScript("set -eu", "mkdir -p d\nprintf '%s\\n' 'x' > 'd/a.txt'"),
|
|
729
|
+
{ git: false },
|
|
730
|
+
)
|
|
731
|
+
expect(result).toMatchObject({ ok: true })
|
|
732
|
+
expect(repo.readFile("d/a.txt")).toBe("x\n")
|
|
733
|
+
})
|
|
734
|
+
})
|
|
@@ -563,7 +563,7 @@ const recognizeGtdUncheck = (repo: InMemRepo, block: string): BlockOutcome | und
|
|
|
563
563
|
const recognizeDidNotRunComment = (block: string): BlockOutcome | undefined =>
|
|
564
564
|
block === DID_NOT_RUN_COMMENT ? { kind: "noop" } : undefined
|
|
565
565
|
|
|
566
|
-
/** `combinedScript`'s optional half.
|
|
566
|
+
/** `combinedScript`'s optional half. Reports `noop` whatever the inner result, mirroring bash's `( … ) || <warning>`. Exception: under `git: false` any unrecognized inner block fails the outer script (git or not), so the in-memory twin knowingly diverges from `sh` rather than run git unseen. */
|
|
567
567
|
const PRESENTATION_FAILURE_TEXT = `${extractQuotedTokens(PRESENTATION_FAILURE_WARNING)[0]!.replace(/\\n$/, "")}\n`
|
|
568
568
|
const PRESENTATION_SUBSHELL_PREFIX = `${PRESENTATION_ONLY_COMMENT}\n(\n`
|
|
569
569
|
const PRESENTATION_SUBSHELL_SUFFIX = `\n) || ${PRESENTATION_FAILURE_WARNING}`
|
|
@@ -572,6 +572,7 @@ const recognizePresentationSubshell = (
|
|
|
572
572
|
repo: InMemRepo,
|
|
573
573
|
commands: ReadonlyMap<string, ScriptedCommand>,
|
|
574
574
|
block: string,
|
|
575
|
+
git: boolean,
|
|
575
576
|
): BlockOutcome | undefined => {
|
|
576
577
|
if (
|
|
577
578
|
!block.startsWith(PRESENTATION_SUBSHELL_PREFIX) ||
|
|
@@ -583,8 +584,11 @@ const recognizePresentationSubshell = (
|
|
|
583
584
|
PRESENTATION_SUBSHELL_PREFIX.length,
|
|
584
585
|
block.length - PRESENTATION_SUBSHELL_SUFFIX.length,
|
|
585
586
|
)
|
|
586
|
-
const result = applyEmittedScript(repo, commands, inner)
|
|
587
|
+
const result = applyEmittedScript(repo, commands, inner, { git })
|
|
587
588
|
if (result.ok) return { kind: "noop", output: result.output }
|
|
589
|
+
if (!git && result.error?.startsWith(UNRECOGNIZED_PREFIX)) {
|
|
590
|
+
return { kind: "failed", error: `${result.output}${result.error}` }
|
|
591
|
+
}
|
|
588
592
|
return { kind: "noop", output: `${result.output}${PRESENTATION_FAILURE_TEXT}` }
|
|
589
593
|
}
|
|
590
594
|
|
|
@@ -703,17 +707,18 @@ const recognizeWriteLines = (repo: InMemRepo, block: string): BlockOutcome | und
|
|
|
703
707
|
const recognizersFor = (
|
|
704
708
|
repo: InMemRepo,
|
|
705
709
|
commands: ReadonlyMap<string, ScriptedCommand>,
|
|
710
|
+
git: boolean,
|
|
706
711
|
): ReadonlyArray<(block: string) => BlockOutcome | undefined> => [
|
|
707
712
|
(block) => recognizeSetFlags(block),
|
|
708
713
|
(block) => recognizeDidNotRunComment(block),
|
|
709
714
|
(block) => recognizePrecondition(repo, block),
|
|
710
715
|
(block) => recognizeFileExistsGuard(repo, block),
|
|
711
716
|
(block) => recognizeBinaryGuard(commands, block),
|
|
712
|
-
(block) => recognizeGitBuilders(repo, block),
|
|
717
|
+
(block) => (git ? recognizeGitBuilders(repo, block) : undefined),
|
|
713
718
|
(block) => recognizeDeferNotes(repo, block),
|
|
714
719
|
(block) => recognizeWriteLines(repo, block),
|
|
715
720
|
(block) => recognizeFailurePromptWrapper(repo, commands, block),
|
|
716
|
-
(block) => recognizePresentationSubshell(repo, commands, block),
|
|
721
|
+
(block) => recognizePresentationSubshell(repo, commands, block, git),
|
|
717
722
|
(block) => recognizeModeContradictionSkipNotice(block),
|
|
718
723
|
(block) => recognizeModeContradictionCheck(repo, commands, block),
|
|
719
724
|
(block) => recognizeGtdCheck(repo, block),
|
|
@@ -746,6 +751,7 @@ export const applyEmittedScript = (
|
|
|
746
751
|
repo: InMemRepo,
|
|
747
752
|
commands: ReadonlyMap<string, ScriptedCommand>,
|
|
748
753
|
script: string,
|
|
754
|
+
{ git = true }: { readonly git?: boolean } = {},
|
|
749
755
|
): AppliedScriptResult => {
|
|
750
756
|
const flow = applyFlowScript(repo, commands, script)
|
|
751
757
|
if (flow !== undefined) {
|
|
@@ -753,15 +759,16 @@ export const applyEmittedScript = (
|
|
|
753
759
|
? { ok: true, status: 0, output: flow.output }
|
|
754
760
|
: { ok: false, status: 1, output: flow.output, error: flow.error ?? "" }
|
|
755
761
|
}
|
|
756
|
-
return script.startsWith("#!") ? unrecognized(script) : applyBlocks(repo, commands, script)
|
|
762
|
+
return script.startsWith("#!") ? unrecognized(script) : applyBlocks(repo, commands, script, git)
|
|
757
763
|
}
|
|
758
764
|
|
|
759
765
|
const applyBlocks = (
|
|
760
766
|
repo: InMemRepo,
|
|
761
767
|
commands: ReadonlyMap<string, ScriptedCommand>,
|
|
762
768
|
script: string,
|
|
769
|
+
git: boolean,
|
|
763
770
|
): AppliedScriptResult => {
|
|
764
|
-
const recognizers = recognizersFor(repo, commands)
|
|
771
|
+
const recognizers = recognizersFor(repo, commands, git)
|
|
765
772
|
let output = ""
|
|
766
773
|
for (const block of splitBlocks(script)) {
|
|
767
774
|
const outcome = recognizeBlock(recognizers, block)
|
|
@@ -786,9 +793,11 @@ const failedAt = (
|
|
|
786
793
|
}
|
|
787
794
|
}
|
|
788
795
|
|
|
796
|
+
const UNRECOGNIZED_PREFIX = "unrecognized script block: "
|
|
797
|
+
|
|
789
798
|
const unrecognized = (block: string, output = ""): AppliedScriptResult => ({
|
|
790
799
|
ok: false,
|
|
791
800
|
status: 1,
|
|
792
801
|
output,
|
|
793
|
-
error:
|
|
802
|
+
error: `${UNRECOGNIZED_PREFIX}${block.split("\n")[0]}`,
|
|
794
803
|
})
|
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest"
|
|
2
|
+
import {
|
|
3
|
+
commitAll,
|
|
4
|
+
combinedScript,
|
|
5
|
+
commitAsIs,
|
|
6
|
+
commitClean,
|
|
7
|
+
deleteRef,
|
|
8
|
+
discardPending,
|
|
9
|
+
hardResetTo,
|
|
10
|
+
keepTurn,
|
|
11
|
+
mixedResetTo,
|
|
12
|
+
softResetTo,
|
|
13
|
+
updateRef,
|
|
14
|
+
} from "../emit/index.js"
|
|
2
15
|
import { agent, human, moveScript, read, removeScript, run } from "../flows/index.js"
|
|
3
16
|
import { runsItsScript, testRepo } from "./TestRepo.js"
|
|
4
17
|
|
|
@@ -93,7 +106,72 @@ describe("testRepo", () => {
|
|
|
93
106
|
|
|
94
107
|
it("refuses to fake a script it cannot run", () => {
|
|
95
108
|
const repo = testRepo({ workflow: { default: tiny } })
|
|
96
|
-
expect(() => repo.runScript("npm test\n")).toThrow(/cannot run
|
|
109
|
+
expect(() => repo.runScript("npm test\n")).toThrow(/cannot run script in memory[\s\S]*npm test/)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it("carries the recognizer's error when a git-free fallback fails", () => {
|
|
113
|
+
const repo = testRepo({ workflow: { default: tiny } })
|
|
114
|
+
expect(() => repo.runScript("printf 'x' > 'y'\n")).toThrow(
|
|
115
|
+
"testkit: cannot run script in memory — give the step an actor instead\nunrecognized script block: printf 'x' > 'y'",
|
|
116
|
+
)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it("names the block that failed, not an earlier line that ran", () => {
|
|
120
|
+
const repo = testRepo({ workflow: { default: tiny } })
|
|
121
|
+
let message = ""
|
|
122
|
+
try {
|
|
123
|
+
repo.runScript("mkdir -p d\nprintf '%s\\n' 'x' > 'd/a.txt'\n\nnpm test\n")
|
|
124
|
+
} catch (e) {
|
|
125
|
+
message = (e as Error).message
|
|
126
|
+
}
|
|
127
|
+
expect(message).toContain("unrecognized script block: npm test")
|
|
128
|
+
expect(message).not.toContain("printf")
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it.each([
|
|
132
|
+
["commitAll", commitAll("chore: sneaky")],
|
|
133
|
+
["commitClean", commitClean("chore: sneaky")],
|
|
134
|
+
["commitAsIs", commitAsIs("chore: sneaky")],
|
|
135
|
+
["keepTurn", keepTurn("refs/x", "chore: sneaky")],
|
|
136
|
+
["discardPending", discardPending()],
|
|
137
|
+
["softResetTo", softResetTo("HEAD~1")],
|
|
138
|
+
["mixedResetTo", mixedResetTo("HEAD~1")],
|
|
139
|
+
["hardResetTo", hardResetTo("HEAD~1")],
|
|
140
|
+
["updateRef", updateRef("refs/x", "HEAD")],
|
|
141
|
+
["deleteRef", deleteRef("refs/x")],
|
|
142
|
+
])("refuses a %s script instead of mutating git in memory", (_, script) => {
|
|
143
|
+
const repo = testRepo({ workflow: { default: tiny } })
|
|
144
|
+
repo.write("a.txt", "dirty\n")
|
|
145
|
+
const before = repo.subjects()
|
|
146
|
+
expect(() => repo.runScript(script)).toThrow(/unrecognized script block/)
|
|
147
|
+
expect(repo.subjects()).toEqual(before)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it.each([
|
|
151
|
+
["commitAll", commitAll("chore: sneaky")],
|
|
152
|
+
["commitClean", commitClean("chore: sneaky")],
|
|
153
|
+
["commitAsIs", commitAsIs("chore: sneaky")],
|
|
154
|
+
["keepTurn", keepTurn("refs/x", "chore: sneaky")],
|
|
155
|
+
["discardPending", discardPending()],
|
|
156
|
+
["softResetTo", softResetTo("HEAD~1")],
|
|
157
|
+
["mixedResetTo", mixedResetTo("HEAD~1")],
|
|
158
|
+
["hardResetTo", hardResetTo("HEAD~1")],
|
|
159
|
+
["updateRef", updateRef("refs/x", "HEAD")],
|
|
160
|
+
["deleteRef", deleteRef("refs/x")],
|
|
161
|
+
])("refuses a wrapped %s script instead of mutating git in memory", (_, script) => {
|
|
162
|
+
const repo = testRepo({ workflow: { default: tiny } })
|
|
163
|
+
repo.write("a.txt", "dirty\n")
|
|
164
|
+
const before = repo.subjects()
|
|
165
|
+
expect(() => repo.runScript(combinedScript("set -eu", script))).toThrow(
|
|
166
|
+
/cannot run script in memory[\s\S]*unrecognized script block/,
|
|
167
|
+
)
|
|
168
|
+
expect(repo.subjects()).toEqual(before)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it("applies a write block `LINES` lacks through the git-free recognizers", () => {
|
|
172
|
+
const repo = testRepo({ workflow: { default: tiny } })
|
|
173
|
+
repo.runScript("mkdir -p d\nprintf '%s\\n' 'echo $(git rev-parse HEAD)' > 'd/a.txt'\n")
|
|
174
|
+
expect(repo.read("d/a.txt")).toBe("echo $(git rev-parse HEAD)\n")
|
|
97
175
|
})
|
|
98
176
|
|
|
99
177
|
it("reads a file as committed", () => {
|
package/src/testkit/TestRepo.ts
CHANGED
|
@@ -150,12 +150,23 @@ const LINES: ReadonlyArray<readonly [RegExp, (repo: InMemRepo, words: string[])
|
|
|
150
150
|
],
|
|
151
151
|
]
|
|
152
152
|
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
153
|
+
const knownLine = (line: string) => LINES.find(([pattern]) => pattern.test(line))
|
|
154
|
+
|
|
155
|
+
const cannotRun = (error: string): Error =>
|
|
156
|
+
new Error(`testkit: cannot run script in memory — give the step an actor instead\n${error}`)
|
|
157
|
+
|
|
158
|
+
// A script with a line `LINES` lacks goes to the contract-tested recognizers
|
|
159
|
+
// whole, minus the git builders: they would apply a commit/reset/update-ref in
|
|
160
|
+
// memory, so any git-mutating block is refused whatever its spelling.
|
|
161
|
+
const runLines = (repo: InMemRepo, script: string): void => {
|
|
162
|
+
const lines = script.split("\n").map((line) => line.trim())
|
|
163
|
+
const known = lines.map(knownLine)
|
|
164
|
+
if (known.every(Boolean)) {
|
|
165
|
+
known.forEach((k, i) => k))
|
|
166
|
+
return
|
|
157
167
|
}
|
|
158
|
-
|
|
168
|
+
const applied = applyEmittedScript(repo, new Map(), script, { git: false })
|
|
169
|
+
if (!applied.ok) throw cannotRun(applied.error ?? "")
|
|
159
170
|
}
|
|
160
171
|
|
|
161
172
|
const failed = (what: string, result: CapturedCliResult): Error =>
|
|
@@ -251,7 +262,7 @@ export const testRepo = (options: TestRepoOptions): TestRepo => {
|
|
|
251
262
|
show: (ref, path) => repo.fileAtRef(ref, path) ?? undefined,
|
|
252
263
|
runScript: (script) => {
|
|
253
264
|
const flow = applyFlowScript(repo, new Map(), script)
|
|
254
|
-
if (flow === undefined)
|
|
265
|
+
if (flow === undefined) runLines(repo, script)
|
|
255
266
|
else if (!flow.ok) throw new Error(`testkit: ${flow.error ?? ""}`)
|
|
256
267
|
},
|
|
257
268
|
gtd,
|