Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
28461ee
chore: record delivery binding for stable-release
LeXwDeX Sep 4, 2026
3100332
chore: record delivery binding for stable-release
LeXwDeX Sep 4, 2026
0e6c95c
docs(release): record accepted v1.0.40 evidence
LeXwDeX Sep 4, 2026
cf5fd26
Merge pull request #544 from LeXwDeX/chore/543-stable-release
LeXwDeX Sep 4, 2026
9fa793f
chore: record delivery binding for dag-release
LeXwDeX Sep 5, 2026
72cc75f
chore: record delivery binding for dag-release
LeXwDeX Sep 5, 2026
68f28d2
chore(release): reconcile main and prepare v1.0.41 notes
LeXwDeX Sep 5, 2026
243481f
chore: record delivery binding for dag-release
LeXwDeX Sep 5, 2026
913b23c
Merge pull request #555 from LeXwDeX/chore/554-dag-release
LeXwDeX Sep 5, 2026
62fa870
chore: record delivery binding for hooks-runtime-reliability
LeXwDeX Sep 7, 2026
deb8b7e
chore: record delivery binding for hooks-runtime-reliability
LeXwDeX Sep 7, 2026
eba6221
chore: refresh SpecGit 1.14 delivery harness
LeXwDeX Sep 7, 2026
07e8787
fix(hooks): repair execution, decisions and lifecycle
LeXwDeX Sep 7, 2026
c92b853
merge: reconcile hooks repair with current main
LeXwDeX Sep 7, 2026
98a5588
chore: record delivery binding for hooks-runtime-reliability
LeXwDeX Sep 7, 2026
b56d931
fix(hooks): deliver feedback for tool defects and rejected MCP calls
LeXwDeX Sep 7, 2026
b37b336
Merge pull request #567 from LeXwDeX/fix/557-hooks-runtime-reliability
LeXwDeX Sep 7, 2026
949ff5f
chore: record delivery binding for goal-reliability
LeXwDeX Sep 7, 2026
0aa501b
chore: record delivery binding for goal-reliability
LeXwDeX Sep 7, 2026
f0e51eb
chore: record delivery binding for goal-reliability
LeXwDeX Sep 7, 2026
80689e3
chore: record delivery binding for goal-reliability
LeXwDeX Sep 7, 2026
9a65133
fix(goal): preserve execution boundaries and production wiring
LeXwDeX Sep 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/releases/v1.0.40.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@
### 🧪 Test Summary

```
Integration CI baseline (dev 8060765fcc):
Release integration CI (PR #539, 00695dab86):
core: 1225 pass, 6 skip, 0 fail
opencode: 4426 pass, 23 skip, 1 todo, 0 fail
opencode: 4429 pass, 23 skip, 1 todo, 0 fail
HttpAPI coverage / auth / effect: 230 pass each, no failures or missing routes
Generated client and SDK freshness: passed
Typecheck, DAG core gate, Linux and Windows E2E: passed
Expand All @@ -46,7 +46,7 @@ Merged opencode package typecheck: passed

### 🔍 Verification

The slow-tool regression was observed failing before the fix and passing afterward through the real session processor and a local HTTP model endpoint. Additional cases cover parallel local tools and explicit cancellation. Independent Standards and Spec reviews found no code blockers. The integration statistics above come from [dev CI](https://github.com/LeXwDeX/OpenCode-GraphAgent/actions/runs/33868550950); they identify the tested baseline and do not substitute for the final release PR's Typecheck, Linux unit, Linux/Windows E2E and SpecGit acceptance gates. Reported model usage in the regression is deterministic test input; no live model context limit is inferred from it.
The slow-tool regression was observed failing before the fix and passing afterward through the real session processor and a local HTTP model endpoint. Additional cases cover parallel local tools and explicit cancellation. Independent Standards, Spec and merge reviews found no code blockers. The integration statistics above come from the accepted [PR #539 CI](https://github.com/LeXwDeX/OpenCode-GraphAgent/actions/runs/33894718562), including generated client/SDK freshness and all three HttpAPI modes. [Typecheck, lint and DAG core](https://github.com/LeXwDeX/OpenCode-GraphAgent/actions/runs/33894718548), both E2E platforms and SpecGit 1.10.1 acceptance also passed before merge to dev. The release branch preserves that accepted runtime tree; [release PR #544](https://github.com/LeXwDeX/OpenCode-GraphAgent/pull/544) carries its own binding and main-targeted gates. Reported model usage in the regression is deterministic test input; no live model context limit is inferred from it.

---

Expand Down
51 changes: 51 additions & 0 deletions .github/releases/v1.0.41.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
## opencode {VERSION}

{Prerelease/Stable} release from `{branch}` branch. DAG replanning, recovery, input mapping, and single-step execution now preserve durable workflow semantics across races and restarts.

---

### 🐛 Bug Fixes

- **Stale execution isolation, [#545](https://github.com/LeXwDeX/OpenCode-GraphAgent/issues/545)**: bind admission and settlement to the durable execution identity so delayed workers, deadlines, and recovery observations cannot terminalize a replacement attempt.
- **Running definition consistency, [#546](https://github.com/LeXwDeX/OpenCode-GraphAgent/issues/546)**: reject execution-semantic changes to an admitted node unless a supported explicit restart applies the new definition, while retaining valid timeout updates.
- **Atomic replans, [#547](https://github.com/LeXwDeX/OpenCode-GraphAgent/issues/547)**: commit node mutations, workflow configuration, and graph revision together so a failed replan cannot expose a partial graph or dispatch missing configuration. Forward committed notifications in order even when earlier legacy listeners yield.
- **Durable checkpoint veto recovery, [#548](https://github.com/LeXwDeX/OpenCode-GraphAgent/issues/548)**: reconstruct unresolved reporting-checkpoint decisions after restart before dispatch or workflow completion.
- **Input mapping validation, [#549](https://github.com/LeXwDeX/OpenCode-GraphAgent/issues/549)**: reject unknown, self, and unordered mapping sources before execution and report missing required runtime values without dropping valid nulls.
- **Plain-text recovery parity, [#550](https://github.com/LeXwDeX/OpenCode-GraphAgent/issues/550)**: classify missing, empty, and whitespace-only recovered output the same way as live provider output while preserving valid text exactly.
- **Single-step convergence, [#551](https://github.com/LeXwDeX/OpenCode-GraphAgent/issues/551)**: propagate skipped dependency chains to a terminal state without dispatching more than one child for each user step.

---

### ⚙️ CI / Engineering

- **SpecGit 1.13.1 delivery compatibility, [#552](https://github.com/LeXwDeX/OpenCode-GraphAgent/issues/552)**: refresh the generated acceptance harness and local entry points, preserve the required long-running CI budget, and update the repository bootstrap wrapper for current body-file flags and init assets.
- **Integrated delivery, [PR #553](https://github.com/LeXwDeX/OpenCode-GraphAgent/pull/553)**: track the seven DAG fixes and harness maintenance as independently verifiable issues on the shared dev delivery.

---

### 🧪 Test Summary

```
core DAG: 92 pass
OpenCode DAG: 665 pass
output schema: 3 pass
TUI: 60 pass
local gate total: 820 tests, 0 failures
typecheck: 29/29 packages green
lint: 4845 warnings, budget 4850
SpecGit wrapper: 283 assertions, 0 failures
atomicity repeat: 20/20 passes
macOS install: 6 assertions, 0 failures
```

---

### 🔍 Verification

PR #553 was accepted at `e742eec4cef4339e54c6c72aedd5dbe7a7594fdd` and merged to dev as `345e5c13e7bdce2c739bace6327a896ebed88075`. [Typecheck and DAG coverage](https://github.com/LeXwDeX/OpenCode-GraphAgent/actions/runs/33931615264) and [unit tests, HttpAPI contracts, and Linux/Windows E2E](https://github.com/LeXwDeX/OpenCode-GraphAgent/actions/runs/33931615315) passed; SpecGit accepted the final head before merge. Independent GPT-5.6-Sol Standards and Spec reviews resolved every mandatory finding.

Regressions exercised real DAG commands, projections, durable stores, delayed execution boundaries, injected transaction rollback, restart recovery, checkpoint vetoes, mapping validation, plain-text settlement, and guarded single-step dispatch. Existing coverage floors and generated SDK freshness checks passed. A native candidate included the Web UI, 16 validated templates, and ripgrep; its real archive passed installer signature, tamper-rejection, and executable checks. The compiled server returned a healthy `1.0.41` response and HTTP 200 for its embedded homepage. The official release workflow validates reference templates and produces the platform archives with SHA256SUMS.

---

**Full changelog:** [`{previous_tag}`...`{current_tag}`](https://github.com/LeXwDeX/OpenCode-GraphAgent/compare/{previous_tag}...{current_tag})
2 changes: 1 addition & 1 deletion .github/workflows/specgit-accept.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ jobs:
# same CLI generation that wrote the binding; upgrades are a
# deliberate re-init. An isolated prefix avoids installing the
# adopting project's dependencies or running its lifecycle scripts.
run: npm install --prefix "$RUNNER_TEMP/specgit-cli" --no-save --no-audit --no-fund specgit@1.13.1
run: npm install --prefix "$RUNNER_TEMP/specgit-cli" --no-save --no-audit --no-fund specgit@1.14.0

- name: Prepare approved policy for acceptance
env:
Expand Down
190 changes: 178 additions & 12 deletions .opencode/hooks/specgit-merge-guard.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,169 @@ case "$tool" in
# (branch "feat/1-a" must never satisfy a record for "feat/1-a2").
branch=$(git branch --show-current 2>/dev/null)
if [ -z "$branch" ] || [ ! -f .specgit.yaml ] || ! grep -qFx " branch: $branch" .specgit.yaml; then
echo "specgit: start gate - this branch has no delivery binding. Start the delivery first: specgit issue \"<type>: <title>\", then fill each issue body from the discussion, then edit files." >&2
echo "specgit: start gate - this branch has no delivery binding. Start the delivery first according to the managed guidance: prepare any required body files, then run specgit issue \"<type>: <title>\" with them before editing files." >&2
exit 2
fi
exit 0
;;
esac
command=$(printf '%s' "$payload" | node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{try{const j=JSON.parse(s);process.stdout.write((j.tool_input&&j.tool_input.command)||'')}catch{process.stdout.write('')}})")

case "$command" in
gh\ pr\ merge*|glab\ mr\ merge*)
# Classify statically visible forge-merge commands without executing or
# expanding shell input. The lexer understands quoting, command separators,
# environment assignments, env/command/exec wrappers, and forge-global repo
# selectors. It deliberately inspects only each simple command's executable
# and leading global options, so quoted prose such as echo "gh pr merge" does
# not trigger the gate.
merge_command=$(printf '%s' "$command" | node -e '
const fs = require("fs");
const source = fs.readFileSync(0, "utf8");
const SQ = String.fromCharCode(39);
const DQ = String.fromCharCode(34);
const BS = String.fromCharCode(92);

function tokenize(text) {
const tokens = [];
let word = "";
let active = false;
let quote = 0;
const flush = () => {
if (!active) return;
tokens.push({ kind: "word", value: word });
word = "";
active = false;
};
for (let index = 0; index < text.length; index += 1) {
const char = text[index];
if (quote === 39) {
if (char === SQ) quote = 0;
else word += char;
continue;
}
if (quote === 34) {
if (char === DQ) {
quote = 0;
} else if (char === BS) {
if (index + 1 >= text.length) return null;
word += text[++index];
} else {
word += char;
}
continue;
}
if (char === SQ || char === DQ) {
quote = char === SQ ? 39 : 34;
active = true;
} else if (char === BS) {
if (index + 1 >= text.length) return null;
const next = text[++index];
if (next !== "\n") {
word += next;
active = true;
}
} else if (char === " " || char === "\t" || char === "\r") {
flush();
} else if (char === "\n" || char === ";" || char === "|" ||
char === "&" || char === "(" || char === ")") {
flush();
if ((char === "|" || char === "&") && text[index + 1] === char) index += 1;
tokens.push({ kind: "boundary" });
} else if (char === "#" && !active) {
while (index + 1 < text.length && text[index + 1] !== "\n") index += 1;
} else {
word += char;
active = true;
}
}
if (quote !== 0) return null;
flush();
return tokens;
}

const executable = (word) => {
const base = word.replace(/\\/g, "/").split("/").pop() || "";
return base.toLowerCase().replace(/\.exe$/, "");
};
const assignment = /^[A-Za-z_][A-Za-z0-9_]*=/;

function unwrap(words) {
let index = 0;
while (assignment.test(words[index] || "")) index += 1;
if (executable(words[index] || "") === "env") {
index += 1;
while (assignment.test(words[index] || "")) index += 1;
}
while (["command", "exec"].includes(executable(words[index] || ""))) {
index += 1;
}
return words.slice(index);
}

function isForgeMerge(segment) {
const words = unwrap(segment);
const forge = executable(words[0] || "");
if (forge !== "gh" && forge !== "glab") return false;
let index = 1;
while (index < words.length) {
const option = words[index];
if (["-R", "--repo", "--hostname"].includes(option)) {
if (index + 1 >= words.length) return false;
index += 2;
} else if (option === "--" || option.startsWith("--repo=") ||
option.startsWith("--hostname=") ||
(option.startsWith("-R") && option.length > 2)) {
index += 1;
if (option === "--") break;
} else {
break;
}
}
return forge === "gh"
? words[index] === "pr" && words[index + 1] === "merge"
: words[index] === "mr" && words[index + 1] === "merge";
}

const tokens = tokenize(source);
if (tokens === null) {
process.stdout.write("indeterminate");
} else {
let segment = [];
for (const token of [...tokens, { kind: "boundary" }]) {
if (token.kind === "word") {
segment.push(token.value);
} else {
if (isForgeMerge(segment)) {
process.stdout.write("merge");
process.exit(0);
}
segment = [];
}
}
}
')
classifier_status=$?
if [ "$classifier_status" -ne 0 ]; then
merge_command=indeterminate
fi

case "$merge_command" in
indeterminate)
echo "specgit: command blocked - the merge guard could not safely classify the shell input. Retry with a direct gh pr merge or glab mr merge command." >&2
exit 2
;;
merge)
exec node -e '
const { spawn } = require("child_process");
const fs = require("fs");
const path = require("path");
const ghMsRaw = parseInt(process.env.SPECGIT_GH_TIMEOUT_MS || "", 10);
const ghMs = Number.isFinite(ghMsRaw) && ghMsRaw > 0 ? ghMsRaw : 15000;
const ghS = Math.max(1, Math.floor(ghMs / 1000));
let budgetS = Math.max(60, ghS * 8);
const timeoutMs = ["SPECGIT_GH_TIMEOUT_MS", "SPECGIT_GLAB_TIMEOUT_MS"]
.map((name) => parseInt(process.env[name] || "", 10))
.map((value) => Number.isFinite(value) && value > 0 ? value : 15000);
const providerS = Math.max(1, Math.ceil(Math.max(...timeoutMs) / 1000));
let budgetS = Math.max(60, providerS * 8);
const overrideRaw = parseInt(process.env.SPECGIT_GUARD_BUDGET_S || "", 10);
if (Number.isFinite(overrideRaw) && overrideRaw > 0) {
budgetS = Math.max(overrideRaw, ghS);
budgetS = Math.max(overrideRaw, providerS);
}
// The hook runner kills long hooks; surface the mismatch instead of
// being cut off mid-verdict.
Expand Down Expand Up @@ -135,7 +277,7 @@ case "$command" in
);
} else {
lines.push(
"specgit: merge blocked - no verdict possible (evidence incomplete, exit " + code + "). This is not a rejection: fix evidence gathering (network, gh auth), then retry."
"specgit: merge blocked - no verdict possible (evidence incomplete, exit " + code + "). This is not a rejection: follow errors[].fix in the specgit finish --json result first. Run specgit doctor --json only for git, repository, origin, configured provider CLI/auth, or policy probes, then retry."
);
}
if (pending.length > 0) {
Expand Down Expand Up @@ -167,9 +309,33 @@ case "$command" in
});
'
;;
git\ push\ origin\ main*|git\ push\ origin\ +main*|git\ push\ origin\ HEAD:main*)
echo "specgit: direct push to main is not the delivery path. Deliveries go: specgit issue -> PR -> CI -> specgit finish (exit 0) -> merge." >&2
exit 2
esac
unset classifier_status merge_command

specgit_default_ref() {
specgit_ref=$(git symbolic-ref --quiet refs/remotes/origin/HEAD 2>/dev/null) || return 1
case "$specgit_ref" in
refs/remotes/origin/HEAD) return 1 ;;
refs/remotes/origin/?*) ;;
*) return 1 ;;
esac
git rev-parse --verify "$specgit_ref^{commit}" >/dev/null 2>&1 || return 1
printf '%s' "$specgit_ref"
}

case "$command" in
git\ push\ origin\ *)
default_ref=$(specgit_default_ref) || {
echo "specgit: cannot prove origin/HEAD. Run git fetch origin and git remote set-head origin -a before pushing." >&2
exit 2
}
default_branch=${default_ref#refs/remotes/origin/}
case "$command" in
git\ push\ origin\ "$default_branch"|git\ push\ origin\ "$default_branch"\ *|git\ push\ origin\ +"$default_branch"|git\ push\ origin\ +"$default_branch"\ *|git\ push\ origin\ HEAD:"$default_branch"|git\ push\ origin\ HEAD:"$default_branch"\ *)
echo "specgit: direct push to $default_branch is not the delivery path. Deliveries go: specgit issue -> PR/MR -> CI -> specgit finish (exit 0) -> merge." >&2
exit 2
;;
esac
;;
esac
exit 0
39 changes: 16 additions & 23 deletions .specgit.yaml
Original file line number Diff line number Diff line change
@@ -1,33 +1,26 @@
version: 1
delivery: dag-audit-reliability
delivery: goal-reliability
context:
kind: worktree
label: opencode-dag-delivery-2026-09-05
branch: fix/545-dag-audit-reliability
label: opencode-goal-fixes
branch: fix/568-goal-reliability
issues:
- 545
- 546
- 547
- 548
- 549
- 550
- 551
- 552
- 568
- 569
- 570
- 571
- 572
- 574
- 575
issueKinds:
- issue: 545
- issue: 568
kind: kind::fix
- issue: 546
- issue: 569
kind: kind::fix
- issue: 547
- issue: 570
kind: kind::fix
- issue: 548
- issue: 571
kind: kind::fix
- issue: 549
- issue: 572
kind: kind::fix
- issue: 550
kind: kind::fix
- issue: 551
kind: kind::fix
- issue: 552
kind: kind::chore
pr: 553
pr: 573
Loading
Loading