@try-works/dsh-recursive-mode 0.4.4 → 0.4.6
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 +15 -4
- package/lib/errors.d.ts +45 -0
- package/lib/goals-projection.d.ts +34 -4
- package/lib/index.js +783 -51
- package/lib/recursive_ask.tool.d.ts +137 -0
- package/lib/recursive_closeout.tool.d.ts +10 -0
- package/lib/recursive_init.tool.d.ts +23 -0
- package/lib/recursive_phase.tool.d.ts +8 -0
- package/lib/recursive_scratch.tool.d.ts +10 -0
- package/lib/recursive_worktree.tool.d.ts +16 -0
- package/lib/run-id.d.ts +62 -0
- package/lib/run-start.d.ts +55 -0
- package/lib/runtime.d.ts +137 -31
- package/package.json +1 -1
- package/scripts/test-recursive-mode-smoke.ts +5 -1
- package/src/errors.ts +45 -0
- package/src/goals-projection.ts +48 -7
- package/src/index.ts +9 -0
- package/src/recursive_ask.tool.ts +368 -18
- package/src/recursive_closeout.tool.ts +53 -35
- package/src/recursive_init.tool.ts +35 -4
- package/src/recursive_phase.tool.ts +22 -2
- package/src/recursive_scratch.tool.ts +17 -1
- package/src/recursive_worktree.tool.ts +27 -2
- package/src/run-id.ts +100 -0
- package/src/run-start.ts +129 -0
- package/src/runtime.ts +157 -14
package/README.md
CHANGED
|
@@ -179,7 +179,7 @@ control plane on disk is the only state it trusts across restarts.
|
|
|
179
179
|
| `recursive_audit_team` | Fan a phase out across roles (audit) |
|
|
180
180
|
| `recursive_review` | **Independent review** of the phase artifact, with a repair path |
|
|
181
181
|
| `recursive_delegate` | **Delegate the work of a phase** to a durable child; it produces, you judge |
|
|
182
|
-
| `recursive_ask` | Ask
|
|
182
|
+
| `recursive_ask` | Ask a **human** gate as a structured decision: `tdd-mode`, `qa-signoff`, `gate-block`, or `run-start` — the phase-0 approval that decides whether the run goal exists |
|
|
183
183
|
| `recursive_preview` | Preview what a tool would do, without doing it |
|
|
184
184
|
|
|
185
185
|
### 4.2 The command surface
|
|
@@ -405,9 +405,20 @@ prove. Three facts shape the picture:
|
|
|
405
405
|
the audit contract: `01-as-is`, `01.5-root-cause`, `02-to-be-plan`, `03-implementation-summary`,
|
|
406
406
|
`03.5-code-review`, `04-test-summary`, `06-decisions-update`, `07-state-update`, `08-memory-impact`.
|
|
407
407
|
`00-requirements`, `00-worktree` and `05-manual-qa` are not in it.
|
|
408
|
-
3. **`recursive_ask` is not a subagent tool.** It carries the workflow's
|
|
409
|
-
`
|
|
410
|
-
|
|
408
|
+
3. **`recursive_ask` is not a subagent tool.** It carries the workflow's **four human gates** —
|
|
409
|
+
`tdd-mode`, `qa-signoff` and `gate-block` (the list in `ASK_GATE_IDS`), plus **`run-start`**, the
|
|
410
|
+
phase-0 approval that decides whether a run's goal exists at all — as structured decisions rather
|
|
411
|
+
than prose, so the answer is validated and citeable.
|
|
412
|
+
|
|
413
|
+
`run-start` is unlike the other three: it is the only gate that asks the harness's **blocking
|
|
414
|
+
human channel**, and the only one that **arms a goal**. Creating a spec therefore creates no goal;
|
|
415
|
+
nothing runs unattended until a person approves it. When the mounted channel cannot deliver the
|
|
416
|
+
question, the refusal **names the channel's own cause** (`RM5503`) instead of asserting one — and a
|
|
417
|
+
composition that cannot render a card at all can still record the decision explicitly by passing
|
|
418
|
+
`answer` with `relay: true`, which is reported as relayed rather than as a person's selection. A
|
|
419
|
+
person's own answer always wins, and a question that was cancelled, aborted or timed out is **never**
|
|
420
|
+
relayable. A channel that *did* reach a person whose answer was not one of the offered labels is
|
|
421
|
+
`RM5504`, which is a different event from `RM5503`.
|
|
411
422
|
|
|
412
423
|
```mermaid
|
|
413
424
|
flowchart TB
|
package/lib/errors.d.ts
CHANGED
|
@@ -61,12 +61,30 @@ export declare const TOOL_ERRORS: {
|
|
|
61
61
|
readonly problem: "this gate has no default artifact, so one must be named";
|
|
62
62
|
readonly next: "pass artifact: <file> so the answer has somewhere durable to land";
|
|
63
63
|
};
|
|
64
|
+
readonly RELAY_ONLY_FOR_RUN_START: {
|
|
65
|
+
readonly code: "RM1150";
|
|
66
|
+
readonly klass: "input";
|
|
67
|
+
readonly problem: "relay applies only to the run-start gate, which is the only gate that asks the user-questions channel";
|
|
68
|
+
readonly next: "drop relay for this gate and answer it with one of its own labels, or call recursive_ask with gate: run-start when the decision is whether to start the run";
|
|
69
|
+
};
|
|
64
70
|
readonly MISSING_RUN_ID: {
|
|
65
71
|
readonly code: "RM1101";
|
|
66
72
|
readonly klass: "input";
|
|
67
73
|
readonly problem: "runId is required";
|
|
68
74
|
readonly next: "call recursive_status with no runId to see the latest run id in this workspace";
|
|
69
75
|
};
|
|
76
|
+
/**
|
|
77
|
+
* A run id is the NAME of the run directory and is joined onto the run layer
|
|
78
|
+
* as one path segment, so a path-shaped id is refused before any directory is
|
|
79
|
+
* created. See `run-id.ts` for the rule and for why it is not the runtime that
|
|
80
|
+
* learns to accept a path.
|
|
81
|
+
*/
|
|
82
|
+
readonly BAD_RUN_ID: {
|
|
83
|
+
readonly code: "RM1107";
|
|
84
|
+
readonly klass: "input";
|
|
85
|
+
readonly problem: "runId is not a single directory name";
|
|
86
|
+
readonly next: "pass the run directory name such as 03-something, then call recursive_init again";
|
|
87
|
+
};
|
|
70
88
|
readonly MISSING_ARTIFACT: {
|
|
71
89
|
readonly code: "RM1102";
|
|
72
90
|
readonly klass: "input";
|
|
@@ -133,6 +151,33 @@ export declare const TOOL_ERRORS: {
|
|
|
133
151
|
readonly problem: "the run has unresolved delegated work, so this phase cannot lock yet";
|
|
134
152
|
readonly next: "call recursive_status to see the pending delegation, have the child write its reply.md, then lock again";
|
|
135
153
|
};
|
|
154
|
+
readonly RUN_START_NO_CHANNEL: {
|
|
155
|
+
readonly code: "RM5502";
|
|
156
|
+
readonly klass: "runtime";
|
|
157
|
+
readonly problem: "the run-start gate needs an answer, and this composition mounts no user-questions channel to ask one directly";
|
|
158
|
+
readonly next: string;
|
|
159
|
+
};
|
|
160
|
+
/**
|
|
161
|
+
* ⚠ RM5503 USED TO LIE. Its text asserted "so no person was asked" for EVERY cause, because the caller
|
|
162
|
+
* that produced it had already thrown the cause away — and a live session showed the cost: the gate
|
|
163
|
+
* failed 22.9 s into the call with a reason nobody could see, and its `Next:` clause prescribed the very
|
|
164
|
+
* call that had just failed, so no route to start a run remained. The problem statement now claims only
|
|
165
|
+
* what the gate knows (no decision came back), and the cause travels in the `detail` the caller
|
|
166
|
+
* supplies. `RUN_START_ANSWER_UNUSABLE` carries the one case this entry must NOT cover: a person was
|
|
167
|
+
* reached and their answer was not an approval.
|
|
168
|
+
*/
|
|
169
|
+
readonly RUN_START_UNANSWERED: {
|
|
170
|
+
readonly code: "RM5503";
|
|
171
|
+
readonly klass: "runtime";
|
|
172
|
+
readonly problem: "the run-start question reached no decision: the mounted user-questions channel failed before a person answered it";
|
|
173
|
+
readonly next: string;
|
|
174
|
+
};
|
|
175
|
+
readonly RUN_START_ANSWER_UNUSABLE: {
|
|
176
|
+
readonly code: "RM5504";
|
|
177
|
+
readonly klass: "runtime";
|
|
178
|
+
readonly problem: "a person was asked to start this run and their answer was not one of the labels the run-start gate offered";
|
|
179
|
+
readonly next: "call recursive_ask again and have the person choose exactly \"Start run\" or \"Hold\"; a skipped or custom answer is not an approval, and no relayed answer can replace a decision the person made";
|
|
180
|
+
};
|
|
136
181
|
readonly RUNTIME_REFUSED: {
|
|
137
182
|
readonly code: "RM5501";
|
|
138
183
|
readonly klass: "runtime";
|
|
@@ -81,12 +81,42 @@ export declare function isRunGoal(goal: GoalViewLike | undefined, runId: string)
|
|
|
81
81
|
* Sync a run's durable goal to the requested phase. Safe: never touches a goal
|
|
82
82
|
* whose objective is not this run's marker, and never re-creates over a
|
|
83
83
|
* non-complete foreign goal.
|
|
84
|
+
*
|
|
85
|
+
* ⚠ `approved` IS THE PHASE-0 GATE, and it defaults to the SAFE direction. A goal is not a label:
|
|
86
|
+
* `create` returns an ARMED view and the harness starts driving autonomous goal rounds for the
|
|
87
|
+
* session, so creating one is starting the run. The owner's rule is that phase 0 requires explicit
|
|
88
|
+
* approval, which means the projection must be unable to arm anything on its own — hence a default of
|
|
89
|
+
* `false` and an explicit refusal in EVERY branch that would call `create`, including the two
|
|
90
|
+
* replace-a-completed-goal branches (an unapproved run cannot have reached `complete`, but "cannot
|
|
91
|
+
* happen" is what the single unguarded branch relied on too).
|
|
92
|
+
*
|
|
93
|
+
* ⚠ AND IT IS REACHED ON ORDINARY WORK, so the unapproved path is QUIET AND IDEMPOTENT: no goal is
|
|
94
|
+
* created, nothing is written, no error is thrown, and the run's artifacts are untouched. The caller
|
|
95
|
+
* reads {@link RUN_START_NOT_APPROVED} to tell "this run has not been started yet" apart from a real
|
|
96
|
+
* failure, so a normal phase step never surfaces a warning.
|
|
97
|
+
*
|
|
98
|
+
* `approved` is passed IN rather than read here because this module is pure: it takes the goal service
|
|
99
|
+
* seam and nothing else, and the plugin's own filesystem reads live in the runtime (see
|
|
100
|
+
* `RecursiveRuntime.readRunStartApproval`).
|
|
101
|
+
*/
|
|
102
|
+
export declare function syncRunGoal(service: GoalServiceLike | undefined | null, agent: AgentHandle, runId: string, runState: RunState, approved?: boolean): SyncResult;
|
|
103
|
+
/**
|
|
104
|
+
* Block the current run goal (used on a gate-block). Never touches a foreign goal.
|
|
105
|
+
*
|
|
106
|
+
* ⚠ A RUN THAT WAS NEVER STARTED HAS NO GOAL TO BLOCK, so this reports the unapproved state in the
|
|
107
|
+
* same words as {@link syncRunGoal} rather than "no current goal to block": the caller's question is
|
|
108
|
+
* "why is there no goal", and the answer must not depend on which entry point happened to ask.
|
|
84
109
|
*/
|
|
85
|
-
export declare function syncRunGoal(service: GoalServiceLike | undefined | null, agent: AgentHandle, runId: string, runState: RunState): SyncResult;
|
|
86
|
-
/** Block the current run goal (used on a gate-block). Never touches a foreign goal. */
|
|
87
110
|
export declare function blockRunGoal(service: GoalServiceLike | undefined | null, agent: AgentHandle, runId: string, reason: {
|
|
88
111
|
code: string;
|
|
89
112
|
message: string;
|
|
90
113
|
}): SyncResult;
|
|
91
|
-
/**
|
|
92
|
-
|
|
114
|
+
/**
|
|
115
|
+
* Bridge a run's blocked goal back to active (used on a reopen).
|
|
116
|
+
*
|
|
117
|
+
* ⚠ REOPEN IS NOT A BACK DOOR TO STARTING A RUN. It routes through {@link syncRunGoal}, so a reopen of
|
|
118
|
+
* an unapproved run cannot create the goal that init deliberately withheld. An APPROVED run is
|
|
119
|
+
* unaffected: its approval outlives the reopen, because the approval is a durable line in the run's
|
|
120
|
+
* own Phase 0 artifact rather than a value held in memory (verified in `tests/run-start-approval.spec.ts`).
|
|
121
|
+
*/
|
|
122
|
+
export declare function resumeRunGoal(service: GoalServiceLike | undefined | null, agent: AgentHandle, runId: string, approved?: boolean): SyncResult;
|