Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
8b2ef01
fix(opencode): auto-update notifies only; latest() tracks fork releas…
LeXwDeX Aug 19, 2026
cb17d3c
Merge pull request #352 from LeXwDeX/feat/351-fix-opencode-auto
LeXwDeX Aug 19, 2026
271a6d7
chore(specgit): enable SpecGit delivery harness
LeXwDeX Aug 19, 2026
bb93543
chore(specgit): acceptance workflow triggers on PRs to dev
LeXwDeX Aug 19, 2026
b2cd8df
chore(specgit): acceptance workflow installs npm specgit, drops pnpm
LeXwDeX Aug 19, 2026
e4b60ca
chore(specgit): record PR binding 354 in delivery record
LeXwDeX Aug 19, 2026
dfb5f4c
Merge pull request #354 from LeXwDeX/feat/353-chore-specgit-enable
LeXwDeX Aug 19, 2026
e3877c0
docs(audit): add 2026-08-19 deep-dive audit report
LeXwDeX Aug 19, 2026
43fe32c
chore(specgit): record PR binding 356 in delivery record
LeXwDeX Aug 19, 2026
f25f37d
Merge pull request #356 from LeXwDeX/feat/355-docs-audit-add
LeXwDeX Aug 19, 2026
9cd6810
fix(goal): wire GoalLoop.node into the server request-context app graph
LeXwDeX Aug 19, 2026
a1e2a8b
chore(specgit): record PR binding in delivery record
LeXwDeX Aug 19, 2026
2881f28
Merge pull request #357 from LeXwDeX/feat/340-issue340
LeXwDeX Aug 19, 2026
86fae34
fix(dag): build DagSupervisionSweep in the server app graph
LeXwDeX Aug 19, 2026
2438e04
chore(specgit): record PR binding in delivery record
LeXwDeX Aug 19, 2026
224eea2
Merge pull request #358 from LeXwDeX/feat/341-issue341
LeXwDeX Aug 19, 2026
a425e14
fix(dag): sweep freeze window covers the live watcher's actual cadence
LeXwDeX Aug 19, 2026
ca43330
chore(specgit): record delivery binding for issue 342
LeXwDeX Aug 19, 2026
4bb142e
chore(specgit): record PR binding in delivery record
LeXwDeX Aug 19, 2026
2d7b312
Merge pull request #359 from LeXwDeX/feat/342-issue342
LeXwDeX Aug 19, 2026
175e700
fix(dag): sweep terminalizes workflows and releases their lease after…
LeXwDeX Aug 19, 2026
44b3139
chore(specgit): record PR binding in delivery record
LeXwDeX Aug 19, 2026
3ee0254
Merge pull request #360 from LeXwDeX/feat/343-issue343
LeXwDeX Aug 19, 2026
d305707
fix(dag): httpapi dag.start passes Workflow Authoring
LeXwDeX Aug 19, 2026
38d4186
chore(specgit): record PR binding in delivery record
LeXwDeX Aug 19, 2026
668a2f1
chore(tool): drop imports unused after the catalog-loader extraction
LeXwDeX Aug 19, 2026
698713c
Merge pull request #361 from LeXwDeX/feat/344-issue344
LeXwDeX Aug 19, 2026
80aeafb
fix(dag): crash recovery preserves a schemaless node's string verdict
LeXwDeX Aug 19, 2026
5eb1b90
chore(specgit): record PR binding in delivery record
LeXwDeX Aug 19, 2026
4fdddf7
Merge pull request #362 from LeXwDeX/feat/345-issue345
LeXwDeX Aug 19, 2026
429ac78
fix(dag): validateAgainstSchema enforces object-semantic keywords
LeXwDeX Aug 19, 2026
d5290bf
chore(specgit): record PR binding in delivery record
LeXwDeX Aug 19, 2026
961cb41
Merge pull request #363 from LeXwDeX/feat/346-issue346
LeXwDeX Aug 19, 2026
36f4a05
fix(dag): aggregator contract reconciles declared write-sets against …
LeXwDeX Aug 19, 2026
bee2cb6
chore(specgit): record PR binding in delivery record
LeXwDeX Aug 19, 2026
4289538
Merge pull request #364 from LeXwDeX/feat/347-issue347
LeXwDeX Aug 19, 2026
85fb68e
fix(memory): /memory on and memory_search say why Memory is inert
LeXwDeX Aug 19, 2026
de66213
chore(specgit): record PR binding in delivery record
LeXwDeX Aug 19, 2026
ab29932
Merge pull request #365 from LeXwDeX/feat/350-issue350
LeXwDeX Aug 19, 2026
747d8c0
chore(specgit): pin the acceptance workflow's specgit CLI to ^0.5.0
LeXwDeX Aug 19, 2026
16918ec
chore(specgit): record PR binding in delivery record
LeXwDeX Aug 19, 2026
1b14857
Merge pull request #367 from LeXwDeX/feat/366-issue366
LeXwDeX Aug 19, 2026
732d30d
fix(license): add the SPDX headers to environment-catalogs.ts
LeXwDeX Aug 19, 2026
495eea6
chore(specgit): record PR binding in delivery record
LeXwDeX Aug 19, 2026
195be09
Merge pull request #369 from LeXwDeX/feat/368-issue368
LeXwDeX Aug 19, 2026
e2a031f
chore(ci): raise the dev PR gate to Typecheck + Unit Tests (linux)
LeXwDeX Aug 19, 2026
44975d7
chore(specgit): record PR binding in delivery record
LeXwDeX Aug 19, 2026
2f15445
fix(dag): dag.start treats model.unavailable as advisory, matching th…
LeXwDeX Aug 19, 2026
ed39030
fix(dag): dag.start keeps model.unavailable advisory; exerciser seeds…
LeXwDeX Aug 19, 2026
3731028
fix(test): session model field is {id, providerID}, matching Session'…
LeXwDeX Aug 19, 2026
6a3f3b2
chore(specgit): acceptance timeout 45min — must outlast Unit Tests (~…
LeXwDeX Aug 19, 2026
8d5bb3d
chore(specgit): wait-step deadline 40min — the 15min inline deadline …
LeXwDeX Aug 19, 2026
c0cf65e
fix(dag): low-severity batch from the 2026-08-19 deep-dive audit
LeXwDeX Aug 19, 2026
f128668
chore(specgit): record PR binding in delivery record
LeXwDeX Aug 19, 2026
d666e3a
docs(dag): 'one objective, one live DAG' downgraded from Invariant to…
LeXwDeX Aug 19, 2026
603d349
chore(specgit): record PR binding in delivery record
LeXwDeX Aug 19, 2026
f9f6e55
Merge pull request #371 from LeXwDeX/feat/370-issue370
LeXwDeX Aug 19, 2026
b94d9ee
test(dag): REC-1 pins the hardened behavior — cancel failure continue…
LeXwDeX Aug 19, 2026
7d2f605
Merge branch 'dev' into feat/349-issue349 (resolve delivery-record co…
LeXwDeX Aug 19, 2026
0370974
Merge pull request #372 from LeXwDeX/feat/349-issue349
LeXwDeX Aug 19, 2026
bf4a350
Merge branch 'dev' into feat/348-issue348 (resolve delivery-record co…
LeXwDeX Aug 19, 2026
61b141a
Merge pull request #373 from LeXwDeX/feat/348-issue348
LeXwDeX Aug 19, 2026
ddfa2e1
chore: post-review cleanup — docs drift, CONTEXT.md section order, re…
LeXwDeX Aug 19, 2026
ecdc300
chore(specgit): record PR binding in delivery record
LeXwDeX Aug 19, 2026
319637e
chore(specgit): bind delivery to issue 374 (was mistakenly bound to t…
LeXwDeX Aug 19, 2026
583b1d1
chore(specgit): correct delivery record context (branch + PR binding)
LeXwDeX Aug 19, 2026
638ae7e
Merge pull request #375 from LeXwDeX/feat/375-issue375
LeXwDeX Aug 19, 2026
3ce5d8b
chore(specgit): acceptance also runs on PRs to main
LeXwDeX Aug 19, 2026
a6937b8
Merge pull request #377 from LeXwDeX/fix/specgit-accept-main
LeXwDeX Aug 19, 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
9 changes: 6 additions & 3 deletions .github/workflows/ci-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@
# 🧪 CI · Test
# ----------------------------------------------------------------------------
# Purpose : Run unit + Playwright e2e tests across Linux & Windows
# Trigger : Push to `main`/`dev`, PRs targeting `main`, manual dispatch
# Trigger : Push to `main`/`dev`, PRs targeting `main` and `dev`, manual dispatch
# Jobs : unit — `bun turbo test` + config_assistant Go tests on linux
# only (windows dropped — see
# unit-tests matrix comment; free windows-latest runners
# can't fit the suite in a reasonable CI budget)
# e2e — Playwright chromium on linux + windows (matrix)
# Gate : Required status check on the `main` ruleset — full suite gates
# dev → main PRs. Pushes to `dev` also get a full run (dev is the
# integration/testing branch), but feat/fix → dev PRs are gated by
# typecheck only (see ci-typecheck.yml) to keep CI budget sane.
# integration/testing branch). feat/fix → dev PRs run the unit
# matrix as a required check (#370: the Typecheck-only gate let an
# assertion-level regression merge and keep dev red for 75min);
# E2E stays push-on-dev + dev→main only to keep CI budget sane.
# Notes : `cancel-in-progress: false` — every main/dev push gets a full run
# No trigger on feat/* or fix/* (frequent changes).
# ============================================================================
Expand All @@ -26,6 +28,7 @@ on:
pull_request:
branches:
- main
- dev
workflow_dispatch:

concurrency:
Expand Down
100 changes: 100 additions & 0 deletions .github/workflows/specgit-accept.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
name: SpecGit Acceptance

on:
pull_request:
# Delivery PRs target dev (fast-integration layer) and are promoted to
# main via the release PR — main's legacy branch protection also requires
# the SpecGit Acceptance check, so the verdict must run on both targets.
# dev→main promotion stays governed by the protect-main Ruleset's four
# required checks.
branches: [dev, main]

permissions:
contents: read

jobs:
specgit-acceptance:
name: SpecGit Acceptance
runs-on: ubuntu-latest
# Must exceed the slowest required sibling (Unit Tests (linux) runs
# ~28min on PRs): the verdict waits for every policy check to reach a
# terminal state before evaluating.
timeout-minutes: 45
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Check out the PR head branch by name so HEAD is on the branch
# (not the detached merge ref): the execution context gate reads
# live git. Falls back to the default ref on non-PR events.
ref: ${{ github.head_ref || github.ref }}
fetch-depth: 0
persist-credentials: false

- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'

# This repo is a bun workspace and does not vendor the SpecGit CLI;
# install the published CLI instead of building from source. Pinned
# with a caret floor (#366): the CLI releases multiple times a day and
# an unpinned install would let an unnoticed upstream change flip CI
# acceptance verdicts repo-wide.
- name: Install specgit CLI
run: npm install -g specgit@^0.5.0

- name: Wait for sibling checks
# The verdict must see the OTHER required checks in a terminal
# state. Sibling jobs start in parallel AND may not have registered
# their check-runs yet, so an empty poll is not "done": wait until
# every name in spec_git/policy.yaml is present with a terminal
# conclusion. This job is not in the policy, so no self-deadlock.
env:
GH_TOKEN: ${{ github.token }}
WAIT_REPO: ${{ github.repository }}
WAIT_SHA: ${{ github.event.pull_request.head.sha }}
run: |
node --input-type=module <<'EOF'
import { readFileSync } from 'node:fs';
// Minimal parse of policy.yaml's required_checks block list —
// avoids a yaml dependency in this bun-based repo.
const policy = readFileSync('spec_git/policy.yaml', 'utf8');
const section = policy.slice(policy.indexOf('required_checks:'));
const required = [...section.matchAll(/^\s*-\s*(.+)$/gm)].map((m) => m[1].trim());
const headers = {
authorization: 'Bearer ' + process.env.GH_TOKEN,
accept: 'application/vnd.github+json',
};
const url = 'https://api.github.com/repos/' + process.env.WAIT_REPO
+ '/commits/' + process.env.WAIT_SHA + '/check-runs?per_page=100';
const terminal = new Set(['completed']);
const terminalHas = (byName, name) => {
if (byName.has(name)) return terminal.has(byName.get(name));
const retried = [...byName.keys()].find((k) => k.startsWith(name + ' ('));
return retried !== undefined && terminal.has(byName.get(retried));
};
// Must outlast the slowest required sibling (Unit Tests (linux)
// runs ~28min on PRs); the job timeout above bounds this too.
const deadline = Date.now() + 40 * 60 * 1000;
while (Date.now() < deadline) {
const res = await fetch(url, { headers });
if (!res.ok) throw new Error('check-runs API ' + res.status);
const payload = await res.json();
const byName = new Map(payload.check_runs.map((r) => [r.name, r.status]));
const missing = required.filter((n) => !terminalHas(byName, n));
if (missing.length === 0) {
console.log('All required checks are in a terminal state.');
process.exit(0);
}
console.log('Waiting for: ' + missing.join(', '));
await new Promise((r) => setTimeout(r, 10000));
}
console.error('Timed out waiting for sibling checks.');
process.exit(1);
EOF

- name: specgit finish
run: specgit finish --json
env:
GH_TOKEN: ${{ github.token }}
27 changes: 27 additions & 0 deletions .opencode/command/specgit-finish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
description: Run the SpecGit evidence verdict and drive the fix loop to exit 0
---

# /specgit-finish

Thin trigger for the acceptance verdict. The canonical behavior lives in the
AGENTS.md SpecGit block; this command only launches it.

## Steps

1. Run from the delivery branch:

```bash
specgit finish --json
```

2. Branch on the exit code:
- `exit 0` → produce the merge brief (issues + PR + CI run links + the
verdict) and ask the user to approve the merge. Do not merge yourself
without approval.
- `exit 1` → read `errors[].fix` / gate failures, fix exactly what they
name, re-run. Loop until exit 0.
- `exit 3` → report the environment problem (gh auth / network); never
edit the record or the policy to work around it.
3. Iron rules: never weaken `spec_git/policy.yaml` to pass; `--json` is the
only parse surface; a non-zero verdict never merges.
22 changes: 22 additions & 0 deletions .opencode/command/specgit-issue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
description: Start a SpecGit delivery from a title or existing issue number
---

# /specgit-issue

Thin trigger for the delivery bootstrap. The canonical behavior lives in the
AGENTS.md SpecGit block; this command only launches it.

## Steps

1. Collect the argument: `$ARGUMENTS` is either an issue title (create) or a
pure number (reuse). Multiple arguments = N issues in one delivery.
2. Run from the repo root:

```bash
specgit issue "$ARGUMENTS" --json
```

3. On success report the brief: issue URL(s), PR URL (draft), branch name.
4. Switch to the delivery branch and begin the TDD loop.
5. On error, read `errors[].fix` and follow it — never bypass the record.
20 changes: 20 additions & 0 deletions .opencode/hooks/specgit-merge-guard.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/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('')}})")

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
;;
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
exit 0
8 changes: 8 additions & 0 deletions .specgit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
version: 1
delivery: issue374
context:
kind: branch
branch: feat/375-issue375
issues:
- 374
pr: 375
56 changes: 52 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@
## Git Workflow (铁律)

```
feat/**, fix/** ──PR(Typecheck 门禁)──▶ dev ──push 触发全量测试──▶
feat/**, fix/** ──PR(Typecheck + Unit Tests 门禁)──▶ dev ──push 触发全量测试──▶
dev ──手动 release-fork──▶ prerelease 测试版
dev ──PR(全量测试门禁)──▶ main ──手动 release-fork──▶ 正式版
```

**分层门禁**:`dev` 是快速集成层(Typecheck),`main` 是正式质量门禁(Typecheck + 全量 Unit Tests + E2E)。所有改动通过 PR 流转,禁止直推 `main` 和 `dev`(由 GitHub Rulesets 强制)。
**分层门禁**:`dev` 是快速集成层(Typecheck + Unit Tests (linux);E2E 不阻塞),`main` 是正式质量门禁(Typecheck + 全量 Unit Tests + E2E)。所有改动通过 PR 流转,禁止直推 `main` 和 `dev`(由 GitHub Rulesets 强制)。

| Branch | 直推 | PR 门禁 | CI 触发 | Purpose |
|--------|------|---------|---------|---------|
| `{type}/**` | ✅ 允许 | — | ❌ 不跑 | 开发分支,频繁变更 |
| `dev` | ❌ 禁止 | PR 必须通过 **Typecheck** | ✅ push 触发 Typecheck + 全量测试 | 快速集成层 |
| `dev` | ❌ 禁止 | PR 必须通过 **Typecheck + Unit Tests (linux)** | ✅ push 触发 Typecheck + 全量测试 | 快速集成层 |
| `main` | ❌ 禁止 | PR 必须通过 **Typecheck + Unit Tests + E2E (linux + windows)** | ✅ push 触发全量 | 正式质量门禁 + 发版 |

**流程**:
1. 从 `main` 切出 `feat/**` 或 `fix/**` 分支开发
2. PR → `dev`(Typecheck 门禁,快速合并)
2. PR → `dev`(Typecheck + Unit Tests (linux) 门禁,快速合并)
3. push 到 `dev` 自动触发全量测试验证
4. 从 `dev` 手动 `release-fork` → 产出 **prerelease** 测试版
5. PR `dev` → `main`(全量测试门禁:Typecheck + Unit Tests + E2E)
Expand Down Expand Up @@ -240,3 +240,51 @@ Triage uses the five canonical labels `needs-triage`, `needs-info`, `ready-for-a
### Domain docs

This repository uses a multi-context domain-document layout rooted at `CONTEXT-MAP.md`. See `docs/agents/domain.md`.

<!-- specgit:block:start -->
## SpecGit delivery harness

Managed by `specgit init`. Everything between the markers is rewritten on
re-init; keep manual guidance outside them.

### The delivery story

- Start with `specgit issue <title-or-number>...`: it creates or reuses
the issues, branches, opens the draft pull request that closes every
bound issue, and writes `.specgit.yaml`. Re-running resumes; it is
idempotent.
- Finish with `specgit finish`: the verdict, derived from real git, PR,
and CI evidence. Exit code 0 is the only "done".

### Repair and diagnostics

- `specgit pr` repairs the pull-request binding: with no arguments it
auto-discovers the pull request for this head branch, errors with a fix
when none is found, and refuses with a list when several match.
- `specgit status` shows local evidence only: record, state, drift,
origin. `specgit doctor` probes git, repository, origin, gh, and
policy.

### Issue granularity

One issue = one independently verifiable WHY. If a deliverable cannot be
verified on its own evidence, split it before binding.

### Iron rules

- `specgit finish` exit code other than 0: never request merge. Fix the
delivery, not the gate.
- Never weaken `spec_git/policy.yaml` to make a verdict pass.
- `--json` is the only parse surface: stdout is exactly one JSON
document; never scrape human-readable output.
<!-- specgit:block:end -->

## Tool-call discipline (hard rules)

- Never fan out duplicate or near-duplicate queries. One question, one
tool call; if the answer is already in context, make zero calls.
- Parallel tool batches must contain distinct, independently justified
calls. Before sending a batch, verify no two calls answer the same
question. A repeated identical call is a bug regardless of intent.
- Long CI waits use `sleep N && <single check>`, never repeated watches
of the same resource. One watch command, one result.
38 changes: 38 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,41 @@ pushes to `main`/`dev` are blocked by GitHub Rulesets. Branch names: `{type}/{sh
(`feat`, `fix`, `chore`, `docs`, `refactor`, `test`, `release`, `hotfix`), enforced by
Ruleset. Commits/PR titles: conventional `type(scope): summary`. All PRs must reference an
existing issue (`Fixes #N`). Curated DAG configs are owned by the `opencode-dag-config` repo.

<!-- specgit:block:start -->
## SpecGit delivery harness

Managed by `specgit init`. Everything between the markers is rewritten on
re-init; keep manual guidance outside them.

### The delivery story

- Start with `specgit issue <title-or-number>...`: it creates or reuses
the issues, branches, opens the draft pull request that closes every
bound issue, and writes `.specgit.yaml`. Re-running resumes; it is
idempotent.
- Finish with `specgit finish`: the verdict, derived from real git, PR,
and CI evidence. Exit code 0 is the only "done".

### Repair and diagnostics

- `specgit pr` repairs the pull-request binding: with no arguments it
auto-discovers the pull request for this head branch, errors with a fix
when none is found, and refuses with a list when several match.
- `specgit status` shows local evidence only: record, state, drift,
origin. `specgit doctor` probes git, repository, origin, gh, and
policy.

### Issue granularity

One issue = one independently verifiable WHY. If a deliverable cannot be
verified on its own evidence, split it before binding.

### Iron rules

- `specgit finish` exit code other than 0: never request merge. Fix the
delivery, not the gate.
- Never weaken `spec_git/policy.yaml` to make a verdict pass.
- `--json` is the only parse surface: stdout is exactly one JSON
document; never scrape human-readable output.
<!-- specgit:block:end -->
Loading
Loading