diff --git a/testdata/backlog-picker.flow.yaml b/testdata/backlog-picker.flow.yaml index 7f17cee83..41c8209f4 100644 --- a/testdata/backlog-picker.flow.yaml +++ b/testdata/backlog-picker.flow.yaml @@ -15,7 +15,7 @@ steps: type: deterministic dependsOn: [read-backlog, build-sdk] command: >- - node -e 'const fs=require("node:fs");const P=require("node:path"),F=require("node:fs");const SDK=(()=>{if(process.env.RELAYFLOWS_SDK_DIST)return P.resolve(process.env.RELAYFLOWS_SDK_DIST,"backlog-picker.js");let c=process.cwd();for(;;){const f=P.join(c,"sdk","dist","backlog-picker.js");if(F.existsSync(f))return f;const up=P.dirname(c);if(up===c)throw new Error("SDK_DIST_NOT_FOUND: build the sdk or set RELAYFLOWS_SDK_DIST");c=up}})();const {validateWorkPackage,packageFromEntry:pack}=require(SDK);fs.rmSync(".relayflow/backlog-picker-entry.json",{force:true});const text=fs.readFileSync(".relayflow/backlog-picker-source.md","utf8");const entries=[...text.matchAll(/^- \*\*(.+?)\*\*\s*(.*(?:\n .*)*)/gm)].map(m=>({title:m[1],body:m[2].replace(/\s+/g," ").trim()}));const skipped=[];for(const entry of entries){const verdict=validateWorkPackage(pack(entry));if(verdict.accepted){if(skipped.length)process.stderr.write("SKIPPED_UNACTIONABLE="+skipped.length+" "+skipped.join("; ")+"\n");fs.writeFileSync(".relayflow/backlog-picker-entry.json",JSON.stringify(entry));process.stdout.write(JSON.stringify(entry));process.exit(0)}skipped.push(entry.title.slice(0,40)+"["+verdict.reason+"]")}process.stderr.write("NO_ACTIONABLE_BACKLOG_ENTRY scanned="+entries.length+" "+skipped.join("; ")+"\n");process.exit(1)' + node -e 'const fs=require("node:fs");const P=require("node:path"),F=require("node:fs");const SDK=(()=>{if(process.env.RELAYFLOWS_SDK_DIST)return P.resolve(process.env.RELAYFLOWS_SDK_DIST,"backlog-picker.js");let c=process.cwd();for(;;){const f=P.join(c,"sdk","dist","backlog-picker.js");if(F.existsSync(f))return f;const up=P.dirname(c);if(up===c)throw new Error("SDK_DIST_NOT_FOUND: build the sdk or set RELAYFLOWS_SDK_DIST");c=up}})();const {validateWorkPackage,packageFromEntry:pack}=require(SDK);fs.rmSync(".relayflow/backlog-picker-entry.json",{force:true});const text=fs.readFileSync(".relayflow/backlog-picker-source.md","utf8");const entries=[...text.matchAll(/^- \*\*(.+?)\*\*\s*(.*(?:\n .*)*)/gm)].map(m=>({title:m[1],body:m[2].replace(/\s+/g," ").trim()}));const skipped=[];for(const entry of entries){const verdict=validateWorkPackage(pack(entry));if(verdict.accepted){process.stderr.write("SKIPPED_UNACTIONABLE="+skipped.length+(skipped.length?" "+skipped.join("; "):"")+"\n");fs.writeFileSync(".relayflow/backlog-picker-entry.json",JSON.stringify(entry));process.stdout.write(JSON.stringify(entry));process.exit(0)}skipped.push(entry.title.slice(0,40)+"["+verdict.reason+"]")}process.stderr.write("NO_ACTIONABLE_BACKLOG_ENTRY scanned="+entries.length+" "+skipped.join("; ")+"\n");process.exit(1)' - id: emit-package type: deterministic dependsOn: [select-entry] diff --git a/testdata/backlog-picker.spec.canonical.json b/testdata/backlog-picker.spec.canonical.json index 21c6bc3cd..ff98fc9a0 100644 --- a/testdata/backlog-picker.spec.canonical.json +++ b/testdata/backlog-picker.spec.canonical.json @@ -1,68 +1 @@ -{ - "description": "Read ops/BACKLOG.md and deterministically emit its first work package.", - "name": "backlog-picker", - "steps": [ - { - "command": "mkdir -p .relayflow && cat ops/BACKLOG.md > .relayflow/backlog-picker-source.md", - "depends_on": [], - "id": "read-backlog", - "max_iterations": 1, - "retry": { - "initial_backoff_ms": 100, - "jitter_percent": 20, - "max_backoff_ms": 60000, - "multiplier": 2 - }, - "type": "deterministic", - "verification": {} - }, - { - "id": "build-sdk", - "type": "deterministic", - "dependsOn": [ - "read-backlog" - ], - "command": "cd sdk && npm ci --silent --no-audit --no-fund && npm run build --silent" - }, - { - "command": "node -e 'const fs=require(\"node:fs\");const P=require(\"node:path\"),F=require(\"node:fs\");const SDK=(()=>{if(process.env.RELAYFLOWS_SDK_DIST)return P.resolve(process.env.RELAYFLOWS_SDK_DIST,\"backlog-picker.js\");let c=process.cwd();for(;;){const f=P.join(c,\"sdk\",\"dist\",\"backlog-picker.js\");if(F.existsSync(f))return f;const up=P.dirname(c);if(up===c)throw new Error(\"SDK_DIST_NOT_FOUND: build the sdk or set RELAYFLOWS_SDK_DIST\");c=up}})();const {validateWorkPackage,packageFromEntry:pack}=require(SDK);fs.rmSync(\".relayflow/backlog-picker-entry.json\",{force:true});const text=fs.readFileSync(\".relayflow/backlog-picker-source.md\",\"utf8\");const entries=[...text.matchAll(/^- \\*\\*(.+?)\\*\\*\\s*(.*(?:\\n .*)*)/gm)].map(m=>({title:m[1],body:m[2].replace(/\\s+/g,\" \").trim()}));const skipped=[];for(const entry of entries){const verdict=validateWorkPackage(pack(entry));if(verdict.accepted){if(skipped.length)process.stderr.write(\"SKIPPED_UNACTIONABLE=\"+skipped.length+\" \"+skipped.join(\"; \")+\"\\n\");fs.writeFileSync(\".relayflow/backlog-picker-entry.json\",JSON.stringify(entry));process.stdout.write(JSON.stringify(entry));process.exit(0)}skipped.push(entry.title.slice(0,40)+\"[\"+verdict.reason+\"]\")}process.stderr.write(\"NO_ACTIONABLE_BACKLOG_ENTRY scanned=\"+entries.length+\" \"+skipped.join(\"; \")+\"\\n\");process.exit(1)'", - "depends_on": [ - "read-backlog" - ], - "id": "select-entry", - "max_iterations": 1, - "retry": { - "initial_backoff_ms": 100, - "jitter_percent": 20, - "max_backoff_ms": 60000, - "multiplier": 2 - }, - "type": "deterministic", - "verification": {}, - "dependsOn": [ - "read-backlog", - "build-sdk" - ] - }, - { - "command": "node -e 'const fs=require(\"node:fs\");const P=require(\"node:path\"),F=require(\"node:fs\");const SDK=(()=>{if(process.env.RELAYFLOWS_SDK_DIST)return P.resolve(process.env.RELAYFLOWS_SDK_DIST,\"backlog-picker.js\");let c=process.cwd();for(;;){const f=P.join(c,\"sdk\",\"dist\",\"backlog-picker.js\");if(F.existsSync(f))return f;const up=P.dirname(c);if(up===c)throw new Error(\"SDK_DIST_NOT_FOUND: build the sdk or set RELAYFLOWS_SDK_DIST\");c=up}})();const sdk=require(SDK);const entry=JSON.parse(fs.readFileSync(\".relayflow/backlog-picker-entry.json\",\"utf8\"));const pkg=sdk.packageFromEntry(entry);const verdict=sdk.validateWorkPackage(pkg);if(!verdict.accepted){process.stderr.write(\"REFUSED_MALFORMED_BACKLOG_ENTRY reason=\"+verdict.reason+\"\\n\");process.exit(1)}process.stdout.write(JSON.stringify(pkg))'", - "depends_on": [ - "select-entry" - ], - "id": "emit-package", - "max_iterations": 1, - "retry": { - "initial_backoff_ms": 100, - "jitter_percent": 20, - "max_backoff_ms": 60000, - "multiplier": 2 - }, - "type": "deterministic", - "verification": {}, - "dependsOn": [ - "select-entry" - ] - } - ], - "version": "0.1.0" -} +{"description":"Read ops/BACKLOG.md and deterministically emit its first work package.","name":"backlog-picker","steps":[{"command":"mkdir -p .relayflow && cat ops/BACKLOG.md > .relayflow/backlog-picker-source.md","depends_on":[],"id":"read-backlog","max_iterations":1,"retry":{"initial_backoff_ms":100,"jitter_percent":20,"max_backoff_ms":60000,"multiplier":2},"type":"deterministic","verification":{}},{"command":"cd sdk && npm ci --silent --no-audit --no-fund && npm run build --silent","depends_on":["read-backlog"],"id":"build-sdk","max_iterations":1,"retry":{"initial_backoff_ms":100,"jitter_percent":20,"max_backoff_ms":60000,"multiplier":2},"type":"deterministic","verification":{}},{"command":"node -e 'const fs=require(\"node:fs\");const P=require(\"node:path\"),F=require(\"node:fs\");const SDK=(()=>{if(process.env.RELAYFLOWS_SDK_DIST)return P.resolve(process.env.RELAYFLOWS_SDK_DIST,\"backlog-picker.js\");let c=process.cwd();for(;;){const f=P.join(c,\"sdk\",\"dist\",\"backlog-picker.js\");if(F.existsSync(f))return f;const up=P.dirname(c);if(up===c)throw new Error(\"SDK_DIST_NOT_FOUND: build the sdk or set RELAYFLOWS_SDK_DIST\");c=up}})();const {validateWorkPackage,packageFromEntry:pack}=require(SDK);fs.rmSync(\".relayflow/backlog-picker-entry.json\",{force:true});const text=fs.readFileSync(\".relayflow/backlog-picker-source.md\",\"utf8\");const entries=[...text.matchAll(/^- \\*\\*(.+?)\\*\\*\\s*(.*(?:\\n .*)*)/gm)].map(m=>({title:m[1],body:m[2].replace(/\\s+/g,\" \").trim()}));const skipped=[];for(const entry of entries){const verdict=validateWorkPackage(pack(entry));if(verdict.accepted){process.stderr.write(\"SKIPPED_UNACTIONABLE=\"+skipped.length+(skipped.length?\" \"+skipped.join(\"; \"):\"\")+\"\\n\");fs.writeFileSync(\".relayflow/backlog-picker-entry.json\",JSON.stringify(entry));process.stdout.write(JSON.stringify(entry));process.exit(0)}skipped.push(entry.title.slice(0,40)+\"[\"+verdict.reason+\"]\")}process.stderr.write(\"NO_ACTIONABLE_BACKLOG_ENTRY scanned=\"+entries.length+\" \"+skipped.join(\"; \")+\"\\n\");process.exit(1)'","depends_on":["read-backlog","build-sdk"],"id":"select-entry","max_iterations":1,"retry":{"initial_backoff_ms":100,"jitter_percent":20,"max_backoff_ms":60000,"multiplier":2},"type":"deterministic","verification":{}},{"command":"node -e 'const fs=require(\"node:fs\");const P=require(\"node:path\"),F=require(\"node:fs\");const SDK=(()=>{if(process.env.RELAYFLOWS_SDK_DIST)return P.resolve(process.env.RELAYFLOWS_SDK_DIST,\"backlog-picker.js\");let c=process.cwd();for(;;){const f=P.join(c,\"sdk\",\"dist\",\"backlog-picker.js\");if(F.existsSync(f))return f;const up=P.dirname(c);if(up===c)throw new Error(\"SDK_DIST_NOT_FOUND: build the sdk or set RELAYFLOWS_SDK_DIST\");c=up}})();const sdk=require(SDK);const entry=JSON.parse(fs.readFileSync(\".relayflow/backlog-picker-entry.json\",\"utf8\"));const pkg=sdk.packageFromEntry(entry);const verdict=sdk.validateWorkPackage(pkg);if(!verdict.accepted){process.stderr.write(\"REFUSED_MALFORMED_BACKLOG_ENTRY reason=\"+verdict.reason+\"\\n\");process.exit(1)}process.stdout.write(JSON.stringify(pkg))'","depends_on":["select-entry"],"id":"emit-package","max_iterations":1,"retry":{"initial_backoff_ms":100,"jitter_percent":20,"max_backoff_ms":60000,"multiplier":2},"type":"deterministic","verification":{}}],"version":"0.1.0"}