@pmelab/gtd 49.0.0 → 51.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 +16 -3
- package/dist/chunks/{reserved-CYZ1bSBy.mjs → reserved-CUkk9XgL.mjs} +151 -5
- package/dist/chunks/{review-BewyJflS.mjs → review-BaCXlIT2.mjs} +84 -20
- package/dist/chunks/{steering-BXwjmocB.mjs → steering-DtUVUz9l.mjs} +44 -14
- package/dist/chunks/{tail-C1Oi2YEy.mjs → tail-CX6Pdsql.mjs} +1 -1
- package/dist/gtd.bundle.mjs +22 -5
- package/dist/steering.mjs +2 -2
- package/dist/testkit.mjs +1 -1
- package/dist/web/main.js +27 -3
- package/dist/workflow-kit.mjs +1 -1
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +144 -11
- package/dist/workflows/fix.mjs +3 -3
- package/dist/workflows/review.mjs +3 -3
- package/dist/workflows/rollout.mjs +1 -1
- package/dist/workflows.mjs +415 -42
- package/package.json +1 -1
- package/src/steering/index.ts +1 -0
- package/src/steering/review.test.ts +60 -2
- package/src/steering/review.ts +66 -9
- package/src/workflows/feature/coverage.feature +1 -0
- package/src/workflows/feature/fast-path.feature +1 -0
- package/src/workflows/feature/feature.ts +4 -1
- package/src/workflows/feature/flag-mode.feature +1 -1
- package/src/workflows/feature/lens-models.feature +1 -0
- package/src/workflows/feature/lens-review.feature +17 -4
- package/src/workflows/feature/one-step-land.feature +1 -0
- package/src/workflows/feature/prototype-rounds.feature +152 -0
- package/src/workflows/feature/prototype.feature +419 -0
- package/src/workflows/feature/prototype.test.ts +43 -0
- package/src/workflows/feature/prototype.ts +181 -0
- package/src/workflows/feature/refactor-absorbs.feature +9 -1
- package/src/workflows/feature/refactor-first.feature +13 -3
- package/src/workflows/feature/refactor-rebuild.feature +29 -5
- package/src/workflows/feature/refactor-review.feature +111 -13
- package/src/workflows/feature/refactor.test.ts +14 -4
- package/src/workflows/feature/refactor.ts +2 -0
- package/src/workflows/feature/review-answers.feature +9 -1
- package/src/workflows/feature/review-gate.feature +21 -3
- package/src/workflows/feature/reviewer-walkthrough.feature +15 -3
- package/src/workflows/feature/seam-rejection.feature +1 -0
- package/src/workflows/feature/seam.feature +1 -0
- package/src/workflows/feature/seam.test.ts +3 -2
- package/src/workflows/feature/seam.ts +19 -5
- package/src/workflows/feature/severity-budget.feature +112 -0
- package/src/workflows/feature/severity-sorted-review.feature +198 -0
- package/src/workflows/feature/slice-findings.feature +15 -2
- package/src/workflows/feature/slicing-cap.feature +9 -0
- package/src/workflows/feature/slicing.feature +1 -0
- package/src/workflows/feature/standards.feature +5 -0
- package/src/workflows/shared/options.ts +1 -0
- package/src/workflows/shared/prose.ts +6 -0
- package/src/workflows/shared/review.ts +124 -18
- package/src/workflows/shared/skills.test.ts +4 -0
- package/src/workflows/shared/skills.ts +5 -0
- package/src/workflows/shared/steps.ts +1 -0
- package/src/workflows/shared/text.test.ts +90 -0
- package/src/workflows/shared/text.ts +181 -4
- package/src/workflows/shared/vars.ts +3 -0
package/dist/steering.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
export { ANSWER_FIX, ANSWER_LEAD, ANSWER_WONT_FIX, BUILT_IN_MODE_NAMES, FOOTNOTE_ACTION_TITLE, FREE_TEXT_PLACEHOLDER, THREAD_REPLY_ACTION_TITLE, applySteeringEdits, formatFinding, freeFormFormat, getParseCount, headingSectionBodies, headingSections, isAnswered, openThreadFindings, openThreads, parseCodeThreads, parseFootnotes, parseThreads, parseThreadsWithFindings, reviewChunks, reviewDecisions, reviewNotes, reviewRisks, steeringFormatFor, steeringFormatOrFreeForm, stripCodeThreads, unansweredNotes, unansweredQuestions, viewOf };
|
|
1
|
+
import { A as headingSections, C as THREAD_REPLY_ACTION_TITLE, D as parseThreadsWithFindings, E as parseThreads, O as getParseCount, S as FOOTNOTE_ACTION_TITLE, T as parseFootnotes, _ as reviewRisks, a as unansweredQuestions, b as FREE_TEXT_PLACEHOLDER, c as stripCodeThreads, d as ANSWER_FIX, f as ANSWER_LEAD, g as reviewNotes, h as reviewDecisions, i as steeringFormatOrFreeForm, j as formatFinding, k as headingSectionBodies, l as freeFormFormat, m as reviewChunks, n as openThreads, o as viewOf, p as ANSWER_WONT_FIX, r as steeringFormatFor, s as parseCodeThreads, t as BUILT_IN_MODE_NAMES, u as applySteeringEdits, v as severityLine, w as openThreadFindings, x as isAnswered, y as unansweredNotes } from "./chunks/steering-DtUVUz9l.mjs";
|
|
2
|
+
export { ANSWER_FIX, ANSWER_LEAD, ANSWER_WONT_FIX, BUILT_IN_MODE_NAMES, FOOTNOTE_ACTION_TITLE, FREE_TEXT_PLACEHOLDER, THREAD_REPLY_ACTION_TITLE, applySteeringEdits, formatFinding, freeFormFormat, getParseCount, headingSectionBodies, headingSections, isAnswered, openThreadFindings, openThreads, parseCodeThreads, parseFootnotes, parseThreads, parseThreadsWithFindings, reviewChunks, reviewDecisions, reviewNotes, reviewRisks, severityLine, steeringFormatFor, steeringFormatOrFreeForm, stripCodeThreads, unansweredNotes, unansweredQuestions, viewOf };
|
package/dist/testkit.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { answered, applyScript, checkScript, configProblems, configure, installContext, isFactoryWorkflow, moveScript, numeric, protocolMismatch, removeScript, restoreScript, revertScript, showScript, t as flows_exports, writeScript } from "./flows.mjs";
|
|
2
2
|
import { A as beatKindOf, B as ACCESS_REFUSAL, C as renderLandPlain, D as renderBeatJson, E as beatDocument, O as statusOf, S as renderLandJson, T as renderBeatPlain, U as renderFormat, _ as selectPath, a as contextLimitFault, g as resolveSession, h as globMatches, j as demandOf, l as parseContextWindow, m as slicesDocument, o as contextShapeFault, p as renderSlicesJson, v as judgeJson, x as noopText, y as landFields } from "./chunks/wire-BJuZWR3C.mjs";
|
|
3
|
-
import { A as
|
|
3
|
+
import { A as headingSections, D as parseThreadsWithFindings, a as unansweredQuestions, j as formatFinding, k as headingSectionBodies, n as openThreads, r as steeringFormatFor, s as parseCodeThreads, t as BUILT_IN_MODE_NAMES } from "./chunks/steering-DtUVUz9l.mjs";
|
|
4
4
|
import { $ as logWarning, $i as BaseProto, $n as map$5, $r as make$19, $t as provide$1, A as catchIf, Ai as add$1, An as scopeMake, Ar as flatMap$3, At as suspend$5, B as flatMap$4, Bi as getOrElse, Bn as get$3, Br as sync$1, Bt as unsafeMakeLatch, C as all$1, Ci as toArray, Cn as isInterrupted, Cr as exitFail, Ct as sandbox, D as async, Di as GenericTag, Dn as squash, Dr as exitZipRight, Dt as scopedWith$1, E as asVoid$1, Ei as unsafeHead, En as reduceWithContext, Er as exitVoid, Et as scoped$4, F as ensuring$2, Fi as fromIterable, Fn as join$3, Fr as map$3, Ft as tryMap, G as forkIn, Gi as match$4, Gn as TypeId$10, Gr as whenEffect, Gt as zipLeft, H as fnUntraced, Hi as isNone, Hn as modify$1, Hr as uninterruptible, Ht as when, I as exit, Ii as splitAt, In as sequential, Ir as onInterrupt, It as tryPromise, J as ignore, Ji as isLeft, Jn as all, Jr as DeferredTypeId$1, Jt as spaced$1, K as forkScoped, Ki as none, Kn as Class, Kr as withFiberRuntime, Kt as zipRight$3, L as fail$5, Li as filter$1, Ln as bind, Lr as scopeClose, Lt as try_, M as die$2, Mi as getOption, Mn as _await$2, Mr as interruptAsFiber, Mt as tap, N as dieMessage, Ni as omit, Nn as inheritAll$1, Nr as interruptFiber, Nt as tapErrorCause, O as catchAll$1, Oi as Reference, On as ensuring$3, Or as fiberId, Ot as serviceOption, P as either, Pi as empty$5, Pn as interruptAllAs$1, Pr as interruptible$1, Pt as timeout, Q as isEffect, Qi as pipeArguments, Qn as isFailure, Qr as ids, Qt as mergeAll$2, R as failCause$4, Ri as flatMap$5, Rn as bindTo, Rr as succeed$4, Rt as uninterruptible$1, S as addFinalizer$1, Si as takeRight, Sn as isDieType, Sr as exitAs, St as runtime, T as as, Ti as unsafeGet, Tn as map$6, Tr as exitSucceed, Tt as scopeWith, U as forEach$2, Ui as isSome, Un as set$1, Ur as uninterruptibleMask$1, Ut as withFiberRuntime$1, V as flatten$4, Vi as getOrUndefined, Vn as make$16, Vr as tap$1, Vt as unsafeMakeSemaphore$1, W as forkDaemon, Wi as map$8, Wn as update$1, Wr as void_$2, Wt as zip$1, X as interruptible, Xi as match$2, Xn as fail$4, Xr as combine, Xt as tagged, Y as interrupt$4, Yi as left, Yn as die, Yr as empty$6, Yt as TaggedError$1, Z as intoDeferred, Zi as right, Zn as failCause$3, Zr as combineAll, Zt as fromEffect$5, _ as decodeSync, _i as of, _n as fail$3, _r as deferredIsDone, _t as raceWith, a as Literal, aa as constFalse, ai as size$2, an as updateContext$1, ar as zipRight$2, at as matchCause, b as acquireRelease, bi as reverse, bn as interrupt$3, br as deferredUnsafeDone, bt as runPromise, c as String$, ca as constVoid, ci as append$1, cn as Scope, cr as asyncInterrupt, ct as negate, d as Union, da as pipe, di as empty$4, dn as close, dr as deferredAwait, dt as orElse, ea as NodeInspectSymbol, ei as none$1, en as provideMerge$1, er as mapBoth, et as makeSemaphore, f as between, fi as filter, fn as extend, fr as deferredComplete, ft as orElseSucceed, gi as map$7, gn as die$1, gr as deferredInterruptWith, gt as race, hi as isEmpty$2, hn as NoSuchElementException, hr as deferredInterrupt, ht as provideService, i as Defect, ia as hasProperty, ii as has, in as unsafeRunPromiseExit, ir as zip$2, it as match$5, j as catchTag, ji as get$5, jn as scopeTag, jr as interrupt$2, jt as sync$2, k as catchAllCause, ki as Tag, kn as forEachConcurrentDiscard, kr as fiberRefLocally, kt as succeed$6, l as Struct, la as dual, li as appendAll, ln as addFinalizer, lr as catchAll, lt as onError, m as optional$1, mi as isChunk, mn as make$18, mr as deferredFailCause, mt as provide$2, n as Boolean$, na as toJSON, ni as make$17, nn as succeed$3, nr as succeed$5, nt as mapBoth$1, o as Never, oa as constTrue, oi as decode, on as forkIn$1, or as as$1, ot as matchCauseEffect, p as decodeUnknown, pi as fromIterable$1, pn as fork, pr as deferredFail, pt as promise, q as gen, qi as some, qn as spanTag, qr as zipRight$1, qt as zipWith, r as Cause, ra as equals, ri as set$2, rn as unsafeFork, rr as void_$3, rt as mapError, s as Number$, sa as constUndefined, si as isFinite, sn as unsafeMakeSemaphore, sr as asVoid, st as matchEffect, t as Array$, ta as format, ti as get$4, tn as scoped$3, tr as match$3, tt as map$4, u as TaggedError, ua as identity, ui as drop, un as addFinalizerExit, ur as currentContext$1, ut as orDie, v as encodeSync, vi as prepend, vn as failureOrCause, vr as deferredMake, vt as repeat, w as annotateLogs, wi as unsafeFromArray, wn as isInterruptedOnly, wr as exitFailCause, wt as scope, x as acquireUseRelease$2, xi as take$2, xn as interruptors, xr as deferredUnsafeMake, xt as runPromiseExit$1, y as _void, yi as reduce, yn as flipCauseOption, yr as deferredSucceed, yt as retry, z as fiberIdWith, zi as fromNullable, zn as sleep, zr as suspend$4, zt as uninterruptibleMask } from "./chunks/Schema-C6fLlQzx.mjs";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import * as Crypto from "node:crypto";
|
package/dist/web/main.js
CHANGED
|
@@ -31565,6 +31565,8 @@ const qaDescriptor = {
|
|
|
31565
31565
|
unansweredQuestions,
|
|
31566
31566
|
openThreads: openThreadsOf
|
|
31567
31567
|
};
|
|
31568
|
+
/** A chunk's leading `_critical: security_` paragraph: the tier gtd ranked it with, and why. */
|
|
31569
|
+
const SEVERITY_LINE_RE = /^_((?:critical|should|could)(?::[^_\n]*)?)_$/;
|
|
31568
31570
|
/**
|
|
31569
31571
|
* The canonical sample. Every detail here is load-bearing for the formatter
|
|
31570
31572
|
* round-trip: both footnote bodies exceed 80 characters so a reflow actually
|
|
@@ -31855,12 +31857,26 @@ const answerListOf = (content, body) => {
|
|
|
31855
31857
|
})
|
|
31856
31858
|
};
|
|
31857
31859
|
};
|
|
31860
|
+
/** A leading severity line is the chunk's tier, never its prose. */
|
|
31861
|
+
const splitSeverity = (content, run) => {
|
|
31862
|
+
const first = run[0];
|
|
31863
|
+
const label = first?.type === "paragraph" ? SEVERITY_LINE_RE.exec(sourceText(content, first).trim())?.[1] : void 0;
|
|
31864
|
+
if (label === void 0) return { leadingRun: run };
|
|
31865
|
+
return {
|
|
31866
|
+
severity: {
|
|
31867
|
+
label,
|
|
31868
|
+
line: toLspPosition(first.position.start).line
|
|
31869
|
+
},
|
|
31870
|
+
leadingRun: run.slice(1)
|
|
31871
|
+
};
|
|
31872
|
+
};
|
|
31858
31873
|
/** Splits one chunk's body nodes into its file pointers (hunk pointers are task items, collected recursively at ANY nesting depth via `taskItems` — a nested hunk is the same kind of hunk as a top-level one) and description prose. The description is the chunk's own leading run and never a node that contains a hunk pointer: it stops at the first node whose `taskItems` include a real pointer (at any depth — a blockquote wrapping a pointer list stops it just as a top-level list would). Every node kind in what's left before that point contributes — a `footnoteDefinition` is the one exception, excluded by `blockNodesOfRun` itself (it's a note, never document content). */
|
|
31859
31874
|
const parseChunkBody = (content, lines, title, body) => {
|
|
31860
31875
|
const answer = answerListOf(content, body);
|
|
31861
31876
|
const isAnswer = (n) => answer !== void 0 && (n === answer.list || n === body[answer.lead]);
|
|
31862
31877
|
const firstPointerIndex = body.findIndex((n) => !isAnswer(n) && taskItems(n).some((item) => hasPointerToken(content, item)));
|
|
31863
|
-
const
|
|
31878
|
+
const run = (firstPointerIndex === -1 ? body : body.slice(0, firstPointerIndex)).filter((n) => !isAnswer(n));
|
|
31879
|
+
const { severity, leadingRun } = splitSeverity(content, run);
|
|
31864
31880
|
const descriptionNodes = blockNodesOfRun(content, leadingRun);
|
|
31865
31881
|
const descriptionInline = collapseImages(blockRunInline(content, leadingRun));
|
|
31866
31882
|
const description = leadingRun.filter((n) => n.type !== "footnoteDefinition").map((n) => sourceText(content, n)).join(" ").trim();
|
|
@@ -31879,6 +31895,7 @@ const parseChunkBody = (content, lines, title, body) => {
|
|
|
31879
31895
|
files,
|
|
31880
31896
|
errors,
|
|
31881
31897
|
...answer === void 0 ? {} : { answers: answer.options },
|
|
31898
|
+
...severity === void 0 ? {} : { severity },
|
|
31882
31899
|
boxed: body.some((n) => !isAnswer(n) && taskItems(n).length > 0)
|
|
31883
31900
|
};
|
|
31884
31901
|
};
|
|
@@ -31915,7 +31932,7 @@ const parseChangesets = (tree, content, lines) => {
|
|
|
31915
31932
|
const errors = [];
|
|
31916
31933
|
for (const { heading, headingLine, body } of splitChunks(tree)) {
|
|
31917
31934
|
const title = headingText$1(content, heading);
|
|
31918
|
-
const { description, descriptionNodes, descriptionInline, files, answers, boxed, errors: bodyErrors } = parseChunkBody(content, lines, title, body);
|
|
31935
|
+
const { description, descriptionNodes, descriptionInline, files, answers, severity, boxed, errors: bodyErrors } = parseChunkBody(content, lines, title, body);
|
|
31919
31936
|
errors.push(...bodyErrors);
|
|
31920
31937
|
if (files.length === 0 && boxed) errors.push({
|
|
31921
31938
|
message: `Chunk "${title}" has no file pointers`,
|
|
@@ -31929,7 +31946,8 @@ const parseChangesets = (tree, content, lines) => {
|
|
|
31929
31946
|
descriptionInline,
|
|
31930
31947
|
files,
|
|
31931
31948
|
headingLine,
|
|
31932
|
-
...answers === void 0 ? {} : { answers }
|
|
31949
|
+
...answers === void 0 ? {} : { answers },
|
|
31950
|
+
...severity === void 0 ? {} : { severity }
|
|
31933
31951
|
});
|
|
31934
31952
|
}
|
|
31935
31953
|
if (changesets.length === 0) errors.push({ message: "REVIEW.md has no '##' chunks" });
|
|
@@ -32230,6 +32248,7 @@ const reviewView = (content) => {
|
|
|
32230
32248
|
detail: chunk.description,
|
|
32231
32249
|
detailInline: chunk.descriptionInline,
|
|
32232
32250
|
body: chunk.descriptionNodes,
|
|
32251
|
+
...chunk.severity === void 0 ? {} : { status: chunk.severity.label },
|
|
32233
32252
|
anchor: {
|
|
32234
32253
|
kind: "chunk",
|
|
32235
32254
|
index: chunkIndex
|
|
@@ -34779,6 +34798,11 @@ const ChunkRow = ({ chunk, chunkIndex, state }) => {
|
|
|
34779
34798
|
disabled: hunks.length === 0,
|
|
34780
34799
|
className: "flex-1 text-left",
|
|
34781
34800
|
children: [
|
|
34801
|
+
chunk.status !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
34802
|
+
"data-testid": `chunk-severity-${chunkIndex}`,
|
|
34803
|
+
className: cn("text-small font-semibold", chunk.status.startsWith("critical") ? "text-warning" : "text-muted"),
|
|
34804
|
+
children: chunk.status
|
|
34805
|
+
}),
|
|
34782
34806
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
34783
34807
|
className: "font-semibold",
|
|
34784
34808
|
children: chunk.title
|
package/dist/workflow-kit.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { changes, changesSince, hasThreadFor, head, mark, read, refuse, requireReplies, requireThreadsClosed, scope } from "./flows.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { E as parseThreads, _ as reviewRisks, c as stripCodeThreads, g as reviewNotes, h as reviewDecisions, y as unansweredNotes } from "./chunks/steering-DtUVUz9l.mjs";
|
|
3
3
|
//#region src/workflow-kit/health.ts
|
|
4
4
|
/** Hand a red suite that repeated attempts could not fix to a person, until `limit` rounds are spent. */
|
|
5
5
|
const escalation = async (count, steps) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { agent, changes, glob, read, refuse, scope, start, vars } from "../flows.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-
|
|
2
|
+
import { C as coder, It as defaults, Lt as envDefaults, Qt as requireFlag, Yt as flagReferences, Zt as registryEntry, i as skills, n as summary, o as flagShipped, r as access, t as steering } from "../chunks/reserved-CUkk9XgL.mjs";
|
|
3
|
+
import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-CX6Pdsql.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, revertScript, run, scope, slice, start, vars, wrote } from "../flows.mjs";
|
|
2
2
|
import { lensEntriesOf } from "../workflow-kit.mjs";
|
|
3
|
-
import { $ as
|
|
4
|
-
import { a as refusePendingHandOff, c as joinReports, o as tail } from "../chunks/tail-
|
|
5
|
-
import { i as reUnwind, n as humanReview, r as isCode$1, t as buildTail } from "../chunks/review-
|
|
3
|
+
import { $ as implementFixPrompt, C as coder, Ct as seamByHandMessage, Dt as testerSystem, E as fixNits, Et as startGateBlockedMessage, G as designGateAnswerMessage, H as coverageQuestion, I as builderSystem, It as defaults, K as designSystem, Lt as envDefaults, Mt as unwindFailure, N as agentWithSkills, P as buildReviewCollectingPrompt, Q as implementBuildingPrompt, R as clearCriteriaMessage, Rt as testFileGlobs, St as reviewerSystem, Tt as slicingPrompt, U as coveringPrompt, V as coverageMessage, W as criterionLine, Z as idleMessage, _ as REFACTOR_NOTE, a as flagModeOn, at as prototypeOpenMessage, b as SLICES, d as healthy, dt as refactorBuildingPrompt, et as naiveApplyFailure, f as FEEDBACK, ft as refactorDroppedRefusal, g as REFACTOR, ht as refactorRules, i as skills, it as prototypeFixingPrompt, jt as unwindFailedMessage, k as planner, kt as uncoveredMessage, l as gate, m as PROTOTYPE, mt as refactorNotePrompt, n as summary, nt as normaliseCriteriaScript, ot as prototypeTestingPrompt, p as HANDOFF, pt as refactorFixPrompt, q as designTriagePrompt, r as access, rt as oversizedMessage, s as shipped, st as prototypeUnguidedMessage, t as steering, tt as naivePassPrompt, v as REQUIREMENTS, w as collecting, wt as seamSteerMessage, x as answerReviewQuestions, z as clearCriteriaQuestion } from "../chunks/reserved-CUkk9XgL.mjs";
|
|
4
|
+
import { a as refusePendingHandOff, c as joinReports, o as tail } from "../chunks/tail-CX6Pdsql.mjs";
|
|
5
|
+
import { i as reUnwind, n as humanReview, r as isCode$1, t as buildTail } from "../chunks/review-BaCXlIT2.mjs";
|
|
6
6
|
import { posix } from "node:path";
|
|
7
7
|
//#region src/workflows/shared/fences.ts
|
|
8
8
|
const markFenced = (text) => {
|
|
@@ -561,7 +561,9 @@ const reviewedRefactor = async (shape, options = {}) => {
|
|
|
561
561
|
feedback,
|
|
562
562
|
fixAccess: strayAccess(p.strays),
|
|
563
563
|
lensBase: entry,
|
|
564
|
-
rules: refactorRules(p.strays, p.pattern)
|
|
564
|
+
rules: refactorRules(p.strays, p.pattern),
|
|
565
|
+
walkthrough: true,
|
|
566
|
+
lap: "refactor"
|
|
565
567
|
});
|
|
566
568
|
if (outcome.verdict === "signoff") return true;
|
|
567
569
|
await reUnwind(outcome);
|
|
@@ -576,7 +578,7 @@ const reviewedRefactor = async (shape, options = {}) => {
|
|
|
576
578
|
const COVERED_MIN_P = .7;
|
|
577
579
|
/** Covering turns before the process rests at `uncovered`; a human edit there refills them. */
|
|
578
580
|
const COVERING_RETRIES = 2;
|
|
579
|
-
const frozen = () => requireCriteriaFrozen(REQUIREMENTS);
|
|
581
|
+
const frozen$1 = () => requireCriteriaFrozen(REQUIREMENTS);
|
|
580
582
|
const green = (guard) => healthy(() => agentWithSkills("fix.suite.fixing", implementFixPrompt(), {
|
|
581
583
|
label: "Fixing the check",
|
|
582
584
|
file: FEEDBACK,
|
|
@@ -584,7 +586,7 @@ const green = (guard) => healthy(() => agentWithSkills("fix.suite.fixing", imple
|
|
|
584
586
|
system: builderSystem()
|
|
585
587
|
}), {
|
|
586
588
|
guard: () => {
|
|
587
|
-
frozen();
|
|
589
|
+
frozen$1();
|
|
588
590
|
guard?.();
|
|
589
591
|
},
|
|
590
592
|
guardRests: false
|
|
@@ -637,7 +639,7 @@ const requireCoverage = async (base = start(), slice) => {
|
|
|
637
639
|
system: builderSystem(),
|
|
638
640
|
allowEmpty: true
|
|
639
641
|
});
|
|
640
|
-
frozen();
|
|
642
|
+
frozen$1();
|
|
641
643
|
} else {
|
|
642
644
|
await human("uncovered", {
|
|
643
645
|
message: uncoveredMessage(uncovered),
|
|
@@ -646,13 +648,136 @@ const requireCoverage = async (base = start(), slice) => {
|
|
|
646
648
|
acceptClean: true
|
|
647
649
|
});
|
|
648
650
|
if (!changes().some((c) => isCode$1(c.path))) return record(covered, uncovered);
|
|
649
|
-
frozen();
|
|
651
|
+
frozen$1();
|
|
650
652
|
retries = 0;
|
|
651
653
|
}
|
|
652
654
|
await green();
|
|
653
655
|
}
|
|
654
656
|
};
|
|
655
657
|
//#endregion
|
|
658
|
+
//#region src/workflows/feature/prototype.ts
|
|
659
|
+
/** The `prototypeRounds` setting. Anything but a non-negative integer fails rather than guess a cap. */
|
|
660
|
+
const prototypeRounds = () => {
|
|
661
|
+
const value = vars.prototypeRounds ?? defaults.prototypeRounds;
|
|
662
|
+
if (!/^(0|[1-9]\d*)$/.test(value)) refuse(`gtd: prototypeRounds must be a non-negative integer, got ${JSON.stringify(value)}`);
|
|
663
|
+
return Number(value);
|
|
664
|
+
};
|
|
665
|
+
const CHUNK = /^(AC-\d+|Problem\b.*?)\s*:\s*(pass|fail|blocked)$/i;
|
|
666
|
+
const UNGUIDED = /^no testing instructions$/i;
|
|
667
|
+
/** The `## ` chunks of a prototype report; a heading in neither shape is prose, not an item. */
|
|
668
|
+
const reportOf = (text) => {
|
|
669
|
+
let unguided = false;
|
|
670
|
+
const items = [];
|
|
671
|
+
const malformed = [];
|
|
672
|
+
for (const { line, fenced } of markFenced(text)) {
|
|
673
|
+
const heading = fenced ? null : HEADING.exec(line);
|
|
674
|
+
if (heading?.[1] !== "##") continue;
|
|
675
|
+
if (UNGUIDED.test(heading[2])) unguided = true;
|
|
676
|
+
const chunk = CHUNK.exec(heading[2]);
|
|
677
|
+
if (chunk === null && /^(ac-\d|problem)/i.test(heading[2])) malformed.push(heading[2]);
|
|
678
|
+
if (chunk !== null) items.push({
|
|
679
|
+
subject: /^ac-/i.test(chunk[1]) ? chunk[1].toUpperCase() : chunk[1],
|
|
680
|
+
verdict: chunk[2].toLowerCase()
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
return {
|
|
684
|
+
unguided,
|
|
685
|
+
items,
|
|
686
|
+
malformed
|
|
687
|
+
};
|
|
688
|
+
};
|
|
689
|
+
/**
|
|
690
|
+
* The report the testing turn left, refused unless it is the only change and
|
|
691
|
+
* names every criterion once, with its open items: another slice's criteria
|
|
692
|
+
* are not this slice's to fix.
|
|
693
|
+
*/
|
|
694
|
+
const requireReport = (slice, retest) => {
|
|
695
|
+
const others = changes().map((c) => c.path).filter((path) => path !== PROTOTYPE);
|
|
696
|
+
if (others.length > 0) refuse(`gtd land: prototype-report: test in a disposable place and change nothing but ${PROTOTYPE} — revert these:\n${others.map((path) => ` - ${path}`).join("\n")}`);
|
|
697
|
+
const text = read(PROTOTYPE);
|
|
698
|
+
if (text === void 0) return refuse(`gtd land: prototype-report: write ${PROTOTYPE}`);
|
|
699
|
+
const report = reportOf(text);
|
|
700
|
+
if (report.malformed.length > 0) refuse(`gtd land: prototype-report: head each chunk \`## <ID>: pass|fail|blocked\` or \`## Problem <title>: pass|fail|blocked\` — these miss it:\n${report.malformed.map((h) => ` - ${h}`).join("\n")}`);
|
|
701
|
+
if (report.unguided && report.items.length === 0 && !retest) return {
|
|
702
|
+
unguided: true,
|
|
703
|
+
open: []
|
|
704
|
+
};
|
|
705
|
+
const requirements = read(".gtd/REQUIREMENTS.md") ?? "";
|
|
706
|
+
const ids = criterionIds(requirements, slice);
|
|
707
|
+
const elsewhere = criterionIds(requirements).filter((id) => !ids.includes(id));
|
|
708
|
+
const count = (id) => report.items.filter((i) => i.subject === id).length;
|
|
709
|
+
const wrong = ids.filter((id) => count(id) !== 1);
|
|
710
|
+
if (wrong.length > 0) refuse(`gtd land: prototype-report: ${PROTOTYPE} needs exactly one chunk headed \`## <ID>: pass|fail|blocked\` for ${wrong.join(", ")}`);
|
|
711
|
+
return {
|
|
712
|
+
unguided: false,
|
|
713
|
+
open: report.items.filter((i) => i.verdict !== "pass" && !elsewhere.includes(i.subject))
|
|
714
|
+
};
|
|
715
|
+
};
|
|
716
|
+
const frozen = () => requireCriteriaFrozen(REQUIREMENTS);
|
|
717
|
+
const testing = async (slice, retest) => {
|
|
718
|
+
await agentWithSkills("testing", prototypeTestingPrompt(sliceIds(slice), retest), {
|
|
719
|
+
label: retest ? "Re-testing the slice by hand" : "Testing the slice by hand",
|
|
720
|
+
file: PROTOTYPE,
|
|
721
|
+
model: coder(),
|
|
722
|
+
system: testerSystem(),
|
|
723
|
+
allowEmpty: true
|
|
724
|
+
});
|
|
725
|
+
return requireReport(slice, retest);
|
|
726
|
+
};
|
|
727
|
+
const fixing = async (slice, guard) => {
|
|
728
|
+
await scope("fix", () => agentWithSkills("fixing", prototypeFixingPrompt(sliceIds(slice)), {
|
|
729
|
+
label: "Fixing what the hands-on test found",
|
|
730
|
+
file: PROTOTYPE,
|
|
731
|
+
model: coder(),
|
|
732
|
+
system: builderSystem(),
|
|
733
|
+
allowEmpty: true
|
|
734
|
+
}));
|
|
735
|
+
if (changes().some((c) => c.path === ".gtd/PROTOTYPE.md")) refuse(`gtd land: prototype-fix: leave ${PROTOTYPE} as it is — the next round re-tests from it`);
|
|
736
|
+
frozen();
|
|
737
|
+
guard?.();
|
|
738
|
+
};
|
|
739
|
+
/** A person's rest; resolves `true` when they changed something to test again, `false` on a clean overrule. */
|
|
740
|
+
const askPerson = async (name, message, label) => {
|
|
741
|
+
await human(name, {
|
|
742
|
+
message,
|
|
743
|
+
label,
|
|
744
|
+
file: PROTOTYPE,
|
|
745
|
+
acceptClean: true
|
|
746
|
+
});
|
|
747
|
+
if (!changes().some((c) => c.path !== ".gtd/PROTOTYPE.md")) return false;
|
|
748
|
+
frozen();
|
|
749
|
+
return true;
|
|
750
|
+
};
|
|
751
|
+
/**
|
|
752
|
+
* Use the slice by hand: a testing turn reports on every criterion, a fix turn
|
|
753
|
+
* takes each failure, and the slice is tested again — until the report is
|
|
754
|
+
* clean, or `prototypeRounds` fixes are spent and a person decides. `guard`
|
|
755
|
+
* holds every fix turn, as it holds the implementation's.
|
|
756
|
+
*/
|
|
757
|
+
const prototypeTest = async (slice, guard) => {
|
|
758
|
+
const cap = prototypeRounds();
|
|
759
|
+
if (cap === 0) return;
|
|
760
|
+
await scope("prototype", async () => {
|
|
761
|
+
let fixes = 0;
|
|
762
|
+
let retest = false;
|
|
763
|
+
for (;;) {
|
|
764
|
+
const { unguided, open } = await testing(slice, retest);
|
|
765
|
+
retest = !unguided;
|
|
766
|
+
if (unguided) {
|
|
767
|
+
if (!await askPerson("unguided", prototypeUnguidedMessage(), "No way to test by hand")) return;
|
|
768
|
+
} else if (open.length === 0) return;
|
|
769
|
+
else if (fixes < cap && open.some((i) => i.verdict === "fail")) {
|
|
770
|
+
fixes++;
|
|
771
|
+
await fixing(slice, guard);
|
|
772
|
+
} else {
|
|
773
|
+
if (!await askPerson("open", prototypeOpenMessage(open, fixes), "Hands-on test still failing")) return;
|
|
774
|
+
fixes = 0;
|
|
775
|
+
}
|
|
776
|
+
await green(guard);
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
};
|
|
780
|
+
//#endregion
|
|
656
781
|
//#region src/workflows/feature/seam.ts
|
|
657
782
|
/** Naive-pass retries after a `reject` or a red suite; the next one rests at `seam.steer`. */
|
|
658
783
|
const NAIVE_RETRIES = 2;
|
|
@@ -859,8 +984,10 @@ const implementing = (options) => scope("implement", async () => {
|
|
|
859
984
|
...acceptance,
|
|
860
985
|
ids: settled(acceptance.ids, options.slice)
|
|
861
986
|
}, since);
|
|
862
|
-
|
|
987
|
+
const keepsTests = built && acceptance !== void 0 ? () => requireAcceptanceKept(acceptance) : void 0;
|
|
988
|
+
await green(keepsTests);
|
|
863
989
|
await requireCoverage(options.base, options.slice);
|
|
990
|
+
await prototypeTest(options.slice, keepsTests);
|
|
864
991
|
const ids = criterionIds(read(".gtd/REQUIREMENTS.md") ?? "");
|
|
865
992
|
await closing(moreToBuild(options.more));
|
|
866
993
|
return ids;
|
|
@@ -869,11 +996,16 @@ const moreToBuild = (more) => typeof more === "function" ? more() : more === tru
|
|
|
869
996
|
/** The last slice's closing, for a last slice a steer left with no criterion to build. */
|
|
870
997
|
const closeOut = () => scope("implement", () => closing(false));
|
|
871
998
|
/** A slice with `more` after it keeps the criteria and the slicing for the next one. */
|
|
872
|
-
const closing = (more) => run("closing", removeScript(more ? [
|
|
999
|
+
const closing = (more) => run("closing", removeScript(more ? [
|
|
1000
|
+
REFACTOR_NOTE,
|
|
1001
|
+
HANDOFF,
|
|
1002
|
+
PROTOTYPE
|
|
1003
|
+
] : [
|
|
873
1004
|
REQUIREMENTS,
|
|
874
1005
|
REFACTOR_NOTE,
|
|
875
1006
|
SLICES,
|
|
876
|
-
HANDOFF
|
|
1007
|
+
HANDOFF,
|
|
1008
|
+
PROTOTYPE
|
|
877
1009
|
]), { label: "Closing out the implementation" });
|
|
878
1010
|
/** Resolves `true` when the human steered (edited), `false` on a clean overrule. */
|
|
879
1011
|
const steer = async (retry) => {
|
|
@@ -1238,6 +1370,7 @@ const ordinaryStart = async () => {
|
|
|
1238
1370
|
refusePendingHandOff();
|
|
1239
1371
|
flagModeOn();
|
|
1240
1372
|
sliceCap();
|
|
1373
|
+
prototypeRounds();
|
|
1241
1374
|
const stated = statedCriteria(changes(), head());
|
|
1242
1375
|
await unwind();
|
|
1243
1376
|
if (changes(".gtd/FEEDBACK.md").some((c) => c.status !== "deleted")) await human("unwind-failed", {
|
package/dist/workflows/fix.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { read, refuse, removeScript, run, scope, start } from "../flows.mjs";
|
|
2
|
-
import { _ as reviewRisks, g as reviewNotes } from "../chunks/steering-
|
|
3
|
-
import {
|
|
4
|
-
import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-
|
|
2
|
+
import { _ as reviewRisks, g as reviewNotes } from "../chunks/steering-DtUVUz9l.mjs";
|
|
3
|
+
import { C as coder, It as defaults, J as finisherSystem, Lt as envDefaults, N as agentWithSkills, Y as fixReviewNotesPrompt, c as baseline, i as skills, n as summary, r as access, s as shipped, t as steering, y as REVIEW } from "../chunks/reserved-CUkk9XgL.mjs";
|
|
4
|
+
import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-CX6Pdsql.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 {
|
|
3
|
-
import { a as refusePendingHandOff } from "../chunks/tail-
|
|
4
|
-
import { i as reUnwind, t as buildTail } from "../chunks/review-
|
|
2
|
+
import { C as coder, It as defaults, J as finisherSystem, Lt as envDefaults, N as agentWithSkills, i as skills, l as gate, n as summary, r as access, s as shipped, t as steering, v as REQUIREMENTS, vt as reviewFeedbackFixPrompt, yt as reviewGateBlockedMessage } from "../chunks/reserved-CUkk9XgL.mjs";
|
|
3
|
+
import { a as refusePendingHandOff } from "../chunks/tail-CX6Pdsql.mjs";
|
|
4
|
+
import { i as reUnwind, t as buildTail } from "../chunks/review-BaCXlIT2.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 {
|
|
2
|
+
import { C as coder, It as defaults, Jt as flagMentions, Lt as envDefaults, Qt as requireFlag, Xt as flipProblems, f as FEEDBACK, i as skills, l as gate, n as summary, o as flagShipped, r as access, t as steering } from "../chunks/reserved-CUkk9XgL.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.";
|