{
  "name": "Run a lessons-learned review",
  "nodes": [
    {
      "parameters": {
        "content": "## Run a lessons-learned review\nTurn your notes on a finished project or event into a blame-free review, with what happened, what went well, what got in the way and why, one change to try for each problem, and the questions the notes cannot answer.\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 quote in the Quote column appears word for word in the what happened. 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 \"went well\" or \"got in the way\" in the Effect 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 \"Change to try\", \"Owner (a role)\" and \"How we will know\" (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 includes the words \"Check this draft with the people who were there before you share it.\". If it fails: try once more, then flag.\n- Check 5 (a plain rule, run automatically with no AI): Every number in the answer also appears in the what happened. If it fails: try once more, then flag.\n- Check 6 (a second AI prompt answers pass or fail): Read the review in the answer. Does any line name a person as the cause, say someone failed, forgot or should have done something, or guess at a person's reasons? Roles are fine when they only say who owns a change. Answer FAIL and quote each line that does. If it fails: try once more, then flag.\n- Check 7 (a second AI prompt answers pass or fail): Compare each change in the answer with its cause in the sorted events below. Does each change answer the cause in its own row, and does it say what a person would do, rather than advice such as \"be more careful\" or \"plan better\"? Answer FAIL and quote each change that does not. If it fails: try once more, then flag.\n- Check 8 (you check it by hand): Read the review with the people who were there. Ask each whether it matches what they remember and whether any line reads as blame, and change any line they dispute before you share it. Only they know what the notes left out. If it fails: flag it and carry on.\n\nFrom Logic Lab (logiclabhq.com). Free to use and adapt, with credit.",
        "height": 820,
        "width": 760,
        "color": 5
      },
      "id": "e7704412-3f5c-446b-a7f0-9390111ac321",
      "name": "About this workflow",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -40,
        -780
      ]
    },
    {
      "parameters": {},
      "id": "5e2961a6-56b9-4a7b-a9ee-154c4247c229",
      "name": "Start",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        320
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "50d0cff0-e50d-4ba3-a138-cf5afed91ddd",
              "name": "goal",
              "value": "Sample: Run a plant sale on Saturday, April 25 that raises 1,500 dollars for the community garden",
              "type": "string"
            },
            {
              "id": "3d96f584-ab53-402d-abd4-569e3d555627",
              "name": "notes",
              "value": "Sample: Plant sale review notes, written the week after. The goal was to raise 1,500 dollars.\nMarch 2: plan agreed. Sale on Saturday, April 25, 9 am to 1 pm, at the church hall.\nApril 6: flyers sent to the printer. Marcus, the publicity lead, was told they would be ready by April 15.\nApril 15: Priya, the volunteer coordinator, opened a sign-up sheet and 12 people signed up.\nApril 20: flyers came back, 5 days later than promised. They were handed out only 2 days before the sale.\nApril 22: the hall told Priya the kitchen would be closed on the day. She had booked the hall by phone and had nothing in writing.\nApril 24: the plants arrived from the nursery a day before the sale, not on the morning. The shed was full, so about half the plants stayed outside overnight and some were damaged by the cold.\nApril 25: the tea stand had no hot water. 7 of the 12 volunteers came, and the sign-up sheet did not say what time to arrive. The sale raised 1,180 dollars. Two people counted the cash and it matched the receipts. Several visitors liked the free seed packets.",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "ab26604b-576b-4744-abe8-9825dff9b8de",
      "name": "Inputs",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        220,
        320
      ],
      "notes": "Paste your own material here: goal = What the project set out to do; notes = What happened.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are a calm note-taker who records what happened and nothing more.\n\nYour task is to list the events in the notes in the order they happened.\n\nWhat the project set out to do: {{ $(\"Inputs\").first().json[\"goal\"] }}\n\nThe notes:\n{{ $(\"Inputs\").first().json[\"notes\"] }}\n\nRules:\n- One row for each event. Quote the words about it from the notes, word for word, as short as you can. Do not reword.\n- In \"When\", write the date or time the notes give, or Not in the notes.\n- If the notes give two accounts of the same thing, write one row for each account and begin the second row's \"What happened\" with \"Another account:\". Do not decide which is right.\n- In \"What happened\", write the event as a plain fact. Do not say who was at fault, and do not use words such as failed, forgot, careless, mistake or should have.\n- Do not add an event the notes do not mention.\n\nReturn one markdown table and nothing else:\n| When | What happened | Quote |{{ $(\"Check 1: Every quote is word for word from the notes\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every quote is word for word from the notes. What was wrong: \" + $(\"Check 1: Every quote is word for word from the notes\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "65af49b8-9486-4ae7-a750-43daedab1611",
      "name": "1. Lay out what happened",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        480,
        320
      ],
      "notes": "List the events in the order they happened, each quoted from the notes, with no judgment yet. Returns: A table of the events in order, each quoted from the notes.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Every quote is word for word from the notes\n// Rule: Every quote in the Quote column appears word for word in the what happened.\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. Lay out what happened\").first().json.text ?? '');\nconst sources = {\n  \"notes\": String($(\"Inputs\").first().json[\"notes\"] ?? ''),\n};\nconst result = runRule(\"column_quotes_source\", {\"column\":\"Quote\",\"input\":\"notes\"}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "699e05f3-6959-4c40-a5f3-9cf145f91ec6",
      "name": "Check 1: Every quote is word for word from the notes",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        740,
        320
      ],
      "notes": "Every quote in the Quote column appears word for word in the what happened.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "9ee4bbad-680a-4e36-a100-93873788a020",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "26a1213e-4a4a-4483-adbe-160c3b6f9fe1",
      "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": "83dc786a-6549-4b91-a13b-7cf4d208612b",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "903bb376-2abe-4b99-af32-52e4454c3b0f",
      "name": "Try again? (check 1)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1000,
        520
      ],
      "notes": "True = first failure: send back to \"1. Lay out what happened\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "7766d0af-7bb3-46bc-a872-4325962e2b2a",
              "name": "flag",
              "value": "=Every quote is word for word from the notes: did not pass. {{ $(\"Check 1: Every quote is word for word from the notes\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "30b115c3-17e0-4940-a082-ce1962a47cb6",
      "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 fair reviewer who looks for causes in the situation and never in a person.\n\nYour task is to sort the events that went well from the events that got in the way, and say why each happened.\n\nThe events:\n{{ $(\"1. Lay out what happened\").first().json.text }}\n\nRules:\n- Skip an event that only sets the scene or only states the final result. Keep the ones that helped or hurt.\n- In \"Effect\", use exactly one of these: went well, got in the way.\n- In \"Why it happened\", write what in the situation made it happen, using only what the notes say, such as a missing step, a missing piece of information or a date that slipped. If the notes do not say, write exactly Not in the notes. If two accounts differ, say so and give both.\n- Do not name a person as the cause and do not guess at anyone's reasons. Do not use anything from outside the events.\n\nReturn one markdown table and nothing else:\n| # | What happened | Effect | Why it happened |\nThe Effect column must say exactly went well or got in the way.{{ $(\"Check 2: Each effect is went well or got in the way\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Each effect is went well or got in the way. What was wrong: \" + $(\"Check 2: Each effect is went well or got in the way\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "3adb245d-5dde-4c58-a4a9-79977428a052",
      "name": "2. Sort what went well from what got in the way",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        1260,
        320
      ],
      "notes": "Pick out the events that mattered, mark each one, and say what in the situation made it happen. Returns: A table that marks each event that mattered as went well or got in the way, with the cause from the notes.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Each effect is went well or got in the way\n// Rule: Every row of the table has one of \"went well\" or \"got in the way\" in the Effect 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. Sort what went well from what got in the way\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"column_values_in\", {\"column\":\"Effect\",\"allowed\":[\"went well\",\"got in the way\"]}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "3354a224-08fd-4695-a630-a7b672daac27",
      "name": "Check 2: Each effect is went well or got in the way",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1520,
        320
      ],
      "notes": "Every row of the table has one of \"went well\" or \"got in the way\" in the Effect column.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "9de4ba1a-690a-4fc9-a000-91f43888a1b3",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "a6999bf9-4a48-45ec-adc1-1e233b765ba6",
      "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": "84dc79fd-6449-49fe-a23b-7e87d1085f98",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "783dcc45-d2bb-427a-af2a-cd9fc553c054",
      "name": "Try again? (check 2)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1780,
        520
      ],
      "notes": "True = first failure: send back to \"2. Sort what went well from what got in the way\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "7666cf1c-7cb3-484f-a772-4192972e2cbd",
              "name": "flag",
              "value": "=Each effect is went well or got in the way: did not pass. {{ $(\"Check 2: Each effect is went well or got in the way\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "31b11756-1ae0-4df9-ad82-c96061a47b23",
      "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 practical coach who turns a problem into one small change.\n\nYour task is to write one change to try for each event that got in the way.\n\nThe sorted events:\n{{ $(\"2. Sort what went well from what got in the way\").first().json.text }}\n\nRules:\n- One row for each event marked \"got in the way\", in the same order. Do not write a row for an event that went well.\n- In \"Change to try\", write one action a person could do, starting with a verb. Do not write advice such as \"communicate better\", \"be more careful\" or \"plan more\".\n- In \"Owner (a role)\", write a role named in the notes, such as a lead or coordinator. Do not use a person's name. If the notes name no fitting role, write Not decided.\n- In \"How we will know\", write something that can be seen or counted next time.\n- If the cause is Not in the notes, the change is to find out why before choosing one.\n- Do not put a number, date or name in a change unless it is in the events above.\n- If nothing got in the way, write one row with None found in every cell.\n\nReturn one markdown table and nothing else:\n| # | What got in the way | Change to try | Owner (a role) | How we will know |{{ $(\"Check 3: Every change has an owner and a way to know\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every change has an owner and a way to know. What was wrong: \" + $(\"Check 3: Every change has an owner and a way to know\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 7: Each change fits its cause and names an action\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Each change fits its cause and names an action. What was wrong: \" + $(\"Check 7: Each change fits its cause and names an action\").first().json.text + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "b0908a07-cfec-41c0-a308-c7e51faf4986",
      "name": "3. Turn each problem into a change",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        2040,
        320
      ],
      "notes": "For every event that got in the way, write one change to try, a role to own it and a way to tell it worked. Returns: A table with one change, one owner role and one way to know for each problem.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Every change has an owner and a way to know\n// Rule: Every row of the table has something written in \"Change to try\", \"Owner (a role)\" and \"How we will know\" (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. Turn each problem into a change\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"table_cells_filled\", [\"Change to try\",\"Owner (a role)\",\"How we will know\"], work, sources);\nreturn [{ json: result }];"
      },
      "id": "e045daf1-ccf2-4602-acb1-d70fda123ed0",
      "name": "Check 3: Every change has an owner and a way to know",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2300,
        320
      ],
      "notes": "Every row of the table has something written in \"Change to try\", \"Owner (a role)\" and \"How we will know\" (a blank or a dash does not count).",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "9ce4b887-660a-4b10-a300-96ad3988a346",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "269ca410-4a46-4755-adc3-5cba3b741d0f",
      "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": "81dc7544-6349-486b-a33b-801ad4086451",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "ec40c178-aeb9-4b37-a32d-44e2a1514911",
      "name": "Try again? (check 3)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2560,
        520
      ],
      "notes": "True = first failure: send back to \"3. Turn each problem into a change\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "7966d3d5-7db3-49e2-a672-3fff942e2804",
              "name": "flag",
              "value": "=Every change has an owner and a way to know: did not pass. {{ $(\"Check 3: Every change has an owner and a way to know\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "32b118e9-19e0-4c66-ae82-caf360a47990",
      "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 facilitator who writes up a review so a team can read it together without anyone feeling blamed.\n\nYour task is to write the lessons-learned review from the material below.\n\nWhat the project set out to do: {{ $(\"Inputs\").first().json[\"goal\"] }}\n\nThe sorted events:\n{{ $(\"2. Sort what went well from what got in the way\").first().json.text }}\n\nThe changes:\n{{ $(\"3. Turn each problem into a change\").first().json.text }}\n\nThe notes:\n{{ $(\"Inputs\").first().json[\"notes\"] }}\n\nRules:\n- Start with one line that begins \"Summary:\" and has two sentences: what the project set out to do and how it ended, then the main things that made it harder.\n- Under \"What went well\", write one bullet for each event marked went well. If there are none, write \"- None found.\"\n- Under \"What got in the way\", write one table row for each change, with the problem, why it happened, the change, the owner role and how we will know. Keep the wording of the changes.\n- Under \"Questions the notes do not answer\", write up to three questions, each ending with a question mark, about causes the notes leave open. If there are none, write \"- None found.\"\n- Refer to people only by role or as \"the team\". Do not say anyone failed, forgot or should have done something.\n- Use only facts and numbers from the notes. Do not add a number.\n- End with exactly this line: Check this draft with the people who were there before you share it.\n\nReturn markdown in this form and nothing else:\nSummary: (two sentences)\n## What went well\n(one bullet for each)\n## What got in the way\n| What got in the way | Why it happened | Change to try | Owner (a role) | How we will know |\n## Questions the notes do not answer\n(up to three bullets)\nCheck this draft with the people who were there before you share it.{{ $(\"Check 4: The review ends with the reminder to check it with the people involved\").isExecuted ? \"\\n\\nYour last answer did not pass this check: The review ends with the reminder to check it with the people involved. What was wrong: \" + $(\"Check 4: The review ends with the reminder to check it with the people involved\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 5: Every number in the review is in the notes\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every number in the review is in the notes. What was wrong: \" + $(\"Check 5: Every number in the review is in the notes\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 6: No line puts the fault on a person\").isExecuted ? \"\\n\\nYour last answer did not pass this check: No line puts the fault on a person. What was wrong: \" + $(\"Check 6: No line puts the fault on a person\").first().json.text + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "e3309568-3a18-4335-a64b-550eb2ed2eb3",
      "name": "4. Write the review",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        2820,
        320
      ],
      "notes": "Put the summary, what went well, the changes and the open questions in one short review a team can read together. Returns: A short review with a summary, what went well, a table of problems and changes, and the open questions.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// The review ends with the reminder to check it with the people involved\n// Rule: The answer includes the words \"Check this draft with the people who were there before you share it.\".\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 review\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"has_text\", \"Check this draft with the people who were there before you share it.\", work, sources);\nreturn [{ json: result }];"
      },
      "id": "f7afc59d-0048-474c-a83a-6b67e81229d6",
      "name": "Check 4: The review ends with the reminder to check it with the people involved",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3080,
        320
      ],
      "notes": "The answer includes the words \"Check this draft with the people who were there before you share it.\".",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "9be4b6f4-670a-4ca3-a200-951a3a88a4d9",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "a6951ecb-4a57-4076-adc5-9b51bb681a9c",
      "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": "82dc76d7-6249-46d8-a43b-81add30862be",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "14433f07-c6b7-4268-a326-56bd2145469e",
      "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 review\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "7866d242-7eb3-4b75-a572-3e6c952e2997",
              "name": "flag",
              "value": "=The review ends with the reminder to check it with the people involved: did not pass. {{ $(\"Check 4: The review ends with the reminder to check it with the people involved\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "33b11a7c-1ce0-411f-a382-d2d267a48495",
      "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": {
        "jsCode": "// Every number in the review is in the notes\n// Rule: Every number in the answer also appears in the what happened.\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 review\").first().json.text ?? '');\nconst sources = {\n  \"notes\": String($(\"Inputs\").first().json[\"notes\"] ?? ''),\n};\nconst result = runRule(\"numbers_in_source\", {\"input\":\"notes\"}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "184cec8a-446a-4f63-a5eb-a198bfd9b8d1",
      "name": "Check 5: Every number in the review is in the notes",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3600,
        320
      ],
      "notes": "Every number in the answer also appears in the what happened.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "9ae4b561-6c0a-4482-ad00-8d3b3b88a66c",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "a6975d62-ca54-485f-adc8-a3683b651285",
      "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": "7fdc721e-6949-41dd-ad3b-76a8d6086777",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "f845518a-52b4-4d35-a728-b4d0bd426a9b",
      "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 review\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "7366ca63-77b3-4070-a472-3cd9922e24de",
              "name": "flag",
              "value": "=Every number in the review is in the notes: did not pass. {{ $(\"Check 5: Every number in the review is in the notes\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "34b11c0f-1be0-4f8c-a482-d46566a48302",
      "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": {
        "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: No line puts the fault on a person\nRead the review in the answer. Does any line name a person as the cause, say someone failed, forgot or should have done something, or guess at a person's reasons? Roles are fine when they only say who owns a change. Answer FAIL and quote each line that does.\n\nThe work to check (the answer from Step 4 (Write the review)):\n{{ $(\"4. Write the review\").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": "44206c31-0c50-4ec4-ac88-74cf8a8b3e4a",
      "name": "Check 6: No line puts the fault on a person",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        4120,
        320
      ],
      "notes": "Answers PASS or FAIL. Read the review in the answer. Does any line name a person as the cause, say someone failed, forgot or should have done something, or guess at a person's reasons? Roles are fine when they only say who owns a change. Answer FAIL and quote each line that does.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "99e4b3ce-6d0a-4615-ac00-8ba83c88a7ff",
              "leftValue": "={{ String($json.text ?? '').trim().toUpperCase().startsWith('PASS') }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "268fd81d-ca52-49c8-adca-e1ffbb6c97ca",
      "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": "80dc73b1-6849-404a-ae3b-783bd50865e4",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "1047b5e9-2ab1-4fa6-a720-fd2b1d49bd80",
      "name": "Try again? (check 6)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        4380,
        520
      ],
      "notes": "True = first failure: send back to \"4. Write the review\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "7266c8d0-78b3-4203-a372-3b46932e2671",
              "name": "flag",
              "value": "=No line puts the fault on a person: did not pass. {{ $(\"Check 6: No line puts the fault on a person\").first().json.text }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "35b11da2-1ee0-4445-a182-cfac65a4816f",
      "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": {
        "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: Each change fits its cause and names an action\nCompare each change in the answer with its cause in the sorted events below. Does each change answer the cause in its own row, and does it say what a person would do, rather than advice such as \"be more careful\" or \"plan better\"? Answer FAIL and quote each change that does not.\n\nThe sorted events:\n{{ $(\"2. Sort what went well from what got in the way\").first().json.text }}\n\nThe work to check (the answer from Step 3 (Turn each problem into a change)):\n{{ $(\"3. Turn each problem into a change\").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": "277b4ce3-2397-4976-ae42-da3541ec1f78",
      "name": "Check 7: Each change fits its cause and names an action",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        4640,
        320
      ],
      "notes": "Answers PASS or FAIL. Compare each change in the answer with its cause in the sorted events below. Does each change answer the cause in its own row, and does it say what a person would do, rather than advice such as \"be more careful\" or \"plan better\"? Answer FAIL and quote each change that does not.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "98e4b23b-6a0a-415c-af00-90613d88a992",
              "leftValue": "={{ String($json.text ?? '').trim().toUpperCase().startsWith('PASS') }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "a692e034-ca50-4b31-adcd-2096bb6a5933",
      "name": "Passed check 7?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        4900,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "7ddc6ef8-6749-4eb7-af3b-79ced8086a9d",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "744a91ec-46af-4d23-ab23-746e4947c42d",
      "name": "Try again? (check 7)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        4900,
        520
      ],
      "notes": "True = first failure: send back to \"3. Turn each problem into a change\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "7566cd89-79b3-4396-a272-39b3902e21b8",
              "name": "flag",
              "value": "=Each change fits its cause and names an action: did not pass. {{ $(\"Check 7: Each change fits its cause and names an action\").first().json.text }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "36b11f35-1de0-42b2-a282-d13f64a47fdc",
      "name": "Flag check 7",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        5030,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "content": "**Check 8: check by hand**\nRead the review with the people who were there. Ask each whether it matches what they remember and whether any line reads as blame, and change any line they dispute before you share it. Only they know what the notes left out.\n\nIf not: flag it and carry on. This check is also listed in Result.",
        "height": 200,
        "width": 240,
        "color": 3
      },
      "id": "7852b213-6ffe-437a-abdf-9e1d686b7744",
      "name": "Check 8 (by hand)",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        4880,
        70
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "e3ce4b8a-293a-426f-af17-e06482efcc31",
              "name": "result",
              "value": "={{ $(\"4. Write the review\").first().json.text }}",
              "type": "string"
            },
            {
              "id": "311dc233-e562-421c-a36a-8c8de27b43ee",
              "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 : \"\", $(\"Flag check 7\").isExecuted ? $(\"Flag check 7\").first().json.flag : \"\"].filter(x => x).join(\"\\n\") || \"None\" }}",
              "type": "string"
            },
            {
              "id": "6494dae9-32c9-452a-ad58-0383ce01d5bc",
              "name": "check_by_hand",
              "value": "- Read the review with the people who were there. Ask each whether it matches what they remember and whether any line reads as blame, and change any line they dispute before you share it. Only they know what the notes left out.",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "948fd36f-b03c-4b98-aecf-26099d7b43e2",
      "name": "Result",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        5160,
        320
      ],
      "notes": "The final answer, anything flagged, and the checks left for you.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "6c9c2355-0cdf-40fa-a1ea-566f0432f60c",
      "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. Lay out what happened",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1. Lay out what happened": {
      "main": [
        [
          {
            "node": "Check 1: Every quote is word for word from the notes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 1: Every quote is word for word from the notes": {
      "main": [
        [
          {
            "node": "Passed check 1?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 1?": {
      "main": [
        [
          {
            "node": "2. Sort what went well from what got in the way",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 1)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 1)": {
      "main": [
        [
          {
            "node": "1. Lay out what happened",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 1": {
      "main": [
        [
          {
            "node": "2. Sort what went well from what got in the way",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. Sort what went well from what got in the way": {
      "main": [
        [
          {
            "node": "Check 2: Each effect is went well or got in the way",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 2: Each effect is went well or got in the way": {
      "main": [
        [
          {
            "node": "Passed check 2?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 2?": {
      "main": [
        [
          {
            "node": "3. Turn each problem into a change",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 2)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 2)": {
      "main": [
        [
          {
            "node": "2. Sort what went well from what got in the way",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 2": {
      "main": [
        [
          {
            "node": "3. Turn each problem into a change",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3. Turn each problem into a change": {
      "main": [
        [
          {
            "node": "Check 3: Every change has an owner and a way to know",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 3: Every change has an owner and a way to know": {
      "main": [
        [
          {
            "node": "Passed check 3?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 3?": {
      "main": [
        [
          {
            "node": "4. Write the review",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 3)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 3)": {
      "main": [
        [
          {
            "node": "3. Turn each problem into a change",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 3": {
      "main": [
        [
          {
            "node": "4. Write the review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4. Write the review": {
      "main": [
        [
          {
            "node": "Check 4: The review ends with the reminder to check it with the people involved",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 4: The review ends with the reminder to check it with the people involved": {
      "main": [
        [
          {
            "node": "Passed check 4?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 4?": {
      "main": [
        [
          {
            "node": "Check 5: Every number in the review is in the notes",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 4)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 4)": {
      "main": [
        [
          {
            "node": "4. Write the review",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 4": {
      "main": [
        [
          {
            "node": "Check 5: Every number in the review is in the notes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 5: Every number in the review is in the notes": {
      "main": [
        [
          {
            "node": "Passed check 5?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 5?": {
      "main": [
        [
          {
            "node": "Check 6: No line puts the fault on a person",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 5)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 5)": {
      "main": [
        [
          {
            "node": "4. Write the review",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 5",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 5": {
      "main": [
        [
          {
            "node": "Check 6: No line puts the fault on a person",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 6: No line puts the fault on a person": {
      "main": [
        [
          {
            "node": "Passed check 6?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 6?": {
      "main": [
        [
          {
            "node": "Check 7: Each change fits its cause and names an action",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 6)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 6)": {
      "main": [
        [
          {
            "node": "4. Write the review",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 6",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 6": {
      "main": [
        [
          {
            "node": "Check 7: Each change fits its cause and names an action",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 7: Each change fits its cause and names an action": {
      "main": [
        [
          {
            "node": "Passed check 7?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 7?": {
      "main": [
        [
          {
            "node": "Result",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 7)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 7)": {
      "main": [
        [
          {
            "node": "3. Turn each problem into a change",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 7",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 7": {
      "main": [
        [
          {
            "node": "Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Chat model": {
      "ai_languageModel": [
        [
          {
            "node": "1. Lay out what happened",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "2. Sort what went well from what got in the way",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "3. Turn each problem into a change",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "4. Write the review",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Check 6: No line puts the fault on a person",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Check 7: Each change fits its cause and names an action",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "template": "run-a-lessons-learned-review"
  }
}
