{
  "id": "SpeckyFeedbackSpec",
  "name": "Specky — feedback to a cited spec (no API key)",
  "nodes": [
    {
      "parameters": {},
      "id": "a1000000-0000-4000-8000-000000000001",
      "name": "When clicking 'Execute workflow'",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -240,
        0
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "b1000000-0000-4000-8000-000000000001",
              "name": "feedback",
              "value": "I keep having to re-export the weekly report by hand because the scheduled email always arrives missing the last two days of data. It's been like this for a month and I've stopped trusting the numbers in it.",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "a1000000-0000-4000-8000-000000000002",
      "name": "Sample feedback",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -20,
        0
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "specky-feedback",
        "options": {}
      },
      "id": "a1000000-0000-4000-8000-000000000003",
      "name": "Webhook (your form)",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -240,
        220
      ],
      "webhookId": "a1000000-0000-4000-8000-000000000003"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "b1000000-0000-4000-8000-000000000002",
              "name": "feedback",
              "value": "={{ $json.body?.feedback ?? $json.body?.text ?? $json.body?.message ?? $json.feedback ?? $json.text ?? '' }}",
              "type": "string"
            }
          ]
        },
        "includeOtherFields": false,
        "options": {}
      },
      "id": "a1000000-0000-4000-8000-000000000004",
      "name": "Normalize incoming text",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -20,
        220
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "c1000000-0000-4000-8000-000000000001",
              "leftValue": "={{ ($json.feedback || '').trim().length }}",
              "rightValue": 15,
              "operator": {
                "type": "number",
                "operation": "gte"
              }
            }
          ],
          "combinator": "and"
        },
        "looseTypeValidation": true,
        "options": {}
      },
      "id": "a1000000-0000-4000-8000-000000000005",
      "name": "Long enough to analyse?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        200,
        100
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://www.specky.space/api/mcp",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name: 'feedback_to_spec', arguments: { feedback: $json.feedback } } }) }}",
        "options": {
          "timeout": 60000
        }
      },
      "id": "a1000000-0000-4000-8000-000000000006",
      "name": "Specky — feedback_to_spec (no key)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        440,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// Unwrap the JSON-RPC envelope from Specky's MCP server.\n//\n// Success shape:\n//   { jsonrpc, id, result: { content: [{ type: 'text', text }], structuredContent: {...} } }\n// Error shape:\n//   { jsonrpc, id, error: { code, message } }\n//\n// structuredContent is the one to branch on — it is typed:\n//   problem, who, evidence[], insight, prdSnippet, severity ('low'|'medium'|'high')\n\nconst out = [];\n\nfor (const item of $input.all()) {\n  const body = item.json;\n\n  if (body.error) {\n    throw new Error(\n      `Specky MCP error ${body.error.code}: ${body.error.message}`\n    );\n  }\n\n  const result = body.result || {};\n  const spec = result.structuredContent || {};\n  const text = (result.content || [])\n    .filter((c) => c.type === 'text')\n    .map((c) => c.text)\n    .join('\\n');\n\n  if (result.isError) {\n    throw new Error(text || 'Specky returned an error result.');\n  }\n\n  out.push({\n    json: {\n      problem: spec.problem ?? null,\n      who: spec.who ?? null,\n      severity: spec.severity ?? null,\n      insight: spec.insight ?? null,\n      prdSnippet: spec.prdSnippet ?? null,\n      evidence: spec.evidence ?? [],\n      // Ready-to-post Slack text. Evidence is verbatim from the input by\n      // design — that is the part worth showing a human.\n      slackText: [\n        `*${spec.problem ?? 'Untitled problem'}*  _(${spec.severity ?? 'unknown'} severity)_`,\n        `Who it hurts: ${spec.who ?? 'unclear'}`,\n        '',\n        spec.insight ?? '',\n        '',\n        '*They actually said:*',\n        ...(spec.evidence ?? []).map((q) => `> ${q}`),\n        '',\n        '*PRD problem statement*',\n        spec.prdSnippet ?? '',\n      ].join('\\n'),\n      raw: text,\n    },\n  });\n}\n\nreturn out;\n"
      },
      "id": "a1000000-0000-4000-8000-000000000007",
      "name": "Parse the spec",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        660,
        0
      ]
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "",
          "mode": "name"
        },
        "text": "={{ $json.slackText }}",
        "otherOptions": {}
      },
      "id": "a1000000-0000-4000-8000-000000000008",
      "name": "Post to Slack",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [
        880,
        0
      ],
      "disabled": true,
      "notesInFlow": true,
      "notes": "Enable + pick a channel once you've added Slack credentials."
    },
    {
      "parameters": {},
      "id": "a1000000-0000-4000-8000-000000000009",
      "name": "Too short — ask for more",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        440,
        240
      ],
      "notesInFlow": true,
      "notes": "feedback_to_spec needs 15+ characters."
    },
    {
      "parameters": {
        "content": "## Run me first\n\nHit **Execute workflow**. The Specky node needs **no API key and no account** — it should return a real, structured spec on the first run.\n\nIf it doesn't, nothing below this point is worth your time.",
        "height": 260,
        "width": 300,
        "color": 4
      },
      "id": "a1000000-0000-4000-8000-00000000000a",
      "name": "Sticky: run me first",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -560,
        -60
      ]
    },
    {
      "parameters": {
        "content": "## Swap this trigger\n\nPoint the webhook at whatever already collects feedback — Tally, Typeform, a support inbox, a Slack action.\n\nThe Specky node takes **one string**. Nothing downstream cares where it came from.",
        "height": 240,
        "width": 300,
        "color": 7
      },
      "id": "a1000000-0000-4000-8000-00000000000b",
      "name": "Sticky: swap the trigger",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -560,
        220
      ]
    },
    {
      "parameters": {
        "content": "## Keyless vs. connected\n\nAs shipped this judges **one** piece of feedback in isolation. That's the demo.\n\nAdd `Authorization: Bearer sk_live_…` to this node's headers and Specky writes into a Product Graph instead — so the next response gets compared against everything said before it.\n\nThat comparison is the actual product. Rate limit without a key: 10/hour per IP.",
        "height": 320,
        "width": 340,
        "color": 5
      },
      "id": "a1000000-0000-4000-8000-00000000000c",
      "name": "Sticky: add a key",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        400,
        -400
      ]
    },
    {
      "parameters": {
        "content": "## Prefer the MCP node?\n\nThis template uses a plain HTTP Request so it runs on any n8n version.\n\nIf you're on a build with the **MCP Client Tool** node, point it at the same endpoint instead and use the authenticated Specky workspace toolset rather than just this one public tool. Add `Authorization: Bearer sk_live_…` for workspace access:\n\n`https://www.specky.space/api/mcp`\n\nTransport: HTTP Streamable.",
        "height": 300,
        "width": 320,
        "color": 7
      },
      "id": "a1000000-0000-4000-8000-00000000000d",
      "name": "Sticky: MCP node alternative",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        760,
        -400
      ]
    }
  ],
  "connections": {
    "When clicking 'Execute workflow'": {
      "main": [
        [
          {
            "node": "Sample feedback",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sample feedback": {
      "main": [
        [
          {
            "node": "Long enough to analyse?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook (your form)": {
      "main": [
        [
          {
            "node": "Normalize incoming text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize incoming text": {
      "main": [
        [
          {
            "node": "Long enough to analyse?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Long enough to analyse?": {
      "main": [
        [
          {
            "node": "Specky — feedback_to_spec (no key)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Too short — ask for more",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Specky — feedback_to_spec (no key)": {
      "main": [
        [
          {
            "node": "Parse the spec",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse the spec": {
      "main": [
        [
          {
            "node": "Post to Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": []
}
