{
  "name": "Build your \"how I like work done\" file",
  "nodes": [
    {
      "parameters": {
        "content": "## Build your \"how I like work done\" file\nAnswer a few questions about how you work and what you like, turn the answers into a short list of rules for an AI tool, test each rule on a sample task, and keep a file you can paste at the start of any chat.\n\n**How to use**\n1. Open **Chat model** and pick your own credential. To use another AI service, delete that node, add the service's chat model node, and connect it to the Model plug under every step and AI check.\n2. Open **Inputs** and paste your material into each field. Examples are filled in so you can try it first.\n3. Click **Execute workflow**.\n4. Open **Result** to read the answer, anything flagged, and the checks left for you.\n\n**Checks**\n- Check 1 (a plain rule, run automatically with no AI): Every row of the table has something written in \"Rule\", \"Reason\" and \"Test\" (a blank or a dash does not count). If it fails: try once more, then flag.\n- Check 2 (a plain rule, run automatically with no AI): Every row of the table has one of \"yes\" or \"no\" in the Followed column. If it fails: try once more, then flag.\n- Check 3 (a plain rule, run automatically with no AI): Every row of the table has one of \"yes\" or \"no\" in the Changed column. If it fails: try once more, then flag.\n- Check 4 (a plain rule, run automatically with no AI): The answer is no longer than 350 words. If it fails: try once more, then flag.\n- Check 5 (a second AI prompt answers pass or fail): Compare the file with the final rules below. Is every rule in the table present in the file, with the same meaning, and does the file add no rule that is not in the table? If it fails: try once more, then flag.\n- Check 6 (a plain rule, run automatically with no AI): Every row of the table has something written in \"Followed\" and \"What I saw\" (a blank or a dash does not count). If it fails: try once more, then flag.\n- Check 7 (you check it by hand): Read the file. Does each rule say something you really want? Cross out any you would not miss, then try the file in a new chat on a real task and see if the answer feels right. If it fails: flag it and carry on.\n\nFrom Logic Lab (logiclabhq.com). Free to use and adapt, with credit.",
        "height": 680,
        "width": 760,
        "color": 5
      },
      "id": "868c362b-6413-4ef6-ae85-57fdbd140108",
      "name": "About this workflow",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -40,
        -640
      ]
    },
    {
      "parameters": {},
      "id": "da862e3b-c105-467a-a090-43b5525ee274",
      "name": "Start",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        320
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "e5b05dbd-2a25-4ae6-a59b-090b40b66744",
              "name": "work",
              "value": "Sample: I run a small bookkeeping office. I write to clients who are not finance people, and to my two staff.",
              "type": "string"
            },
            {
              "id": "aa259cff-2353-453a-a51d-ed75e1a23930",
              "name": "sound",
              "value": "Sample: Plain and friendly. Short sentences. No jargon. I say \"please\" and \"thanks\" but I do not gush.",
              "type": "string"
            },
            {
              "id": "eb7d8456-d51b-4f61-a15f-ba804bc11733",
              "name": "layout",
              "value": "Sample: The answer first, then the reasons in three bullets at most",
              "type": "string"
            },
            {
              "id": "4bb19471-f9ec-4da2-a9c6-a98f6b08f1c0",
              "name": "annoyances",
              "value": "Sample: It repeats my question back to me. It adds long introductions. It guesses facts I did not give it. It ends with a summary of what it just said.",
              "type": "string"
            },
            {
              "id": "488b547d-0765-464a-ab93-15e3f66f2608",
              "name": "always",
              "value": "Sample: Ask me if a number or a date is missing. Say so clearly when it is not sure.",
              "type": "string"
            },
            {
              "id": "5d47491e-5838-4117-adb3-200099c22861",
              "name": "sample_task",
              "value": "Sample: Write a short email to a client reminding them that their documents are due on Friday",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "153e7104-01d3-4daf-a7f9-429ab0c9c6fd",
      "name": "Inputs",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        220,
        320
      ],
      "notes": "Paste your own material here: work = What you do and who you write for; sound = How you like writing to sound; layout = How you like answers laid out; annoyances = What annoys you in AI answers; always = What it should always do (optional); sample_task = A real task you do often.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are an expert at writing clear working instructions for an AI assistant.\n\nYour task is to turn the person's answers below into a list of plain rules that an AI assistant can follow.\n\nWhat they do and who they write for:\n{{ $(\"Inputs\").first().json[\"work\"] }}\n\nHow they like writing to sound:\n{{ $(\"Inputs\").first().json[\"sound\"] }}\n\nHow they like answers laid out:\n{{ $(\"Inputs\").first().json[\"layout\"] }}\n\nWhat annoys them:\n{{ $(\"Inputs\").first().json[\"annoyances\"] }}\n\nWhat it should always do:\n{{ $(\"Inputs\").first().json[\"always\"] }}\n\nRules for you:\n- Write 6 to 12 rules. Each rule is one instruction, written so it can be followed or broken, not a wish. \"Put the answer in the first sentence\" is a rule. \"Be clear\" is not.\n- Use the person's own words where you can. Do not add preferences they did not give.\n- Give each rule a short reason taken from their answers.\n- For each rule, write a \"Test\" that says what you would see in an answer that follows it.\n- Do not write the final file yet.\n\nReturn one markdown table and nothing else:\n| # | Rule | Reason | Test |{{ $(\"Check 1: Every rule has a reason and a test\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every rule has a reason and a test. What was wrong: \" + $(\"Check 1: Every rule has a reason and a test\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "765ff3e4-5380-4da5-ad58-81fefe634b6f",
      "name": "1. Turn your answers into rules",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        480,
        320
      ],
      "notes": "Write a short list of clear rules for an AI tool, each with the reason you gave and a way to test it. Returns: A table of 6 to 12 rules, each with a reason and a test.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Every rule has a reason and a test\n// Rule: Every row of the table has something written in \"Rule\", \"Reason\" and \"Test\" (a blank or a dash does not count).\n// Runs with no AI. Returns pass (true or false) and a reason.\nconst runRule = function runRule(rule, value, work, sources) {\n    const norm = (s) => String(s ?? '')\n        .toLowerCase()\n        .replace(/[\\u2018\\u2019\\u201c\\u201d\"'`]/g, '')\n        .replace(/\\s+/g, ' ')\n        .trim();\n    const readTable = (text) => {\n        const lines = String(text ?? '')\n            .split(/\\r?\\n/)\n            .map((l) => l.trim());\n        const tables = [];\n        let cur = null;\n        const cells = (l) => l\n            .replace(/^\\|/, '')\n            .replace(/\\|$/, '')\n            .split('|')\n            .map((c) => c.trim());\n        for (let i = 0; i < lines.length; i++) {\n            const l = lines[i] ?? '';\n            if (!l.startsWith('|')) {\n                cur = null;\n                continue;\n            }\n            if (!cur) {\n                const next = lines[i + 1] ?? '';\n                if (/^\\|?\\s*:?-{2,}/.test(next)) {\n                    cur = { header: cells(l), rows: [] };\n                    tables.push(cur);\n                    i++;\n                }\n                continue;\n            }\n            cur.rows.push(cells(l));\n        }\n        return tables;\n    };\n    // Every table that has this column, with \"None found\" rows left out.\n    // Returns that column's cells from every such table, or null if no table has it.\n    const findColumn = (name) => {\n        let seen = false;\n        const cells = [];\n        for (const t of readTable(work)) {\n            const i = t.header.findIndex((h) => norm(h) === norm(name));\n            if (i < 0)\n                continue;\n            seen = true;\n            for (const r of t.rows) {\n                if (r.some((c) => norm(c) === 'none found'))\n                    continue;\n                cells.push(r[i] ?? '');\n            }\n        }\n        return seen ? cells : null;\n    };\n    const empty = (c) => !c || /^[-\\u2013\\u2014\\s]*$/.test(c);\n    switch (rule) {\n        case 'has_text':\n            return norm(work).includes(norm(value))\n                ? { pass: true, reason: 'Found.' }\n                : { pass: false, reason: `The answer does not include \"${value}\".` };\n        case 'lacks_text':\n            return norm(work).includes(norm(value))\n                ? { pass: false, reason: `The answer includes \"${value}\", which it should not.` }\n                : { pass: true, reason: 'Not found, as it should be.' };\n        case 'max_words': {\n            const n = String(work ?? '')\n                .split(/\\s+/)\n                .filter(Boolean).length;\n            return n <= Number(value)\n                ? { pass: true, reason: `${n} words.` }\n                : { pass: false, reason: `The answer has ${n} words; the limit is ${value}.` };\n        }\n        case 'table_cells_filled': {\n            const problems = [];\n            for (const col of value) {\n                const f = findColumn(col);\n                if (!f) {\n                    problems.push(`No table with a \"${col}\" column was found.`);\n                    continue;\n                }\n                f.forEach((c, i) => {\n                    if (empty(c))\n                        problems.push(`Row ${i + 1} has nothing in \"${col}\".`);\n                });\n            }\n            return problems.length\n                ? { pass: false, reason: problems.join(' ') }\n                : { pass: true, reason: 'Every row is filled in.' };\n        }\n        case 'column_values_in': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            const allowed = value.allowed.map(norm);\n            const bad = [];\n            f.forEach((c, i) => {\n                if (!allowed.includes(norm(c)))\n                    bad.push(`Row ${i + 1} says \"${c}\".`);\n            });\n            return bad.length\n                ? {\n                    pass: false,\n                    reason: `The \"${value.column}\" column must say ${value.allowed.join(' or ')}. ${bad.join(' ')}`,\n                }\n                : { pass: true, reason: 'Every row uses an allowed word.' };\n        }\n        case 'column_quotes_source': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            const src = norm(sources[value.input] ?? '');\n            const bad = [];\n            f.forEach((c, i) => {\n                // A quote may be shortened with \"...\"; each piece must be in the source.\n                const pieces = c\n                    .split(/\\.\\.\\.|\\u2026/)\n                    .map(norm)\n                    .filter(Boolean);\n                if (!pieces.length || !pieces.every((p) => src.includes(p)))\n                    bad.push(`Row ${i + 1}: \"${c}\" is not in the source.`);\n            });\n            return bad.length\n                ? { pass: false, reason: bad.join(' ') }\n                : { pass: true, reason: 'Every quote was found in the source.' };\n        }\n        case 'numbers_in_source': {\n            const nums = (s) => (String(s ?? '').match(/\\d[\\d,]*(?:\\.\\d+)?/g) ?? []).map((n) => n.replace(/,/g, ''));\n            const src = new Set(nums(sources[value.input] ?? ''));\n            const missing = [...new Set(nums(work))].filter((n) => !src.has(n));\n            return missing.length\n                ? {\n                    pass: false,\n                    reason: `These numbers are not in the source: ${missing.join(', ')}.`,\n                }\n                : { pass: true, reason: 'Every number was found in the source.' };\n        }\n        default:\n            return { pass: false, reason: `Unknown rule \"${rule}\".` };\n    }\n};\n\nconst work = String($(\"1. Turn your answers into rules\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"table_cells_filled\", [\"Rule\",\"Reason\",\"Test\"], work, sources);\nreturn [{ json: result }];"
      },
      "id": "0d4e5b58-252e-4749-a71e-26e224b527d3",
      "name": "Check 1: Every rule has a reason and a test",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        740,
        320
      ],
      "notes": "Every row of the table has something written in \"Rule\", \"Reason\" and \"Test\" (a blank or a dash does not count).",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "065ba6f6-18a7-4529-ad58-9c8458e2356f",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "11eefb43-30af-4f42-ac71-b7dd7bc9a204",
      "name": "Passed check 1?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1000,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "4a721251-ccca-4446-a405-5acf785e0f5c",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "3d6318d9-a560-4dca-a891-6f6b10f502bc",
      "name": "Try again? (check 1)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1000,
        520
      ],
      "notes": "True = first failure: send back to \"1. Turn your answers into rules\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "4643d07c-137d-4f5b-a08f-542ef86c62fd",
              "name": "flag",
              "value": "=Every rule has a reason and a test: did not pass. {{ $(\"Check 1: Every rule has a reason and a test\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "90e42e80-1647-459f-a5b2-935adfb94ea1",
      "name": "Flag check 1",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1130,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are an assistant who follows written working rules exactly, and then checks your own work.\n\nYour task is to do the sample task below while following every rule, and then check whether each rule was followed.\n\nThe sample task:\n{{ $(\"Inputs\").first().json[\"sample_task\"] }}\n\nBackground about the person:\n{{ $(\"Inputs\").first().json[\"work\"] }}\n\nThe rules, with their tests:\n{{ $(\"1. Turn your answers into rules\").first().json.text }}\n\nRules for you:\n- Do the task first, following every rule. Then check each rule against what you wrote, using its test.\n- Be honest. If a rule was not followed or could not be tested on this task, say so.\n- Do not invent facts for the task. Use a placeholder such as [name] where a detail is missing.\n\nReturn in this shape:\nThe result of the task:\n(the text you wrote)\n\nThen one markdown table:\n| # | Rule | Followed | What I saw |\nThe Followed column must say exactly yes or no.{{ $(\"Check 2: Each rule is marked yes or no\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Each rule is marked yes or no. What was wrong: \" + $(\"Check 2: Each rule is marked yes or no\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 6: Each rule was tested on the sample\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Each rule was tested on the sample. What was wrong: \" + $(\"Check 6: Each rule was tested on the sample\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "5ef4d06a-7fb1-4e3d-ad00-2ee85469c4bb",
      "name": "2. Try each rule on your sample task",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        1260,
        320
      ],
      "notes": "Do the sample task while following the rules, then check each rule against the result. Returns: The sample task done with the rules, and a table that says whether each rule was followed.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Each rule is marked yes or no\n// Rule: Every row of the table has one of \"yes\" or \"no\" in the Followed column.\n// Runs with no AI. Returns pass (true or false) and a reason.\nconst runRule = function runRule(rule, value, work, sources) {\n    const norm = (s) => String(s ?? '')\n        .toLowerCase()\n        .replace(/[\\u2018\\u2019\\u201c\\u201d\"'`]/g, '')\n        .replace(/\\s+/g, ' ')\n        .trim();\n    const readTable = (text) => {\n        const lines = String(text ?? '')\n            .split(/\\r?\\n/)\n            .map((l) => l.trim());\n        const tables = [];\n        let cur = null;\n        const cells = (l) => l\n            .replace(/^\\|/, '')\n            .replace(/\\|$/, '')\n            .split('|')\n            .map((c) => c.trim());\n        for (let i = 0; i < lines.length; i++) {\n            const l = lines[i] ?? '';\n            if (!l.startsWith('|')) {\n                cur = null;\n                continue;\n            }\n            if (!cur) {\n                const next = lines[i + 1] ?? '';\n                if (/^\\|?\\s*:?-{2,}/.test(next)) {\n                    cur = { header: cells(l), rows: [] };\n                    tables.push(cur);\n                    i++;\n                }\n                continue;\n            }\n            cur.rows.push(cells(l));\n        }\n        return tables;\n    };\n    // Every table that has this column, with \"None found\" rows left out.\n    // Returns that column's cells from every such table, or null if no table has it.\n    const findColumn = (name) => {\n        let seen = false;\n        const cells = [];\n        for (const t of readTable(work)) {\n            const i = t.header.findIndex((h) => norm(h) === norm(name));\n            if (i < 0)\n                continue;\n            seen = true;\n            for (const r of t.rows) {\n                if (r.some((c) => norm(c) === 'none found'))\n                    continue;\n                cells.push(r[i] ?? '');\n            }\n        }\n        return seen ? cells : null;\n    };\n    const empty = (c) => !c || /^[-\\u2013\\u2014\\s]*$/.test(c);\n    switch (rule) {\n        case 'has_text':\n            return norm(work).includes(norm(value))\n                ? { pass: true, reason: 'Found.' }\n                : { pass: false, reason: `The answer does not include \"${value}\".` };\n        case 'lacks_text':\n            return norm(work).includes(norm(value))\n                ? { pass: false, reason: `The answer includes \"${value}\", which it should not.` }\n                : { pass: true, reason: 'Not found, as it should be.' };\n        case 'max_words': {\n            const n = String(work ?? '')\n                .split(/\\s+/)\n                .filter(Boolean).length;\n            return n <= Number(value)\n                ? { pass: true, reason: `${n} words.` }\n                : { pass: false, reason: `The answer has ${n} words; the limit is ${value}.` };\n        }\n        case 'table_cells_filled': {\n            const problems = [];\n            for (const col of value) {\n                const f = findColumn(col);\n                if (!f) {\n                    problems.push(`No table with a \"${col}\" column was found.`);\n                    continue;\n                }\n                f.forEach((c, i) => {\n                    if (empty(c))\n                        problems.push(`Row ${i + 1} has nothing in \"${col}\".`);\n                });\n            }\n            return problems.length\n                ? { pass: false, reason: problems.join(' ') }\n                : { pass: true, reason: 'Every row is filled in.' };\n        }\n        case 'column_values_in': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            const allowed = value.allowed.map(norm);\n            const bad = [];\n            f.forEach((c, i) => {\n                if (!allowed.includes(norm(c)))\n                    bad.push(`Row ${i + 1} says \"${c}\".`);\n            });\n            return bad.length\n                ? {\n                    pass: false,\n                    reason: `The \"${value.column}\" column must say ${value.allowed.join(' or ')}. ${bad.join(' ')}`,\n                }\n                : { pass: true, reason: 'Every row uses an allowed word.' };\n        }\n        case 'column_quotes_source': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            const src = norm(sources[value.input] ?? '');\n            const bad = [];\n            f.forEach((c, i) => {\n                // A quote may be shortened with \"...\"; each piece must be in the source.\n                const pieces = c\n                    .split(/\\.\\.\\.|\\u2026/)\n                    .map(norm)\n                    .filter(Boolean);\n                if (!pieces.length || !pieces.every((p) => src.includes(p)))\n                    bad.push(`Row ${i + 1}: \"${c}\" is not in the source.`);\n            });\n            return bad.length\n                ? { pass: false, reason: bad.join(' ') }\n                : { pass: true, reason: 'Every quote was found in the source.' };\n        }\n        case 'numbers_in_source': {\n            const nums = (s) => (String(s ?? '').match(/\\d[\\d,]*(?:\\.\\d+)?/g) ?? []).map((n) => n.replace(/,/g, ''));\n            const src = new Set(nums(sources[value.input] ?? ''));\n            const missing = [...new Set(nums(work))].filter((n) => !src.has(n));\n            return missing.length\n                ? {\n                    pass: false,\n                    reason: `These numbers are not in the source: ${missing.join(', ')}.`,\n                }\n                : { pass: true, reason: 'Every number was found in the source.' };\n        }\n        default:\n            return { pass: false, reason: `Unknown rule \"${rule}\".` };\n    }\n};\n\nconst work = String($(\"2. Try each rule on your sample task\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"column_values_in\", {\"column\":\"Followed\",\"allowed\":[\"yes\",\"no\"]}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "5b8db06f-96cd-4890-a6d7-b0097aef5d9a",
      "name": "Check 2: Each rule is marked yes or no",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1520,
        320
      ],
      "notes": "Every row of the table has one of \"yes\" or \"no\" in the Followed column.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "075ba889-17a7-4396-ae58-9e1757e233dc",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "11ecbcac-b0a7-49fd-ac79-3d227bcbe09b",
      "name": "Passed check 2?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1780,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "497210be-cdca-45d9-a305-593c795e10ef",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "e5604fba-bd63-4229-a899-2710f0ee1497",
      "name": "Try again? (check 2)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1780,
        520
      ],
      "notes": "True = first failure: send back to \"2. Try each rule on your sample task\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "4743d20f-127d-4dc8-a18f-55c1f76c616a",
              "name": "flag",
              "value": "=Each rule is marked yes or no: did not pass. {{ $(\"Check 2: Each rule is marked yes or no\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "93e43339-1747-4732-a4b2-91c7dcb949e8",
      "name": "Flag check 2",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1910,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are an expert at writing working instructions that an AI assistant follows reliably.\n\nYour task is to improve the rules that were not followed in the test.\n\nThe rules:\n{{ $(\"1. Turn your answers into rules\").first().json.text }}\n\nThe test result:\n{{ $(\"2. Try each rule on your sample task\").first().json.text }}\n\nRules for you:\n- Keep every rule that was followed, word for word.\n- For a rule that was not followed, reword it so it is more specific, add an example of what to do, or split it in two. Explain in a few words what you changed.\n- Do not add new preferences and do not remove a rule the person asked for.\n- If every rule was followed, say so and change nothing.\n\nReturn one markdown table and nothing else:\n| # | Rule | Changed |\nThe Changed column must say exactly yes or no, and the Rule column holds the final wording.{{ $(\"Check 3: Every row says if it changed\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every row says if it changed. What was wrong: \" + $(\"Check 3: Every row says if it changed\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "fbff1a59-47af-4a3a-a17d-30cf4eb27348",
      "name": "3. Fix the rules that failed",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        2040,
        320
      ],
      "notes": "Reword any rule that was not followed so that it is easier to follow, and keep the ones that worked. Returns: The final list of rules, with the reworded ones marked.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Every row says if it changed\n// Rule: Every row of the table has one of \"yes\" or \"no\" in the Changed column.\n// Runs with no AI. Returns pass (true or false) and a reason.\nconst runRule = function runRule(rule, value, work, sources) {\n    const norm = (s) => String(s ?? '')\n        .toLowerCase()\n        .replace(/[\\u2018\\u2019\\u201c\\u201d\"'`]/g, '')\n        .replace(/\\s+/g, ' ')\n        .trim();\n    const readTable = (text) => {\n        const lines = String(text ?? '')\n            .split(/\\r?\\n/)\n            .map((l) => l.trim());\n        const tables = [];\n        let cur = null;\n        const cells = (l) => l\n            .replace(/^\\|/, '')\n            .replace(/\\|$/, '')\n            .split('|')\n            .map((c) => c.trim());\n        for (let i = 0; i < lines.length; i++) {\n            const l = lines[i] ?? '';\n            if (!l.startsWith('|')) {\n                cur = null;\n                continue;\n            }\n            if (!cur) {\n                const next = lines[i + 1] ?? '';\n                if (/^\\|?\\s*:?-{2,}/.test(next)) {\n                    cur = { header: cells(l), rows: [] };\n                    tables.push(cur);\n                    i++;\n                }\n                continue;\n            }\n            cur.rows.push(cells(l));\n        }\n        return tables;\n    };\n    // Every table that has this column, with \"None found\" rows left out.\n    // Returns that column's cells from every such table, or null if no table has it.\n    const findColumn = (name) => {\n        let seen = false;\n        const cells = [];\n        for (const t of readTable(work)) {\n            const i = t.header.findIndex((h) => norm(h) === norm(name));\n            if (i < 0)\n                continue;\n            seen = true;\n            for (const r of t.rows) {\n                if (r.some((c) => norm(c) === 'none found'))\n                    continue;\n                cells.push(r[i] ?? '');\n            }\n        }\n        return seen ? cells : null;\n    };\n    const empty = (c) => !c || /^[-\\u2013\\u2014\\s]*$/.test(c);\n    switch (rule) {\n        case 'has_text':\n            return norm(work).includes(norm(value))\n                ? { pass: true, reason: 'Found.' }\n                : { pass: false, reason: `The answer does not include \"${value}\".` };\n        case 'lacks_text':\n            return norm(work).includes(norm(value))\n                ? { pass: false, reason: `The answer includes \"${value}\", which it should not.` }\n                : { pass: true, reason: 'Not found, as it should be.' };\n        case 'max_words': {\n            const n = String(work ?? '')\n                .split(/\\s+/)\n                .filter(Boolean).length;\n            return n <= Number(value)\n                ? { pass: true, reason: `${n} words.` }\n                : { pass: false, reason: `The answer has ${n} words; the limit is ${value}.` };\n        }\n        case 'table_cells_filled': {\n            const problems = [];\n            for (const col of value) {\n                const f = findColumn(col);\n                if (!f) {\n                    problems.push(`No table with a \"${col}\" column was found.`);\n                    continue;\n                }\n                f.forEach((c, i) => {\n                    if (empty(c))\n                        problems.push(`Row ${i + 1} has nothing in \"${col}\".`);\n                });\n            }\n            return problems.length\n                ? { pass: false, reason: problems.join(' ') }\n                : { pass: true, reason: 'Every row is filled in.' };\n        }\n        case 'column_values_in': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            const allowed = value.allowed.map(norm);\n            const bad = [];\n            f.forEach((c, i) => {\n                if (!allowed.includes(norm(c)))\n                    bad.push(`Row ${i + 1} says \"${c}\".`);\n            });\n            return bad.length\n                ? {\n                    pass: false,\n                    reason: `The \"${value.column}\" column must say ${value.allowed.join(' or ')}. ${bad.join(' ')}`,\n                }\n                : { pass: true, reason: 'Every row uses an allowed word.' };\n        }\n        case 'column_quotes_source': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            const src = norm(sources[value.input] ?? '');\n            const bad = [];\n            f.forEach((c, i) => {\n                // A quote may be shortened with \"...\"; each piece must be in the source.\n                const pieces = c\n                    .split(/\\.\\.\\.|\\u2026/)\n                    .map(norm)\n                    .filter(Boolean);\n                if (!pieces.length || !pieces.every((p) => src.includes(p)))\n                    bad.push(`Row ${i + 1}: \"${c}\" is not in the source.`);\n            });\n            return bad.length\n                ? { pass: false, reason: bad.join(' ') }\n                : { pass: true, reason: 'Every quote was found in the source.' };\n        }\n        case 'numbers_in_source': {\n            const nums = (s) => (String(s ?? '').match(/\\d[\\d,]*(?:\\.\\d+)?/g) ?? []).map((n) => n.replace(/,/g, ''));\n            const src = new Set(nums(sources[value.input] ?? ''));\n            const missing = [...new Set(nums(work))].filter((n) => !src.has(n));\n            return missing.length\n                ? {\n                    pass: false,\n                    reason: `These numbers are not in the source: ${missing.join(', ')}.`,\n                }\n                : { pass: true, reason: 'Every number was found in the source.' };\n        }\n        default:\n            return { pass: false, reason: `Unknown rule \"${rule}\".` };\n    }\n};\n\nconst work = String($(\"3. Fix the rules that failed\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"column_values_in\", {\"column\":\"Changed\",\"allowed\":[\"yes\",\"no\"]}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "e2f30829-d79b-4344-a040-ad73d1e65676",
      "name": "Check 3: Every row says if it changed",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2300,
        320
      ],
      "notes": "Every row of the table has one of \"yes\" or \"no\" in the Changed column.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "045ba3d0-16a7-4203-af58-9faa5ae23895",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "11ea7e15-b0aa-4894-ac76-fe8b7bce1f32",
      "name": "Passed check 3?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2560,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "48720f2b-caca-4120-a605-5df57a5e1282",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "c15dd877-2166-4e2c-a496-c8fd14f08bda",
      "name": "Try again? (check 3)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2560,
        520
      ],
      "notes": "True = first failure: send back to \"3. Fix the rules that failed\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "4843d3a2-157d-4281-ae8f-5108f66c5fd7",
              "name": "flag",
              "value": "=Every row says if it changed: did not pass. {{ $(\"Check 3: Every row says if it changed\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "92e431a6-1847-48c5-a3b2-9034ddb94b7b",
      "name": "Flag check 3",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        2690,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are an expert at writing the short instructions people paste at the start of an AI chat.\n\nYour task is to write the final \"how I like work done\" file from the rules below.\n\nWhat they do and who they write for:\n{{ $(\"Inputs\").first().json[\"work\"] }}\n\nThe final rules:\n{{ $(\"3. Fix the rules that failed\").first().json.text }}\n\nRules for you:\n- Write it to the AI, in the second person (\"You are helping...\", \"Put the answer first\").\n- Open with two sentences on who the person is and who they write for.\n- Group the rules under short headings, such as Tone, Layout and Always do, and Never do.\n- Keep every rule from the final list. Do not add rules and do not add facts about the person.\n- Keep it to 350 words or fewer. Do not use jargon.\n\nReturn the file as plain text and nothing else.{{ $(\"Check 4: The file is 350 words or fewer\").isExecuted ? \"\\n\\nYour last answer did not pass this check: The file is 350 words or fewer. What was wrong: \" + $(\"Check 4: The file is 350 words or fewer\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 5: Every rule is in the file\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every rule is in the file. What was wrong: \" + $(\"Check 5: Every rule is in the file\").first().json.text + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "ef0ad35a-10b6-43a3-a53e-4dac84dbd065",
      "name": "4. Write the file you paste into your AI tool",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        2820,
        320
      ],
      "notes": "Put the final rules into one short file, written to the AI, that you can paste at the start of any chat. Returns: A short file of instructions, ready to paste at the start of any AI chat.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// The file is 350 words or fewer\n// Rule: The answer is no longer than 350 words.\n// Runs with no AI. Returns pass (true or false) and a reason.\nconst runRule = function runRule(rule, value, work, sources) {\n    const norm = (s) => String(s ?? '')\n        .toLowerCase()\n        .replace(/[\\u2018\\u2019\\u201c\\u201d\"'`]/g, '')\n        .replace(/\\s+/g, ' ')\n        .trim();\n    const readTable = (text) => {\n        const lines = String(text ?? '')\n            .split(/\\r?\\n/)\n            .map((l) => l.trim());\n        const tables = [];\n        let cur = null;\n        const cells = (l) => l\n            .replace(/^\\|/, '')\n            .replace(/\\|$/, '')\n            .split('|')\n            .map((c) => c.trim());\n        for (let i = 0; i < lines.length; i++) {\n            const l = lines[i] ?? '';\n            if (!l.startsWith('|')) {\n                cur = null;\n                continue;\n            }\n            if (!cur) {\n                const next = lines[i + 1] ?? '';\n                if (/^\\|?\\s*:?-{2,}/.test(next)) {\n                    cur = { header: cells(l), rows: [] };\n                    tables.push(cur);\n                    i++;\n                }\n                continue;\n            }\n            cur.rows.push(cells(l));\n        }\n        return tables;\n    };\n    // Every table that has this column, with \"None found\" rows left out.\n    // Returns that column's cells from every such table, or null if no table has it.\n    const findColumn = (name) => {\n        let seen = false;\n        const cells = [];\n        for (const t of readTable(work)) {\n            const i = t.header.findIndex((h) => norm(h) === norm(name));\n            if (i < 0)\n                continue;\n            seen = true;\n            for (const r of t.rows) {\n                if (r.some((c) => norm(c) === 'none found'))\n                    continue;\n                cells.push(r[i] ?? '');\n            }\n        }\n        return seen ? cells : null;\n    };\n    const empty = (c) => !c || /^[-\\u2013\\u2014\\s]*$/.test(c);\n    switch (rule) {\n        case 'has_text':\n            return norm(work).includes(norm(value))\n                ? { pass: true, reason: 'Found.' }\n                : { pass: false, reason: `The answer does not include \"${value}\".` };\n        case 'lacks_text':\n            return norm(work).includes(norm(value))\n                ? { pass: false, reason: `The answer includes \"${value}\", which it should not.` }\n                : { pass: true, reason: 'Not found, as it should be.' };\n        case 'max_words': {\n            const n = String(work ?? '')\n                .split(/\\s+/)\n                .filter(Boolean).length;\n            return n <= Number(value)\n                ? { pass: true, reason: `${n} words.` }\n                : { pass: false, reason: `The answer has ${n} words; the limit is ${value}.` };\n        }\n        case 'table_cells_filled': {\n            const problems = [];\n            for (const col of value) {\n                const f = findColumn(col);\n                if (!f) {\n                    problems.push(`No table with a \"${col}\" column was found.`);\n                    continue;\n                }\n                f.forEach((c, i) => {\n                    if (empty(c))\n                        problems.push(`Row ${i + 1} has nothing in \"${col}\".`);\n                });\n            }\n            return problems.length\n                ? { pass: false, reason: problems.join(' ') }\n                : { pass: true, reason: 'Every row is filled in.' };\n        }\n        case 'column_values_in': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            const allowed = value.allowed.map(norm);\n            const bad = [];\n            f.forEach((c, i) => {\n                if (!allowed.includes(norm(c)))\n                    bad.push(`Row ${i + 1} says \"${c}\".`);\n            });\n            return bad.length\n                ? {\n                    pass: false,\n                    reason: `The \"${value.column}\" column must say ${value.allowed.join(' or ')}. ${bad.join(' ')}`,\n                }\n                : { pass: true, reason: 'Every row uses an allowed word.' };\n        }\n        case 'column_quotes_source': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            const src = norm(sources[value.input] ?? '');\n            const bad = [];\n            f.forEach((c, i) => {\n                // A quote may be shortened with \"...\"; each piece must be in the source.\n                const pieces = c\n                    .split(/\\.\\.\\.|\\u2026/)\n                    .map(norm)\n                    .filter(Boolean);\n                if (!pieces.length || !pieces.every((p) => src.includes(p)))\n                    bad.push(`Row ${i + 1}: \"${c}\" is not in the source.`);\n            });\n            return bad.length\n                ? { pass: false, reason: bad.join(' ') }\n                : { pass: true, reason: 'Every quote was found in the source.' };\n        }\n        case 'numbers_in_source': {\n            const nums = (s) => (String(s ?? '').match(/\\d[\\d,]*(?:\\.\\d+)?/g) ?? []).map((n) => n.replace(/,/g, ''));\n            const src = new Set(nums(sources[value.input] ?? ''));\n            const missing = [...new Set(nums(work))].filter((n) => !src.has(n));\n            return missing.length\n                ? {\n                    pass: false,\n                    reason: `These numbers are not in the source: ${missing.join(', ')}.`,\n                }\n                : { pass: true, reason: 'Every number was found in the source.' };\n        }\n        default:\n            return { pass: false, reason: `Unknown rule \"${rule}\".` };\n    }\n};\n\nconst work = String($(\"4. Write the file you paste into your AI tool\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"max_words\", 350, work, sources);\nreturn [{ json: result }];"
      },
      "id": "ffa4e219-1079-48fc-a827-f4f3fcf8cdce",
      "name": "Check 4: The file is 350 words or fewer",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3080,
        320
      ],
      "notes": "The answer is no longer than 350 words.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "055ba563-15a7-4070-a058-a13d59e23702",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "11fbc736-b0b6-4487-ac7e-83d0fbd12749",
      "name": "Passed check 4?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3340,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "47720d98-cbca-42b3-a505-5c627b5e1415",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "d95bbfa8-1954-4873-a49d-b72294e90695",
      "name": "Try again? (check 4)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3340,
        520
      ],
      "notes": "True = first failure: send back to \"4. Write the file you paste into your AI tool\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "4943d535-147d-40ee-af8f-529bf56c5e44",
              "name": "flag",
              "value": "=The file is 350 words or fewer: did not pass. {{ $(\"Check 4: The file is 350 words or fewer\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "95e4365f-1147-4dc0-a2b2-8ea1e2b9535a",
      "name": "Flag check 4",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        3470,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are a careful reviewer. You do not fix or rewrite the work; you only decide whether it passes one check.\n\nThe check: Every rule is in the file\nCompare the file with the final rules below. Is every rule in the table present in the file, with the same meaning, and does the file add no rule that is not in the table?\n\nThe final rules:\n{{ $(\"3. Fix the rules that failed\").first().json.text }}\n\nThe work to check (the answer from Step 4 (Write the file you paste into your AI tool)):\n{{ $(\"4. Write the file you paste into your AI tool\").first().json.text }}\n\nReply with exactly PASS or FAIL on the first line. On the second line, say in one sentence why. If it fails, name the exact item or line that fails. Do not add anything else."
      },
      "id": "f315b6a5-6ada-45c6-a3db-e6abf90dd94c",
      "name": "Check 5: Every rule is in the file",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        3600,
        320
      ],
      "notes": "Answers PASS or FAIL. Compare the file with the final rules below. Is every rule in the table present in the file, with the same meaning, and does the file add no rule that is not in the table?",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "0a5bad42-1ca7-4b75-a958-963854e22f23",
              "leftValue": "={{ String($json.text ?? '').trim().toUpperCase().startsWith('PASS') }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "91f8bf1f-b0b9-431e-ac7b-7bb9fbd365e0",
      "name": "Passed check 5?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3860,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "4e72189d-d0ca-4a92-a005-5483745e0910",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "6558ca75-3d57-4fb6-a09b-3fdf88eb3248",
      "name": "Try again? (check 5)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3860,
        520
      ],
      "notes": "True = first failure: send back to \"4. Write the file you paste into your AI tool\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "4243ca30-177d-45a7-a48f-5a7af46c5cb1",
              "name": "flag",
              "value": "=Every rule is in the file: did not pass. {{ $(\"Check 5: Every rule is in the file\").first().json.text }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "94e434cc-1247-4f53-a1b2-8d0ee3b954ed",
      "name": "Flag check 5",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        3990,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Each rule was tested on the sample\n// Rule: Every row of the table has something written in \"Followed\" and \"What I saw\" (a blank or a dash does not count).\n// Runs with no AI. Returns pass (true or false) and a reason.\nconst runRule = function runRule(rule, value, work, sources) {\n    const norm = (s) => String(s ?? '')\n        .toLowerCase()\n        .replace(/[\\u2018\\u2019\\u201c\\u201d\"'`]/g, '')\n        .replace(/\\s+/g, ' ')\n        .trim();\n    const readTable = (text) => {\n        const lines = String(text ?? '')\n            .split(/\\r?\\n/)\n            .map((l) => l.trim());\n        const tables = [];\n        let cur = null;\n        const cells = (l) => l\n            .replace(/^\\|/, '')\n            .replace(/\\|$/, '')\n            .split('|')\n            .map((c) => c.trim());\n        for (let i = 0; i < lines.length; i++) {\n            const l = lines[i] ?? '';\n            if (!l.startsWith('|')) {\n                cur = null;\n                continue;\n            }\n            if (!cur) {\n                const next = lines[i + 1] ?? '';\n                if (/^\\|?\\s*:?-{2,}/.test(next)) {\n                    cur = { header: cells(l), rows: [] };\n                    tables.push(cur);\n                    i++;\n                }\n                continue;\n            }\n            cur.rows.push(cells(l));\n        }\n        return tables;\n    };\n    // Every table that has this column, with \"None found\" rows left out.\n    // Returns that column's cells from every such table, or null if no table has it.\n    const findColumn = (name) => {\n        let seen = false;\n        const cells = [];\n        for (const t of readTable(work)) {\n            const i = t.header.findIndex((h) => norm(h) === norm(name));\n            if (i < 0)\n                continue;\n            seen = true;\n            for (const r of t.rows) {\n                if (r.some((c) => norm(c) === 'none found'))\n                    continue;\n                cells.push(r[i] ?? '');\n            }\n        }\n        return seen ? cells : null;\n    };\n    const empty = (c) => !c || /^[-\\u2013\\u2014\\s]*$/.test(c);\n    switch (rule) {\n        case 'has_text':\n            return norm(work).includes(norm(value))\n                ? { pass: true, reason: 'Found.' }\n                : { pass: false, reason: `The answer does not include \"${value}\".` };\n        case 'lacks_text':\n            return norm(work).includes(norm(value))\n                ? { pass: false, reason: `The answer includes \"${value}\", which it should not.` }\n                : { pass: true, reason: 'Not found, as it should be.' };\n        case 'max_words': {\n            const n = String(work ?? '')\n                .split(/\\s+/)\n                .filter(Boolean).length;\n            return n <= Number(value)\n                ? { pass: true, reason: `${n} words.` }\n                : { pass: false, reason: `The answer has ${n} words; the limit is ${value}.` };\n        }\n        case 'table_cells_filled': {\n            const problems = [];\n            for (const col of value) {\n                const f = findColumn(col);\n                if (!f) {\n                    problems.push(`No table with a \"${col}\" column was found.`);\n                    continue;\n                }\n                f.forEach((c, i) => {\n                    if (empty(c))\n                        problems.push(`Row ${i + 1} has nothing in \"${col}\".`);\n                });\n            }\n            return problems.length\n                ? { pass: false, reason: problems.join(' ') }\n                : { pass: true, reason: 'Every row is filled in.' };\n        }\n        case 'column_values_in': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            const allowed = value.allowed.map(norm);\n            const bad = [];\n            f.forEach((c, i) => {\n                if (!allowed.includes(norm(c)))\n                    bad.push(`Row ${i + 1} says \"${c}\".`);\n            });\n            return bad.length\n                ? {\n                    pass: false,\n                    reason: `The \"${value.column}\" column must say ${value.allowed.join(' or ')}. ${bad.join(' ')}`,\n                }\n                : { pass: true, reason: 'Every row uses an allowed word.' };\n        }\n        case 'column_quotes_source': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            const src = norm(sources[value.input] ?? '');\n            const bad = [];\n            f.forEach((c, i) => {\n                // A quote may be shortened with \"...\"; each piece must be in the source.\n                const pieces = c\n                    .split(/\\.\\.\\.|\\u2026/)\n                    .map(norm)\n                    .filter(Boolean);\n                if (!pieces.length || !pieces.every((p) => src.includes(p)))\n                    bad.push(`Row ${i + 1}: \"${c}\" is not in the source.`);\n            });\n            return bad.length\n                ? { pass: false, reason: bad.join(' ') }\n                : { pass: true, reason: 'Every quote was found in the source.' };\n        }\n        case 'numbers_in_source': {\n            const nums = (s) => (String(s ?? '').match(/\\d[\\d,]*(?:\\.\\d+)?/g) ?? []).map((n) => n.replace(/,/g, ''));\n            const src = new Set(nums(sources[value.input] ?? ''));\n            const missing = [...new Set(nums(work))].filter((n) => !src.has(n));\n            return missing.length\n                ? {\n                    pass: false,\n                    reason: `These numbers are not in the source: ${missing.join(', ')}.`,\n                }\n                : { pass: true, reason: 'Every number was found in the source.' };\n        }\n        default:\n            return { pass: false, reason: `Unknown rule \"${rule}\".` };\n    }\n};\n\nconst work = String($(\"2. Try each rule on your sample task\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"table_cells_filled\", [\"Followed\",\"What I saw\"], work, sources);\nreturn [{ json: result }];"
      },
      "id": "cff10522-8815-402f-a630-8d08d6ffb38d",
      "name": "Check 6: Each rule was tested on the sample",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        4120,
        320
      ],
      "notes": "Every row of the table has something written in \"Followed\" and \"What I saw\" (a blank or a dash does not count).",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "0b5baed5-1ba7-49e2-aa58-97cb53e22d90",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "91f68088-30b1-4dd9-ac83-00fefbd5a477",
      "name": "Passed check 6?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        4380,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "4d72170a-d1ca-4c25-af05-52f0755e0aa3",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "3d564ce6-2559-4885-a0a2-c52408e3ad03",
      "name": "Try again? (check 6)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        4380,
        520
      ],
      "notes": "True = first failure: send back to \"2. Try each rule on your sample task\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "4343cbc3-167d-4414-a58f-5c0df36c5b1e",
              "name": "flag",
              "value": "=Each rule was tested on the sample: did not pass. {{ $(\"Check 6: Each rule was tested on the sample\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "97e43985-1347-40e6-a0b2-8b7be0b95034",
      "name": "Flag check 6",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        4510,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "content": "**Check 7: check by hand**\nRead the file. Does each rule say something you really want? Cross out any you would not miss, then try the file in a new chat on a real task and see if the answer feels right.\n\nIf not: flag it and carry on. This check is also listed in Result.",
        "height": 200,
        "width": 240,
        "color": 3
      },
      "id": "d07607e7-8d33-45a2-a674-3579d74d974c",
      "name": "Check 7 (by hand)",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        4360,
        70
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "cfe0fbaf-38b8-411e-aebe-1b9dff45e07c",
              "name": "result",
              "value": "={{ $(\"4. Write the file you paste into your AI tool\").first().json.text }}",
              "type": "string"
            },
            {
              "id": "27635b5c-36ef-462f-aa12-b882c9a34695",
              "name": "flagged",
              "value": "={{ [$(\"Flag check 1\").isExecuted ? $(\"Flag check 1\").first().json.flag : \"\", $(\"Flag check 2\").isExecuted ? $(\"Flag check 2\").first().json.flag : \"\", $(\"Flag check 3\").isExecuted ? $(\"Flag check 3\").first().json.flag : \"\", $(\"Flag check 4\").isExecuted ? $(\"Flag check 4\").first().json.flag : \"\", $(\"Flag check 5\").isExecuted ? $(\"Flag check 5\").first().json.flag : \"\", $(\"Flag check 6\").isExecuted ? $(\"Flag check 6\").first().json.flag : \"\"].filter(x => x).join(\"\\n\") || \"None\" }}",
              "type": "string"
            },
            {
              "id": "87bcba6a-ac0e-4ec5-aa23-d070b98a5a2b",
              "name": "check_by_hand",
              "value": "- Read the file. Does each rule say something you really want? Cross out any you would not miss, then try the file in a new chat on a real task and see if the answer feels right.",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "2883af58-c15f-4adb-ada1-a186772e55f1",
      "name": "Result",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        4640,
        320
      ],
      "notes": "The final answer, anything flagged, and the checks left for you.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "671cf73a-ec26-4581-ab47-c75072053da7",
      "name": "Chat model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.2,
      "position": [
        480,
        840
      ],
      "notes": "Attach your credential here, or swap in any chat model node.",
      "notesInFlow": true
    }
  ],
  "connections": {
    "Start": {
      "main": [
        [
          {
            "node": "Inputs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Inputs": {
      "main": [
        [
          {
            "node": "1. Turn your answers into rules",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1. Turn your answers into rules": {
      "main": [
        [
          {
            "node": "Check 1: Every rule has a reason and a test",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 1: Every rule has a reason and a test": {
      "main": [
        [
          {
            "node": "Passed check 1?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 1?": {
      "main": [
        [
          {
            "node": "2. Try each rule on your sample task",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 1)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 1)": {
      "main": [
        [
          {
            "node": "1. Turn your answers into rules",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 1": {
      "main": [
        [
          {
            "node": "2. Try each rule on your sample task",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. Try each rule on your sample task": {
      "main": [
        [
          {
            "node": "Check 2: Each rule is marked yes or no",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 2: Each rule is marked yes or no": {
      "main": [
        [
          {
            "node": "Passed check 2?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 2?": {
      "main": [
        [
          {
            "node": "3. Fix the rules that failed",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 2)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 2)": {
      "main": [
        [
          {
            "node": "2. Try each rule on your sample task",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 2": {
      "main": [
        [
          {
            "node": "3. Fix the rules that failed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3. Fix the rules that failed": {
      "main": [
        [
          {
            "node": "Check 3: Every row says if it changed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 3: Every row says if it changed": {
      "main": [
        [
          {
            "node": "Passed check 3?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 3?": {
      "main": [
        [
          {
            "node": "4. Write the file you paste into your AI tool",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 3)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 3)": {
      "main": [
        [
          {
            "node": "3. Fix the rules that failed",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 3": {
      "main": [
        [
          {
            "node": "4. Write the file you paste into your AI tool",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4. Write the file you paste into your AI tool": {
      "main": [
        [
          {
            "node": "Check 4: The file is 350 words or fewer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 4: The file is 350 words or fewer": {
      "main": [
        [
          {
            "node": "Passed check 4?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 4?": {
      "main": [
        [
          {
            "node": "Check 5: Every rule is in the file",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 4)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 4)": {
      "main": [
        [
          {
            "node": "4. Write the file you paste into your AI tool",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 4": {
      "main": [
        [
          {
            "node": "Check 5: Every rule is in the file",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 5: Every rule is in the file": {
      "main": [
        [
          {
            "node": "Passed check 5?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 5?": {
      "main": [
        [
          {
            "node": "Check 6: Each rule was tested on the sample",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 5)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 5)": {
      "main": [
        [
          {
            "node": "4. Write the file you paste into your AI tool",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 5",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 5": {
      "main": [
        [
          {
            "node": "Check 6: Each rule was tested on the sample",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 6: Each rule was tested on the sample": {
      "main": [
        [
          {
            "node": "Passed check 6?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 6?": {
      "main": [
        [
          {
            "node": "Result",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 6)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 6)": {
      "main": [
        [
          {
            "node": "2. Try each rule on your sample task",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 6",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 6": {
      "main": [
        [
          {
            "node": "Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Chat model": {
      "ai_languageModel": [
        [
          {
            "node": "1. Turn your answers into rules",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "2. Try each rule on your sample task",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "3. Fix the rules that failed",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "4. Write the file you paste into your AI tool",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Check 5: Every rule is in the file",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "template": "how-i-like-work-done"
  }
}
