fix(native): require publication for files named under a Deliverable label

A delegated brief often lists its output under "Deliverable:" rather than with
a creation verb. The completion gate missed those briefs, and .html/.css names,
so a worker could finish with the website only in its workspace.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
DottaandClaude Opus 5.5 committed 2026-11-09 04:17:25 -06:00
1 parent 3a8822988a
commit 9227344705
3 files changed
+71

No files matched your search

+4
View File
@@ -34,6 +34,10 @@ their contents inline does not by itself require a file attachment.
JSON output intent is recognized from a direct filename or an explicit output
destination. Ambiguous descriptions of input files joined by `to` remain text
requests; use `as`, `into`, or an explicit output file when an artifact is required.
A `Deliverable:` or `Deliverables` label also states the output. A file named
on that line, or in the list directly below it, is a requested file. This
includes web page files such as `index.html` and `styles.css`. Items that
describe a repository change, pull request, commit, or branch do not count.
The completion gate permits a narrow sentence-bound exception for one requested
file when the following sentence explicitly qualifies it, for example:
@@ -168,3 +168,40 @@ describe("explicit task-document output", () => {
expect(explicitlyRequestsTaskDocumentOutput(objective)).toBe(false);
});
});
describe("deliverable labels that name files", () => {
// The delegated brief from a real first-website run: the worker finished with
// index.html and styles.css only in its workspace and the gate did not ask it
// to publish them.
const delegatedBrief = [
"Build a simple, polished static marketing website that introduces the business, Acorn Notes.",
"",
"Deliverable:",
"- A self-contained static website in this task's workspace: index.html plus a stylesheet (for example styles.css). Plain HTML/CSS, no build step.",
"- Sections: a hero with the value proposition and a contact call to action.",
"",
"Constraints:",
"- Do not publish or deploy externally; no hosting, DNS, analytics, or third-party services.",
].join("\n");
it.each([
delegatedBrief,
"Deliverables: index.html and styles.css",
"## Deliverables\n- `index.html`\n- `styles.css`",
"**Deliverable:** report.pdf",
"Requested deliverable:\n1. summary.csv with one row per team",
])("requires publication for a labelled file output: %s", objective => {
expect(explicitlyRequestsFileOutput(objective)).toBe(true);
});
it.each([
"Build a simple website introducing the business.",
"A self-contained static website in this task's workspace: index.html plus a stylesheet.",
"Deliverable: a short answer in chat.",
"Deliverables:\n- A summary of input.csv in the reply",
"Deliverable: a pull request that updates index.html",
"Deliverables:\n- Repository change to styles.css",
"Deliverable:\n\nindex.html is mentioned after the list ended.",
"Deliverable quality matters more than speed for index.html.",
])("does not treat this as a labelled file output: %s", objective => {
expect(explicitlyRequestsFileOutput(objective)).toBe(false);
});
});
@@ -99,6 +99,36 @@ async function hasCurrentPublicationReceipt(db: Db, companyId: string, receipts:
* an output requirement or erase a user's request for a file.
*/
export function explicitlyRequestsFileOutput(objective: string): boolean {
return deliverableLabelNamesFile(objective) || explicitlyRequestsFileOutputBySentence(objective);
}
/** A brief that labels its output ("Deliverable:", "## Deliverables") and names
* a file there has stated the requested result as plainly as a creation verb.
* Delegated briefs often take this form, so a worker cannot finish with that
* file left only in its workspace. Web page files count here: the label makes
* them the output, not a repository file being edited in passing.
*/
function deliverableLabelNamesFile(objective: string): boolean {
const lines = objective.replaceAll("\\_", "_").split(/\r?\n/u);
const named = /(?:^|[\s(`'"*:])([^\s/()`'"*:]+\.(?:html?|css|md|txt|json|pdf|docx?|xlsx?|csv|pptx?|png|jpe?g|svg|zip))\b/giu;
// A repository change names files it edits; it is delivered as a PR or commit.
const namesFile = (text: string) => !/\b(?:[Rr]epo(?:sitory)?|[Pp]ull [Rr]equest|PR|[Cc]ommit|[Bb]ranch)\b/u.test(text) && [...text.matchAll(named)].some(match =>
!/\b(?:of|about|from|using|reading|no|without)\s+(?:(?:the|a|an|this|that)\s+)?[`'"*]*$/iu.test(text.slice(0, match.index! + match[0].indexOf(match[1]!))));
for (let index = 0; index < lines.length; index += 1) {
const label = /^\s*(?:#{1,6}\s*)?(?:\*\*|__)?(?:requested\s+)?deliverables?(?:\*\*|__)?\s*(?::(?:\*\*|__)?\s*(.*)|(?:\*\*|__)?\s*)$/iu.exec(lines[index]!);
if (!label) continue;
if (namesFile(label[1] ?? "")) return true;
// The label's own list: consecutive bullet or numbered items below it.
for (let item = index + 1; item < lines.length; item += 1) {
const bullet = /^\s*(?:[-*+]|\d+[.)])\s+(.*)$/u.exec(lines[item]!);
if (!bullet) break;
if (namesFile(bullet[1]!)) return true;
}
}
return false;
}
function explicitlyRequestsFileOutputBySentence(objective: string): boolean {
const sentences = objective.replaceAll("\\_", "_").split(/(?:[.!?](?:\s|$)|\n)/iu);
let precedingFileOutput = false;
return sentences.some((sentence, index) => {