Make the reusable prompt terminal-only
This commit is contained in:
+5
-1
@@ -40,7 +40,11 @@ that copy, run `scripts/nq-controller --check`, and verify the OpenCode
|
|||||||
agents (terminal-orchestrator, developer, and approver)
|
agents (terminal-orchestrator, developer, and approver)
|
||||||
with `opencode debug agent <name>` while `OPENCODE_CONFIG_DIR` points at the
|
with `opencode debug agent <name>` while `OPENCODE_CONFIG_DIR` points at the
|
||||||
installed `.opencode` directory. Install `scripts/nq-orchestrate` in
|
installed `.opencode` directory. Install `scripts/nq-orchestrate` in
|
||||||
`~/.local/bin/`. The old `nestquest-controller.service` remains static and
|
`~/.local/bin/`. For the global slash command, also install
|
||||||
|
`.opencode/agents/terminal-orchestrator.md` under
|
||||||
|
`~/.config/opencode/agents/` and
|
||||||
|
`deploy/opencode-global-commands/start-nestquest-development.md` under
|
||||||
|
`~/.config/opencode/commands/`. The old `nestquest-controller.service` remains static and
|
||||||
inactive only for migration safety; foreground launch stops it.
|
inactive only for migration safety; foreground launch stops it.
|
||||||
|
|
||||||
Inside an already-open OpenCode TUI, `/start-development` (or the global
|
Inside an already-open OpenCode TUI, `/start-development` (or the global
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import unittest
|
|||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[1]
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
RENDER = runpy.run_path(str(ROOT / "scripts/render-cycle-prompt"))["render"]
|
RENDER = runpy.run_path(str(ROOT / "scripts/render-cycle-prompt"))["render"]
|
||||||
SELECT_RUNTIME = runpy.run_path(str(ROOT / "scripts/render-cycle-prompt"))["select_runtime"]
|
|
||||||
TEMPLATE = (ROOT / "templates/ORCHESTRATOR-PROMPT.template.md").read_text()
|
TEMPLATE = (ROOT / "templates/ORCHESTRATOR-PROMPT.template.md").read_text()
|
||||||
PROFILE = json.loads((ROOT / "templates/nestquest.profile.json").read_text())
|
PROFILE = json.loads((ROOT / "templates/nestquest.profile.json").read_text())
|
||||||
|
|
||||||
@@ -23,9 +22,8 @@ class CyclePromptTests(unittest.TestCase):
|
|||||||
"RELEASE_BRANCH": "production",
|
"RELEASE_BRANCH": "production",
|
||||||
"CONTROL_HOME": "/opt/examplequest/controller",
|
"CONTROL_HOME": "/opt/examplequest/controller",
|
||||||
"SERVICE_NAME": "examplequest-controller.service",
|
"SERVICE_NAME": "examplequest-controller.service",
|
||||||
"START_COMMAND": "/start-examplequest-development",
|
"PROJECT_ROOT": "/srv/examplequest",
|
||||||
"STOP_COMMAND": "/stop-examplequest-development",
|
"TERMINAL_LAUNCHER": "eq-orchestrate",
|
||||||
"STATUS_COMMAND": "/examplequest-development-status",
|
|
||||||
"FEATURE_BRANCH_EXAMPLE": "feature/EQ-17",
|
"FEATURE_BRANCH_EXAMPLE": "feature/EQ-17",
|
||||||
})
|
})
|
||||||
output = RENDER(TEMPLATE, other)
|
output = RENDER(TEMPLATE, other)
|
||||||
@@ -45,9 +43,8 @@ class CyclePromptTests(unittest.TestCase):
|
|||||||
with self.assertRaisesRegex(ValueError, "PROJECT_NAME"):
|
with self.assertRaisesRegex(ValueError, "PROJECT_NAME"):
|
||||||
RENDER(TEMPLATE, other)
|
RENDER(TEMPLATE, other)
|
||||||
|
|
||||||
def test_terminal_runtime_replaces_service_controller(self):
|
def test_template_contains_only_terminal_runtime(self):
|
||||||
selected = SELECT_RUNTIME(TEMPLATE, "terminal")
|
output = RENDER(TEMPLATE, PROFILE)
|
||||||
output = RENDER(selected, PROFILE)
|
|
||||||
self.assertIn("# 27. FOREGROUND TERMINAL RUNTIME", output)
|
self.assertIn("# 27. FOREGROUND TERMINAL RUNTIME", output)
|
||||||
self.assertIn("nq-orchestrate", output)
|
self.assertIn("nq-orchestrate", output)
|
||||||
self.assertNotIn("# 27. HOST CONTROLLER RUNTIME", output)
|
self.assertNotIn("# 27. HOST CONTROLLER RUNTIME", output)
|
||||||
|
|||||||
@@ -24,7 +24,13 @@ class ForegroundLauncherTests(unittest.TestCase):
|
|||||||
renderer = scripts / "render-cycle-prompt"
|
renderer = scripts / "render-cycle-prompt"
|
||||||
renderer.write_text("#!/bin/sh\nprintf 'BASE TERMINAL PROMPT'")
|
renderer.write_text("#!/bin/sh\nprintf 'BASE TERMINAL PROMPT'")
|
||||||
controller = scripts / "nq-controller"
|
controller = scripts / "nq-controller"
|
||||||
controller.write_text("#!/bin/sh\nprintf 'no interactive NestQuest session\\n'")
|
controller_args = root / "controller-args"
|
||||||
|
open_marker = root / "open-session"
|
||||||
|
controller.write_text(
|
||||||
|
f"#!/bin/sh\nprintf '%s\\n' \"$@\" > {controller_args}\n"
|
||||||
|
f"if test -f {open_marker}; then printf 'interactive NestQuest session open\\n'; "
|
||||||
|
"else printf 'no interactive NestQuest session\\n'; fi\n"
|
||||||
|
)
|
||||||
opencode = root / "opencode"
|
opencode = root / "opencode"
|
||||||
output = root / "args"
|
output = root / "args"
|
||||||
opencode.write_text(f"#!/bin/sh\nprintf '%s\\n' \"$@\" > {output}\n")
|
opencode.write_text(f"#!/bin/sh\nprintf '%s\\n' \"$@\" > {output}\n")
|
||||||
@@ -57,6 +63,16 @@ class ForegroundLauncherTests(unittest.TestCase):
|
|||||||
self.assertIn("Complete feature EQ-17.", args)
|
self.assertIn("Complete feature EQ-17.", args)
|
||||||
self.assertEqual(stopped.read_text().splitlines(),
|
self.assertEqual(stopped.read_text().splitlines(),
|
||||||
["--user", "stop", "nestquest-controller.service"])
|
["--user", "stop", "nestquest-controller.service"])
|
||||||
|
self.assertEqual(controller_args.read_text().splitlines(),
|
||||||
|
["--check-interactive", "--project", str(project)])
|
||||||
|
|
||||||
|
output.unlink()
|
||||||
|
open_marker.touch()
|
||||||
|
refused = subprocess.run([str(SCRIPT)], env=env, text=True,
|
||||||
|
capture_output=True)
|
||||||
|
self.assertEqual(refused.returncode, 75)
|
||||||
|
self.assertIn("Another interactive NestQuest", refused.stderr)
|
||||||
|
self.assertFalse(output.exists())
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Manually control the durable NestQuest development cycle from OpenCode.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
if [[ $# -ne 1 ]]; then
|
|
||||||
echo "usage: nq-cycle start|stop|status" >&2
|
|
||||||
exit 64
|
|
||||||
fi
|
|
||||||
|
|
||||||
unit=nestquest-controller.service
|
|
||||||
controller=/home/overseer/.local/share/nestquest-controller/scripts/nq-controller
|
|
||||||
|
|
||||||
show_status() {
|
|
||||||
systemctl --user show "$unit" -p ActiveState -p SubState -p UnitFileState
|
|
||||||
if [[ -f /home/overseer/.local/state/nestquest-controller/last-run.json ]]; then
|
|
||||||
python3 -c 'import json,pathlib; p=pathlib.Path.home()/".local/state/nestquest-controller/last-run.json"; d=json.loads(p.read_text()); print("LastTickState="+str(d.get("state","unknown"))); print("LastTickTime="+str(d.get("time","unknown")))'
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
case "$1" in
|
|
||||||
start)
|
|
||||||
systemctl --user start "$unit"
|
|
||||||
show_status
|
|
||||||
if session_status=$("$controller" --check-interactive); then
|
|
||||||
echo "$session_status"
|
|
||||||
if [[ "$session_status" == "interactive NestQuest session open" ]]; then
|
|
||||||
echo "Close the interactive NestQuest OpenCode session to let the controller run its first tick."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "The controller will wait for OpenCode session detection to recover."
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
stop)
|
|
||||||
systemctl --user stop "$unit"
|
|
||||||
show_status
|
|
||||||
;;
|
|
||||||
status)
|
|
||||||
show_status
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "usage: nq-cycle start|stop|status" >&2
|
|
||||||
exit 64
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
@@ -18,7 +18,7 @@ for required in "$renderer" "$profile"; do
|
|||||||
[[ -e "$required" ]] || { echo "missing required path: $required" >&2; exit 66; }
|
[[ -e "$required" ]] || { echo "missing required path: $required" >&2; exit 66; }
|
||||||
done
|
done
|
||||||
|
|
||||||
prompt=$("$renderer" "$profile" --runtime terminal)
|
prompt=$("$renderer" "$profile")
|
||||||
if [[ ${1:-} == "--print-prompt" ]]; then
|
if [[ ${1:-} == "--print-prompt" ]]; then
|
||||||
printf '%s\n' "$prompt"
|
printf '%s\n' "$prompt"
|
||||||
exit 0
|
exit 0
|
||||||
@@ -41,7 +41,7 @@ fi
|
|||||||
# Foreground mode owns orchestration. A dormant service must never race it.
|
# Foreground mode owns orchestration. A dormant service must never race it.
|
||||||
"$systemctl_bin" --user stop nestquest-controller.service
|
"$systemctl_bin" --user stop nestquest-controller.service
|
||||||
|
|
||||||
session_status=$("$control_home/scripts/nq-controller" --check-interactive)
|
session_status=$("$control_home/scripts/nq-controller" --check-interactive --project "$project")
|
||||||
if [[ "$session_status" == "interactive NestQuest session open" ]]; then
|
if [[ "$session_status" == "interactive NestQuest session open" ]]; then
|
||||||
echo "Another interactive NestQuest OpenCode session is open." >&2
|
echo "Another interactive NestQuest OpenCode session is open." >&2
|
||||||
echo "Close it, then run nq-orchestrate again." >&2
|
echo "Close it, then run nq-orchestrate again." >&2
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import sys
|
|||||||
|
|
||||||
|
|
||||||
TEMPLATE = Path(__file__).resolve().parent.parent / "templates/ORCHESTRATOR-PROMPT.template.md"
|
TEMPLATE = Path(__file__).resolve().parent.parent / "templates/ORCHESTRATOR-PROMPT.template.md"
|
||||||
RUNTIME_DIR = TEMPLATE.parent / "runtime"
|
|
||||||
PLACEHOLDER = re.compile(r"\{\{([A-Z][A-Z0-9_]*)\}\}")
|
PLACEHOLDER = re.compile(r"\{\{([A-Z][A-Z0-9_]*)\}\}")
|
||||||
|
|
||||||
|
|
||||||
@@ -29,27 +28,15 @@ def render(template: str, profile: dict[str, str]) -> str:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def select_runtime(template: str, runtime: str) -> str:
|
|
||||||
if runtime == "service":
|
|
||||||
return template
|
|
||||||
marker = "# 27. HOST CONTROLLER RUNTIME"
|
|
||||||
if template.count(marker) != 1:
|
|
||||||
raise ValueError("controller runtime section is missing or duplicated")
|
|
||||||
prefix = template.split(marker, 1)[0].rstrip()
|
|
||||||
overlay = (RUNTIME_DIR / f"{runtime}.md").read_text().strip()
|
|
||||||
return prefix + "\n\n" + overlay + "\n"
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
parser = argparse.ArgumentParser(description=__doc__)
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
parser.add_argument("profile", type=Path)
|
parser.add_argument("profile", type=Path)
|
||||||
parser.add_argument("--runtime", choices=("service", "terminal"), default="service")
|
|
||||||
parser.add_argument("--output", type=Path)
|
parser.add_argument("--output", type=Path)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
profile = json.loads(args.profile.read_text())
|
profile = json.loads(args.profile.read_text())
|
||||||
if not isinstance(profile, dict):
|
if not isinstance(profile, dict):
|
||||||
parser.error("profile must be a JSON object")
|
parser.error("profile must be a JSON object")
|
||||||
content = render(select_runtime(TEMPLATE.read_text(), args.runtime), profile)
|
content = render(TEMPLATE.read_text(), profile)
|
||||||
if args.output:
|
if args.output:
|
||||||
args.output.write_text(content)
|
args.output.write_text(content)
|
||||||
print(args.output)
|
print(args.output)
|
||||||
|
|||||||
@@ -922,44 +922,37 @@ You are the controller of a persistent development pipeline. Keep the pipeline m
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# 27. HOST CONTROLLER RUNTIME
|
# 27. FOREGROUND TERMINAL RUNTIME
|
||||||
|
|
||||||
This section is the host execution contract for OpenCode {{OPENCODE_VERSION}} and takes
|
This section is the execution contract for a foreground OpenCode
|
||||||
precedence over Section 21 when deciding whether a single model invocation may
|
orchestrator. It replaces the service runtime section in the rendered prompt
|
||||||
end. The persistent user service runs one fresh, headless orchestrator turn per
|
and takes precedence over any service-specific runtime instructions later
|
||||||
controller tick. Section 21 governs mission completion, not the lifetime of
|
read from {{CONTEXT_STORE}}.
|
||||||
one OpenCode turn.
|
|
||||||
|
|
||||||
The user starts this project's cycle explicitly from OpenCode with
|
The user explicitly started this run with `{{TERMINAL_LAUNCHER}}`. Begin
|
||||||
`{{START_COMMAND}}`. `{{STOP_COMMAND}}` stops it, and
|
work immediately in `{{PROJECT_ROOT}}`; do not start, wait for, or delegate
|
||||||
`{{STATUS_COMMAND}}` reports its state. The `{{SERVICE_NAME}}` user unit is
|
control to `{{SERVICE_NAME}}`. This single OpenCode session owns the
|
||||||
not enabled at login. Reading this prompt in an ordinary interactive session
|
orchestration loop until a global terminal condition, an explicit user stop,
|
||||||
does not authorize starting the background service. The service waits for the
|
or a host-forced yield.
|
||||||
interactive project session to close before its first tick.
|
|
||||||
|
|
||||||
At the end of EVERY orchestrator invocation, emit exactly one final line:
|
Execute `CONTROLLER_TICK` repeatedly inside this foreground session. Do not
|
||||||
`ORCHESTRATOR_STATE: CONTINUE`, `ORCHESTRATOR_STATE: QUIESCENT`, or
|
end a turn merely to emit `ORCHESTRATOR_STATE: CONTINUE`, and do not treat
|
||||||
`ORCHESTRATOR_STATE: BLOCKED`. Use CONTINUE whenever eligible or in-flight
|
one task, review, pull request, or feature merge as completion. After every
|
||||||
work remains and state the next concrete action immediately above that line.
|
child result or external state change, reconcile {{CONTEXT_STORE}},
|
||||||
The service starts a fresh invocation after reconciling {{CONTEXT_STORE}}, {{WORK_TRACKER}}, Git,
|
{{WORK_TRACKER}}, Git, pull requests, and checks, then perform the next
|
||||||
pull requests, and checks. Use QUIESCENT only after verifying that no eligible
|
eligible action.
|
||||||
or in-flight work remains; the service polls again. Use BLOCKED only for a
|
|
||||||
specific required human decision or access failure; name the missing action.
|
|
||||||
A blocked task does not block other eligible work. If {{CONTEXT_STORE}} is unavailable,
|
|
||||||
do not dispatch or merge, and return BLOCKED.
|
|
||||||
|
|
||||||
The installed launchers live at
|
The installed role launchers live at `{{CONTROL_HOME}}/scripts/`. Use
|
||||||
`{{CONTROL_HOME}}/scripts/`. Use `{{ROLE_LAUNCHER}}`
|
`{{ROLE_LAUNCHER}}` for developer and approver sessions. Use
|
||||||
to start developer or approver OpenCode sessions. Use `{{REVIEW_LAUNCHER}}` with
|
`{{REVIEW_LAUNCHER}}` with WORKTREE, BASE_REF, HEAD_REF,
|
||||||
WORKTREE, BASE_REF, HEAD_REF, REVIEW_PROMPT_FILE, PR_NUMBER, OUTPUT_FILE to
|
REVIEW_PROMPT_FILE, PR_NUMBER, OUTPUT_FILE for a fresh read-only
|
||||||
run a fresh read-only {{REVIEWER_DISPLAY_NAME}} review at high effort. Its output file
|
{{REVIEWER_DISPLAY_NAME}} review. Review output belongs under
|
||||||
must be under `{{SCRATCH_DIR}}`; it posts a {{CODE_HOST}} review as `{{REVIEW_ACCOUNT}}`
|
`{{SCRATCH_DIR}}`, and exact-head approval must be posted by
|
||||||
only after approving the exact current head. The feature-to-{{INTEGRATION_BRANCH}} approver uses
|
`{{REVIEW_ACCOUNT}}`. The feature-to-{{INTEGRATION_BRANCH}} approver must
|
||||||
`{{MERGE_BROKER}}` and must verify that same exact-head {{CODE_HOST}} approval.
|
use `{{MERGE_BROKER}}`; the orchestrator and developer never perform that
|
||||||
No controller or developer session performs the feature-to-{{INTEGRATION_BRANCH}} merge.
|
merge.
|
||||||
|
|
||||||
The service holds one controller lock and waits while an interactive
|
If the host forces a yield before a global terminal condition, return the
|
||||||
{{PROJECT_NAME}} OpenCode session is open. Each model turn has a {{TICK_TIMEOUT_LABEL}} host
|
machine-readable checkpoint from Section 22. The user can resume the same
|
||||||
timeout. After an error or timeout, reconcile external state before retrying;
|
OpenCode session from the terminal. A yield is a checkpoint, not a claim that
|
||||||
three consecutive failed turns stop the service for inspection. A successful
|
the project queue is complete.
|
||||||
feature merge is a transition back into scheduling, not a terminal condition.
|
|
||||||
|
|||||||
+5
-6
@@ -7,7 +7,6 @@ example. Render a concrete prompt with:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
scripts/render-cycle-prompt templates/nestquest.profile.json --output /tmp/opencode/nestquest-prompt.md
|
scripts/render-cycle-prompt templates/nestquest.profile.json --output /tmp/opencode/nestquest-prompt.md
|
||||||
scripts/render-cycle-prompt templates/nestquest.profile.json --runtime terminal --output /tmp/opencode/nestquest-terminal-prompt.md
|
|
||||||
```
|
```
|
||||||
|
|
||||||
For another project, copy the profile and set its project name, tracker,
|
For another project, copy the profile and set its project name, tracker,
|
||||||
@@ -17,17 +16,17 @@ section, especially task/feature semantics, release rules, permissions,
|
|||||||
review gates, and the host runtime section. The template assumes an OpenCode
|
review gates, and the host runtime section. The template assumes an OpenCode
|
||||||
controller, a feature/task tracker, a versioned context store, a code host,
|
controller, a feature/task tracker, a versioned context store, a code host,
|
||||||
and a separate reviewer and approver. Adapt those sections when the project's
|
and a separate reviewer and approver. Adapt those sections when the project's
|
||||||
systems differ. The default `service` runtime reproduces the CTXD prompt.
|
systems differ. Section 27 is a single-session foreground loop suitable for
|
||||||
The `terminal` runtime replaces Section 27 with a single-session foreground
|
`opencode --agent ... --prompt ...`; the reusable prompt has no service
|
||||||
loop suitable for `opencode --agent ... --prompt ...`.
|
runtime.
|
||||||
|
|
||||||
For NestQuest, `nq-orchestrate --print-prompt` emits the fully rendered
|
For NestQuest, `nq-orchestrate --print-prompt` emits the fully rendered
|
||||||
foreground prompt without starting OpenCode. `nq-orchestrate [mission.md]`
|
foreground prompt without starting OpenCode. `nq-orchestrate [mission.md]`
|
||||||
renders that same prompt, appends the optional mission, and supplies it to the
|
renders that same prompt, appends the optional mission, and supplies it to the
|
||||||
foreground orchestrator agent.
|
foreground orchestrator agent.
|
||||||
|
|
||||||
Publish the reviewed **service-rendered** prompt into that project's context
|
Publish the reviewed rendered prompt into that project's context store with
|
||||||
store with its version check when CTXD should retain both runtime choices.
|
its version check.
|
||||||
Give the project its own agent definitions, broker, profile, and terminal
|
Give the project its own agent definitions, broker, profile, and terminal
|
||||||
launcher. The template itself does not start an orchestrator or register
|
launcher. The template itself does not start an orchestrator or register
|
||||||
another project.
|
another project.
|
||||||
|
|||||||
@@ -25,9 +25,6 @@
|
|||||||
"PROJECT_ROOT": "/mnt/ai-storage/projects/NestQuest",
|
"PROJECT_ROOT": "/mnt/ai-storage/projects/NestQuest",
|
||||||
"CONTROL_HOME": "/home/overseer/.local/share/nestquest-controller",
|
"CONTROL_HOME": "/home/overseer/.local/share/nestquest-controller",
|
||||||
"SERVICE_NAME": "nestquest-controller.service",
|
"SERVICE_NAME": "nestquest-controller.service",
|
||||||
"START_COMMAND": "/start-nestquest-development",
|
|
||||||
"STOP_COMMAND": "/stop-nestquest-development",
|
|
||||||
"STATUS_COMMAND": "/nestquest-development-status",
|
|
||||||
"TERMINAL_LAUNCHER": "nq-orchestrate",
|
"TERMINAL_LAUNCHER": "nq-orchestrate",
|
||||||
"ROLE_LAUNCHER": "nq-agent",
|
"ROLE_LAUNCHER": "nq-agent",
|
||||||
"REVIEW_LAUNCHER": "nq-review",
|
"REVIEW_LAUNCHER": "nq-review",
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
# 27. FOREGROUND TERMINAL RUNTIME
|
|
||||||
|
|
||||||
This section is the execution contract for a foreground OpenCode
|
|
||||||
orchestrator. It replaces the service runtime section in the rendered prompt
|
|
||||||
and takes precedence over any service-specific runtime instructions later
|
|
||||||
read from {{CONTEXT_STORE}}.
|
|
||||||
|
|
||||||
The user explicitly started this run with `{{TERMINAL_LAUNCHER}}`. Begin
|
|
||||||
work immediately in `{{PROJECT_ROOT}}`; do not start, wait for, or delegate
|
|
||||||
control to `{{SERVICE_NAME}}`. This single OpenCode session owns the
|
|
||||||
orchestration loop until a global terminal condition, an explicit user stop,
|
|
||||||
or a host-forced yield.
|
|
||||||
|
|
||||||
Execute `CONTROLLER_TICK` repeatedly inside this foreground session. Do not
|
|
||||||
end a turn merely to emit `ORCHESTRATOR_STATE: CONTINUE`, and do not treat
|
|
||||||
one task, review, pull request, or feature merge as completion. After every
|
|
||||||
child result or external state change, reconcile {{CONTEXT_STORE}},
|
|
||||||
{{WORK_TRACKER}}, Git, pull requests, and checks, then perform the next
|
|
||||||
eligible action.
|
|
||||||
|
|
||||||
The installed role launchers live at `{{CONTROL_HOME}}/scripts/`. Use
|
|
||||||
`{{ROLE_LAUNCHER}}` for developer and approver sessions. Use
|
|
||||||
`{{REVIEW_LAUNCHER}}` with WORKTREE, BASE_REF, HEAD_REF,
|
|
||||||
REVIEW_PROMPT_FILE, PR_NUMBER, OUTPUT_FILE for a fresh read-only
|
|
||||||
{{REVIEWER_DISPLAY_NAME}} review. Review output belongs under
|
|
||||||
`{{SCRATCH_DIR}}`, and exact-head approval must be posted by
|
|
||||||
`{{REVIEW_ACCOUNT}}`. The feature-to-{{INTEGRATION_BRANCH}} approver must
|
|
||||||
use `{{MERGE_BROKER}}`; the orchestrator and developer never perform that
|
|
||||||
merge.
|
|
||||||
|
|
||||||
If the host forces a yield before a global terminal condition, return the
|
|
||||||
machine-readable checkpoint from Section 22. The user can resume the same
|
|
||||||
OpenCode session from the terminal. A yield is a checkpoint, not a claim that
|
|
||||||
the project queue is complete.
|
|
||||||
Reference in New Issue
Block a user