@julioborges/gantry 1.0.6 → 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.
- package/.agents/skills/gantry/SKILL.md +15 -1
- package/.agents/skills/gantry/capabilities/codex.json +4 -3
- package/.agents/skills/gantry/dashboard/static/app.js +776 -30
- package/.agents/skills/gantry/dashboard/static/history.html +180 -0
- package/.agents/skills/gantry/dashboard/static/index.html +95 -0
- package/.agents/skills/gantry/dashboard/static/style.css +708 -0
- package/.agents/skills/gantry/reference/plan-workflow.md +13 -0
- package/.agents/skills/gantry/reference/round-workflow.md +90 -2
- package/.agents/skills/gantry/scripts/common.py +3 -0
- package/.agents/skills/gantry/scripts/dashboard.py +664 -11
- package/.agents/skills/gantry/scripts/discovery.py +117 -8
- package/.agents/skills/gantry/scripts/execution.py +131 -14
- package/.agents/skills/gantry/scripts/plan.py +658 -0
- package/.agents/skills/gantry/scripts/result.py +56 -1
- package/.agents/skills/gantry/scripts/runlog.py +2 -1
- package/.agents/skills/gantry/scripts/setup.py +101 -8
- package/.agents/skills/gantry/scripts/wait_gate.py +179 -0
- package/.agents/skills/gantry-dashboard/SKILL.md +21 -2
- package/.agents/skills/gantry-plan/SKILL.md +104 -0
- package/.agents/skills/gantry-setup/SKILL.md +9 -3
- package/README.md +125 -98
- package/package.json +2 -1
|
@@ -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
|
-
|
|
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},
|