Skip to content
Closed
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
18 changes: 18 additions & 0 deletions bin/local-publish-validate.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,24 @@ function gitCommitAndTag(cwd, version, message, opts) {
return;
}
runInherit('git', ['add', '-u'], cwd, opts);
// `--no-verify` below disables every pre-commit hook, including the L3
// distribution boundary gate. Publishing is precisely when that gate must not
// be skipped: it is the last point before templates/ reaches every managed
// project. Run it explicitly so the release path cannot ship L3 content that
// belongs in <repo>/.agent/ only.
const gateScript = path.join(cwd, 'scripts', 'check-l3-boundary.js');
if (fs.existsSync(gateScript)) {
const gateRun = spawnSync(process.execPath, [gateScript], {
cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
});
if (gateRun.status !== 0) {
process.stdout.write(gateRun.stdout || '');
process.stderr.write(gateRun.stderr || '');
console.error('❌ L3 distribution boundary check failed; refusing to commit and tag.');
console.error(' L3 内容只能留在 <repo>/.agent/(标记 scope: L3),不得进入 templates/。');
process.exit(1);
}
}
runInherit('git', ['commit', '-m', message, '--no-verify'], cwd, opts);
runInherit('git', ['tag', '-a', `v${version}`, '-m', `v${version}`], cwd, opts);
}
Expand Down
8 changes: 7 additions & 1 deletion templates/en/.agent/workflows/briefing.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
---
name: briefing
description: Produce a read-only project briefing covering progress, active work, Decisions, Waitpoints, Inbox, and recommended next actions.
type: procedure
applicable_to:
- all
owner: Kucell
last_verified: 2026-10-10
status: stable
---

# Project Briefing Workflow (/briefing)
Expand Down Expand Up @@ -57,7 +63,7 @@ Read-only probe of the latest published version. Never auto-upgrades.
cortex-agent doctor --json
```

`available_update` field contract (`/Users/xueyq/myworks/cortex-agent/lib/commands/doctor.js`):
`available_update` field contract (emitted by `cortex-agent doctor --json`):

- `status`: `"current"` / `"outdated"` / `"unknown"` (registry unreachable)
- `current`: CLI version (mirrors `package.json`)
Expand Down
2 changes: 1 addition & 1 deletion templates/zh/.agent/workflows/briefing.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ node .agent/skills/runtime-continuity/scripts/index.js resume-bundle --project "
cortex-agent doctor --json
```

`available_update` 字段契约(`/Users/xueyq/myworks/cortex-agent/lib/commands/doctor.js`):
`available_update` 字段契约(由 `cortex-agent doctor --json` 输出):

- `status`: `"current"` / `"outdated"` / `"unknown"`(网络/registry 不可达)
- `current`: 当前 CLI 版本(与 package.json 一致)
Expand Down
114 changes: 114 additions & 0 deletions tests/commands/local-publish-validate-l3-gate.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"use strict";

// ─── local-publish-validate must not publish past the L3 boundary gate ────────
//
// `gitCommitAndTag()` commits with `--no-verify`, which disables every
// pre-commit hook — including the L3 distribution boundary gate added in
// 79f3677. Publishing is exactly when that gate must hold: it is the last point
// before templates/ reaches every managed project.
//
// A real leak reached main this way: 1eb3d98 added an author-local absolute
// path to templates/{en,zh}/.agent/workflows/briefing.md and the hook never
// fired, because the commit went through this script.
//
// The gate runs in step 3, before npm pack (step 4) and volta install (step 5),
// so a failure exits before anything is packed or installed. These tests
// therefore run the real script against a throwaway git repo and assert that
// nothing was committed, packed or installed.

const assert = require("node:assert/strict");
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
const { spawnSync } = require("node:child_process");
const { test, describe, beforeEach, afterEach } = require("node:test");

const ROOT = path.resolve(__dirname, "..", "..");
const SCRIPT = path.join(ROOT, "bin", "local-publish-validate.cjs");

let repo;

function git(args) {
return spawnSync("git", ["-C", repo, ...args], { encoding: "utf8" });
}

function writeStubGate(body) {
const dir = path.join(repo, "scripts");
fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(path.join(dir, "check-l3-boundary.js"), body);
}

beforeEach(() => {
repo = fs.mkdtempSync(path.join(os.tmpdir(), "cortex-lpv-l3-"));
git(["init", "-q"]);
git(["config", "user.email", "lpv@test"]);
git(["config", "user.name", "lpv"]);
fs.writeFileSync(
path.join(repo, "package.json"),
JSON.stringify({ name: "lpv-l3-fixture", version: "1.0.0", private: true }),
);
fs.mkdirSync(path.join(repo, "templates", "x"), { recursive: true });
git(["add", "."]);
git(["commit", "-q", "-m", "init"]);
});

afterEach(() => fs.rmSync(repo, { recursive: true, force: true }));

function run() {
return spawnSync(process.execPath, [SCRIPT, "--force", "--skip-tests"], {
cwd: repo,
encoding: "utf8",
});
}

describe("local-publish-validate — L3 boundary gate before commit", () => {
test("a failing gate blocks the commit and tags nothing", () => {
writeStubGate('process.stdout.write("violation found\\n"); process.exit(1);');
const before = git(["rev-parse", "HEAD"]).stdout.trim();

const r = run();

assert.notEqual(r.status, 0, "the script must fail when the gate fails");
assert.match(r.stdout + r.stderr, /L3 distribution boundary check failed/);
assert.equal(git(["rev-parse", "HEAD"]).stdout.trim(), before, "HEAD must not advance");
assert.equal(git(["tag"]).stdout.trim(), "", "no tag may be created");
});

test("a failing gate stops before npm pack", () => {
writeStubGate("process.exit(1);");
const r = run();
assert.doesNotMatch(r.stdout, /npm pack/, "pack must not run once the gate fails");
assert.doesNotMatch(r.stdout, /volta install/, "install must not run once the gate fails");
});

test("the gate is invoked at all", () => {
// A marker file proves the script actually executed the checker rather
// than merely carrying the code path.
writeStubGate(
'require("node:fs").writeFileSync(process.env.LPV_MARKER, "ran");\n' +
"process.exit(1);"
);
const marker = path.join(repo, "gate-ran.marker");
spawnSync(process.execPath, [SCRIPT, "--force", "--skip-tests"], {
cwd: repo,
encoding: "utf8",
env: { ...process.env, LPV_MARKER: marker },
});
assert.ok(fs.existsSync(marker), "the boundary checker must be executed");
});

test("the commit still passes --no-verify, which is why the explicit gate exists", () => {
const src = fs.readFileSync(SCRIPT, "utf8");
assert.match(
src,
/'commit', '-m', message, '--no-verify'/,
"the --no-verify call is expected; the guard above it is what must hold",
);
const gateIdx = src.indexOf("check-l3-boundary.js");
const commitIdx = src.indexOf("'--no-verify'");
assert.ok(
gateIdx !== -1 && gateIdx < commitIdx,
"the boundary gate must be checked before the commit that skips hooks",
);
});
});
Loading