dorfl 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/advance-drivers.d.ts +1 -1
- package/dist/advance-drivers.d.ts.map +1 -1
- package/dist/advance-drivers.js +15 -3
- package/dist/advance-drivers.js.map +1 -1
- package/dist/advance-lifecycle-template.d.ts.map +1 -1
- package/dist/advance-lifecycle-template.js +76 -4
- package/dist/advance-lifecycle-template.js.map +1 -1
- package/dist/advance-treeless-publish.d.ts +23 -0
- package/dist/advance-treeless-publish.d.ts.map +1 -1
- package/dist/advance-treeless-publish.js +41 -0
- package/dist/advance-treeless-publish.js.map +1 -1
- package/dist/advance.d.ts +59 -7
- package/dist/advance.d.ts.map +1 -1
- package/dist/advance.js +389 -82
- package/dist/advance.js.map +1 -1
- package/dist/advancing-lock.d.ts +30 -2
- package/dist/advancing-lock.d.ts.map +1 -1
- package/dist/advancing-lock.js +50 -0
- package/dist/advancing-lock.js.map +1 -1
- package/dist/agent-launch.d.ts +12 -0
- package/dist/agent-launch.d.ts.map +1 -1
- package/dist/agent-launch.js +22 -12
- package/dist/agent-launch.js.map +1 -1
- package/dist/agent-stop.d.ts +40 -2
- package/dist/agent-stop.d.ts.map +1 -1
- package/dist/agent-stop.js +30 -2
- package/dist/agent-stop.js.map +1 -1
- package/dist/apply-decide.d.ts +17 -3
- package/dist/apply-decide.d.ts.map +1 -1
- package/dist/apply-decide.js +36 -7
- package/dist/apply-decide.js.map +1 -1
- package/dist/apply-merge-action.d.ts +17 -5
- package/dist/apply-merge-action.d.ts.map +1 -1
- package/dist/apply-merge-action.js +43 -8
- package/dist/apply-merge-action.js.map +1 -1
- package/dist/apply-persist.d.ts +57 -26
- package/dist/apply-persist.d.ts.map +1 -1
- package/dist/apply-persist.js +166 -37
- package/dist/apply-persist.js.map +1 -1
- package/dist/apply-stuck-action.d.ts +151 -0
- package/dist/apply-stuck-action.d.ts.map +1 -0
- package/dist/apply-stuck-action.js +125 -0
- package/dist/apply-stuck-action.js.map +1 -0
- package/dist/brand.d.ts +12 -1
- package/dist/brand.d.ts.map +1 -1
- package/dist/brand.js +2 -1
- package/dist/brand.js.map +1 -1
- package/dist/cli-spinner.d.ts +1 -1
- package/dist/cli-spinner.js +1 -1
- package/dist/cli.d.ts +10 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +320 -63
- package/dist/cli.js.map +1 -1
- package/dist/close-job-template.js +1 -1
- package/dist/complete.d.ts +6 -3
- package/dist/complete.d.ts.map +1 -1
- package/dist/complete.js +91 -37
- package/dist/complete.js.map +1 -1
- package/dist/config.d.ts +55 -9
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +56 -4
- package/dist/config.js.map +1 -1
- package/dist/continue-branch.d.ts.map +1 -1
- package/dist/continue-branch.js +22 -0
- package/dist/continue-branch.js.map +1 -1
- package/dist/decision-engine.d.ts +38 -10
- package/dist/decision-engine.d.ts.map +1 -1
- package/dist/decision-engine.js +10 -6
- package/dist/decision-engine.js.map +1 -1
- package/dist/do-autopick.d.ts.map +1 -1
- package/dist/do-autopick.js +11 -1
- package/dist/do-autopick.js.map +1 -1
- package/dist/do-config.d.ts +1 -1
- package/dist/do.d.ts +49 -56
- package/dist/do.d.ts.map +1 -1
- package/dist/do.js +419 -30
- package/dist/do.js.map +1 -1
- package/dist/env-config.d.ts.map +1 -1
- package/dist/env-config.js +12 -4
- package/dist/env-config.js.map +1 -1
- package/dist/failure-cause.d.ts +3 -2
- package/dist/failure-cause.d.ts.map +1 -1
- package/dist/failure-cause.js +28 -1
- package/dist/failure-cause.js.map +1 -1
- package/dist/format.d.ts +6 -6
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +10 -27
- package/dist/format.js.map +1 -1
- package/dist/frontmatter.d.ts +16 -4
- package/dist/frontmatter.d.ts.map +1 -1
- package/dist/frontmatter.js +27 -1
- package/dist/frontmatter.js.map +1 -1
- package/dist/gc.d.ts +65 -6
- package/dist/gc.d.ts.map +1 -1
- package/dist/gc.js +126 -9
- package/dist/gc.js.map +1 -1
- package/dist/github.d.ts +14 -0
- package/dist/github.d.ts.map +1 -1
- package/dist/github.js +73 -0
- package/dist/github.js.map +1 -1
- package/dist/harness.d.ts +25 -0
- package/dist/harness.d.ts.map +1 -1
- package/dist/harness.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/install-ci-branch-protection.d.ts +132 -38
- package/dist/install-ci-branch-protection.d.ts.map +1 -1
- package/dist/install-ci-branch-protection.js +190 -48
- package/dist/install-ci-branch-protection.js.map +1 -1
- package/dist/install-ci-core.d.ts +21 -1
- package/dist/install-ci-core.d.ts.map +1 -1
- package/dist/install-ci-core.js +10 -3
- package/dist/install-ci-core.js.map +1 -1
- package/dist/install-ci-github.d.ts +32 -0
- package/dist/install-ci-github.d.ts.map +1 -1
- package/dist/install-ci-github.js +73 -0
- package/dist/install-ci-github.js.map +1 -1
- package/dist/install-skills.d.ts +123 -0
- package/dist/install-skills.d.ts.map +1 -0
- package/dist/install-skills.js +100 -0
- package/dist/install-skills.js.map +1 -0
- package/dist/intake-trigger-template.js +1 -1
- package/dist/intake.d.ts +17 -4
- package/dist/intake.d.ts.map +1 -1
- package/dist/intake.js +23 -7
- package/dist/intake.js.map +1 -1
- package/dist/integration-core.d.ts +10 -7
- package/dist/integration-core.d.ts.map +1 -1
- package/dist/integration-core.js +174 -60
- package/dist/integration-core.js.map +1 -1
- package/dist/integrator.d.ts +1 -1
- package/dist/integrator.d.ts.map +1 -1
- package/dist/integrator.js +19 -3
- package/dist/integrator.js.map +1 -1
- package/dist/isolation.d.ts +2 -2
- package/dist/isolation.d.ts.map +1 -1
- package/dist/isolation.js +9 -0
- package/dist/isolation.js.map +1 -1
- package/dist/item-lock.d.ts +196 -129
- package/dist/item-lock.d.ts.map +1 -1
- package/dist/item-lock.js +329 -254
- package/dist/item-lock.js.map +1 -1
- package/dist/ledger-write.d.ts +31 -26
- package/dist/ledger-write.d.ts.map +1 -1
- package/dist/ledger-write.js +100 -122
- package/dist/ledger-write.js.map +1 -1
- package/dist/lifecycle-gather.d.ts +17 -0
- package/dist/lifecycle-gather.d.ts.map +1 -1
- package/dist/lifecycle-gather.js +7 -1
- package/dist/lifecycle-gather.js.map +1 -1
- package/dist/lifecycle-pools.d.ts +40 -3
- package/dist/lifecycle-pools.d.ts.map +1 -1
- package/dist/lifecycle-pools.js +26 -6
- package/dist/lifecycle-pools.js.map +1 -1
- package/dist/merge-question-surfacer.d.ts +15 -1
- package/dist/merge-question-surfacer.d.ts.map +1 -1
- package/dist/merge-question-surfacer.js +18 -3
- package/dist/merge-question-surfacer.js.map +1 -1
- package/dist/migrate-stuck-locks.d.ts +129 -0
- package/dist/migrate-stuck-locks.d.ts.map +1 -0
- package/dist/migrate-stuck-locks.js +355 -0
- package/dist/migrate-stuck-locks.js.map +1 -0
- package/dist/needs-attention.d.ts +293 -16
- package/dist/needs-attention.d.ts.map +1 -1
- package/dist/needs-attention.js +532 -57
- package/dist/needs-attention.js.map +1 -1
- package/dist/orphan-sidecar.d.ts +8 -4
- package/dist/orphan-sidecar.d.ts.map +1 -1
- package/dist/orphan-sidecar.js +35 -2
- package/dist/orphan-sidecar.js.map +1 -1
- package/dist/pi-harness.d.ts +16 -0
- package/dist/pi-harness.d.ts.map +1 -1
- package/dist/pi-harness.js +82 -2
- package/dist/pi-harness.js.map +1 -1
- package/dist/placement.d.ts +3 -3
- package/dist/placement.js +1 -1
- package/dist/prd-to-spec.d.ts.map +1 -1
- package/dist/prd-to-spec.js +9 -5
- package/dist/prd-to-spec.js.map +1 -1
- package/dist/protocol/CLAIM-PROTOCOL.md +17 -10
- package/dist/protocol/REVIEW-PROTOCOL.md +4 -1
- package/dist/protocol/SURFACE-PROTOCOL.md +16 -2
- package/dist/protocol/TASKING-PROTOCOL.md +3 -1
- package/dist/protocol/WORK-CONTRACT.md +22 -18
- package/dist/protocol/task-template.md +1 -1
- package/dist/reap-branches.d.ts +12 -9
- package/dist/reap-branches.d.ts.map +1 -1
- package/dist/reap-branches.js +25 -7
- package/dist/reap-branches.js.map +1 -1
- package/dist/recover-isolated.d.ts +12 -0
- package/dist/recover-isolated.d.ts.map +1 -1
- package/dist/recover-isolated.js +5 -0
- package/dist/recover-isolated.js.map +1 -1
- package/dist/repo-config.d.ts +23 -2
- package/dist/repo-config.d.ts.map +1 -1
- package/dist/repo-config.js +53 -7
- package/dist/repo-config.js.map +1 -1
- package/dist/repo-mirror.d.ts.map +1 -1
- package/dist/repo-mirror.js +18 -2
- package/dist/repo-mirror.js.map +1 -1
- package/dist/review-verdict.js +1 -1
- package/dist/review-verdict.js.map +1 -1
- package/dist/run.d.ts +1 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +54 -19
- package/dist/run.js.map +1 -1
- package/dist/scan.d.ts +17 -8
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +22 -8
- package/dist/scan.js.map +1 -1
- package/dist/select-priority.d.ts +2 -2
- package/dist/select-priority.js +1 -1
- package/dist/select-priority.js.map +1 -1
- package/dist/sidecar-apply.js +1 -1
- package/dist/sidecar-apply.js.map +1 -1
- package/dist/sidecar.d.ts +45 -10
- package/dist/sidecar.d.ts.map +1 -1
- package/dist/sidecar.js +82 -2
- package/dist/sidecar.js.map +1 -1
- package/dist/skills/answer-questions/SKILL.md +89 -0
- package/dist/skills/capture-signal/SKILL.md +52 -0
- package/dist/skills/convert-from-prd-to-spec/SKILL.md +90 -0
- package/dist/skills/drive-tasks/SKILL.md +218 -0
- package/dist/skills/from-idea/SKILL.md +83 -0
- package/dist/skills/merge-prs/SKILL.md +70 -0
- package/dist/skills/orchestrate/SKILL.md +101 -0
- package/dist/skills/promote/SKILL.md +35 -0
- package/dist/skills/review/SKILL.md +16 -0
- package/dist/skills/setup/SKILL.md +258 -0
- package/dist/skills/setup/protocol/ADR-FORMAT.md +47 -0
- package/dist/skills/setup/protocol/CLAIM-PROTOCOL.md +224 -0
- package/dist/skills/setup/protocol/REVIEW-PROTOCOL.md +122 -0
- package/dist/skills/setup/protocol/SURFACE-PROTOCOL.md +135 -0
- package/dist/skills/setup/protocol/TASKING-PROTOCOL.md +124 -0
- package/dist/skills/setup/protocol/WORK-CONTRACT.md +280 -0
- package/dist/skills/setup/protocol/spec-template.md +71 -0
- package/dist/skills/setup/protocol/task-template.md +65 -0
- package/dist/skills/surface-questions/SKILL.md +16 -0
- package/dist/skills/to-spec/SKILL.md +34 -0
- package/dist/skills/to-task/SKILL.md +19 -0
- package/dist/skills/triage-observations/SKILL.md +78 -0
- package/dist/skills/work/SKILL.md +51 -0
- package/dist/slug-namespace.d.ts +5 -5
- package/dist/slug-namespace.js +5 -5
- package/dist/slug-namespace.js.map +1 -1
- package/dist/spec-complete.d.ts.map +1 -1
- package/dist/spec-complete.js +2 -3
- package/dist/spec-complete.js.map +1 -1
- package/dist/start.d.ts.map +1 -1
- package/dist/start.js +51 -57
- package/dist/start.js.map +1 -1
- package/dist/surface-gate.d.ts.map +1 -1
- package/dist/surface-gate.js +10 -3
- package/dist/surface-gate.js.map +1 -1
- package/dist/surface-persist.js +1 -1
- package/dist/surface-persist.js.map +1 -1
- package/dist/tasker-review-loop.d.ts +4 -3
- package/dist/tasker-review-loop.d.ts.map +1 -1
- package/dist/tasker-review-loop.js.map +1 -1
- package/dist/tasking-lock.d.ts +2 -2
- package/dist/tasking-lock.d.ts.map +1 -1
- package/dist/tasking-lock.js +53 -32
- package/dist/tasking-lock.js.map +1 -1
- package/dist/tasking.d.ts +40 -6
- package/dist/tasking.d.ts.map +1 -1
- package/dist/tasking.js +224 -12
- package/dist/tasking.js.map +1 -1
- package/dist/triage-persist.d.ts +10 -6
- package/dist/triage-persist.d.ts.map +1 -1
- package/dist/triage-persist.js +34 -4
- package/dist/triage-persist.js.map +1 -1
- package/dist/vendor/incur/agents.d.ts +58 -0
- package/dist/vendor/incur/agents.d.ts.map +1 -0
- package/dist/vendor/incur/agents.js +343 -0
- package/dist/vendor/incur/agents.js.map +1 -0
- package/dist/verify-workflow-template.js +1 -1
- package/dist/watch-session.d.ts +11 -3
- package/dist/watch-session.d.ts.map +1 -1
- package/dist/watch-session.js +94 -7
- package/dist/watch-session.js.map +1 -1
- package/dist/work-layout.d.ts +7 -3
- package/dist/work-layout.d.ts.map +1 -1
- package/dist/work-layout.js +6 -3
- package/dist/work-layout.js.map +1 -1
- package/dist/workspace.d.ts +1 -1
- package/package.json +2 -2
- package/src/advance-drivers.ts +16 -3
- package/src/advance-lifecycle-template.ts +89 -4
- package/src/advance-treeless-publish.ts +45 -0
- package/src/advance.ts +467 -87
- package/src/advancing-lock.ts +96 -1
- package/src/agent-launch.ts +37 -12
- package/src/agent-stop.ts +60 -2
- package/src/apply-decide.ts +36 -7
- package/src/apply-merge-action.ts +45 -7
- package/src/apply-persist.ts +228 -54
- package/src/apply-stuck-action.ts +260 -0
- package/src/brand.ts +14 -2
- package/src/cli-spinner.ts +1 -1
- package/src/cli.ts +401 -70
- package/src/close-job-template.ts +1 -1
- package/src/complete.ts +110 -52
- package/src/config.ts +107 -13
- package/src/continue-branch.ts +22 -0
- package/src/decision-engine.ts +55 -17
- package/src/do-autopick.ts +12 -1
- package/src/do-config.ts +1 -1
- package/src/do.ts +511 -38
- package/src/env-config.ts +12 -4
- package/src/failure-cause.ts +30 -1
- package/src/format.ts +10 -30
- package/src/frontmatter.ts +35 -5
- package/src/gc.ts +172 -9
- package/src/github.ts +78 -0
- package/src/harness.ts +25 -0
- package/src/index.ts +10 -0
- package/src/install-ci-branch-protection.ts +282 -57
- package/src/install-ci-core.ts +30 -3
- package/src/install-ci-github.ts +86 -0
- package/src/install-skills.ts +166 -0
- package/src/intake-trigger-template.ts +1 -1
- package/src/intake.ts +46 -11
- package/src/integration-core.ts +199 -65
- package/src/integrator.ts +22 -9
- package/src/isolation.ts +11 -2
- package/src/item-lock.ts +485 -341
- package/src/ledger-write.ts +135 -150
- package/src/lifecycle-gather.ts +29 -6
- package/src/lifecycle-pools.ts +67 -9
- package/src/merge-question-surfacer.ts +28 -4
- package/src/migrate-stuck-locks.ts +451 -0
- package/src/needs-attention.ts +857 -84
- package/src/orphan-sidecar.ts +47 -6
- package/src/pi-harness.ts +82 -2
- package/src/placement.ts +3 -3
- package/src/prd-to-spec.ts +13 -5
- package/src/reap-branches.ts +38 -14
- package/src/recover-isolated.ts +17 -0
- package/src/repo-config.ts +55 -6
- package/src/repo-mirror.ts +23 -1
- package/src/review-verdict.ts +1 -1
- package/src/run.ts +64 -18
- package/src/scan.ts +22 -8
- package/src/select-priority.ts +3 -3
- package/src/sidecar-apply.ts +1 -1
- package/src/sidecar.ts +138 -11
- package/src/slug-namespace.ts +7 -7
- package/src/spec-complete.ts +2 -3
- package/src/start.ts +53 -59
- package/src/surface-gate.ts +10 -3
- package/src/surface-persist.ts +1 -1
- package/src/tasker-review-loop.ts +4 -3
- package/src/tasking-lock.ts +52 -36
- package/src/tasking.ts +261 -17
- package/src/triage-persist.ts +56 -12
- package/src/vendor/incur/LICENSE +21 -0
- package/src/vendor/incur/README.md +19 -0
- package/src/vendor/incur/agents.ts +392 -0
- package/src/verify-workflow-template.ts +1 -1
- package/src/watch-session.ts +102 -7
- package/src/work-layout.ts +6 -3
- package/src/workspace.ts +1 -1
package/src/advance.ts
CHANGED
|
@@ -66,6 +66,12 @@ import {
|
|
|
66
66
|
type MergeActionHandler,
|
|
67
67
|
type MergeActionResult,
|
|
68
68
|
} from './apply-merge-action.js';
|
|
69
|
+
import {
|
|
70
|
+
detectAnsweredStuckAction,
|
|
71
|
+
performStuckAction,
|
|
72
|
+
type StuckActionHandler,
|
|
73
|
+
type StuckActionResult,
|
|
74
|
+
} from './apply-stuck-action.js';
|
|
69
75
|
import type {VerifyConfig} from './verify.js';
|
|
70
76
|
import type {NewQuestion} from './sidecar.js';
|
|
71
77
|
|
|
@@ -131,11 +137,34 @@ export type AdvanceOutcome =
|
|
|
131
137
|
| 'advanced'
|
|
132
138
|
| 'no-op'
|
|
133
139
|
| 'vanished'
|
|
140
|
+
/**
|
|
141
|
+
* The triage/apply promote leg found the task it would mint ALREADY EXISTS on
|
|
142
|
+
* the arbiter AND was PROVABLY minted from THIS observation in a prior run (its
|
|
143
|
+
* `promotedFrom:` back-reference matches) — an idempotency fact, so the source was
|
|
144
|
+
* already triaged. `exitCode: 0` (the matrix tolerates it, so it does NOT red CI),
|
|
145
|
+
* DISTINGUISHABLE from `vanished` (item file gone) and `no-op` (calm classify) so
|
|
146
|
+
* reviewers can grep it. The LOUD `lost` (exit 2) is reserved for a genuine
|
|
147
|
+
* concurrent-create race with an UNRELATED same-path item, where a retry helps.
|
|
148
|
+
* (task `observation-triage-already-triaged-benign-skip`; sibling of the
|
|
149
|
+
* stale-snapshot / held-lock CI-noise fixes.)
|
|
150
|
+
*/
|
|
151
|
+
| 'already-triaged'
|
|
134
152
|
| 'usage-error'
|
|
135
153
|
| 'lost'
|
|
136
154
|
| 'contended'
|
|
137
155
|
| 'not-implemented'
|
|
138
|
-
| 'invariant-violation'
|
|
156
|
+
| 'invariant-violation'
|
|
157
|
+
/**
|
|
158
|
+
* The answered-merge dispatcher REFUSED the land on the rebased tip (RED
|
|
159
|
+
* re-verify, rebase conflict, or a pre-checkout failure). Distinct from
|
|
160
|
+
* `usage-error` (which is reserved for genuine caller-usage errors — e.g.
|
|
161
|
+
* the workspacesDir-unset guard below). `performIntegration` has already
|
|
162
|
+
* routed the item to needs-attention via its shared seam, so `main` never
|
|
163
|
+
* received a failing tree; the sidecar is LEFT IN PLACE so the open answer
|
|
164
|
+
* stays surfaced for a human follow-up. `exitCode: 1`.
|
|
165
|
+
* (task `merge-action-nits-followup` nit 2.)
|
|
166
|
+
*/
|
|
167
|
+
| 'merge-refused';
|
|
139
168
|
|
|
140
169
|
/** Maps onto the claim-CAS exit codes (identical semantics). */
|
|
141
170
|
export type AdvanceExitCode = 0 | 1 | 2 | 3;
|
|
@@ -166,7 +195,7 @@ export interface RungExecutor {
|
|
|
166
195
|
export interface RungExecInput {
|
|
167
196
|
/** The canonical namespaced identity (`task:<slug>` / `spec:<slug>` / `observation:<slug>`). */
|
|
168
197
|
item: string;
|
|
169
|
-
/** The resolved namespace (`task` / `
|
|
198
|
+
/** The resolved namespace (`task` / `spec` / `observation`). */
|
|
170
199
|
namespace: SlugNamespace;
|
|
171
200
|
/** The bare slug. */
|
|
172
201
|
slug: string;
|
|
@@ -250,8 +279,8 @@ export interface AdvanceContext {
|
|
|
250
279
|
* The AGENTIC apply DECISION seam (task
|
|
251
280
|
* `agentic-apply-retire-disposition-vocabulary`): the fresh-context decision
|
|
252
281
|
* agent the apply rung runs on a fully-answered OBSERVATION to choose what to DO
|
|
253
|
-
* with the signal (`mint-task | mint-spec | mint-adr |
|
|
254
|
-
* ask-follow-up`),
|
|
282
|
+
* with the signal (`mint-task | mint-spec | mint-adr | dispose-source |
|
|
283
|
+
* resolve-no-mint | ask-follow-up`),
|
|
255
284
|
* grounded in the source's full context. It is the injected
|
|
256
285
|
* {@link ApplyDecider} the shared `decide(input, allowedOutcomes)` engine runs;
|
|
257
286
|
* tests inject a CANNED verdict (no model). `undefined` ⇒ the apply rung defaults
|
|
@@ -322,9 +351,13 @@ export interface AdvanceContext {
|
|
|
322
351
|
* The execution working area (`workspacesDir`, default `~/.dorfl`) the
|
|
323
352
|
* answered-merge LAND uses to cut a per-job worktree from the hub mirror
|
|
324
353
|
* (via `workspace.ts` `createJob`). Unset ⇒ no `workspacesDir` is available
|
|
325
|
-
* to the dispatcher, so an answered `kind: merge` entry is REFUSED
|
|
326
|
-
*
|
|
327
|
-
*
|
|
354
|
+
* to the dispatcher, so an answered `kind: merge` entry is REFUSED as a
|
|
355
|
+
* genuine caller-usage error (outcome `usage-error`, `exitCode: 1`) and the
|
|
356
|
+
* answer stays surfaced for a follow-up — threading `workspacesDir` is the
|
|
357
|
+
* caller's contract, and forgetting it gets this documented clean refusal
|
|
358
|
+
* rather than a mysterious downstream failure (task
|
|
359
|
+
* `merge-action-nits-followup` nit 3). The registry-set advance driver
|
|
360
|
+
* threads the resolved `workspacesDir` here.
|
|
328
361
|
* (spec `land-time-reverify-and-parallel-merge-ceiling`, task
|
|
329
362
|
* `apply-rung-merge-disposition`)
|
|
330
363
|
*/
|
|
@@ -370,6 +403,20 @@ export interface AdvanceContext {
|
|
|
370
403
|
* WITHOUT spinning up a hub mirror or running real verify.
|
|
371
404
|
*/
|
|
372
405
|
mergeAction?: MergeActionHandler;
|
|
406
|
+
/**
|
|
407
|
+
* The stuck-action dispatch SEAM (task
|
|
408
|
+
* `apply-resolve-reset-flag-discards-work-branch`, spec
|
|
409
|
+
* `surface-stuck-as-questions-and-retire-stuck-lock-state`): the
|
|
410
|
+
* deterministic answer-driven runner-action handler the apply rung invokes
|
|
411
|
+
* BEFORE the fall-through persist when an answered `kind: 'stuck'` entry
|
|
412
|
+
* (the shape the bounce-surface path stamps) is detected. Production wires
|
|
413
|
+
* {@link performStuckAction} (drives the shared
|
|
414
|
+
* `deleteRemoteWorkBranchIfPresent` primitive on `reset`); tests inject a
|
|
415
|
+
* stub so they assert on the apply-rung's `keep | reset | refused | cancel`
|
|
416
|
+
* routing WITHOUT touching a real arbiter. The deterministic sibling of
|
|
417
|
+
* {@link mergeAction} for the `kind: 'stuck'` axis.
|
|
418
|
+
*/
|
|
419
|
+
stuckAction?: StuckActionHandler;
|
|
373
420
|
/** Sink for human-readable progress notes. */
|
|
374
421
|
note?: (message: string) => void;
|
|
375
422
|
}
|
|
@@ -602,7 +649,10 @@ function mapDoOutcome(result: DoResult): AdvanceOutcome {
|
|
|
602
649
|
* answered sidecar back to not-all-answered (the persist owns that). An EMPTY
|
|
603
650
|
* emit (the skill's honest "no open judgement") writes nothing and reports it.
|
|
604
651
|
*/
|
|
605
|
-
async function surfaceRung(
|
|
652
|
+
async function surfaceRung(
|
|
653
|
+
input: RungExecInput,
|
|
654
|
+
surfaceOpts: {baseQuestions?: NewQuestion[]} = {},
|
|
655
|
+
): Promise<RungExecResult> {
|
|
606
656
|
const {item, context} = input;
|
|
607
657
|
const note = context.note ?? (() => {});
|
|
608
658
|
const cwd = context.cwd;
|
|
@@ -615,50 +665,84 @@ async function surfaceRung(input: RungExecInput): Promise<RungExecResult> {
|
|
|
615
665
|
return vanishedSkip({rung: 'surface', item});
|
|
616
666
|
}
|
|
617
667
|
|
|
668
|
+
// DETERMINISTIC BASE QUESTIONS (the triage rung's "always ask" contract): the
|
|
669
|
+
// caller may pass engine-built questions that MUST be surfaced regardless of the
|
|
670
|
+
// agent. They are added ONLY on the FIRST pass (no sidecar yet) — on a re-surface
|
|
671
|
+
// the sidecar already carries them, and `appendQuestions` does NOT dedup, so
|
|
672
|
+
// re-adding would duplicate. When base questions are present the agent is
|
|
673
|
+
// ADDITIVE ONLY and its flake/empty is NON-FATAL (the base question still lands).
|
|
674
|
+
const baseQuestions = surfaceOpts.baseQuestions ?? [];
|
|
675
|
+
const hasBase = baseQuestions.length > 0;
|
|
676
|
+
const sidecarExists = existsSync(join(cwd, sidecarPathFor(item)));
|
|
677
|
+
const baseToAdd = hasBase && !sidecarExists ? baseQuestions : [];
|
|
678
|
+
|
|
618
679
|
// 1. SPAWN the fresh-context `surface-questions` agent (the skill JUDGES). The
|
|
619
680
|
// expensive model work is POST-lock (the lock is held by `performAdvance`).
|
|
681
|
+
//
|
|
682
|
+
// SHORT-CIRCUIT (primary/load-bearing half of task
|
|
683
|
+
// `surface-short-circuit-already-triaged-observations-and-harden-skill-empty-emit`):
|
|
684
|
+
// an OBSERVATION with provably no open judgement (frontmatter `needsAnswers` NOT
|
|
685
|
+
// true, no non-empty `## Open questions` section, no pending sidecar) — the
|
|
686
|
+
// typical shape of a decision-record / already-triaged note — deterministically
|
|
687
|
+
// yields `{questions: []}` WITHOUT round-tripping the flaky surface-questions
|
|
688
|
+
// agent (source observation
|
|
689
|
+
// `surface-questions-agent-still-emits-no-parseable-questions-on-decision-record-obs-2026-07-10`).
|
|
690
|
+
// The loud-error contract from
|
|
691
|
+
// `advance-surface-limbo-observation-loudly-instead-of-silent-no-op` is
|
|
692
|
+
// PRESERVED for the observations that DO reach the agent — this only spares the
|
|
693
|
+
// ones that provably have nothing to ask. Conservative: fires only on
|
|
694
|
+
// observations; tasks/specs still always ask.
|
|
620
695
|
const gate = context.surfaceGate ?? harnessSurfaceGate();
|
|
621
696
|
let emit;
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
item,
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
}
|
|
697
|
+
if (isNothingToSurfaceObservation(cwd, input, itemPath, sidecarExists)) {
|
|
698
|
+
note(
|
|
699
|
+
`surface ${item}: auto-triaged (no open questions, no sidecar) — skipped agent.`,
|
|
700
|
+
);
|
|
701
|
+
emit = {questions: []};
|
|
702
|
+
} else {
|
|
703
|
+
try {
|
|
704
|
+
emit = await gate({
|
|
705
|
+
item,
|
|
706
|
+
cwd,
|
|
707
|
+
surfaceModel: context.surfaceModel,
|
|
708
|
+
});
|
|
709
|
+
} catch (err) {
|
|
710
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
711
|
+
if (!hasBase) {
|
|
712
|
+
return {
|
|
713
|
+
exitCode: 1,
|
|
714
|
+
outcome: 'usage-error',
|
|
715
|
+
message: `surface ${item}: the surface-questions agent produced no usable emit (${detail}).`,
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
// The agent flaked but we have a deterministic base question to surface: the
|
|
719
|
+
// flake is NON-FATAL. Treat the agent's extras as empty and carry on (the
|
|
720
|
+
// base triage question still lands, so the human can triage via the sidecar).
|
|
721
|
+
note(
|
|
722
|
+
`surface ${item}: the surface-questions agent produced no usable emit ` +
|
|
723
|
+
`(${detail}); surfacing the deterministic question(s) only.`,
|
|
724
|
+
);
|
|
725
|
+
emit = {questions: []};
|
|
726
|
+
}
|
|
635
727
|
}
|
|
636
728
|
|
|
637
729
|
// 2. The ENGINE persists (the skill wrote nothing): append-or-create the sidecar
|
|
638
730
|
// + set `needsAnswers:true` in ONE commit (CAS-atomic under the held lock).
|
|
731
|
+
// Base questions FIRST (q1…), then the agent's additive extras.
|
|
639
732
|
const persist = context.surfacePersist ?? persistSurfacedQuestions;
|
|
640
733
|
const result = persist({
|
|
641
734
|
cwd,
|
|
642
735
|
item,
|
|
643
736
|
itemPath,
|
|
644
|
-
questions: toNewQuestions(emit),
|
|
737
|
+
questions: [...baseToAdd, ...toNewQuestions(emit)],
|
|
645
738
|
note,
|
|
646
739
|
});
|
|
647
740
|
if (result.outcome === 'nothing') {
|
|
648
|
-
//
|
|
649
|
-
//
|
|
650
|
-
//
|
|
651
|
-
//
|
|
652
|
-
//
|
|
653
|
-
// the promote path only from an answered `disposition: promote` SIDECAR
|
|
654
|
-
// (`triage-persist.ts`). Without either, the item is untriaged (re-enumerated
|
|
655
|
-
// every tick), un-surfaceable (nothing to ask), un-promotable (no sidecar) —
|
|
656
|
-
// a silent exit-0 no-op forever. Surface it LOUDLY instead.
|
|
657
|
-
const limbo = detectObservationLimbo(input);
|
|
658
|
-
if (limbo !== undefined) {
|
|
659
|
-
note(limbo);
|
|
660
|
-
return {exitCode: 1, outcome: 'usage-error', message: limbo};
|
|
661
|
-
}
|
|
741
|
+
// The agent had nothing to ask AND there was no base question to add (a
|
|
742
|
+
// task/spec surface, or a re-surface whose base question is already present).
|
|
743
|
+
// This is the calm "no open judgement" no-op — no sidecar written. (There is
|
|
744
|
+
// no "limbo" any more: the triage rung ALWAYS passes a base question on the
|
|
745
|
+
// first pass, so an untriaged observation can never fall here empty-handed.)
|
|
662
746
|
return {
|
|
663
747
|
exitCode: 0,
|
|
664
748
|
outcome: 'no-op',
|
|
@@ -675,40 +759,113 @@ async function surfaceRung(input: RungExecInput): Promise<RungExecResult> {
|
|
|
675
759
|
}
|
|
676
760
|
|
|
677
761
|
/**
|
|
678
|
-
*
|
|
679
|
-
*
|
|
680
|
-
*
|
|
681
|
-
*
|
|
762
|
+
* Would this OBSERVATION provably surface `{questions: []}` — i.e. is there no
|
|
763
|
+
* open judgement anywhere on it? Cheap read-only predicate the surface rung uses
|
|
764
|
+
* to skip the flaky agent round-trip for already-triaged / decision-record notes
|
|
765
|
+
* (task
|
|
766
|
+
* `surface-short-circuit-already-triaged-observations-and-harden-skill-empty-emit`;
|
|
767
|
+
* source observation
|
|
768
|
+
* `surface-questions-agent-still-emits-no-parseable-questions-on-decision-record-obs-2026-07-10`).
|
|
682
769
|
*
|
|
683
|
-
*
|
|
684
|
-
*
|
|
685
|
-
*
|
|
686
|
-
*
|
|
687
|
-
*
|
|
770
|
+
* Fires ONLY when ALL of these hold (conservative — err on the side of STILL
|
|
771
|
+
* calling the agent if uncertain):
|
|
772
|
+
* - the item's namespace is `observation` (tasks/specs are out of scope here —
|
|
773
|
+
* they carry no engine-owned base question and a false-positive there would
|
|
774
|
+
* silently drop a real question the author asked for);
|
|
775
|
+
* - the item body's frontmatter does NOT set `needsAnswers: true` (an author
|
|
776
|
+
* who set the flag is explicitly asking for the agent's pass);
|
|
777
|
+
* - the body carries no non-empty `## Open questions` section (empty /
|
|
778
|
+
* whitespace-only sections don't count as judgement);
|
|
779
|
+
* - there is no pending open-question sidecar for the item (same signal the
|
|
780
|
+
* classifier's invariant-1 read uses — the sidecar path is identity-derived).
|
|
688
781
|
*
|
|
689
|
-
*
|
|
690
|
-
*
|
|
691
|
-
*
|
|
692
|
-
* (the sidecar + `triaged:` frontmatter) keeps the loop honest.
|
|
782
|
+
* The decision-record shape (a `Decision (…)` line and/or `## Alternatives
|
|
783
|
+
* considered` section) is a HINT, not required — the four conditions above are
|
|
784
|
+
* load-bearing on their own.
|
|
693
785
|
*/
|
|
694
|
-
function
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
if (
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
786
|
+
function isNothingToSurfaceObservation(
|
|
787
|
+
cwd: string,
|
|
788
|
+
input: RungExecInput,
|
|
789
|
+
itemPath: string,
|
|
790
|
+
sidecarExists: boolean,
|
|
791
|
+
): boolean {
|
|
792
|
+
if (input.namespace !== 'observation') {
|
|
793
|
+
return false;
|
|
794
|
+
}
|
|
795
|
+
if (sidecarExists) {
|
|
796
|
+
return false;
|
|
797
|
+
}
|
|
798
|
+
let content: string;
|
|
799
|
+
try {
|
|
800
|
+
content = readFileSync(join(cwd, itemPath), 'utf8');
|
|
801
|
+
} catch {
|
|
802
|
+
return false;
|
|
803
|
+
}
|
|
804
|
+
const fm = parseFrontmatter(content);
|
|
805
|
+
if (fm.needsAnswers === true) {
|
|
806
|
+
return false;
|
|
807
|
+
}
|
|
808
|
+
if (hasNonEmptyOpenQuestionsSection(content)) {
|
|
809
|
+
return false;
|
|
810
|
+
}
|
|
811
|
+
return true;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* Does the body carry a non-empty `## Open questions` section? Tolerates the
|
|
816
|
+
* `## Open questions to NOT guess` variant the capture-signal skill writes (same
|
|
817
|
+
* pattern the triage split uses). Whitespace-only / "none" markers count as
|
|
818
|
+
* empty.
|
|
819
|
+
*/
|
|
820
|
+
function hasNonEmptyOpenQuestionsSection(content: string): boolean {
|
|
821
|
+
const lines = content.replace(/\r\n/g, '\n').split('\n');
|
|
822
|
+
const startIdx = lines.findIndex((l) => /^##\s+Open questions\b/i.test(l));
|
|
823
|
+
if (startIdx === -1) {
|
|
824
|
+
return false;
|
|
825
|
+
}
|
|
826
|
+
let endIdx = lines.length;
|
|
827
|
+
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
828
|
+
if (/^##\s+/.test(lines[i])) {
|
|
829
|
+
endIdx = i;
|
|
830
|
+
break;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
const body = lines
|
|
834
|
+
.slice(startIdx + 1, endIdx)
|
|
835
|
+
.join('\n')
|
|
836
|
+
.trim();
|
|
837
|
+
if (body === '') {
|
|
838
|
+
return false;
|
|
839
|
+
}
|
|
840
|
+
// A single "none" / "n/a" marker line — treat as empty (author signalled "no
|
|
841
|
+
// open judgement" explicitly).
|
|
842
|
+
if (/^(?:none|n\/a|-|_+)\.?$/i.test(body)) {
|
|
843
|
+
return false;
|
|
844
|
+
}
|
|
845
|
+
return true;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* The DETERMINISTIC triage question the triage rung ALWAYS surfaces for an
|
|
850
|
+
* untriaged observation (the "no limbo, ever" contract). It is engine-built (NOT
|
|
851
|
+
* LLM output), so it can never be zeroed out or flake: every untriaged observation
|
|
852
|
+
* gets exactly this question, and the human answers it via the sidecar — a record,
|
|
853
|
+
* a rationale note, and a fresh-bug signal are all treated identically (the human
|
|
854
|
+
* decides the disposition). The `surface-questions` agent runs ADDITIVELY on top,
|
|
855
|
+
* adding any extra pointed questions it extracts from the body.
|
|
856
|
+
*/
|
|
857
|
+
export function buildTriageBaseQuestion(): NewQuestion {
|
|
858
|
+
return {
|
|
859
|
+
question:
|
|
860
|
+
'What should become of this observation? Reply with a disposition and a ' +
|
|
861
|
+
'reason: resolve (settle it, keep the note on record — say why), promote ' +
|
|
862
|
+
'(mint a task / spec / adr — say which and why), delete (redundant or ' +
|
|
863
|
+
'obsolete — say why), or duplicate (maps onto an existing item — name it).',
|
|
864
|
+
context:
|
|
865
|
+
'The engine records your disposition from the answer (no token needed); an ' +
|
|
866
|
+
'answered promote mints the artifact, resolve keeps the note settled, ' +
|
|
867
|
+
'delete/duplicate discharge it.',
|
|
868
|
+
};
|
|
712
869
|
}
|
|
713
870
|
|
|
714
871
|
/**
|
|
@@ -745,6 +902,28 @@ async function triageRung(input: RungExecInput): Promise<RungExecResult> {
|
|
|
745
902
|
const note = context.note ?? (() => {});
|
|
746
903
|
const cwd = context.cwd;
|
|
747
904
|
|
|
905
|
+
// SETTLED GUARD: a `triaged:` frontmatter marker means a human already
|
|
906
|
+
// dispositioned this observation, so it DROPS OUT of the triage pool. The pool
|
|
907
|
+
// enumeration already excludes it, but an EXPLICIT `obs:<slug>` bypasses the pool
|
|
908
|
+
// gate and still classifies as `triage-observation` — so re-check the marker here
|
|
909
|
+
// and no-op rather than surfacing a FRESH triage question on an already-settled
|
|
910
|
+
// note. (Replaces the old `detectObservationLimbo` special-case: there is no
|
|
911
|
+
// limbo any more — an UNtriaged observation always surfaces the deterministic
|
|
912
|
+
// question below; a SETTLED one is a calm no-op here.)
|
|
913
|
+
{
|
|
914
|
+
const itemRel = findItemPath(cwd, input.namespace, input.slug);
|
|
915
|
+
if (itemRel !== undefined) {
|
|
916
|
+
const fm = parseFrontmatter(readFileSync(join(cwd, itemRel), 'utf8'));
|
|
917
|
+
if (fm.triaged !== undefined && fm.triaged !== '') {
|
|
918
|
+
return {
|
|
919
|
+
exitCode: 0,
|
|
920
|
+
outcome: 'no-op',
|
|
921
|
+
message: `triage ${item}: already triaged (triaged:${fm.triaged}) — nothing to do.`,
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
748
927
|
// The CONSERVATIVE auto-disposition EXCEPTION — ONLY under `observationTriage:
|
|
749
928
|
// 'auto'`. Under `'ask'`/`'off'`/unset (including `off` + an EXPLICIT obs:<slug>
|
|
750
929
|
// that bypassed the selection gate), EVERY untriaged observation surfaces the
|
|
@@ -795,7 +974,12 @@ async function triageRung(input: RungExecInput): Promise<RungExecResult> {
|
|
|
795
974
|
// skill emits the triage question — NO disposition token any more, task
|
|
796
975
|
// `agentic-apply-retire-disposition-vocabulary`); the AGENTIC apply decision
|
|
797
976
|
// reads the human's answer + source and decides what to DO when it is answered.
|
|
798
|
-
|
|
977
|
+
//
|
|
978
|
+
// ALWAYS-ASK (the "no limbo" contract): pass the DETERMINISTIC triage question as
|
|
979
|
+
// the base question, so an untriaged observation ALWAYS surfaces it (q1) even if
|
|
980
|
+
// the `surface-questions` agent emits empty or flakes. The agent is ADDITIVE:
|
|
981
|
+
// any pointed questions it extracts from the body are appended after q1.
|
|
982
|
+
return surfaceRung(input, {baseQuestions: [buildTriageBaseQuestion()]});
|
|
799
983
|
}
|
|
800
984
|
|
|
801
985
|
/**
|
|
@@ -807,8 +991,9 @@ async function triageRung(input: RungExecInput): Promise<RungExecResult> {
|
|
|
807
991
|
* apply rung is now AGENT-DRIVEN: it runs the shared `decide(input, allowedOutcomes)`
|
|
808
992
|
* engine ({@link decide}) over `(the answered question(s) + the SOURCE item + its
|
|
809
993
|
* type/context)` via the injected {@link ApplyDecider}, allowing the set
|
|
810
|
-
* `{task |
|
|
811
|
-
*
|
|
994
|
+
* `{task | spec | adr | dispose | resolve | ask}` (= `{mint-task | mint-spec |
|
|
995
|
+
* mint-adr | dispose-source | resolve-no-mint | ask-follow-up}`; `adr` is now WIRED
|
|
996
|
+
* by task
|
|
812
997
|
* `agentic-apply-mint-adr-route`, which added the {@link mintAdr} route). The
|
|
813
998
|
* verdict ROUTES:
|
|
814
999
|
* - `ask` → the EXISTING append/re-pause loop ({@link applyAnsweredQuestions}
|
|
@@ -817,11 +1002,22 @@ async function triageRung(input: RungExecInput): Promise<RungExecResult> {
|
|
|
817
1002
|
* - `adr` → {@link mintAdr} (mint a SELF-CONTAINED ADR into `docs/adr/` + `git
|
|
818
1003
|
* rm` the source + sidecar in the SAME atomic commit; the SIBLING route for the
|
|
819
1004
|
* off-board target);
|
|
820
|
-
* - `task` / `
|
|
1005
|
+
* - `task` / `spec` → {@link promoteObservation} (mint a SELF-CONTAINED artifact +
|
|
821
1006
|
* `git rm` the source + sidecar in the SAME atomic commit); the artifact type
|
|
822
1007
|
* comes from the agent's VERDICT, NOT a human `promote-*` field;
|
|
823
|
-
* - `
|
|
824
|
-
*
|
|
1008
|
+
* - `resolve` → {@link applyAnsweredQuestions} resolve-fully (harvest answers
|
|
1009
|
+
* into the body, clear `needsAnswers`, delete the sidecar; the note is
|
|
1010
|
+
* RETAINED — the sibling of `dispose` that mints nothing but keeps the note);
|
|
1011
|
+
* - `dispose` → {@link applyAnsweredQuestions} regime-polymorphic disposal
|
|
1012
|
+
* (task `apply-disposition-delete-to-dispose-regime-polymorphic`, spec
|
|
1013
|
+
* `surface-stuck-as-questions-and-retire-stuck-lock-state` decision #5): an
|
|
1014
|
+
* OBSERVATION is `git rm`-ed in one revertible commit (notes leave by
|
|
1015
|
+
* deletion; reason in the message); a TASK is `git mv`-ed to
|
|
1016
|
+
* `tasks/cancelled/` (RETAINED, `reason:` written into the moved body); a
|
|
1017
|
+
* SPEC is `git mv`-ed to `specs/dropped/` (RETAINED). Making `dispose`
|
|
1018
|
+
* polymorphic (rather than a literal `delete`) makes "a task cannot be
|
|
1019
|
+
* hard-deleted by the apply rung, only disposed to its terminal" true BY
|
|
1020
|
+
* CONSTRUCTION.
|
|
825
1021
|
*
|
|
826
1022
|
* For a TASK/SPEC (answering its OWN open questions) or a caller-supplied follow-up
|
|
827
1023
|
* batch, it delegates straight to {@link applyAnsweredQuestions} (resolve fully /
|
|
@@ -859,6 +1055,26 @@ async function applyRung(input: RungExecInput): Promise<RungExecResult> {
|
|
|
859
1055
|
return mergeRoute;
|
|
860
1056
|
}
|
|
861
1057
|
|
|
1058
|
+
// RUNNER-ACTION KIND-CHECK for `kind: 'stuck'` (task
|
|
1059
|
+
// `apply-resolve-reset-flag-discards-work-branch`, spec
|
|
1060
|
+
// `surface-stuck-as-questions-and-retire-stuck-lock-state` decision #6): a
|
|
1061
|
+
// bounced TASK surfaces as a `kind: 'stuck'` question; the human's plain
|
|
1062
|
+
// `keep | reset | cancel` answer drives one of the three deterministic
|
|
1063
|
+
// verbs. The DIRECT SIBLING of `maybeRunMergeAction` above (there is NO
|
|
1064
|
+
// agentic decider on the TASK apply path — `runAgenticDecision` fires only
|
|
1065
|
+
// for observations — so a bounced task's answer MUST be sourced from a
|
|
1066
|
+
// deterministic parse+dispatch, not a widened decider). `keep` and `reset`
|
|
1067
|
+
// fall through to the normal fall-through persist (with the branch
|
|
1068
|
+
// pre-deleted on `reset` via the SHARED
|
|
1069
|
+
// `deleteRemoteWorkBranchIfPresent`); `cancel` dispatches through
|
|
1070
|
+
// `applyAnsweredQuestions`' `dispose` option (task →
|
|
1071
|
+
// `tasks/cancelled/`); `refused` short-circuits with the sidecar left in
|
|
1072
|
+
// place for a re-answer.
|
|
1073
|
+
const stuckRoute = await maybeRunStuckAction(input, itemPath);
|
|
1074
|
+
if (stuckRoute !== undefined) {
|
|
1075
|
+
return stuckRoute;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
862
1078
|
// AGENTIC APPLY for an answered OBSERVATION (the subsumed triage rung): run the
|
|
863
1079
|
// shared decision engine over the answer(s) + source, route the verdict. A
|
|
864
1080
|
// caller-supplied follow-up batch (`applyFollowups`) bypasses the decision and
|
|
@@ -995,8 +1211,13 @@ async function maybeRunMergeAction(
|
|
|
995
1211
|
// bounce to needs-attention through its own shared seam). SHORT-CIRCUIT:
|
|
996
1212
|
// leave the sidecar so the open answer stays surfaced — the apply rung
|
|
997
1213
|
// MUST NOT also resolve it (the next surfacer / human will follow up).
|
|
1214
|
+
// Outcome tag `merge-refused` (task `merge-action-nits-followup` nit 2):
|
|
1215
|
+
// distinct from `usage-error` — which is reserved for genuine caller-usage
|
|
1216
|
+
// errors (e.g. the workspacesDir-unset guard above) — so reviewers can
|
|
1217
|
+
// grep the refusal-on-rebased-tip signal without confusing it with a
|
|
1218
|
+
// misuse. `exitCode: 1` preserved.
|
|
998
1219
|
note(result.message);
|
|
999
|
-
return {exitCode: 1, outcome: '
|
|
1220
|
+
return {exitCode: 1, outcome: 'merge-refused', message: result.message};
|
|
1000
1221
|
}
|
|
1001
1222
|
|
|
1002
1223
|
if (result.outcome === 'restale') {
|
|
@@ -1064,6 +1285,113 @@ async function maybeRunMergeAction(
|
|
|
1064
1285
|
}
|
|
1065
1286
|
}
|
|
1066
1287
|
|
|
1288
|
+
/**
|
|
1289
|
+
* Dispatch an answered STUCK-QUESTION (a sidecar entry stamped `kind: 'stuck'`
|
|
1290
|
+
* by the bounce-surface path) through the deterministic
|
|
1291
|
+
* {@link performStuckAction} sibling. The DIRECT SIBLING of
|
|
1292
|
+
* {@link maybeRunMergeAction} for the `kind: 'stuck'` axis (task
|
|
1293
|
+
* `apply-resolve-reset-flag-discards-work-branch`, spec
|
|
1294
|
+
* `surface-stuck-as-questions-and-retire-stuck-lock-state` decision #6):
|
|
1295
|
+
*
|
|
1296
|
+
* - `keep` -> fall through to today's normal `applyAnsweredQuestions`
|
|
1297
|
+
* resolve (branch untouched; continue-from-WIP);
|
|
1298
|
+
* - `reset` -> the SHARED `deleteRemoteWorkBranchIfPresent` primitive
|
|
1299
|
+
* discards the remote `work/task-<slug>` FIRST, then the
|
|
1300
|
+
* normal fall-through persist clears `needsAnswers`. Safely
|
|
1301
|
+
* IDEMPOTENT when no branch exists (an item never built) —
|
|
1302
|
+
* an `already-gone` push is tolerated as a no-op;
|
|
1303
|
+
* - `cancel` -> fall through to the normal persist with the `dispose`
|
|
1304
|
+
* option (`git mv -> tasks/cancelled/`), the answer text
|
|
1305
|
+
* recorded as the human's reason;
|
|
1306
|
+
* - `refused` -> a REAL push-delete failure aborted the discard;
|
|
1307
|
+
* SHORT-CIRCUIT with the sidecar left in place so the human
|
|
1308
|
+
* sees the failure and re-answers (matches the
|
|
1309
|
+
* `requeue --reset` abort-on-failed-delete contract).
|
|
1310
|
+
*
|
|
1311
|
+
* Returns `undefined` when no answered `kind: 'stuck'` entry is present (the
|
|
1312
|
+
* apply rung then proceeds to the existing path — an unrelated content
|
|
1313
|
+
* question falls through to the normal `applyAnsweredQuestions` /
|
|
1314
|
+
* agentic-decider path).
|
|
1315
|
+
*/
|
|
1316
|
+
async function maybeRunStuckAction(
|
|
1317
|
+
input: RungExecInput,
|
|
1318
|
+
itemPath: string,
|
|
1319
|
+
): Promise<RungExecResult | undefined> {
|
|
1320
|
+
const {item, context} = input;
|
|
1321
|
+
const cwd = context.cwd;
|
|
1322
|
+
const note = context.note ?? (() => {});
|
|
1323
|
+
|
|
1324
|
+
const detected = detectAnsweredStuckAction(cwd, item);
|
|
1325
|
+
if (detected === undefined) return undefined;
|
|
1326
|
+
|
|
1327
|
+
const handler = context.stuckAction ?? performStuckAction;
|
|
1328
|
+
|
|
1329
|
+
let result: StuckActionResult;
|
|
1330
|
+
try {
|
|
1331
|
+
result = await handler({
|
|
1332
|
+
action: detected,
|
|
1333
|
+
item,
|
|
1334
|
+
slug: input.slug,
|
|
1335
|
+
cwd,
|
|
1336
|
+
arbiter: context.arbiter ?? DEFAULT_ARBITER,
|
|
1337
|
+
note,
|
|
1338
|
+
});
|
|
1339
|
+
} catch (err) {
|
|
1340
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
1341
|
+
const message =
|
|
1342
|
+
`apply ${item}: answered stuck-question dispatch raised (${detail}); NOT ` +
|
|
1343
|
+
`clearing needsAnswers; the sidecar stays surfaced.`;
|
|
1344
|
+
note(message);
|
|
1345
|
+
return {exitCode: 1, outcome: 'usage-error', message};
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
if (result.outcome === 'refused') {
|
|
1349
|
+
// The arbiter delete FAILED (not `already-gone`); we MUST NOT clear
|
|
1350
|
+
// needsAnswers, because that would leave the item claimable while still
|
|
1351
|
+
// carrying the WIP branch we meant to discard — the very stale-continue
|
|
1352
|
+
// trap the `requeue --reset` path guards against. Leave the sidecar in
|
|
1353
|
+
// place for a re-answer.
|
|
1354
|
+
note(result.message);
|
|
1355
|
+
return {exitCode: 1, outcome: 'usage-error', message: result.message};
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
note(result.message);
|
|
1359
|
+
const apply = context.applyPersist ?? applyAnsweredQuestions;
|
|
1360
|
+
try {
|
|
1361
|
+
const applyOptions: ApplyAnsweredQuestionsOptions = {
|
|
1362
|
+
cwd,
|
|
1363
|
+
item,
|
|
1364
|
+
itemPath,
|
|
1365
|
+
note,
|
|
1366
|
+
};
|
|
1367
|
+
if (result.outcome === 'cancel') {
|
|
1368
|
+
// The human's answer text is the dispose reason (verbatim), so the
|
|
1369
|
+
// terminal `tasks/cancelled/` body records WHY — the same reason
|
|
1370
|
+
// contract the agentic `dispose` verdict uses.
|
|
1371
|
+
applyOptions.dispose = {reason: detected.entry.answer.trim()};
|
|
1372
|
+
}
|
|
1373
|
+
const applied = apply(applyOptions);
|
|
1374
|
+
const mapped: AdvanceOutcome =
|
|
1375
|
+
applied.outcome === 'repaused'
|
|
1376
|
+
? 'no-op'
|
|
1377
|
+
: applied.outcome === 'vanished'
|
|
1378
|
+
? 'vanished'
|
|
1379
|
+
: 'advanced';
|
|
1380
|
+
return {
|
|
1381
|
+
exitCode: 0,
|
|
1382
|
+
outcome: mapped,
|
|
1383
|
+
message: `${result.message} ${applied.message}`,
|
|
1384
|
+
};
|
|
1385
|
+
} catch (err) {
|
|
1386
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
1387
|
+
return {
|
|
1388
|
+
exitCode: 1,
|
|
1389
|
+
outcome: 'usage-error',
|
|
1390
|
+
message: `apply ${item}: ${detail}`,
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1067
1395
|
/**
|
|
1068
1396
|
* The cross-tick-window BENIGN SKIP shared by all three rungs that need to
|
|
1069
1397
|
* resolve an item file (surface / triage / apply). The lifecycle pool enumerated
|
|
@@ -1119,15 +1447,24 @@ function findItemPath(
|
|
|
1119
1447
|
*
|
|
1120
1448
|
* - `ask` → append the follow-up question(s) + re-pause (the EXISTING loop, via
|
|
1121
1449
|
* {@link applyAnsweredQuestions}'s `appendQuestions`);
|
|
1122
|
-
* - `task` / `
|
|
1450
|
+
* - `task` / `spec` → {@link promoteObservation} (mint self-contained + delete
|
|
1123
1451
|
* source in the same atomic commit);
|
|
1124
1452
|
* - `adr` → {@link mintAdr} (mint a self-contained ADR into `docs/adr/` + delete
|
|
1125
1453
|
* source in the same atomic commit; the SIBLING route for the off-board target,
|
|
1126
1454
|
* task `agentic-apply-mint-adr-route`);
|
|
1127
|
-
* - `
|
|
1128
|
-
*
|
|
1455
|
+
* - `resolve` → {@link applyAnsweredQuestions}'s resolve-fully path (harvest the
|
|
1456
|
+
* answers into `## Applied answers`, strip the open-questions block, clear
|
|
1457
|
+
* `needsAnswers`, DELETE the sidecar — the note is RETAINED). The sibling of
|
|
1458
|
+
* `dispose` that KEEPS the note instead of moving/rm-ing it (task
|
|
1459
|
+
* `apply-decide-resolve-verdict-mint-nothing`);
|
|
1460
|
+
* - `dispose` → {@link applyAnsweredQuestions}'s regime-polymorphic disposal
|
|
1461
|
+
* (task `apply-disposition-delete-to-dispose-regime-polymorphic`): an
|
|
1462
|
+
* observation is `git rm`-ed in one revertible commit (reason in the
|
|
1463
|
+
* message); a task is `git mv`-ed to `tasks/cancelled/` (reason written into
|
|
1464
|
+
* the body); a spec is `git mv`-ed to `specs/dropped/`.
|
|
1129
1465
|
*
|
|
1130
|
-
* The allowed set is `{task |
|
|
1466
|
+
* The allowed set is `{task | spec | adr | dispose | resolve | ask}`; a verdict
|
|
1467
|
+
* outside it is
|
|
1131
1468
|
* rejected by the engine's allowed-outcome guard ({@link DisallowedOutcomeError})
|
|
1132
1469
|
* and mapped onto a usage-error — never dispatched.
|
|
1133
1470
|
*/
|
|
@@ -1234,11 +1571,13 @@ async function applyAgenticDecision(
|
|
|
1234
1571
|
outcome:
|
|
1235
1572
|
result.outcome === 'promoted'
|
|
1236
1573
|
? 'advanced'
|
|
1237
|
-
: result.outcome === '
|
|
1238
|
-
? '
|
|
1239
|
-
: result.outcome === '
|
|
1240
|
-
? '
|
|
1241
|
-
: '
|
|
1574
|
+
: result.outcome === 'already-triaged'
|
|
1575
|
+
? 'already-triaged'
|
|
1576
|
+
: result.outcome === 'lost'
|
|
1577
|
+
? 'lost'
|
|
1578
|
+
: result.outcome === 'contended'
|
|
1579
|
+
? 'contended'
|
|
1580
|
+
: 'usage-error',
|
|
1242
1581
|
message: result.message,
|
|
1243
1582
|
};
|
|
1244
1583
|
} catch (err) {
|
|
@@ -1303,16 +1642,53 @@ async function applyAgenticDecision(
|
|
|
1303
1642
|
}
|
|
1304
1643
|
}
|
|
1305
1644
|
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1645
|
+
if (verdict.outcome === 'resolve') {
|
|
1646
|
+
// resolve-no-mint → the EXISTING resolve-fully path (task
|
|
1647
|
+
// `apply-decide-resolve-verdict-mint-nothing`). The answer SETTLES the item
|
|
1648
|
+
// with NOTHING to mint and the note RETAINED: call `applyAnsweredQuestions`
|
|
1649
|
+
// with NEITHER `appendQuestions` (re-pause) NOR `dispose` (drop/terminal), so it
|
|
1650
|
+
// takes the default resolve-fully branch — harvest the answers into `##
|
|
1651
|
+
// Applied answers`, strip the marker-fenced open-questions block, clear
|
|
1652
|
+
// `needsAnswers`, and DELETE the sidecar in ONE atomic commit. Invariant-clean
|
|
1653
|
+
// (`needsAnswers:false` ⟺ no active sidecar). The note file is KEPT (this is
|
|
1654
|
+
// the sibling of `dispose`, which git-rm's it or moves it to a terminal). `resolveReason` is advisory
|
|
1655
|
+
// context only; the durable disposition record is the harvested `## Applied
|
|
1656
|
+
// answers` block the resolve-fully path writes (NOT a separate convention).
|
|
1657
|
+
const apply = context.applyPersist ?? applyAnsweredQuestions;
|
|
1658
|
+
try {
|
|
1659
|
+
const result = apply({cwd, item, itemPath, note});
|
|
1660
|
+
return {
|
|
1661
|
+
exitCode: 0,
|
|
1662
|
+
outcome: result.outcome === 'vanished' ? 'vanished' : 'advanced',
|
|
1663
|
+
message: result.message,
|
|
1664
|
+
};
|
|
1665
|
+
} catch (err) {
|
|
1666
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
1667
|
+
return {
|
|
1668
|
+
exitCode: 1,
|
|
1669
|
+
outcome: 'usage-error',
|
|
1670
|
+
message: `apply ${item}: ${detail}`,
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
// dispose-source → regime-polymorphic disposal (DIRECT, no confirm — decision
|
|
1676
|
+
// 12; task `apply-disposition-delete-to-dispose-regime-polymorphic`, spec
|
|
1677
|
+
// `surface-stuck-as-questions-and-retire-stuck-lock-state` decision #5). The
|
|
1678
|
+
// human's answer is the source of truth; the persist chooses the on-disk
|
|
1679
|
+
// effect by the source's regime: an OBSERVATION is `git rm`-ed in one
|
|
1680
|
+
// revertible commit (reason in the message, git history = archive); a TASK is
|
|
1681
|
+
// `git mv`-ed to `tasks/cancelled/` (RETAINED, `reason:` written into the
|
|
1682
|
+
// moved body); a SPEC is `git mv`-ed to `specs/dropped/` (RETAINED). A task
|
|
1683
|
+
// cannot be hard-deleted by the apply rung — dispose is the only path off the
|
|
1684
|
+
// board.
|
|
1309
1685
|
const apply = context.applyPersist ?? applyAnsweredQuestions;
|
|
1310
1686
|
try {
|
|
1311
1687
|
const result = apply({
|
|
1312
1688
|
cwd,
|
|
1313
1689
|
item,
|
|
1314
1690
|
itemPath,
|
|
1315
|
-
|
|
1691
|
+
dispose: {reason: verdict.disposeReason ?? ''},
|
|
1316
1692
|
note,
|
|
1317
1693
|
});
|
|
1318
1694
|
return {
|
|
@@ -1506,6 +1882,10 @@ export async function performAdvance(
|
|
|
1506
1882
|
verify: options.verify,
|
|
1507
1883
|
strictMergeApproval: options.strictMergeApproval,
|
|
1508
1884
|
mergeAction: options.mergeAction,
|
|
1885
|
+
// The answered-stuck-question dispatch context (task
|
|
1886
|
+
// `apply-resolve-reset-flag-discards-work-branch`): threaded into the
|
|
1887
|
+
// apply rung's kind-check for a `kind: 'stuck'` runner-action.
|
|
1888
|
+
stuckAction: options.stuckAction,
|
|
1509
1889
|
note,
|
|
1510
1890
|
},
|
|
1511
1891
|
});
|