{
  "name": "Scope a job before you start",
  "nodes": [
    {
      "parameters": {
        "content": "## Scope a job before you start\nList everything a job could involve, decide what is in and out, say what done looks like in a way you can check by looking, and set when to stop, all on one page.\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 one of \"stated\" or \"assumed\" in the Source column. 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 \"in\", \"out\" or \"later\" in the Decision 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 something written in \"Done means\" and \"How to check by looking\" (a blank or a dash does not count). 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 300 words. If it fails: try once more, then flag.\n- Check 5 (a second AI prompt answers pass or fail): Read the \"How to check by looking\" column. Can each check be done by looking at, counting or opening something, with no judgment about quality? Answer FAIL and name any row that uses a feeling word such as good, clear, tidy or ready instead of something you can see. If it fails: try once more, then flag.\n- Check 6 (a second AI prompt answers pass or fail): Compare the scope note with the scope decisions below. Is every piece marked \"in\" named in the note, and is every piece marked \"out\" or \"later\" shown as not included? If it fails: try once more, then flag.\n- Check 7 (you check it by hand): Read the note as the person who asked. Would they agree with what is left out? If not, fix the Decision column and run the last two steps again. If it fails: flag it and carry on.\n\nFrom Logic Lab (logiclabhq.com). Free to use and adapt, with credit.",
        "height": 740,
        "width": 760,
        "color": 5
      },
      "id": "a10e5230-002f-49b3-ac34-6edad81b8f95",
      "name": "About this workflow",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -40,
        -700
      ]
    },
    {
      "parameters": {},
      "id": "fbc29f6c-8065-4313-a311-e35edffb611d",
      "name": "Start",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        320
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "fbd8ca28-6f9f-4bab-a9c8-dc9e28b30981",
              "name": "job",
              "value": "Sample: Our manager asked me to \"sort out the shared drive\" before the end of the month. It is a mess. People can't find things and there are old files everywhere.",
              "type": "string"
            },
            {
              "id": "892378e9-7961-4f78-a49d-34e3989b378a",
              "name": "limits",
              "value": "Sample: About two hours a week for three weeks, deadline 31 October, no help",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "79f6cfc5-e589-424c-ae49-4db355df3ef2",
      "name": "Inputs",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        220,
        320
      ],
      "notes": "Paste your own material here: job = The job as it was given to you; limits = Time and help you have.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are an experienced project coordinator who makes vague requests concrete.\n\nYour task is to list every piece of work the job below could involve, from the obvious to the easy to forget.\n\nThe job:\n{{ $(\"Inputs\").first().json[\"job\"] }}\n\nTime and help available: {{ $(\"Inputs\").first().json[\"limits\"] }}\n\nRules:\n- Write each piece as a short action, such as \"Delete files older than five years\".\n- Mark each piece \"stated\" if the request clearly names it, or \"assumed\" if you are adding it as something someone might expect.\n- Include the pieces nobody mentions: talking to the people affected, testing, telling people it is done.\n- List 6 to 15 pieces. Do not decide yet what is in or out. Do not estimate time yet.\n\nReturn one markdown table and nothing else:\n| # | Piece of work | Source |\nThe Source column must say exactly stated or assumed.{{ $(\"Check 1: Each piece is marked stated or assumed\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Each piece is marked stated or assumed. What was wrong: \" + $(\"Check 1: Each piece is marked stated or assumed\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "9ce8517e-9202-4ea3-af21-2960baea71c5",
      "name": "1. List everything the job could involve",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        480,
        320
      ],
      "notes": "Write out every piece of work the job might include, marking which ones the request actually names. Returns: A numbered table of every piece of work, each marked stated or assumed.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Each piece is marked stated or assumed\n// Rule: Every row of the table has one of \"stated\" or \"assumed\" in the Source 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($(\"1. List everything the job could involve\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"column_values_in\", {\"column\":\"Source\",\"allowed\":[\"stated\",\"assumed\"]}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "0dd5c300-056b-4103-a129-95625cf9d695",
      "name": "Check 1: Each piece is marked stated or assumed",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        740,
        320
      ],
      "notes": "Every row of the table has one of \"stated\" or \"assumed\" in the Source column.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "41e9d3e7-cdbc-42ee-a44e-e8056210e3ec",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "6ad07aac-1d4e-44cb-ad5c-6d460e79c895",
      "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": "7af2d9d4-6afc-4d69-ad9b-cbe243d3cc97",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "89144384-1006-4ce1-a4d9-5dfeac332f83",
      "name": "Try again? (check 1)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1000,
        520
      ],
      "notes": "True = first failure: send back to \"1. List everything the job could involve\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "2ba52305-1616-4944-aaae-bcf7a7fb0606",
              "name": "flag",
              "value": "=Each piece is marked stated or assumed: did not pass. {{ $(\"Check 1: Each piece is marked stated or assumed\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "4079d139-6f31-4a58-a6ba-ea3bceb4c342",
      "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 a practical coordinator who protects people from taking on too much.\n\nYour task is to decide, for every piece of work below, whether it is in scope, out of scope or for later.\n\nThe pieces:\n{{ $(\"1. List everything the job could involve\").first().json.text }}\n\nThe job as given: {{ $(\"Inputs\").first().json[\"job\"] }}\n\nTime and help available: {{ $(\"Inputs\").first().json[\"limits\"] }}\n\nRules:\n- Mark \"in\" only the pieces that can reasonably be done in the time available and that the request names or clearly needs.\n- Mark \"later\" what is worth doing but will not fit. Mark \"out\" what is not part of this job.\n- Give a reason for every row, in a short phrase.\n- Do not drop any piece. Do not add new pieces.\n\nReturn one markdown table and nothing else:\n| # | Piece of work | Decision | Reason |\nThe Decision column must say exactly in, out or later.{{ $(\"Check 2: Each decision is in, out or later\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Each decision is in, out or later. What was wrong: \" + $(\"Check 2: Each decision is in, out or later\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "5edc2b66-c02d-4047-a8df-9f1c9e05bcad",
      "name": "2. Decide what is in, out or later",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        1260,
        320
      ],
      "notes": "Put every piece in, out or later, with a reason, using the time and help you have. Returns: A table that puts every piece in, out or later, with a reason.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Each decision is in, out or later\n// Rule: Every row of the table has one of \"in\", \"out\" or \"later\" in the Decision 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. Decide what is in, out or later\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"column_values_in\", {\"column\":\"Decision\",\"allowed\":[\"in\",\"out\",\"later\"]}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "e0ceb5ed-2269-4998-a195-11cb7ea8075e",
      "name": "Check 2: Each decision is in, out or later",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1520,
        320
      ],
      "notes": "Every row of the table has one of \"in\", \"out\" or \"later\" in the Decision column.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "40e9d254-cebc-4481-a34e-e6726310e57f",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "6ad2b943-1d4c-4634-ad54-e8018e814dda",
      "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": "7bf2db67-69fc-4bd6-ae9b-cd7542d3cb04",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "090cbe3f-9803-4162-acdb-db8d2c3ab4c8",
      "name": "Try again? (check 2)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1780,
        520
      ],
      "notes": "True = first failure: send back to \"2. Decide what is in, out or later\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "2aa52172-1716-4ad7-a9ae-bb64a8fb0799",
              "name": "flag",
              "value": "=Each decision is in, out or later: did not pass. {{ $(\"Check 2: Each decision is in, out or later\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "3d79cc80-7231-4f11-a7ba-ebcecdb4c1af",
      "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 a quality-minded coordinator who makes finished work easy to recognize.\n\nYour task is to write, for every piece marked \"in\" below, what done means and how someone could check it just by looking.\n\nThe scope decisions:\n{{ $(\"2. Decide what is in, out or later\").first().json.text }}\n\nRules:\n- A check must be something you can see, count or open: \"The folder has 10 or fewer top-level folders\" is a check. \"The drive is tidy\" is not.\n- Do not use words like good, clear, tidy, better or ready on their own. Say what you would see.\n- Write one row for every piece marked \"in\" and no rows for other pieces.\n- Do not change the scope decisions.\n\nReturn one markdown table and nothing else:\n| # | Piece of work | Done means | How to check by looking |{{ $(\"Check 3: Every piece has a way to check it\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every piece has a way to check it. What was wrong: \" + $(\"Check 3: Every piece has a way to check it\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 5: Every done check can be made by looking\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every done check can be made by looking. What was wrong: \" + $(\"Check 5: Every done check can be made by looking\").first().json.text + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "f27c1ac8-5c4c-4321-ab77-f51247da69fb",
      "name": "3. Define done for each piece that is in",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        2040,
        320
      ],
      "notes": "For each piece marked in, write what done means and how to see that it is done. Returns: A table that says what done means for each piece in scope and how to check it by looking.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Every piece has a way to check it\n// Rule: Every row of the table has something written in \"Done means\" and \"How to check by looking\" (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($(\"3. Define done for each piece that is in\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"table_cells_filled\", [\"Done means\",\"How to check by looking\"], work, sources);\nreturn [{ json: result }];"
      },
      "id": "6d0b3fbe-1551-418f-a1ea-670c8dd0f795",
      "name": "Check 3: Every piece has a way to check it",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2300,
        320
      ],
      "notes": "Every row of the table has something written in \"Done means\" and \"How to check by looking\" (a blank or a dash does not count).",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "43e9d70d-cbbc-4fc8-a24e-e4df6410e712",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "ead5c15a-9d49-4e1d-ad57-26980e7e45c3",
      "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": "7cf2dcfa-68fc-4a43-ab9b-c8bc45d3cfbd",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "2d0f3582-7400-4a1f-a0de-9e6038388915",
      "name": "Try again? (check 3)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2560,
        520
      ],
      "notes": "True = first failure: send back to \"3. Define done for each piece that is in\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "29a51fdf-1816-4c6a-acae-c01da5fb02e0",
              "name": "flag",
              "value": "=Every piece has a way to check it: did not pass. {{ $(\"Check 3: Every piece has a way to check it\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "3e79ce13-7131-4d7e-a8ba-ed61ccb4c01c",
      "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 a coordinator who helps people stop jobs from growing.\n\nYour task is to write the stopping rules for the job below.\n\nThe job:\n{{ $(\"Inputs\").first().json[\"job\"] }}\n\nTime and help available: {{ $(\"Inputs\").first().json[\"limits\"] }}\n\nWhat is in scope:\n{{ $(\"3. Define done for each piece that is in\").first().json.text }}\n\nRules:\n- Write three kinds of rule: a time limit (when to stop even if it is not finished), a size limit (what to do if the job turns out to be bigger than expected), and a question rule (what to ask, and who, before going further).\n- Base the limits on the time and help given. Do not invent a deadline or a person.\n- Do not write more than six lines in all.\n\nReturn a short list with three headings: Time limit, Size limit, Ask before going further."
      },
      "id": "fac51911-6b7f-4cf0-a93d-13dbd52ab7da",
      "name": "4. Decide when to stop",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        2820,
        320
      ],
      "notes": "Set the limits that tell you when to stop and ask, so the job does not grow. Returns: Three short stopping rules (a time limit, a size limit and when to ask).",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are a coordinator writing a short note that gets agreement before work starts.\n\nYour task is to combine the work below into one scope note that the person who asked can read in two minutes.\n\nThe scope decisions:\n{{ $(\"2. Decide what is in, out or later\").first().json.text }}\n\nWhat done looks like:\n{{ $(\"3. Define done for each piece that is in\").first().json.text }}\n\nThe stopping rules:\n{{ $(\"4. Decide when to stop\").first().json.text }}\n\nRules:\n- Use plain words. Keep it to 300 words or fewer.\n- List \"out\" and \"later\" items so the reader sees what is not included.\n- End with one line asking the reader to confirm or change the scope.\n- Do not add items that are not in the work above.\n\nReturn markdown in this shape and nothing else:\n## The job\n## In scope\n## Not included\n## Done means\n## When I will stop\n## Please confirm{{ $(\"Check 4: The note is 300 words or fewer\").isExecuted ? \"\\n\\nYour last answer did not pass this check: The note is 300 words or fewer. What was wrong: \" + $(\"Check 4: The note is 300 words or fewer\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 6: Every in-scope piece reaches the note\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every in-scope piece reaches the note. What was wrong: \" + $(\"Check 6: Every in-scope piece reaches the note\").first().json.text + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "38cba3c3-9188-496a-a2f0-ba658b813e04",
      "name": "5. Write the one-page scope note",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        3080,
        320
      ],
      "notes": "Put the scope, the done checks and the stopping rules on one short page you can share. Returns: A one-page scope note the person who asked can confirm.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// The note is 300 words or fewer\n// Rule: The answer is no longer than 300 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($(\"5. Write the one-page scope note\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"max_words\", 300, work, sources);\nreturn [{ json: result }];"
      },
      "id": "cc706e9b-8af4-49a0-a3bb-1ed93bcfba3e",
      "name": "Check 4: The note is 300 words or fewer",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3340,
        320
      ],
      "notes": "The answer is no longer than 300 words.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "42e9d57a-ccbc-415b-a14e-e34c6510e8a5",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "ead7fff1-9d5a-473e-ad4f-a1538e85cb08",
      "name": "Passed check 4?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3600,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "7df2de8d-67fc-48b0-ac9b-ca4f44d3ce2a",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "8d077ddd-8bfe-4150-a8e1-1befb8400e5a",
      "name": "Try again? (check 4)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3600,
        520
      ],
      "notes": "True = first failure: send back to \"5. Write the one-page scope note\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "28a51e4c-1916-4dfd-abae-be8aa6fb0473",
              "name": "flag",
              "value": "=The note is 300 words or fewer: did not pass. {{ $(\"Check 4: The note is 300 words or fewer\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "4379d5f2-7431-4237-a9ba-eef4cbb4be89",
      "name": "Flag check 4",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        3730,
        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 done check can be made by looking\nRead the \"How to check by looking\" column. Can each check be done by looking at, counting or opening something, with no judgment about quality? Answer FAIL and name any row that uses a feeling word such as good, clear, tidy or ready instead of something you can see.\n\nThe work to check (the answer from Step 3 (Define done for each piece that is in)):\n{{ $(\"3. Define done for each piece that is in\").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": "fb9ef7ac-f28b-4a35-a406-85fa3ee3881b",
      "name": "Check 5: Every done check can be made by looking",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        3860,
        320
      ],
      "notes": "Answers PASS or FAIL. Read the \"How to check by looking\" column. Can each check be done by looking at, counting or opening something, with no judgment about quality? Answer FAIL and name any row that uses a feeling word such as good, clear, tidy or ready instead of something you can see.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "3de9cd9b-d1bc-493a-a04e-e1b95e10dda0",
              "leftValue": "={{ String($json.text ?? '').trim().toUpperCase().startsWith('PASS') }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "eada3e88-9d58-48a7-ad52-a96a8e838c71",
      "name": "Passed check 5?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        4120,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "76f2d388-6efd-43b5-a99b-c5963fd3c64b",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "a109dbf0-77fc-433d-ace3-2e72943d9717",
      "name": "Try again? (check 5)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        4120,
        520
      ],
      "notes": "True = first failure: send back to \"3. Define done for each piece that is in\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "27a51cb9-1216-42f8-a6ae-b6ababfb0c52",
              "name": "flag",
              "value": "=Every done check can be made by looking: did not pass. {{ $(\"Check 5: Every done check can be made by looking\").first().json.text }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "4479d785-7331-40a4-aaba-f087cab4bcf6",
      "name": "Flag check 5",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        4250,
        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 in-scope piece reaches the note\nCompare the scope note with the scope decisions below. Is every piece marked \"in\" named in the note, and is every piece marked \"out\" or \"later\" shown as not included?\n\nThe scope decisions:\n{{ $(\"2. Decide what is in, out or later\").first().json.text }}\n\nThe work to check (the answer from Step 5 (Write the one-page scope note)):\n{{ $(\"5. Write the one-page scope note\").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": "3f5fbfaf-dfe0-4e5e-a635-54997d5a5a48",
      "name": "Check 6: Every in-scope piece reaches the note",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        4380,
        320
      ],
      "notes": "Answers PASS or FAIL. Compare the scope note with the scope decisions below. Is every piece marked \"in\" named in the note, and is every piece marked \"out\" or \"later\" shown as not included?",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "3ce9cc08-d2bc-4acd-af4e-e0265f10df33",
              "leftValue": "={{ String($json.text ?? '').trim().toUpperCase().startsWith('PASS') }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "eadc7d1f-9d56-4a10-ad4b-24250e8b11b6",
      "name": "Passed check 6?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        4640,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "77f2d51b-6dfd-4222-aa9b-c7293ed3c4b8",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "8102edcb-0ff9-40ee-a4e6-29f1b444853c",
      "name": "Try again? (check 6)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        4640,
        520
      ],
      "notes": "True = first failure: send back to \"5. Write the one-page scope note\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "26a51b26-1316-448b-a5ae-b518acfb0de5",
              "name": "flag",
              "value": "=Every in-scope piece reaches the note: did not pass. {{ $(\"Check 6: Every in-scope piece reaches the note\").first().json.text }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "4179d2cc-7631-455d-abba-f21ac9b4bb63",
      "name": "Flag check 6",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        4770,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "content": "**Check 7: check by hand**\nRead the note as the person who asked. Would they agree with what is left out? If not, fix the Decision column and run the last two steps again.\n\nIf not: flag it and carry on. This check is also listed in Result.",
        "height": 200,
        "width": 240,
        "color": 3
      },
      "id": "7e158c94-e348-476f-a9ef-1d16a4781a51",
      "name": "Check 7 (by hand)",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        4620,
        70
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "e73bb8c4-7254-44d7-a418-6d124d479515",
              "name": "result",
              "value": "={{ $(\"5. Write the one-page scope note\").first().json.text }}",
              "type": "string"
            },
            {
              "id": "f392776d-9bc6-41f4-a230-0ccbbc2eef8a",
              "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": "7d7fee63-821a-4002-a9bc-6b6139f6c678",
              "name": "check_by_hand",
              "value": "- Read the note as the person who asked. Would they agree with what is left out? If not, fix the Decision column and run the last two steps again.",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "585e02a9-f88c-4a90-a399-12472ff2f60e",
      "name": "Result",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        4900,
        320
      ],
      "notes": "The final answer, anything flagged, and the checks left for you.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "1fb4d58f-d240-4e42-aa6a-571dc4ddf9c8",
      "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. List everything the job could involve",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1. List everything the job could involve": {
      "main": [
        [
          {
            "node": "Check 1: Each piece is marked stated or assumed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 1: Each piece is marked stated or assumed": {
      "main": [
        [
          {
            "node": "Passed check 1?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 1?": {
      "main": [
        [
          {
            "node": "2. Decide what is in, out or later",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 1)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 1)": {
      "main": [
        [
          {
            "node": "1. List everything the job could involve",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 1": {
      "main": [
        [
          {
            "node": "2. Decide what is in, out or later",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. Decide what is in, out or later": {
      "main": [
        [
          {
            "node": "Check 2: Each decision is in, out or later",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 2: Each decision is in, out or later": {
      "main": [
        [
          {
            "node": "Passed check 2?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 2?": {
      "main": [
        [
          {
            "node": "3. Define done for each piece that is in",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 2)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 2)": {
      "main": [
        [
          {
            "node": "2. Decide what is in, out or later",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 2": {
      "main": [
        [
          {
            "node": "3. Define done for each piece that is in",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3. Define done for each piece that is in": {
      "main": [
        [
          {
            "node": "Check 3: Every piece has a way to check it",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 3: Every piece has a way to check it": {
      "main": [
        [
          {
            "node": "Passed check 3?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 3?": {
      "main": [
        [
          {
            "node": "4. Decide when to stop",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 3)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 3)": {
      "main": [
        [
          {
            "node": "3. Define done for each piece that is in",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 3": {
      "main": [
        [
          {
            "node": "4. Decide when to stop",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4. Decide when to stop": {
      "main": [
        [
          {
            "node": "5. Write the one-page scope note",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5. Write the one-page scope note": {
      "main": [
        [
          {
            "node": "Check 4: The note is 300 words or fewer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 4: The note is 300 words or fewer": {
      "main": [
        [
          {
            "node": "Passed check 4?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 4?": {
      "main": [
        [
          {
            "node": "Check 5: Every done check can be made by looking",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 4)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 4)": {
      "main": [
        [
          {
            "node": "5. Write the one-page scope note",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 4": {
      "main": [
        [
          {
            "node": "Check 5: Every done check can be made by looking",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 5: Every done check can be made by looking": {
      "main": [
        [
          {
            "node": "Passed check 5?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 5?": {
      "main": [
        [
          {
            "node": "Check 6: Every in-scope piece reaches the note",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 5)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 5)": {
      "main": [
        [
          {
            "node": "3. Define done for each piece that is in",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 5",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 5": {
      "main": [
        [
          {
            "node": "Check 6: Every in-scope piece reaches the note",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 6: Every in-scope piece reaches the note": {
      "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": "5. Write the one-page scope note",
            "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. List everything the job could involve",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "2. Decide what is in, out or later",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "3. Define done for each piece that is in",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "4. Decide when to stop",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "5. Write the one-page scope note",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Check 5: Every done check can be made by looking",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Check 6: Every in-scope piece reaches the note",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "template": "scope-a-job"
  }
}
