@pmelab/gtd 2.7.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 -1107
- package/bin/gtd-loop +1 -1
- package/dist/gtd.bundle.mjs +140 -41
- 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/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
|
@@ -351784,6 +351784,7 @@ const parseReviewDoc = (content) => {
|
|
|
351784
351784
|
const GTD_DIR = ".gtd";
|
|
351785
351785
|
const TODO_FILE = `${GTD_DIR}/TODO.md`;
|
|
351786
351786
|
const ARCHITECTURE_FILE = `${GTD_DIR}/ARCHITECTURE.md`;
|
|
351787
|
+
const PLAN_FILE = `${GTD_DIR}/PLAN.md`;
|
|
351787
351788
|
const REVIEW_FILE = `${GTD_DIR}/REVIEW.md`;
|
|
351788
351789
|
const FEEDBACK_FILE = `${GTD_DIR}/FEEDBACK.md`;
|
|
351789
351790
|
const ERRORS_FILE = `${GTD_DIR}/ERRORS.md`;
|
|
@@ -351795,6 +351796,7 @@ const LEGACY_FEEDBACK_FILE = "FEEDBACK.md";
|
|
|
351795
351796
|
const STEERING_FILES = {
|
|
351796
351797
|
todo: TODO_FILE,
|
|
351797
351798
|
architecture: ARCHITECTURE_FILE,
|
|
351799
|
+
plan: PLAN_FILE,
|
|
351798
351800
|
review: REVIEW_FILE,
|
|
351799
351801
|
feedback: FEEDBACK_FILE,
|
|
351800
351802
|
errors: ERRORS_FILE,
|
|
@@ -352014,18 +352016,21 @@ const gatherEvents = (invoker) => gen(function* () {
|
|
|
352014
352016
|
const parsed = parseSubject(subjectOf$1(commit.message));
|
|
352015
352017
|
const isTurn = parsed.kind === "turn";
|
|
352016
352018
|
const isRouting = parsed.kind === "routing";
|
|
352019
|
+
const routingPhase = parsed.kind === "routing" ? parsed.phase : "";
|
|
352020
|
+
const turnGate = parsed.kind === "turn" ? parsed.gate : "";
|
|
352017
352021
|
return {
|
|
352018
352022
|
type: "COMMIT",
|
|
352019
|
-
...
|
|
352023
|
+
...parsed.kind === "turn" ? {
|
|
352020
352024
|
turnActor: parsed.actor,
|
|
352021
352025
|
turnGate: parsed.gate
|
|
352022
352026
|
} : {},
|
|
352023
|
-
isErrors:
|
|
352024
|
-
isFeedback:
|
|
352025
|
-
isPackageStart:
|
|
352027
|
+
isErrors: routingPhase === "errors",
|
|
352028
|
+
isFeedback: turnGate === "agentic-review" && touchedFeedback(commit.touched),
|
|
352029
|
+
isPackageStart: routingPhase === "planning" || routingPhase === "package-done",
|
|
352026
352030
|
isWorkflowCommit: isTurn || isRouting,
|
|
352027
352031
|
removedErrors: commit.removedErrors,
|
|
352028
|
-
isHealthCheck:
|
|
352032
|
+
isHealthCheck: routingPhase === "health-check",
|
|
352033
|
+
isTestsGreen: routingPhase === "tests-green"
|
|
352029
352034
|
};
|
|
352030
352035
|
});
|
|
352031
352036
|
const hasCommits = yield* git.hasCommits();
|
|
@@ -352058,6 +352063,7 @@ const gatherEvents = (invoker) => gen(function* () {
|
|
|
352058
352063
|
const codeDirty = entries.some((e) => !isGtdPath(e.path));
|
|
352059
352064
|
const todoExists = yield* fs.exists(resolve(TODO_FILE));
|
|
352060
352065
|
const architectureExists = yield* fs.exists(resolve(ARCHITECTURE_FILE));
|
|
352066
|
+
const planExists = yield* fs.exists(resolve(PLAN_FILE));
|
|
352061
352067
|
const reviewPresent = yield* fs.exists(resolve(REVIEW_FILE));
|
|
352062
352068
|
const feedbackPresent = yield* fs.exists(resolve(FEEDBACK_FILE));
|
|
352063
352069
|
const errorsPresent = yield* fs.exists(resolve(ERRORS_FILE));
|
|
@@ -352073,6 +352079,7 @@ const gatherEvents = (invoker) => gen(function* () {
|
|
|
352073
352079
|
const reviewDirty = reviewTrackedAtHead && !workingTreeClean;
|
|
352074
352080
|
const todoCommitted = todoExists && !isUncommitted(TODO_FILE);
|
|
352075
352081
|
const architectureCommitted = architectureExists && !isUncommitted(ARCHITECTURE_FILE);
|
|
352082
|
+
const planCommitted = planExists && !isUncommitted(PLAN_FILE);
|
|
352076
352083
|
const grillingDocPath = todoExists ? TODO_FILE : architectureExists ? ARCHITECTURE_FILE : void 0;
|
|
352077
352084
|
const grillingDocErrors = grillingDocPath ? parseOpenQuestions(yield* fs.readFileString(resolve(grillingDocPath))).errors : [];
|
|
352078
352085
|
const reviewDocErrors = reviewPresent ? parseReviewDoc(yield* fs.readFileString(resolve(REVIEW_FILE))).errors : [];
|
|
@@ -352101,7 +352108,7 @@ const gatherEvents = (invoker) => gen(function* () {
|
|
|
352101
352108
|
}
|
|
352102
352109
|
const isGrillingTurn = (message) => {
|
|
352103
352110
|
const parsed = parseSubject(subjectOf$1(message));
|
|
352104
|
-
return parsed.kind === "turn" && (parsed.gate === "grilling" || parsed.gate === "architecting");
|
|
352111
|
+
return parsed.kind === "turn" && (parsed.gate === "grilling" || parsed.gate === "architecting" || parsed.gate === "grilled");
|
|
352105
352112
|
};
|
|
352106
352113
|
const firstGrilling = currentCycle.find((c) => isGrillingTurn(c.message));
|
|
352107
352114
|
const lastAwaitingReview = (() => {
|
|
@@ -352141,7 +352148,7 @@ const gatherEvents = (invoker) => gen(function* () {
|
|
|
352141
352148
|
const squashCycle = squashHistory.slice(prevDoneIdx + 1, lastDoneIdxForSquash + 1);
|
|
352142
352149
|
const isGrillingTurnSubject = (subject) => {
|
|
352143
352150
|
const parsed = parseSubject(subject);
|
|
352144
|
-
return parsed.kind === "turn" && (parsed.gate === "grilling" || parsed.gate === "architecting");
|
|
352151
|
+
return parsed.kind === "turn" && (parsed.gate === "grilling" || parsed.gate === "architecting" || parsed.gate === "grilled");
|
|
352145
352152
|
};
|
|
352146
352153
|
const isReviewingAnchor = (subject) => {
|
|
352147
352154
|
const parsed = parseSubject(subject);
|
|
@@ -352176,28 +352183,23 @@ const gatherEvents = (invoker) => gen(function* () {
|
|
|
352176
352183
|
const healthCommitted = healthPresent && !isUncommitted(HEALTH_FILE);
|
|
352177
352184
|
let healthFixBase;
|
|
352178
352185
|
if (config.squash || config.learning) {
|
|
352179
|
-
|
|
352180
|
-
let
|
|
352181
|
-
for (
|
|
352182
|
-
|
|
352183
|
-
|
|
352184
|
-
|
|
352185
|
-
|
|
352186
|
-
|
|
352187
|
-
|
|
352188
|
-
|
|
352189
|
-
|
|
352190
|
-
for (let i = 0; i < history.length; i++) {
|
|
352191
|
-
const c = commitEvents[i];
|
|
352192
|
-
if (c.isPackageStart || c.removedErrors) resetAt = i;
|
|
352193
|
-
}
|
|
352194
|
-
for (let i = resetAt + 1; i < history.length; i++) if (commitEvents[i].isHealthCheck) {
|
|
352195
|
-
firstHealthCheckHash = history[i].hash;
|
|
352196
|
-
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;
|
|
352197
352197
|
}
|
|
352198
|
+
if (anchorIdx === -1 && (c.isHealthCheck || isHealthEntryTurn(c))) anchorIdx = i;
|
|
352198
352199
|
}
|
|
352199
|
-
if (
|
|
352200
|
-
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)));
|
|
352201
352203
|
healthFixBase = healthBase;
|
|
352202
352204
|
const healthCandidateDiff = yield* git.diffRef(healthBase).pipe(catchAll(() => succeed$3("")));
|
|
352203
352205
|
if (healthCandidateDiff.trim().length > 0) {
|
|
@@ -352217,6 +352219,8 @@ const gatherEvents = (invoker) => gen(function* () {
|
|
|
352217
352219
|
todoCommitted,
|
|
352218
352220
|
architectureExists,
|
|
352219
352221
|
architectureCommitted,
|
|
352222
|
+
planExists,
|
|
352223
|
+
planCommitted,
|
|
352220
352224
|
packagesPresent: packages.length > 0,
|
|
352221
352225
|
reviewPresent,
|
|
352222
352226
|
feedbackPresent,
|
|
@@ -352349,6 +352353,13 @@ const SQUASH_TEMPLATE = [
|
|
|
352349
352353
|
*/
|
|
352350
352354
|
const ARCHITECTURE_SEED_BANNER = "<!-- gtd: seeded from the converged product plan (.gtd/TODO.md); decide the technical/architectural questions below. -->\n\n";
|
|
352351
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
|
+
/**
|
|
352352
352363
|
* A short skeleton written by `writeLearningTemplate`, instructing the
|
|
352353
352364
|
* learning agent to replace it with the real distilled learnings.
|
|
352354
352365
|
*/
|
|
@@ -352379,8 +352390,11 @@ const perform = (action) => gen(function* () {
|
|
|
352379
352390
|
const ensureGtdDir = fs.makeDirectory(resolve(GTD_DIR), { recursive: true }).pipe(catchAll(() => _void));
|
|
352380
352391
|
switch (action.kind) {
|
|
352381
352392
|
case "captureTurn":
|
|
352382
|
-
|
|
352383
|
-
|
|
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));
|
|
352384
352398
|
yield* git.commitAllWithPrefix(turnSubject(action.actor, action.gate));
|
|
352385
352399
|
return { stop: false };
|
|
352386
352400
|
case "commitRouting":
|
|
@@ -352390,6 +352404,12 @@ const perform = (action) => gen(function* () {
|
|
|
352390
352404
|
yield* fs.writeFileString(resolve(ARCHITECTURE_FILE), ARCHITECTURE_SEED_BANNER + todoContent);
|
|
352391
352405
|
yield* fs.remove(resolve(TODO_FILE)).pipe(catchAll(() => _void));
|
|
352392
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
|
+
}
|
|
352393
352413
|
if (action.removeArchitecture === true) yield* fs.remove(resolve(ARCHITECTURE_FILE)).pipe(catchAll(() => _void));
|
|
352394
352414
|
if (action.removeReview === true) yield* fs.remove(resolve(REVIEW_FILE)).pipe(catchAll(() => _void));
|
|
352395
352415
|
if (action.removeFeedback === true) yield* fs.remove(resolve(FEEDBACK_FILE)).pipe(catchAll(() => _void));
|
|
@@ -352481,7 +352501,7 @@ const counterRules = {
|
|
|
352481
352501
|
incrementsOn: (e) => e.isFeedback
|
|
352482
352502
|
},
|
|
352483
352503
|
healthFixCount: {
|
|
352484
|
-
resetsOn: (e) => e.isPackageStart || e.removedErrors,
|
|
352504
|
+
resetsOn: (e) => e.isPackageStart || e.removedErrors || e.isTestsGreen,
|
|
352485
352505
|
incrementsOn: (e) => e.isHealthCheck
|
|
352486
352506
|
}
|
|
352487
352507
|
};
|
|
@@ -352533,6 +352553,8 @@ const DEFAULT_PAYLOAD = {
|
|
|
352533
352553
|
todoCommitted: false,
|
|
352534
352554
|
architectureExists: false,
|
|
352535
352555
|
architectureCommitted: false,
|
|
352556
|
+
planExists: false,
|
|
352557
|
+
planCommitted: false,
|
|
352536
352558
|
packagesPresent: false,
|
|
352537
352559
|
reviewPresent: false,
|
|
352538
352560
|
feedbackPresent: false,
|
|
@@ -352594,6 +352616,52 @@ const healthFileConflictRules = [
|
|
|
352594
352616
|
{
|
|
352595
352617
|
isViolated: (p) => p.healthPresent && p.errorsPresent,
|
|
352596
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"
|
|
352597
352665
|
}
|
|
352598
352666
|
];
|
|
352599
352667
|
const learningFileConflictRules = [
|
|
@@ -352689,6 +352757,7 @@ const assertLegal = (p) => {
|
|
|
352689
352757
|
for (const rule of [
|
|
352690
352758
|
...healthFileConflictRules,
|
|
352691
352759
|
...learningFileConflictRules,
|
|
352760
|
+
...planFileConflictRules,
|
|
352692
352761
|
...reviewAndFeedbackRules
|
|
352693
352762
|
]) if (rule.isViolated(p)) throw new GtdStateError("illegal-combination", `illegal combination: ${rule.message}`);
|
|
352694
352763
|
};
|
|
@@ -352818,6 +352887,16 @@ const classifyHead = (subject, flags) => {
|
|
|
352818
352887
|
actor: "agent"
|
|
352819
352888
|
};
|
|
352820
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;
|
|
352821
352900
|
if (actor === "agent" && gate === "grilled") return flags.hasPackages ? {
|
|
352822
352901
|
kind: "mid-chain",
|
|
352823
352902
|
state: "grilled",
|
|
@@ -353064,7 +353143,11 @@ const gateForState = (state) => STATE_IS_OWN_GATE.has(state) ? state : GATE_OVER
|
|
|
353064
353143
|
* (CLAUDE.md/AGENTS.md/docs), so it's unconditionally inert on a clean tree,
|
|
353065
353144
|
* same as `building`/`fixing`. `health-fixing` is deliberately absent: its
|
|
353066
353145
|
* empty turn is meaningful (environmental fix; the machine removes
|
|
353067
|
-
* 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`).
|
|
353068
353151
|
*/
|
|
353069
353152
|
const INERT_EMPTY_AGENT_GATES = /* @__PURE__ */ new Set([
|
|
353070
353153
|
"grilling",
|
|
@@ -353077,11 +353160,20 @@ const INERT_EMPTY_AGENT_GATES = /* @__PURE__ */ new Set([
|
|
|
353077
353160
|
"learning-apply"
|
|
353078
353161
|
]);
|
|
353079
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
|
+
/**
|
|
353080
353172
|
* True when a `step-agent` invocation at this rest has nothing to capture —
|
|
353081
353173
|
* see `INERT_EMPTY_AGENT_GATES`. Shared by `applyTurnTaking` (author nothing)
|
|
353082
353174
|
* and `predictTurn` (predict null) so the two can never disagree.
|
|
353083
353175
|
*/
|
|
353084
|
-
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));
|
|
353085
353177
|
/**
|
|
353086
353178
|
* The baseline decision: classify HEAD, falling through to the payload-driven
|
|
353087
353179
|
* ladder for the rows `classifyHead` cannot resolve alone (boundary subjects,
|
|
@@ -353132,6 +353224,7 @@ const resolveBaseline = (p, counters, head, corrupt, lastTurn) => {
|
|
|
353132
353224
|
const flags = {
|
|
353133
353225
|
headTurnIsEmpty: p.headTurnIsEmpty,
|
|
353134
353226
|
hasPackages: p.packagesPresent,
|
|
353227
|
+
planExists: p.planExists,
|
|
353135
353228
|
agenticReviewForceApproved: forceApprove,
|
|
353136
353229
|
squashEnabled: p.squashEnabled,
|
|
353137
353230
|
hasSquashBase: p.squashBase !== void 0,
|
|
@@ -353198,6 +353291,11 @@ const resolveBaseline = (p, counters, head, corrupt, lastTurn) => {
|
|
|
353198
353291
|
state: "architecting",
|
|
353199
353292
|
actor: "agent"
|
|
353200
353293
|
};
|
|
353294
|
+
if (p.planExists) return {
|
|
353295
|
+
kind: "rest",
|
|
353296
|
+
state: "grilled",
|
|
353297
|
+
actor: "human"
|
|
353298
|
+
};
|
|
353201
353299
|
if (p.packagesPresent && p.packages.length > 0 && lastTurn?.actor === "agent" && lastTurn.gate === "building") return {
|
|
353202
353300
|
kind: "mid-chain",
|
|
353203
353301
|
state: "building",
|
|
@@ -353208,7 +353306,7 @@ const resolveBaseline = (p, counters, head, corrupt, lastTurn) => {
|
|
|
353208
353306
|
capReached: false
|
|
353209
353307
|
}
|
|
353210
353308
|
};
|
|
353211
|
-
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);
|
|
353212
353310
|
return corrupt();
|
|
353213
353311
|
};
|
|
353214
353312
|
/** Boundary/idle lifecycle: no steering files, unrecognized or absent workflow HEAD. */
|
|
@@ -353233,8 +353331,8 @@ const resolveIdleOrHealth = (p) => {
|
|
|
353233
353331
|
const applyTurnTaking = (p, counters, head, baseline) => {
|
|
353234
353332
|
const context = buildContext(p, counters);
|
|
353235
353333
|
const invoker = p.invoker;
|
|
353236
|
-
if (invoker === "human" && !p.workingTreeClean && !p.todoCommitted && !p.architectureCommitted && !p.packagesPresent && !p.reviewPresent && !p.feedbackPresent && !p.errorsPresent && !p.
|
|
353237
|
-
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";
|
|
353238
353336
|
return {
|
|
353239
353337
|
state: entryGate,
|
|
353240
353338
|
actor: "human",
|
|
@@ -353271,7 +353369,7 @@ const applyTurnTaking = (p, counters, head, baseline) => {
|
|
|
353271
353369
|
};
|
|
353272
353370
|
const healthCheckCapReached = head === "gtd: health-check" && counters.healthFixCount >= p.fixAttemptCap;
|
|
353273
353371
|
if (head === "gtd: health-fix" && baseline.state === "idle" || healthCheckCapReached && baseline.state === "health-fixing") {
|
|
353274
|
-
const chainAfterGreenAtHealthFix = (p.squashEnabled || p.learningEnabled) &&
|
|
353372
|
+
const chainAfterGreenAtHealthFix = (p.squashEnabled || p.learningEnabled) && p.healthFixBase !== void 0;
|
|
353275
353373
|
return {
|
|
353276
353374
|
state: "idle",
|
|
353277
353375
|
actor: "agent",
|
|
@@ -353293,7 +353391,7 @@ const applyTurnTaking = (p, counters, head, baseline) => {
|
|
|
353293
353391
|
context
|
|
353294
353392
|
};
|
|
353295
353393
|
if (baseline.state === "idle" && invoker === "human") {
|
|
353296
|
-
const chainAfterGreen = (p.squashEnabled || p.learningEnabled) &&
|
|
353394
|
+
const chainAfterGreen = (p.squashEnabled || p.learningEnabled) && p.healthFixBase !== void 0;
|
|
353297
353395
|
return {
|
|
353298
353396
|
state: "idle",
|
|
353299
353397
|
actor: "human",
|
|
@@ -353401,8 +353499,8 @@ const predictTurn = (events) => {
|
|
|
353401
353499
|
state: baseline.state
|
|
353402
353500
|
};
|
|
353403
353501
|
}
|
|
353404
|
-
if (!payload.workingTreeClean && !payload.todoCommitted && !payload.architectureCommitted && !payload.packagesPresent && !payload.reviewPresent && !payload.feedbackPresent && !payload.errorsPresent && !payload.
|
|
353405
|
-
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";
|
|
353406
353504
|
return {
|
|
353407
353505
|
actor: "human",
|
|
353408
353506
|
subject: `gtd(human): ${entryGate}`,
|
|
@@ -353965,7 +354063,7 @@ var building_default = "<%~ include(\"@header\") %>\n\nBuild the package describ
|
|
|
353965
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";
|
|
353966
354064
|
//#endregion
|
|
353967
354065
|
//#region src/prompts/health-fixing.md
|
|
353968
|
-
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";
|
|
353969
354067
|
//#endregion
|
|
353970
354068
|
//#region src/prompts/agentic-review.md
|
|
353971
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";
|
|
@@ -365139,7 +365237,7 @@ const reviewWindowBase = (history) => {
|
|
|
365139
365237
|
const parsed = parseSubject(subjectOf(c.message));
|
|
365140
365238
|
if (parsed.kind === "routing" && parsed.phase === "reviewing" && parsed.param !== void 0) anchor = parsed.param;
|
|
365141
365239
|
if (parsed.kind === "routing" && parsed.phase === "awaiting-review") lastAwaitingReview = c.hash;
|
|
365142
|
-
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;
|
|
365143
365241
|
}
|
|
365144
365242
|
return anchor ?? lastAwaitingReview ?? firstGrilling;
|
|
365145
365243
|
};
|
|
@@ -365476,6 +365574,7 @@ const edgeActionHandlers = {
|
|
|
365476
365574
|
commitRouting: (a) => {
|
|
365477
365575
|
let msg = `commit routing as "${a.subject}"`;
|
|
365478
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)";
|
|
365479
365578
|
const removed = [];
|
|
365480
365579
|
if (a.removeArchitecture) removed.push(".gtd/ARCHITECTURE.md");
|
|
365481
365580
|
if (a.removeReview) removed.push(".gtd/REVIEW.md");
|