@pmelab/gtd 2.6.0 → 2.8.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/README.md +81 -1096
- package/bin/gtd-loop +1 -1
- package/dist/gtd.bundle.mjs +177 -47
- package/docs/cli.md +356 -0
- package/docs/configuration.md +113 -0
- package/docs/development.md +82 -0
- package/docs/loop.md +104 -0
- package/docs/upgrading-from-v1.md +45 -0
- package/docs/workflow.md +428 -0
- package/package.json +2 -1
- package/schema.json +3 -0
package/bin/gtd-loop
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
|
-
# Drives the two-beat protocol (
|
|
4
|
+
# Drives the two-beat protocol (docs/loop.md "The reference loop driver") to a
|
|
5
5
|
# human gate. Both mutators are attempted every iteration, refusal-tolerant:
|
|
6
6
|
# whichever actor is actually awaited claims the turn (captures the human's
|
|
7
7
|
# uncommitted edit after a halt, or the agent's own edit from the previous
|
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -189138,6 +189138,7 @@ const DEFAULT_TEST_COMMAND = "npm run test";
|
|
|
189138
189138
|
const DEFAULT_AGENTIC_REVIEW = true;
|
|
189139
189139
|
const DEFAULT_SQUASH = true;
|
|
189140
189140
|
const DEFAULT_LEARNING = true;
|
|
189141
|
+
const DEFAULT_DECISION_LOG = true;
|
|
189141
189142
|
const DEFAULT_FIX_ATTEMPT_CAP = 3;
|
|
189142
189143
|
const DEFAULT_REVIEW_THRESHOLD = 3;
|
|
189143
189144
|
const ModelStatesSchema = Struct({
|
|
@@ -189160,6 +189161,7 @@ const ConfigSchema = Struct({
|
|
|
189160
189161
|
agenticReview: optional(Boolean$),
|
|
189161
189162
|
squash: optional(Boolean$),
|
|
189162
189163
|
learning: optional(Boolean$),
|
|
189164
|
+
decisionLog: optional(Boolean$),
|
|
189163
189165
|
fixAttemptCap: optional(Int.pipe(greaterThanOrEqualTo(0))),
|
|
189164
189166
|
reviewThreshold: optional(Int.pipe(greaterThanOrEqualTo(1)))
|
|
189165
189167
|
});
|
|
@@ -189300,6 +189302,7 @@ const toOperations = (decoded) => {
|
|
|
189300
189302
|
agenticReview: decoded.agenticReview ?? DEFAULT_AGENTIC_REVIEW,
|
|
189301
189303
|
squash: decoded.squash ?? DEFAULT_SQUASH,
|
|
189302
189304
|
learning: decoded.learning ?? DEFAULT_LEARNING,
|
|
189305
|
+
decisionLog: decoded.decisionLog ?? DEFAULT_DECISION_LOG,
|
|
189303
189306
|
fixAttemptCap: decoded.fixAttemptCap ?? DEFAULT_FIX_ATTEMPT_CAP,
|
|
189304
189307
|
reviewThreshold: decoded.reviewThreshold ?? DEFAULT_REVIEW_THRESHOLD
|
|
189305
189308
|
};
|
|
@@ -351781,6 +351784,7 @@ const parseReviewDoc = (content) => {
|
|
|
351781
351784
|
const GTD_DIR = ".gtd";
|
|
351782
351785
|
const TODO_FILE = `${GTD_DIR}/TODO.md`;
|
|
351783
351786
|
const ARCHITECTURE_FILE = `${GTD_DIR}/ARCHITECTURE.md`;
|
|
351787
|
+
const PLAN_FILE = `${GTD_DIR}/PLAN.md`;
|
|
351784
351788
|
const REVIEW_FILE = `${GTD_DIR}/REVIEW.md`;
|
|
351785
351789
|
const FEEDBACK_FILE = `${GTD_DIR}/FEEDBACK.md`;
|
|
351786
351790
|
const ERRORS_FILE = `${GTD_DIR}/ERRORS.md`;
|
|
@@ -351792,6 +351796,7 @@ const LEGACY_FEEDBACK_FILE = "FEEDBACK.md";
|
|
|
351792
351796
|
const STEERING_FILES = {
|
|
351793
351797
|
todo: TODO_FILE,
|
|
351794
351798
|
architecture: ARCHITECTURE_FILE,
|
|
351799
|
+
plan: PLAN_FILE,
|
|
351795
351800
|
review: REVIEW_FILE,
|
|
351796
351801
|
feedback: FEEDBACK_FILE,
|
|
351797
351802
|
errors: ERRORS_FILE,
|
|
@@ -351972,6 +351977,25 @@ const subjectOf$1 = (message) => (message.split("\n")[0] ?? "").trim();
|
|
|
351972
351977
|
* keeps pre-namespaced history classifying identically.
|
|
351973
351978
|
*/
|
|
351974
351979
|
const touchedFeedback = (touched) => touched.includes(FEEDBACK_FILE) || touched.includes(LEGACY_FEEDBACK_FILE);
|
|
351980
|
+
const DECISIONS_TRAILER_RE = /^Gtd-Decisions:\s*true\s*$/m;
|
|
351981
|
+
const DECISIONS_HEADING = "## Decisions";
|
|
351982
|
+
/**
|
|
351983
|
+
* Extracts one commit message's `## Decisions` section verbatim (heading
|
|
351984
|
+
* through the next `#`/`##` heading or end of message, trailer line
|
|
351985
|
+
* excluded), or `undefined` when the trailer is absent or the heading can't
|
|
351986
|
+
* be found despite it (a malformed historical commit — skipped, not an
|
|
351987
|
+
* error, since this reads commits the current turn doesn't own and can't be
|
|
351988
|
+
* asked to fix). Total, never throws.
|
|
351989
|
+
*/
|
|
351990
|
+
const extractDecisionsSection = (message) => {
|
|
351991
|
+
if (!DECISIONS_TRAILER_RE.test(message)) return void 0;
|
|
351992
|
+
const lines = message.split(/\r?\n/);
|
|
351993
|
+
const headingIdx = lines.findIndex((line) => line.trim() === DECISIONS_HEADING);
|
|
351994
|
+
if (headingIdx === -1) return void 0;
|
|
351995
|
+
const endIdx = lines.findIndex((line, i) => i > headingIdx && /^#{1,2}\s+\S/.test(line.trim()));
|
|
351996
|
+
const section = lines.slice(headingIdx, endIdx === -1 ? lines.length : endIdx).filter((line) => !DECISIONS_TRAILER_RE.test(line)).join("\n").trim();
|
|
351997
|
+
return section.length > 0 ? section : void 0;
|
|
351998
|
+
};
|
|
351975
351999
|
/**
|
|
351976
352000
|
* Gather ALL git/filesystem facts and produce the typed event stream the pure
|
|
351977
352001
|
* machine folds: one `COMMIT` per first-parent commit (oldest→newest) followed
|
|
@@ -351992,18 +352016,21 @@ const gatherEvents = (invoker) => gen(function* () {
|
|
|
351992
352016
|
const parsed = parseSubject(subjectOf$1(commit.message));
|
|
351993
352017
|
const isTurn = parsed.kind === "turn";
|
|
351994
352018
|
const isRouting = parsed.kind === "routing";
|
|
352019
|
+
const routingPhase = parsed.kind === "routing" ? parsed.phase : "";
|
|
352020
|
+
const turnGate = parsed.kind === "turn" ? parsed.gate : "";
|
|
351995
352021
|
return {
|
|
351996
352022
|
type: "COMMIT",
|
|
351997
|
-
...
|
|
352023
|
+
...parsed.kind === "turn" ? {
|
|
351998
352024
|
turnActor: parsed.actor,
|
|
351999
352025
|
turnGate: parsed.gate
|
|
352000
352026
|
} : {},
|
|
352001
|
-
isErrors:
|
|
352002
|
-
isFeedback:
|
|
352003
|
-
isPackageStart:
|
|
352027
|
+
isErrors: routingPhase === "errors",
|
|
352028
|
+
isFeedback: turnGate === "agentic-review" && touchedFeedback(commit.touched),
|
|
352029
|
+
isPackageStart: routingPhase === "planning" || routingPhase === "package-done",
|
|
352004
352030
|
isWorkflowCommit: isTurn || isRouting,
|
|
352005
352031
|
removedErrors: commit.removedErrors,
|
|
352006
|
-
isHealthCheck:
|
|
352032
|
+
isHealthCheck: routingPhase === "health-check",
|
|
352033
|
+
isTestsGreen: routingPhase === "tests-green"
|
|
352007
352034
|
};
|
|
352008
352035
|
});
|
|
352009
352036
|
const hasCommits = yield* git.hasCommits();
|
|
@@ -352036,6 +352063,7 @@ const gatherEvents = (invoker) => gen(function* () {
|
|
|
352036
352063
|
const codeDirty = entries.some((e) => !isGtdPath(e.path));
|
|
352037
352064
|
const todoExists = yield* fs.exists(resolve(TODO_FILE));
|
|
352038
352065
|
const architectureExists = yield* fs.exists(resolve(ARCHITECTURE_FILE));
|
|
352066
|
+
const planExists = yield* fs.exists(resolve(PLAN_FILE));
|
|
352039
352067
|
const reviewPresent = yield* fs.exists(resolve(REVIEW_FILE));
|
|
352040
352068
|
const feedbackPresent = yield* fs.exists(resolve(FEEDBACK_FILE));
|
|
352041
352069
|
const errorsPresent = yield* fs.exists(resolve(ERRORS_FILE));
|
|
@@ -352051,6 +352079,7 @@ const gatherEvents = (invoker) => gen(function* () {
|
|
|
352051
352079
|
const reviewDirty = reviewTrackedAtHead && !workingTreeClean;
|
|
352052
352080
|
const todoCommitted = todoExists && !isUncommitted(TODO_FILE);
|
|
352053
352081
|
const architectureCommitted = architectureExists && !isUncommitted(ARCHITECTURE_FILE);
|
|
352082
|
+
const planCommitted = planExists && !isUncommitted(PLAN_FILE);
|
|
352054
352083
|
const grillingDocPath = todoExists ? TODO_FILE : architectureExists ? ARCHITECTURE_FILE : void 0;
|
|
352055
352084
|
const grillingDocErrors = grillingDocPath ? parseOpenQuestions(yield* fs.readFileString(resolve(grillingDocPath))).errors : [];
|
|
352056
352085
|
const reviewDocErrors = reviewPresent ? parseReviewDoc(yield* fs.readFileString(resolve(REVIEW_FILE))).errors : [];
|
|
@@ -352062,8 +352091,10 @@ const gatherEvents = (invoker) => gen(function* () {
|
|
|
352062
352091
|
let refDiff;
|
|
352063
352092
|
let reviewAnchor;
|
|
352064
352093
|
let hasCommitsAfterLastDone = true;
|
|
352094
|
+
let decisionLog = "";
|
|
352065
352095
|
if (hasCommits) {
|
|
352066
352096
|
const allHistory = isNone(base) ? history : yield* git.commitHistory();
|
|
352097
|
+
if (config.decisionLog) decisionLog = allHistory.map((c) => extractDecisionsSection(c.message)).filter((section) => section !== void 0).join("\n\n");
|
|
352067
352098
|
const lastDoneIdx = (() => {
|
|
352068
352099
|
let idx = -1;
|
|
352069
352100
|
for (let i = 0; i < allHistory.length; i++) if (subjectOf$1(allHistory[i].message) === DONE_SUBJECT) idx = i;
|
|
@@ -352077,7 +352108,7 @@ const gatherEvents = (invoker) => gen(function* () {
|
|
|
352077
352108
|
}
|
|
352078
352109
|
const isGrillingTurn = (message) => {
|
|
352079
352110
|
const parsed = parseSubject(subjectOf$1(message));
|
|
352080
|
-
return parsed.kind === "turn" && (parsed.gate === "grilling" || parsed.gate === "architecting");
|
|
352111
|
+
return parsed.kind === "turn" && (parsed.gate === "grilling" || parsed.gate === "architecting" || parsed.gate === "grilled");
|
|
352081
352112
|
};
|
|
352082
352113
|
const firstGrilling = currentCycle.find((c) => isGrillingTurn(c.message));
|
|
352083
352114
|
const lastAwaitingReview = (() => {
|
|
@@ -352117,7 +352148,7 @@ const gatherEvents = (invoker) => gen(function* () {
|
|
|
352117
352148
|
const squashCycle = squashHistory.slice(prevDoneIdx + 1, lastDoneIdxForSquash + 1);
|
|
352118
352149
|
const isGrillingTurnSubject = (subject) => {
|
|
352119
352150
|
const parsed = parseSubject(subject);
|
|
352120
|
-
return parsed.kind === "turn" && (parsed.gate === "grilling" || parsed.gate === "architecting");
|
|
352151
|
+
return parsed.kind === "turn" && (parsed.gate === "grilling" || parsed.gate === "architecting" || parsed.gate === "grilled");
|
|
352121
352152
|
};
|
|
352122
352153
|
const isReviewingAnchor = (subject) => {
|
|
352123
352154
|
const parsed = parseSubject(subject);
|
|
@@ -352152,28 +352183,23 @@ const gatherEvents = (invoker) => gen(function* () {
|
|
|
352152
352183
|
const healthCommitted = healthPresent && !isUncommitted(HEALTH_FILE);
|
|
352153
352184
|
let healthFixBase;
|
|
352154
352185
|
if (config.squash || config.learning) {
|
|
352155
|
-
|
|
352156
|
-
let
|
|
352157
|
-
for (
|
|
352158
|
-
|
|
352159
|
-
|
|
352160
|
-
|
|
352161
|
-
|
|
352162
|
-
|
|
352163
|
-
|
|
352164
|
-
|
|
352165
|
-
|
|
352166
|
-
for (let i = 0; i < history.length; i++) {
|
|
352167
|
-
const c = commitEvents[i];
|
|
352168
|
-
if (c.isPackageStart || c.removedErrors) resetAt = i;
|
|
352169
|
-
}
|
|
352170
|
-
for (let i = resetAt + 1; i < history.length; i++) if (commitEvents[i].isHealthCheck) {
|
|
352171
|
-
firstHealthCheckHash = history[i].hash;
|
|
352172
|
-
break;
|
|
352186
|
+
const inHealthProcessingChain = headParsedForSquash.kind === "routing" && (headParsedForSquash.phase === "tests-green" || headParsedForSquash.phase === "learning-template" || headParsedForSquash.phase === "learning-drafted" || headParsedForSquash.phase === "learning-approved" || headParsedForSquash.phase === "squash-template" || headParsedForSquash.phase === "learning-applied" && config.squash) || headParsedForSquash.kind === "turn" && SQUASH_OR_LEARNING_TURN_GATES.has(headParsedForSquash.gate);
|
|
352187
|
+
let lastTestsGreenIdx = -1;
|
|
352188
|
+
for (let i = 0; i < commitEvents.length; i++) if (commitEvents[i].isTestsGreen) lastTestsGreenIdx = i;
|
|
352189
|
+
const ignoredTestsGreenIdx = inHealthProcessingChain ? lastTestsGreenIdx : -1;
|
|
352190
|
+
const isHealthEntryTurn = (c) => c.turnActor === "human" && c.turnGate === "health-fixing";
|
|
352191
|
+
let anchorIdx = -1;
|
|
352192
|
+
for (let i = 0; i < commitEvents.length; i++) {
|
|
352193
|
+
const c = commitEvents[i];
|
|
352194
|
+
if (c.isPackageStart || c.removedErrors || c.isTestsGreen && i !== ignoredTestsGreenIdx) {
|
|
352195
|
+
anchorIdx = -1;
|
|
352196
|
+
continue;
|
|
352173
352197
|
}
|
|
352198
|
+
if (anchorIdx === -1 && (c.isHealthCheck || isHealthEntryTurn(c))) anchorIdx = i;
|
|
352174
352199
|
}
|
|
352175
|
-
if (
|
|
352176
|
-
const
|
|
352200
|
+
if (anchorIdx !== -1) {
|
|
352201
|
+
const anchorHash = history[anchorIdx].hash;
|
|
352202
|
+
const healthBase = yield* git.resolveRef(`${anchorHash}~1`).pipe(catchAll(() => succeed$3(EMPTY_TREE)));
|
|
352177
352203
|
healthFixBase = healthBase;
|
|
352178
352204
|
const healthCandidateDiff = yield* git.diffRef(healthBase).pipe(catchAll(() => succeed$3("")));
|
|
352179
352205
|
if (healthCandidateDiff.trim().length > 0) {
|
|
@@ -352193,6 +352219,8 @@ const gatherEvents = (invoker) => gen(function* () {
|
|
|
352193
352219
|
todoCommitted,
|
|
352194
352220
|
architectureExists,
|
|
352195
352221
|
architectureCommitted,
|
|
352222
|
+
planExists,
|
|
352223
|
+
planCommitted,
|
|
352196
352224
|
packagesPresent: packages.length > 0,
|
|
352197
352225
|
reviewPresent,
|
|
352198
352226
|
feedbackPresent,
|
|
@@ -352230,7 +352258,8 @@ const gatherEvents = (invoker) => gen(function* () {
|
|
|
352230
352258
|
...healthFixBase !== void 0 ? { healthFixBase } : {},
|
|
352231
352259
|
learningEnabled: config.learning,
|
|
352232
352260
|
learningMsgPresent,
|
|
352233
|
-
learningMsgIsTemplate
|
|
352261
|
+
learningMsgIsTemplate,
|
|
352262
|
+
decisionLog
|
|
352234
352263
|
}
|
|
352235
352264
|
};
|
|
352236
352265
|
return [...commitEvents, resolveEvent];
|
|
@@ -352324,6 +352353,13 @@ const SQUASH_TEMPLATE = [
|
|
|
352324
352353
|
*/
|
|
352325
352354
|
const ARCHITECTURE_SEED_BANNER = "<!-- gtd: seeded from the converged product plan (.gtd/TODO.md); decide the technical/architectural questions below. -->\n\n";
|
|
352326
352355
|
/**
|
|
352356
|
+
* Banner prepended to `.gtd/ARCHITECTURE.md` when it is seeded from a
|
|
352357
|
+
* hand-written `.gtd/PLAN.md` (the direct-to-decompose entry) — same
|
|
352358
|
+
* orientation role as `ARCHITECTURE_SEED_BANNER`, one phase later: the plan
|
|
352359
|
+
* is final, the next turn decomposes it.
|
|
352360
|
+
*/
|
|
352361
|
+
const ARCHITECTURE_PLAN_SEED_BANNER = "<!-- gtd: seeded from the final plan (.gtd/PLAN.md); decompose into ordered work packages. -->\n\n";
|
|
352362
|
+
/**
|
|
352327
352363
|
* A short skeleton written by `writeLearningTemplate`, instructing the
|
|
352328
352364
|
* learning agent to replace it with the real distilled learnings.
|
|
352329
352365
|
*/
|
|
@@ -352354,8 +352390,11 @@ const perform = (action) => gen(function* () {
|
|
|
352354
352390
|
const ensureGtdDir = fs.makeDirectory(resolve(GTD_DIR), { recursive: true }).pipe(catchAll(() => _void));
|
|
352355
352391
|
switch (action.kind) {
|
|
352356
352392
|
case "captureTurn":
|
|
352357
|
-
|
|
352358
|
-
|
|
352393
|
+
for (const file of [
|
|
352394
|
+
TODO_FILE,
|
|
352395
|
+
ARCHITECTURE_FILE,
|
|
352396
|
+
PLAN_FILE
|
|
352397
|
+
]) if (yield* fs.exists(resolve(file))) yield* formatFile(resolve(file)).pipe(catchAll(() => _void));
|
|
352359
352398
|
yield* git.commitAllWithPrefix(turnSubject(action.actor, action.gate));
|
|
352360
352399
|
return { stop: false };
|
|
352361
352400
|
case "commitRouting":
|
|
@@ -352365,6 +352404,12 @@ const perform = (action) => gen(function* () {
|
|
|
352365
352404
|
yield* fs.writeFileString(resolve(ARCHITECTURE_FILE), ARCHITECTURE_SEED_BANNER + todoContent);
|
|
352366
352405
|
yield* fs.remove(resolve(TODO_FILE)).pipe(catchAll(() => _void));
|
|
352367
352406
|
}
|
|
352407
|
+
if (action.seedArchitectureFromPlan === true) {
|
|
352408
|
+
const planContent = yield* fs.readFileString(resolve(PLAN_FILE)).pipe(catchAll(() => succeed$3("")));
|
|
352409
|
+
yield* ensureGtdDir;
|
|
352410
|
+
yield* fs.writeFileString(resolve(ARCHITECTURE_FILE), ARCHITECTURE_PLAN_SEED_BANNER + planContent);
|
|
352411
|
+
yield* fs.remove(resolve(PLAN_FILE)).pipe(catchAll(() => _void));
|
|
352412
|
+
}
|
|
352368
352413
|
if (action.removeArchitecture === true) yield* fs.remove(resolve(ARCHITECTURE_FILE)).pipe(catchAll(() => _void));
|
|
352369
352414
|
if (action.removeReview === true) yield* fs.remove(resolve(REVIEW_FILE)).pipe(catchAll(() => _void));
|
|
352370
352415
|
if (action.removeFeedback === true) yield* fs.remove(resolve(FEEDBACK_FILE)).pipe(catchAll(() => _void));
|
|
@@ -352456,7 +352501,7 @@ const counterRules = {
|
|
|
352456
352501
|
incrementsOn: (e) => e.isFeedback
|
|
352457
352502
|
},
|
|
352458
352503
|
healthFixCount: {
|
|
352459
|
-
resetsOn: (e) => e.isPackageStart || e.removedErrors,
|
|
352504
|
+
resetsOn: (e) => e.isPackageStart || e.removedErrors || e.isTestsGreen,
|
|
352460
352505
|
incrementsOn: (e) => e.isHealthCheck
|
|
352461
352506
|
}
|
|
352462
352507
|
};
|
|
@@ -352508,6 +352553,8 @@ const DEFAULT_PAYLOAD = {
|
|
|
352508
352553
|
todoCommitted: false,
|
|
352509
352554
|
architectureExists: false,
|
|
352510
352555
|
architectureCommitted: false,
|
|
352556
|
+
planExists: false,
|
|
352557
|
+
planCommitted: false,
|
|
352511
352558
|
packagesPresent: false,
|
|
352512
352559
|
reviewPresent: false,
|
|
352513
352560
|
feedbackPresent: false,
|
|
@@ -352537,7 +352584,8 @@ const DEFAULT_PAYLOAD = {
|
|
|
352537
352584
|
healthCommitted: false,
|
|
352538
352585
|
learningEnabled: false,
|
|
352539
352586
|
learningMsgPresent: false,
|
|
352540
|
-
learningMsgIsTemplate: false
|
|
352587
|
+
learningMsgIsTemplate: false,
|
|
352588
|
+
decisionLog: ""
|
|
352541
352589
|
};
|
|
352542
352590
|
/** Build the prompt context from the payload passthrough + the folded counters. */
|
|
352543
352591
|
const buildContext = (p, counters) => ({
|
|
@@ -352549,7 +352597,8 @@ const buildContext = (p, counters) => ({
|
|
|
352549
352597
|
...p.squashBase !== void 0 ? { squashBase: p.squashBase } : {},
|
|
352550
352598
|
...p.squashDiff !== void 0 ? { squashDiff: p.squashDiff } : {},
|
|
352551
352599
|
feedbackContent: p.feedbackContent !== "" ? p.feedbackContent : p.healthContent,
|
|
352552
|
-
...p.headTurnDiff !== "" ? { turnDiff: p.headTurnDiff } : {}
|
|
352600
|
+
...p.headTurnDiff !== "" ? { turnDiff: p.headTurnDiff } : {},
|
|
352601
|
+
decisionLog: p.decisionLog
|
|
352553
352602
|
});
|
|
352554
352603
|
const healthFileConflictRules = [
|
|
352555
352604
|
{
|
|
@@ -352567,6 +352616,52 @@ const healthFileConflictRules = [
|
|
|
352567
352616
|
{
|
|
352568
352617
|
isViolated: (p) => p.healthPresent && p.errorsPresent,
|
|
352569
352618
|
message: ".gtd/HEALTH.md + .gtd/ERRORS.md"
|
|
352619
|
+
},
|
|
352620
|
+
{
|
|
352621
|
+
isViolated: (p) => p.healthPresent && p.todoExists,
|
|
352622
|
+
message: ".gtd/HEALTH.md + .gtd/TODO.md"
|
|
352623
|
+
},
|
|
352624
|
+
{
|
|
352625
|
+
isViolated: (p) => p.healthPresent && p.architectureExists,
|
|
352626
|
+
message: ".gtd/HEALTH.md + .gtd/ARCHITECTURE.md"
|
|
352627
|
+
},
|
|
352628
|
+
{
|
|
352629
|
+
isViolated: (p) => p.healthPresent && p.planExists,
|
|
352630
|
+
message: ".gtd/HEALTH.md + .gtd/PLAN.md"
|
|
352631
|
+
}
|
|
352632
|
+
];
|
|
352633
|
+
const planFileConflictRules = [
|
|
352634
|
+
{
|
|
352635
|
+
isViolated: (p) => p.planExists && p.todoExists,
|
|
352636
|
+
message: ".gtd/PLAN.md + .gtd/TODO.md"
|
|
352637
|
+
},
|
|
352638
|
+
{
|
|
352639
|
+
isViolated: (p) => p.planExists && p.architectureExists,
|
|
352640
|
+
message: ".gtd/PLAN.md + .gtd/ARCHITECTURE.md"
|
|
352641
|
+
},
|
|
352642
|
+
{
|
|
352643
|
+
isViolated: (p) => p.planExists && p.packagesPresent,
|
|
352644
|
+
message: ".gtd/PLAN.md + packages"
|
|
352645
|
+
},
|
|
352646
|
+
{
|
|
352647
|
+
isViolated: (p) => p.planExists && p.reviewPresent,
|
|
352648
|
+
message: ".gtd/PLAN.md + .gtd/REVIEW.md"
|
|
352649
|
+
},
|
|
352650
|
+
{
|
|
352651
|
+
isViolated: (p) => p.planExists && p.feedbackPresent,
|
|
352652
|
+
message: ".gtd/PLAN.md + .gtd/FEEDBACK.md"
|
|
352653
|
+
},
|
|
352654
|
+
{
|
|
352655
|
+
isViolated: (p) => p.planExists && p.errorsPresent,
|
|
352656
|
+
message: ".gtd/PLAN.md + .gtd/ERRORS.md"
|
|
352657
|
+
},
|
|
352658
|
+
{
|
|
352659
|
+
isViolated: (p) => p.planExists && p.squashMsgPresent,
|
|
352660
|
+
message: ".gtd/PLAN.md + .gtd/SQUASH_MSG.md"
|
|
352661
|
+
},
|
|
352662
|
+
{
|
|
352663
|
+
isViolated: (p) => p.planExists && p.learningMsgPresent,
|
|
352664
|
+
message: ".gtd/PLAN.md + .gtd/LEARNINGS.md"
|
|
352570
352665
|
}
|
|
352571
352666
|
];
|
|
352572
352667
|
const learningFileConflictRules = [
|
|
@@ -352662,6 +352757,7 @@ const assertLegal = (p) => {
|
|
|
352662
352757
|
for (const rule of [
|
|
352663
352758
|
...healthFileConflictRules,
|
|
352664
352759
|
...learningFileConflictRules,
|
|
352760
|
+
...planFileConflictRules,
|
|
352665
352761
|
...reviewAndFeedbackRules
|
|
352666
352762
|
]) if (rule.isViolated(p)) throw new GtdStateError("illegal-combination", `illegal combination: ${rule.message}`);
|
|
352667
352763
|
};
|
|
@@ -352791,6 +352887,16 @@ const classifyHead = (subject, flags) => {
|
|
|
352791
352887
|
actor: "agent"
|
|
352792
352888
|
};
|
|
352793
352889
|
}
|
|
352890
|
+
if (actor === "human" && gate === "grilled") return flags.planExists ? {
|
|
352891
|
+
kind: "mid-chain",
|
|
352892
|
+
state: "grilled",
|
|
352893
|
+
actor: "agent",
|
|
352894
|
+
action: {
|
|
352895
|
+
kind: "commitRouting",
|
|
352896
|
+
subject: "gtd: grilled",
|
|
352897
|
+
seedArchitectureFromPlan: true
|
|
352898
|
+
}
|
|
352899
|
+
} : null;
|
|
352794
352900
|
if (actor === "agent" && gate === "grilled") return flags.hasPackages ? {
|
|
352795
352901
|
kind: "mid-chain",
|
|
352796
352902
|
state: "grilled",
|
|
@@ -353037,7 +353143,11 @@ const gateForState = (state) => STATE_IS_OWN_GATE.has(state) ? state : GATE_OVER
|
|
|
353037
353143
|
* (CLAUDE.md/AGENTS.md/docs), so it's unconditionally inert on a clean tree,
|
|
353038
353144
|
* same as `building`/`fixing`. `health-fixing` is deliberately absent: its
|
|
353039
353145
|
* empty turn is meaningful (environmental fix; the machine removes
|
|
353040
|
-
* HEALTH.md and re-tests)
|
|
353146
|
+
* HEALTH.md and re-tests) — EXCEPT while HEAD is the human's hand-written
|
|
353147
|
+
* HEALTH.md entry turn (`gtd(human): health-fixing`), where the loop
|
|
353148
|
+
* protocol's opening clean-tree `gtd step-agent` would otherwise consume the
|
|
353149
|
+
* human's error description before any agent read it (see
|
|
353150
|
+
* `isInertEmptyAgentRest`).
|
|
353041
353151
|
*/
|
|
353042
353152
|
const INERT_EMPTY_AGENT_GATES = /* @__PURE__ */ new Set([
|
|
353043
353153
|
"grilling",
|
|
@@ -353050,11 +353160,20 @@ const INERT_EMPTY_AGENT_GATES = /* @__PURE__ */ new Set([
|
|
|
353050
353160
|
"learning-apply"
|
|
353051
353161
|
]);
|
|
353052
353162
|
/**
|
|
353163
|
+
* HEAD is the human's HEALTH.md entry turn (`gtd(human): health-fixing`) —
|
|
353164
|
+
* the hand-written error description was just captured and no agent has
|
|
353165
|
+
* acted on it yet.
|
|
353166
|
+
*/
|
|
353167
|
+
const isHumanHealthEntryHead = (head) => {
|
|
353168
|
+
const parsed = parseSubject(head);
|
|
353169
|
+
return parsed.kind === "turn" && parsed.actor === "human" && parsed.gate === "health-fixing";
|
|
353170
|
+
};
|
|
353171
|
+
/**
|
|
353053
353172
|
* True when a `step-agent` invocation at this rest has nothing to capture —
|
|
353054
353173
|
* see `INERT_EMPTY_AGENT_GATES`. Shared by `applyTurnTaking` (author nothing)
|
|
353055
353174
|
* and `predictTurn` (predict null) so the two can never disagree.
|
|
353056
353175
|
*/
|
|
353057
|
-
const isInertEmptyAgentRest = (state, p) => p.workingTreeClean && (INERT_EMPTY_AGENT_GATES.has(state) || state === "squashing" && p.squashMsgIsTemplate || state === "learning" && p.learningMsgIsTemplate);
|
|
353176
|
+
const isInertEmptyAgentRest = (state, p) => p.workingTreeClean && (INERT_EMPTY_AGENT_GATES.has(state) || state === "squashing" && p.squashMsgIsTemplate || state === "learning" && p.learningMsgIsTemplate || state === "health-fixing" && isHumanHealthEntryHead(p.lastCommitSubject));
|
|
353058
353177
|
/**
|
|
353059
353178
|
* The baseline decision: classify HEAD, falling through to the payload-driven
|
|
353060
353179
|
* ladder for the rows `classifyHead` cannot resolve alone (boundary subjects,
|
|
@@ -353105,6 +353224,7 @@ const resolveBaseline = (p, counters, head, corrupt, lastTurn) => {
|
|
|
353105
353224
|
const flags = {
|
|
353106
353225
|
headTurnIsEmpty: p.headTurnIsEmpty,
|
|
353107
353226
|
hasPackages: p.packagesPresent,
|
|
353227
|
+
planExists: p.planExists,
|
|
353108
353228
|
agenticReviewForceApproved: forceApprove,
|
|
353109
353229
|
squashEnabled: p.squashEnabled,
|
|
353110
353230
|
hasSquashBase: p.squashBase !== void 0,
|
|
@@ -353171,6 +353291,11 @@ const resolveBaseline = (p, counters, head, corrupt, lastTurn) => {
|
|
|
353171
353291
|
state: "architecting",
|
|
353172
353292
|
actor: "agent"
|
|
353173
353293
|
};
|
|
353294
|
+
if (p.planExists) return {
|
|
353295
|
+
kind: "rest",
|
|
353296
|
+
state: "grilled",
|
|
353297
|
+
actor: "human"
|
|
353298
|
+
};
|
|
353174
353299
|
if (p.packagesPresent && p.packages.length > 0 && lastTurn?.actor === "agent" && lastTurn.gate === "building") return {
|
|
353175
353300
|
kind: "mid-chain",
|
|
353176
353301
|
state: "building",
|
|
@@ -353181,7 +353306,7 @@ const resolveBaseline = (p, counters, head, corrupt, lastTurn) => {
|
|
|
353181
353306
|
capReached: false
|
|
353182
353307
|
}
|
|
353183
353308
|
};
|
|
353184
|
-
if (!p.packagesPresent && !p.reviewPresent && !p.feedbackPresent && !p.errorsPresent && !p.healthPresent && !p.todoExists && !p.architectureExists) return resolveIdleOrHealth(p);
|
|
353309
|
+
if (!p.packagesPresent && !p.reviewPresent && !p.feedbackPresent && !p.errorsPresent && !p.healthPresent && !p.todoExists && !p.architectureExists && !p.planExists) return resolveIdleOrHealth(p);
|
|
353185
353310
|
return corrupt();
|
|
353186
353311
|
};
|
|
353187
353312
|
/** Boundary/idle lifecycle: no steering files, unrecognized or absent workflow HEAD. */
|
|
@@ -353206,8 +353331,8 @@ const resolveIdleOrHealth = (p) => {
|
|
|
353206
353331
|
const applyTurnTaking = (p, counters, head, baseline) => {
|
|
353207
353332
|
const context = buildContext(p, counters);
|
|
353208
353333
|
const invoker = p.invoker;
|
|
353209
|
-
if (invoker === "human" && !p.workingTreeClean && !p.todoCommitted && !p.architectureCommitted && !p.packagesPresent && !p.reviewPresent && !p.feedbackPresent && !p.errorsPresent && !p.
|
|
353210
|
-
const entryGate = p.architectureExists ? "architecting" : "grilling";
|
|
353334
|
+
if (invoker === "human" && !p.workingTreeClean && !p.todoCommitted && !p.architectureCommitted && !p.planCommitted && !p.packagesPresent && !p.reviewPresent && !p.feedbackPresent && !p.errorsPresent && !p.healthCommitted && (parseSubject(head).kind === "boundary" || head === "gtd: done")) {
|
|
353335
|
+
const entryGate = p.healthPresent ? "health-fixing" : p.planExists ? "grilled" : p.architectureExists ? "architecting" : "grilling";
|
|
353211
353336
|
return {
|
|
353212
353337
|
state: entryGate,
|
|
353213
353338
|
actor: "human",
|
|
@@ -353244,7 +353369,7 @@ const applyTurnTaking = (p, counters, head, baseline) => {
|
|
|
353244
353369
|
};
|
|
353245
353370
|
const healthCheckCapReached = head === "gtd: health-check" && counters.healthFixCount >= p.fixAttemptCap;
|
|
353246
353371
|
if (head === "gtd: health-fix" && baseline.state === "idle" || healthCheckCapReached && baseline.state === "health-fixing") {
|
|
353247
|
-
const chainAfterGreenAtHealthFix = (p.squashEnabled || p.learningEnabled) &&
|
|
353372
|
+
const chainAfterGreenAtHealthFix = (p.squashEnabled || p.learningEnabled) && p.healthFixBase !== void 0;
|
|
353248
353373
|
return {
|
|
353249
353374
|
state: "idle",
|
|
353250
353375
|
actor: "agent",
|
|
@@ -353266,7 +353391,7 @@ const applyTurnTaking = (p, counters, head, baseline) => {
|
|
|
353266
353391
|
context
|
|
353267
353392
|
};
|
|
353268
353393
|
if (baseline.state === "idle" && invoker === "human") {
|
|
353269
|
-
const chainAfterGreen = (p.squashEnabled || p.learningEnabled) &&
|
|
353394
|
+
const chainAfterGreen = (p.squashEnabled || p.learningEnabled) && p.healthFixBase !== void 0;
|
|
353270
353395
|
return {
|
|
353271
353396
|
state: "idle",
|
|
353272
353397
|
actor: "human",
|
|
@@ -353374,8 +353499,8 @@ const predictTurn = (events) => {
|
|
|
353374
353499
|
state: baseline.state
|
|
353375
353500
|
};
|
|
353376
353501
|
}
|
|
353377
|
-
if (!payload.workingTreeClean && !payload.todoCommitted && !payload.architectureCommitted && !payload.packagesPresent && !payload.reviewPresent && !payload.feedbackPresent && !payload.errorsPresent && !payload.
|
|
353378
|
-
const entryGate = payload.architectureExists ? "architecting" : "grilling";
|
|
353502
|
+
if (!payload.workingTreeClean && !payload.todoCommitted && !payload.architectureCommitted && !payload.planCommitted && !payload.packagesPresent && !payload.reviewPresent && !payload.feedbackPresent && !payload.errorsPresent && !payload.healthCommitted && (parseSubject(head).kind === "boundary" || head === "gtd: done")) {
|
|
353503
|
+
const entryGate = payload.healthPresent ? "health-fixing" : payload.planExists ? "grilled" : payload.architectureExists ? "architecting" : "grilling";
|
|
353379
353504
|
return {
|
|
353380
353505
|
actor: "human",
|
|
353381
353506
|
subject: `gtd(human): ${entryGate}`,
|
|
@@ -353910,17 +354035,20 @@ var feedback_default = "<%\n const body = it.content.replace(/\\n$/, \"\")\n c
|
|
|
353910
354035
|
//#region src/prompts/partials/package.md
|
|
353911
354036
|
var package_default = "<%\n const lines = [\"\", \"### Package: `\" + it.pkg.name + \"/`\", \"\"]\n for (const task of it.pkg.taskContents) {\n const fence = it.fenceFor(task.content)\n lines.push(\"#### `\" + task.name + \"`\", \"\", fence, task.content.replace(/\\n$/, \"\"), fence, \"\")\n }\n%><%~ lines.join(\"\\n\") %>\n";
|
|
353912
354037
|
//#endregion
|
|
354038
|
+
//#region src/prompts/partials/decision-log.md
|
|
354039
|
+
var decision_log_default = "<%\n const body = it.decisionLog.replace(/\\n$/, \"\")\n const fence = it.fenceFor(body)\n const out = [\n \"\",\n \"### Prior decisions\",\n \"\",\n \"Recorded in past squash commits, oldest to newest, with no deduplication\" +\n \" — if two entries answer the same question differently, the LATER one\" +\n \" (further down) is the current, authoritative answer. Don't re-ask a\" +\n \" settled question unless the codebase now genuinely contradicts it.\",\n \"\",\n fence,\n body,\n fence,\n \"\",\n ].join(\"\\n\")\n%><%~ out %>\n";
|
|
354040
|
+
//#endregion
|
|
353913
354041
|
//#region src/prompts/partials/agent-turn.md
|
|
353914
354042
|
var agent_turn_default = "Finish your turn by running `gtd step-agent`. Then run `gtd next` and follow\nits output — repeat this cycle as long as the output is addressed to you (the\nagent); when it awaits the human, stop and hand off.\n";
|
|
353915
354043
|
//#endregion
|
|
353916
354044
|
//#region src/prompts/grilling-agent.md
|
|
353917
|
-
var grilling_agent_default = "<%~ include(\"@header\") %>\n\n`.gtd/TODO.md` holds the plan under development. Develop it into a concrete,\nproduct-level plan **in this one turn** — use subagents / internal iteration\nto go as deep as needed; there is no further agent-only round after this one.\n\n**Scope: product and user-facing decisions only.** Do not decide\nimplementation details in this file — file/module structure, data models,\nlibrary or tech-stack choices, and other architecture questions belong to the\nnext phase (`.gtd/ARCHITECTURE.md`) and must not be treated as open questions\nhere.\n\n### Develop the plan\n\nSpawn a **planning-model subagent** using model `<%= it.model %>` to develop the plan.\nThe subagent works entirely by editing `.gtd/TODO.md`:\n\n1. **Explore the codebase before asking anything** — read the relevant files,\n tests, and docs so every question below is one the codebase genuinely\n cannot answer.\n2. **Replace the captured input / seed template with a real product plan** —\n what to build and why, and the user-facing behavior it should have,\n grounded in the codebase. Leave *how* to build it for the next phase.\n3. **Iterate internally** (spawn further subagents, reconsider, cross-check)\n until the plan is as complete as it can get without human input. Do not\n leave this to a future round — there isn't one before the human is asked.\n4. For every remaining open question, add it under a `## Open Questions`\n section near the top of the file, one `### <question>` sub-heading per\n question, whose first body line is `Suggested default: <answer>` — your\n best-guess answer, stated plainly, that the human can accept as-is. This\n structure is enforced: a `###` question with no `Suggested default:` line\n blocks your turn — `gtd step-agent` refuses until it's fixed. Omit the\n `## Open Questions` section entirely once there are none.\n5. Leave `.gtd/TODO.md` **uncommitted** — the human's turn (`gtd step`) reads it\n next.\n<% if (it.context.turnDiff && it.context.turnDiff.trim()) { %>\n<%~ include(\"@diff\", { heading: \"Latest human input (answers / sketch / review feedback)\", diff: it.context.turnDiff, fenceFor: it.fenceFor }) %>\n\nRead the diff above as **feedback, not finished work**:\n\n- Code changes are suggestions — fold them into the plan and re-implement them\n properly, including test coverage, rather than restoring them verbatim.\n- Code comments are positional feedback about the code at that location.\n- Plan-text changes are global feedback on the plan (or the reviewed work) as\n a whole.\n<% } %>\n\n<% if (it.tail) { %>\n<%~ include(it.tail) %>\n<% } %>\n";
|
|
354045
|
+
var grilling_agent_default = "<%~ include(\"@header\") %>\n\n`.gtd/TODO.md` holds the plan under development. Develop it into a concrete,\nproduct-level plan **in this one turn** — use subagents / internal iteration\nto go as deep as needed; there is no further agent-only round after this one.\n\n**Scope: product and user-facing decisions only.** Do not decide\nimplementation details in this file — file/module structure, data models,\nlibrary or tech-stack choices, and other architecture questions belong to the\nnext phase (`.gtd/ARCHITECTURE.md`) and must not be treated as open questions\nhere.\n<% if (it.context.decisionLog && it.context.decisionLog.trim()) { %>\n<%~ include(\"@decision-log\", { decisionLog: it.context.decisionLog, fenceFor: it.fenceFor }) %>\n<% } %>\n\n### Develop the plan\n\nSpawn a **planning-model subagent** using model `<%= it.model %>` to develop the plan.\nThe subagent works entirely by editing `.gtd/TODO.md`:\n\n1. **Explore the codebase before asking anything** — read the relevant files,\n tests, and docs so every question below is one the codebase genuinely\n cannot answer.\n2. **Replace the captured input / seed template with a real product plan** —\n what to build and why, and the user-facing behavior it should have,\n grounded in the codebase. Leave *how* to build it for the next phase.\n3. **Iterate internally** (spawn further subagents, reconsider, cross-check)\n until the plan is as complete as it can get without human input. Do not\n leave this to a future round — there isn't one before the human is asked.\n4. For every remaining open question, add it under a `## Open Questions`\n section near the top of the file, one `### <question>` sub-heading per\n question, whose first body line is `Suggested default: <answer>` — your\n best-guess answer, stated plainly, that the human can accept as-is. This\n structure is enforced: a `###` question with no `Suggested default:` line\n blocks your turn — `gtd step-agent` refuses until it's fixed. Omit the\n `## Open Questions` section entirely once there are none.\n5. Leave `.gtd/TODO.md` **uncommitted** — the human's turn (`gtd step`) reads it\n next.\n<% if (it.context.turnDiff && it.context.turnDiff.trim()) { %>\n<%~ include(\"@diff\", { heading: \"Latest human input (answers / sketch / review feedback)\", diff: it.context.turnDiff, fenceFor: it.fenceFor }) %>\n\nRead the diff above as **feedback, not finished work**:\n\n- Code changes are suggestions — fold them into the plan and re-implement them\n properly, including test coverage, rather than restoring them verbatim.\n- Code comments are positional feedback about the code at that location.\n- Plan-text changes are global feedback on the plan (or the reviewed work) as\n a whole.\n<% } %>\n\n<% if (it.tail) { %>\n<%~ include(it.tail) %>\n<% } %>\n";
|
|
353918
354046
|
//#endregion
|
|
353919
354047
|
//#region src/prompts/grilling-answers.md
|
|
353920
354048
|
var grilling_answers_default = "<%~ include(\"@header\") %>\n\n`.gtd/TODO.md` holds the plan under development, with open questions under a\n`## Open Questions` section — each `### <question>` carrying a suggested\ndefault.\n\nThis is a human gate; there is nothing for the agent to do.\n\nTell the user:\n\n- To answer a question, edit its `### <question>` entry under `## Open\n Questions` in place — replace the `Suggested default: ...` line with\n `Answer: ...` (or annotate it further), then run `gtd step`.\n- To accept **all** suggested defaults as-is, run `gtd step` with **no\n edits** — this converges the plan and moves on to technical architecting.\n\n<% if (it.tail) { %>\n<%~ include(it.tail) %>\n<% } %>\n";
|
|
353921
354049
|
//#endregion
|
|
353922
354050
|
//#region src/prompts/architecting-agent.md
|
|
353923
|
-
var architecting_agent_default = "<%~ include(\"@header\") %>\n\n`.gtd/ARCHITECTURE.md` holds the technical plan under development — seeded\nfrom the converged product plan, or, if this is the very first turn on\nalready-technical input, written directly by the human. Develop it into a concrete,\nimplementation-ready technical plan **in this one turn** — use subagents /\ninternal iteration to go as deep as needed; there is no further agent-only\nround after this one.\n\n**Scope: technical/architectural decisions only.** Decide file/module\nstructure, data models, module boundaries, library or tech-stack choices, and\nerror-handling/concurrency strategy — the *how*, building on the *what*\nalready settled in this file's product content. Do not re-open or re-litigate\nproduct/user-facing decisions from the prior phase; treat them as settled\ncontext.\n\n### Develop the architecture\n\nSpawn a **planning-model subagent** using model `<%= it.model %>` to develop the\narchitecture. The subagent works entirely by editing `.gtd/ARCHITECTURE.md`:\n\n1. **Explore the codebase before asking anything** — read the relevant files,\n tests, and docs so every question below is one the codebase genuinely\n cannot answer.\n2. **Replace the seeded/captured content with a real architecture** — the\n files to change, module/data-model structure, and why, grounded in the\n codebase.\n3. **Iterate internally** (spawn further subagents, reconsider, cross-check)\n until the architecture is as complete as it can get without human input.\n Do not leave this to a future round — there isn't one before the human is\n asked.\n4. For every remaining open question, add it under a `## Open Questions`\n section near the top of the file, one `### <question>` sub-heading per\n question, whose first body line is `Suggested default: <answer>` — your\n best-guess answer, stated plainly, that the human can accept as-is. This\n structure is enforced: a `###` question with no `Suggested default:` line\n blocks your turn — `gtd step-agent` refuses until it's fixed. Omit the\n `## Open Questions` section entirely once there are none.\n5. Leave `.gtd/ARCHITECTURE.md` **uncommitted** — the human's turn (`gtd step`)\n reads it next.\n<% if (it.context.turnDiff && it.context.turnDiff.trim()) { %>\n<%~ include(\"@diff\", { heading: \"Latest human input (answers / sketch / review feedback)\", diff: it.context.turnDiff, fenceFor: it.fenceFor }) %>\n\nRead the diff above as **feedback, not finished work**:\n\n- Code changes are suggestions — fold them into the architecture and\n re-implement them properly, including test coverage, rather than restoring\n them verbatim.\n- Code comments are positional feedback about the code at that location.\n- Plan-text changes are global feedback on the architecture (or the reviewed\n work) as a whole.\n<% } %>\n\n<% if (it.tail) { %>\n<%~ include(it.tail) %>\n<% } %>\n";
|
|
354051
|
+
var architecting_agent_default = "<%~ include(\"@header\") %>\n\n`.gtd/ARCHITECTURE.md` holds the technical plan under development — seeded\nfrom the converged product plan, or, if this is the very first turn on\nalready-technical input, written directly by the human. Develop it into a concrete,\nimplementation-ready technical plan **in this one turn** — use subagents /\ninternal iteration to go as deep as needed; there is no further agent-only\nround after this one.\n\n**Scope: technical/architectural decisions only.** Decide file/module\nstructure, data models, module boundaries, library or tech-stack choices, and\nerror-handling/concurrency strategy — the *how*, building on the *what*\nalready settled in this file's product content. Do not re-open or re-litigate\nproduct/user-facing decisions from the prior phase; treat them as settled\ncontext.\n<% if (it.context.decisionLog && it.context.decisionLog.trim()) { %>\n<%~ include(\"@decision-log\", { decisionLog: it.context.decisionLog, fenceFor: it.fenceFor }) %>\n<% } %>\n\n### Develop the architecture\n\nSpawn a **planning-model subagent** using model `<%= it.model %>` to develop the\narchitecture. The subagent works entirely by editing `.gtd/ARCHITECTURE.md`:\n\n1. **Explore the codebase before asking anything** — read the relevant files,\n tests, and docs so every question below is one the codebase genuinely\n cannot answer.\n2. **Replace the seeded/captured content with a real architecture** — the\n files to change, module/data-model structure, and why, grounded in the\n codebase.\n3. **Iterate internally** (spawn further subagents, reconsider, cross-check)\n until the architecture is as complete as it can get without human input.\n Do not leave this to a future round — there isn't one before the human is\n asked.\n4. For every remaining open question, add it under a `## Open Questions`\n section near the top of the file, one `### <question>` sub-heading per\n question, whose first body line is `Suggested default: <answer>` — your\n best-guess answer, stated plainly, that the human can accept as-is. This\n structure is enforced: a `###` question with no `Suggested default:` line\n blocks your turn — `gtd step-agent` refuses until it's fixed. Omit the\n `## Open Questions` section entirely once there are none.\n5. Leave `.gtd/ARCHITECTURE.md` **uncommitted** — the human's turn (`gtd step`)\n reads it next.\n<% if (it.context.turnDiff && it.context.turnDiff.trim()) { %>\n<%~ include(\"@diff\", { heading: \"Latest human input (answers / sketch / review feedback)\", diff: it.context.turnDiff, fenceFor: it.fenceFor }) %>\n\nRead the diff above as **feedback, not finished work**:\n\n- Code changes are suggestions — fold them into the architecture and\n re-implement them properly, including test coverage, rather than restoring\n them verbatim.\n- Code comments are positional feedback about the code at that location.\n- Plan-text changes are global feedback on the architecture (or the reviewed\n work) as a whole.\n<% } %>\n\n<% if (it.tail) { %>\n<%~ include(it.tail) %>\n<% } %>\n";
|
|
353924
354052
|
//#endregion
|
|
353925
354053
|
//#region src/prompts/architecting-answers.md
|
|
353926
354054
|
var architecting_answers_default = "<%~ include(\"@header\") %>\n\n`.gtd/ARCHITECTURE.md` holds the technical plan under development, with open\nquestions under a `## Open Questions` section — each `### <question>`\ncarrying a suggested default.\n\nThis is a human gate; there is nothing for the agent to do.\n\nTell the user:\n\n- To answer a question, edit its `### <question>` entry under `## Open\n Questions` in place — replace the `Suggested default: ...` line with\n `Answer: ...` (or annotate it further), then run `gtd step`.\n- To accept **all** suggested defaults as-is, run `gtd step` with **no\n edits** — this converges the architecture and moves on to decomposition.\n\n<% if (it.tail) { %>\n<%~ include(it.tail) %>\n<% } %>\n";
|
|
@@ -353935,7 +354063,7 @@ var building_default = "<%~ include(\"@header\") %>\n\nBuild the package describ
|
|
|
353935
354063
|
var fixing_default = "<%~ include(\"@header\") %>\n\nSpawn a **fix subagent** using model `<%= it.model %>` to apply fixes for the feedback\nbelow:\n\n1. **Work through the Feedback to address section below** — address every item\n it lists. For test output, make the failing tests pass; for review findings,\n satisfy each finding against the package's task specs.\n2. **Make the fix in place** — change the code to resolve the feedback. Keep the\n change focused; do not refactor unrelated code.\n3. **Or dispute the feedback** — if a finding is wrong, empty or delete\n `.gtd/FEEDBACK.md` instead of fixing it. The machine picks the dispute up\n either way.\n4. **Leave every change uncommitted and finish your turn** — do **not** commit\n or stage.\n<% if (it.context.feedbackContent.trim()) { %>\n<%~ include(\"@feedback\", { content: it.context.feedbackContent, fenceFor: it.fenceFor }) %>\n<% } %>\n\n<% if (it.tail) { %>\n<%~ include(it.tail) %>\n<% } %>\n";
|
|
353936
354064
|
//#endregion
|
|
353937
354065
|
//#region src/prompts/health-fixing.md
|
|
353938
|
-
var health_fixing_default = "<%~ include(\"@header\") %>\n\nThe
|
|
354066
|
+
var health_fixing_default = "<%~ include(\"@header\") %>\n\nThe repository's health gate is red outside any work cycle — either the idle\nhealth check failed, or a human hand-wrote `.gtd/HEALTH.md` describing errors\nto fix. Spawn a **fix subagent** using model `<%= it.model %>` to repair it:\n\n1. **Work through the report below** — fix the described errors and make the\n test command pass.\n2. **Make the fix in place** — keep the change focused; do not refactor\n unrelated code.\n3. **If the failure does not reproduce** (or nothing needs changing), change\n nothing and simply finish your turn — the machine re-runs the health check\n either way.\n4. **Never create or edit any file under `.gtd/`** — in particular, do NOT\n write `.gtd/FEEDBACK.md`. The machine removes `.gtd/HEALTH.md` itself when\n your turn is captured.\n5. **Leave every change uncommitted and finish your turn** — do **not** commit\n or stage.\n<% if (it.context.feedbackContent.trim()) { %>\n<%~ include(\"@feedback\", { content: it.context.feedbackContent, fenceFor: it.fenceFor }) %>\n<% } %>\n\n<% if (it.tail) { %>\n<%~ include(it.tail) %>\n<% } %>\n";
|
|
353939
354067
|
//#endregion
|
|
353940
354068
|
//#region src/prompts/agentic-review.md
|
|
353941
354069
|
var agentic_review_default = "<%~ include(\"@header\") %>\n\nSpawn a **reviewing subagent** using model `<%= it.model %>`. The package's task spec\nfiles and its cumulative diff are found below. The subagent must:\n\n1. **Read the task spec files** — they define the acceptance criteria. Every\n requirement must be checked, with one exception: ignore any spec\n requirement about files under `.gtd/` (or a root-level `TODO.md` /\n `REVIEW.md` / `FEEDBACK.md` acting as workflow state). Those files are\n managed by the workflow machine — created and deleted outside the\n implementation's control — so never report a finding about their\n presence, absence, or content.\n2. **Read the package diff** — examine every hunk to determine whether the\n implementation satisfies the spec.\n3. **Always write `.gtd/FEEDBACK.md`**:\n - **Fully satisfies the spec** → write an **empty** `.gtd/FEEDBACK.md` (zero bytes\n or whitespace only). This empty file is the **approval** signal.\n - **Does not fully satisfy** → write concrete, actionable findings anchored\n to specific file and symbol names, grouped under short headings, so the fix\n agent can act without re-reading the diff.\n4. **Do not edit source files and do not commit** — the reviewer only writes\n `.gtd/FEEDBACK.md`, left uncommitted, then finishes its turn.\n<% if (it.context.packages[0]) { %>\n<%~ include(\"@package\", { pkg: it.context.packages[0], fenceFor: it.fenceFor }) %>\n<% } %><% if (it.context.refDiff && it.context.refDiff.trim()) { %>\n<%~ include(\"@diff\", { heading: \"Package diff\", diff: it.context.refDiff, fenceFor: it.fenceFor }) %>\n<% } %>\n\n<% if (it.tail) { %>\n<%~ include(it.tail) %>\n<% } %>\n";
|
|
@@ -353947,7 +354075,7 @@ var review_default = "<%~ include(\"@header\") %>\n\nSpawn a **planning-model su
|
|
|
353947
354075
|
var await_review_default = "<%~ include(\"@header\") %>\n\n`.gtd/REVIEW.md` holds the review record for the completed work. This is a human\ngate; there is nothing for the agent to do.\n\nTell the user:\n\n- Read `.gtd/REVIEW.md` (and the underlying work it points at).\n- To **approve**: run `gtd step` with no edits, or only tick checkboxes in\n `.gtd/REVIEW.md` — either way ends the review with an approval.\n- To **request changes**: write substantive edits or annotations into\n `.gtd/REVIEW.md` (or the code) describing what's wrong, then run `gtd step` —\n this sends the feedback back for another round.\n\n<% if (it.tail) { %>\n<%~ include(it.tail) %>\n<% } %>\n";
|
|
353948
354076
|
//#endregion
|
|
353949
354077
|
//#region src/prompts/squashing.md
|
|
353950
|
-
var squashing_default = "<%~ include(\"@header\") %>\n\nThe process is **approved and done**. `.gtd/SQUASH_MSG.md` has already been\ncommitted with a template — your job is to overwrite it with the real\nconventional-commits squash message and finish your turn.\n\n### Step 1 — Extract decisions from grilling rounds\n\nScan the git history of recent `gtd: ...` / `gtd(agent): ...` /\n`gtd(human): ...` commits. Look for changes to `.gtd/TODO.md` and\n`.gtd/ARCHITECTURE.md` — specifically the `## Captured input` sections and any\nedits to plan/architecture text. Extract **key decisions, trade-offs, and\ndesign choices** made during grilling and architecting rounds
|
|
354078
|
+
var squashing_default = "<%~ include(\"@header\") %>\n\nThe process is **approved and done**. `.gtd/SQUASH_MSG.md` has already been\ncommitted with a template — your job is to overwrite it with the real\nconventional-commits squash message and finish your turn.\n\n### Step 1 — Extract decisions from grilling rounds\n\nScan the git history of recent `gtd: ...` / `gtd(agent): ...` /\n`gtd(human): ...` commits. Look for changes to `.gtd/TODO.md` and\n`.gtd/ARCHITECTURE.md` — specifically the `## Captured input` sections and any\nedits to plan/architecture text. Extract **key decisions, trade-offs, and\ndesign choices** made during grilling and architecting rounds — specifically\nthe `### <question>` entries under `## Open Questions` that got resolved\n(`Answer:`, or an accepted `Suggested default:`) this cycle.\n\n### Step 2 — Draft the commit message\n\nDraft ONE conventional-commits message:\n\n```\ntype(scope): subject\n\nbody (explain the why — motivation, trade-offs, key decisions from grilling)\n\n## Decisions\n\n### <question, verbatim from Step 1>\nAnswer: <the resolved answer> (default accepted | human override: <reason>)\n\nGtd-Decisions: true\n```\n\n- **type**: `feat` / `fix` / `refactor` / `chore` / `docs` / `test`\n- **subject**: imperative mood, ≤ 72 characters, lowercase after the colon\n- **body**: a brief motivation/trade-off summary.\n- **`## Decisions`**: one block per question resolved THIS cycle (from Step\n 1) — omit the whole section (and the `Gtd-Decisions: true` line) entirely\n when this cycle recorded no decisions. Don't restate decisions from earlier\n cycles — this commit only records what changed now; a later grilling round\n reads the full history of these sections back as context, so repeating old\n entries here would just duplicate them forever. If this cycle's answer\n contradicts an earlier cycle's, don't try to edit or annotate the old one —\n just state the new answer plainly; the newer commit naturally reads as the\n current truth.\n\n### Step 3 — Overwrite .gtd/SQUASH_MSG.md\n\nReplace the **entire content** of `.gtd/SQUASH_MSG.md` (plain text, no markdown\nwrapper, no leftover template scaffolding) with the message from Step 2, then\nleave it uncommitted and finish your turn — `gtd step-agent` performs the\nsquash using this file's content.\n<% if (it.context.squashDiff && it.context.squashDiff.trim()) { %>\n<%= it.context.squashBase !== undefined ? \"\\nSquash base: \" + it.context.squashBase + \"\\n\" : \"\" %>\n<%~ include(\"@diff\", { heading: \"Full-process diff (`git diff \" + (it.context.squashBase !== undefined ? it.context.squashBase : \"<squashBase>\") + \" HEAD`)\", diff: it.context.squashDiff, fenceFor: it.fenceFor }) %>\n<% } %>\n\n<% if (it.tail) { %>\n<%~ include(it.tail) %>\n<% } %>\n";
|
|
353951
354079
|
//#endregion
|
|
353952
354080
|
//#region src/prompts/learning.md
|
|
353953
354081
|
var learning_default = "<%~ include(\"@header\") %>\n\nThe process is **approved and done**, and headed for a squash. Before that\nhappens, `.gtd/LEARNINGS.md` has been committed with a template — your job is\nto overwrite it with the real, distilled learnings from this cycle so they\nsurvive the squash even though the granular history won't.\n\n### Step 1 — Walk the cycle's history\n\nScan the git history back to the cycle's start (the full-process diff is\ninlined below, and `git log` over the same range shows every `gtd: ...` /\n`gtd(agent): ...` / `gtd(human): ...` commit). Look specifically for:\n\n- **Test failures and fixes** — each `gtd: errors` round and the fix that\n followed it. What broke, and what actually made it pass?\n- **Review feedback** — human `.gtd/REVIEW.md` notes and `gtd: review\n feedback` detours. What did the human have to correct that the agent got\n wrong on its own?\n- **Health-check rounds** — any `gtd: health-check` / `gtd: health-fix`\n cycles. Environmental or systemic issues surfaced outside normal building?\n- **Grilling decisions** — `.gtd/TODO.md`'s \"## Captured input\" sections and\n plan edits. Trade-offs or conventions the human specified that a future\n agent should already know.\n\n### Step 2 — Distill, don't transcribe\n\nFor each thing you found, ask: is this a **durable, generalizable lesson**\n(a convention, a gotcha, a pattern this project wants followed next time), or\na **one-off detail** specific to this change? Keep only the former. A future\nagent reading this should learn something that changes how it works on this\nproject, not a recap of what happened.\n\n### Step 3 — Overwrite .gtd/LEARNINGS.md\n\nReplace the **entire content** of `.gtd/LEARNINGS.md` with your distilled\nlearnings (plain markdown, no leftover template scaffolding). If nothing\ndurable surfaced this cycle, write a short explicit note saying so — don't\nleave the placeholder text in place. Leave the file uncommitted and finish\nyour turn; a human reviews it next before it's integrated into the project's\nown memory (CLAUDE.md/AGENTS.md/docs).\n<% if (it.context.squashDiff && it.context.squashDiff.trim()) { %>\n<%= it.context.squashBase !== undefined ? \"\\nCycle base: \" + it.context.squashBase + \"\\n\" : \"\" %>\n<%~ include(\"@diff\", { heading: \"Full-process diff (`git diff \" + (it.context.squashBase !== undefined ? it.context.squashBase : \"<squashBase>\") + \" HEAD`)\", diff: it.context.squashDiff, fenceFor: it.fenceFor }) %>\n<% } %>\n\n<% if (it.tail) { %>\n<%~ include(it.tail) %>\n<% } %>\n";
|
|
@@ -354034,6 +354162,7 @@ eta.loadTemplate("@header", header_default);
|
|
|
354034
354162
|
eta.loadTemplate("@diff", diff_default);
|
|
354035
354163
|
eta.loadTemplate("@feedback", feedback_default);
|
|
354036
354164
|
eta.loadTemplate("@package", package_default);
|
|
354165
|
+
eta.loadTemplate("@decision-log", decision_log_default);
|
|
354037
354166
|
eta.loadTemplate("@agent-turn", agent_turn_default);
|
|
354038
354167
|
eta.loadTemplate("@grilling-agent", grilling_agent_default);
|
|
354039
354168
|
eta.loadTemplate("@grilling-answers", grilling_answers_default);
|
|
@@ -365108,7 +365237,7 @@ const reviewWindowBase = (history) => {
|
|
|
365108
365237
|
const parsed = parseSubject(subjectOf(c.message));
|
|
365109
365238
|
if (parsed.kind === "routing" && parsed.phase === "reviewing" && parsed.param !== void 0) anchor = parsed.param;
|
|
365110
365239
|
if (parsed.kind === "routing" && parsed.phase === "awaiting-review") lastAwaitingReview = c.hash;
|
|
365111
|
-
if (parsed.kind === "turn" && parsed.gate === "grilling" && firstGrilling === void 0) firstGrilling = c.hash;
|
|
365240
|
+
if (parsed.kind === "turn" && (parsed.gate === "grilling" || parsed.gate === "architecting" || parsed.gate === "grilled") && firstGrilling === void 0) firstGrilling = c.hash;
|
|
365112
365241
|
}
|
|
365113
365242
|
return anchor ?? lastAwaitingReview ?? firstGrilling;
|
|
365114
365243
|
};
|
|
@@ -365445,6 +365574,7 @@ const edgeActionHandlers = {
|
|
|
365445
365574
|
commitRouting: (a) => {
|
|
365446
365575
|
let msg = `commit routing as "${a.subject}"`;
|
|
365447
365576
|
if (a.seedArchitectureFromTodo) msg += " (seeding .gtd/ARCHITECTURE.md from .gtd/TODO.md)";
|
|
365577
|
+
if (a.seedArchitectureFromPlan) msg += " (seeding .gtd/ARCHITECTURE.md from .gtd/PLAN.md)";
|
|
365448
365578
|
const removed = [];
|
|
365449
365579
|
if (a.removeArchitecture) removed.push(".gtd/ARCHITECTURE.md");
|
|
365450
365580
|
if (a.removeReview) removed.push(".gtd/REVIEW.md");
|