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/advancing-lock.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {dirname, join} from 'node:path';
|
|
|
3
3
|
import {runAsync, type RunResult} from './git.js';
|
|
4
4
|
import {ledgerWrite} from './ledger-write.js';
|
|
5
5
|
import {resolveSidecarIdentity} from './sidecar.js';
|
|
6
|
+
import {readFrontmatterField} from './frontmatter.js';
|
|
6
7
|
import {acquireItemLock, releaseItemLock} from './item-lock.js';
|
|
7
8
|
import {realSleep, type Sleep} from './retry-backoff.js';
|
|
8
9
|
|
|
@@ -545,11 +546,25 @@ export function nextCasContentionDelayMs(input: {
|
|
|
545
546
|
return Math.max(base, Math.min(cap, Math.floor(base + r * (upper - base))));
|
|
546
547
|
}
|
|
547
548
|
|
|
548
|
-
/**
|
|
549
|
+
/**
|
|
550
|
+
* A semantic label for the new-item creation outcome.
|
|
551
|
+
*
|
|
552
|
+
* `already-exists-from-source` (task
|
|
553
|
+
* `observation-triage-already-triaged-benign-skip`): the target path already
|
|
554
|
+
* exists on `<arbiter>/main` AND that existing item is PROVABLY the one this same
|
|
555
|
+
* `sourceItem` already minted (its `promotedFrom:` back-reference equals
|
|
556
|
+
* `sourceItem`). This is a TERMINAL idempotency fact — the source was already
|
|
557
|
+
* triaged in a prior run and a retry can NEVER succeed — so it is a BENIGN SKIP
|
|
558
|
+
* (`exitCode: 0`), NOT the loud `lost` (exit 2) a genuine concurrent-create race
|
|
559
|
+
* gets. Distinguishable from `created`/`lost` so a caller (and CI) can grep it out.
|
|
560
|
+
* Only ever produced when the caller passes `sourceItem`; without it the
|
|
561
|
+
* path-exists case stays `lost` exactly as before (zero behaviour change).
|
|
562
|
+
*/
|
|
549
563
|
export type CreateItemOutcome =
|
|
550
564
|
| 'created'
|
|
551
565
|
| 'usage-error'
|
|
552
566
|
| 'lost'
|
|
567
|
+
| 'already-exists-from-source'
|
|
553
568
|
| 'contended';
|
|
554
569
|
|
|
555
570
|
/** Maps onto the claim-CAS exit codes (identical semantics). */
|
|
@@ -574,6 +589,21 @@ export interface CreateItemThroughCasOptions {
|
|
|
574
589
|
* A listed path that is absent on the create branch is skipped (best-effort rm).
|
|
575
590
|
*/
|
|
576
591
|
deletePaths?: string[];
|
|
592
|
+
/**
|
|
593
|
+
* OPTIONAL provenance disambiguator for the LOST-race case (task
|
|
594
|
+
* `observation-triage-already-triaged-benign-skip`). When set, this is the
|
|
595
|
+
* namespaced identity of the SOURCE item this create was minted FROM (e.g.
|
|
596
|
+
* `observation:<slug>`). If the target `path` already exists on `<arbiter>/main`
|
|
597
|
+
* AND that existing file's `promotedFrom:` back-reference equals THIS `sourceItem`,
|
|
598
|
+
* the create is TERMINAL-by-existence (the source was already triaged in a prior
|
|
599
|
+
* run) — a BENIGN SKIP (`already-exists-from-source`, exit 0), NOT the loud `lost`
|
|
600
|
+
* (exit 2) a genuine concurrent-create race with an UNRELATED same-path item gets.
|
|
601
|
+
* The match is on the explicit back-reference, NOT the slug, so an unrelated task
|
|
602
|
+
* that merely shares the slug does NOT false-positive into a benign skip. Absent
|
|
603
|
+
* (or the existing file carries no matching `promotedFrom:`) ⇒ the path-exists case
|
|
604
|
+
* stays `lost` exactly as before (zero behaviour change for non-provenance callers).
|
|
605
|
+
*/
|
|
606
|
+
sourceItem?: string;
|
|
577
607
|
/** Working clone/worktree the creation runs in. */
|
|
578
608
|
cwd: string;
|
|
579
609
|
/** Name of the arbiter remote (`--arbiter`). Defaults to `origin`. */
|
|
@@ -688,6 +718,36 @@ async function runCreate(
|
|
|
688
718
|
);
|
|
689
719
|
}
|
|
690
720
|
|
|
721
|
+
// TERMINAL 'already triaged' PRE-CHECK (case A; task
|
|
722
|
+
// `observation-triage-already-triaged-benign-skip`). When the caller passes
|
|
723
|
+
// `sourceItem`, check ONCE UP FRONT whether the target already exists on the
|
|
724
|
+
// arbiter AND was PROVABLY minted from THIS same source in a PRIOR run (its
|
|
725
|
+
// `promotedFrom:` back-reference matches). If so this is an idempotency fact, not
|
|
726
|
+
// a race: a BENIGN SKIP (exit 0), never a retry. Doing this BEFORE the contention
|
|
727
|
+
// loop is what keeps it distinct from case B (a LIVE concurrent-create race): a
|
|
728
|
+
// race winner's file appears DURING the loop (the target is absent at this
|
|
729
|
+
// pre-check), so the loser still funnels through the loop to the loud `lost`
|
|
730
|
+
// (exit 2) the existing same-NEW-task race semantics require. Only a task that
|
|
731
|
+
// ALREADY existed before this leg began resolves here.
|
|
732
|
+
if (options.sourceItem !== undefined && options.sourceItem !== '') {
|
|
733
|
+
await gitHard(['fetch', '--quiet', arbiter], cwd, env);
|
|
734
|
+
const skip = await alreadyMintedFromSource(
|
|
735
|
+
options.sourceItem,
|
|
736
|
+
path,
|
|
737
|
+
arbiter,
|
|
738
|
+
cwd,
|
|
739
|
+
env,
|
|
740
|
+
);
|
|
741
|
+
if (skip !== undefined) {
|
|
742
|
+
note(skip);
|
|
743
|
+
return {
|
|
744
|
+
exitCode: 0,
|
|
745
|
+
outcome: 'already-exists-from-source',
|
|
746
|
+
message: skip,
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
691
751
|
// A throwaway create branch, slugified off the new item's path (so concurrent
|
|
692
752
|
// creators of DIFFERENT paths don't collide on the branch name either).
|
|
693
753
|
const createBranch = `create/${path.replace(/[^A-Za-z0-9._-]+/g, '-')}`;
|
|
@@ -911,6 +971,41 @@ async function catFileExists(
|
|
|
911
971
|
return (await gitSoft(['cat-file', '-e', object], cwd, env)).status === 0;
|
|
912
972
|
}
|
|
913
973
|
|
|
974
|
+
/**
|
|
975
|
+
* The LOST-race case-A disambiguator (task
|
|
976
|
+
* `observation-triage-already-triaged-benign-skip`): is the item ALREADY at `path`
|
|
977
|
+
* on `<arbiter>/main` PROVABLY the one `sourceItem` already minted? Reads that
|
|
978
|
+
* file's `promotedFrom:` back-reference and returns a benign-skip MESSAGE iff it
|
|
979
|
+
* equals `sourceItem`, else `undefined` (⇒ the caller keeps the loud `lost`).
|
|
980
|
+
*
|
|
981
|
+
* The match is on the EXPLICIT `promotedFrom:` back-reference, NOT the slug, so an
|
|
982
|
+
* unrelated task that merely shares the slug (or a slug PREFIX) never
|
|
983
|
+
* false-positives — only an item this exact source stamped resolves to the skip. A
|
|
984
|
+
* plumbing failure reading the existing file degrades to `undefined` (stay loud),
|
|
985
|
+
* never a false benign skip.
|
|
986
|
+
*/
|
|
987
|
+
async function alreadyMintedFromSource(
|
|
988
|
+
sourceItem: string,
|
|
989
|
+
path: string,
|
|
990
|
+
arbiter: string,
|
|
991
|
+
cwd: string,
|
|
992
|
+
env: NodeJS.ProcessEnv | undefined,
|
|
993
|
+
): Promise<string | undefined> {
|
|
994
|
+
const show = await gitSoft(['show', `${arbiter}/main:${path}`], cwd, env);
|
|
995
|
+
if (show.status !== 0) {
|
|
996
|
+
return undefined;
|
|
997
|
+
}
|
|
998
|
+
const promotedFrom = readFrontmatterField(show.stdout, 'promotedFrom');
|
|
999
|
+
if (promotedFrom !== sourceItem) {
|
|
1000
|
+
return undefined;
|
|
1001
|
+
}
|
|
1002
|
+
return (
|
|
1003
|
+
`'${path}' already exists on ${arbiter}/main and was PROVABLY minted from ` +
|
|
1004
|
+
`${sourceItem} (its promotedFrom: back-reference matches) — already triaged in ` +
|
|
1005
|
+
`a prior run. Terminal by existence; a retry can never succeed. Benign skip.`
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
914
1009
|
/** Run git, returning the raw result (no throw) — for soft checks. */
|
|
915
1010
|
function gitSoft(
|
|
916
1011
|
args: string[],
|
package/src/agent-launch.ts
CHANGED
|
@@ -66,6 +66,18 @@ export interface LaunchWithOptionalWatchInput {
|
|
|
66
66
|
sessionsDir?: string;
|
|
67
67
|
/** Tail the session `.jsonl` live (pi harness only). */
|
|
68
68
|
watch?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* **Optional agent-session DEADLINE** (absolute epoch-ms wall-clock timestamp,
|
|
71
|
+
* spec `graceful-pre-timeout-wip-checkpoint`) threaded through to
|
|
72
|
+
* {@link LaunchInput.deadlineMs}. Forces the ASYNC {@link
|
|
73
|
+
* PiHarness.launchAsync} path (even without `watch`), because the deadline
|
|
74
|
+
* race only works there — `spawnSync` cannot be interrupted from the same
|
|
75
|
+
* event loop. A non-pi harness with a deadline set silently degrades to a
|
|
76
|
+
* plain `harness.launch` (no race is possible), the same safe fall-through
|
|
77
|
+
* used for `watch` on a non-pi harness. Absent ⇒ no deadline; a run that
|
|
78
|
+
* finishes normally is byte-for-byte unchanged.
|
|
79
|
+
*/
|
|
80
|
+
deadlineMs?: number;
|
|
69
81
|
/** Where the tailed high-signal lines are written (defaults to stderr). */
|
|
70
82
|
watchSink?: (line: string) => void;
|
|
71
83
|
/** Emit ANSI colour in the tailed lines (the caller's TTY/`NO_COLOR` decision). */
|
|
@@ -97,17 +109,27 @@ export async function launchWithOptionalWatch(
|
|
|
97
109
|
id: input.sessionId,
|
|
98
110
|
});
|
|
99
111
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
112
|
+
const wantsAsync =
|
|
113
|
+
(input.watch === true || input.deadlineMs !== undefined) &&
|
|
114
|
+
harness instanceof PiHarness;
|
|
115
|
+
|
|
116
|
+
// `--watch` OR a threaded deadline (spec
|
|
117
|
+
// `graceful-pre-timeout-wip-checkpoint`) forces the ASYNC path: the deadline
|
|
118
|
+
// race is only expressible against `launchAsync` (a spawnSync child cannot be
|
|
119
|
+
// SIGTERMed from the same event loop), and `--watch` needs it to tail the
|
|
120
|
+
// session log concurrently. Both are pi-only — a non-pi harness here falls
|
|
121
|
+
// through to the sync launch below (safe: watch would have no log to tail,
|
|
122
|
+
// and the deadline was already opt-in).
|
|
123
|
+
if (wantsAsync) {
|
|
124
|
+
const tailer =
|
|
125
|
+
input.watch === true
|
|
126
|
+
? new SessionTailer({
|
|
127
|
+
sessionFile: session,
|
|
128
|
+
color: input.color ?? false,
|
|
129
|
+
sink: input.watchSink,
|
|
130
|
+
})
|
|
131
|
+
: undefined;
|
|
132
|
+
tailer?.start();
|
|
111
133
|
try {
|
|
112
134
|
return await harness.launchAsync({
|
|
113
135
|
dir: input.dir,
|
|
@@ -116,12 +138,15 @@ export async function launchWithOptionalWatch(
|
|
|
116
138
|
prompt: input.prompt,
|
|
117
139
|
model: input.model,
|
|
118
140
|
session,
|
|
141
|
+
deadlineMs: input.deadlineMs,
|
|
119
142
|
env: input.env,
|
|
120
143
|
});
|
|
121
144
|
} finally {
|
|
122
145
|
// Always release the tailer (one final drain) — even on a launch error —
|
|
123
146
|
// so the observer never outlives the run or leaks a handle.
|
|
124
|
-
|
|
147
|
+
if (tailer) {
|
|
148
|
+
await tailer.stop();
|
|
149
|
+
}
|
|
125
150
|
}
|
|
126
151
|
}
|
|
127
152
|
|
package/src/agent-stop.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {runAsync} from './git.js';
|
|
2
2
|
import {JOB_RECORD_FILENAME} from './workspace.js';
|
|
3
|
+
import type {NewQuestion} from './sidecar.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* **The build-agent → runner REPORTING CHANNEL** (task `agent-stop-signal`).
|
|
@@ -11,8 +12,10 @@ import {JOB_RECORD_FILENAME} from './workspace.js';
|
|
|
11
12
|
*
|
|
12
13
|
* 1. A HARD **STOP** sentinel ({@link STOP_SENTINEL_OPEN} … {@link
|
|
13
14
|
* STOP_SENTINEL_CLOSE}): the task DRIFTED / is ambiguous / rests on a stale
|
|
14
|
-
* premise, so the agent could not build it. The runner
|
|
15
|
-
*
|
|
15
|
+
* premise, so the agent could not build it. The runner marks the item stuck
|
|
16
|
+
* on its per-item lock (post lock-cutover — `state: stuck` with the agent's
|
|
17
|
+
* reason VERBATIM on the lock entry, no `work/needs-attention/` folder
|
|
18
|
+
* write) and SKIPS the
|
|
16
19
|
* acceptance gate + the Gate-2 review (a clean STOP is not a build that
|
|
17
20
|
* changed nothing). See {@link parseStopSentinel}.
|
|
18
21
|
* 2. A SOFT **`## Decisions`** block ({@link extractDecisionsBlock}): the agent
|
|
@@ -359,3 +362,58 @@ export function emptyDiffStopReason(slug: string): string {
|
|
|
359
362
|
'arbiter main); treating as a no-op/stop — re-scope or re-claim.'
|
|
360
363
|
);
|
|
361
364
|
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* The DISCRIMINATED classification of a `saveAgentStop` reason (spec
|
|
368
|
+
* `surface-stuck-as-questions-and-retire-stuck-lock-state` resolved decision #2,
|
|
369
|
+
* task `empty-diff-bounce-surfaces-dispose-defaulted-question`). The two kinds
|
|
370
|
+
* route DIFFERENTLY at the bounce seam:
|
|
371
|
+
*
|
|
372
|
+
* - `'sentinel'` — the agent raised a hard STOP with a specific drift report
|
|
373
|
+
* (the principled case). The runner surfaces the reason VERBATIM through the
|
|
374
|
+
* existing lock-stuck bounce path.
|
|
375
|
+
* - `'empty-diff'` — the agent produced no source change (the deterministic
|
|
376
|
+
* backstop). Because "nothing to do" is a NON-DETERMINISTIC LLM judgement, a
|
|
377
|
+
* blind requeue would infinite-loop; instead the runner surfaces a sidecar
|
|
378
|
+
* with an engine-authored DISPOSE-DEFAULTED disposition question. The engine
|
|
379
|
+
* owns the envelope + the safe default; the LLM owns the prose.
|
|
380
|
+
*/
|
|
381
|
+
export type AgentStopKind = 'sentinel' | 'empty-diff';
|
|
382
|
+
|
|
383
|
+
/** A classified STOP verdict + the needs-attention reason prose. */
|
|
384
|
+
export interface AgentStopClass {
|
|
385
|
+
kind: AgentStopKind;
|
|
386
|
+
reason: string;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* The DISPOSE-DEFAULTED disposition question the empty-diff bounce path surfaces
|
|
391
|
+
* as its engine-authored envelope entry (spec resolved decision #2). The engine
|
|
392
|
+
* GUARANTEES this envelope exists on every empty-diff bounce even when the
|
|
393
|
+
* agent surfaced no questions of its own; the LLM/human owns the prose that
|
|
394
|
+
* follows. The `default: 'dispose'` hint routes an answered "cancel" to the
|
|
395
|
+
* regime-polymorphic `dispose` outcome (task → `git mv → tasks/cancelled/`,
|
|
396
|
+
* RETAINED; NOT a `git rm`, NOT a requeue) via the shared apply decider
|
|
397
|
+
* (`apply-decide.ts`).
|
|
398
|
+
*/
|
|
399
|
+
export function emptyDiffDisposeEnvelope(params: {
|
|
400
|
+
item: string;
|
|
401
|
+
reason: string;
|
|
402
|
+
}): NewQuestion {
|
|
403
|
+
const {item, reason} = params;
|
|
404
|
+
return {
|
|
405
|
+
question:
|
|
406
|
+
`'${item}': the agent produced no change (${reason}). ` +
|
|
407
|
+
'Cancel this item? [default: yes]',
|
|
408
|
+
context:
|
|
409
|
+
'"Nothing to do" is a non-deterministic LLM judgement, so this bounce is ' +
|
|
410
|
+
'SURFACED (not blindly requeued) to break any infinite "re-run → re-judge ' +
|
|
411
|
+
'nothing-to-do → re-bounce" loop. Answering "cancel" (or accepting the ' +
|
|
412
|
+
'default) disposes this task to its terminal (`git mv → ' +
|
|
413
|
+
'`work/tasks/cancelled/`, retained) via the regime-polymorphic `dispose` ' +
|
|
414
|
+
'outcome — the task is NOT hard-deleted. Answer with a REQUEUE / RESET ' +
|
|
415
|
+
'directive if you disagree with the agent and want the loop to try again.',
|
|
416
|
+
default: 'dispose (cancel this task → work/tasks/cancelled/, retained)',
|
|
417
|
+
kind: 'stuck',
|
|
418
|
+
};
|
|
419
|
+
}
|
package/src/apply-decide.ts
CHANGED
|
@@ -29,8 +29,19 @@ import {
|
|
|
29
29
|
* from the agent's VERDICT, NOT a human `promote-*` field.
|
|
30
30
|
*
|
|
31
31
|
* **The allowed set** (the SUBSET the apply rung passes to `decide`):
|
|
32
|
-
* `{task | spec | adr |
|
|
33
|
-
*
|
|
32
|
+
* `{task | spec | adr | dispose | resolve | ask}` \u2014 i.e. `{mint-task | mint-spec |
|
|
33
|
+
* mint-adr | dispose-source | resolve-no-mint | ask-follow-up}`. The `dispose`
|
|
34
|
+
* verdict replaced the older `delete` token (task
|
|
35
|
+
* `apply-disposition-delete-to-dispose-regime-polymorphic`, spec
|
|
36
|
+
* `surface-stuck-as-questions-and-retire-stuck-lock-state` decision #5): the
|
|
37
|
+
* disposal is now REGIME-POLYMORPHIC (observation \u2192 `git rm`, task \u2192
|
|
38
|
+
* `git mv` to `tasks/cancelled/`, spec \u2192 `git mv` to `specs/dropped/`), so a
|
|
39
|
+
* task can no longer be hard-deleted by the apply rung. `resolve` was added
|
|
40
|
+
* by the task `apply-decide-resolve-verdict-mint-nothing` so the decider can
|
|
41
|
+
* honestly handle "the human answered, keep the note on record, mint nothing"
|
|
42
|
+
* (previously it had no valid verdict for that case and looped on `ask`,
|
|
43
|
+
* re-surfacing an already-answered question every tick). `adr` was DEFERRED at the
|
|
44
|
+
* keystone launch (no
|
|
34
45
|
* ADR-mint path existed yet) and is now WIRED by the follow-on task
|
|
35
46
|
* `agentic-apply-mint-adr-route`, which added the {@link
|
|
36
47
|
* import('./mint-adr.js').mintAdr} route and widened this set. Intake's set is
|
|
@@ -54,7 +65,8 @@ export const APPLY_ALLOWED_OUTCOMES: readonly DecisionOutcome[] = [
|
|
|
54
65
|
'task',
|
|
55
66
|
'spec',
|
|
56
67
|
'adr',
|
|
57
|
-
'
|
|
68
|
+
'dispose',
|
|
69
|
+
'resolve',
|
|
58
70
|
'ask',
|
|
59
71
|
];
|
|
60
72
|
|
|
@@ -136,7 +148,10 @@ export function buildApplyDecisionInput(opts: {
|
|
|
136
148
|
* the human's recorded ANSWER(S) + the SOURCE item and emit a single
|
|
137
149
|
* `{outcome, \u2026}` verdict ({@link parseDecisionVerdict} reads it). The agent decides
|
|
138
150
|
* what to DO with the answered signal \u2014 mint a self-contained task, mint a SPEC,
|
|
139
|
-
* mint an ADR,
|
|
151
|
+
* mint an ADR, DISPOSE the source (regime-polymorphic: an observation is
|
|
152
|
+
* git-rm-ed, a task is `git mv`-ed to `tasks/cancelled/`, a spec to
|
|
153
|
+
* `specs/dropped/`), resolve it (settle the loop, mint nothing, KEEP the note),
|
|
154
|
+
* or ask one BATCH of follow-up questions \u2014
|
|
140
155
|
* grounded in the source's full context. It writes NOTHING (the engine acts on
|
|
141
156
|
* the verdict).
|
|
142
157
|
*
|
|
@@ -178,9 +193,23 @@ export function buildApplyDecisionPrompt(input: ApplyDecisionInput): string {
|
|
|
178
193
|
` a self-contained ADR (docs/adr/, the context/decision/why shape) carrying`,
|
|
179
194
|
` the WHY from the answer(s). Emit {"outcome":"adr","adrSlug":"\u2026",`,
|
|
180
195
|
` "adrTitle":"\u2026","adrBody":"\u2026 (markdown AFTER the frontmatter)"}.`,
|
|
181
|
-
` - "
|
|
182
|
-
` {"outcome":"
|
|
183
|
-
`
|
|
196
|
+
` - "dispose": the answer means this signal should be DISPOSED. Emit`,
|
|
197
|
+
` {"outcome":"dispose","disposeReason":"\u2026"}. REGIME-POLYMORPHIC on the`,
|
|
198
|
+
` SOURCE type: an OBSERVATION is git-rm-ed in a single revertible commit`,
|
|
199
|
+
` (reason rides the message, git history is the archive); a TASK is git-mv-ed`,
|
|
200
|
+
` to \`tasks/cancelled/\` (RETAINED, reason recorded in the moved body's`,
|
|
201
|
+
` \`reason:\` frontmatter); a SPEC is git-mv-ed to \`specs/dropped/\` (RETAINED).`,
|
|
202
|
+
` A task can NEVER be hard-deleted by the apply rung \u2014 dispose is the`,
|
|
203
|
+
` only path off the board.`,
|
|
204
|
+
` - "resolve": the answer SETTLES this item and there is NOTHING to mint (no`,
|
|
205
|
+
` task/spec/adr) \u2014 the correct move is to CLOSE the question-loop while`,
|
|
206
|
+
` KEEPING the note on record (e.g. an evidence/watch-item observation whose`,
|
|
207
|
+
` answer is "acknowledged, keep this on record, no artifact"). The answers are`,
|
|
208
|
+
` harvested into the item body and the loop is cleared; the note is RETAINED`,
|
|
209
|
+
` (this is the sibling of "dispose", which DROPS the observation-note or`,
|
|
210
|
+
` moves a task/spec to its terminal \u2014 pick "resolve" when the observation`,
|
|
211
|
+
` should SURVIVE in place, "dispose" when it should not). Emit`,
|
|
212
|
+
` {"outcome":"resolve","resolveReason":"\u2026"}.`,
|
|
184
213
|
` - "ask": you need more from the human before acting. Emit`,
|
|
185
214
|
` {"outcome":"ask","question":"\u2026"} \u2014 ask everything you still need as ONE`,
|
|
186
215
|
` batch (never a drip); the engine appends it and re-pauses.`,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {existsSync, readFileSync} from 'node:fs';
|
|
2
2
|
import {join} from 'node:path';
|
|
3
3
|
import {
|
|
4
|
-
allAnswered,
|
|
5
4
|
isEntryAnswered,
|
|
6
5
|
parseSidecar,
|
|
6
|
+
resolveSidecarIdentity,
|
|
7
7
|
sidecarPathFor,
|
|
8
8
|
type SidecarEntry,
|
|
9
9
|
type SidecarModel,
|
|
@@ -116,11 +116,23 @@ export function parseMergeAnswer(text: string): MergeActionVerb | undefined {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
/**
|
|
119
|
-
* Detect the
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
119
|
+
* Detect the `kind: merge` action verb that should drive the next apply run.
|
|
120
|
+
* The apply rung's pre-decider kind-check; returns `undefined` when there is
|
|
121
|
+
* nothing for the dispatcher to act on.
|
|
122
|
+
*
|
|
123
|
+
* ORDERING (task `merge-action-nits-followup` nit 1). The re-stale re-surface
|
|
124
|
+
* path (`advance.ts` `maybeRunMergeAction` restale branch) APPENDS a new
|
|
125
|
+
* `kind: merge` follow-up via `appendQuestions` rather than clearing the prior
|
|
126
|
+
* `answer=merge` on the original entry — that append-and-keep-history shape is
|
|
127
|
+
* canonical, but a naive FIRST-match lookup would re-fire against the STALE
|
|
128
|
+
* prior answer instead of the human's fresh follow-up. So:
|
|
129
|
+
*
|
|
130
|
+
* 1. If ANY `kind: merge` entry is UNANSWERED, return `undefined` — that is
|
|
131
|
+
* the re-paused state (a freshly-appended follow-up awaiting an answer),
|
|
132
|
+
* and the apply MUST NOT fire against a stale prior sibling.
|
|
133
|
+
* 2. Otherwise return the LATEST answered `kind: merge` entry — a fresh
|
|
134
|
+
* follow-up answer wins over the stale one, and a plain single-entry
|
|
135
|
+
* merge-question sidecar behaves as before.
|
|
124
136
|
*
|
|
125
137
|
* The sidecar is read OFF DISK keyed off the namespaced item identity; the
|
|
126
138
|
* model is parsed via the SAME `parseSidecar` the rest of the engine uses.
|
|
@@ -139,8 +151,13 @@ export function detectAnsweredMergeAction(
|
|
|
139
151
|
} catch {
|
|
140
152
|
return undefined;
|
|
141
153
|
}
|
|
142
|
-
|
|
154
|
+
// (1) Unanswered `kind: merge` follow-up ⇒ re-paused; the apply must NOT fire.
|
|
143
155
|
for (const entry of model.entries) {
|
|
156
|
+
if (entry.kind === 'merge' && !isEntryAnswered(entry)) return undefined;
|
|
157
|
+
}
|
|
158
|
+
// (2) LATEST answered `kind: merge` entry — a fresh follow-up beats a stale one.
|
|
159
|
+
for (let i = model.entries.length - 1; i >= 0; i--) {
|
|
160
|
+
const entry = model.entries[i];
|
|
144
161
|
if (entry.kind !== 'merge') continue;
|
|
145
162
|
if (!isEntryAnswered(entry)) continue;
|
|
146
163
|
const verb = parseMergeAnswer(entry.answer);
|
|
@@ -336,6 +353,27 @@ export async function performMergeAction(
|
|
|
336
353
|
const note = input.note ?? (() => {});
|
|
337
354
|
const {verb} = input.action;
|
|
338
355
|
|
|
356
|
+
// INVARIANT (task `merge-action-nits-followup` nit 4). The merge-question
|
|
357
|
+
// surfacer only emits `kind: merge` sidecars for TASKS, so the hard-coded
|
|
358
|
+
// `type: 'task'` on the `createJob` call below is correct in practice — but
|
|
359
|
+
// if a future surfacer change ever stamps a `kind: merge` sidecar on a
|
|
360
|
+
// non-task item (e.g. a spec-level unmerged branch), the branch name
|
|
361
|
+
// `work/task-<slug>` would silently mis-target. Fail LOUDLY here so the
|
|
362
|
+
// invariant is asserted at the dispatcher's entry rather than manifesting
|
|
363
|
+
// as a mysterious rebase/push mis-target downstream.
|
|
364
|
+
const identity = resolveSidecarIdentity(input.item);
|
|
365
|
+
if (identity.type !== 'task') {
|
|
366
|
+
throw new Error(
|
|
367
|
+
`performMergeAction: sidecar source item \`${input.item}\` has type ` +
|
|
368
|
+
`\`${identity.type}\`, but the answered-merge dispatcher only supports ` +
|
|
369
|
+
`\`task\` items (the surfacer only stamps \`kind: merge\` on tasks; the ` +
|
|
370
|
+
`branch name \`work/task-<slug>\` would silently mis-target otherwise). ` +
|
|
371
|
+
`This is the entry-invariant asserted by nit 4 of task ` +
|
|
372
|
+
`\`merge-action-nits-followup\` — reconcile the surfacer or extend the ` +
|
|
373
|
+
`dispatcher before landing a non-task source item.`,
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
339
377
|
if (verb === 'hold') {
|
|
340
378
|
return {
|
|
341
379
|
outcome: 'hold',
|