Files
2026-07-13 12:20:01 +08:00

94 lines
3.4 KiB
TypeScript

import { readFileSync } from "fs"
import path from "path"
import { describe, expect, test } from "bun:test"
const SOURCES_DIR = path.join(
process.cwd(),
"skills/ce-sweep/references/sources",
)
// The five section headings every source persona must expose verbatim. The
// orchestrator seeds a generic subagent with one of these personas and relies
// on this fixed shape (probe -> fetch -> map -> untrusted handling -> tool
// limits) to reason about what the source returned. A persona that renames or
// drops a heading silently breaks that contract.
const REQUIRED_SECTIONS = [
"## Invocation Contract",
"## Availability Probe",
"## Fetch Guidance",
"## Untrusted Input Handling",
"## Tool Guidance",
]
// Per-persona exact degrade/skip sentences. These are the orchestrator's
// branch signals: on missing write scope a source degrades to read-only
// ingest (items become `ack_deferred`); on missing read tools the source is
// skipped. A paraphrase would break the run loop's detection, so they must
// appear byte-for-byte (em dash included).
const PERSONAS = {
"slack.md": {
bails: [
"Slack write capability unavailable — source degrades to read-only ingest; items will be marked ack_deferred.",
"Slack tools unavailable — source skipped this run.",
],
},
"github-issues.md": {
bails: [
"GitHub write capability unavailable — source degrades to read-only ingest; items will be marked ack_deferred.",
"GitHub tools unavailable — source skipped this run.",
],
},
"email.md": {
bails: [
"Email tools unavailable — source skipped this run.",
"Email acknowledgment primitive unavailable — items from this source are always marked ack_deferred; the orchestrator records acknowledgment in state only.",
],
},
} as const
function read(name: string): string {
return readFileSync(path.join(SOURCES_DIR, name), "utf8")
}
describe("ce-sweep source persona contract", () => {
for (const [name, spec] of Object.entries(PERSONAS)) {
describe(name, () => {
const body = read(name)
test("exposes all five exact contract section headings", () => {
for (const heading of REQUIRED_SECTIONS) {
expect(body).toContain(heading)
}
})
test("contains the exact degrade / skip bail sentences verbatim", () => {
for (const sentence of spec.bails) {
expect(body).toContain(sentence)
}
})
test("declares the cursor hands-off invariant with no cursor-mutation language", () => {
// KTD7: personas report facts; the orchestrator's bundled state script
// owns cursor advancement. Each persona must state this invariant
// verbatim and must never instruct itself to advance a cursor.
expect(body).toContain("You never advance cursors.")
expect(body).not.toMatch(/advance(s|d)? the cursor|cursor-advance/i)
})
test("forbids posting, sending, or replying at the source", () => {
// Writes are limited to the configured ack/close-out action only; no
// messages, comments, replies, or sends.
expect(body).toMatch(/never post|never send|never repl/i)
})
test("carries no YAML frontmatter (prompt asset, not an agent def)", () => {
expect(body.startsWith("---")).toBe(false)
})
})
}
test("email persona is marked experimental", () => {
expect(read("email.md").toLowerCase()).toContain("experimental")
})
})