Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ jobs:
- run: npm test
- run: npm run build

format:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
registry-url: https://registry.npmjs.org
- run: npm ci
- run: npm run format:check

pr-validation:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
Expand Down
208 changes: 104 additions & 104 deletions .github/workflows/pr-artifact.yml
Original file line number Diff line number Diff line change
@@ -1,123 +1,123 @@
name: PR Build Artifact

on:
pull_request:
branches: [master]
pull_request:
branches: [master]

permissions:
contents: read
pull-requests: write
issues: write
contents: read
pull-requests: write
issues: write

jobs:
build-artifact:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
build-artifact:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
registry-url: https://registry.npmjs.org
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
registry-url: https://registry.npmjs.org

- run: npm ci
- run: npm ci

- run: npm run build
- run: npm run build

- name: Publish to npm with PR tag
if: github.event.pull_request.head.repo.full_name == github.repository
run: |
PR_NUMBER="${{ github.event.pull_request.number }}"
RUN_NUMBER="${{ github.run_number }}"
BASE_VERSION=$(node -p "require('./package.json').version")
# Strip any existing prerelease suffix
BASE_VERSION=$(echo "$BASE_VERSION" | sed 's/-.*//')
PR_VERSION="${BASE_VERSION}-pr.${PR_NUMBER}.${RUN_NUMBER}"
- name: Publish to npm with PR tag
if: github.event.pull_request.head.repo.full_name == github.repository
run: |
PR_NUMBER="${{ github.event.pull_request.number }}"
RUN_NUMBER="${{ github.run_number }}"
BASE_VERSION=$(node -p "require('./package.json').version")
# Strip any existing prerelease suffix
BASE_VERSION=$(echo "$BASE_VERSION" | sed 's/-.*//')
PR_VERSION="${BASE_VERSION}-pr.${PR_NUMBER}.${RUN_NUMBER}"

echo "Publishing version $PR_VERSION with tag pr-${PR_NUMBER}"
npm version "$PR_VERSION" --no-git-tag-version
npm publish --tag "pr-${PR_NUMBER}" --access public --ignore-scripts
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
echo "Publishing version $PR_VERSION with tag pr-${PR_NUMBER}"
npm version "$PR_VERSION" --no-git-tag-version
npm publish --tag "pr-${PR_NUMBER}" --access public --ignore-scripts
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

- name: Create installable tarball
run: |
npm pack
PR_NUM="${{ github.event.pull_request.number }}"
TARBALL=$(ls acp-kernel-*.tgz | head -1)
if [ -n "$PR_NUM" ] && [ -n "$TARBALL" ]; then
cp "$TARBALL" "acp-kernel-pr${PR_NUM}.tgz"
fi
- name: Create installable tarball
run: |
npm pack
PR_NUM="${{ github.event.pull_request.number }}"
TARBALL=$(ls acp-kernel-*.tgz | head -1)
if [ -n "$PR_NUM" ] && [ -n "$TARBALL" ]; then
cp "$TARBALL" "acp-kernel-pr${PR_NUM}.tgz"
fi

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: acp-kernel-pr-${{ github.event.pull_request.number }}
path: |
acp-kernel-pr*.tgz
dist/
retention-days: 30
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: acp-kernel-pr-${{ github.event.pull_request.number }}
path: |
acp-kernel-pr*.tgz
dist/
retention-days: 30

- name: Comment on PR with install instructions
continue-on-error: true
uses: actions/github-script@v7
with:
script: |
const prNumber = context.payload.pull_request.number;
const sha = context.payload.pull_request.head.sha.slice(0, 7);
const branch = context.payload.pull_request.head.ref;
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
- name: Comment on PR with install instructions
continue-on-error: true
uses: actions/github-script@v7
with:
script: |
const prNumber = context.payload.pull_request.number;
const sha = context.payload.pull_request.head.sha.slice(0, 7);
const branch = context.payload.pull_request.head.ref;
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;

const body = [
'## 📦 Built Package Artifact',
'',
`**Branch:** \`${branch}\` (${sha})`,
'',
'### Option A — Install from npm PR tag (recommended)',
'',
'In your adapter project:',
'',
'```bash',
'npm install acp-kernel@pr-' + prNumber,
'```',
'',
'Each push to this PR publishes a new version under the `pr-' + prNumber + '` npm tag.',
'',
'### Option B — Download artifact',
'',
`1. Download the artifact from the [Actions run](${runUrl})`,
'2. Extract the tarball and install:',
'',
'```bash',
'tar xzf acp-kernel-pr' + prNumber + '.tgz',
'npm install ./package',
'```',
'',
'---',
'*This comment is automatically updated on each push.*',
].join('\n');
const body = [
'## 📦 Built Package Artifact',
'',
`**Branch:** \`${branch}\` (${sha})`,
'',
'### Option A — Install from npm PR tag (recommended)',
'',
'In your adapter project:',
'',
'```bash',
'npm install acp-kernel@pr-' + prNumber,
'```',
'',
'Each push to this PR publishes a new version under the `pr-' + prNumber + '` npm tag.',
'',
'### Option B — Download artifact',
'',
`1. Download the artifact from the [Actions run](${runUrl})`,
'2. Extract the tarball and install:',
'',
'```bash',
'tar xzf acp-kernel-pr' + prNumber + '.tgz',
'npm install ./package',
'```',
'',
'---',
'*This comment is automatically updated on each push.*',
].join('\n');

// Find and update existing comment, or create new one
const comments = await github.rest.issues.listComments({
...context.repo,
issue_number: prNumber,
});
// Find and update existing comment, or create new one
const comments = await github.rest.issues.listComments({
...context.repo,
issue_number: prNumber,
});

const existing = comments.data.find(c =>
c.user.type === 'Bot' && c.body.includes('📦 Built Package Artifact')
);
const existing = comments.data.find(c =>
c.user.type === 'Bot' && c.body.includes('📦 Built Package Artifact')
);

if (existing) {
await github.rest.issues.updateComment({
...context.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
...context.repo,
issue_number: prNumber,
body,
});
}
if (existing) {
await github.rest.issues.updateComment({
...context.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
...context.repo,
issue_number: prNumber,
body,
});
}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
node_modules/
dist/
tmp/
.tmp/
*.log
.DS_Store
coverage/
Expand Down
12 changes: 12 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Build output (prettier would reformat bundled/minified artifacts)
dist/

# npm-generated lockfile: npm rewrites it with its own formatting on
# dependency changes, so formatting it here causes permanent churn.
package-lock.json

# Local scratch directory (never present in CI)
.tmp/

# Ignored by prettier by default; listed for clarity
node_modules/
11 changes: 11 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": false,
"trailingComma": "all",
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf"
}
46 changes: 27 additions & 19 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,21 @@ Consumed by adapters: `pai-acp` (Pi), and future adapters for other agent platfo

### Tech Stack

| Category | Technology |
|----------|-----------|
| Language | TypeScript (strict, ESM) |
| Build | tsup (bundling) + tsc --emitDeclarationOnly |
| Test | Node.js built-in: `node --import tsx --test tests/*.test.ts` |
| Package Manager | npm |
| Zero Runtime Dependencies | `dependencies: {}` in package.json |
| Category | Technology |
| ------------------------- | ------------------------------------------------------------ |
| Language | TypeScript (strict, ESM) |
| Build | tsup (bundling) + tsc --emitDeclarationOnly |
| Test | Node.js built-in: `node --import tsx --test tests/*.test.ts` |
| Package Manager | npm |
| Zero Runtime Dependencies | `dependencies: {}` in package.json |

### Repository Info

| Field | Value |
|-------|-------|
| npm package | `acp-kernel` |
| GitHub | https://github.com/ranxianglei/acp-kernel |
| License | MIT |
| Field | Value |
| ----------- | ----------------------------------------- |
| npm package | `acp-kernel` |
| GitHub | https://github.com/ranxianglei/acp-kernel |
| License | MIT |

## 2. Architecture

Expand Down Expand Up @@ -121,12 +121,12 @@ npm run format:check # Check formatting

## 4. Git Safety Rules (MANDATORY)

| Rule | Enforcement |
|------|-------------|
| **NEVER force-push to `master`** | Under no circumstances |
| **NEVER merge PRs** | PR merges are human-only. The Agent MUST NEVER merge. |
| **Branch naming** | `YYYY-MM-DD_short-title` |
| **NEVER modify `version` on non-release branches** | Version bumps only on release branches |
| Rule | Enforcement |
| -------------------------------------------------- | ----------------------------------------------------- |
| **NEVER force-push to `master`** | Under no circumstances |
| **NEVER merge PRs** | PR merges are human-only. The Agent MUST NEVER merge. |
| **Branch naming** | `YYYY-MM-DD_short-title` |
| **NEVER modify `version` on non-release branches** | Version bumps only on release branches |

### PR Merge — Absolute Prohibition

Expand Down Expand Up @@ -206,18 +206,22 @@ All source changes require review by **at least 2 separate agents** before merge
> Distilled from a full-history audit of this repo + siblings ([billion-context#801](https://github.com/ranxianglei/billion-context/issues/801)). Full cited material: [AUTO-MERGE-GUARDRAILS.md](./AUTO-MERGE-GUARDRAILS.md). This is the CORE library: it OWNS the compression-block model and the wire-artifact format every adapter depends on, so its auto-merge surface is the narrowest of the three repos. Complements §6 Code Review (≥2 agents) by defining when a bugfix may skip the human round-trip.

### 7.1 Before you start

- **Duplicate screening first** (open AND closed). Link existing work, don't start parallel work.
- **One issue = one scope.** Never bundle unrelated changes or mass whitespace/reformatting.
- **Open a PR, never just push a branch.**

### 7.2 Review discipline

- **Rebase to CURRENT master** before claiming mergeable; after rebase re-run typecheck + test + build + format:check.
- **Pipeline nodes are shared hotspots** — if another open PR rewrites the same node/module, resolve by union of intent, then prove with tests.
- **Done = evidence**; tests stay pure (no I/O / network / mocks of kernel internals).
- **Deterministic tests** — no env/port luck.

### 7.3 Kernel-owned contracts (first-class; ALWAYS human-gated)

The invariants every adapter relies on. Changing ANY of these is cross-repo + prefix-cache-affecting by construction and MUST stay human (and follow cross-repo sequencing — kernel ships first):

1. **Message-id / ref immutability** (§2 principle 6) — no slot reclamation/recycling; widen `REF_WIDTH`. A re-issued number silently misattributes on decompress.
2. **Wire-artifact format** — the ACP compression tag wrapping `mNNNNN`, the `acp_summary` structure, block refs, and rendered-tag token counts being SNAPSHOTS frozen at first render.
3. **Single-owner content** (§2 principle 3) — `assignRefsNode` is the sole writer of message content.
Expand All @@ -230,7 +234,9 @@ The invariants every adapter relies on. Changing ANY of these is cross-repo + pr
Plus structural constraints that stay human: zero runtime deps (§2 principle 1 — no new dep without sign-off), platform-agnostic (§2 principle 5 — no host API / file I/O / network creep), and the state schema (explicit in/out; changing its shape is cross-repo).

### 7.4 Auto-merge gate (narrowest of the three repos)

A bugfix may **auto-merge** only if ALL hold:

1. Scoped to ONE pipeline node or standalone module; no change to node ordering or pipeline shape.
2. A pure regression test reproduces the bug and now passes.
3. Green on the rebased head (typecheck + test + build + format:check).
Expand All @@ -242,7 +248,9 @@ A bugfix may **auto-merge** only if ALL hold:
**Must stay human:** any §7.3 contract, any structural constraint, wire/message-shape changes, config/schema, state format, cross-repo dependencies, feat/refactor/architecture, security, or any default-value change (a product decision). Because a contract change is cross-repo by construction, it ALSO follows the manual cross-repo rule: release acp-kernel first, verify `npm view acp-kernel version`, then bump the adapters.

### 7.5 Reviewer focus — the "重灾区"

Of merged PRs that drew review, ≈25% needed a 2nd+ round. Dominant drivers:

1. **Stale-base / concurrent-file churn** on shared pipeline nodes.
2. **Incomplete first pass** — the repro passes but round-trip fidelity, tool-pair atomicity, or a doc-vs-default drift is missed.
Also check explicitly: does the fix preserve lossless round-trip and tool-pair atomicity? Do docs still match the actual code defaults (#163)?
Also check explicitly: does the fix preserve lossless round-trip and tool-pair atomicity? Do docs still match the actual code defaults (#163)?
Loading
Loading