@julioborges/gantry 1.0.5 → 1.1.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.
@@ -6,7 +6,7 @@
6
6
  "hooks": [
7
7
  {
8
8
  "type": "command",
9
- "command": "python3 \".agents/skills/gantry/scripts/guard.py\" PreToolUse --json"
9
+ "command": "python3 \"skills/gantry/scripts/guard.py\" PreToolUse --json"
10
10
  }
11
11
  ]
12
12
  }
@@ -17,7 +17,7 @@
17
17
  "hooks": [
18
18
  {
19
19
  "type": "command",
20
- "command": "python3 \".agents/skills/gantry/scripts/guard.py\" PostToolUse --json"
20
+ "command": "python3 \"skills/gantry/scripts/guard.py\" PostToolUse --json"
21
21
  }
22
22
  ]
23
23
  }
@@ -57,6 +57,19 @@ python3 "$skillDir/scripts/roadmap.py" check
57
57
  The last command must exit 0 before the Run recomputes the frontier. Planning approval does not permit
58
58
  silent issue additions, splits, merges or dependency changes.
59
59
 
60
+ ## Codex Host Orchestration and Hybrid Dispatch
61
+
62
+ When Codex CLI operates as the Host Harness:
63
+ 1. **Instruction Ingestion**: Codex reads repository policy and instructions from `AGENTS.md` (configured by `gantry-setup`), respecting all Gantry invariants (no direct commits to `main`, no manual edits to `ROADMAP.md` or Issue statuses).
64
+ 2. **Phase Orchestration**: The host drives the planning sequence:
65
+ - Structural validation (`spec.py --check`)
66
+ - Requirement Critic invocation via bounded subprocess dispatch
67
+ - Parallel research and vertical-slice drafting
68
+ - Plan Critic adversarial refutation
69
+ - Halting for mandatory operator approval before transitioning issues (`roadmap.py status <ref> ready-for-agent`, `roadmap.py waves`, `roadmap.py check`)
70
+ 3. **Bounded Subprocess Execution**: Roles are dispatched via `python3 "$skillDir/scripts/execution.py" dispatch --role <role> --cwd <dir>`. If a role uses Codex, it executes bounded `codex exec "<prompt>" --model <model>`. If a role is delegated to an external harness (such as Claude Code or Antigravity), cross-harness dispatch is invoked.
71
+ 4. **Contract Verification**: Every role result is parsed through `result.extract_json()` and validated against its schema contract (`result.py`). Delimiters and markdown code fences (` ```json `) are handled robustly, and protocol failures trigger bounded retries.
72
+
60
73
  ## Executable Claude Code Workflow
61
74
 
62
75
  The following is the executable Workflow script. Other harnesses execute the same prompt functions and
@@ -137,6 +137,41 @@ continuation) can pick it up. "Blocked" here names a Run-log fact about this att
137
137
  projection — status authority stays in Issue files, per this Issue's contract, and only `roadmap.py done`
138
138
  (after Critic acceptance) ever changes an Issue's `Status:` line.
139
139
 
140
+ ## Dashboard lifecycle hooks
141
+
142
+ Before the `Implement` phase begins in any round, the workflow queries `dashboard.py status --json`:
143
+ - If inactive, it prompts the operator asking whether to start the dashboard (`dashboard.py start --daemon`). If approved, it launches the daemon and displays the URL; if declined, it proceeds without prompting again.
144
+ - If already active, it logs the active URL without prompting.
145
+
146
+ Immediately after the `Integrate` step completes at the end of each round:
147
+ - The workflow queries `dashboard.py status --json`. If active, it prompts the operator asking whether to terminate the dashboard (`dashboard.py stop`). If approved, it terminates the server daemon.
148
+
149
+ ## Codex Host Orchestration and Hybrid Dispatch
150
+
151
+ When Codex CLI serves as the Host Harness:
152
+ 1. **Instruction Ingestion**: Codex reads repository policy and guidance from the marked Gantry section of `AGENTS.md`, adhering to immutable workflow invariants:
153
+ - Feature branches only (`feat/*`, `fix/*`, `docs/*`); never commit directly to `main`.
154
+ - Never manually edit `ROADMAP.md`, issue statuses, or criteria checkboxes; only `roadmap.py done` updates them upon verified Critic acceptance.
155
+ 2. **Lifecycle and Round Orchestration**:
156
+ - The host queries dashboard status before implementation (`dashboard.py status --json`) and offers daemon activation.
157
+ - For each issue in the frontier round, Codex isolates work in a dedicated worktree and branch (`common.issue_branch()`).
158
+ - Telemetry is recorded via `runlog.py` CLI events (`run.started`, `round.started`, `phase.started`, `phase.finished`, `subagent.started`, `subagent.stopped`, `issue.done`, `round.finished`).
159
+ 3. **Bounded Role Invocations**:
160
+ - Roles are invoked through bounded CLI processes via `execution.py dispatch --role <role> --cwd <worktree>`.
161
+ - When configured for Codex, role execution invokes `codex exec "<prompt>" --model <model> [--effort <effort>]`.
162
+ - When configured for cross-harness execution (e.g. an adversarial Critic powered by Claude Code or Antigravity), bounded dispatch invokes the respective CLI (`claude -p`, `agy --print`).
163
+ - Execution runners enforce process timeouts, error isolation, and model fallback detection.
164
+ 4. **Result Contract Enforcement**:
165
+ - All role outputs (Implementer, Reviewer, Critic, Learner) must conform to schemas bundled in `schemas/<role>.json`.
166
+ - Parsers robustly extract JSON payloads from markdown fences (` ```json `, ` ``` `, ` ````json `) and handle conversational commentary.
167
+ - Any schema mismatch triggers a Protocol Failure and bounded retry.
168
+ 5. **Defense in Depth**:
169
+ - Because Codex CLI does not expose native pre-tool hooks, guardrails are enforced through layered defense:
170
+ - Clear prompt invariants injected into `AGENTS.md`.
171
+ - Git hooks (`pre-commit`, `pre-push`) configured in `.git/hooks` enforcing fast-forward updates and preventing test-skips.
172
+ - Independent adversarial Critic running differential gates (`gates.py`) and acceptance verification (`acceptance.py`), refuting unverified claims or prompt violations.
173
+ - Branches integrate serially only after Critic acceptance and clean gate runs.
174
+
140
175
  ## Executable Claude Code Workflow
141
176
 
142
177
  This Workflow script is the executable chain. It creates one Implementer chain per Issue; `pipeline`
@@ -671,6 +706,30 @@ async function implement(issue, feedback, previous) {
671
706
  return result && result.worktree === assigned.worktree && result.branch === assigned.branch ? result : null
672
707
  }
673
708
 
709
+ // Pre-implementation dashboard check
710
+ if (typeof prompt === 'function' && !A.skipDashboardPrompt) {
711
+ const dashCheck = await runCommand(`python3 "${scripts}/dashboard.py" status --json`, { cwd: A.repoRoot })
712
+ let dashStatus = null
713
+ if (dashCheck && dashCheck.exitCode === 0 && dashCheck.stdout && dashCheck.stdout.trim()) {
714
+ try { dashStatus = JSON.parse(dashCheck.stdout) } catch (e) {}
715
+ }
716
+ if (dashStatus && dashStatus.running) {
717
+ log(`Dashboard active at ${dashStatus.url}`)
718
+ } else if (dashStatus && !dashStatus.running) {
719
+ const askStart = await prompt('The dashboard is currently inactive. Would you like to start it in the background (dashboard.py start --daemon)? (yes/no)')
720
+ if (askStart && askStart.toLowerCase().trim() === 'yes') {
721
+ const startRes = await runCommand(`python3 "${scripts}/dashboard.py" start --daemon`, { cwd: A.repoRoot })
722
+ if (startRes && startRes.exitCode === 0) {
723
+ const recheck = await runCommand(`python3 "${scripts}/dashboard.py" status --json`, { cwd: A.repoRoot })
724
+ try {
725
+ const recheckStatus = JSON.parse(recheck.stdout)
726
+ log(`Dashboard started at ${recheckStatus.url}`)
727
+ } catch (e) {}
728
+ }
729
+ }
730
+ }
731
+ }
732
+
674
733
  const results = await pipeline(
675
734
  A.issues,
676
735
  issue => implement(issue, null, null),
@@ -906,11 +965,23 @@ let integrationStopped = false
906
965
  let cancelReason = null
907
966
  for (const delivery of deliveries) {
908
967
  if (delivery.outcome !== 'accepted') continue
968
+ if (A.unitId && A.runId && A.waitGate) {
969
+ const stateRootArg = A.stateRoot ? ` --state-root "${A.stateRoot}"` : ''
970
+ await runWorkflowCommand(`python3 "${scripts}/wait_gate.py" ${delivery.ref} --unit "${A.unitId}" --run "${A.runId}"${stateRootArg}`)
971
+ }
909
972
  if (integrationStopped) {
910
973
  delivery.outcome = 'integration_pending'
911
974
  continue
912
975
  }
913
- if (A.isolate) {
976
+ const strategy = (A.policy && A.policy.delivery && A.policy.delivery.strategy) || 'branch-merge'
977
+ if (strategy === 'pull-request') {
978
+ const target = (A.policy && A.policy.git && A.policy.git.target) || 'main'
979
+ try {
980
+ await runWorkflowCommand(`gh pr view "${delivery.branch}" --json state -q .state`)
981
+ } catch {
982
+ await runWorkflowCommand(`git merge-base --is-ancestor "${delivery.branch}" "${target}"`)
983
+ }
984
+ } else if (A.isolate) {
914
985
  if (!delivery.branch) {
915
986
  delivery.outcome = 'integration_failed'
916
987
  integrationStopped = true
@@ -937,13 +1008,30 @@ for (const delivery of deliveries) {
937
1008
  await runWorkflowCommand(`git add -- ROADMAP.md "${delivery.issuePath}"`)
938
1009
  await runWorkflowCommand(`git commit -m "gantry: complete ${delivery.ref}"`)
939
1010
  delivery.outcome = 'done'
940
- await appendRunEvent('issue.done', delivery.ref, undefined, { worktree: delivery.worktree })
1011
+ await appendRunEvent('issue.done', delivery.ref, undefined, { worktree: delivery.worktree, strategy })
941
1012
  } catch {
942
1013
  delivery.outcome = 'integration_failed'
943
1014
  integrationStopped = true
944
1015
  cancelReason = { issue: delivery.ref, reason: 'integration_commit_failed' }
945
1016
  }
946
1017
  }
1018
+
1019
+ // Post-integration dashboard check
1020
+ if (typeof prompt === 'function' && !A.skipDashboardPrompt) {
1021
+ const postDashCmd = await runCommand(`python3 "${scripts}/dashboard.py" status --json`, { cwd: A.repoRoot })
1022
+ let postDashStatus = null
1023
+ if (postDashCmd && postDashCmd.exitCode === 0 && postDashCmd.stdout && postDashCmd.stdout.trim()) {
1024
+ try { postDashStatus = JSON.parse(postDashCmd.stdout) } catch (e) {}
1025
+ }
1026
+ if (postDashStatus && postDashStatus.running) {
1027
+ const askStop = await prompt('Integration complete. Would you like to shut down the background dashboard server (dashboard.py stop)? (yes/no)')
1028
+ if (askStop && askStop.toLowerCase().trim() === 'yes') {
1029
+ await runCommand(`python3 "${scripts}/dashboard.py" stop`, { cwd: A.repoRoot })
1030
+ log('Dashboard stopped.')
1031
+ }
1032
+ }
1033
+ }
1034
+
947
1035
  await appendRunEvent('round.finished', undefined, undefined, { round: A.round })
948
1036
  if (integrationStopped) {
949
1037
  await appendRunEvent('run.cancelled', cancelReason && cancelReason.issue, undefined, {
@@ -25,6 +25,9 @@ DEFAULT_POLICY = {
25
25
  "prefix": "gantry/",
26
26
  "issueBranch": "{prefix}{spec}-{number:02d}",
27
27
  },
28
+ "delivery": {
29
+ "strategy": "branch-merge",
30
+ },
28
31
  "hooks": {"record": [], "deny": []},
29
32
  "budget": {"corrections": 2, "contextShare": 0.15},
30
33
  "dashboard": {"staleAfterSeconds": 900},