From e600df236efd52615f18c0cad6622b8a97c1243a Mon Sep 17 00:00:00 2001 From: nntrivi2001 Date: Tue, 4 Aug 2026 17:56:38 +0700 Subject: [PATCH] feat(lanes): add ship-feature pipeline template (E1) --- server/__tests__/stage-detect.test.js | 43 ++++++++++++++++++++++--- server/data/pipelines/ship-feature.json | 22 +++++++++++++ 2 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 server/data/pipelines/ship-feature.json diff --git a/server/__tests__/stage-detect.test.js b/server/__tests__/stage-detect.test.js index 27f671c..ee288db 100644 --- a/server/__tests__/stage-detect.test.js +++ b/server/__tests__/stage-detect.test.js @@ -334,7 +334,7 @@ test.describe("rule precision against real command shapes", () => { tool_name: "Bash", tool_input: { command: - 'GIT_TERMINAL_PROMPT=0 git -c credential.helper=x push --force-with-lease origin main', + "GIT_TERMINAL_PROMPT=0 git -c credential.helper=x push --force-with-lease origin main", }, }); assert.equal(r.nodeId, "ship"); @@ -354,8 +354,8 @@ test.describe("ship detection against this repo's actual push command", () => { // `|`-free shell between `git` and `push`, so a same-segment pattern like // `git[^;&|]*push` never matches it. Six real pushes produced no signal. const REAL_PUSH = - 'cd /repo && git add -A && git commit -q --amend --no-edit && ' + - 'GIT_TERMINAL_PROMPT=0 git -c credential.helper=\'!f() { echo username=token; echo "password=$TK"; }; f\' push --force-with-lease origin main'; + "cd /repo && git add -A && git commit -q --amend --no-edit && " + + "GIT_TERMINAL_PROMPT=0 git -c credential.helper='!f() { echo username=token; echo \"password=$TK\"; }; f' push --force-with-lease origin main"; test.it("detects ship from the real push command", () => { const r = detect(pipeline, { tool_name: "Bash", tool_input: { command: REAL_PUSH } }); @@ -404,7 +404,7 @@ test.describe("ship must match a git push INVOCATION, not the word push", () => const cases = [ "git push origin main", "git push --force-with-lease", - 'GIT_TERMINAL_PROMPT=0 git -c credential.helper=\'!f() { echo x; }; f\' push --force-with-lease origin main', + "GIT_TERMINAL_PROMPT=0 git -c credential.helper='!f() { echo x; }; f' push --force-with-lease origin main", "cd /repo && git -c core.hooksPath=/dev/null push origin main", ]; for (const command of cases) { @@ -448,3 +448,38 @@ test.describe("tests rule covers the runners this repo actually uses", () => { } }); }); + +test.describe("ship-feature pipeline template", () => { + test.it("loads without throwing and has one node per skill stage", () => { + const { reload } = require("../lib/pipelines"); + reload(); + const pipeline = getPipeline("ship-feature"); + const ids = pipeline.nodes.map((n) => n.id); + assert.deepEqual(ids, [ + "intake", + "plan", + "implementing", + "gates", + "e2e-feature", + "e2e-feature-passed", + "review", + "qc-plan", + "qc", + "gate", + "publishing", + "pr-open", + "reported", + "watching-pr", + "merged", + "done", + ]); + }); + + test.it("every node id is unique", () => { + const { reload } = require("../lib/pipelines"); + reload(); + const pipeline = getPipeline("ship-feature"); + const ids = pipeline.nodes.map((n) => n.id); + assert.deepEqual(ids, [...new Set(ids)]); + }); +}); diff --git a/server/data/pipelines/ship-feature.json b/server/data/pipelines/ship-feature.json new file mode 100644 index 0000000..d60161a --- /dev/null +++ b/server/data/pipelines/ship-feature.json @@ -0,0 +1,22 @@ +{ + "id": "ship-feature", + "name": "Ship feature (lane pipeline)", + "nodes": [ + { "id": "intake", "label": "intake", "icon": "๐Ÿ“", "gate": false, "aliases": [] }, + { "id": "plan", "label": "plan", "icon": "๐Ÿงญ", "gate": false, "aliases": [] }, + { "id": "implementing", "label": "implement (TDD)", "icon": "๐Ÿ› ", "gate": false, "aliases": [] }, + { "id": "gates", "label": "CI gates + preflight", "icon": "๐Ÿงช", "gate": true, "aliases": [] }, + { "id": "e2e-feature", "label": "e2e on feature branch", "icon": "๐Ÿงช", "gate": false, "aliases": [] }, + { "id": "e2e-feature-passed", "label": "e2e passed", "icon": "๐Ÿงช", "gate": true, "aliases": [] }, + { "id": "review", "label": "code review", "icon": "๐Ÿ‘€", "gate": true, "aliases": [] }, + { "id": "qc-plan", "label": "QC plan", "icon": "๐Ÿ“‹", "gate": false, "aliases": [] }, + { "id": "qc", "label": "browser QC", "icon": "๐Ÿ”", "gate": true, "aliases": [] }, + { "id": "gate", "label": "senior GO/NO-GO gate", "icon": "๐Ÿšฆ", "gate": true, "aliases": [] }, + { "id": "publishing", "label": "publish PR", "icon": "๐Ÿ”€", "gate": false, "aliases": [] }, + { "id": "pr-open", "label": "PR open", "icon": "๐Ÿ”€", "gate": false, "aliases": ["ship"] }, + { "id": "reported", "label": "reported", "icon": "๐Ÿ“ฃ", "gate": false, "aliases": [] }, + { "id": "watching-pr", "label": "watching PR", "icon": "๐Ÿ‘", "gate": false, "aliases": [] }, + { "id": "merged", "label": "merged โ€” post-verify", "icon": "๐Ÿ”—", "gate": false, "aliases": [] }, + { "id": "done", "label": "done", "icon": "โœ…", "gate": false, "aliases": ["complete", "completed"] } + ] +}