dorfl 0.1.2 → 0.2.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/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/dist/advance.js
CHANGED
|
@@ -18,6 +18,7 @@ import { APPLY_ALLOWED_OUTCOMES, buildApplyDecisionInput, harnessApplyDecider, }
|
|
|
18
18
|
import { persistSurfacedQuestions, } from './surface-persist.js';
|
|
19
19
|
import { applyAnsweredQuestions, } from './apply-persist.js';
|
|
20
20
|
import { detectAnsweredMergeAction, performMergeAction, } from './apply-merge-action.js';
|
|
21
|
+
import { detectAnsweredStuckAction, performStuckAction, } from './apply-stuck-action.js';
|
|
21
22
|
/**
|
|
22
23
|
* The **`advance` verb SKELETON** (spec `advance-loop`, task
|
|
23
24
|
* `advance-verb-resolver`, US #1/5/6/18). `advance` is the SIBLING top-level verb
|
|
@@ -223,7 +224,7 @@ function mapDoOutcome(result) {
|
|
|
223
224
|
* answered sidecar back to not-all-answered (the persist owns that). An EMPTY
|
|
224
225
|
* emit (the skill's honest "no open judgement") writes nothing and reports it.
|
|
225
226
|
*/
|
|
226
|
-
async function surfaceRung(input) {
|
|
227
|
+
async function surfaceRung(input, surfaceOpts = {}) {
|
|
227
228
|
const { item, context } = input;
|
|
228
229
|
const note = context.note ?? (() => { });
|
|
229
230
|
const cwd = context.cwd;
|
|
@@ -234,50 +235,80 @@ async function surfaceRung(input) {
|
|
|
234
235
|
if (itemPath === undefined) {
|
|
235
236
|
return vanishedSkip({ rung: 'surface', item });
|
|
236
237
|
}
|
|
238
|
+
// DETERMINISTIC BASE QUESTIONS (the triage rung's "always ask" contract): the
|
|
239
|
+
// caller may pass engine-built questions that MUST be surfaced regardless of the
|
|
240
|
+
// agent. They are added ONLY on the FIRST pass (no sidecar yet) — on a re-surface
|
|
241
|
+
// the sidecar already carries them, and `appendQuestions` does NOT dedup, so
|
|
242
|
+
// re-adding would duplicate. When base questions are present the agent is
|
|
243
|
+
// ADDITIVE ONLY and its flake/empty is NON-FATAL (the base question still lands).
|
|
244
|
+
const baseQuestions = surfaceOpts.baseQuestions ?? [];
|
|
245
|
+
const hasBase = baseQuestions.length > 0;
|
|
246
|
+
const sidecarExists = existsSync(join(cwd, sidecarPathFor(item)));
|
|
247
|
+
const baseToAdd = hasBase && !sidecarExists ? baseQuestions : [];
|
|
237
248
|
// 1. SPAWN the fresh-context `surface-questions` agent (the skill JUDGES). The
|
|
238
249
|
// expensive model work is POST-lock (the lock is held by `performAdvance`).
|
|
250
|
+
//
|
|
251
|
+
// SHORT-CIRCUIT (primary/load-bearing half of task
|
|
252
|
+
// `surface-short-circuit-already-triaged-observations-and-harden-skill-empty-emit`):
|
|
253
|
+
// an OBSERVATION with provably no open judgement (frontmatter `needsAnswers` NOT
|
|
254
|
+
// true, no non-empty `## Open questions` section, no pending sidecar) — the
|
|
255
|
+
// typical shape of a decision-record / already-triaged note — deterministically
|
|
256
|
+
// yields `{questions: []}` WITHOUT round-tripping the flaky surface-questions
|
|
257
|
+
// agent (source observation
|
|
258
|
+
// `surface-questions-agent-still-emits-no-parseable-questions-on-decision-record-obs-2026-07-10`).
|
|
259
|
+
// The loud-error contract from
|
|
260
|
+
// `advance-surface-limbo-observation-loudly-instead-of-silent-no-op` is
|
|
261
|
+
// PRESERVED for the observations that DO reach the agent — this only spares the
|
|
262
|
+
// ones that provably have nothing to ask. Conservative: fires only on
|
|
263
|
+
// observations; tasks/specs still always ask.
|
|
239
264
|
const gate = context.surfaceGate ?? harnessSurfaceGate();
|
|
240
265
|
let emit;
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
cwd,
|
|
245
|
-
surfaceModel: context.surfaceModel,
|
|
246
|
-
});
|
|
266
|
+
if (isNothingToSurfaceObservation(cwd, input, itemPath, sidecarExists)) {
|
|
267
|
+
note(`surface ${item}: auto-triaged (no open questions, no sidecar) — skipped agent.`);
|
|
268
|
+
emit = { questions: [] };
|
|
247
269
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
270
|
+
else {
|
|
271
|
+
try {
|
|
272
|
+
emit = await gate({
|
|
273
|
+
item,
|
|
274
|
+
cwd,
|
|
275
|
+
surfaceModel: context.surfaceModel,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
280
|
+
if (!hasBase) {
|
|
281
|
+
return {
|
|
282
|
+
exitCode: 1,
|
|
283
|
+
outcome: 'usage-error',
|
|
284
|
+
message: `surface ${item}: the surface-questions agent produced no usable emit (${detail}).`,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
// The agent flaked but we have a deterministic base question to surface: the
|
|
288
|
+
// flake is NON-FATAL. Treat the agent's extras as empty and carry on (the
|
|
289
|
+
// base triage question still lands, so the human can triage via the sidecar).
|
|
290
|
+
note(`surface ${item}: the surface-questions agent produced no usable emit ` +
|
|
291
|
+
`(${detail}); surfacing the deterministic question(s) only.`);
|
|
292
|
+
emit = { questions: [] };
|
|
293
|
+
}
|
|
255
294
|
}
|
|
256
295
|
// 2. The ENGINE persists (the skill wrote nothing): append-or-create the sidecar
|
|
257
296
|
// + set `needsAnswers:true` in ONE commit (CAS-atomic under the held lock).
|
|
297
|
+
// Base questions FIRST (q1…), then the agent's additive extras.
|
|
258
298
|
const persist = context.surfacePersist ?? persistSurfacedQuestions;
|
|
259
299
|
const result = persist({
|
|
260
300
|
cwd,
|
|
261
301
|
item,
|
|
262
302
|
itemPath,
|
|
263
|
-
questions: toNewQuestions(emit),
|
|
303
|
+
questions: [...baseToAdd, ...toNewQuestions(emit)],
|
|
264
304
|
note,
|
|
265
305
|
});
|
|
266
306
|
if (result.outcome === 'nothing') {
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
// the promote path only from an answered `disposition: promote` SIDECAR
|
|
273
|
-
// (`triage-persist.ts`). Without either, the item is untriaged (re-enumerated
|
|
274
|
-
// every tick), un-surfaceable (nothing to ask), un-promotable (no sidecar) —
|
|
275
|
-
// a silent exit-0 no-op forever. Surface it LOUDLY instead.
|
|
276
|
-
const limbo = detectObservationLimbo(input);
|
|
277
|
-
if (limbo !== undefined) {
|
|
278
|
-
note(limbo);
|
|
279
|
-
return { exitCode: 1, outcome: 'usage-error', message: limbo };
|
|
280
|
-
}
|
|
307
|
+
// The agent had nothing to ask AND there was no base question to add (a
|
|
308
|
+
// task/spec surface, or a re-surface whose base question is already present).
|
|
309
|
+
// This is the calm "no open judgement" no-op — no sidecar written. (There is
|
|
310
|
+
// no "limbo" any more: the triage rung ALWAYS passes a base question on the
|
|
311
|
+
// first pass, so an untriaged observation can never fall here empty-handed.)
|
|
281
312
|
return {
|
|
282
313
|
exitCode: 0,
|
|
283
314
|
outcome: 'no-op',
|
|
@@ -292,42 +323,105 @@ async function surfaceRung(input) {
|
|
|
292
323
|
};
|
|
293
324
|
}
|
|
294
325
|
/**
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
326
|
+
* Would this OBSERVATION provably surface `{questions: []}` — i.e. is there no
|
|
327
|
+
* open judgement anywhere on it? Cheap read-only predicate the surface rung uses
|
|
328
|
+
* to skip the flaky agent round-trip for already-triaged / decision-record notes
|
|
329
|
+
* (task
|
|
330
|
+
* `surface-short-circuit-already-triaged-observations-and-harden-skill-empty-emit`;
|
|
331
|
+
* source observation
|
|
332
|
+
* `surface-questions-agent-still-emits-no-parseable-questions-on-decision-record-obs-2026-07-10`).
|
|
299
333
|
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
334
|
+
* Fires ONLY when ALL of these hold (conservative — err on the side of STILL
|
|
335
|
+
* calling the agent if uncertain):
|
|
336
|
+
* - the item's namespace is `observation` (tasks/specs are out of scope here —
|
|
337
|
+
* they carry no engine-owned base question and a false-positive there would
|
|
338
|
+
* silently drop a real question the author asked for);
|
|
339
|
+
* - the item body's frontmatter does NOT set `needsAnswers: true` (an author
|
|
340
|
+
* who set the flag is explicitly asking for the agent's pass);
|
|
341
|
+
* - the body carries no non-empty `## Open questions` section (empty /
|
|
342
|
+
* whitespace-only sections don't count as judgement);
|
|
343
|
+
* - there is no pending open-question sidecar for the item (same signal the
|
|
344
|
+
* classifier's invariant-1 read uses — the sidecar path is identity-derived).
|
|
305
345
|
*
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
* (the sidecar + `triaged:` frontmatter) keeps the loop honest.
|
|
346
|
+
* The decision-record shape (a `Decision (…)` line and/or `## Alternatives
|
|
347
|
+
* considered` section) is a HINT, not required — the four conditions above are
|
|
348
|
+
* load-bearing on their own.
|
|
310
349
|
*/
|
|
311
|
-
function
|
|
312
|
-
if (input.namespace !== 'observation')
|
|
313
|
-
return
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
350
|
+
function isNothingToSurfaceObservation(cwd, input, itemPath, sidecarExists) {
|
|
351
|
+
if (input.namespace !== 'observation') {
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
if (sidecarExists) {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
let content;
|
|
358
|
+
try {
|
|
359
|
+
content = readFileSync(join(cwd, itemPath), 'utf8');
|
|
360
|
+
}
|
|
361
|
+
catch {
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
const fm = parseFrontmatter(content);
|
|
365
|
+
if (fm.needsAnswers === true) {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
if (hasNonEmptyOpenQuestionsSection(content)) {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
return true;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Does the body carry a non-empty `## Open questions` section? Tolerates the
|
|
375
|
+
* `## Open questions to NOT guess` variant the capture-signal skill writes (same
|
|
376
|
+
* pattern the triage split uses). Whitespace-only / "none" markers count as
|
|
377
|
+
* empty.
|
|
378
|
+
*/
|
|
379
|
+
function hasNonEmptyOpenQuestionsSection(content) {
|
|
380
|
+
const lines = content.replace(/\r\n/g, '\n').split('\n');
|
|
381
|
+
const startIdx = lines.findIndex((l) => /^##\s+Open questions\b/i.test(l));
|
|
382
|
+
if (startIdx === -1) {
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
let endIdx = lines.length;
|
|
386
|
+
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
387
|
+
if (/^##\s+/.test(lines[i])) {
|
|
388
|
+
endIdx = i;
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
const body = lines
|
|
393
|
+
.slice(startIdx + 1, endIdx)
|
|
394
|
+
.join('\n')
|
|
395
|
+
.trim();
|
|
396
|
+
if (body === '') {
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
// A single "none" / "n/a" marker line — treat as empty (author signalled "no
|
|
400
|
+
// open judgement" explicitly).
|
|
401
|
+
if (/^(?:none|n\/a|-|_+)\.?$/i.test(body)) {
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* The DETERMINISTIC triage question the triage rung ALWAYS surfaces for an
|
|
408
|
+
* untriaged observation (the "no limbo, ever" contract). It is engine-built (NOT
|
|
409
|
+
* LLM output), so it can never be zeroed out or flake: every untriaged observation
|
|
410
|
+
* gets exactly this question, and the human answers it via the sidecar — a record,
|
|
411
|
+
* a rationale note, and a fresh-bug signal are all treated identically (the human
|
|
412
|
+
* decides the disposition). The `surface-questions` agent runs ADDITIVELY on top,
|
|
413
|
+
* adding any extra pointed questions it extracts from the body.
|
|
414
|
+
*/
|
|
415
|
+
export function buildTriageBaseQuestion() {
|
|
416
|
+
return {
|
|
417
|
+
question: 'What should become of this observation? Reply with a disposition and a ' +
|
|
418
|
+
'reason: resolve (settle it, keep the note on record — say why), promote ' +
|
|
419
|
+
'(mint a task / spec / adr — say which and why), delete (redundant or ' +
|
|
420
|
+
'obsolete — say why), or duplicate (maps onto an existing item — name it).',
|
|
421
|
+
context: 'The engine records your disposition from the answer (no token needed); an ' +
|
|
422
|
+
'answered promote mints the artifact, resolve keeps the note settled, ' +
|
|
423
|
+
'delete/duplicate discharge it.',
|
|
424
|
+
};
|
|
331
425
|
}
|
|
332
426
|
/**
|
|
333
427
|
* The observation TRIAGE rung BODY (task `advance-rung-triage`, US #16/17/23):
|
|
@@ -362,6 +456,27 @@ async function triageRung(input) {
|
|
|
362
456
|
const { item, context } = input;
|
|
363
457
|
const note = context.note ?? (() => { });
|
|
364
458
|
const cwd = context.cwd;
|
|
459
|
+
// SETTLED GUARD: a `triaged:` frontmatter marker means a human already
|
|
460
|
+
// dispositioned this observation, so it DROPS OUT of the triage pool. The pool
|
|
461
|
+
// enumeration already excludes it, but an EXPLICIT `obs:<slug>` bypasses the pool
|
|
462
|
+
// gate and still classifies as `triage-observation` — so re-check the marker here
|
|
463
|
+
// and no-op rather than surfacing a FRESH triage question on an already-settled
|
|
464
|
+
// note. (Replaces the old `detectObservationLimbo` special-case: there is no
|
|
465
|
+
// limbo any more — an UNtriaged observation always surfaces the deterministic
|
|
466
|
+
// question below; a SETTLED one is a calm no-op here.)
|
|
467
|
+
{
|
|
468
|
+
const itemRel = findItemPath(cwd, input.namespace, input.slug);
|
|
469
|
+
if (itemRel !== undefined) {
|
|
470
|
+
const fm = parseFrontmatter(readFileSync(join(cwd, itemRel), 'utf8'));
|
|
471
|
+
if (fm.triaged !== undefined && fm.triaged !== '') {
|
|
472
|
+
return {
|
|
473
|
+
exitCode: 0,
|
|
474
|
+
outcome: 'no-op',
|
|
475
|
+
message: `triage ${item}: already triaged (triaged:${fm.triaged}) — nothing to do.`,
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
365
480
|
// The CONSERVATIVE auto-disposition EXCEPTION — ONLY under `observationTriage:
|
|
366
481
|
// 'auto'`. Under `'ask'`/`'off'`/unset (including `off` + an EXPLICIT obs:<slug>
|
|
367
482
|
// that bypassed the selection gate), EVERY untriaged observation surfaces the
|
|
@@ -410,7 +525,12 @@ async function triageRung(input) {
|
|
|
410
525
|
// skill emits the triage question — NO disposition token any more, task
|
|
411
526
|
// `agentic-apply-retire-disposition-vocabulary`); the AGENTIC apply decision
|
|
412
527
|
// reads the human's answer + source and decides what to DO when it is answered.
|
|
413
|
-
|
|
528
|
+
//
|
|
529
|
+
// ALWAYS-ASK (the "no limbo" contract): pass the DETERMINISTIC triage question as
|
|
530
|
+
// the base question, so an untriaged observation ALWAYS surfaces it (q1) even if
|
|
531
|
+
// the `surface-questions` agent emits empty or flakes. The agent is ADDITIVE:
|
|
532
|
+
// any pointed questions it extracts from the body are appended after q1.
|
|
533
|
+
return surfaceRung(input, { baseQuestions: [buildTriageBaseQuestion()] });
|
|
414
534
|
}
|
|
415
535
|
/**
|
|
416
536
|
* The APPLY rung BODY (task `advance-rung-apply`; AGENTIC apply, task
|
|
@@ -421,8 +541,9 @@ async function triageRung(input) {
|
|
|
421
541
|
* apply rung is now AGENT-DRIVEN: it runs the shared `decide(input, allowedOutcomes)`
|
|
422
542
|
* engine ({@link decide}) over `(the answered question(s) + the SOURCE item + its
|
|
423
543
|
* type/context)` via the injected {@link ApplyDecider}, allowing the set
|
|
424
|
-
* `{task |
|
|
425
|
-
*
|
|
544
|
+
* `{task | spec | adr | dispose | resolve | ask}` (= `{mint-task | mint-spec |
|
|
545
|
+
* mint-adr | dispose-source | resolve-no-mint | ask-follow-up}`; `adr` is now WIRED
|
|
546
|
+
* by task
|
|
426
547
|
* `agentic-apply-mint-adr-route`, which added the {@link mintAdr} route). The
|
|
427
548
|
* verdict ROUTES:
|
|
428
549
|
* - `ask` → the EXISTING append/re-pause loop ({@link applyAnsweredQuestions}
|
|
@@ -431,11 +552,22 @@ async function triageRung(input) {
|
|
|
431
552
|
* - `adr` → {@link mintAdr} (mint a SELF-CONTAINED ADR into `docs/adr/` + `git
|
|
432
553
|
* rm` the source + sidecar in the SAME atomic commit; the SIBLING route for the
|
|
433
554
|
* off-board target);
|
|
434
|
-
* - `task` / `
|
|
555
|
+
* - `task` / `spec` → {@link promoteObservation} (mint a SELF-CONTAINED artifact +
|
|
435
556
|
* `git rm` the source + sidecar in the SAME atomic commit); the artifact type
|
|
436
557
|
* comes from the agent's VERDICT, NOT a human `promote-*` field;
|
|
437
|
-
* - `
|
|
438
|
-
*
|
|
558
|
+
* - `resolve` → {@link applyAnsweredQuestions} resolve-fully (harvest answers
|
|
559
|
+
* into the body, clear `needsAnswers`, delete the sidecar; the note is
|
|
560
|
+
* RETAINED — the sibling of `dispose` that mints nothing but keeps the note);
|
|
561
|
+
* - `dispose` → {@link applyAnsweredQuestions} regime-polymorphic disposal
|
|
562
|
+
* (task `apply-disposition-delete-to-dispose-regime-polymorphic`, spec
|
|
563
|
+
* `surface-stuck-as-questions-and-retire-stuck-lock-state` decision #5): an
|
|
564
|
+
* OBSERVATION is `git rm`-ed in one revertible commit (notes leave by
|
|
565
|
+
* deletion; reason in the message); a TASK is `git mv`-ed to
|
|
566
|
+
* `tasks/cancelled/` (RETAINED, `reason:` written into the moved body); a
|
|
567
|
+
* SPEC is `git mv`-ed to `specs/dropped/` (RETAINED). Making `dispose`
|
|
568
|
+
* polymorphic (rather than a literal `delete`) makes "a task cannot be
|
|
569
|
+
* hard-deleted by the apply rung, only disposed to its terminal" true BY
|
|
570
|
+
* CONSTRUCTION.
|
|
439
571
|
*
|
|
440
572
|
* For a TASK/SPEC (answering its OWN open questions) or a caller-supplied follow-up
|
|
441
573
|
* batch, it delegates straight to {@link applyAnsweredQuestions} (resolve fully /
|
|
@@ -470,6 +602,25 @@ async function applyRung(input) {
|
|
|
470
602
|
if (mergeRoute !== undefined) {
|
|
471
603
|
return mergeRoute;
|
|
472
604
|
}
|
|
605
|
+
// RUNNER-ACTION KIND-CHECK for `kind: 'stuck'` (task
|
|
606
|
+
// `apply-resolve-reset-flag-discards-work-branch`, spec
|
|
607
|
+
// `surface-stuck-as-questions-and-retire-stuck-lock-state` decision #6): a
|
|
608
|
+
// bounced TASK surfaces as a `kind: 'stuck'` question; the human's plain
|
|
609
|
+
// `keep | reset | cancel` answer drives one of the three deterministic
|
|
610
|
+
// verbs. The DIRECT SIBLING of `maybeRunMergeAction` above (there is NO
|
|
611
|
+
// agentic decider on the TASK apply path — `runAgenticDecision` fires only
|
|
612
|
+
// for observations — so a bounced task's answer MUST be sourced from a
|
|
613
|
+
// deterministic parse+dispatch, not a widened decider). `keep` and `reset`
|
|
614
|
+
// fall through to the normal fall-through persist (with the branch
|
|
615
|
+
// pre-deleted on `reset` via the SHARED
|
|
616
|
+
// `deleteRemoteWorkBranchIfPresent`); `cancel` dispatches through
|
|
617
|
+
// `applyAnsweredQuestions`' `dispose` option (task →
|
|
618
|
+
// `tasks/cancelled/`); `refused` short-circuits with the sidecar left in
|
|
619
|
+
// place for a re-answer.
|
|
620
|
+
const stuckRoute = await maybeRunStuckAction(input, itemPath);
|
|
621
|
+
if (stuckRoute !== undefined) {
|
|
622
|
+
return stuckRoute;
|
|
623
|
+
}
|
|
473
624
|
// AGENTIC APPLY for an answered OBSERVATION (the subsumed triage rung): run the
|
|
474
625
|
// shared decision engine over the answer(s) + source, route the verdict. A
|
|
475
626
|
// caller-supplied follow-up batch (`applyFollowups`) bypasses the decision and
|
|
@@ -595,8 +746,13 @@ async function maybeRunMergeAction(input, itemPath) {
|
|
|
595
746
|
// bounce to needs-attention through its own shared seam). SHORT-CIRCUIT:
|
|
596
747
|
// leave the sidecar so the open answer stays surfaced — the apply rung
|
|
597
748
|
// MUST NOT also resolve it (the next surfacer / human will follow up).
|
|
749
|
+
// Outcome tag `merge-refused` (task `merge-action-nits-followup` nit 2):
|
|
750
|
+
// distinct from `usage-error` — which is reserved for genuine caller-usage
|
|
751
|
+
// errors (e.g. the workspacesDir-unset guard above) — so reviewers can
|
|
752
|
+
// grep the refusal-on-rebased-tip signal without confusing it with a
|
|
753
|
+
// misuse. `exitCode: 1` preserved.
|
|
598
754
|
note(result.message);
|
|
599
|
-
return { exitCode: 1, outcome: '
|
|
755
|
+
return { exitCode: 1, outcome: 'merge-refused', message: result.message };
|
|
600
756
|
}
|
|
601
757
|
if (result.outcome === 'restale') {
|
|
602
758
|
// `strictMergeApproval` re-surface: append a follow-up question and
|
|
@@ -661,6 +817,105 @@ async function maybeRunMergeAction(input, itemPath) {
|
|
|
661
817
|
};
|
|
662
818
|
}
|
|
663
819
|
}
|
|
820
|
+
/**
|
|
821
|
+
* Dispatch an answered STUCK-QUESTION (a sidecar entry stamped `kind: 'stuck'`
|
|
822
|
+
* by the bounce-surface path) through the deterministic
|
|
823
|
+
* {@link performStuckAction} sibling. The DIRECT SIBLING of
|
|
824
|
+
* {@link maybeRunMergeAction} for the `kind: 'stuck'` axis (task
|
|
825
|
+
* `apply-resolve-reset-flag-discards-work-branch`, spec
|
|
826
|
+
* `surface-stuck-as-questions-and-retire-stuck-lock-state` decision #6):
|
|
827
|
+
*
|
|
828
|
+
* - `keep` -> fall through to today's normal `applyAnsweredQuestions`
|
|
829
|
+
* resolve (branch untouched; continue-from-WIP);
|
|
830
|
+
* - `reset` -> the SHARED `deleteRemoteWorkBranchIfPresent` primitive
|
|
831
|
+
* discards the remote `work/task-<slug>` FIRST, then the
|
|
832
|
+
* normal fall-through persist clears `needsAnswers`. Safely
|
|
833
|
+
* IDEMPOTENT when no branch exists (an item never built) —
|
|
834
|
+
* an `already-gone` push is tolerated as a no-op;
|
|
835
|
+
* - `cancel` -> fall through to the normal persist with the `dispose`
|
|
836
|
+
* option (`git mv -> tasks/cancelled/`), the answer text
|
|
837
|
+
* recorded as the human's reason;
|
|
838
|
+
* - `refused` -> a REAL push-delete failure aborted the discard;
|
|
839
|
+
* SHORT-CIRCUIT with the sidecar left in place so the human
|
|
840
|
+
* sees the failure and re-answers (matches the
|
|
841
|
+
* `requeue --reset` abort-on-failed-delete contract).
|
|
842
|
+
*
|
|
843
|
+
* Returns `undefined` when no answered `kind: 'stuck'` entry is present (the
|
|
844
|
+
* apply rung then proceeds to the existing path — an unrelated content
|
|
845
|
+
* question falls through to the normal `applyAnsweredQuestions` /
|
|
846
|
+
* agentic-decider path).
|
|
847
|
+
*/
|
|
848
|
+
async function maybeRunStuckAction(input, itemPath) {
|
|
849
|
+
const { item, context } = input;
|
|
850
|
+
const cwd = context.cwd;
|
|
851
|
+
const note = context.note ?? (() => { });
|
|
852
|
+
const detected = detectAnsweredStuckAction(cwd, item);
|
|
853
|
+
if (detected === undefined)
|
|
854
|
+
return undefined;
|
|
855
|
+
const handler = context.stuckAction ?? performStuckAction;
|
|
856
|
+
let result;
|
|
857
|
+
try {
|
|
858
|
+
result = await handler({
|
|
859
|
+
action: detected,
|
|
860
|
+
item,
|
|
861
|
+
slug: input.slug,
|
|
862
|
+
cwd,
|
|
863
|
+
arbiter: context.arbiter ?? DEFAULT_ARBITER,
|
|
864
|
+
note,
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
catch (err) {
|
|
868
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
869
|
+
const message = `apply ${item}: answered stuck-question dispatch raised (${detail}); NOT ` +
|
|
870
|
+
`clearing needsAnswers; the sidecar stays surfaced.`;
|
|
871
|
+
note(message);
|
|
872
|
+
return { exitCode: 1, outcome: 'usage-error', message };
|
|
873
|
+
}
|
|
874
|
+
if (result.outcome === 'refused') {
|
|
875
|
+
// The arbiter delete FAILED (not `already-gone`); we MUST NOT clear
|
|
876
|
+
// needsAnswers, because that would leave the item claimable while still
|
|
877
|
+
// carrying the WIP branch we meant to discard — the very stale-continue
|
|
878
|
+
// trap the `requeue --reset` path guards against. Leave the sidecar in
|
|
879
|
+
// place for a re-answer.
|
|
880
|
+
note(result.message);
|
|
881
|
+
return { exitCode: 1, outcome: 'usage-error', message: result.message };
|
|
882
|
+
}
|
|
883
|
+
note(result.message);
|
|
884
|
+
const apply = context.applyPersist ?? applyAnsweredQuestions;
|
|
885
|
+
try {
|
|
886
|
+
const applyOptions = {
|
|
887
|
+
cwd,
|
|
888
|
+
item,
|
|
889
|
+
itemPath,
|
|
890
|
+
note,
|
|
891
|
+
};
|
|
892
|
+
if (result.outcome === 'cancel') {
|
|
893
|
+
// The human's answer text is the dispose reason (verbatim), so the
|
|
894
|
+
// terminal `tasks/cancelled/` body records WHY — the same reason
|
|
895
|
+
// contract the agentic `dispose` verdict uses.
|
|
896
|
+
applyOptions.dispose = { reason: detected.entry.answer.trim() };
|
|
897
|
+
}
|
|
898
|
+
const applied = apply(applyOptions);
|
|
899
|
+
const mapped = applied.outcome === 'repaused'
|
|
900
|
+
? 'no-op'
|
|
901
|
+
: applied.outcome === 'vanished'
|
|
902
|
+
? 'vanished'
|
|
903
|
+
: 'advanced';
|
|
904
|
+
return {
|
|
905
|
+
exitCode: 0,
|
|
906
|
+
outcome: mapped,
|
|
907
|
+
message: `${result.message} ${applied.message}`,
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
catch (err) {
|
|
911
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
912
|
+
return {
|
|
913
|
+
exitCode: 1,
|
|
914
|
+
outcome: 'usage-error',
|
|
915
|
+
message: `apply ${item}: ${detail}`,
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
}
|
|
664
919
|
/**
|
|
665
920
|
* The cross-tick-window BENIGN SKIP shared by all three rungs that need to
|
|
666
921
|
* resolve an item file (surface / triage / apply). The lifecycle pool enumerated
|
|
@@ -706,15 +961,24 @@ function findItemPath(cwd, namespace, slug) {
|
|
|
706
961
|
*
|
|
707
962
|
* - `ask` → append the follow-up question(s) + re-pause (the EXISTING loop, via
|
|
708
963
|
* {@link applyAnsweredQuestions}'s `appendQuestions`);
|
|
709
|
-
* - `task` / `
|
|
964
|
+
* - `task` / `spec` → {@link promoteObservation} (mint self-contained + delete
|
|
710
965
|
* source in the same atomic commit);
|
|
711
966
|
* - `adr` → {@link mintAdr} (mint a self-contained ADR into `docs/adr/` + delete
|
|
712
967
|
* source in the same atomic commit; the SIBLING route for the off-board target,
|
|
713
968
|
* task `agentic-apply-mint-adr-route`);
|
|
714
|
-
* - `
|
|
715
|
-
*
|
|
969
|
+
* - `resolve` → {@link applyAnsweredQuestions}'s resolve-fully path (harvest the
|
|
970
|
+
* answers into `## Applied answers`, strip the open-questions block, clear
|
|
971
|
+
* `needsAnswers`, DELETE the sidecar — the note is RETAINED). The sibling of
|
|
972
|
+
* `dispose` that KEEPS the note instead of moving/rm-ing it (task
|
|
973
|
+
* `apply-decide-resolve-verdict-mint-nothing`);
|
|
974
|
+
* - `dispose` → {@link applyAnsweredQuestions}'s regime-polymorphic disposal
|
|
975
|
+
* (task `apply-disposition-delete-to-dispose-regime-polymorphic`): an
|
|
976
|
+
* observation is `git rm`-ed in one revertible commit (reason in the
|
|
977
|
+
* message); a task is `git mv`-ed to `tasks/cancelled/` (reason written into
|
|
978
|
+
* the body); a spec is `git mv`-ed to `specs/dropped/`.
|
|
716
979
|
*
|
|
717
|
-
* The allowed set is `{task |
|
|
980
|
+
* The allowed set is `{task | spec | adr | dispose | resolve | ask}`; a verdict
|
|
981
|
+
* outside it is
|
|
718
982
|
* rejected by the engine's allowed-outcome guard ({@link DisallowedOutcomeError})
|
|
719
983
|
* and mapped onto a usage-error — never dispatched.
|
|
720
984
|
*/
|
|
@@ -812,11 +1076,13 @@ async function applyAgenticDecision(input, itemPath) {
|
|
|
812
1076
|
exitCode: result.exitCode,
|
|
813
1077
|
outcome: result.outcome === 'promoted'
|
|
814
1078
|
? 'advanced'
|
|
815
|
-
: result.outcome === '
|
|
816
|
-
? '
|
|
817
|
-
: result.outcome === '
|
|
818
|
-
? '
|
|
819
|
-
: '
|
|
1079
|
+
: result.outcome === 'already-triaged'
|
|
1080
|
+
? 'already-triaged'
|
|
1081
|
+
: result.outcome === 'lost'
|
|
1082
|
+
? 'lost'
|
|
1083
|
+
: result.outcome === 'contended'
|
|
1084
|
+
? 'contended'
|
|
1085
|
+
: 'usage-error',
|
|
820
1086
|
message: result.message,
|
|
821
1087
|
};
|
|
822
1088
|
}
|
|
@@ -880,16 +1146,53 @@ async function applyAgenticDecision(input, itemPath) {
|
|
|
880
1146
|
};
|
|
881
1147
|
}
|
|
882
1148
|
}
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
1149
|
+
if (verdict.outcome === 'resolve') {
|
|
1150
|
+
// resolve-no-mint → the EXISTING resolve-fully path (task
|
|
1151
|
+
// `apply-decide-resolve-verdict-mint-nothing`). The answer SETTLES the item
|
|
1152
|
+
// with NOTHING to mint and the note RETAINED: call `applyAnsweredQuestions`
|
|
1153
|
+
// with NEITHER `appendQuestions` (re-pause) NOR `dispose` (drop/terminal), so it
|
|
1154
|
+
// takes the default resolve-fully branch — harvest the answers into `##
|
|
1155
|
+
// Applied answers`, strip the marker-fenced open-questions block, clear
|
|
1156
|
+
// `needsAnswers`, and DELETE the sidecar in ONE atomic commit. Invariant-clean
|
|
1157
|
+
// (`needsAnswers:false` ⟺ no active sidecar). The note file is KEPT (this is
|
|
1158
|
+
// the sibling of `dispose`, which git-rm's it or moves it to a terminal). `resolveReason` is advisory
|
|
1159
|
+
// context only; the durable disposition record is the harvested `## Applied
|
|
1160
|
+
// answers` block the resolve-fully path writes (NOT a separate convention).
|
|
1161
|
+
const apply = context.applyPersist ?? applyAnsweredQuestions;
|
|
1162
|
+
try {
|
|
1163
|
+
const result = apply({ cwd, item, itemPath, note });
|
|
1164
|
+
return {
|
|
1165
|
+
exitCode: 0,
|
|
1166
|
+
outcome: result.outcome === 'vanished' ? 'vanished' : 'advanced',
|
|
1167
|
+
message: result.message,
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
catch (err) {
|
|
1171
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
1172
|
+
return {
|
|
1173
|
+
exitCode: 1,
|
|
1174
|
+
outcome: 'usage-error',
|
|
1175
|
+
message: `apply ${item}: ${detail}`,
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
// dispose-source → regime-polymorphic disposal (DIRECT, no confirm — decision
|
|
1180
|
+
// 12; task `apply-disposition-delete-to-dispose-regime-polymorphic`, spec
|
|
1181
|
+
// `surface-stuck-as-questions-and-retire-stuck-lock-state` decision #5). The
|
|
1182
|
+
// human's answer is the source of truth; the persist chooses the on-disk
|
|
1183
|
+
// effect by the source's regime: an OBSERVATION is `git rm`-ed in one
|
|
1184
|
+
// revertible commit (reason in the message, git history = archive); a TASK is
|
|
1185
|
+
// `git mv`-ed to `tasks/cancelled/` (RETAINED, `reason:` written into the
|
|
1186
|
+
// moved body); a SPEC is `git mv`-ed to `specs/dropped/` (RETAINED). A task
|
|
1187
|
+
// cannot be hard-deleted by the apply rung — dispose is the only path off the
|
|
1188
|
+
// board.
|
|
886
1189
|
const apply = context.applyPersist ?? applyAnsweredQuestions;
|
|
887
1190
|
try {
|
|
888
1191
|
const result = apply({
|
|
889
1192
|
cwd,
|
|
890
1193
|
item,
|
|
891
1194
|
itemPath,
|
|
892
|
-
|
|
1195
|
+
dispose: { reason: verdict.disposeReason ?? '' },
|
|
893
1196
|
note,
|
|
894
1197
|
});
|
|
895
1198
|
return {
|
|
@@ -1069,6 +1372,10 @@ export async function performAdvance(options) {
|
|
|
1069
1372
|
verify: options.verify,
|
|
1070
1373
|
strictMergeApproval: options.strictMergeApproval,
|
|
1071
1374
|
mergeAction: options.mergeAction,
|
|
1375
|
+
// The answered-stuck-question dispatch context (task
|
|
1376
|
+
// `apply-resolve-reset-flag-discards-work-branch`): threaded into the
|
|
1377
|
+
// apply rung's kind-check for a `kind: 'stuck'` runner-action.
|
|
1378
|
+
stuckAction: options.stuckAction,
|
|
1072
1379
|
note,
|
|
1073
1380
|
},
|
|
1074
1381
|
});
|