mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-10 12:37:55 +01:00
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:
1 parent
3a8822988a
commit
9227344705
3 files changed
+71
No files matched your search
@@ -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) => {
|
||||
|
||||
Reference in new issue
Block a user