tumwater 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/README.md +74 -108
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +24 -0
- package/dist/src/agent/agent-backend.js +2 -2
- package/dist/src/agent/backend-traits.js +60 -0
- package/dist/src/agent/claude/claude-account.js +61 -8
- package/dist/src/agent/claude/claude-args.js +12 -15
- package/dist/src/agent/claude/claude-backend.js +51 -41
- package/dist/src/agent/claude/claude-env.js +7 -3
- package/dist/src/agent/claude/claude-stream.js +7 -16
- package/dist/src/agent/image-input.js +107 -0
- package/dist/src/agent/notes-shim.js +41 -0
- package/dist/src/agent/opencode/opencode-args.js +61 -0
- package/dist/src/agent/opencode/opencode-backend.js +255 -0
- package/dist/src/agent/opencode/opencode-catalog.js +184 -0
- package/dist/src/agent/opencode/opencode-config.js +188 -0
- package/dist/src/agent/opencode/opencode-env.js +36 -0
- package/dist/src/agent/opencode/opencode-session-id.js +37 -0
- package/dist/src/agent/opencode/opencode-stream.js +278 -0
- package/dist/src/agent/role-notes-write.js +16 -32
- package/dist/src/agent/run-agent.js +95 -18
- package/dist/src/agent/session-ledger.js +57 -1
- package/dist/src/backlog/backlog-archive.js +296 -0
- package/dist/src/backlog/backlog-cli.js +573 -0
- package/dist/src/backlog/backlog-commit.js +31 -0
- package/dist/src/backlog/backlog-eligibility.js +357 -33
- package/dist/src/backlog/backlog-md.js +98 -18
- package/dist/src/backlog/backlog-render.js +62 -11
- package/dist/src/backlog/backlog-structure.js +261 -58
- package/dist/src/backlog/backlog-write.js +197 -319
- package/dist/src/backlog/backlog.js +113 -16
- package/dist/src/backlog/question-answer.js +339 -0
- package/dist/src/baseline/main-baseline.js +75 -88
- package/dist/src/baseline/main-red.js +38 -30
- package/dist/src/brief/brief-edit.js +43 -0
- package/dist/src/{brief.js → brief/brief.js} +66 -10
- package/dist/src/budget/budget.js +32 -15
- package/dist/src/budget/fallback-breaker.js +8 -7
- package/dist/src/build/build-check-detect.js +8 -7
- package/dist/src/build/build-check-events.js +68 -22
- package/dist/src/build/build-check-report.js +22 -23
- package/dist/src/build/build-check-scoped.js +51 -62
- package/dist/src/build/build-check.js +15 -14
- package/dist/src/build/build-info.js +2 -1
- package/dist/src/build/build-stage.js +23 -22
- package/dist/src/build/dep-install.js +22 -21
- package/dist/src/build/host-sleep.js +1 -1
- package/dist/src/change/change-data.js +25 -14
- package/dist/src/change/change-render.js +11 -10
- package/dist/src/cli/brief-command.js +58 -0
- package/dist/src/cli/cli-args.js +25 -5
- package/dist/src/cli/cli-command-args.js +46 -14
- package/dist/src/cli/cli-editor.js +64 -0
- package/dist/src/cli/cli-flag-specs.js +54 -16
- package/dist/src/cli/cli-marker-commands.js +13 -10
- package/dist/src/cli/cli-output.js +9 -0
- package/dist/src/cli/cli-query-commands.js +458 -49
- package/dist/src/cli/cli-repo-gate.js +1 -1
- package/dist/src/cli/cli-run.js +208 -59
- package/dist/src/cli/completion.js +207 -0
- package/dist/src/cli/config-commands.js +14 -11
- package/dist/src/cli/help.js +305 -110
- package/dist/src/cli/log-commands.js +127 -21
- package/dist/src/cli/notify-command.js +33 -18
- package/dist/src/cli/question-commands.js +29 -224
- package/dist/src/cli.js +121 -40
- package/dist/src/collections.js +66 -16
- package/dist/src/concurrency/check-permit.js +16 -17
- package/dist/src/concurrency/lock.js +6 -5
- package/dist/src/concurrency/provider-lease.js +69 -0
- package/dist/src/concurrency/semaphore.js +258 -36
- package/dist/src/config/config-aliases.js +106 -0
- package/dist/src/config/config-editable-keys.js +3 -3
- package/dist/src/config/config-fallbacks.js +379 -0
- package/dist/src/config/config-field-checks.js +19 -10
- package/dist/src/config/config-keys.js +160 -0
- package/dist/src/config/config-live.js +64 -12
- package/dist/src/config/config-schema.js +34 -22
- package/dist/src/config/config-validation.js +132 -48
- package/dist/src/config/config-views.js +119 -337
- package/dist/src/config/config-write.js +138 -177
- package/dist/src/config/config.js +60 -49
- package/dist/src/config/model-selector.js +28 -2
- package/dist/src/doctor/doctor-backlog.js +546 -49
- package/dist/src/doctor/doctor-checks.js +534 -56
- package/dist/src/doctor/doctor-holds.js +193 -53
- package/dist/src/doctor/doctor-install.js +30 -0
- package/dist/src/doctor/doctor-launch-services.js +2 -2
- package/dist/src/doctor/doctor-model-checks.js +549 -157
- package/dist/src/doctor/doctor-notify.js +27 -0
- package/dist/src/doctor/doctor-orphans.js +10 -9
- package/dist/src/doctor/doctor-render.js +36 -9
- package/dist/src/doctor/doctor.js +80 -22
- package/dist/src/doctor/image-probe.js +244 -0
- package/dist/src/doctor/probe.js +64 -0
- package/dist/src/events/event-format.js +226 -102
- package/dist/src/events/event-labels.js +116 -0
- package/dist/src/events/event-legacy.js +140 -0
- package/dist/src/events/event-read.js +58 -22
- package/dist/src/events/event-window.js +27 -7
- package/dist/src/events/events.js +18 -14
- package/dist/src/events/notify.js +137 -38
- package/dist/src/failure/failure-cluster.js +12 -9
- package/dist/src/failure/failure-data.js +96 -48
- package/dist/src/failure/failure-render.js +69 -58
- package/dist/src/failure/failure-state-change.js +33 -26
- package/dist/src/failure/time-spend.js +79 -59
- package/dist/src/files/file-queue.js +16 -4
- package/dist/src/files/files.js +18 -6
- package/dist/src/files/json-object.js +18 -3
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +5 -4
- package/dist/src/gates/budget-gates.js +250 -34
- package/dist/src/gates/disk-gate.js +18 -18
- package/dist/src/gates/gate-polls.js +117 -69
- package/dist/src/gates/maintenance-quota.js +29 -29
- package/dist/src/gates/pause-gates.js +11 -11
- package/dist/src/gates/readiness.js +2 -1
- package/dist/src/gates/role-cap-gates.js +16 -21
- package/dist/src/gates/startup-gate.js +75 -31
- package/dist/src/gates/streak-gate.js +18 -16
- package/dist/src/git/commit-message.js +12 -5
- package/dist/src/git/git-diff.js +16 -16
- package/dist/src/git/git-run.js +8 -8
- package/dist/src/git/git.js +86 -55
- package/dist/src/git/slots-state.js +14 -5
- package/dist/src/git/worktree-pool.js +25 -18
- package/dist/src/git/worktree-use.js +15 -14
- package/dist/src/git/worktree.js +18 -18
- package/dist/src/gui/gui-args.js +10 -14
- package/dist/src/gui/gui-backlog-commands.js +273 -0
- package/dist/src/gui/gui-command.js +1 -1
- package/dist/src/gui/gui-config-commands.js +440 -0
- package/dist/src/gui/gui-endpoint-commands.js +16 -669
- package/dist/src/gui/gui-endpoint-common.js +49 -0
- package/dist/src/gui/gui-endpoints.js +116 -56
- package/dist/src/gui/gui-operator-commands.js +145 -0
- package/dist/src/gui/gui-prompt-commands.js +234 -0
- package/dist/src/gui/gui-server.js +28 -8
- package/dist/src/gui/gui-team-commands.js +164 -0
- package/dist/src/gui/http-body.js +17 -1
- package/dist/src/history/history-data.js +194 -98
- package/dist/src/history/history-render.js +76 -0
- package/dist/src/history/history.js +37 -58
- package/dist/src/inbox/inbox-attachments.js +75 -14
- package/dist/src/inbox/inbox-cancel.js +7 -5
- package/dist/src/inbox/inbox-edit.js +1 -1
- package/dist/src/inbox/inbox-submit.js +44 -4
- package/dist/src/inbox/inbox-top.js +83 -0
- package/dist/src/inbox/inbox.js +8 -2
- package/dist/src/inbox/pending-prompt.js +1 -1
- package/dist/src/inbox/prompt-commands.js +170 -80
- package/dist/src/init/init-templates.js +16 -14
- package/dist/src/init/init.js +12 -11
- package/dist/src/loop/leftover.js +28 -27
- package/dist/src/loop/loop-fallback.js +2 -2
- package/dist/src/loop/loop-pi.js +106 -52
- package/dist/src/loop/loop-recovery.js +15 -15
- package/dist/src/loop/loop-state.js +139 -13
- package/dist/src/loop/loop-tick.js +194 -86
- package/dist/src/loop/loop.js +72 -36
- package/dist/src/loop/model-fallback.js +3 -2
- package/dist/src/loop/revision.js +16 -7
- package/dist/src/merge/backlog-conflicts.js +155 -0
- package/dist/src/{landing/landing-batch.js → merge/merge-batch.js} +90 -89
- package/dist/src/{landing/landing-check-failures.js → merge/merge-check-failures.js} +87 -41
- package/dist/src/{landing/landing-core.js → merge/merge-core.js} +119 -95
- package/dist/src/{landing/landing-diff.js → merge/merge-diff.js} +1 -1
- package/dist/src/merge/merge-drain.js +212 -0
- package/dist/src/{landing/landing-git.js → merge/merge-git.js} +94 -21
- package/dist/src/merge/merge-pipeline.js +99 -0
- package/dist/src/{landing/landing-questions.js → merge/merge-questions.js} +9 -6
- package/dist/src/merge/merge-queue-render.js +75 -0
- package/dist/src/{landing/landing-queue.js → merge/merge-queue.js} +95 -36
- package/dist/src/merge/merge-slot.js +340 -0
- package/dist/src/{landing/landing-stack.js → merge/merge-stack.js} +66 -62
- package/dist/src/{landing/landing-merge.js → merge/merge-to-main.js} +72 -62
- package/dist/src/merge/merge-verify.js +212 -0
- package/dist/src/notes-cli.js +97 -0
- package/dist/src/operator/operator-commands.js +154 -47
- package/dist/src/operator/operator-intent.js +87 -49
- package/dist/src/operator/operator-requests.js +52 -52
- package/dist/src/operator/retire.js +62 -27
- package/dist/src/operator/revert.js +71 -0
- package/dist/src/orchestrator/orchestrator-launch.js +74 -14
- package/dist/src/orchestrator/orchestrator-scheduling.js +129 -44
- package/dist/src/orchestrator/orchestrator.js +158 -100
- package/dist/src/orchestrator/retention.js +29 -12
- package/dist/src/orchestrator/startup-migration-steps.js +51 -0
- package/dist/src/orchestrator/startup-migrations.js +40 -0
- package/dist/src/paths.js +128 -37
- package/dist/src/pi/pi-args.js +26 -3
- package/dist/src/pi/pi-backend.js +15 -23
- package/dist/src/pi/pi-bin.js +18 -12
- package/dist/src/pi/pi-event-line.js +17 -1
- package/dist/src/pi/pi-failure.js +87 -0
- package/dist/src/pi/pi-models.js +126 -38
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +86 -0
- package/dist/src/pi/pi-stream.js +131 -107
- package/dist/src/pi/pi-watchdogs.js +47 -43
- package/dist/src/pi-extension/bounded-output.js +20 -5
- package/dist/src/pi-extension/context-budget.js +3 -1
- package/dist/src/pi-extension/context-shake.js +54 -21
- package/dist/src/pi-extension/context-window.js +37 -0
- package/dist/src/pi-extension/full-output.js +2 -2
- package/dist/src/pi-extension/strip-thinking.js +80 -0
- package/dist/src/pi-extension/trim-system-prompt.js +38 -0
- package/dist/src/process/launch-services.js +11 -10
- package/dist/src/process/process-table.js +1 -1
- package/dist/src/process/process.js +7 -7
- package/dist/src/process/run-marker.js +29 -6
- package/dist/src/process/supervisor.js +47 -25
- package/dist/src/prompt/dialect.js +30 -10
- package/dist/src/prompt/gate-prompts.js +170 -67
- package/dist/src/prompt/principles.js +1 -0
- package/dist/src/prompt/prompt-followup.js +35 -20
- package/dist/src/prompt/prompt.js +249 -91
- package/dist/src/redeploy/redeploy-policy.js +10 -10
- package/dist/src/redeploy/redeploy.js +6 -4
- package/dist/src/redeploy/redeployer.js +63 -55
- package/dist/src/redeploy/self-reload.js +1 -1
- package/dist/src/report/report-data.js +264 -255
- package/dist/src/report/report-fold.js +353 -0
- package/dist/src/report/report-render.js +270 -33
- package/dist/src/request-timeouts.js +1 -1
- package/dist/src/review/known-flakes.js +34 -14
- package/dist/src/review/protected-edits.js +33 -0
- package/dist/src/review/review-followup.js +16 -4
- package/dist/src/review/review-precheck.js +14 -13
- package/dist/src/review/review-verdict.js +14 -0
- package/dist/src/review/review.js +81 -39
- package/dist/src/review/secret-leaks.js +127 -0
- package/dist/src/review/suite-rerun.js +4 -3
- package/dist/src/roles/legacy-role-ids.js +21 -0
- package/dist/src/roles/legacy-role-migration.js +62 -0
- package/dist/src/roles/loop-ids.js +19 -2
- package/dist/src/roles/role-catalog.js +125 -135
- package/dist/src/roles/role-guidance.js +64 -31
- package/dist/src/roles/role-labels.js +10 -0
- package/dist/src/roles/role-render.js +194 -18
- package/dist/src/roles/role-view.js +216 -17
- package/dist/src/roles/roles.js +18 -15
- package/dist/src/scheduling/backoff.js +29 -19
- package/dist/src/scheduling/claims.js +25 -9
- package/dist/src/scheduling/once-round.js +11 -11
- package/dist/src/scheduling/quiet-hours.js +25 -32
- package/dist/src/scheduling/scheduling.js +43 -24
- package/dist/src/scheduling/{work-landed-cache.js → work-merged-cache.js} +14 -14
- package/dist/src/status/status-budget.js +115 -0
- package/dist/src/status/status-data.js +77 -184
- package/dist/src/status/status-inbox.js +47 -0
- package/dist/src/status/status-install.js +27 -0
- package/dist/src/status/status-polls.js +68 -33
- package/dist/src/status/status-roles.js +105 -0
- package/dist/src/{fleet → team}/error-storm.js +10 -10
- package/dist/src/{fleet → team}/failure-spread.js +8 -8
- package/dist/src/{fleet → team}/orchestrator-info.js +86 -18
- package/dist/src/{fleet → team}/reclaim.js +5 -4
- package/dist/src/{fleet/fleet-hold.js → team/team-hold.js} +55 -21
- package/dist/src/{fleet/fleet-polls.js → team/team-polls.js} +116 -65
- package/dist/src/{fleet/fleet-state.js → team/team-state.js} +62 -37
- package/dist/src/text/datetime.js +42 -2
- package/dist/src/text/format.js +10 -1
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +35 -34
- package/dist/src/text/suggest.js +3 -2
- package/dist/src/text/text.js +2 -2
- package/dist/src/tick/qa-coverage.js +2 -1
- package/dist/src/tick/stage-check.js +147 -26
- package/dist/src/tick/telemetry-digest.js +10 -9
- package/dist/src/tick/tick-apply.js +101 -72
- package/dist/src/tick/tick-detail-data.js +32 -18
- package/dist/src/tick/tick-detail.js +62 -21
- package/dist/src/tick/tick-finalize.js +29 -22
- package/dist/src/tick/tick-outcome.js +2 -1
- package/dist/src/tick/tick-prompt.js +94 -26
- package/dist/src/tick/tick-resume.js +29 -13
- package/dist/src/tick/tick-stage.js +91 -47
- package/dist/src/tick/tick-timing.js +60 -59
- package/dist/src/tick/tick-usage.js +53 -23
- package/dist/src/tick/tick-verdict.js +76 -10
- package/dist/src/ui/badges.js +118 -67
- package/dist/src/ui/gui/gui-client-boot.js +209 -30
- package/dist/src/ui/gui/gui-client-composer.js +102 -23
- package/dist/src/ui/gui/gui-client-drawer.js +330 -75
- package/dist/src/ui/gui/gui-client-history.js +96 -38
- package/dist/src/ui/gui/gui-client-labels.js +24 -0
- package/dist/src/ui/gui/gui-client-loops.js +94 -52
- package/dist/src/ui/gui/gui-client-markdown.js +1 -0
- package/dist/src/ui/gui/gui-client-model.js +63 -43
- package/dist/src/ui/gui/gui-client-operator.js +129 -39
- package/dist/src/ui/gui/gui-client-pending.js +36 -32
- package/dist/src/ui/gui/gui-client-report.js +339 -46
- package/dist/src/ui/gui/gui-client-settings.js +148 -14
- package/dist/src/ui/gui/gui-client-sound.js +2 -1
- package/dist/src/ui/gui/{gui-client-fleet.js → gui-client-team.js} +196 -72
- package/dist/src/ui/gui/gui-client.js +26 -20
- package/dist/src/ui/gui/gui-page.js +19 -19
- package/dist/src/ui/gui/gui-styles.js +12 -4
- package/dist/src/ui/progress-data.js +5 -3
- package/dist/src/ui/status-model.js +184 -122
- package/dist/src/ui/status-payload.js +119 -32
- package/dist/src/ui/status-render.js +122 -52
- package/dist/src/ui/team-alerts.js +341 -0
- package/dist/src/ui/tick-progress-model.js +14 -14
- package/dist/src/ui/tone.js +11 -8
- package/dist/src/ui/transcript-tail.js +1 -0
- package/dist/src/ui/transcript.js +23 -16
- package/dist/src/ui/tui/tui-app.js +1 -0
- package/dist/src/ui/tui/tui-backlog.js +85 -11
- package/dist/src/ui/tui/tui-editor-answer.js +97 -0
- package/dist/src/ui/tui/tui-editor-attach.js +107 -0
- package/dist/src/ui/tui/tui-editor-brief.js +107 -0
- package/dist/src/ui/tui/tui-editor-budget.js +155 -0
- package/dist/src/ui/tui/tui-editor-ctx.js +1 -0
- package/dist/src/ui/tui/tui-editor-entry.js +230 -0
- package/dist/src/ui/tui/tui-editor-interval.js +122 -0
- package/dist/src/ui/tui/tui-editor-notes.js +114 -0
- package/dist/src/ui/tui/tui-editor-pause.js +266 -0
- package/dist/src/ui/tui/tui-editor-prompt.js +95 -0
- package/dist/src/ui/tui/tui-editor-queued.js +98 -0
- package/dist/src/ui/tui/tui-editor-quiet.js +106 -0
- package/dist/src/ui/tui/tui-editor-retire.js +83 -0
- package/dist/src/ui/tui/tui-editor-search.js +73 -0
- package/dist/src/ui/tui/tui-editor-session.js +97 -0
- package/dist/src/ui/tui/tui-editors.js +152 -0
- package/dist/src/ui/tui/tui-frame.js +67 -55
- package/dist/src/ui/tui/tui-history.js +12 -7
- package/dist/src/ui/tui/tui-input.js +49 -44
- package/dist/src/ui/tui/tui-keymap.js +1 -0
- package/dist/src/ui/tui/tui-keys.js +91 -1704
- package/dist/src/ui/tui/tui-pane-activity.js +99 -0
- package/dist/src/ui/tui/tui-pane-backlog.js +224 -0
- package/dist/src/ui/tui/tui-pane-brief.js +30 -0
- package/dist/src/ui/tui/tui-pane-ctx.js +11 -0
- package/dist/src/ui/tui/tui-pane-health.js +124 -0
- package/dist/src/ui/tui/tui-pane-history.js +316 -0
- package/dist/src/ui/tui/tui-pane-pending.js +74 -0
- package/dist/src/ui/tui/tui-pane-queued.js +143 -0
- package/dist/src/ui/tui/tui-pane-report.js +122 -0
- package/dist/src/ui/tui/tui-pane-session.js +101 -0
- package/dist/src/ui/tui/tui-pane-transcript.js +150 -0
- package/dist/src/ui/tui/tui-pane.js +66 -28
- package/dist/src/ui/tui/tui-panes.js +294 -0
- package/dist/src/ui/tui/tui.js +45 -25
- package/dist/src/verdict/fix-claim.js +141 -38
- package/dist/src/verdict/no-change.js +19 -0
- package/dist/src/verdict/refusal.js +92 -8
- package/dist/src/verdict/reply-contract.js +17 -5
- package/dist/src/version.js +3 -2
- package/package.json +4 -2
- package/dist/src/agent/mcp/role-notes-server.js +0 -160
- package/dist/src/landing/backlog-conflicts.js +0 -116
- package/dist/src/landing/landing-drain.js +0 -204
- package/dist/src/landing/landing-pipeline.js +0 -96
- package/dist/src/landing/landing-queue-render.js +0 -31
- package/dist/src/landing/landing-slot.js +0 -246
- package/dist/src/landing/landing-vetting.js +0 -196
- package/dist/src/pi-extension/role-notes.js +0 -40
- package/dist/src/ui/fleet-alerts.js +0 -174
|
@@ -1,38 +1,58 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
1
2
|
/** The CLI layer of `tumwater prompt` — the steering-prompt command: submit a prompt to the
|
|
2
3
|
* director (default) or one role's queue, `--list` what is queued with per-loop position
|
|
3
4
|
* numbering, or `--cancel <n>` a queued prompt by position. Split out of
|
|
4
|
-
* operator/operator-commands.ts because this command is not a
|
|
5
|
+
* operator/operator-commands.ts because this command is not a team-control marker: its
|
|
5
6
|
* sibling commands there ride the operator-intent marker protocol, while this command drives
|
|
6
7
|
* the durable per-loop queues (src/inbox/inbox.ts) through the inbox-cancel, inbox-edit, and
|
|
7
8
|
* operator-intent helpers, each with its own broken-config policy — so the command lives
|
|
8
|
-
* beside the queue module it drives. The
|
|
9
|
+
* beside the queue module it drives. The team-side half — the dequeue and requeue a loop
|
|
9
10
|
* performs — is inbox.ts and pending-prompt.ts. */
|
|
10
11
|
import { fail, say, sayJson } from "../cli/cli-output.js";
|
|
11
|
-
import fs from "node:fs";
|
|
12
12
|
import path from "node:path";
|
|
13
13
|
import { parsePromptArgs } from "../cli/cli-command-args.js";
|
|
14
|
-
import { cancelListedPrompt, cancelRolePrompt, } from "./inbox-cancel.js";
|
|
15
|
-
import { editListedPrompt, editRolePrompt, } from "./inbox-edit.js";
|
|
16
|
-
import {
|
|
14
|
+
import { cancelListedPrompt, cancelRolePrompt, resolveListedQueue, } from "./inbox-cancel.js";
|
|
15
|
+
import { editListedPrompt, editQueuedFile, editRolePrompt, readQueuedPromptBody, } from "./inbox-edit.js";
|
|
16
|
+
import { movePromptToTop } from "./inbox-top.js";
|
|
17
|
+
import { editInEditor } from "../cli/cli-editor.js";
|
|
18
|
+
import { listWideScope, queuedFileAtPosition, promptPreview, queuedListRows, renderQueuedList } from "./inbox.js";
|
|
17
19
|
import { durationLabel } from "../cli/cli-args.js";
|
|
18
20
|
import { knownRoleIds, knownRoleIdsCached, loadConfig } from "../config/config.js";
|
|
19
21
|
import { errorMessage } from "../text/text.js";
|
|
20
|
-
import {
|
|
22
|
+
import { readPromptImageFiles } from "./inbox-attachments.js";
|
|
21
23
|
import { DIRECTOR_ROLE, unknownRoleMessage } from "../roles/roles.js";
|
|
22
24
|
import { submitRolePromptAndWake } from "../operator/operator-intent.js";
|
|
25
|
+
import { imagePromptGuidance } from "./inbox-submit.js";
|
|
26
|
+
import { roleLabel } from "../roles/role-labels.js";
|
|
27
|
+
import { canonicalRoleId } from "../roles/legacy-role-ids.js";
|
|
28
|
+
/** Fail with the ambiguity or miss message of a list-wide position resolution, else return
|
|
29
|
+
* the resolved value narrowed. Shared by sayListedOutcome and the editor form so the wordings
|
|
30
|
+
* cannot drift. */
|
|
31
|
+
function failQueueMiss(listed, position) {
|
|
32
|
+
if (isQueueMiss(listed)) {
|
|
33
|
+
if (listed.status === "ambiguous") {
|
|
34
|
+
fail(`position ${position} is in more than one role's inbox (${listed.roles.join(", ")}) — name one with --role <id>`);
|
|
35
|
+
}
|
|
36
|
+
// `queued` is the longest queue's length — the highest position any loop's numbering can
|
|
37
|
+
// reach — so a miss names it as the valid range when at least one loop holds a prompt.
|
|
38
|
+
const range = listed.queued > 0 ? ` — valid: 1-${listed.queued}` : "";
|
|
39
|
+
fail(`no prompt at position ${position} (longest inbox holds ${listed.queued})${range}`);
|
|
40
|
+
}
|
|
41
|
+
return listed;
|
|
42
|
+
}
|
|
43
|
+
/** Whether a list-wide outcome is one of the two miss shapes — the guard that narrows the
|
|
44
|
+
* miss fields (`roles`, `queued`) without a cast. */
|
|
45
|
+
function isQueueMiss(listed) {
|
|
46
|
+
return listed.status === "ambiguous" || listed.status === "missing";
|
|
47
|
+
}
|
|
23
48
|
/** The shared tail of the list-wide cancel/edit branches: report an ambiguity (naming the
|
|
24
49
|
* rival loops, with the `--role` escape hatch) or a miss (carrying the length of the longest
|
|
25
50
|
* queue, the highest position numbering any loop can hold), else render the resolved outcome
|
|
26
51
|
* through sayPromptOutcome. One helper so the
|
|
27
52
|
* two verbs' handling of the miss shapes cannot drift. */
|
|
28
53
|
function sayListedOutcome(listed, position) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
if (listed.status === "missing") {
|
|
33
|
-
fail(`no prompt at position ${position} (longest queue holds ${listed.queued})`);
|
|
34
|
-
}
|
|
35
|
-
sayPromptOutcome(position, listed.role, listed.outcome, true);
|
|
54
|
+
const found = failQueueMiss(listed, position);
|
|
55
|
+
sayPromptOutcome(position, found.role, found.outcome, true);
|
|
36
56
|
}
|
|
37
57
|
/** Run one loop-targeted queue verb (cancelRolePrompt/editRolePrompt) for a `--role`-scoped
|
|
38
58
|
* command: a thrown failure (a broken config, an unwritable queue) exits through fail with
|
|
@@ -55,77 +75,56 @@ function runRolePromptCommand(root, role, position, verb) {
|
|
|
55
75
|
* command. One helper so the cancel and edit wordings cannot drift. */
|
|
56
76
|
function sayPromptOutcome(position, role, outcome, labelRole) {
|
|
57
77
|
if (outcome.status === "gone") {
|
|
58
|
-
|
|
78
|
+
sayPromptGone(position, role);
|
|
59
79
|
return;
|
|
60
80
|
}
|
|
61
81
|
// A resolved cancel carries the removed text; a resolved edit carries what it wrote.
|
|
62
82
|
const text = outcome.status === "cancelled" ? outcome.text : outcome.newText;
|
|
63
83
|
say(labelRole ? `${outcome.status} (${role}): ${promptPreview(text)}` : `${outcome.status}: ${promptPreview(text)}`);
|
|
64
84
|
}
|
|
65
|
-
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return extProblem;
|
|
82
|
-
try {
|
|
83
|
-
const stat = fs.statSync(p);
|
|
84
|
-
if (!stat.isFile())
|
|
85
|
-
return `attached image ${JSON.stringify(p)} is not a regular file — pass a path to an image file`;
|
|
86
|
-
const sizeProblem = promptImageSizeProblem(path.basename(p), stat.size);
|
|
87
|
-
if (sizeProblem !== null)
|
|
88
|
-
return sizeProblem;
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
// Missing or unreadable: the read below names it.
|
|
92
|
-
}
|
|
85
|
+
/** The gone-case tail shared by every resolved queue-verb reply: a concurrent dequeue is a
|
|
86
|
+
* normal race, not an error, so report it and exit clean, naming the loop that took the
|
|
87
|
+
* prompt. One helper so the cancel, edit and top verbs' gone wording cannot drift. */
|
|
88
|
+
function sayPromptGone(position, role) {
|
|
89
|
+
say(`prompt ${position} is no longer in the inbox — ${roleLabel(role)} already took it`);
|
|
90
|
+
}
|
|
91
|
+
/** The user-facing reply for one resolved top — the two branches of cmdPrompt's top mode
|
|
92
|
+
* (list-wide and `--role`-scoped): a concurrent dequeue is a normal race, not an error, so it
|
|
93
|
+
* is reported and exited clean; a resolved one previews the moved prompt. When `labelRole` the
|
|
94
|
+
* line names the loop — the no-`--role` command resolves across every loop, so its output must
|
|
95
|
+
* say where the prompt moved — while a `--role`-scoped command already names it in the user's
|
|
96
|
+
* own command. The `sayPromptOutcome` contract, so the verbs cannot drift. */
|
|
97
|
+
function sayTopOutcome(position, role, outcome, labelRole) {
|
|
98
|
+
if (outcome.status === "gone") {
|
|
99
|
+
sayPromptGone(position, role);
|
|
100
|
+
return;
|
|
93
101
|
}
|
|
94
|
-
|
|
102
|
+
say(labelRole ? `topped (${role}): ${outcome.preview}` : `topped: ${outcome.preview}`);
|
|
95
103
|
}
|
|
96
|
-
/** Read and validate an `--attach <path>` list into the GUI's image input shape
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
* always raised, so the enqueue and `--edit` forms share one definition and cannot drift. */
|
|
104
|
+
/** Read and validate an `--attach <path>` list into the GUI's image input shape, through the
|
|
105
|
+
* shared throwing core both surfaces use (readPromptImageFiles in inbox-attachments.ts), with
|
|
106
|
+
* each failure exiting through `fail` with the same messages the enqueue branch always
|
|
107
|
+
* raised — so the enqueue and `--edit` forms share one definition and cannot drift from the
|
|
108
|
+
* TUI's prompt-line attach. */
|
|
102
109
|
function readPromptImages(paths) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
bytes = fs.readFileSync(p);
|
|
110
|
-
}
|
|
111
|
-
catch (err) {
|
|
112
|
-
return fail(`cannot read attached image ${JSON.stringify(p)}: ${errorMessage(err)}`);
|
|
113
|
-
}
|
|
114
|
-
return { name: path.basename(p), dataBase64: bytes.toString("base64") };
|
|
115
|
-
});
|
|
116
|
-
const problem = promptImagesProblem(images);
|
|
117
|
-
if (problem !== null)
|
|
118
|
-
fail(problem);
|
|
119
|
-
return images;
|
|
110
|
+
try {
|
|
111
|
+
return readPromptImageFiles(paths);
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
return fail(errorMessage(err));
|
|
115
|
+
}
|
|
120
116
|
}
|
|
121
|
-
/** `tumwater prompt [--role <id>] <text|list|cancel <n>>`: submit a steering prompt to the
|
|
117
|
+
/** `tumwater prompt [--role <id>] <text|list|cancel <n>|top <n>>`: submit a steering prompt to the
|
|
122
118
|
* director (default) or one role's queue, list what is queued with per-loop position
|
|
123
|
-
* numbering,
|
|
119
|
+
* numbering, cancel a queued prompt by position, or move one to the head of its
|
|
120
|
+
* queue. The dispatcher in cli.ts gates on a ready
|
|
124
121
|
* repo and delegates here; list output is grouped by loop — the director first (its queue is
|
|
125
122
|
* the shared one every pre-1/2 prompt landed in), then each role with queued prompts — so a
|
|
126
123
|
* per-role queue's position numbering stays unambiguous. */
|
|
127
124
|
export async function cmdPrompt(root, args) {
|
|
128
125
|
const parsed = parsePromptArgs(args);
|
|
126
|
+
if (parsed.role !== null)
|
|
127
|
+
parsed.role = canonicalRoleId(parsed.role);
|
|
129
128
|
// `--role <id>` scopes every mode to one loop's queue; the value is validated here, with
|
|
130
129
|
// the same message every other --role consumer uses. The director is always valid: its
|
|
131
130
|
// queue is the historical inbox (inbox.ts).
|
|
@@ -162,7 +161,7 @@ export async function cmdPrompt(root, args) {
|
|
|
162
161
|
return;
|
|
163
162
|
}
|
|
164
163
|
if (prompts.length === 0) {
|
|
165
|
-
say(role !== null ?
|
|
164
|
+
say(role !== null ? `${roleLabel(role)}'s inbox is empty` : "every inbox is empty");
|
|
166
165
|
return;
|
|
167
166
|
}
|
|
168
167
|
say(renderQueuedList(prompts, Date.now()));
|
|
@@ -189,12 +188,96 @@ export async function cmdPrompt(root, args) {
|
|
|
189
188
|
// `--attach` list is read through the same helper the enqueue form uses and appended to
|
|
190
189
|
// the prompt's images in place.
|
|
191
190
|
const images = parsed.attachPaths.length > 0 ? readPromptImages(parsed.attachPaths) : undefined;
|
|
191
|
+
// Image-input gate, `--edit`'s twin of the submit branch below (plans/image-input.md
|
|
192
|
+
// "Attachments", part 5/5): editing an attachment into a queued prompt is the same
|
|
193
|
+
// tick-time discovery the submit gate prevents, so the refusal promise holds across every
|
|
194
|
+
// CLI attach surface — an image no configured model can read is refused before a byte is
|
|
195
|
+
// written, and a loop that reroutes a blind read is told where the prompt will run. The
|
|
196
|
+
// target loop is resolved first (the same resolution the verb below makes) so the gate can
|
|
197
|
+
// name it.
|
|
198
|
+
if (images !== undefined && images.length > 0) {
|
|
199
|
+
const loop = role ?? failQueueMiss(resolveListedQueue(root, listWideScope(root), parsed.position), parsed.position).role;
|
|
200
|
+
const guidance = imagePromptGuidance(root, loop);
|
|
201
|
+
if (guidance.refusal)
|
|
202
|
+
return fail(guidance.refusal);
|
|
203
|
+
if (guidance.notice)
|
|
204
|
+
say(guidance.notice);
|
|
205
|
+
}
|
|
206
|
+
const typed = parsed.text;
|
|
207
|
+
if (typed === null) {
|
|
208
|
+
// Bare `--edit <n>`: open the prompt's body in the editor. The loop is resolved first
|
|
209
|
+
// and the write goes by file name, so a dequeue while the editor is open reports gone
|
|
210
|
+
// instead of editing the neighbour that shifted into the position.
|
|
211
|
+
let target = role;
|
|
212
|
+
if (target === null) {
|
|
213
|
+
const resolved = resolveListedQueue(root, listWideScope(root), parsed.position);
|
|
214
|
+
target = failQueueMiss(resolved, parsed.position).role;
|
|
215
|
+
}
|
|
216
|
+
const loop = target;
|
|
217
|
+
let name;
|
|
218
|
+
let body;
|
|
219
|
+
try {
|
|
220
|
+
name = path.basename(queuedFileAtPosition(root, loop, parsed.position));
|
|
221
|
+
body = readQueuedPromptBody(root, loop, name);
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
return fail(errorMessage(err));
|
|
225
|
+
}
|
|
226
|
+
if (body === null) {
|
|
227
|
+
sayPromptOutcome(parsed.position, loop, { status: "gone" }, role === null);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const after = editInEditor(body, "prompt.md");
|
|
231
|
+
// An emptied buffer is an abort, not a blank prompt: the queue would otherwise hold a
|
|
232
|
+
// prompt with no text, which the enqueue forms refuse.
|
|
233
|
+
if (after.trim() === "" && body.trim() !== "")
|
|
234
|
+
fail("the edited prompt is empty; nothing changed (use --cancel <n> to drop it)");
|
|
235
|
+
if (after.trim() === body.trim() && images === undefined) {
|
|
236
|
+
say(`prompt ${parsed.position} unchanged`);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
sayPromptOutcome(parsed.position, loop, editQueuedFile(root, loop, name, after, images), role === null);
|
|
241
|
+
}
|
|
242
|
+
catch (err) {
|
|
243
|
+
fail(errorMessage(err));
|
|
244
|
+
}
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (role === null) {
|
|
248
|
+
sayListedOutcome(editListedPrompt(root, listWideScope(root), parsed.position, typed, images), parsed.position);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const target = role ?? DIRECTOR_ROLE;
|
|
252
|
+
sayPromptOutcome(parsed.position, target, runRolePromptCommand(root, target, parsed.position, (r, role, position) => editRolePrompt(r, role, position, typed, images)), false);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (parsed.mode === "top") {
|
|
256
|
+
// The same list-wide resolution the cancel branch uses, one verb on: no --role resolves the
|
|
257
|
+
// position across `--list`'s per-loop sections and moves it in the resolved loop's queue;
|
|
258
|
+
// with --role it is the role-scoped call. A concurrent dequeue reports gone and exits
|
|
259
|
+
// clean; position 1 of the resolved loop is "already first".
|
|
192
260
|
if (role === null) {
|
|
193
|
-
|
|
261
|
+
const loop = failQueueMiss(resolveListedQueue(root, listWideScope(root), parsed.position), parsed.position).role;
|
|
262
|
+
let outcome;
|
|
263
|
+
try {
|
|
264
|
+
outcome = movePromptToTop(root, loop, parsed.position);
|
|
265
|
+
}
|
|
266
|
+
catch (err) {
|
|
267
|
+
return fail(errorMessage(err));
|
|
268
|
+
}
|
|
269
|
+
sayTopOutcome(parsed.position, loop, outcome, true);
|
|
194
270
|
return;
|
|
195
271
|
}
|
|
196
272
|
const target = role ?? DIRECTOR_ROLE;
|
|
197
|
-
|
|
273
|
+
let outcome;
|
|
274
|
+
try {
|
|
275
|
+
outcome = movePromptToTop(root, target, parsed.position);
|
|
276
|
+
}
|
|
277
|
+
catch (err) {
|
|
278
|
+
return fail(errorMessage(err));
|
|
279
|
+
}
|
|
280
|
+
sayTopOutcome(parsed.position, target, outcome, false);
|
|
198
281
|
return;
|
|
199
282
|
}
|
|
200
283
|
const target = role ?? DIRECTOR_ROLE;
|
|
@@ -202,21 +285,28 @@ export async function cmdPrompt(root, args) {
|
|
|
202
285
|
// parsed duration, computed at submit so the deferral starts when the operator queued it.
|
|
203
286
|
const dueMs = parsed.atDelayMs !== null ? Date.now() + parsed.atDelayMs : undefined;
|
|
204
287
|
// `--attach <path>`: read and validate through the shared helper (a bad path fails by name
|
|
205
|
-
// before anything is queued).
|
|
288
|
+
// before anything is queued). An attachment the target loop cannot read is refused here by
|
|
289
|
+
// name — the same gate submitRolePrompt enforces, surfaced early so the CLI exits non-zero
|
|
290
|
+
// with the fix before anything is written or queued; a loop that reroutes a blind read is
|
|
291
|
+
// not refused, but the operator is told where the prompt will actually run.
|
|
206
292
|
const images = parsed.attachPaths.length > 0 ? readPromptImages(parsed.attachPaths) : undefined;
|
|
293
|
+
if (images && images.length > 0) {
|
|
294
|
+
const guidance = imagePromptGuidance(root, target);
|
|
295
|
+
if (guidance.refusal)
|
|
296
|
+
return fail(guidance.refusal);
|
|
297
|
+
if (guidance.notice)
|
|
298
|
+
say(guidance.notice);
|
|
299
|
+
}
|
|
207
300
|
const wake = submitRolePromptAndWake(root, target, parsed.text, images, dueMs);
|
|
208
301
|
const attached = images !== undefined ? ` with ${images.length} image(s)` : "";
|
|
302
|
+
const owner = target === DIRECTOR_ROLE ? "the director's" : `${roleLabel(target)}'s`;
|
|
209
303
|
if (parsed.atDelayMs !== null) {
|
|
210
304
|
// A deferred prompt's confirmation names the delivery delay, in the same duration
|
|
211
|
-
// vocabulary --at parsed (durationLabel), so the
|
|
212
|
-
|
|
213
|
-
say(role === null ? `queued for the director loop${deferred}${attached}` : `queued for the ${role} loop${deferred}${attached}`);
|
|
214
|
-
}
|
|
215
|
-
else if (role === null) {
|
|
216
|
-
say(`queued for the director loop${attached}`);
|
|
305
|
+
// vocabulary --at parsed (durationLabel), so the user can re-type it.
|
|
306
|
+
say(`added to ${owner} inbox — delivers in ${durationLabel(parsed.atDelayMs)}${attached}`);
|
|
217
307
|
}
|
|
218
308
|
else {
|
|
219
|
-
say(`
|
|
309
|
+
say(`added to ${owner} inbox${attached}`);
|
|
220
310
|
}
|
|
221
311
|
say(wake);
|
|
222
312
|
}
|
|
@@ -1,20 +1,21 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
1
2
|
/** The bundled project templates for `tumwater init --template`
|
|
2
3
|
* (docs/feature-project-templates.md). Static string data, no file reads and no runtime
|
|
3
4
|
* dependencies: a template is a brief preamble (the operator's own words stay appended after
|
|
4
5
|
* it) plus a seeded PLANS.md backlog and a starter directory layout — never code scaffolds,
|
|
5
|
-
* since the
|
|
6
|
+
* since the team's own first ticks write any code.
|
|
6
7
|
* `blank` is the default and reproduces today's init output byte-identically. */
|
|
7
8
|
import { typoSuffix } from "../text/suggest.js";
|
|
8
9
|
const PYTHON_CLI_PREAMBLE = `This project is a Python command-line tool.
|
|
9
10
|
The entry point is a \`main()\` function guarded by \`if __name__ == "__main__":\` in a single
|
|
10
11
|
module at the project root; the CLI parses arguments with the standard library's argparse.
|
|
11
12
|
Every behavior change ships with tests runnable by \`python -m pytest\` (or unittest, once a
|
|
12
|
-
suite exists), and the
|
|
13
|
+
suite exists), and the team keeps a README documenting install and usage as the tool grows.
|
|
13
14
|
Prefer the standard library over third-party dependencies.`;
|
|
14
15
|
const NODE_CLI_PREAMBLE = `This project is a Node.js command-line tool.
|
|
15
16
|
The entry point is \`bin.js\` (or \`src/main.js\`) wired to a \`bin\` field in package.json;
|
|
16
17
|
argument parsing uses the standard library's util.parseArgs. Every behavior change ships with
|
|
17
|
-
tests runnable by \`npm test\`, and the
|
|
18
|
+
tests runnable by \`npm test\`, and the team keeps a README documenting install and usage as
|
|
18
19
|
the tool grows. Prefer node built-ins over third-party dependencies.`;
|
|
19
20
|
const STATIC_SITE_PREAMBLE = `This project is a small static website: plain HTML, CSS, and
|
|
20
21
|
minimal vanilla JavaScript served as-is, with no build step. Pages live at the project root
|
|
@@ -23,7 +24,7 @@ change keeps the site renderable by opening the files directly. Keep pages acces
|
|
|
23
24
|
markup, alt text, and keyboard-navigable controls.`;
|
|
24
25
|
const blank = {
|
|
25
26
|
id: "blank",
|
|
26
|
-
description: "no preamble, no seeded backlog — the
|
|
27
|
+
description: "no preamble, no seeded backlog — the team plans itself from your brief",
|
|
27
28
|
briefPreamble: "",
|
|
28
29
|
starterPlans: [],
|
|
29
30
|
starterDirs: [],
|
|
@@ -94,8 +95,9 @@ export function templateCatalog() {
|
|
|
94
95
|
* into PLANS_TEMPLATE under `## Planned`; the other three files land byte-identically. */
|
|
95
96
|
export const PLANS_TEMPLATE = `# Plans
|
|
96
97
|
|
|
97
|
-
Planned features, written by the plan
|
|
98
|
-
Each plan: goal, approach, files touched, acceptance criteria. Move finished plans to Done
|
|
98
|
+
Planned features, written by the plan role and implemented by the feature role.
|
|
99
|
+
Each plan: goal, approach, files touched, acceptance criteria. Move finished plans to Done;
|
|
100
|
+
tumwater then archives them in PLANS-DONE.md, so this file holds only open work.
|
|
99
101
|
|
|
100
102
|
## Planned
|
|
101
103
|
|
|
@@ -107,11 +109,11 @@ _None yet._
|
|
|
107
109
|
`;
|
|
108
110
|
export const BUGS_TEMPLATE = `# Bugs
|
|
109
111
|
|
|
110
|
-
Known bugs, recorded by any
|
|
112
|
+
Known bugs, recorded by any role and fixed by the bugfix role.
|
|
111
113
|
Each bug: symptom, how to reproduce, suspected cause if known. Move fixed bugs to Fixed, with the
|
|
112
114
|
required \`**Validation gap:** <tag> — <one sentence>\` line recording what made the bug hard to
|
|
113
115
|
confirm (tag one of: none, no-repro, no-fake, real-run-needed, no-observability, slow-check,
|
|
114
|
-
unclear-invariant).
|
|
116
|
+
unclear-invariant); tumwater then archives them in BUGS-FIXED.md, so this file holds only open work.
|
|
115
117
|
|
|
116
118
|
## Open
|
|
117
119
|
|
|
@@ -123,9 +125,9 @@ _None yet._
|
|
|
123
125
|
`;
|
|
124
126
|
export const QUESTIONS_TEMPLATE = `# Questions
|
|
125
127
|
|
|
126
|
-
Open questions
|
|
127
|
-
|
|
128
|
-
director).
|
|
128
|
+
Open questions roles have posted for a human decision — each with context, the options, and the
|
|
129
|
+
role's recommendation. Answer by moving an entry to ## Answered with your decision (or tell the
|
|
130
|
+
director). Roles never block on their own questions; they check here at the start of each run.
|
|
129
131
|
|
|
130
132
|
## Open
|
|
131
133
|
|
|
@@ -138,7 +140,7 @@ _None yet._
|
|
|
138
140
|
export const PRINCIPLES_TEMPLATE = `# Principles
|
|
139
141
|
|
|
140
142
|
Design principles this project holds — the codified answer to "what would a senior engineer on
|
|
141
|
-
this team always do." Every
|
|
143
|
+
this team always do." Every role's instructions carry these; uphold them in everything you produce.
|
|
142
144
|
Only the director and steward roles may edit this file. Phrase new principles positively: state
|
|
143
145
|
what to do, not what to avoid. This list is a starting point: the director and steward own it
|
|
144
146
|
and will tune it to this project.
|
|
@@ -146,7 +148,7 @@ and will tune it to this project.
|
|
|
146
148
|
- Prefer the standard library over a new dependency.
|
|
147
149
|
- Keep each file focused on one responsibility, and small enough to read in one sitting.
|
|
148
150
|
- Every behavior change ships with a test.
|
|
149
|
-
- Small, complete, and correct beats big and half-done: one focused change per
|
|
151
|
+
- Small, complete, and correct beats big and half-done: one focused change per run. Focused
|
|
150
152
|
means one theme, not one site: a kind of fix applied everywhere it holds is one change, and
|
|
151
|
-
the same fix
|
|
153
|
+
the same fix applied one site per run spends a review and a merge on each.
|
|
152
154
|
`;
|
package/dist/src/init/init.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
1
2
|
import fs from "node:fs";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { saveConfig } from "../config/config.js";
|
|
@@ -5,7 +6,7 @@ import { seedConfig } from "../config/config-example.js";
|
|
|
5
6
|
import { findOnPath, writeTextAtomic } from "../files/files.js";
|
|
6
7
|
import { COMMIT_IDENT, GIT_MISSING_MESSAGE, git, gitTry } from "../git/git-run.js";
|
|
7
8
|
import { hasCommits, isGitRepo } from "../git/git.js";
|
|
8
|
-
import { INITIAL_PROMPT_MAX_CHARS, PROMPT_END, PROMPT_START, briefFile, briefTemplate, readInitialPrompt, readmeTemplate, } from "../brief.js";
|
|
9
|
+
import { INITIAL_PROMPT_MAX_CHARS, PROMPT_END, PROMPT_START, briefFile, briefTemplate, readInitialPrompt, readmeTemplate, } from "../brief/brief.js";
|
|
9
10
|
import { CONFIG_BASENAME, STATE_DIR, configPath } from "../paths.js";
|
|
10
11
|
import { projectName } from "../project-name.js";
|
|
11
12
|
import { tooLongMessage } from "../text/text.js";
|
|
@@ -97,7 +98,7 @@ export async function initProject(root, initialPrompt, branch, opts = {}) {
|
|
|
97
98
|
? `${tpl.briefPreamble}\n\n${initialPrompt.trim()}`
|
|
98
99
|
: initialPrompt.trim();
|
|
99
100
|
if (combinedPrompt.length > INITIAL_PROMPT_MAX_CHARS) {
|
|
100
|
-
throw new Error(tooLongMessage(`the
|
|
101
|
+
throw new Error(tooLongMessage(`the brief with the ${tpl.id} template's preamble`, combinedPrompt.length, INITIAL_PROMPT_MAX_CHARS, PREFILL_REASON));
|
|
101
102
|
}
|
|
102
103
|
if (!findOnPath("git"))
|
|
103
104
|
throw new Error(GIT_MISSING_MESSAGE);
|
|
@@ -117,17 +118,17 @@ export async function initProject(root, initialPrompt, branch, opts = {}) {
|
|
|
117
118
|
const readmeHint = fs.existsSync(path.join(root, "README.md"))
|
|
118
119
|
? ` (a bare \`tumwater init\` reads the prompt from README.md between ${PROMPT_START} and ${PROMPT_END}, but README.md carries none)`
|
|
119
120
|
: "";
|
|
120
|
-
throw new Error(`
|
|
121
|
+
throw new Error(`a brief is required: tumwater init <prompt | --file prompt.md>${readmeHint}`);
|
|
121
122
|
}
|
|
122
123
|
// `--branch` seeds a NEW repository only: an existing repo's checked-out branch is the
|
|
123
|
-
//
|
|
124
|
+
// team's business (`tumwater start --branch` or baseBranch chooses it at runtime), so a
|
|
124
125
|
// --branch given here would otherwise be silently ignored — the exact failure mode
|
|
125
126
|
// rejectUnknownArgs exists to prevent. Fail before any side effect, dry-run included.
|
|
126
127
|
// Whether `root` already is a repo is asked once here; nothing below until the `git init`
|
|
127
128
|
// can change it.
|
|
128
129
|
const wasRepo = await isGitRepo(root);
|
|
129
130
|
if (branch !== undefined && wasRepo) {
|
|
130
|
-
throw new Error(`--branch only seeds a new repository: this directory is already a git repository and init leaves its checked-out branch alone — drop --branch, or point the
|
|
131
|
+
throw new Error(`--branch only seeds a new repository: this directory is already a git repository and init leaves its checked-out branch alone — drop --branch, or point the team at a branch with \`tumwater start --branch ${branch}\` (or baseBranch in tumwater.json)`);
|
|
131
132
|
}
|
|
132
133
|
// The loops read the project's reason to exist back out of the project brief on every
|
|
133
134
|
// tick (readInitialPrompt). A README.md without the managed section is the project's own
|
|
@@ -142,7 +143,7 @@ export async function initProject(root, initialPrompt, branch, opts = {}) {
|
|
|
142
143
|
// untouched and the prompt silently dropped — every loop would then run blind. Fail before
|
|
143
144
|
// creating anything so the user fixes it and re-runs.
|
|
144
145
|
if (adopted && fs.existsSync(path.join(root, "TUMWATER.md"))) {
|
|
145
|
-
throw new Error(`TUMWATER.md already exists without
|
|
146
|
+
throw new Error(`TUMWATER.md already exists without a brief between the tumwater:prompt markers, so your prompt would be lost — add it to TUMWATER.md between ${PROMPT_START} and ${PROMPT_END} (or delete TUMWATER.md so init creates one), then re-run \`tumwater init\``);
|
|
146
147
|
}
|
|
147
148
|
// The same loss one step later: a brief that already owns the managed sections is never
|
|
148
149
|
// rewritten (init only creates what is absent), so a DIFFERENT prompt given here would be
|
|
@@ -151,7 +152,7 @@ export async function initProject(root, initialPrompt, branch, opts = {}) {
|
|
|
151
152
|
// so pointing at README.md would send the user to edit a file nobody reads. A re-run with the
|
|
152
153
|
// same prompt, or a bare init, is the idempotent re-seed and passes.
|
|
153
154
|
if (prompt && brief !== null && combinedPrompt !== readInitialPrompt(root)) {
|
|
154
|
-
throw new Error(`${brief} already carries a different
|
|
155
|
+
throw new Error(`${brief} already carries a different brief between the tumwater:prompt markers, and init never rewrites an existing project brief, so your prompt would be lost — to change the prompt, edit it in ${brief} between ${PROMPT_START} and ${PROMPT_END}; to re-seed with the current one, re-run a bare \`tumwater init\``);
|
|
155
156
|
}
|
|
156
157
|
let repoInitialized = false;
|
|
157
158
|
let createdBranch;
|
|
@@ -159,7 +160,7 @@ export async function initProject(root, initialPrompt, branch, opts = {}) {
|
|
|
159
160
|
// Branch precedence: the caller's explicit --branch, else git's own init.defaultBranch
|
|
160
161
|
// preference, else main. `git init -b` does not consult init.defaultBranch, so the
|
|
161
162
|
// preference is read here: an operator who configured `init.defaultBranch=trunk`
|
|
162
|
-
// expects `git init`-compatible behavior, and the
|
|
163
|
+
// expects `git init`-compatible behavior, and the team then targets that branch.
|
|
163
164
|
const preferred = branch ?? ((await gitTry(root, "config", "--get", "init.defaultBranch")) || "main");
|
|
164
165
|
// A branch name git rejects (a space, a leading dash, "@{…}" history syntax, …) would
|
|
165
166
|
// abort `git init -b` AFTER it has created .git — a half-initialized directory whose
|
|
@@ -219,7 +220,7 @@ export async function initProject(root, initialPrompt, branch, opts = {}) {
|
|
|
219
220
|
else
|
|
220
221
|
write("README.md", readmeTemplate(name, combinedPrompt));
|
|
221
222
|
// A template with starter plans seeds the backlog with them, rendered as `### ` entries —
|
|
222
|
-
// the primary entry format every backlog reader parses — so the
|
|
223
|
+
// the primary entry format every backlog reader parses — so the team's first ticks land on
|
|
223
224
|
// real work. blank keeps the `_None yet._` placeholder byte-identical to today.
|
|
224
225
|
const plansContent = tpl.starterPlans.length > 0
|
|
225
226
|
? PLANS_TEMPLATE.replace("_None yet._", tpl.starterPlans.map((p) => `### ${p}`).join("\n\n"))
|
|
@@ -245,7 +246,7 @@ export async function initProject(root, initialPrompt, branch, opts = {}) {
|
|
|
245
246
|
created.push(".gitignore");
|
|
246
247
|
else if (fs.existsSync(path.join(root, ".gitignore")))
|
|
247
248
|
leftAlone.push(".gitignore");
|
|
248
|
-
// The template's starter directories: empty ones only, before the first tick — the
|
|
249
|
+
// The template's starter directories: empty ones only, before the first tick — the team's
|
|
249
250
|
// own ticks write any code. Existing directories are left alone, like existing files above.
|
|
250
251
|
for (const dir of tpl.starterDirs) {
|
|
251
252
|
claim(dir, path.join(root, dir), () => fs.mkdirSync(path.join(root, dir), { recursive: true }));
|
|
@@ -263,7 +264,7 @@ export async function initProject(root, initialPrompt, branch, opts = {}) {
|
|
|
263
264
|
const staged = await gitTry(root, "diff", "--cached", "--quiet");
|
|
264
265
|
if (staged === null) {
|
|
265
266
|
// Non-zero exit = something is staged.
|
|
266
|
-
const message = withCommits ? "tumwater: init
|
|
267
|
+
const message = withCommits ? "tumwater: init files" : "tumwater: init";
|
|
267
268
|
await git(root, ...COMMIT_IDENT, "commit", "-m", message, "--", ...committable);
|
|
268
269
|
committed = true;
|
|
269
270
|
}
|