Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
3a3347e
chore(specgit): bind delivery to issue 409
LeXwDeX Aug 21, 2026
2712227
chore: record delivery binding for issue409
LeXwDeX Aug 21, 2026
0e53c65
fix(session): complete run-mode turns for early-return commands
LeXwDeX Aug 21, 2026
9f28df2
chore(specgit): re-init harness, align policy check IDs
LeXwDeX Aug 21, 2026
a157c9e
fix(specgit): checkout immutable head SHA to clear CodeQL cache-poiso…
LeXwDeX Aug 21, 2026
8ac482a
Revert "fix(specgit): checkout immutable head SHA to clear CodeQL cac…
LeXwDeX Aug 21, 2026
66b3708
chore(specgit): specialize accept workflow for this repo, drop dispat…
LeXwDeX Aug 21, 2026
732824a
docs(agents): add standard delivery workflow as mandatory loop
LeXwDeX Aug 21, 2026
c675b98
merge: integrate dev into issue409 delivery branch (keep binding)
LeXwDeX Aug 21, 2026
49ee9b9
Merge pull request #411 from LeXwDeX/feat/409-issue409
LeXwDeX Aug 21, 2026
a3aa221
Merge pull request #421 from LeXwDeX/chore/specgit-reinit
LeXwDeX Aug 21, 2026
5b8028b
fix(dag): compare review fingerprints on trimmed values
LeXwDeX Aug 21, 2026
a93514c
chore: record delivery binding for issue410
LeXwDeX Aug 21, 2026
49358ec
merge: integrate dev into issue410 delivery branch (keep binding)
LeXwDeX Aug 21, 2026
75c0414
Merge pull request #422 from LeXwDeX/feat/410-issue410
LeXwDeX Aug 21, 2026
0c90173
chore: record delivery binding for open-security-alerts
LeXwDeX Aug 24, 2026
72afcd6
fix(security): remediate open security alerts
LeXwDeX Aug 24, 2026
bea0baf
chore: align specgit-accept workflow with the main-line harness
LeXwDeX Aug 24, 2026
b6473cc
chore: align spec_git policy with the main-line harness
LeXwDeX Aug 24, 2026
a6d244b
merge: integrate main into fix/423-open-security-alerts (binding + ha…
LeXwDeX Aug 24, 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
3 changes: 3 additions & 0 deletions .github/workflows/ci-typecheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ on:
- dev
workflow_dispatch:

permissions:
contents: read

jobs:
typecheck:
name: Typecheck
Expand Down
156 changes: 148 additions & 8 deletions .opencode/hooks/specgit-merge-guard.sh
Original file line number Diff line number Diff line change
@@ -1,16 +1,156 @@
#!/bin/sh
# SpecGit merge guard (managed by specgit init). Exit 2 = block with reason.
command=$(printf '%s' "$1" | 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('')}})")
GUARD_DIR=$(cd "$(dirname "$0")" && pwd)
export GUARD_DIR
# Hook payloads arrive as the first argument or on stdin; accept both.
if [ -n "$1" ]; then
payload=$1
else
payload=$(cat)
fi
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*)
# Real-time verdict: re-evaluate the delivery before letting a merge
# through. Verdicts are never persisted, so compute one now.
if specgit finish >/dev/null 2>&1; then
exit 0
fi
echo "specgit: merge blocked - 'specgit finish' does not exit 0 right now. Fix what the failures name; never weaken spec_git/policy.yaml to pass." >&2
exit 2
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 overrideRaw = parseInt(process.env.SPECGIT_GUARD_BUDGET_S || "", 10);
if (Number.isFinite(overrideRaw) && overrideRaw > 0) {
budgetS = Math.max(overrideRaw, ghS);
}
// The hook runner kills long hooks; surface the mismatch instead of
// being cut off mid-verdict.
try {
const hooks = JSON.parse(
fs.readFileSync(path.join(process.env.GUARD_DIR || ".", "..", "hooks.json"), "utf8")
);
const runner = (hooks.PreToolUse || [])
.flatMap((entry) => entry.hooks || [])
.map((hook) => hook.timeout)
.find((timeout) => typeof timeout === "number");
if (runner !== undefined && runner - 10 < budgetS) {
console.error(
"specgit: guard budget " + budgetS + "s exceeds the hook runner timeout " +
runner + "s in .opencode/hooks.json - raise the runner timeout or lower SPECGIT_GUARD_BUDGET_S."
);
}
} catch {}
const cp = require("child_process");
const isWin = process.platform === "win32";
// Windows: cmd.exe cannot exec an extensionless sh shim, so prefer
// git-bash sh when present; only then fall back to shell mode.
let child;
if (isWin) {
const probe = cp.spawnSync("sh", ["-c", "exit 0"]);
if (probe.status === 0) {
child = spawn("sh", ["-c", "specgit finish --json"], {
stdio: ["ignore", "pipe", "pipe"],
});
}
}
if (!child) {
child = spawn("specgit", ["finish", "--json"], {
shell: isWin,
stdio: ["ignore", "pipe", "pipe"],
});
}
let out = "";
let err = "";
let expired = false;
child.stdout.on("data", (chunk) => (out += chunk));
child.stderr.on("data", (chunk) => (err += chunk));
const timer = setTimeout(() => {
expired = true;
// Bound the wait strictly: descendants may inherit the pipes, so
// destroy them and exit now — never lag behind orphaned children.
child.stdout.destroy();
child.stderr.destroy();
child.kill("SIGKILL");
console.error(
"specgit: merge blocked - guard budget " + budgetS + "s exhausted before a verdict. This says nothing about the delivery; run specgit finish directly for the full verdict."
);
process.exit(2);
}, budgetS * 1000);
child.on("error", (error) => {
clearTimeout(timer);
console.error(
"specgit: merge blocked - the verdict could not run (" + error.message + "). Install specgit on PATH, then retry the merge."
);
process.exit(2);
});
child.on("close", (code) => {
clearTimeout(timer);
if (expired) {
process.exit(2);
}
if (code === 0) {
process.exit(0);
}
let envelope = null;
try {
envelope = JSON.parse(out);
} catch {}
const verdict = envelope && envelope.verdict;
const gates = (envelope && (envelope.gates || (verdict && verdict.gates))) || [];
const failures = [];
for (const gate of gates) {
for (const failure of (gate && gate.failures) || []) failures.push(failure);
}
const label = (failure, suffix) => {
const detail = failure.detail || {};
const name = detail.name || failure.code;
const state = suffix || detail.status || detail.conclusion || "";
return name + (state ? " [" + state + "]" : "");
};
const pending = failures.filter((f) => f.code === "checks_pending");
const failed = failures.filter((f) => f.code === "checks_failed");
const other = failures.filter(
(f) => f.code !== "checks_pending" && f.code !== "checks_failed"
);
const lines = [];
if (code === 1) {
lines.push(
"specgit: merge blocked - verdict rejected (exit 1). Fix what the failures name; never weaken spec_git/policy.yaml to pass."
);
} 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."
);
}
if (pending.length > 0) {
lines.push(
" pending (transient - wait, then re-run): " + pending.map((f) => label(f)).join(", ")
);
}
if (failed.length > 0) {
lines.push(
" failed (repair required): " +
failed
.map((f) =>
label(
f,
f.detail && f.detail.conclusion === "action_required"
? "action_required - run awaits maintainer approval"
: undefined
)
)
.join(", ")
);
}
if (other.length > 0) {
lines.push(" other failures: " + other.map((f) => label(f)).join(", "));
}
lines.push("Full verdict: specgit finish");
console.error(lines.join("\n"));
process.exit(2);
});
'
;;
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
Expand Down
8 changes: 4 additions & 4 deletions .specgit.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
version: 1
delivery: worker-config-on
delivery: open-security-alerts
context:
kind: branch
branch: fix/425-worker-config-on
branch: fix/423-open-security-alerts
issues:
- 425
pr: 426
- 423
pr: 424
Loading
Loading