From 46ca211a048ed26fe154fe34a2fd475cea01e9d9 Mon Sep 17 00:00:00 2001 From: Jack McIntyre Date: Wed, 24 Jun 2026 08:33:54 +1000 Subject: [PATCH] feat(01KVS2MG): refuse vitest targets that cannot resolve to a runnable package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Authoring and scan-time discipline now reject a story whose vitest verification target has no package.json between it and the repo root (e.g. a wrong-prefix `mcp-server/tests/x.test.ts` instead of `plugins/flow/mcp-server/tests/x.test.ts`), with a new `unresolvable-test-target` violation that names the offending target — before any build is wasted. - Extract findPackageRoot (and its workspace helpers) from tools/run-reviewer-session.ts into lib/find-package-root.ts so the author/scan-time check and the review-time vitest check share ONE walk and cannot diverge. run-reviewer-session.ts re-exports findPackageRoot so it stays importable by name. - discipline-resolvability.ts runs the shared walk after the existing shape check; the test FILE need not exist (the build creates it), only an enclosing package must resolve — so a not-yet-existing test file under a real package still passes. - Existing non-runnable-test-target shape check and artifact/cited-source existence checks unchanged (no regression). - Tests cover: wrong-prefix refusal, not-yet-existing-file pass, shape-invalid still non-runnable-test-target. Fixtures across the suite seed a workspace-root package.json so shape-valid vitest targets resolve. - Rebuilt committed dist bundle. Claude-Session: https://claude.ai/code/session_01E5VyKQMGJSL6fRfRmCzuvs --- plugins/flow/mcp-server/dist/cli.js | 1197 +++++++++-------- plugins/flow/mcp-server/dist/index.js | 1041 +++++++------- .../planning-friction-emission.test.ts | 2 + .../flow/mcp-server/src/adapters/adapter.ts | 12 +- .../mcp-server/src/lib/find-package-root.ts | 210 +++ .../__tests__/plan-reopen.integration.test.ts | 2 + .../src/tools/__tests__/author-seam.test.ts | 3 + .../__tests__/bmad-to-native-ingest.test.ts | 3 + .../claim-complete-loop.integration.test.ts | 3 + .../__tests__/classify-story-lane.test.ts | 2 + .../__tests__/inner-cycle.integration.test.ts | 3 + .../risk-reasoning-enforcement.test.ts | 2 + ...can-sources-expected-work-counters.test.ts | 3 + .../src/tools/__tests__/scan-sources.test.ts | 6 + ...date-planner-backlog.resolvability.test.ts | 3 + .../__tests__/write-native-story.test.ts | 6 + .../src/tools/gather-retro-inputs.test.ts | 2 + .../src/tools/run-reviewer-session.ts | 196 +-- .../discipline-resolvability.parity.test.ts | 4 + .../discipline-runnable-test-kind.test.ts | 156 +++ .../validators/discipline-resolvability.ts | 33 + .../mcp-server/tests/scan-sources.test.ts | 2 + .../tests/write-native-story.test.ts | 2 + 23 files changed, 1605 insertions(+), 1288 deletions(-) create mode 100644 plugins/flow/mcp-server/src/lib/find-package-root.ts diff --git a/plugins/flow/mcp-server/dist/cli.js b/plugins/flow/mcp-server/dist/cli.js index cd39bdcc..60eec488 100644 --- a/plugins/flow/mcp-server/dist/cli.js +++ b/plugins/flow/mcp-server/dist/cli.js @@ -114,17 +114,17 @@ var require_visit = __commonJS({ visit.BREAK = BREAK; visit.SKIP = SKIP; visit.REMOVE = REMOVE; - function visit_(key, node, visitor, path85) { - const ctrl = callVisitor(key, node, visitor, path85); + function visit_(key, node, visitor, path86) { + const ctrl = callVisitor(key, node, visitor, path86); if (identity3.isNode(ctrl) || identity3.isPair(ctrl)) { - replaceNode(key, path85, ctrl); - return visit_(key, ctrl, visitor, path85); + replaceNode(key, path86, ctrl); + return visit_(key, ctrl, visitor, path86); } if (typeof ctrl !== "symbol") { if (identity3.isCollection(node)) { - path85 = Object.freeze(path85.concat(node)); + path86 = Object.freeze(path86.concat(node)); for (let i2 = 0; i2 < node.items.length; ++i2) { - const ci = visit_(i2, node.items[i2], visitor, path85); + const ci = visit_(i2, node.items[i2], visitor, path86); if (typeof ci === "number") i2 = ci - 1; else if (ci === BREAK) @@ -135,13 +135,13 @@ var require_visit = __commonJS({ } } } else if (identity3.isPair(node)) { - path85 = Object.freeze(path85.concat(node)); - const ck = visit_("key", node.key, visitor, path85); + path86 = Object.freeze(path86.concat(node)); + const ck = visit_("key", node.key, visitor, path86); if (ck === BREAK) return BREAK; else if (ck === REMOVE) node.key = null; - const cv = visit_("value", node.value, visitor, path85); + const cv = visit_("value", node.value, visitor, path86); if (cv === BREAK) return BREAK; else if (cv === REMOVE) @@ -162,17 +162,17 @@ var require_visit = __commonJS({ visitAsync.BREAK = BREAK; visitAsync.SKIP = SKIP; visitAsync.REMOVE = REMOVE; - async function visitAsync_(key, node, visitor, path85) { - const ctrl = await callVisitor(key, node, visitor, path85); + async function visitAsync_(key, node, visitor, path86) { + const ctrl = await callVisitor(key, node, visitor, path86); if (identity3.isNode(ctrl) || identity3.isPair(ctrl)) { - replaceNode(key, path85, ctrl); - return visitAsync_(key, ctrl, visitor, path85); + replaceNode(key, path86, ctrl); + return visitAsync_(key, ctrl, visitor, path86); } if (typeof ctrl !== "symbol") { if (identity3.isCollection(node)) { - path85 = Object.freeze(path85.concat(node)); + path86 = Object.freeze(path86.concat(node)); for (let i2 = 0; i2 < node.items.length; ++i2) { - const ci = await visitAsync_(i2, node.items[i2], visitor, path85); + const ci = await visitAsync_(i2, node.items[i2], visitor, path86); if (typeof ci === "number") i2 = ci - 1; else if (ci === BREAK) @@ -183,13 +183,13 @@ var require_visit = __commonJS({ } } } else if (identity3.isPair(node)) { - path85 = Object.freeze(path85.concat(node)); - const ck = await visitAsync_("key", node.key, visitor, path85); + path86 = Object.freeze(path86.concat(node)); + const ck = await visitAsync_("key", node.key, visitor, path86); if (ck === BREAK) return BREAK; else if (ck === REMOVE) node.key = null; - const cv = await visitAsync_("value", node.value, visitor, path85); + const cv = await visitAsync_("value", node.value, visitor, path86); if (cv === BREAK) return BREAK; else if (cv === REMOVE) @@ -216,23 +216,23 @@ var require_visit = __commonJS({ } return visitor; } - function callVisitor(key, node, visitor, path85) { + function callVisitor(key, node, visitor, path86) { if (typeof visitor === "function") - return visitor(key, node, path85); + return visitor(key, node, path86); if (identity3.isMap(node)) - return visitor.Map?.(key, node, path85); + return visitor.Map?.(key, node, path86); if (identity3.isSeq(node)) - return visitor.Seq?.(key, node, path85); + return visitor.Seq?.(key, node, path86); if (identity3.isPair(node)) - return visitor.Pair?.(key, node, path85); + return visitor.Pair?.(key, node, path86); if (identity3.isScalar(node)) - return visitor.Scalar?.(key, node, path85); + return visitor.Scalar?.(key, node, path86); if (identity3.isAlias(node)) - return visitor.Alias?.(key, node, path85); + return visitor.Alias?.(key, node, path86); return void 0; } - function replaceNode(key, path85, node) { - const parent = path85[path85.length - 1]; + function replaceNode(key, path86, node) { + const parent = path86[path86.length - 1]; if (identity3.isCollection(parent)) { parent.items[key] = node; } else if (identity3.isPair(parent)) { @@ -842,10 +842,10 @@ var require_Collection = __commonJS({ var createNode = require_createNode(); var identity3 = require_identity(); var Node = require_Node(); - function collectionFromPath(schema, path85, value) { + function collectionFromPath(schema, path86, value) { let v = value; - for (let i2 = path85.length - 1; i2 >= 0; --i2) { - const k = path85[i2]; + for (let i2 = path86.length - 1; i2 >= 0; --i2) { + const k = path86[i2]; if (typeof k === "number" && Number.isInteger(k) && k >= 0) { const a2 = []; a2[k] = v; @@ -864,7 +864,7 @@ var require_Collection = __commonJS({ sourceObjects: /* @__PURE__ */ new Map() }); } - var isEmptyPath = (path85) => path85 == null || typeof path85 === "object" && !!path85[Symbol.iterator]().next().done; + var isEmptyPath = (path86) => path86 == null || typeof path86 === "object" && !!path86[Symbol.iterator]().next().done; var Collection = class extends Node.NodeBase { constructor(type, schema) { super(type); @@ -894,11 +894,11 @@ var require_Collection = __commonJS({ * be a Pair instance or a `{ key, value }` object, which may not have a key * that already exists in the map. */ - addIn(path85, value) { - if (isEmptyPath(path85)) + addIn(path86, value) { + if (isEmptyPath(path86)) this.add(value); else { - const [key, ...rest] = path85; + const [key, ...rest] = path86; const node = this.get(key, true); if (identity3.isCollection(node)) node.addIn(rest, value); @@ -912,8 +912,8 @@ var require_Collection = __commonJS({ * Removes a value from the collection. * @returns `true` if the item was found and removed. */ - deleteIn(path85) { - const [key, ...rest] = path85; + deleteIn(path86) { + const [key, ...rest] = path86; if (rest.length === 0) return this.delete(key); const node = this.get(key, true); @@ -927,8 +927,8 @@ var require_Collection = __commonJS({ * scalar values from their surrounding node; to disable set `keepScalar` to * `true` (collections are always returned intact). */ - getIn(path85, keepScalar) { - const [key, ...rest] = path85; + getIn(path86, keepScalar) { + const [key, ...rest] = path86; const node = this.get(key, true); if (rest.length === 0) return !keepScalar && identity3.isScalar(node) ? node.value : node; @@ -946,8 +946,8 @@ var require_Collection = __commonJS({ /** * Checks if the collection includes a value with the key `key`. */ - hasIn(path85) { - const [key, ...rest] = path85; + hasIn(path86) { + const [key, ...rest] = path86; if (rest.length === 0) return this.has(key); const node = this.get(key, true); @@ -957,8 +957,8 @@ var require_Collection = __commonJS({ * Sets a value in this collection. For `!!set`, `value` needs to be a * boolean to add/remove the item from the set. */ - setIn(path85, value) { - const [key, ...rest] = path85; + setIn(path86, value) { + const [key, ...rest] = path86; if (rest.length === 0) { this.set(key, value); } else { @@ -3473,9 +3473,9 @@ var require_Document = __commonJS({ this.contents.add(value); } /** Adds a value to the document. */ - addIn(path85, value) { + addIn(path86, value) { if (assertCollection(this.contents)) - this.contents.addIn(path85, value); + this.contents.addIn(path86, value); } /** * Create a new `Alias` node, ensuring that the target `node` has the required anchor. @@ -3550,14 +3550,14 @@ var require_Document = __commonJS({ * Removes a value from the document. * @returns `true` if the item was found and removed. */ - deleteIn(path85) { - if (Collection.isEmptyPath(path85)) { + deleteIn(path86) { + if (Collection.isEmptyPath(path86)) { if (this.contents == null) return false; this.contents = null; return true; } - return assertCollection(this.contents) ? this.contents.deleteIn(path85) : false; + return assertCollection(this.contents) ? this.contents.deleteIn(path86) : false; } /** * Returns item at `key`, or `undefined` if not found. By default unwraps @@ -3572,10 +3572,10 @@ var require_Document = __commonJS({ * scalar values from their surrounding node; to disable set `keepScalar` to * `true` (collections are always returned intact). */ - getIn(path85, keepScalar) { - if (Collection.isEmptyPath(path85)) + getIn(path86, keepScalar) { + if (Collection.isEmptyPath(path86)) return !keepScalar && identity3.isScalar(this.contents) ? this.contents.value : this.contents; - return identity3.isCollection(this.contents) ? this.contents.getIn(path85, keepScalar) : void 0; + return identity3.isCollection(this.contents) ? this.contents.getIn(path86, keepScalar) : void 0; } /** * Checks if the document includes a value with the key `key`. @@ -3586,10 +3586,10 @@ var require_Document = __commonJS({ /** * Checks if the document includes a value at `path`. */ - hasIn(path85) { - if (Collection.isEmptyPath(path85)) + hasIn(path86) { + if (Collection.isEmptyPath(path86)) return this.contents !== void 0; - return identity3.isCollection(this.contents) ? this.contents.hasIn(path85) : false; + return identity3.isCollection(this.contents) ? this.contents.hasIn(path86) : false; } /** * Sets a value in this document. For `!!set`, `value` needs to be a @@ -3606,13 +3606,13 @@ var require_Document = __commonJS({ * Sets a value in this document. For `!!set`, `value` needs to be a * boolean to add/remove the item from the set. */ - setIn(path85, value) { - if (Collection.isEmptyPath(path85)) { + setIn(path86, value) { + if (Collection.isEmptyPath(path86)) { this.contents = value; } else if (this.contents == null) { - this.contents = Collection.collectionFromPath(this.schema, Array.from(path85), value); + this.contents = Collection.collectionFromPath(this.schema, Array.from(path86), value); } else if (assertCollection(this.contents)) { - this.contents.setIn(path85, value); + this.contents.setIn(path86, value); } } /** @@ -5572,9 +5572,9 @@ var require_cst_visit = __commonJS({ visit.BREAK = BREAK; visit.SKIP = SKIP; visit.REMOVE = REMOVE; - visit.itemAtPath = (cst, path85) => { + visit.itemAtPath = (cst, path86) => { let item = cst; - for (const [field, index] of path85) { + for (const [field, index] of path86) { const tok = item?.[field]; if (tok && "items" in tok) { item = tok.items[index]; @@ -5583,23 +5583,23 @@ var require_cst_visit = __commonJS({ } return item; }; - visit.parentCollection = (cst, path85) => { - const parent = visit.itemAtPath(cst, path85.slice(0, -1)); - const field = path85[path85.length - 1][0]; + visit.parentCollection = (cst, path86) => { + const parent = visit.itemAtPath(cst, path86.slice(0, -1)); + const field = path86[path86.length - 1][0]; const coll = parent?.[field]; if (coll && "items" in coll) return coll; throw new Error("Parent collection not found"); }; - function _visit(path85, item, visitor) { - let ctrl = visitor(item, path85); + function _visit(path86, item, visitor) { + let ctrl = visitor(item, path86); if (typeof ctrl === "symbol") return ctrl; for (const field of ["key", "value"]) { const token = item[field]; if (token && "items" in token) { for (let i2 = 0; i2 < token.items.length; ++i2) { - const ci = _visit(Object.freeze(path85.concat([[field, i2]])), token.items[i2], visitor); + const ci = _visit(Object.freeze(path86.concat([[field, i2]])), token.items[i2], visitor); if (typeof ci === "number") i2 = ci - 1; else if (ci === BREAK) @@ -5610,10 +5610,10 @@ var require_cst_visit = __commonJS({ } } if (typeof ctrl === "function" && field === "key") - ctrl = ctrl(item, path85); + ctrl = ctrl(item, path86); } } - return typeof ctrl === "function" ? ctrl(item, path85) : ctrl; + return typeof ctrl === "function" ? ctrl(item, path86) : ctrl; } exports.visit = visit; } @@ -7619,8 +7619,8 @@ var require_utils = __commonJS({ } return output; }; - exports.basename = (path85, { windows } = {}) => { - const segs = path85.split(windows ? /[\\/]/ : "/"); + exports.basename = (path86, { windows } = {}) => { + const segs = path86.split(windows ? /[\\/]/ : "/"); const last = segs[segs.length - 1]; if (last === "") { return segs[segs.length - 2]; @@ -9120,7 +9120,7 @@ var require_windows = __commonJS({ module.exports = isexe; isexe.sync = sync; var fs67 = __require("fs"); - function checkPathExt(path85, options) { + function checkPathExt(path86, options) { var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT; if (!pathext) { return true; @@ -9131,25 +9131,25 @@ var require_windows = __commonJS({ } for (var i2 = 0; i2 < pathext.length; i2++) { var p = pathext[i2].toLowerCase(); - if (p && path85.substr(-p.length).toLowerCase() === p) { + if (p && path86.substr(-p.length).toLowerCase() === p) { return true; } } return false; } - function checkStat(stat2, path85, options) { + function checkStat(stat2, path86, options) { if (!stat2.isSymbolicLink() && !stat2.isFile()) { return false; } - return checkPathExt(path85, options); + return checkPathExt(path86, options); } - function isexe(path85, options, cb) { - fs67.stat(path85, function(er, stat2) { - cb(er, er ? false : checkStat(stat2, path85, options)); + function isexe(path86, options, cb) { + fs67.stat(path86, function(er, stat2) { + cb(er, er ? false : checkStat(stat2, path86, options)); }); } - function sync(path85, options) { - return checkStat(fs67.statSync(path85), path85, options); + function sync(path86, options) { + return checkStat(fs67.statSync(path86), path86, options); } } }); @@ -9160,13 +9160,13 @@ var require_mode = __commonJS({ module.exports = isexe; isexe.sync = sync; var fs67 = __require("fs"); - function isexe(path85, options, cb) { - fs67.stat(path85, function(er, stat2) { + function isexe(path86, options, cb) { + fs67.stat(path86, function(er, stat2) { cb(er, er ? false : checkStat(stat2, options)); }); } - function sync(path85, options) { - return checkStat(fs67.statSync(path85), options); + function sync(path86, options) { + return checkStat(fs67.statSync(path86), options); } function checkStat(stat2, options) { return stat2.isFile() && checkMode(stat2, options); @@ -9199,7 +9199,7 @@ var require_isexe = __commonJS({ } module.exports = isexe; isexe.sync = sync; - function isexe(path85, options, cb) { + function isexe(path86, options, cb) { if (typeof options === "function") { cb = options; options = {}; @@ -9208,17 +9208,17 @@ var require_isexe = __commonJS({ if (typeof Promise !== "function") { throw new TypeError("callback not provided"); } - return new Promise(function(resolve19, reject) { - isexe(path85, options || {}, function(er, is) { + return new Promise(function(resolve20, reject) { + isexe(path86, options || {}, function(er, is) { if (er) { reject(er); } else { - resolve19(is); + resolve20(is); } }); }); } - core(path85, options || {}, function(er, is) { + core(path86, options || {}, function(er, is) { if (er) { if (er.code === "EACCES" || options && options.ignoreErrors) { er = null; @@ -9228,9 +9228,9 @@ var require_isexe = __commonJS({ cb(er, is); }); } - function sync(path85, options) { + function sync(path86, options) { try { - return core.sync(path85, options || {}); + return core.sync(path86, options || {}); } catch (er) { if (options && options.ignoreErrors || er.code === "EACCES") { return false; @@ -9246,7 +9246,7 @@ var require_isexe = __commonJS({ var require_which = __commonJS({ "../node_modules/.pnpm/which@2.0.2/node_modules/which/which.js"(exports, module) { var isWindows = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys"; - var path85 = __require("path"); + var path86 = __require("path"); var COLON = isWindows ? ";" : ":"; var isexe = require_isexe(); var getNotFoundError = (cmd) => Object.assign(new Error(`not found: ${cmd}`), { code: "ENOENT" }); @@ -9279,27 +9279,27 @@ var require_which = __commonJS({ opt = {}; const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt); const found = []; - const step = (i2) => new Promise((resolve19, reject) => { + const step = (i2) => new Promise((resolve20, reject) => { if (i2 === pathEnv.length) - return opt.all && found.length ? resolve19(found) : reject(getNotFoundError(cmd)); + return opt.all && found.length ? resolve20(found) : reject(getNotFoundError(cmd)); const ppRaw = pathEnv[i2]; const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw; - const pCmd = path85.join(pathPart, cmd); + const pCmd = path86.join(pathPart, cmd); const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd; - resolve19(subStep(p, i2, 0)); + resolve20(subStep(p, i2, 0)); }); - const subStep = (p, i2, ii) => new Promise((resolve19, reject) => { + const subStep = (p, i2, ii) => new Promise((resolve20, reject) => { if (ii === pathExt.length) - return resolve19(step(i2 + 1)); + return resolve20(step(i2 + 1)); const ext = pathExt[ii]; isexe(p + ext, { pathExt: pathExtExe }, (er, is) => { if (!er && is) { if (opt.all) found.push(p + ext); else - return resolve19(p + ext); + return resolve20(p + ext); } - return resolve19(subStep(p, i2, ii + 1)); + return resolve20(subStep(p, i2, ii + 1)); }); }); return cb ? step(0).then((res) => cb(null, res), cb) : step(0); @@ -9311,7 +9311,7 @@ var require_which = __commonJS({ for (let i2 = 0; i2 < pathEnv.length; i2++) { const ppRaw = pathEnv[i2]; const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw; - const pCmd = path85.join(pathPart, cmd); + const pCmd = path86.join(pathPart, cmd); const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd; for (let j = 0; j < pathExt.length; j++) { const cur = p + pathExt[j]; @@ -9359,7 +9359,7 @@ var require_path_key = __commonJS({ var require_resolveCommand = __commonJS({ "../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/resolveCommand.js"(exports, module) { "use strict"; - var path85 = __require("path"); + var path86 = __require("path"); var which = require_which(); var getPathKey = require_path_key(); function resolveCommandAttempt(parsed, withoutPathExt) { @@ -9377,7 +9377,7 @@ var require_resolveCommand = __commonJS({ try { resolved = which.sync(parsed.command, { path: env[getPathKey({ env })], - pathExt: withoutPathExt ? path85.delimiter : void 0 + pathExt: withoutPathExt ? path86.delimiter : void 0 }); } catch (e) { } finally { @@ -9386,7 +9386,7 @@ var require_resolveCommand = __commonJS({ } } if (resolved) { - resolved = path85.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved); + resolved = path86.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved); } return resolved; } @@ -9440,8 +9440,8 @@ var require_shebang_command = __commonJS({ if (!match) { return null; } - const [path85, argument] = match[0].replace(/#! ?/, "").split(" "); - const binary = path85.split("/").pop(); + const [path86, argument] = match[0].replace(/#! ?/, "").split(" "); + const binary = path86.split("/").pop(); if (binary === "env") { return argument; } @@ -9476,7 +9476,7 @@ var require_readShebang = __commonJS({ var require_parse2 = __commonJS({ "../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/parse.js"(exports, module) { "use strict"; - var path85 = __require("path"); + var path86 = __require("path"); var resolveCommand = require_resolveCommand(); var escape = require_escape(); var readShebang = require_readShebang(); @@ -9501,7 +9501,7 @@ var require_parse2 = __commonJS({ const needsShell = !isExecutableRegExp.test(commandFile); if (parsed.options.forceShell || needsShell) { const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile); - parsed.command = path85.normalize(parsed.command); + parsed.command = path86.normalize(parsed.command); parsed.command = escape.command(parsed.command); parsed.args = parsed.args.map((arg) => escape.argument(arg, needsDoubleEscapeMetaChars)); const shellCommand = [parsed.command].concat(parsed.args).join(" "); @@ -12223,10 +12223,10 @@ function mergeDefs(...defs) { function cloneDef(schema) { return mergeDefs(schema._zod.def); } -function getElementAtPath(obj, path85) { - if (!path85) +function getElementAtPath(obj, path86) { + if (!path86) return obj; - return path85.reduce((acc, key) => acc?.[key], obj); + return path86.reduce((acc, key) => acc?.[key], obj); } function promiseAllObject(promisesObj) { const keys = Object.keys(promisesObj); @@ -12635,11 +12635,11 @@ function explicitlyAborted(x, startIndex = 0) { } return false; } -function prefixIssues(path85, issues) { +function prefixIssues(path86, issues) { return issues.map((iss) => { var _a3; (_a3 = iss).path ?? (_a3.path = []); - iss.path.unshift(path85); + iss.path.unshift(path86); return iss; }); } @@ -12786,16 +12786,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) { } function formatError(error51, mapper = (issue2) => issue2.message) { const fieldErrors = { _errors: [] }; - const processError = (error52, path85 = []) => { + const processError = (error52, path86 = []) => { for (const issue2 of error52.issues) { if (issue2.code === "invalid_union" && issue2.errors.length) { - issue2.errors.map((issues) => processError({ issues }, [...path85, ...issue2.path])); + issue2.errors.map((issues) => processError({ issues }, [...path86, ...issue2.path])); } else if (issue2.code === "invalid_key") { - processError({ issues: issue2.issues }, [...path85, ...issue2.path]); + processError({ issues: issue2.issues }, [...path86, ...issue2.path]); } else if (issue2.code === "invalid_element") { - processError({ issues: issue2.issues }, [...path85, ...issue2.path]); + processError({ issues: issue2.issues }, [...path86, ...issue2.path]); } else { - const fullpath = [...path85, ...issue2.path]; + const fullpath = [...path86, ...issue2.path]; if (fullpath.length === 0) { fieldErrors._errors.push(mapper(issue2)); } else { @@ -12822,17 +12822,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) { } function treeifyError(error51, mapper = (issue2) => issue2.message) { const result = { errors: [] }; - const processError = (error52, path85 = []) => { + const processError = (error52, path86 = []) => { var _a3, _b; for (const issue2 of error52.issues) { if (issue2.code === "invalid_union" && issue2.errors.length) { - issue2.errors.map((issues) => processError({ issues }, [...path85, ...issue2.path])); + issue2.errors.map((issues) => processError({ issues }, [...path86, ...issue2.path])); } else if (issue2.code === "invalid_key") { - processError({ issues: issue2.issues }, [...path85, ...issue2.path]); + processError({ issues: issue2.issues }, [...path86, ...issue2.path]); } else if (issue2.code === "invalid_element") { - processError({ issues: issue2.issues }, [...path85, ...issue2.path]); + processError({ issues: issue2.issues }, [...path86, ...issue2.path]); } else { - const fullpath = [...path85, ...issue2.path]; + const fullpath = [...path86, ...issue2.path]; if (fullpath.length === 0) { result.errors.push(mapper(issue2)); continue; @@ -12864,8 +12864,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) { } function toDotPath(_path) { const segs = []; - const path85 = _path.map((seg) => typeof seg === "object" ? seg.key : seg); - for (const seg of path85) { + const path86 = _path.map((seg) => typeof seg === "object" ? seg.key : seg); + for (const seg of path86) { if (typeof seg === "number") segs.push(`[${seg}]`); else if (typeof seg === "symbol") @@ -25557,13 +25557,13 @@ function resolveRef(ref, ctx) { if (!ref.startsWith("#")) { throw new Error("External $ref is not supported, only local refs (#/...) are allowed"); } - const path85 = ref.slice(1).split("/").filter(Boolean); - if (path85.length === 0) { + const path86 = ref.slice(1).split("/").filter(Boolean); + if (path86.length === 0) { return ctx.rootSchema; } const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions"; - if (path85[0] === defsKey) { - const key = path85[1]; + if (path86[0] === defsKey) { + const key = path86[1]; if (!key || !ctx.defs[key]) { throw new Error(`Reference not found: ${ref}`); } @@ -27439,8 +27439,8 @@ async function getStatus(opts) { } // src/tools/open-cycle.ts -var import_yaml11 = __toESM(require_dist(), 1); -import * as path26 from "node:path"; +var import_yaml12 = __toESM(require_dist(), 1); +import * as path27 from "node:path"; // ../node_modules/.pnpm/ulid@3.0.2/node_modules/ulid/dist/node/index.js import crypto from "node:crypto"; @@ -27772,9 +27772,9 @@ async function logTelemetryEvent(opts) { } // src/tools/gather-retro-inputs.ts -var import_yaml10 = __toESM(require_dist(), 1); +var import_yaml11 = __toESM(require_dist(), 1); import { promises as fs19 } from "node:fs"; -import * as path25 from "node:path"; +import * as path26 from "node:path"; // src/schemas/risk-tiering-spec.ts var ChangeTypeSchema = external_exports.enum(["revert", "migration", "schema", "dep-bump"]); @@ -28672,17 +28672,115 @@ async function computeSkillEffectiveness(opts) { // src/tools/write-native-story.ts import { createHash as createHash3 } from "node:crypto"; import { existsSync as existsSync2 } from "node:fs"; -import * as path18 from "node:path"; +import * as path19 from "node:path"; // src/validators/discipline-resolvability.ts import { promises as fs10 } from "node:fs"; +import * as path13 from "node:path"; + +// src/lib/find-package-root.ts +var import_yaml4 = __toESM(require_dist(), 1); import * as path12 from "node:path"; +import { accessSync, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "node:fs"; +function hasLocalVitest(dir) { + try { + accessSync(path12.join(dir, "node_modules", ".bin", "vitest")); + return true; + } catch { + return false; + } +} +function findVitestInWorkspaceMembers(workspaceRoot) { + try { + const yaml = readFileSync2( + path12.join(workspaceRoot, "pnpm-workspace.yaml"), + "utf8" + ); + const parsed = (0, import_yaml4.parse)(yaml); + const packages = parsed?.packages; + if (!Array.isArray(packages)) return { ok: false }; + for (const pattern of packages) { + if (typeof pattern !== "string") continue; + const segments = pattern.split("/"); + const hasGlob = segments.some((s) => s === "*" || s === "**"); + if (!hasGlob) { + const memberDir = path12.join(workspaceRoot, pattern); + if (hasLocalVitest(memberDir)) { + return { ok: true, packageRoot: memberDir }; + } + } else { + const parentSegments = segments.slice(0, segments.indexOf("*")); + const parentDir = path12.join(workspaceRoot, ...parentSegments); + try { + const entries = readdirSync2(parentDir, { withFileTypes: true }); + for (const entry of entries) { + if (!entry.isDirectory()) continue; + const memberDir = path12.join(parentDir, entry.name); + if (hasLocalVitest(memberDir)) { + return { ok: true, packageRoot: memberDir }; + } + } + } catch { + } + } + } + } catch { + } + return { ok: false }; +} +function findWorkspaceYamlInSubtree(root, maxDepth) { + if (maxDepth < 0) return null; + let entries; + try { + entries = readdirSync2(root, { withFileTypes: true }); + } catch { + return null; + } + if (entries.some((e) => e.isFile() && e.name === "pnpm-workspace.yaml")) { + return root; + } + for (const entry of entries) { + if (!entry.isDirectory()) continue; + const name = entry.name; + if (name === "node_modules" || name === ".git") continue; + const found = findWorkspaceYamlInSubtree(path12.join(root, name), maxDepth - 1); + if (found !== null) return found; + } + return null; +} +function findPackageRoot(opts) { + const checkRootAbs = path12.resolve(opts.checkRoot); + let dir = path12.dirname(opts.testFilePathAbs); + const isWithinCheckRoot = (d) => d === checkRootAbs || d.startsWith(checkRootAbs + path12.sep); + while (isWithinCheckRoot(dir)) { + try { + accessSync(path12.join(dir, "package.json")); + const memberResult = findVitestInWorkspaceMembers(dir); + if (memberResult.ok) { + return memberResult; + } + return { ok: true, packageRoot: dir }; + } catch { + } + const parent = path12.dirname(dir); + if (parent === dir) break; + dir = parent; + } + const workspaceYamlDir = findWorkspaceYamlInSubtree(checkRootAbs, 4); + if (workspaceYamlDir !== null) { + const memberResult = findVitestInWorkspaceMembers(workspaceYamlDir); + if (memberResult.ok) return memberResult; + } + return { ok: false }; +} + +// src/validators/discipline-resolvability.ts function isWellFormedTarget(target) { const t = target.trim(); if (t.length === 0) return false; if (/\s/.test(t)) return false; if (t.startsWith("-")) return false; - if (path12.isAbsolute(t)) return false; + if (path13.isAbsolute(t)) return false; return true; } function isRunnableTestTarget(target) { @@ -28710,7 +28808,7 @@ async function resolveDisciplinePaths(story, targetRepoRoot) { }); } else { for (const cited of citedSources) { - const abs = path12.resolve(targetRepoRoot, cited); + const abs = path13.resolve(targetRepoRoot, cited); if (await statOrNull(abs) === null) { reasons.push({ code: "unresolvable-cited-source", @@ -28744,7 +28842,7 @@ async function resolveDisciplinePaths(story, targetRepoRoot) { if (!v) continue; if (v.type !== "artifact") continue; if (!isWellFormedTarget(v.target)) continue; - const abs = path12.resolve(targetRepoRoot, v.target); + const abs = path13.resolve(targetRepoRoot, v.target); if (await statOrNull(abs) === null) { reasons.push({ code: "unresolvable-verification-target", @@ -28753,6 +28851,22 @@ async function resolveDisciplinePaths(story, targetRepoRoot) { }); } } + for (let i2 = 0; i2 < story.acceptance_criteria.length; i2++) { + const v = story.acceptance_criteria[i2].verification; + if (!v) continue; + if (v.type !== "vitest") continue; + if (!isWellFormedTarget(v.target)) continue; + if (!isRunnableTestTarget(v.target)) continue; + const testFilePathAbs = path13.resolve(targetRepoRoot, v.target); + const pkg = findPackageRoot({ testFilePathAbs, checkRoot: targetRepoRoot }); + if (!pkg.ok) { + reasons.push({ + code: "unresolvable-test-target", + field: `acceptance_criteria[${i2}].verification.target`, + detail: `AC${i2 + 1} vitest verification target '${v.target}' cannot resolve to a runnable package: no package.json was found between it (looked at '${testFilePathAbs}') and the repo root ('${targetRepoRoot}'). The test FILE need not exist yet (the build creates it), but a package must enclose it \u2014 this is the same upward walk the reviewer uses, so a target that fails here would also fail at review. A common cause is a wrong-prefix path (e.g. 'mcp-server/tests/x.test.ts' instead of 'plugins/flow/mcp-server/tests/x.test.ts'); fix the prefix so the target lands under a real package.` + }); + } + } return reasons; } @@ -28832,9 +28946,9 @@ async function emitFriction(opts) { } // src/tools/scan-sources.ts -var import_yaml6 = __toESM(require_dist(), 1); +var import_yaml7 = __toESM(require_dist(), 1); import { promises as fs13 } from "node:fs"; -import * as path17 from "node:path"; +import * as path18 from "node:path"; // src/lib/extract-dep-refs.ts var NATIVE_REF_RE2 = /^native:[0-9A-HJKMNP-TV-Z]{26}$/; @@ -28879,10 +28993,10 @@ function extractDepRefsFromSpecBody(body) { // src/state/lookup-risk-tiering-spec.ts import { promises as fs11 } from "node:fs"; -import * as path13 from "node:path"; +import * as path14 from "node:path"; // src/validators/risk-tiering-spec.ts -var import_yaml4 = __toESM(require_dist(), 1); +var import_yaml5 = __toESM(require_dist(), 1); function formatZodIssues2(issues) { const first = issues[0]; if (!first) return "(no issue details)"; @@ -28922,7 +29036,7 @@ function parseRiskTieringSpec(raw, sourcePath, copyTarget) { const yamlBlock = extractFrontmatter(raw, sourcePath, copyTarget); let parsedYaml; try { - parsedYaml = (0, import_yaml4.parse)(yamlBlock); + parsedYaml = (0, import_yaml5.parse)(yamlBlock); } catch (err) { throw new MalformedRiskTieringSpecError({ sourcePath, @@ -28973,8 +29087,8 @@ function parseRiskTieringSpec(raw, sourcePath, copyTarget) { // src/state/lookup-risk-tiering-spec.ts async function lookupRiskTieringSpec(opts) { - const overridePath = path13.join(opts.targetRepoRoot, "docs", "risk-tiering.md"); - const defaultPath = path13.join(opts.pluginRoot, "docs", "risk-tiering.md"); + const overridePath = path14.join(opts.targetRepoRoot, "docs", "risk-tiering.md"); + const defaultPath = path14.join(opts.pluginRoot, "docs", "risk-tiering.md"); try { const raw = await fs11.readFile(overridePath, "utf8"); return parseRiskTieringSpec(raw, overridePath, defaultPath); @@ -28996,7 +29110,7 @@ async function lookupRiskTieringSpec(opts) { // src/lib/detect-change-types.ts var import_picomatch = __toESM(require_picomatch2(), 1); -import * as path14 from "node:path"; +import * as path15 from "node:path"; var isMigrationPath = (0, import_picomatch.default)(["**/migrations/**", "**/migration/**"]); var isSchemaPath = (0, import_picomatch.default)(["**/schema.sql", "**/schema.prisma", "**/schema.graphql", "**/*.sql"]); var DEP_BUMP_BASENAMES = /* @__PURE__ */ new Set([ @@ -29014,7 +29128,7 @@ var DEP_BUMP_BASENAMES = /* @__PURE__ */ new Set([ ]); function classifyPath(filePath) { const types = []; - const basename4 = path14.basename(filePath); + const basename4 = path15.basename(filePath); if (isMigrationPath(filePath)) { types.push("migration"); } @@ -29161,7 +29275,7 @@ async function classifyRiskTier(opts) { // src/tools/classify-story-lane.ts var import_picomatch3 = __toESM(require_picomatch2(), 1); import { promises as fs12 } from "node:fs"; -import * as path15 from "node:path"; +import * as path16 from "node:path"; var SECURITY_SENSITIVE_PATTERNS = [ /migrations?\//i, /\.sql$/i, @@ -29309,7 +29423,7 @@ function applyHint(classified, authorHint, fullResultFn) { } async function matchSpecialistByCitedSources(citedSources, targetRepoRoot) { if (citedSources.length === 0) return null; - const teamDir = path15.join(targetRepoRoot, "team"); + const teamDir = path16.join(targetRepoRoot, "team"); let entries; try { entries = await fs12.readdir(teamDir); @@ -29322,7 +29436,7 @@ async function matchSpecialistByCitedSources(citedSources, targetRepoRoot) { for (const entry of entries.sort()) { if (SKIP_DIRS.has(entry) || entry.startsWith(".")) continue; if (BACKBONE_ROLES.has(entry)) continue; - const personaPath = path15.join(teamDir, entry, "PERSONA.md"); + const personaPath = path16.join(teamDir, entry, "PERSONA.md"); let raw; try { raw = await fs12.readFile(personaPath, "utf8"); @@ -29408,9 +29522,9 @@ function isEnoentError(err) { } // src/state/manifest-state-machine.ts -var import_yaml5 = __toESM(require_dist(), 1); +var import_yaml6 = __toESM(require_dist(), 1); import { rename, mkdir, stat, readFile, unlink } from "node:fs/promises"; -import * as path16 from "node:path"; +import * as path17 from "node:path"; var STATE_NAMES = [ "to-do", "in-progress", @@ -29436,12 +29550,12 @@ async function moveBetweenStates(opts) { reason: "unknown state name" }); } - const stateRoot = path16.join(targetRepoRoot, ".flow", "state"); - const absFromPath = path16.join(stateRoot, from, ref + ".yaml"); - const absToPath = path16.join(stateRoot, to, ref + ".yaml"); + const stateRoot = path17.join(targetRepoRoot, ".flow", "state"); + const absFromPath = path17.join(stateRoot, from, ref + ".yaml"); + const absToPath = path17.join(stateRoot, to, ref + ".yaml"); for (const absPath of [absFromPath, absToPath]) { - const rel = path16.relative(stateRoot, absPath); - if (rel.startsWith("..") || path16.isAbsolute(rel)) { + const rel = path17.relative(stateRoot, absPath); + if (rel.startsWith("..") || path17.isAbsolute(rel)) { throw new InvalidStateNameError({ attemptedFrom: from, attemptedTo: to, @@ -29450,7 +29564,7 @@ async function moveBetweenStates(opts) { }); } } - await fsImpl.mkdir(path16.dirname(absToPath), { recursive: true }); + await fsImpl.mkdir(path17.dirname(absToPath), { recursive: true }); try { await fsImpl.rename(absFromPath, absToPath); } catch (err) { @@ -29492,7 +29606,7 @@ function operatorFieldsEqual(a2, b) { return true; } function snapshotPath(targetRepoRoot, ref) { - return path16.join(targetRepoRoot, ".flow", "state", "in-progress", `${ref}.snapshot.yaml`); + return path17.join(targetRepoRoot, ".flow", "state", "in-progress", `${ref}.snapshot.yaml`); } async function writeInProgressSnapshot(opts) { const { targetRepoRoot, ref, manifest } = opts; @@ -29506,7 +29620,7 @@ async function writeInProgressSnapshot(opts) { depends_on: manifest.depends_on, withdrawn: manifest.withdrawn }; - const yamlText = (0, import_yaml5.stringify)(snapshot, { lineWidth: 0 }); + const yamlText = (0, import_yaml6.stringify)(snapshot, { lineWidth: 0 }); await atomicWriteFile(absPath, yamlText); return { absPath }; } @@ -29526,7 +29640,7 @@ async function readInProgressSnapshot(opts) { const absPath = snapshotPath(targetRepoRoot, ref); try { const raw = await readFile(absPath, "utf8"); - const parsed = (0, import_yaml5.parse)(raw); + const parsed = (0, import_yaml6.parse)(raw); return parsed; } catch (err) { const code = err?.code; @@ -29536,7 +29650,7 @@ async function readInProgressSnapshot(opts) { } async function detectInProgressHandEdit(opts) { const { targetRepoRoot, ref } = opts; - const absPath = path16.join(targetRepoRoot, ".flow", "state", "in-progress", ref + ".yaml"); + const absPath = path17.join(targetRepoRoot, ".flow", "state", "in-progress", ref + ".yaml"); let rawText; try { rawText = await readFile(absPath, "utf8"); @@ -29551,7 +29665,7 @@ async function detectInProgressHandEdit(opts) { } throw err; } - const parsed = (0, import_yaml5.parse)(rawText); + const parsed = (0, import_yaml6.parse)(rawText); const manifest = parseExecutionManifest(parsed, { absPath }); const snapshot = await readInProgressSnapshot({ targetRepoRoot, ref }); if (snapshot === null) { @@ -29655,8 +29769,8 @@ async function statOrNull2(absPath) { } } function repoRelativePath(rawPath, targetRepoRoot) { - const rel = path17.relative(targetRepoRoot, rawPath); - if (rel.startsWith("..") || path17.isAbsolute(rel)) { + const rel = path18.relative(targetRepoRoot, rawPath); + if (rel.startsWith("..") || path18.isAbsolute(rel)) { return rawPath; } return rel; @@ -29724,7 +29838,7 @@ async function writeDepsDriftBlockedManifest(story, driftDetail, absBlockedPath, ] }); const blockedManifest = ExecutionManifestSchema.parse(blockedManifestRaw); - const yamlText = (0, import_yaml6.stringify)(blockedManifest, { lineWidth: 0 }); + const yamlText = (0, import_yaml7.stringify)(blockedManifest, { lineWidth: 0 }); await writeManagedFile({ absPath: absBlockedPath, contents: yamlText, @@ -29815,10 +29929,10 @@ async function scanSources(opts) { filesSeenCount: listingStats.filesSeenCount, filesRejected: listingStats.filesRejected }; - const stateRoot = path17.join(targetRepoRoot, ".flow", "state"); + const stateRoot = path18.join(targetRepoRoot, ".flow", "state"); { - const toDoDir = path17.join(stateRoot, "to-do"); - const blockedDir = path17.join(stateRoot, "blocked"); + const toDoDir = path18.join(stateRoot, "to-do"); + const blockedDir = path18.join(stateRoot, "blocked"); let toDoFiles = []; let blockedFiles = []; try { @@ -29837,14 +29951,14 @@ async function scanSources(opts) { console.warn( `[scanSources] Ref ${ref} exists in both to-do/ and blocked/ \u2014 recovering by removing stale blocked/ manifest (to-do/ wins).` ); - await fs13.unlink(path17.join(blockedDir, blockedFile)); + await fs13.unlink(path18.join(blockedDir, blockedFile)); } } } for (const story of sourceStories) { let currentState = null; for (const stateName of STATE_NAMES) { - const absPath = path17.join(stateRoot, stateName, `${story.ref}.yaml`); + const absPath = path18.join(stateRoot, stateName, `${story.ref}.yaml`); const s = await statOrNull2(absPath); if (s !== null) { currentState = stateName; @@ -29859,9 +29973,9 @@ async function scanSources(opts) { continue; } if (currentState === "blocked") { - const absBlockedPath = path17.join(stateRoot, "blocked", `${story.ref}.yaml`); + const absBlockedPath = path18.join(stateRoot, "blocked", `${story.ref}.yaml`); const rawBlocked = await fs13.readFile(absBlockedPath, "utf8"); - const parsedBlocked = (0, import_yaml6.parse)(rawBlocked); + const parsedBlocked = (0, import_yaml7.parse)(rawBlocked); const existingBlockedHash = parsedBlocked["source_hash"]; if (existingBlockedHash === story.source_hash) { result.skippedRefs.push({ ref: story.ref, reason: "not-in-to-do" }); @@ -29891,10 +30005,10 @@ async function scanSources(opts) { } const disciplineResult = await runFullDisciplineGate(story, activeAdapter, targetRepoRoot); if (disciplineResult === null) { - const absToDoPathNew = path17.join(stateRoot, "to-do", `${story.ref}.yaml`); + const absToDoPathNew = path18.join(stateRoot, "to-do", `${story.ref}.yaml`); const riskFields = await computeAuthorTimeRiskFields(story, targetRepoRoot, pluginRoot); const manifest = composeManifest(story, activeAdapterName, targetRepoRoot, riskFields); - const yamlText = (0, import_yaml6.stringify)(manifest, { lineWidth: 0 }); + const yamlText = (0, import_yaml7.stringify)(manifest, { lineWidth: 0 }); await writeManagedFile({ absPath: absToDoPathNew, contents: yamlText, @@ -29928,7 +30042,7 @@ async function scanSources(opts) { })) }); const blockedManifest = ExecutionManifestSchema.parse(blockedManifestRaw); - const yamlText = (0, import_yaml6.stringify)(blockedManifest, { lineWidth: 0 }); + const yamlText = (0, import_yaml7.stringify)(blockedManifest, { lineWidth: 0 }); await writeManagedFile({ absPath: absBlockedPath, contents: yamlText, @@ -29948,7 +30062,7 @@ async function scanSources(opts) { if (currentState === null) { const driftDetail = await checkDepsDrift(story); if (driftDetail !== null) { - const absBlockedPath = path17.join(stateRoot, "blocked", `${story.ref}.yaml`); + const absBlockedPath = path18.join(stateRoot, "blocked", `${story.ref}.yaml`); await writeDepsDriftBlockedManifest( story, driftDetail, @@ -30001,8 +30115,8 @@ async function scanSources(opts) { })) }); const blockedManifest = ExecutionManifestSchema.parse(blockedManifestRaw); - const absBlockedPath = path17.join(stateRoot, "blocked", `${story.ref}.yaml`); - const yamlText = (0, import_yaml6.stringify)(blockedManifest, { lineWidth: 0 }); + const absBlockedPath = path18.join(stateRoot, "blocked", `${story.ref}.yaml`); + const yamlText = (0, import_yaml7.stringify)(blockedManifest, { lineWidth: 0 }); await writeManagedFile({ absPath: absBlockedPath, contents: yamlText, @@ -30013,11 +30127,11 @@ async function scanSources(opts) { continue; } } - const absToDoPath = path17.join(stateRoot, "to-do", `${story.ref}.yaml`); + const absToDoPath = path18.join(stateRoot, "to-do", `${story.ref}.yaml`); if (currentState === null) { const riskFields = await computeAuthorTimeRiskFields(story, targetRepoRoot, pluginRoot); const manifest = composeManifest(story, activeAdapterName, targetRepoRoot, riskFields); - const yamlText = (0, import_yaml6.stringify)(manifest, { lineWidth: 0 }); + const yamlText = (0, import_yaml7.stringify)(manifest, { lineWidth: 0 }); await writeManagedFile({ absPath: absToDoPath, contents: yamlText, @@ -30040,7 +30154,7 @@ async function scanSources(opts) { } let existingManifest; try { - const parsed = (0, import_yaml6.parse)(rawText); + const parsed = (0, import_yaml7.parse)(rawText); existingManifest = parseExecutionManifest(parsed, { absPath: absToDoPath }); } catch (err) { const detailMessage = err instanceof Error ? err.message : String(err); @@ -30054,7 +30168,7 @@ async function scanSources(opts) { if (existingManifest.source_hash !== story.source_hash) { const driftDetail = await checkDepsDrift(story); if (driftDetail !== null) { - const absBlockedPath = path17.join(stateRoot, "blocked", `${story.ref}.yaml`); + const absBlockedPath = path18.join(stateRoot, "blocked", `${story.ref}.yaml`); await writeDepsDriftBlockedManifest( story, driftDetail, @@ -30081,7 +30195,7 @@ async function scanSources(opts) { source_hash: story.source_hash, source_path: repoRelativePath(story.raw_path, targetRepoRoot) }; - const yamlText = (0, import_yaml6.stringify)(stripUndefined(updatedManifest), { + const yamlText = (0, import_yaml7.stringify)(stripUndefined(updatedManifest), { lineWidth: 0 }); await writeManagedFile({ @@ -30284,7 +30398,7 @@ function resolveDefaultDefinitionOfDone(targetRepoRoot, execSyncImpl, existsImpl return PROJECT_AGNOSTIC_DEFINITION_OF_DONE; } const checkExists = existsImpl ?? existsSync2; - const sentinelPath = path18.join(targetRepoRoot, FLOW_REPO_DISK_SENTINEL); + const sentinelPath = path19.join(targetRepoRoot, FLOW_REPO_DISK_SENTINEL); if (checkExists(sentinelPath)) { return FLOW_DEFINITION_OF_DONE; } @@ -30358,7 +30472,7 @@ function renderNativeStoryBody(input, resolvedDod = FLOW_DEFINITION_OF_DONE) { } async function writeNativeStory(rawInput, seams) { const input = WriteNativeStoryInputSchema.parse(rawInput); - const targetRepoRoot = path18.resolve(input.targetRepoRoot); + const targetRepoRoot = path19.resolve(input.targetRepoRoot); const workspace = await resolveWorkspace({ targetRepoRoot }); if (workspace.activeAdapterName !== "native") { throw new WrongAdapterError({ @@ -30385,8 +30499,8 @@ async function writeNativeStory(rawInput, seams) { } async function renderGateWriteNativeStory(input, targetRepoRoot, agent = "author", execSyncImpl, existsImpl) { const newUlid = ulid3(); - const storiesDir = path18.join(targetRepoRoot, ".flow", "native-stories"); - const absPath = path18.join(storiesDir, `${newUlid}.md`); + const storiesDir = path19.join(targetRepoRoot, ".flow", "native-stories"); + const absPath = path19.join(storiesDir, `${newUlid}.md`); const ref = `native:${newUlid}`; const resolvedDod = resolveDefaultDefinitionOfDone(targetRepoRoot, execSyncImpl, existsImpl); const candidate = inputToSourceStory(input, ref, absPath, resolvedDod); @@ -30470,9 +30584,9 @@ function inputToSourceStory(input, ref, absPath, resolvedDod = FLOW_DEFINITION_O } // src/tools/read-backlog-inventory.ts -var import_yaml7 = __toESM(require_dist(), 1); +var import_yaml8 = __toESM(require_dist(), 1); import { promises as fs14 } from "node:fs"; -import * as path19 from "node:path"; +import * as path20 from "node:path"; var ReadBacklogInventoryInputSchema = external_exports.object({ targetRepoRoot: external_exports.string().min(1), /** @@ -30498,15 +30612,15 @@ function extractH1Title(content, fallback) { } async function readBacklogInventory(rawInput) { const input = ReadBacklogInventoryInputSchema.parse(rawInput); - const targetRepoRoot = path19.resolve(input.targetRepoRoot); + const targetRepoRoot = path20.resolve(input.targetRepoRoot); const workspace = await resolveWorkspace({ targetRepoRoot }); const isNative = workspace.activeAdapterName === "native"; - const stateRoot = path19.join(targetRepoRoot, ".flow", "state"); - const doneDir = path19.join(stateRoot, "done"); + const stateRoot = path20.join(targetRepoRoot, ".flow", "state"); + const doneDir = path20.join(stateRoot, "done"); const inventory = []; const seenRefs = /* @__PURE__ */ new Set(); for (const stateName of STATE_NAMES) { - const stateDir = path19.join(stateRoot, stateName); + const stateDir = path20.join(stateRoot, stateName); let entries; try { entries = await fs14.readdir(stateDir); @@ -30515,14 +30629,14 @@ async function readBacklogInventory(rawInput) { } for (const filename of entries) { if (!filename.endsWith(".yaml") || filename.endsWith(".snapshot.yaml")) continue; - const absPath = path19.join(stateDir, filename); + const absPath = path20.join(stateDir, filename); const rawText = await fs14.readFile(absPath, "utf8"); - const parsed = (0, import_yaml7.parse)(rawText); + const parsed = (0, import_yaml8.parse)(rawText); const manifest = parseExecutionManifest(parsed, { absPath }); let depsReady = true; for (const dep of manifest.depends_on) { try { - await fs14.stat(path19.join(doneDir, `${dep}.yaml`)); + await fs14.stat(path20.join(doneDir, `${dep}.yaml`)); } catch { depsReady = false; break; @@ -30537,7 +30651,7 @@ async function readBacklogInventory(rawInput) { depsReady }; if (input.includeSpecText && (!input.ref || manifest.ref === input.ref)) { - const specAbs = path19.isAbsolute(manifest.source_path) ? manifest.source_path : path19.join(targetRepoRoot, manifest.source_path); + const specAbs = path20.isAbsolute(manifest.source_path) ? manifest.source_path : path20.join(targetRepoRoot, manifest.source_path); try { entry.specText = await fs14.readFile(specAbs, "utf8"); } catch { @@ -30549,7 +30663,7 @@ async function readBacklogInventory(rawInput) { } } if (isNative) { - const nativeStoriesDir2 = path19.join(targetRepoRoot, ".flow", "native-stories"); + const nativeStoriesDir2 = path20.join(targetRepoRoot, ".flow", "native-stories"); let nativeFiles; try { nativeFiles = await fs14.readdir(nativeStoriesDir2); @@ -30562,7 +30676,7 @@ async function readBacklogInventory(rawInput) { if (!ULID_PATTERN.test(basename4)) continue; const ref = `native:${basename4}`; if (seenRefs.has(ref)) continue; - const absPath = path19.join(nativeStoriesDir2, filename); + const absPath = path20.join(nativeStoriesDir2, filename); const content = await fs14.readFile(absPath, "utf8"); const title = extractH1Title(content, basename4); const entry = { @@ -30585,7 +30699,7 @@ async function readBacklogInventory(rawInput) { } // src/lib/lesson-archive.ts -import * as path20 from "node:path"; +import * as path21 from "node:path"; import { promises as fs15 } from "node:fs"; var LESSON_BLOCK_PREFIX = ""; @@ -30689,7 +30803,7 @@ async function archiveLessons(targetRepoRoot, role, lessons, now = () => /* @__P for (const lesson of lessons) { const archived = { ...lesson, archived_at: archivedAt }; const relPath = `team/${role}/_archived/${lesson.id}.json`; - const absPath = path20.join(targetRepoRoot, relPath); + const absPath = path21.join(targetRepoRoot, relPath); await writeManagedFile({ absPath, contents: JSON.stringify(archived, null, 2) + "\n", @@ -30764,7 +30878,7 @@ function selectRetirableLessons(lessons, opts = {}) { return candidates; } async function findArchivedLessonById(targetRepoRoot, role, id) { - const absPath = path20.join(targetRepoRoot, "team", role, "_archived", `${id}.json`); + const absPath = path21.join(targetRepoRoot, "team", role, "_archived", `${id}.json`); let raw; try { raw = await fs15.readFile(absPath, "utf8"); @@ -30805,7 +30919,7 @@ async function findArchivedLessonById(targetRepoRoot, role, id) { } // src/lib/persona-file.ts -var import_yaml9 = __toESM(require_dist(), 1); +var import_yaml10 = __toESM(require_dist(), 1); // src/schemas/catalogue.ts var ModelTierSchema = external_exports.enum(["opus", "sonnet", "haiku"]); @@ -30917,7 +31031,7 @@ var REQUIRED_PERSONA_SECTIONS = [ ]; // src/lib/markdown-frontmatter.ts -var import_yaml8 = __toESM(require_dist(), 1); +var import_yaml9 = __toESM(require_dist(), 1); function splitFrontmatter(raw, sourcePath) { const text = raw.replace(/^/, "").replace(/\r\n/g, "\n"); if (!text.startsWith("---\n") && !text.startsWith("---\r")) { @@ -30942,7 +31056,7 @@ function parseCatalogueRole(raw, sourcePath) { const { frontmatterRaw, body } = splitFrontmatter(raw, sourcePath); let parsedYaml; try { - parsedYaml = (0, import_yaml8.parse)(frontmatterRaw); + parsedYaml = (0, import_yaml9.parse)(frontmatterRaw); } catch (err) { throw new CatalogueShapeError({ sourcePath, @@ -31014,7 +31128,7 @@ function parsePersonaFile(raw, sourcePath) { } let parsedYaml; try { - parsedYaml = (0, import_yaml9.parse)(frontmatterRaw); + parsedYaml = (0, import_yaml10.parse)(frontmatterRaw); } catch (err) { throw new PersonaFileMalformedError({ personaPath: sourcePath, @@ -31081,7 +31195,7 @@ function renderPersonaFile(opts) { hired_at: hiredAt, catalogue_version: catalogueVersion }; - const yamlBlock = (0, import_yaml9.stringify)(frontmatter).replace(/\n$/, ""); + const yamlBlock = (0, import_yaml10.stringify)(frontmatter).replace(/\n$/, ""); const h1 = toDisplayName(catalogue.role); const sections = [ `# ${h1}`, @@ -31182,7 +31296,7 @@ function formatZodIssues4(issues) { } // src/tools/record-maintainer-feedback.ts -import * as path21 from "node:path"; +import * as path22 from "node:path"; // src/schemas/maintainer-feedback.ts var MaintainerFeedbackItemSchema = external_exports.object({ @@ -31222,11 +31336,11 @@ function parseMaintainerFeedbackInput(input) { } // src/tools/record-maintainer-feedback.ts -var INBOX_SUBDIR = path21.join(".flow", "maintainer-inbox"); +var INBOX_SUBDIR = path22.join(".flow", "maintainer-inbox"); function maintainerInboxItemPath(targetRepoRoot, id, raisedAt) { const safeTs = raisedAt.replace(/[:.]/g, "-"); const filename = `${safeTs}-${id}.json`; - return path21.join(targetRepoRoot, INBOX_SUBDIR, filename); + return path22.join(targetRepoRoot, INBOX_SUBDIR, filename); } async function recordMaintainerFeedback(opts) { const { targetRepoRoot, item } = opts; @@ -31268,11 +31382,11 @@ async function recordMaintainerFeedback(opts) { // src/tools/analyze-team-fit.ts import { promises as fs18 } from "node:fs"; -import * as path24 from "node:path"; +import * as path25 from "node:path"; // src/tools/judge-panel.ts import { promises as fs17 } from "node:fs"; -import * as path23 from "node:path"; +import * as path24 from "node:path"; // src/schemas/lens-verdict.ts var LENS_NAMES = [ @@ -31302,7 +31416,7 @@ var PanelVerdictSchema = external_exports.object({ // src/lib/read-reviewer-result-file.ts import { promises as fs16 } from "node:fs"; -import * as path22 from "node:path"; +import * as path23 from "node:path"; function sanitiseRefForPathSegment(ref) { const replaced = ref.replace(/[^A-Za-z0-9._-]/g, "_"); if (replaced === "" || replaced === "." || replaced === "..") { @@ -31311,7 +31425,7 @@ function sanitiseRefForPathSegment(ref) { return replaced; } function reviewerResultFilePath(targetRepoRoot, sessionUlid, ref) { - return path22.join( + return path23.join( targetRepoRoot, ".flow", "state", @@ -31368,7 +31482,7 @@ async function readReviewerResultFile(targetRepoRoot, sessionUlid, ref) { // src/tools/judge-panel.ts function lensVerdictFilePath(targetRepoRoot, sessionUlid, ref, lens) { - return path23.join( + return path24.join( targetRepoRoot, ".flow", "state", @@ -31583,7 +31697,7 @@ async function writeLensVerdict(opts) { const { targetRepoRoot, sessionUlid, ref, lens, role, pass, missed } = opts; const verdict = LensVerdictSchema.parse({ lens, role, pass, missed }); const resultFilePath = lensVerdictFilePath(targetRepoRoot, sessionUlid, ref, lens); - await fs17.mkdir(path23.dirname(resultFilePath), { recursive: true }); + await fs17.mkdir(path24.dirname(resultFilePath), { recursive: true }); await atomicWriteFile(resultFilePath, JSON.stringify(verdict, null, 2)); return { resultFilePath }; } @@ -31623,7 +31737,7 @@ var AnalyzeTeamFitInputSchema = external_exports.object({ }); var MONTH_BUCKET_REGEX = /^\d{4}-\d{2}\.jsonl$/; async function readTelemetrySummary(targetRepoRoot) { - const telemetryDir = path24.join(targetRepoRoot, ".flow", "telemetry"); + const telemetryDir = path25.join(targetRepoRoot, ".flow", "telemetry"); const stallsByDomain = /* @__PURE__ */ new Map(); const usefulWorkByRole = /* @__PURE__ */ new Map(); let entries; @@ -31634,7 +31748,7 @@ async function readTelemetrySummary(targetRepoRoot) { } for (const entry of entries) { if (!MONTH_BUCKET_REGEX.test(entry)) continue; - const filePath = path24.join(telemetryDir, entry); + const filePath = path25.join(telemetryDir, entry); let raw; try { raw = await fs18.readFile(filePath, "utf8"); @@ -31669,7 +31783,7 @@ async function readTelemetrySummary(targetRepoRoot) { return { stallsByDomain, usefulWorkByRole }; } async function readHiredRoles(targetRepoRoot) { - const teamDir = path24.join(targetRepoRoot, "team"); + const teamDir = path25.join(targetRepoRoot, "team"); let dirEntries; try { dirEntries = await fs18.readdir(teamDir); @@ -31682,13 +31796,13 @@ async function readHiredRoles(targetRepoRoot) { if (SKIP_DIRS.has(entry) || entry.startsWith(".")) continue; let stat2; try { - stat2 = await fs18.stat(path24.join(teamDir, entry)); + stat2 = await fs18.stat(path25.join(teamDir, entry)); } catch { continue; } if (!stat2.isDirectory()) continue; try { - await fs18.access(path24.join(teamDir, entry, "PERSONA.md")); + await fs18.access(path25.join(teamDir, entry, "PERSONA.md")); } catch { continue; } @@ -31700,7 +31814,7 @@ async function readHiredRoles(targetRepoRoot) { async function buildAvailableRoleSet(targetRepoRoot, pluginRoot) { const domainToRole = /* @__PURE__ */ new Map(); const availableRoleIds = /* @__PURE__ */ new Set(); - const catalogueDir = path24.join(pluginRoot, "catalogue"); + const catalogueDir = path25.join(pluginRoot, "catalogue"); let catalogueEntries = []; try { catalogueEntries = await fs18.readdir(catalogueDir); @@ -31711,14 +31825,14 @@ async function buildAvailableRoleSet(targetRepoRoot, pluginRoot) { const roleId = entry.slice(0, -3); if (GENERALIST_BACKBONE_ROLES.has(roleId)) continue; try { - const raw = await fs18.readFile(path24.join(catalogueDir, entry), "utf8"); - const role = parseCatalogueRole(raw, path24.join(catalogueDir, entry)); + const raw = await fs18.readFile(path25.join(catalogueDir, entry), "utf8"); + const role = parseCatalogueRole(raw, path25.join(catalogueDir, entry)); domainToRole.set(role.domain, role.role); availableRoleIds.add(role.role); } catch { } } - const customDir = path24.join(targetRepoRoot, "team", "custom"); + const customDir = path25.join(targetRepoRoot, "team", "custom"); let customEntries = []; try { customEntries = await fs18.readdir(customDir); @@ -31729,8 +31843,8 @@ async function buildAvailableRoleSet(targetRepoRoot, pluginRoot) { const roleId = entry.slice(0, -3); if (GENERALIST_BACKBONE_ROLES.has(roleId)) continue; try { - const raw = await fs18.readFile(path24.join(customDir, entry), "utf8"); - const role = parseCatalogueRole(raw, path24.join(customDir, entry)); + const raw = await fs18.readFile(path25.join(customDir, entry), "utf8"); + const role = parseCatalogueRole(raw, path25.join(customDir, entry)); domainToRole.set(role.domain, role.role); availableRoleIds.add(role.role); } catch { @@ -31890,7 +32004,7 @@ async function gatherRetroInputs(opts) { return { doneManifests, telemetrySummary, priorProposals, ruleRegistry, fireCountSignal, recurringFriction, skillEffectiveness, mechanicalFailuresDrafted, nearDuplicateLessonPairs, retirableLessons, crossRoleSharedLessons, teamFitSignal }; } async function gatherDoneManifests(targetRepoRoot, windowStartMs) { - const doneDir = path25.join(targetRepoRoot, ".flow", "state", "done"); + const doneDir = path26.join(targetRepoRoot, ".flow", "state", "done"); let entries; try { entries = await fs19.readdir(doneDir); @@ -31903,7 +32017,7 @@ async function gatherDoneManifests(targetRepoRoot, windowStartMs) { const manifestFiles = entries.filter((f) => f.endsWith(".yaml") && !f.endsWith(".snapshot.yaml")).sort(); const manifests = []; for (const file2 of manifestFiles) { - const absPath = path25.join(doneDir, file2); + const absPath = path26.join(doneDir, file2); if (windowStartMs !== null) { const stat2 = await fs19.stat(absPath); if (stat2.mtimeMs < windowStartMs) { @@ -31911,13 +32025,13 @@ async function gatherDoneManifests(targetRepoRoot, windowStartMs) { } } const raw = await fs19.readFile(absPath, "utf8"); - const parsed = (0, import_yaml10.parse)(raw); + const parsed = (0, import_yaml11.parse)(raw); manifests.push(parseExecutionManifest(parsed, { absPath })); } return manifests; } async function gatherTelemetry(targetRepoRoot, windowStartMs) { - const telemetryDir = path25.join(targetRepoRoot, ".flow", "telemetry"); + const telemetryDir = path26.join(targetRepoRoot, ".flow", "telemetry"); let entries; try { entries = await fs19.readdir(telemetryDir); @@ -31931,7 +32045,7 @@ async function gatherTelemetry(targetRepoRoot, windowStartMs) { const events = []; let skipped_count = 0; for (const file2 of files) { - const absPath = path25.join(telemetryDir, file2); + const absPath = path26.join(telemetryDir, file2); const raw = await fs19.readFile(absPath, "utf8"); const lines = raw.split("\n"); for (const line of lines) { @@ -31959,7 +32073,7 @@ async function gatherTelemetry(targetRepoRoot, windowStartMs) { return { events, skipped_count }; } async function gatherPriorProposals(targetRepoRoot) { - const proposalsDir = path25.join(targetRepoRoot, ".flow", "retro-proposals"); + const proposalsDir = path26.join(targetRepoRoot, ".flow", "retro-proposals"); let entries; try { entries = await fs19.readdir(proposalsDir); @@ -31970,7 +32084,7 @@ async function gatherPriorProposals(targetRepoRoot) { throw err; } const proposals = entries.filter((f) => f.endsWith(".md")).map((f) => ({ - path: path25.join(proposalsDir, f), + path: path26.join(proposalsDir, f), // The writer keys the filename by ISO timestamp (Story 6.3): // `.md`. Strip the `.md` suffix to recover it. iso_timestamp: f.slice(0, -".md".length) @@ -31979,7 +32093,7 @@ async function gatherPriorProposals(targetRepoRoot) { return proposals; } async function gatherRuleRegistry(targetRepoRoot) { - const registryPath = path25.join( + const registryPath = path26.join( targetRepoRoot, "docs", "discipline-rules.yaml" @@ -32140,7 +32254,7 @@ function recurringFrictionDedupKey(kind) { return `${RECURRING_FRICTION_DEDUP_PREFIX}${kind}`; } async function readExistingDedupKeys(targetRepoRoot) { - const inboxDir = path25.join(targetRepoRoot, ".flow", "maintainer-inbox"); + const inboxDir = path26.join(targetRepoRoot, ".flow", "maintainer-inbox"); let entries; try { entries = await fs19.readdir(inboxDir); @@ -32151,7 +32265,7 @@ async function readExistingDedupKeys(targetRepoRoot) { const keys = /* @__PURE__ */ new Set(); for (const file2 of entries) { if (!file2.endsWith(".json")) continue; - const absPath = path25.join(inboxDir, file2); + const absPath = path26.join(inboxDir, file2); let raw; try { raw = await fs19.readFile(absPath, "utf8"); @@ -32252,7 +32366,7 @@ function tokenise(text) { } var NEAR_DUPLICATE_THRESHOLD = 0.35; async function gatherNearDuplicateLessonPairs(targetRepoRoot) { - const teamDir = path25.join(targetRepoRoot, "team"); + const teamDir = path26.join(targetRepoRoot, "team"); let roleEntries; try { roleEntries = await fs19.readdir(teamDir); @@ -32266,12 +32380,12 @@ async function gatherNearDuplicateLessonPairs(targetRepoRoot) { if (SKIP_DIRS.has(entry) || entry.startsWith(".")) continue; let stat2; try { - stat2 = await fs19.stat(path25.join(teamDir, entry)); + stat2 = await fs19.stat(path26.join(teamDir, entry)); } catch { continue; } if (!stat2.isDirectory()) continue; - const personaPath = path25.join(teamDir, entry, "PERSONA.md"); + const personaPath = path26.join(teamDir, entry, "PERSONA.md"); let raw; try { raw = await fs19.readFile(personaPath, "utf8"); @@ -32311,7 +32425,7 @@ async function gatherNearDuplicateLessonPairs(targetRepoRoot) { return pairs; } async function gatherRetirableLessons(targetRepoRoot, opts = {}) { - const teamDir = path25.join(targetRepoRoot, "team"); + const teamDir = path26.join(targetRepoRoot, "team"); let roleEntries; try { roleEntries = await fs19.readdir(teamDir); @@ -32325,12 +32439,12 @@ async function gatherRetirableLessons(targetRepoRoot, opts = {}) { if (SKIP_DIRS.has(entry) || entry.startsWith(".")) continue; let stat2; try { - stat2 = await fs19.stat(path25.join(teamDir, entry)); + stat2 = await fs19.stat(path26.join(teamDir, entry)); } catch { continue; } if (!stat2.isDirectory()) continue; - const personaPath = path25.join(teamDir, entry, "PERSONA.md"); + const personaPath = path26.join(teamDir, entry, "PERSONA.md"); let raw; try { raw = await fs19.readFile(personaPath, "utf8"); @@ -32354,7 +32468,7 @@ async function gatherRetirableLessons(targetRepoRoot, opts = {}) { } var CROSS_ROLE_SIMILARITY_THRESHOLD = 0.35; async function gatherCrossRoleSharedLessons(targetRepoRoot) { - const teamDir = path25.join(targetRepoRoot, "team"); + const teamDir = path26.join(targetRepoRoot, "team"); let roleEntries; try { roleEntries = await fs19.readdir(teamDir); @@ -32368,12 +32482,12 @@ async function gatherCrossRoleSharedLessons(targetRepoRoot) { if (SKIP_DIRS.has(entry) || entry.startsWith(".")) continue; let stat2; try { - stat2 = await fs19.stat(path25.join(teamDir, entry)); + stat2 = await fs19.stat(path26.join(teamDir, entry)); } catch { continue; } if (!stat2.isDirectory()) continue; - const personaPath = path25.join(teamDir, entry, "PERSONA.md"); + const personaPath = path26.join(teamDir, entry, "PERSONA.md"); let raw; try { raw = await fs19.readFile(personaPath, "utf8"); @@ -32498,7 +32612,7 @@ async function archivePriorCycle(targetRepoRoot, priorCycle, openedAt) { done_manifests: inputs.doneManifests, retro_proposals: inputs.priorProposals.map((p) => ({ // Store repo-relative paths so the archive is portable. - path: path26.relative(targetRepoRoot, p.path), + path: path27.relative(targetRepoRoot, p.path), iso_timestamp: p.iso_timestamp })), telemetry_summary: { @@ -32507,8 +32621,8 @@ async function archivePriorCycle(targetRepoRoot, priorCycle, openedAt) { } }; const fileName = `${priorCycle.cycle_ulid}-${isoForFilename(openedAt)}.yaml`; - const absPath = path26.join(targetRepoRoot, ".flow", "cycle-archive", fileName); - await atomicWriteFile(absPath, (0, import_yaml11.stringify)(archiveRecord, { lineWidth: 0 })); + const absPath = path27.join(targetRepoRoot, ".flow", "cycle-archive", fileName); + await atomicWriteFile(absPath, (0, import_yaml12.stringify)(archiveRecord, { lineWidth: 0 })); return absPath; } @@ -32583,10 +32697,10 @@ function runPhaseDone(args) { // src/tools/create-smoke-scratch-repo.ts import { promises as fs20 } from "node:fs"; import * as os from "node:os"; -import * as path33 from "node:path"; +import * as path34 from "node:path"; // src/lib/git.ts -import * as path32 from "node:path"; +import * as path33 from "node:path"; import { promises as fsPromises } from "node:fs"; // ../node_modules/.pnpm/is-plain-obj@4.1.0/node_modules/is-plain-obj/index.js @@ -33434,12 +33548,12 @@ var handleCommand = (filePath, rawArguments, rawOptions) => { // ../node_modules/.pnpm/execa@9.6.1/node_modules/execa/lib/arguments/options.js var import_cross_spawn = __toESM(require_cross_spawn(), 1); -import path31 from "node:path"; +import path32 from "node:path"; import process7 from "node:process"; // ../node_modules/.pnpm/npm-run-path@6.0.0/node_modules/npm-run-path/index.js import process5 from "node:process"; -import path28 from "node:path"; +import path29 from "node:path"; // ../node_modules/.pnpm/path-key@4.0.0/node_modules/path-key/index.js function pathKey(options = {}) { @@ -33456,7 +33570,7 @@ function pathKey(options = {}) { // ../node_modules/.pnpm/unicorn-magic@0.3.0/node_modules/unicorn-magic/node.js import { promisify } from "node:util"; import { execFile as execFileCallback, execFileSync as execFileSyncOriginal } from "node:child_process"; -import path27 from "node:path"; +import path28 from "node:path"; import { fileURLToPath as fileURLToPath3 } from "node:url"; var execFileOriginal = promisify(execFileCallback); function toPath(urlOrPath) { @@ -33465,12 +33579,12 @@ function toPath(urlOrPath) { function traversePathUp(startPath) { return { *[Symbol.iterator]() { - let currentPath = path27.resolve(toPath(startPath)); + let currentPath = path28.resolve(toPath(startPath)); let previousPath; while (previousPath !== currentPath) { yield currentPath; previousPath = currentPath; - currentPath = path27.resolve(currentPath, ".."); + currentPath = path28.resolve(currentPath, ".."); } } }; @@ -33485,27 +33599,27 @@ var npmRunPath = ({ execPath: execPath2 = process5.execPath, addExecPath = true } = {}) => { - const cwdPath = path28.resolve(toPath(cwd)); + const cwdPath = path29.resolve(toPath(cwd)); const result = []; - const pathParts = pathOption.split(path28.delimiter); + const pathParts = pathOption.split(path29.delimiter); if (preferLocal) { applyPreferLocal(result, pathParts, cwdPath); } if (addExecPath) { applyExecPath(result, pathParts, execPath2, cwdPath); } - return pathOption === "" || pathOption === path28.delimiter ? `${result.join(path28.delimiter)}${pathOption}` : [...result, pathOption].join(path28.delimiter); + return pathOption === "" || pathOption === path29.delimiter ? `${result.join(path29.delimiter)}${pathOption}` : [...result, pathOption].join(path29.delimiter); }; var applyPreferLocal = (result, pathParts, cwdPath) => { for (const directory of traversePathUp(cwdPath)) { - const pathPart = path28.join(directory, "node_modules/.bin"); + const pathPart = path29.join(directory, "node_modules/.bin"); if (!pathParts.includes(pathPart)) { result.push(pathPart); } } }; var applyExecPath = (result, pathParts, execPath2, cwdPath) => { - const pathPart = path28.resolve(cwdPath, toPath(execPath2), ".."); + const pathPart = path29.resolve(cwdPath, toPath(execPath2), ".."); if (!pathParts.includes(pathPart)) { result.push(pathPart); } @@ -34137,8 +34251,8 @@ var disconnect = (anyProcess) => { // ../node_modules/.pnpm/execa@9.6.1/node_modules/execa/lib/utils/deferred.js var createDeferred = () => { const methods = {}; - const promise2 = new Promise((resolve19, reject) => { - Object.assign(methods, { resolve: resolve19, reject }); + const promise2 = new Promise((resolve20, reject) => { + Object.assign(methods, { resolve: resolve20, reject }); }); return Object.assign(promise2, methods); }; @@ -34660,7 +34774,7 @@ var killAfterTimeout = async (subprocess, timeout, context, { signal }) => { // ../node_modules/.pnpm/execa@9.6.1/node_modules/execa/lib/methods/node.js import { execPath, execArgv } from "node:process"; -import path29 from "node:path"; +import path30 from "node:path"; var mapNode = ({ options }) => { if (options.node === false) { throw new TypeError('The "node" option cannot be false with `execaNode()`.'); @@ -34679,7 +34793,7 @@ var handleNodeOption = (file2, commandArguments, { throw new TypeError('The "execPath" option has been removed. Please use the "nodePath" option instead.'); } const normalizedNodePath = safeNormalizeFileUrl(nodePath, 'The "nodePath" option'); - const resolvedNodePath = path29.resolve(cwd, normalizedNodePath); + const resolvedNodePath = path30.resolve(cwd, normalizedNodePath); const newOptions = { ...options, nodePath: resolvedNodePath, @@ -34689,7 +34803,7 @@ var handleNodeOption = (file2, commandArguments, { if (!shouldHandleNode) { return [file2, commandArguments, newOptions]; } - if (path29.basename(file2, ".exe") === "node") { + if (path30.basename(file2, ".exe") === "node") { throw new TypeError('When the "node" option is true, the first argument does not need to be "node".'); } return [ @@ -34779,11 +34893,11 @@ var serializeEncoding = (encoding) => typeof encoding === "string" ? `"${encodin // ../node_modules/.pnpm/execa@9.6.1/node_modules/execa/lib/arguments/cwd.js import { statSync as statSync2 } from "node:fs"; -import path30 from "node:path"; +import path31 from "node:path"; import process6 from "node:process"; var normalizeCwd = (cwd = getDefaultCwd()) => { const cwdString = safeNormalizeFileUrl(cwd, 'The "cwd" option'); - return path30.resolve(cwdString); + return path31.resolve(cwdString); }; var getDefaultCwd = () => { try { @@ -34830,7 +34944,7 @@ var normalizeOptions = (filePath, rawArguments, rawOptions) => { options.killSignal = normalizeKillSignal(options.killSignal); options.forceKillAfterDelay = normalizeForceKillAfterDelay(options.forceKillAfterDelay); options.lines = options.lines.map((lines, fdNumber) => lines && !BINARY_ENCODINGS.has(options.encoding) && options.buffer[fdNumber]); - if (process7.platform === "win32" && path31.basename(file2, ".exe") === "cmd") { + if (process7.platform === "win32" && path32.basename(file2, ".exe") === "cmd") { commandArguments.unshift("/q"); } return { file: file2, commandArguments, options }; @@ -35805,7 +35919,7 @@ var handleResult = (result, verboseInfo, { reject }) => { }; // ../node_modules/.pnpm/execa@9.6.1/node_modules/execa/lib/stdio/handle-sync.js -import { readFileSync as readFileSync3 } from "node:fs"; +import { readFileSync as readFileSync4 } from "node:fs"; // ../node_modules/.pnpm/execa@9.6.1/node_modules/execa/lib/stdio/type.js var getStdioItemType = (value, optionName) => { @@ -36135,7 +36249,7 @@ var normalizeStdioSync = (stdioArray, buffer, verboseInfo) => stdioArray.map((st var isOutputPipeOnly = (stdioOption) => stdioOption === "pipe" || Array.isArray(stdioOption) && stdioOption.every((item) => item === "pipe"); // ../node_modules/.pnpm/execa@9.6.1/node_modules/execa/lib/stdio/native.js -import { readFileSync as readFileSync2 } from "node:fs"; +import { readFileSync as readFileSync3 } from "node:fs"; import tty2 from "node:tty"; var handleNativeStream = ({ stdioItem, stdioItem: { type }, isStdioArray, fdNumber, direction, isSync }) => { if (!isStdioArray || type !== "native") { @@ -36169,7 +36283,7 @@ var getTargetFd = ({ value, optionName, fdNumber, direction }) => { if (tty2.isatty(targetFdNumber)) { throw new TypeError(`The \`${optionName}: ${serializeOptionValue(value)}\` option is invalid: it cannot be a TTY with synchronous methods.`); } - return { type: "uint8Array", value: bufferToUint8Array(readFileSync2(targetFdNumber)), optionName }; + return { type: "uint8Array", value: bufferToUint8Array(readFileSync3(targetFdNumber)), optionName }; }; var getTargetFdNumber = (value, fdNumber) => { if (value === "inherit") { @@ -36502,8 +36616,8 @@ var addProperties = { var addPropertiesSync = { input: { ...addProperties, - fileUrl: ({ value }) => ({ contents: [bufferToUint8Array(readFileSync3(value))] }), - filePath: ({ value: { file: file2 } }) => ({ contents: [bufferToUint8Array(readFileSync3(file2))] }), + fileUrl: ({ value }) => ({ contents: [bufferToUint8Array(readFileSync4(value))] }), + filePath: ({ value: { file: file2 } }) => ({ contents: [bufferToUint8Array(readFileSync4(file2))] }), fileNumber: forbiddenIfSync, iterable: ({ value }) => ({ contents: [...value] }), string: ({ value }) => ({ contents: [value] }), @@ -36970,13 +37084,13 @@ var logOutputSync = ({ serializedResult, fdNumber, state, verboseInfo, encoding, } }; var writeToFiles = (serializedResult, stdioItems, outputFiles) => { - for (const { path: path85, append } of stdioItems.filter(({ type }) => FILE_TYPES.has(type))) { - const pathString = typeof path85 === "string" ? path85 : path85.toString(); + for (const { path: path86, append } of stdioItems.filter(({ type }) => FILE_TYPES.has(type))) { + const pathString = typeof path86 === "string" ? path86 : path86.toString(); if (append || outputFiles.has(pathString)) { - appendFileSync(path85, serializedResult); + appendFileSync(path86, serializedResult); } else { outputFiles.add(pathString); - writeFileSync(path85, serializedResult); + writeFileSync(path86, serializedResult); } } }; @@ -38780,11 +38894,11 @@ var addConcurrentStream = (concurrentStreams, stream, waitName) => { const promises = weakMap.get(stream); const promise2 = createDeferred(); promises.push(promise2); - const resolve19 = promise2.resolve.bind(promise2); - return { resolve: resolve19, promises }; + const resolve20 = promise2.resolve.bind(promise2); + return { resolve: resolve20, promises }; }; -var waitForConcurrentStreams = async ({ resolve: resolve19, promises }, subprocess) => { - resolve19(); +var waitForConcurrentStreams = async ({ resolve: resolve20, promises }, subprocess) => { + resolve20(); const [isSubprocessExit] = await Promise.race([ Promise.allSettled([true, subprocess]), Promise.all([false, ...promises]) @@ -39415,7 +39529,7 @@ function gitLockBackoffMs(attempt, random = Math.random) { return Math.floor(random() * window2); } function defaultGitLockSleep(ms) { - return new Promise((resolve19) => setTimeout(resolve19, ms)); + return new Promise((resolve20) => setTimeout(resolve20, ms)); } function isGitLockContention(value) { const stderr = typeof value === "string" ? value : String( @@ -39609,7 +39723,7 @@ async function resolveSessionLedgerRoot(opts) { if ((result.exitCode ?? 1) !== 0) return opts.cwd; const commonDir = (typeof result.stdout === "string" ? result.stdout : "").trim(); if (!commonDir) return opts.cwd; - return path32.dirname(commonDir); + return path33.dirname(commonDir); } async function listDirtyPaths(opts) { const execaImpl = opts.execaImpl ?? execa; @@ -39719,7 +39833,7 @@ async function checkSharedRootLeak(opts) { cwd: worktreeCwd, ...execaImpl ? { execaImpl } : {} }); - if (path32.resolve(sharedRoot) === path32.resolve(worktreeCwd)) { + if (path33.resolve(sharedRoot) === path33.resolve(worktreeCwd)) { return { leaked: false, paths: [], sharedRootPath: sharedRoot }; } if (committedPaths.length === 0) { @@ -39780,7 +39894,7 @@ async function checkStagedArtifactLeakGate(opts) { reason: `staged path "${relPath}" is inside a dependency folder (node_modules). Dependency folders must never be committed \u2014 add "node_modules" (bare, no trailing slash) to .gitignore so both directory and symlink forms are excluded` }; } - const absPath = path32.isAbsolute(relPath) ? relPath : path32.join(targetRepoRoot, relPath); + const absPath = path33.isAbsolute(relPath) ? relPath : path33.join(targetRepoRoot, relPath); try { const stat2 = await lstat(absPath); if (stat2.isSymbolicLink()) { @@ -39819,23 +39933,23 @@ var CreateSmokeScratchRepoOptionsSchema = external_exports.object({ async function createSmokeScratchRepo(opts) { const parsed = CreateSmokeScratchRepoOptionsSchema.parse(opts); const { label, parentDir } = parsed; - const standardsTemplatePath = path33.resolve( + const standardsTemplatePath = path34.resolve( getPluginRoot(), "docs", "standards-example.md" ); const scratchRoot = await fs20.mkdtemp( - path33.join(parentDir ?? os.tmpdir(), `flow-smoke-${label}-`) + path34.join(parentDir ?? os.tmpdir(), `flow-smoke-${label}-`) ); await gitInitWithEmptyCommit({ cwd: scratchRoot }); await writeManagedFile({ - absPath: path33.join(scratchRoot, ".flow", "config.yaml"), + absPath: path34.join(scratchRoot, ".flow", "config.yaml"), contents: "adapter: native\nstandards: {}\n", targetRepoRoot: scratchRoot }); const standardsContents = await fs20.readFile(standardsTemplatePath, "utf8"); await writeManagedFile({ - absPath: path33.join(scratchRoot, ".flow", "standards.md"), + absPath: path34.join(scratchRoot, ".flow", "standards.md"), contents: standardsContents, targetRepoRoot: scratchRoot }); @@ -39847,13 +39961,13 @@ async function createSmokeScratchRepo(opts) { // src/tools/instantiate-persona.ts import { promises as fs23 } from "node:fs"; -import * as path36 from "node:path"; +import * as path37 from "node:path"; // src/tools/read-catalogue.ts import { promises as fs21 } from "node:fs"; -import * as path34 from "node:path"; +import * as path35 from "node:path"; async function readCatalogue(opts) { - const cataloguePath = path34.join(opts.pluginRoot, "catalogue", `${opts.role}.md`); + const cataloguePath = path35.join(opts.pluginRoot, "catalogue", `${opts.role}.md`); let raw; try { raw = await fs21.readFile(cataloguePath, "utf8"); @@ -39874,7 +39988,7 @@ function isEnoent2(err) { // src/tools/read-custom-role.ts import { promises as fs22 } from "node:fs"; -import * as path35 from "node:path"; +import * as path36 from "node:path"; var KEBAB_CASE = /^[a-z0-9-]+$/; async function readCustomRole(opts) { if (!KEBAB_CASE.test(opts.role)) { @@ -39883,7 +39997,7 @@ async function readCustomRole(opts) { zodMessage: `role id '${opts.role}' does not match the required kebab-case shape /^[a-z0-9-]+$/` }); } - const customPath = path35.join( + const customPath = path36.join( opts.targetRepoRoot, "team", "custom", @@ -39918,13 +40032,13 @@ function isEnoent3(err) { async function instantiatePersona(opts) { const clock = opts.clock ?? (() => /* @__PURE__ */ new Date()); const pluginVersion = opts.pluginVersion ?? getPluginVersion(); - const customPath = path36.join( + const customPath = path37.join( opts.targetRepoRoot, "team", "custom", `${opts.role}.md` ); - const cataloguePath = path36.join( + const cataloguePath = path37.join( opts.pluginRoot, "catalogue", `${opts.role}.md` @@ -39956,7 +40070,7 @@ async function instantiatePersona(opts) { throw err; } } - const personaPath = path36.join( + const personaPath = path37.join( opts.targetRepoRoot, "team", opts.role, @@ -39996,14 +40110,14 @@ function isEnoent4(err) { } // src/tools/build-persona-spawn-prompt.ts -import * as path38 from "node:path"; +import * as path39 from "node:path"; import { promises as fs25 } from "node:fs"; // src/tools/read-persona.ts import { promises as fs24 } from "node:fs"; -import * as path37 from "node:path"; +import * as path38 from "node:path"; async function readPersona(opts) { - const personaPath = path37.join(opts.targetRepoRoot, "team", opts.role, "PERSONA.md"); + const personaPath = path38.join(opts.targetRepoRoot, "team", opts.role, "PERSONA.md"); let raw; try { raw = await fs24.readFile(personaPath, "utf8"); @@ -40023,10 +40137,10 @@ function isEnoent5(err) { } // src/lib/apply-promote-lesson-to-skill.ts -var import_yaml13 = __toESM(require_dist(), 1); +var import_yaml14 = __toESM(require_dist(), 1); // src/lib/apply-skill-proposal.ts -var import_yaml12 = __toESM(require_dist(), 1); +var import_yaml13 = __toESM(require_dist(), 1); // src/schemas/skill-frontmatter.ts var SemverSchema = external_exports.string().regex(/^\d+\.\d+\.\d+$/, "must be semver 'x.y.z'"); @@ -40072,7 +40186,7 @@ function extractSkillRefs(skillsBody) { } // src/tools/build-persona-spawn-prompt.ts -var import_yaml14 = __toESM(require_dist(), 1); +var import_yaml15 = __toESM(require_dist(), 1); var TOOL_NAME = "buildPersonaSpawnPrompt"; async function buildPersonaSpawnPrompt(opts) { const { @@ -40087,7 +40201,7 @@ async function buildPersonaSpawnPrompt(opts) { if (overflow.length > 0) { await archiveLessons(targetRepoRoot, role, overflow, now); const rankedBody = rebuildBodyWithTopLessons(knowledgeBody, topLessons); - const personaPath = path38.join(targetRepoRoot, "team", role, "PERSONA.md"); + const personaPath = path39.join(targetRepoRoot, "team", role, "PERSONA.md"); const rawPersona = await fs25.readFile(personaPath, "utf8"); const parsed = parsePersonaFile(rawPersona, personaPath); const newContents = reconstructPersonaFileWithKnowledge(parsed, rankedBody); @@ -40115,7 +40229,7 @@ function reconstructPersonaFileWithKnowledge(parsed, newKnowledgeBody) { hired_at: parsed.hired_at, catalogue_version: parsed.catalogue_version }; - const yamlBlock = (0, import_yaml14.stringify)(frontmatter).replace(/\n$/, ""); + const yamlBlock = (0, import_yaml15.stringify)(frontmatter).replace(/\n$/, ""); const h1 = parsed.role.split("-").map( (part) => part.length === 0 ? part : part[0].toUpperCase() + part.slice(1) ).join(" "); @@ -40252,9 +40366,9 @@ function toDisplayName2(role) { } // src/tools/list-claimable-todos.ts -var import_yaml15 = __toESM(require_dist(), 1); +var import_yaml16 = __toESM(require_dist(), 1); import { promises as fs26 } from "node:fs"; -import * as path39 from "node:path"; +import * as path40 from "node:path"; // src/lib/short-handle.ts function shortHandle(ref) { @@ -40269,10 +40383,10 @@ function shortHandle(ref) { // src/tools/list-claimable-todos.ts async function listClaimableTodos(opts) { const { targetRepoRoot } = opts; - const stateRoot = path39.join(targetRepoRoot, ".flow", "state"); - const todoDir = path39.join(stateRoot, "to-do"); - const inProgressDir = path39.join(stateRoot, "in-progress"); - const doneDir = path39.join(stateRoot, "done"); + const stateRoot = path40.join(targetRepoRoot, ".flow", "state"); + const todoDir = path40.join(stateRoot, "to-do"); + const inProgressDir = path40.join(stateRoot, "in-progress"); + const doneDir = path40.join(stateRoot, "done"); let todoEntries; try { todoEntries = await fs26.readdir(todoDir); @@ -40286,7 +40400,7 @@ async function listClaimableTodos(opts) { const yamlEntries = todoEntries.filter((f) => f.endsWith(".yaml")).sort(); const candidates = []; for (const entry of yamlEntries) { - const absPath = path39.join(todoDir, entry); + const absPath = path40.join(todoDir, entry); let raw; try { raw = await fs26.readFile(absPath, "utf8"); @@ -40296,14 +40410,14 @@ async function listClaimableTodos(opts) { } throw err; } - const parsed = (0, import_yaml15.parse)(raw); + const parsed = (0, import_yaml16.parse)(raw); const manifest = parseExecutionManifest(parsed, { absPath }); if (!isClaimable(manifest)) { continue; } let depsReady = true; for (const dep of manifest.depends_on) { - const depPath = path39.join(doneDir, `${dep}.yaml`); + const depPath = path40.join(doneDir, `${dep}.yaml`); try { await fs26.stat(depPath); } catch (err) { @@ -40341,12 +40455,12 @@ function isEnoent6(err) { } // src/tools/claim-next-story.ts -import * as path44 from "node:path"; +import * as path45 from "node:path"; // src/tools/claim-story.ts -var import_yaml16 = __toESM(require_dist(), 1); +var import_yaml17 = __toESM(require_dist(), 1); import { promises as fs27 } from "node:fs"; -import * as path40 from "node:path"; +import * as path41 from "node:path"; function stripUndefined2(obj) { return Object.fromEntries( Object.entries(obj).filter(([, v]) => v !== void 0) @@ -40354,9 +40468,9 @@ function stripUndefined2(obj) { } async function claimStory(opts) { const { targetRepoRoot, ref, sessionUlid, role = "orchestrator" } = opts; - const stateRoot = path40.join(targetRepoRoot, ".flow", "state"); - const absToDoPath = path40.join(stateRoot, "to-do", `${ref}.yaml`); - const absInProgressPath = path40.join(stateRoot, "in-progress", `${ref}.yaml`); + const stateRoot = path41.join(targetRepoRoot, ".flow", "state"); + const absToDoPath = path41.join(stateRoot, "to-do", `${ref}.yaml`); + const absInProgressPath = path41.join(stateRoot, "in-progress", `${ref}.yaml`); let crashRecoveryRebaselined = false; try { await fs27.stat(absInProgressPath); @@ -40366,7 +40480,7 @@ async function claimStory(opts) { if (code === "ENOENT") { } else if (err instanceof InProgressHandEditError && err.changedFields.length === 1 && err.changedFields[0] === "_snapshot_missing") { const rawInProgress = await fs27.readFile(absInProgressPath, "utf8"); - const parsedInProgress = (0, import_yaml16.parse)(rawInProgress); + const parsedInProgress = (0, import_yaml17.parse)(rawInProgress); const inProgressManifest = parseExecutionManifest(parsedInProgress, { absPath: absInProgressPath }); @@ -40401,11 +40515,11 @@ async function claimStory(opts) { } throw err; } - const parsed = (0, import_yaml16.parse)(rawText); + const parsed = (0, import_yaml17.parse)(rawText); const manifest = parseExecutionManifest(parsed, { absPath: absToDoPath }); const missingDeps = []; for (const dep of manifest.depends_on) { - const depPath = path40.join(stateRoot, "done", `${dep}.yaml`); + const depPath = path41.join(stateRoot, "done", `${dep}.yaml`); try { await fs27.stat(depPath); } catch (err) { @@ -40434,7 +40548,7 @@ async function claimStory(opts) { const reparsed = parseExecutionManifest(updatedManifest, { absPath: absInProgressPath }); - const yamlText = (0, import_yaml16.stringify)( + const yamlText = (0, import_yaml17.stringify)( stripUndefined2(reparsed), { lineWidth: 0 } ); @@ -40450,8 +40564,8 @@ async function claimStory(opts) { // src/lib/dep-merge-check.ts import { promises as fs28 } from "node:fs"; -import * as path41 from "node:path"; -var import_yaml17 = __toESM(require_dist(), 1); +import * as path42 from "node:path"; +var import_yaml18 = __toESM(require_dist(), 1); // src/lib/pr-body.ts function buildBranchSlug(opts) { @@ -40637,7 +40751,7 @@ async function isDependencyPrMerged(opts) { } async function areDependenciesMerged(opts) { const isMerged = opts.isMerged ?? isDependencyPrMerged; - const doneDir = path41.join(opts.targetRepoRoot, ".flow", "state", "done"); + const doneDir = path42.join(opts.targetRepoRoot, ".flow", "state", "done"); const seen = /* @__PURE__ */ new Map(); for (const dep of opts.deps) { const cached2 = seen.get(dep); @@ -40645,7 +40759,7 @@ async function areDependenciesMerged(opts) { if (!cached2) return false; continue; } - const depPath = path41.join(doneDir, `${dep}.yaml`); + const depPath = path42.join(doneDir, `${dep}.yaml`); let raw; try { raw = await fs28.readFile(depPath, "utf8"); @@ -40656,7 +40770,7 @@ async function areDependenciesMerged(opts) { let title; let prNumber; try { - const manifest = parseExecutionManifest((0, import_yaml17.parse)(raw), { absPath: depPath }); + const manifest = parseExecutionManifest((0, import_yaml18.parse)(raw), { absPath: depPath }); title = manifest.title; prNumber = manifest.pr_number; } catch { @@ -40688,7 +40802,7 @@ async function isOverlapBlockerInFlight(opts) { } async function anyOverlapBlockerInFlight(opts) { const isInFlight = opts.isInFlight ?? isOverlapBlockerInFlight; - const doneDir = path41.join(opts.targetRepoRoot, ".flow", "state", "done"); + const doneDir = path42.join(opts.targetRepoRoot, ".flow", "state", "done"); const seen = /* @__PURE__ */ new Map(); for (const ref of opts.blockers) { const cached2 = seen.get(ref); @@ -40696,7 +40810,7 @@ async function anyOverlapBlockerInFlight(opts) { if (cached2) return true; continue; } - const depPath = path41.join(doneDir, `${ref}.yaml`); + const depPath = path42.join(doneDir, `${ref}.yaml`); let raw; try { raw = await fs28.readFile(depPath, "utf8"); @@ -40706,7 +40820,7 @@ async function anyOverlapBlockerInFlight(opts) { } let prNumber; try { - const manifest = parseExecutionManifest((0, import_yaml17.parse)(raw), { + const manifest = parseExecutionManifest((0, import_yaml18.parse)(raw), { absPath: depPath }); prNumber = manifest.pr_number; @@ -40729,15 +40843,15 @@ async function anyOverlapBlockerInFlight(opts) { } // src/lib/cited-source-overlap.ts -var import_yaml18 = __toESM(require_dist(), 1); +var import_yaml19 = __toESM(require_dist(), 1); import { promises as fs29 } from "node:fs"; -import * as path42 from "node:path"; +import * as path43 from "node:path"; var STATE_DIRS = ["to-do", "in-progress", "done"]; async function loadOverlapUniverse(targetRepoRoot) { - const stateRoot = path42.join(targetRepoRoot, ".flow", "state"); + const stateRoot = path43.join(targetRepoRoot, ".flow", "state"); const stories = []; for (const location of STATE_DIRS) { - const dir = path42.join(stateRoot, location); + const dir = path43.join(stateRoot, location); let entries; try { entries = await fs29.readdir(dir); @@ -40749,13 +40863,13 @@ async function loadOverlapUniverse(targetRepoRoot) { if (!entry.endsWith(".yaml") || entry.endsWith(".snapshot.yaml")) continue; let raw; try { - raw = await fs29.readFile(path42.join(dir, entry), "utf8"); + raw = await fs29.readFile(path43.join(dir, entry), "utf8"); } catch { continue; } let doc; try { - doc = (0, import_yaml18.parse)(raw); + doc = (0, import_yaml19.parse)(raw); } catch { continue; } @@ -40803,11 +40917,11 @@ function isEnoent7(err) { } // src/lib/session-liveness.ts -import * as path43 from "node:path"; +import * as path44 from "node:path"; import { promises as fs30 } from "node:fs"; var HEARTBEAT_STALE_MS = 30 * 6e4; function heartbeatFilePath(targetRepoRoot, sessionUlid) { - return path43.join( + return path44.join( targetRepoRoot, ".flow", "state", @@ -41025,7 +41139,7 @@ async function claimNextStory(opts) { throw err; } if (claimSucceeded) { - const manifestPath = path44.resolve( + const manifestPath = path45.resolve( targetRepoRoot, ".flow", "state", @@ -41067,7 +41181,7 @@ async function claimNextStory(opts) { } // src/tools/process-dev-transcript.ts -import * as path46 from "node:path"; +import * as path47 from "node:path"; // src/skills/handoff-parser.ts var HANDOFF_PHRASE_TEMPLATE = "Handoff to reviewer \u2014 story ready for review."; @@ -41091,23 +41205,23 @@ function parseHandoff(transcript, expectedRef) { } // src/lib/manifest-io.ts -var import_yaml19 = __toESM(require_dist(), 1); +var import_yaml20 = __toESM(require_dist(), 1); import { promises as fs31 } from "node:fs"; async function readManifest(absPath) { const raw = await fs31.readFile(absPath, "utf8"); - const parsed = (0, import_yaml19.parse)(raw); + const parsed = (0, import_yaml20.parse)(raw); return parseExecutionManifest(parsed, { absPath }); } async function writeManifest(absPath, manifest) { - const yaml = (0, import_yaml19.stringify)(manifest, { lineWidth: 0 }); + const yaml = (0, import_yaml20.stringify)(manifest, { lineWidth: 0 }); await atomicWriteFile(absPath, yaml); } // src/lib/read-dev-outcome-file.ts import { promises as fs32 } from "node:fs"; -import * as path45 from "node:path"; +import * as path46 from "node:path"; function devOutcomeFilePath(targetRepoRoot, sessionUlid, ref) { - return path45.join( + return path46.join( targetRepoRoot, ".flow", "state", @@ -41187,7 +41301,7 @@ async function processDevTranscript(opts) { await writeSessionHeartbeat(targetRepoRoot, sessionUlid); } catch { } - const manifestPath = path46.resolve( + const manifestPath = path47.resolve( targetRepoRoot, ".flow", "state", @@ -41310,7 +41424,7 @@ async function findOpenPrForRef(opts) { } // src/tools/run-dev-terminal-action.ts -import * as path50 from "node:path"; +import * as path51 from "node:path"; // src/lib/extract-acs-from-spec.ts import { promises as fs33 } from "node:fs"; @@ -41343,9 +41457,9 @@ async function extractAcsFromSpec(specPath) { } // src/lib/gh-error-map.ts -var import_yaml20 = __toESM(require_dist(), 1); +var import_yaml21 = __toESM(require_dist(), 1); import { promises as fs34 } from "node:fs"; -import * as path47 from "node:path"; +import * as path48 from "node:path"; // src/schemas/gh-error-map.ts var GhErrorMapEntrySchema = external_exports.object({ @@ -41361,7 +41475,7 @@ var GhErrorMapSchema = external_exports.object({ var cache = /* @__PURE__ */ new Map(); async function parseGhErrorMap(filePath) { const raw = await fs34.readFile(filePath, "utf8"); - const parsed = (0, import_yaml20.parse)(raw); + const parsed = (0, import_yaml21.parse)(raw); const result = GhErrorMapSchema.safeParse(parsed); if (!result.success) { const firstIssue = result.error.issues[0]; @@ -41404,7 +41518,7 @@ async function parseGhErrorMap(filePath) { return { entries }; } async function loadGhErrorMap(pluginRoot) { - const absPath = path47.resolve(pluginRoot, "permissions", "gh-error-map.yaml"); + const absPath = path48.resolve(pluginRoot, "permissions", "gh-error-map.yaml"); const cached2 = cache.get(absPath); if (cached2 !== void 0) { return cached2; @@ -41486,9 +41600,9 @@ async function gh(opts) { } // src/state/load-role-permissions.ts -var import_yaml21 = __toESM(require_dist(), 1); +var import_yaml22 = __toESM(require_dist(), 1); import { promises as fs35 } from "node:fs"; -import * as path48 from "node:path"; +import * as path49 from "node:path"; // src/schemas/role-permissions.ts var RolePermissionsSchema = external_exports.object({ @@ -41506,7 +41620,7 @@ function formatZodIssues5(issues) { return `${dottedPath}: ${first.message}`; } async function loadRolePermissions(opts) { - const specPath = path48.join(opts.pluginRoot, "permissions", `${opts.role}.yaml`); + const specPath = path49.join(opts.pluginRoot, "permissions", `${opts.role}.yaml`); let raw; try { raw = await fs35.readFile(specPath, "utf8"); @@ -41518,7 +41632,7 @@ async function loadRolePermissions(opts) { } let parsedYaml; try { - parsedYaml = (0, import_yaml21.parse)(raw); + parsedYaml = (0, import_yaml22.parse)(raw); } catch (err) { throw new RolePermissionsMalformedError({ specPath, @@ -41536,12 +41650,12 @@ async function loadRolePermissions(opts) { } // src/lib/run-project-build.ts -import * as path49 from "node:path"; +import * as path50 from "node:path"; var DEFAULT_BUILD_TEST_TIMEOUT_MS = 20 * 60 * 1e3; var PROJECT_BUILD_COMMAND = "pnpm"; var PROJECT_BUILD_ARGS = ["build"]; function deriveProjectBuildCwd(devWorkingDir) { - return path49.join(devWorkingDir, "plugins", "flow"); + return path50.join(devWorkingDir, "plugins", "flow"); } async function runProjectBuild(opts) { const execaImpl = opts.execaImpl ?? execa; @@ -41628,7 +41742,7 @@ async function runDevTerminalAction(opts) { } const branch = buildBranchSlug({ ref, title }); const manifest = await readManifest(manifestPath); - const specPath = path50.isAbsolute(manifest.source_path) ? manifest.source_path : path50.join(targetRepoRoot, manifest.source_path); + const specPath = path51.isAbsolute(manifest.source_path) ? manifest.source_path : path51.join(targetRepoRoot, manifest.source_path); const acs = opts.inlineAcs ? opts.inlineAcs : await extractAcsFromSpec(specPath); const gitRoot = targetRepoRoot; let committedPaths = ["."]; @@ -41814,7 +41928,7 @@ async function runDevTerminalAction(opts) { role: ROLE, ...execaImpl ? { execaImpl } : {} }); - const specPathForPr = path50.isAbsolute(manifest.source_path) ? path50.relative(targetRepoRoot, manifest.source_path) : manifest.source_path; + const specPathForPr = path51.isAbsolute(manifest.source_path) ? path51.relative(targetRepoRoot, manifest.source_path) : manifest.source_path; const manifestAcsByIndex = new Map( (manifest.acceptance_criteria ?? []).map((ac, i2) => [i2 + 1, ac]) ); @@ -41889,7 +42003,7 @@ async function runDevTerminalAction(opts) { JSON.stringify({ prUrl, prNumber, branch, commitSha: commitResult.commitSha }, null, 2) ); try { - const inProgressManifestPath = path50.join( + const inProgressManifestPath = path51.join( ledgerRoot, ".flow", "state", @@ -41911,10 +42025,8 @@ async function runDevTerminalAction(opts) { } // src/tools/run-reviewer-session.ts -import * as path52 from "node:path"; +import * as path53 from "node:path"; import * as fs37 from "node:fs/promises"; -import { accessSync, readFileSync as readFileSync4, readdirSync as readdirSync2 } from "node:fs"; -var import_yaml22 = __toESM(require_dist(), 1); // src/lib/slugify-standards-criterion.ts function slugifyStandardsCriterion(name) { @@ -41922,18 +42034,18 @@ function slugifyStandardsCriterion(name) { } // src/lib/materialise-pr-branch-worktree.ts -import * as path51 from "node:path"; +import * as path52 from "node:path"; import * as fs36 from "node:fs/promises"; function reviewWorktreesRoot(targetRepoRoot, sessionUlid) { - return path51.join( - path51.dirname(targetRepoRoot), + return path52.join( + path52.dirname(targetRepoRoot), ".flow-worktrees", sessionUlid ); } function reviewWorktreePath(targetRepoRoot, sessionUlid, storyRef) { const storySlug = sanitiseRefForPathSegment(storyRef); - return path51.join( + return path52.join( reviewWorktreesRoot(targetRepoRoot, sessionUlid), `review-${storySlug}-worktree` ); @@ -41996,7 +42108,7 @@ async function materialisePrBranchWorktree(opts) { ); } const worktreePath = reviewWorktreePath(targetRepoRoot, sessionUlid, storyRef); - await fs36.mkdir(path51.dirname(worktreePath), { recursive: true }); + await fs36.mkdir(path52.dirname(worktreePath), { recursive: true }); let staleExists = false; try { await fs36.access(worktreePath); @@ -42109,7 +42221,7 @@ function classifyAc(bodyLines) { return { applicability: "manual-check-required" }; } async function runArtifactCheck(index, tag, artifactPath, checkRoot) { - const resolved = path52.resolve(checkRoot, artifactPath); + const resolved = path53.resolve(checkRoot, artifactPath); try { await fs37.access(resolved); return { @@ -42142,97 +42254,6 @@ function capString(s) { if (s.length <= STDOUT_STDERR_CAP) return s; return s.slice(0, STDOUT_STDERR_CAP) + TRUNCATION_MARKER; } -function hasLocalVitest(dir) { - try { - accessSync(path52.join(dir, "node_modules", ".bin", "vitest")); - return true; - } catch { - return false; - } -} -function findVitestInWorkspaceMembers(workspaceRoot) { - try { - const yaml = readFileSync4( - path52.join(workspaceRoot, "pnpm-workspace.yaml"), - "utf8" - ); - const parsed = (0, import_yaml22.parse)(yaml); - const packages = parsed?.packages; - if (!Array.isArray(packages)) return { ok: false }; - for (const pattern of packages) { - if (typeof pattern !== "string") continue; - const segments = pattern.split("/"); - const hasGlob = segments.some((s) => s === "*" || s === "**"); - if (!hasGlob) { - const memberDir = path52.join(workspaceRoot, pattern); - if (hasLocalVitest(memberDir)) { - return { ok: true, packageRoot: memberDir }; - } - } else { - const parentSegments = segments.slice(0, segments.indexOf("*")); - const parentDir = path52.join(workspaceRoot, ...parentSegments); - try { - const entries = readdirSync2(parentDir, { withFileTypes: true }); - for (const entry of entries) { - if (!entry.isDirectory()) continue; - const memberDir = path52.join(parentDir, entry.name); - if (hasLocalVitest(memberDir)) { - return { ok: true, packageRoot: memberDir }; - } - } - } catch { - } - } - } - } catch { - } - return { ok: false }; -} -function findWorkspaceYamlInSubtree(root, maxDepth) { - if (maxDepth < 0) return null; - let entries; - try { - entries = readdirSync2(root, { withFileTypes: true }); - } catch { - return null; - } - if (entries.some((e) => e.isFile() && e.name === "pnpm-workspace.yaml")) { - return root; - } - for (const entry of entries) { - if (!entry.isDirectory()) continue; - const name = entry.name; - if (name === "node_modules" || name === ".git") continue; - const found = findWorkspaceYamlInSubtree(path52.join(root, name), maxDepth - 1); - if (found !== null) return found; - } - return null; -} -function findPackageRoot(opts) { - const checkRootAbs = path52.resolve(opts.checkRoot); - let dir = path52.dirname(opts.testFilePathAbs); - const isWithinCheckRoot = (d) => d === checkRootAbs || d.startsWith(checkRootAbs + path52.sep); - while (isWithinCheckRoot(dir)) { - try { - accessSync(path52.join(dir, "package.json")); - const memberResult = findVitestInWorkspaceMembers(dir); - if (memberResult.ok) { - return memberResult; - } - return { ok: true, packageRoot: dir }; - } catch { - } - const parent = path52.dirname(dir); - if (parent === dir) break; - dir = parent; - } - const workspaceYamlDir = findWorkspaceYamlInSubtree(checkRootAbs, 4); - if (workspaceYamlDir !== null) { - const memberResult = findVitestInWorkspaceMembers(workspaceYamlDir); - if (memberResult.ok) return memberResult; - } - return { ok: false }; -} function stripAnsi(s) { return s.replace(/\[[0-9;]*m/g, ""); } @@ -42247,7 +42268,7 @@ function countExecutedTests(output) { return (passed ? Number(passed[1]) : 0) + (failed ? Number(failed[1]) : 0); } async function runVitestCheck(index, tag, testNameFilter, testFilePath, checkRoot, execaImpl) { - const testFilePathAbs = path52.resolve(checkRoot, testFilePath); + const testFilePathAbs = path53.resolve(checkRoot, testFilePath); const pkgRoot = findPackageRoot({ testFilePathAbs, checkRoot }); if (!pkgRoot.ok) { return { @@ -42262,8 +42283,8 @@ async function runVitestCheck(index, tag, testNameFilter, testFilePath, checkRoo exitCode: -1 }; } - const testFilePathAbs2 = path52.resolve(checkRoot, testFilePath); - const relativeToPackage = path52.relative(pkgRoot.packageRoot, testFilePathAbs2); + const testFilePathAbs2 = path53.resolve(checkRoot, testFilePath); + const relativeToPackage = path53.relative(pkgRoot.packageRoot, testFilePathAbs2); const looksLikeFilePath = (testFilePath.includes("/") || testFilePath.includes("\\")) && !relativeToPackage.startsWith("..") && relativeToPackage !== testFilePath; const vitestArgs = looksLikeFilePath ? ["vitest", "--run", relativeToPackage] : ["vitest", "--run", "-t", testNameFilter]; const result = await execaImpl("pnpm", vitestArgs, { @@ -42395,7 +42416,7 @@ async function runReviewerSession(opts) { } catch (err) { if (err instanceof StandardsDocMissingError) { const setupResultFilePath = reviewerResultFilePath(targetRepoRoot, sessionUlid, ref); - await fs37.mkdir(path52.dirname(setupResultFilePath), { recursive: true }); + await fs37.mkdir(path53.dirname(setupResultFilePath), { recursive: true }); const setupFileProjection = { sessionUlid, ref, @@ -42540,7 +42561,7 @@ async function runReviewerSession(opts) { }); } const resultFilePath = reviewerResultFilePath(targetRepoRoot, sessionUlid, ref); - await fs37.mkdir(path52.dirname(resultFilePath), { recursive: true }); + await fs37.mkdir(path53.dirname(resultFilePath), { recursive: true }); const fileProjection = { sessionUlid, ref, @@ -42568,7 +42589,7 @@ async function runReviewerSession(opts) { } // src/tools/post-reviewer-comments.ts -import * as path53 from "node:path"; +import * as path54 from "node:path"; // src/lib/compose-reviewer-summary.ts function composeVerdictLine(result) { @@ -42706,7 +42727,7 @@ async function stampRiskTierOnManifest(targetRepoRoot, ref, riskTier) { if (riskTier === void 0) { return; } - const manifestPath = path53.join( + const manifestPath = path54.join( targetRepoRoot, ".flow", "state", @@ -43036,7 +43057,7 @@ async function applyReviewerLabels(opts) { } // src/tools/run-auto-merge-gate.ts -import * as path55 from "node:path"; +import * as path56 from "node:path"; import { promises as fs39 } from "node:fs"; var import_yaml23 = __toESM(require_dist(), 1); @@ -43066,7 +43087,7 @@ function decideAutoMerge(input) { } // src/tools/compute-agreement.ts -import * as path54 from "node:path"; +import * as path55 from "node:path"; import { promises as fs38 } from "node:fs"; // src/lib/agreement.ts @@ -43103,7 +43124,7 @@ async function computeAgreement(opts) { reason: "must be a positive integer" }); } - const telemetryDir = path54.join(targetRepoRoot, ".flow", "telemetry"); + const telemetryDir = path55.join(targetRepoRoot, ".flow", "telemetry"); let jsonlFiles; try { if (readTelemetryDirImpl) { @@ -43125,7 +43146,7 @@ async function computeAgreement(opts) { const mergeActions = []; let malformed_lines = 0; for (const filename of jsonlFiles) { - const filePath = path54.join(telemetryDir, filename); + const filePath = path55.join(telemetryDir, filename); const raw = readFileImpl ? await readFileImpl(filePath) : await fs38.readFile(filePath, "utf8"); for (const rawLine of raw.split("\n")) { const line = rawLine.trim(); @@ -43243,7 +43264,7 @@ var AutoMergeGateResultSchema = external_exports.object({ chatLog: external_exports.array(external_exports.string()) }).strict(); async function loadWorkspaceConfig(targetRepoRoot) { - const configPath = path55.join(targetRepoRoot, ".flow", "config.yaml"); + const configPath = path56.join(targetRepoRoot, ".flow", "config.yaml"); let raw; try { raw = await fs39.readFile(configPath, "utf8"); @@ -43308,7 +43329,7 @@ async function waitForCiGreen(opts) { return { kind: "ci-status-unreadable", reason }; } if (Date.now() - start >= CI_GATE_TIMEOUT_MS) return "pending-timeout"; - await new Promise((resolve19) => setTimeout(resolve19, CI_GATE_POLL_INTERVAL_MS)); + await new Promise((resolve20) => setTimeout(resolve20, CI_GATE_POLL_INTERVAL_MS)); continue; } let rollup = []; @@ -43325,7 +43346,7 @@ async function waitForCiGreen(opts) { if (state === "green") return "green"; if (state === "failed") return "failed"; if (Date.now() - start >= CI_GATE_TIMEOUT_MS) return "pending-timeout"; - await new Promise((resolve19) => setTimeout(resolve19, CI_GATE_POLL_INTERVAL_MS)); + await new Promise((resolve20) => setTimeout(resolve20, CI_GATE_POLL_INTERVAL_MS)); } } async function runAutoMergeGate(opts) { @@ -43368,7 +43389,7 @@ async function runAutoMergeGate(opts) { pluginSettings = pluginSettings ?? await loadWorkspaceConfigFn(opts.targetRepoRoot); provisional_trust = pluginSettings.provisional_trust; } - const manifestPath = path55.join( + const manifestPath = path56.join( opts.targetRepoRoot, ".flow", "state", @@ -43541,7 +43562,7 @@ async function runAutoMergeGate(opts) { // src/tools/complete-story.ts var import_yaml24 = __toESM(require_dist(), 1); import { promises as fs40 } from "node:fs"; -import * as path56 from "node:path"; +import * as path57 from "node:path"; function stripUndefined3(obj) { return Object.fromEntries( Object.entries(obj).filter(([, v]) => v !== void 0) @@ -43549,9 +43570,9 @@ function stripUndefined3(obj) { } async function completeStory(opts) { const { targetRepoRoot, ref, sessionUlid, role = "orchestrator" } = opts; - const stateRoot = path56.join(targetRepoRoot, ".flow", "state"); - const absInProgressPath = path56.join(stateRoot, "in-progress", `${ref}.yaml`); - const absDonePath = path56.join(stateRoot, "done", `${ref}.yaml`); + const stateRoot = path57.join(targetRepoRoot, ".flow", "state"); + const absInProgressPath = path57.join(stateRoot, "in-progress", `${ref}.yaml`); + const absDonePath = path57.join(stateRoot, "done", `${ref}.yaml`); await detectInProgressHandEdit({ targetRepoRoot, ref }); let rawText; try { @@ -43607,14 +43628,14 @@ async function completeStory(opts) { // src/tools/get-team-snapshot.ts import { promises as fs42 } from "node:fs"; -import * as path58 from "node:path"; +import * as path59 from "node:path"; // src/lib/team-stats.ts import { promises as fs41 } from "node:fs"; -import * as path57 from "node:path"; +import * as path58 from "node:path"; var MONTH_BUCKET_REGEX2 = /^\d{4}-\d{2}\.jsonl$/; async function readTeamTelemetryStats(opts) { - const telemetryDir = path57.join(opts.targetRepoRoot, ".flow", "telemetry"); + const telemetryDir = path58.join(opts.targetRepoRoot, ".flow", "telemetry"); let entries; try { entries = await fs41.readdir(telemetryDir); @@ -43631,7 +43652,7 @@ async function readTeamTelemetryStats(opts) { if (!MONTH_BUCKET_REGEX2.test(entry)) { continue; } - const filePath = path57.join(telemetryDir, entry); + const filePath = path58.join(telemetryDir, entry); const raw = await fs41.readFile(filePath, "utf8"); const lines = raw.split("\n"); let fileHasMalformation = false; @@ -43711,7 +43732,7 @@ var TeamSnapshotSchema = external_exports.object({ async function getTeamSnapshot(opts) { const { targetRepoRoot } = opts; const knowledgeLimit = opts.knowledgeLimit ?? 3; - const teamDir = path58.join(targetRepoRoot, "team"); + const teamDir = path59.join(targetRepoRoot, "team"); let dirEntries; try { dirEntries = await fs42.readdir(teamDir); @@ -43735,7 +43756,7 @@ async function getTeamSnapshot(opts) { } let stat2; try { - stat2 = await fs42.stat(path58.join(teamDir, entry)); + stat2 = await fs42.stat(path59.join(teamDir, entry)); } catch { continue; } @@ -43855,9 +43876,9 @@ function parseYield(transcript) { // src/tools/lookup-role-by-domain.ts import { promises as fs43 } from "node:fs"; -import * as path59 from "node:path"; +import * as path60 from "node:path"; async function lookupRoleByDomain(opts) { - const teamDir = path59.join(opts.targetRepoRoot, "team"); + const teamDir = path60.join(opts.targetRepoRoot, "team"); try { await fs43.stat(teamDir); } catch (err) { @@ -43869,7 +43890,7 @@ async function lookupRoleByDomain(opts) { const entries = await fs43.readdir(teamDir); for (const entry of entries) { if (entry === "custom" || entry === "_archived") continue; - const subPath = path59.join(teamDir, entry); + const subPath = path60.join(teamDir, entry); let isDir = false; try { const stat2 = await fs43.stat(subPath); @@ -43968,13 +43989,13 @@ async function processReviewerYield(opts) { // src/tools/scan-orphaned-in-progress.ts var import_yaml25 = __toESM(require_dist(), 1); import { promises as fs44 } from "node:fs"; -import * as path60 from "node:path"; +import * as path61 from "node:path"; async function scanOrphanedInProgress(opts) { const { targetRepoRoot, sessionUlid } = opts; const execaImpl = opts.execaImpl ?? execa; const aliveCheck = opts.isSessionAliveImpl ?? isSessionAlive; - const inProgressDir = path60.join(targetRepoRoot, ".flow", "state", "in-progress"); - const sessionsDir = path60.join(targetRepoRoot, ".flow", "state", "sessions"); + const inProgressDir = path61.join(targetRepoRoot, ".flow", "state", "in-progress"); + const sessionsDir = path61.join(targetRepoRoot, ".flow", "state", "sessions"); let entries; try { entries = await fs44.readdir(inProgressDir); @@ -43987,7 +44008,7 @@ async function scanOrphanedInProgress(opts) { const yamlEntries = entries.filter((f) => f.endsWith(".yaml") && !f.endsWith(".snapshot.yaml")).sort(); const orphans = []; for (const entry of yamlEntries) { - const absPath = path60.join(inProgressDir, entry); + const absPath = path61.join(inProgressDir, entry); let raw; try { raw = await fs44.readFile(absPath, "utf8"); @@ -44013,7 +44034,7 @@ async function scanOrphanedInProgress(opts) { continue; } const staleUlid = manifest.claimed_by; - const transcriptPath = path60.join( + const transcriptPath = path61.join( sessionsDir, staleUlid, "dev-transcript.txt" @@ -44078,10 +44099,10 @@ function isEnoent11(err) { } // src/tools/reattach-orphan.ts -import * as path61 from "node:path"; +import * as path62 from "node:path"; async function reattachOrphan(opts) { const { targetRepoRoot, ref, currentSessionUlid } = opts; - const absPath = path61.join( + const absPath = path62.join( targetRepoRoot, ".flow", "state", @@ -44120,7 +44141,7 @@ async function reattachOrphan(opts) { } // src/tools/block-orphan-no-transcript.ts -import * as path62 from "node:path"; +import * as path63 from "node:path"; async function blockOrphanNoTranscript(opts) { const { targetRepoRoot, ref, staleUlid } = opts; await removeInProgressSnapshot({ targetRepoRoot, ref }); @@ -44130,7 +44151,7 @@ async function blockOrphanNoTranscript(opts) { from: "in-progress", to: "blocked" }); - const absBlockedPath = path62.join( + const absBlockedPath = path63.join( targetRepoRoot, ".flow", "state", @@ -44152,7 +44173,7 @@ async function blockOrphanNoTranscript(opts) { } // src/lib/dev-story-worktree.ts -import * as path63 from "node:path"; +import * as path64 from "node:path"; import * as fs45 from "node:fs/promises"; async function runGit2(args, cwd, execaImpl) { const result = await execaImpl("git", args, { cwd, reject: false }); @@ -44197,20 +44218,20 @@ async function reapStaleDevStoryWorktrees(opts) { return { reaped, warnings }; } const canonRoot = await realpathOrSelf(targetRepoRoot); - const worktreesParent = path63.join( - path63.dirname(canonRoot), + const worktreesParent = path64.join( + path64.dirname(canonRoot), ".flow-worktrees" ); - const liveSessionDir = path63.join(worktreesParent, currentSessionUlid); + const liveSessionDir = path64.join(worktreesParent, currentSessionUlid); const registered = list.stdout.split("\n").filter((l) => l.startsWith("worktree ")).map((l) => l.slice("worktree ".length).trim()).filter((p) => p.length > 0); for (const wt of registered) { - const rel = path63.relative(worktreesParent, wt); - if (rel.startsWith("..") || path63.isAbsolute(rel)) continue; - const inLiveSession = path63.relative(liveSessionDir, wt); - if (!inLiveSession.startsWith("..") && !path63.isAbsolute(inLiveSession)) { + const rel = path64.relative(worktreesParent, wt); + if (rel.startsWith("..") || path64.isAbsolute(rel)) continue; + const inLiveSession = path64.relative(liveSessionDir, wt); + if (!inLiveSession.startsWith("..") && !path64.isAbsolute(inLiveSession)) { continue; } - const relSegments = rel.split(path63.sep); + const relSegments = rel.split(path64.sep); const owningSessionUlid = relSegments[0]; if (owningSessionUlid) { const ownerAlive = await aliveCheck(targetRepoRoot, owningSessionUlid); @@ -44260,7 +44281,7 @@ async function reapStaleWorktrees(opts) { // src/tools/mark-story-ready.ts var import_yaml27 = __toESM(require_dist(), 1); import { promises as fs46 } from "node:fs"; -import * as path64 from "node:path"; +import * as path65 from "node:path"; // src/tools/mark-withdrawn.ts var import_yaml26 = __toESM(require_dist(), 1); @@ -44294,14 +44315,14 @@ var MarkStoryReadyInputSchema = external_exports.object({ }); async function markStoryReady(rawInput) { const input = MarkStoryReadyInputSchema.parse(rawInput); - const targetRepoRoot = path64.resolve(input.targetRepoRoot); + const targetRepoRoot = path65.resolve(input.targetRepoRoot); const { ref, ready } = input; const sessionUlid = input.sessionUlid ?? "operator"; - const stateRoot = path64.join(targetRepoRoot, ".flow", "state"); + const stateRoot = path65.join(targetRepoRoot, ".flow", "state"); let foundState = null; let foundAbsPath = null; for (const stateName of STATE_NAMES) { - const candidate = path64.join(stateRoot, stateName, `${ref}.yaml`); + const candidate = path65.join(stateRoot, stateName, `${ref}.yaml`); try { await fs46.stat(candidate); foundState = stateName; @@ -44336,7 +44357,7 @@ async function markStoryReady(rawInput) { for (const stateName of STATE_NAMES) { if (stateName === "to-do") continue; try { - await fs46.stat(path64.join(stateRoot, stateName, `${ref}.yaml`)); + await fs46.stat(path65.join(stateRoot, stateName, `${ref}.yaml`)); } catch { continue; } @@ -44357,7 +44378,7 @@ async function markStoryReady(rawInput) { } // src/tools/guard-clean-root.ts -import * as path65 from "node:path"; +import * as path66 from "node:path"; var CONFIG_PATH_PREFIXES = ["team/", "docs/"]; var GuardCleanRootInputSchema = external_exports.object({ targetRepoRoot: external_exports.string().min(1), @@ -44368,7 +44389,7 @@ var GuardCleanRootInputSchema = external_exports.object({ }); async function guardCleanRoot(rawInput) { const input = GuardCleanRootInputSchema.parse(rawInput); - const cwd = path65.resolve(input.targetRepoRoot); + const cwd = path66.resolve(input.targetRepoRoot); const allDirty = await listDirtyPathsWithStatus({ cwd }); const configEdits = []; const leakPaths = []; @@ -44414,7 +44435,7 @@ async function guardCleanRoot(rawInput) { // src/tools/quality-lead-adjudicate.ts import { promises as fs47 } from "node:fs"; -import * as path66 from "node:path"; +import * as path67 from "node:path"; // src/schemas/adjudication-verdict.ts var ADJUDICATION_DECISIONS = ["ready", "escalate", "rework"]; @@ -44467,7 +44488,7 @@ function synthesiseDecision(input) { }; } function adjudicationVerdictFilePath(targetRepoRoot, sessionUlid, ref) { - return path66.join( + return path67.join( targetRepoRoot, ".flow", "state", @@ -44478,7 +44499,7 @@ function adjudicationVerdictFilePath(targetRepoRoot, sessionUlid, ref) { ); } async function adjudicateQualityLead(opts) { - const targetRepoRoot = path66.resolve(opts.targetRepoRoot); + const targetRepoRoot = path67.resolve(opts.targetRepoRoot); const { sessionUlid, ref } = opts; const round = opts.round ?? 1; const k = opts.k ?? DEFAULT_ADJUDICATION_K; @@ -44513,7 +44534,7 @@ async function adjudicateQualityLead(opts) { round }); const verdictFilePath = adjudicationVerdictFilePath(targetRepoRoot, sessionUlid, ref); - await fs47.mkdir(path66.dirname(verdictFilePath), { recursive: true }); + await fs47.mkdir(path67.dirname(verdictFilePath), { recursive: true }); await atomicWriteFile(verdictFilePath, JSON.stringify(verdict, null, 2)); await logTelemetryEvent({ targetRepoRoot, @@ -44539,8 +44560,8 @@ async function adjudicateQualityLead(opts) { // src/tools/review-maintainer-inbox.ts import { promises as fs48 } from "node:fs"; -import * as path67 from "node:path"; -var INBOX_SUBDIR2 = path67.join(".flow", "maintainer-inbox"); +import * as path68 from "node:path"; +var INBOX_SUBDIR2 = path68.join(".flow", "maintainer-inbox"); var MAX_TITLE_LENGTH = 120; var MAX_URL_BYTES2 = 8192; var SHORTENED_NOTE2 = "\n\n_(body shortened \u2014 see full detail in the maintainer inbox)_"; @@ -44595,7 +44616,7 @@ function buildStoredItemIssueUrl(owner, repo, item) { } async function reviewMaintainerInbox(opts) { const { targetRepoRoot } = opts; - const inboxDir = path67.join(targetRepoRoot, INBOX_SUBDIR2); + const inboxDir = path68.join(targetRepoRoot, INBOX_SUBDIR2); let filenames; try { const entries = await fs48.readdir(inboxDir); @@ -44614,7 +44635,7 @@ async function reviewMaintainerInbox(opts) { const items = []; let malformedCount = 0; for (const filename of filenames) { - const absPath = path67.join(inboxDir, filename); + const absPath = path68.join(inboxDir, filename); let raw; try { raw = await fs48.readFile(absPath, "utf8"); @@ -44668,8 +44689,8 @@ async function reviewMaintainerInbox(opts) { // src/tools/dismiss-maintainer-feedback.ts import { promises as fs49 } from "node:fs"; -import * as path68 from "node:path"; -var INBOX_SUBDIR3 = path68.join(".flow", "maintainer-inbox"); +import * as path69 from "node:path"; +var INBOX_SUBDIR3 = path69.join(".flow", "maintainer-inbox"); var DISMISSED_SUBDIR = "dismissed"; var ULID_PATTERN2 = /^[0-9A-HJKMNP-TV-Z]{26}$/; async function dismissMaintainerFeedback(opts) { @@ -44677,7 +44698,7 @@ async function dismissMaintainerFeedback(opts) { if (typeof id !== "string" || !ULID_PATTERN2.test(id)) { throw new InvalidMaintainerFeedbackIdError({ id: String(id) }); } - const inboxDir = path68.join(targetRepoRoot, INBOX_SUBDIR3); + const inboxDir = path69.join(targetRepoRoot, INBOX_SUBDIR3); let filenames; try { filenames = await fs49.readdir(inboxDir); @@ -44695,8 +44716,8 @@ async function dismissMaintainerFeedback(opts) { if (match === void 0) { return { ok: true, dismissed: false, id, noop: true }; } - const sourcePath = path68.join(inboxDir, match); - const archivedPath = path68.join(inboxDir, DISMISSED_SUBDIR, match); + const sourcePath = path69.join(inboxDir, match); + const archivedPath = path69.join(inboxDir, DISMISSED_SUBDIR, match); let contents; try { contents = await fs49.readFile(sourcePath, "utf8"); @@ -44715,10 +44736,10 @@ async function dismissMaintainerFeedback(opts) { // src/tools/resolve-lens-roles.ts var import_yaml28 = __toESM(require_dist(), 1); import { promises as fs50 } from "node:fs"; -import * as path69 from "node:path"; +import * as path70 from "node:path"; async function resolveLensRoles(opts) { const { targetRepoRoot } = opts; - const teamDir = path69.join(targetRepoRoot, "team"); + const teamDir = path70.join(targetRepoRoot, "team"); let dirEntries; try { dirEntries = await fs50.readdir(teamDir); @@ -44737,7 +44758,7 @@ async function resolveLensRoles(opts) { } let stat2; try { - stat2 = await fs50.stat(path69.join(teamDir, entry)); + stat2 = await fs50.stat(path70.join(teamDir, entry)); } catch { continue; } @@ -44745,7 +44766,7 @@ async function resolveLensRoles(opts) { continue; } try { - await fs50.access(path69.join(teamDir, entry, "PERSONA.md")); + await fs50.access(path70.join(teamDir, entry, "PERSONA.md")); } catch { continue; } @@ -44759,7 +44780,7 @@ async function resolveLensRoles(opts) { return { lensRoles, hiredRoles }; } async function readRoleCapabilities(teamDir, roleId) { - const personaPath = path69.join(teamDir, roleId, "PERSONA.md"); + const personaPath = path70.join(teamDir, roleId, "PERSONA.md"); let raw; try { raw = await fs50.readFile(personaPath, "utf8"); @@ -44802,14 +44823,14 @@ function isEnoent12(err) { // src/tools/resolve-run-slot.ts var import_yaml29 = __toESM(require_dist(), 1); import { promises as fs51 } from "node:fs"; -import * as path70 from "node:path"; +import * as path71 from "node:path"; var RUN_JOB_GENERALISTS = { build: "generalist-dev", review: "generalist-reviewer" }; async function resolveRunSlot(opts) { const { targetRepoRoot, job } = opts; - const teamDir = path70.join(targetRepoRoot, "team"); + const teamDir = path71.join(targetRepoRoot, "team"); const defaultRole = RUN_JOB_GENERALISTS[job]; let dirEntries; try { @@ -44829,7 +44850,7 @@ async function resolveRunSlot(opts) { } let stat2; try { - stat2 = await fs51.stat(path70.join(teamDir, entry)); + stat2 = await fs51.stat(path71.join(teamDir, entry)); } catch { continue; } @@ -44837,7 +44858,7 @@ async function resolveRunSlot(opts) { continue; } try { - await fs51.access(path70.join(teamDir, entry, "PERSONA.md")); + await fs51.access(path71.join(teamDir, entry, "PERSONA.md")); } catch { continue; } @@ -44857,7 +44878,7 @@ async function resolveRunSlot(opts) { return { role: qualifiedRoles[0], isDefault: false }; } async function readRunJobs(teamDir, roleId) { - const personaPath = path70.join(teamDir, roleId, "PERSONA.md"); + const personaPath = path71.join(teamDir, roleId, "PERSONA.md"); let raw; try { raw = await fs51.readFile(personaPath, "utf8"); @@ -44935,7 +44956,7 @@ async function readReviewerLesson(opts) { // src/tools/record-story-retro.ts var import_yaml30 = __toESM(require_dist(), 1); import { promises as fs52 } from "node:fs"; -import * as path71 from "node:path"; +import * as path72 from "node:path"; var NON_DONE_STATES = ["in-progress", "to-do", "blocked"]; function stripUndefined5(obj) { return Object.fromEntries( @@ -44944,7 +44965,7 @@ function stripUndefined5(obj) { } async function findInNonDoneState(stateRoot, ref) { for (const state of NON_DONE_STATES) { - const candidate = path71.join(stateRoot, state, `${ref}.yaml`); + const candidate = path72.join(stateRoot, state, `${ref}.yaml`); try { await fs52.access(candidate); return state; @@ -44961,8 +44982,8 @@ async function recordStoryRetro(opts) { role = "generalist-reviewer" } = opts; const retro = parseStoryRetroPayload(payload); - const stateRoot = path71.join(targetRepoRoot, ".flow", "state"); - const absDonePath = path71.join(stateRoot, "done", `${ref}.yaml`); + const stateRoot = path72.join(targetRepoRoot, ".flow", "state"); + const absDonePath = path72.join(stateRoot, "done", `${ref}.yaml`); let doneExists = true; try { await fs52.access(absDonePath); @@ -45003,13 +45024,13 @@ async function recordStoryRetro(opts) { // src/tools/record-dev-lesson.ts import { promises as fs54 } from "node:fs"; -import * as path73 from "node:path"; +import * as path74 from "node:path"; // src/lib/read-dev-result-file.ts import { promises as fs53 } from "node:fs"; -import * as path72 from "node:path"; +import * as path73 from "node:path"; function devResultFilePath(targetRepoRoot, sessionUlid, ref) { - return path72.join( + return path73.join( targetRepoRoot, ".flow", "state", @@ -45063,7 +45084,7 @@ async function recordDevLesson(opts) { lesson: parsed.data }; const absPath = devResultFilePath(targetRepoRoot, sessionUlid, ref); - await fs54.mkdir(path73.dirname(absPath), { recursive: true }); + await fs54.mkdir(path74.dirname(absPath), { recursive: true }); await atomicWriteFile(absPath, JSON.stringify(merged, null, 2)); return { ok: true, ref, absPath }; } @@ -45077,7 +45098,7 @@ async function readDevLesson(opts) { } // src/tools/recall-lesson.ts -import * as path74 from "node:path"; +import * as path75 from "node:path"; import { promises as fs55 } from "node:fs"; var import_yaml31 = __toESM(require_dist(), 1); var _LESSON_BLOCK_PREFIX = LESSON_BLOCK_PREFIX; @@ -45097,7 +45118,7 @@ async function recallLesson(opts) { }; try { const updatedBody = updateLessonInBody(knowledgeBody, updatedLesson); - const personaPath = path74.join(targetRepoRoot, "team", role, "PERSONA.md"); + const personaPath = path75.join(targetRepoRoot, "team", role, "PERSONA.md"); const rawPersona = await fs55.readFile(personaPath, "utf8"); const parsed = parsePersonaFile(rawPersona, personaPath); const newContents = reconstructPersonaFile(parsed, updatedBody); @@ -45132,7 +45153,7 @@ async function recallLesson(opts) { }; const relPath = `team/${role}/_archived/${id}.json`; await writeManagedFile({ - absPath: path74.join(targetRepoRoot, relPath), + absPath: path75.join(targetRepoRoot, relPath), contents: JSON.stringify(updated, null, 2) + "\n", targetRepoRoot, mcpToolContext: { toolName: TOOL_NAME2, role } @@ -45379,7 +45400,7 @@ async function resolveBuildPlan(opts) { // src/tools/discard-draft.ts import { promises as fs56 } from "node:fs"; -import * as path75 from "node:path"; +import * as path76 from "node:path"; var import_yaml33 = __toESM(require_dist(), 1); var DiscardDraftInputSchema = external_exports.object({ targetRepoRoot: external_exports.string().min(1), @@ -45387,13 +45408,13 @@ var DiscardDraftInputSchema = external_exports.object({ }); async function discardDraft(rawInput) { const input = DiscardDraftInputSchema.parse(rawInput); - const targetRepoRoot = path75.resolve(input.targetRepoRoot); + const targetRepoRoot = path76.resolve(input.targetRepoRoot); const { ref } = input; - const stateRoot = path75.join(targetRepoRoot, ".flow", "state"); + const stateRoot = path76.join(targetRepoRoot, ".flow", "state"); let foundState = null; let foundAbsPath = null; for (const stateName of STATE_NAMES) { - const candidate = path75.join(stateRoot, stateName, `${ref}.yaml`); + const candidate = path76.join(stateRoot, stateName, `${ref}.yaml`); try { await fs56.stat(candidate); foundState = stateName; @@ -45427,8 +45448,8 @@ async function discardDraft(rawInput) { throw new NotAnEligibleDraftError({ ref, foundState, reason: "not-in-to-do" }); } const ulid4 = ref.startsWith("native:") ? ref.slice("native:".length) : ref; - const nativeStoriesDir2 = path75.join(targetRepoRoot, ".flow", "native-stories"); - const sourceDraftPath = path75.join(nativeStoriesDir2, `${ulid4}.md`); + const nativeStoriesDir2 = path76.join(targetRepoRoot, ".flow", "native-stories"); + const sourceDraftPath = path76.join(nativeStoriesDir2, `${ulid4}.md`); await fs56.unlink(foundAbsPath); try { await fs56.unlink(sourceDraftPath); @@ -45452,7 +45473,7 @@ function isEnoent14(err) { // src/tools/block-story.ts var import_yaml34 = __toESM(require_dist(), 1); import { promises as fs57 } from "node:fs"; -import * as path76 from "node:path"; +import * as path77 from "node:path"; // src/lib/emit-story-blocked.ts async function emitStoryBlocked(opts) { @@ -45489,9 +45510,9 @@ function stripUndefined6(obj) { } async function blockStory(opts) { const { targetRepoRoot, ref, sessionUlid, blockedBy, blockDetail, role = "orchestrator" } = opts; - const stateRoot = path76.join(targetRepoRoot, ".flow", "state"); - const absInProgressPath = path76.join(stateRoot, "in-progress", `${ref}.yaml`); - const absBlockedPath = path76.join(stateRoot, "blocked", `${ref}.yaml`); + const stateRoot = path77.join(targetRepoRoot, ".flow", "state"); + const absInProgressPath = path77.join(stateRoot, "in-progress", `${ref}.yaml`); + const absBlockedPath = path77.join(stateRoot, "blocked", `${ref}.yaml`); let rawText; try { rawText = await fs57.readFile(absInProgressPath, "utf8"); @@ -45552,11 +45573,11 @@ async function blockStory(opts) { } // src/tools/extract-native-story-acs.ts -import * as path77 from "node:path"; +import * as path78 from "node:path"; async function extractNativeStoryAcs(opts) { const { targetRepoRoot, ref } = opts; const ulid4 = ref.startsWith("native:") ? ref.slice("native:".length) : ref; - const specPath = path77.join(targetRepoRoot, ".flow", "native-stories", `${ulid4}.md`); + const specPath = path78.join(targetRepoRoot, ".flow", "native-stories", `${ulid4}.md`); try { const acs = await extractAcsFromSpec(specPath); return { acs }; @@ -45567,7 +45588,7 @@ async function extractNativeStoryAcs(opts) { // src/tools/capture-skill-invoke.ts var import_yaml35 = __toESM(require_dist(), 1); -import * as path78 from "node:path"; +import * as path79 from "node:path"; import { promises as fs58 } from "node:fs"; // src/tools/record-skill-invoke.ts @@ -45598,7 +45619,7 @@ async function recordSkillInvoke(opts) { // src/tools/capture-skill-invoke.ts async function resolvePluginVersion(pluginRoot, readFileImpl) { try { - const manifestPath = path78.join(pluginRoot, ".claude-plugin", "plugin.json"); + const manifestPath = path79.join(pluginRoot, ".claude-plugin", "plugin.json"); const raw = await readFileImpl(manifestPath); const parsed = JSON.parse(raw); if (typeof parsed.version === "string" && parsed.version.length > 0) { @@ -45611,7 +45632,7 @@ async function resolvePluginVersion(pluginRoot, readFileImpl) { async function resolveSkillMeta(skillName, pluginRoot, readFileImpl) { const command = skillName.includes(":") ? skillName.slice(skillName.indexOf(":") + 1) : skillName; if (pluginRoot && command) { - const skillPath = path78.join(pluginRoot, "skills", command, "SKILL.md"); + const skillPath = path79.join(pluginRoot, "skills", command, "SKILL.md"); try { const raw = await readFileImpl(skillPath); const match = raw.match(/^version:\s*(.+?)\s*$/m); @@ -45629,7 +45650,7 @@ async function resolveSkillMeta(skillName, pluginRoot, readFileImpl) { } async function resolveActiveStoryRef(targetRepoRoot, readInProgressDirImpl, readFileImpl) { try { - const inProgressDir = path78.join( + const inProgressDir = path79.join( targetRepoRoot, ".flow", "state", @@ -45643,7 +45664,7 @@ async function resolveActiveStoryRef(targetRepoRoot, readInProgressDirImpl, read for (const file2 of manifestFiles) { let parsed; try { - const raw = await readFileImpl(path78.join(inProgressDir, file2)); + const raw = await readFileImpl(path79.join(inProgressDir, file2)); parsed = (0, import_yaml35.parse)(raw); } catch { continue; @@ -45704,12 +45725,12 @@ async function captureSkillInvoke(rawHookPayload, deps = {}) { // src/tools/auto-absorb-retro-proposals.ts var import_yaml38 = __toESM(require_dist(), 1); import { promises as fs61 } from "node:fs"; -import * as path81 from "node:path"; +import * as path82 from "node:path"; // src/lib/locate-proposal.ts var import_yaml36 = __toESM(require_dist(), 1); import { promises as fs59 } from "node:fs"; -import * as path79 from "node:path"; +import * as path80 from "node:path"; // src/schemas/retro-proposal.ts var UlidSchema2 = external_exports.string().regex(/^[0-9A-HJKMNP-TV-Z]{26}$/, "must be a ULID"); @@ -45876,7 +45897,7 @@ function parseRetroProposalFile(input) { // src/lib/locate-proposal.ts async function locateProposal(opts) { const { targetRepoRoot, proposalId } = opts; - const proposalsDir = path79.join( + const proposalsDir = path80.join( targetRepoRoot, ".flow", "retro-proposals" @@ -45893,7 +45914,7 @@ async function locateProposal(opts) { const files = entries.filter((f) => f.endsWith(".md")).sort(); const matches = []; for (const file2 of files) { - const absPath = path79.join(proposalsDir, file2); + const absPath = path80.join(proposalsDir, file2); const raw = await fs59.readFile(absPath, "utf8"); const { frontmatterRaw } = splitFrontmatter(raw, absPath); const parsedYaml = (0, import_yaml36.parse)(frontmatterRaw); @@ -45902,7 +45923,7 @@ async function locateProposal(opts) { if (proposal.id === proposalId) { matches.push({ absPath, - relPath: path79.relative(targetRepoRoot, absPath), + relPath: path80.relative(targetRepoRoot, absPath), file: parsedFile, proposal, index @@ -45931,14 +45952,14 @@ function isEnoent15(err) { // src/lib/apply-persona-append.ts import { promises as fs60 } from "node:fs"; -import * as path80 from "node:path"; +import * as path81 from "node:path"; var import_yaml37 = __toESM(require_dist(), 1); var TOOL_NAME3 = "acceptProposal"; function personaRelPath(targetRole) { return `team/${targetRole}/PERSONA.md`; } async function readPersonaRaw(targetRepoRoot, relPath) { - const abs = path80.join(targetRepoRoot, relPath); + const abs = path81.join(targetRepoRoot, relPath); try { return await fs60.readFile(abs, "utf8"); } catch (err) { @@ -46063,7 +46084,7 @@ function makePersonaAppendHandler() { async apply(proposal, ctx) { assertPersonaAppendProposal(proposal); const relPath = personaRelPath(proposal.target_role); - const absPath = path80.join(ctx.targetRepoRoot, relPath); + const absPath = path81.join(ctx.targetRepoRoot, relPath); const raw = await readPersonaRaw(ctx.targetRepoRoot, relPath); if (raw === null) { throw new PersonaFileNotFoundError({ @@ -46288,7 +46309,7 @@ async function applySingleProposal(opts) { } async function autoAbsorbProposalFile(opts) { const { targetRepoRoot, proposalFileTimestamp, maxAutoAbsorb } = opts; - const absPath = path81.join( + const absPath = path82.join( targetRepoRoot, ".flow", "retro-proposals", @@ -46350,7 +46371,7 @@ async function summariseRetroProposal(opts) { // src/tools/unhire-persona.ts var import_yaml40 = __toESM(require_dist(), 1); import { promises as fs63 } from "node:fs"; -import * as path82 from "node:path"; +import * as path83 from "node:path"; async function readPersonaCapabilities(personaPath) { let raw; try { @@ -46385,7 +46406,7 @@ async function readPersonaCapabilities(personaPath) { return capResult.data.review_lenses; } async function enumerateHiredRolesWithCapabilities(targetRepoRoot) { - const teamDir = path82.join(targetRepoRoot, "team"); + const teamDir = path83.join(targetRepoRoot, "team"); let dirEntries; try { dirEntries = await fs63.readdir(teamDir); @@ -46403,7 +46424,7 @@ async function enumerateHiredRolesWithCapabilities(targetRepoRoot) { } let stat2; try { - stat2 = await fs63.stat(path82.join(teamDir, entry)); + stat2 = await fs63.stat(path83.join(teamDir, entry)); } catch { continue; } @@ -46411,7 +46432,7 @@ async function enumerateHiredRolesWithCapabilities(targetRepoRoot) { continue; } try { - await fs63.access(path82.join(teamDir, entry, "PERSONA.md")); + await fs63.access(path83.join(teamDir, entry, "PERSONA.md")); } catch { continue; } @@ -46421,7 +46442,7 @@ async function enumerateHiredRolesWithCapabilities(targetRepoRoot) { const roles = await Promise.all( roleIds.map(async (id) => { const reviewLenses = await readPersonaCapabilities( - path82.join(teamDir, id, "PERSONA.md") + path83.join(teamDir, id, "PERSONA.md") ); return { id, reviewLenses }; }) @@ -46431,8 +46452,8 @@ async function enumerateHiredRolesWithCapabilities(targetRepoRoot) { async function unhirePersona(opts) { const { targetRepoRoot, role } = opts; const clock = opts.clock ?? (() => /* @__PURE__ */ new Date()); - const livePersonaPath = path82.join(targetRepoRoot, "team", role, "PERSONA.md"); - const archivedPersonaPath = path82.join( + const livePersonaPath = path83.join(targetRepoRoot, "team", role, "PERSONA.md"); + const archivedPersonaPath = path83.join( targetRepoRoot, "team", "_archived", @@ -46477,7 +46498,7 @@ async function unhirePersona(opts) { mcpToolContext: { toolName: "unhirePersona", role } }); await fs63.unlink(livePersonaPath); - const roleDirPath = path82.join(targetRepoRoot, "team", role); + const roleDirPath = path83.join(targetRepoRoot, "team", role); try { const remaining = await fs63.readdir(roleDirPath); if (remaining.length === 0) { @@ -46516,16 +46537,16 @@ function isEnoent16(err) { // src/tools/refresh-persona.ts import { promises as fs64 } from "node:fs"; -import * as path83 from "node:path"; +import * as path84 from "node:path"; async function refreshPersona(opts) { const pluginVersion = opts.pluginVersion ?? getPluginVersion(); - const customPath = path83.join( + const customPath = path84.join( opts.targetRepoRoot, "team", "custom", `${opts.role}.md` ); - const cataloguePath = path83.join( + const cataloguePath = path84.join( opts.pluginRoot, "catalogue", `${opts.role}.md` @@ -46557,7 +46578,7 @@ async function refreshPersona(opts) { throw err; } } - const personaPath = path83.join( + const personaPath = path84.join( opts.targetRepoRoot, "team", opts.role, @@ -46661,7 +46682,7 @@ async function matchStorySpecialist(opts) { // src/tools/record-specialist-engagement.ts var import_yaml42 = __toESM(require_dist(), 1); import { promises as fs66 } from "node:fs"; -import * as path84 from "node:path"; +import * as path85 from "node:path"; function stripUndefined7(obj) { return Object.fromEntries( Object.entries(obj).filter(([, v]) => v !== void 0) @@ -46669,7 +46690,7 @@ function stripUndefined7(obj) { } async function recordSpecialistEngagement(opts) { const { targetRepoRoot, ref, sessionUlid, specialistRole } = opts; - const absPath = path84.join( + const absPath = path85.join( targetRepoRoot, ".flow", "state", diff --git a/plugins/flow/mcp-server/dist/index.js b/plugins/flow/mcp-server/dist/index.js index 9d4cad53..b0fce5ad 100644 --- a/plugins/flow/mcp-server/dist/index.js +++ b/plugins/flow/mcp-server/dist/index.js @@ -2988,7 +2988,7 @@ var require_compile = __commonJS({ const schOrFunc = root.refs[ref]; if (schOrFunc) return schOrFunc; - let _sch = resolve21.call(this, root, ref); + let _sch = resolve22.call(this, root, ref); if (_sch === void 0) { const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref]; const { schemaId } = this.opts; @@ -3015,7 +3015,7 @@ var require_compile = __commonJS({ function sameSchemaEnv(s1, s2) { return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId; } - function resolve21(root, ref) { + function resolve22(root, ref) { let sch; while (typeof (sch = this.refs[ref]) == "string") ref = sch; @@ -3233,8 +3233,8 @@ var require_utils = __commonJS({ } return ind; } - function removeDotSegments(path94) { - let input = path94; + function removeDotSegments(path95) { + let input = path95; const output = []; let nextSlash = -1; let len = 0; @@ -3486,8 +3486,8 @@ var require_schemes = __commonJS({ wsComponent.secure = void 0; } if (wsComponent.resourceName) { - const [path94, query] = wsComponent.resourceName.split("?"); - wsComponent.path = path94 && path94 !== "/" ? path94 : void 0; + const [path95, query] = wsComponent.resourceName.split("?"); + wsComponent.path = path95 && path95 !== "/" ? path95 : void 0; wsComponent.query = query; wsComponent.resourceName = void 0; } @@ -3646,7 +3646,7 @@ var require_fast_uri = __commonJS({ } return uri; } - function resolve21(baseURI, relativeURI, options) { + function resolve22(baseURI, relativeURI, options) { const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" }; const resolved = resolveComponent(parse4(baseURI, schemelessOptions), parse4(relativeURI, schemelessOptions), schemelessOptions, true); schemelessOptions.skipEscape = true; @@ -3904,7 +3904,7 @@ var require_fast_uri = __commonJS({ var fastUri = { SCHEMES, normalize: normalize2, - resolve: resolve21, + resolve: resolve22, resolveComponent, equal, serialize: serialize2, @@ -6970,17 +6970,17 @@ var require_visit = __commonJS({ visit.BREAK = BREAK; visit.SKIP = SKIP; visit.REMOVE = REMOVE; - function visit_(key, node, visitor, path94) { - const ctrl = callVisitor(key, node, visitor, path94); + function visit_(key, node, visitor, path95) { + const ctrl = callVisitor(key, node, visitor, path95); if (identity3.isNode(ctrl) || identity3.isPair(ctrl)) { - replaceNode(key, path94, ctrl); - return visit_(key, ctrl, visitor, path94); + replaceNode(key, path95, ctrl); + return visit_(key, ctrl, visitor, path95); } if (typeof ctrl !== "symbol") { if (identity3.isCollection(node)) { - path94 = Object.freeze(path94.concat(node)); + path95 = Object.freeze(path95.concat(node)); for (let i2 = 0; i2 < node.items.length; ++i2) { - const ci = visit_(i2, node.items[i2], visitor, path94); + const ci = visit_(i2, node.items[i2], visitor, path95); if (typeof ci === "number") i2 = ci - 1; else if (ci === BREAK) @@ -6991,13 +6991,13 @@ var require_visit = __commonJS({ } } } else if (identity3.isPair(node)) { - path94 = Object.freeze(path94.concat(node)); - const ck = visit_("key", node.key, visitor, path94); + path95 = Object.freeze(path95.concat(node)); + const ck = visit_("key", node.key, visitor, path95); if (ck === BREAK) return BREAK; else if (ck === REMOVE) node.key = null; - const cv = visit_("value", node.value, visitor, path94); + const cv = visit_("value", node.value, visitor, path95); if (cv === BREAK) return BREAK; else if (cv === REMOVE) @@ -7018,17 +7018,17 @@ var require_visit = __commonJS({ visitAsync.BREAK = BREAK; visitAsync.SKIP = SKIP; visitAsync.REMOVE = REMOVE; - async function visitAsync_(key, node, visitor, path94) { - const ctrl = await callVisitor(key, node, visitor, path94); + async function visitAsync_(key, node, visitor, path95) { + const ctrl = await callVisitor(key, node, visitor, path95); if (identity3.isNode(ctrl) || identity3.isPair(ctrl)) { - replaceNode(key, path94, ctrl); - return visitAsync_(key, ctrl, visitor, path94); + replaceNode(key, path95, ctrl); + return visitAsync_(key, ctrl, visitor, path95); } if (typeof ctrl !== "symbol") { if (identity3.isCollection(node)) { - path94 = Object.freeze(path94.concat(node)); + path95 = Object.freeze(path95.concat(node)); for (let i2 = 0; i2 < node.items.length; ++i2) { - const ci = await visitAsync_(i2, node.items[i2], visitor, path94); + const ci = await visitAsync_(i2, node.items[i2], visitor, path95); if (typeof ci === "number") i2 = ci - 1; else if (ci === BREAK) @@ -7039,13 +7039,13 @@ var require_visit = __commonJS({ } } } else if (identity3.isPair(node)) { - path94 = Object.freeze(path94.concat(node)); - const ck = await visitAsync_("key", node.key, visitor, path94); + path95 = Object.freeze(path95.concat(node)); + const ck = await visitAsync_("key", node.key, visitor, path95); if (ck === BREAK) return BREAK; else if (ck === REMOVE) node.key = null; - const cv = await visitAsync_("value", node.value, visitor, path94); + const cv = await visitAsync_("value", node.value, visitor, path95); if (cv === BREAK) return BREAK; else if (cv === REMOVE) @@ -7072,23 +7072,23 @@ var require_visit = __commonJS({ } return visitor; } - function callVisitor(key, node, visitor, path94) { + function callVisitor(key, node, visitor, path95) { if (typeof visitor === "function") - return visitor(key, node, path94); + return visitor(key, node, path95); if (identity3.isMap(node)) - return visitor.Map?.(key, node, path94); + return visitor.Map?.(key, node, path95); if (identity3.isSeq(node)) - return visitor.Seq?.(key, node, path94); + return visitor.Seq?.(key, node, path95); if (identity3.isPair(node)) - return visitor.Pair?.(key, node, path94); + return visitor.Pair?.(key, node, path95); if (identity3.isScalar(node)) - return visitor.Scalar?.(key, node, path94); + return visitor.Scalar?.(key, node, path95); if (identity3.isAlias(node)) - return visitor.Alias?.(key, node, path94); + return visitor.Alias?.(key, node, path95); return void 0; } - function replaceNode(key, path94, node) { - const parent = path94[path94.length - 1]; + function replaceNode(key, path95, node) { + const parent = path95[path95.length - 1]; if (identity3.isCollection(parent)) { parent.items[key] = node; } else if (identity3.isPair(parent)) { @@ -7698,10 +7698,10 @@ var require_Collection = __commonJS({ var createNode = require_createNode(); var identity3 = require_identity(); var Node = require_Node(); - function collectionFromPath(schema, path94, value) { + function collectionFromPath(schema, path95, value) { let v = value; - for (let i2 = path94.length - 1; i2 >= 0; --i2) { - const k = path94[i2]; + for (let i2 = path95.length - 1; i2 >= 0; --i2) { + const k = path95[i2]; if (typeof k === "number" && Number.isInteger(k) && k >= 0) { const a2 = []; a2[k] = v; @@ -7720,7 +7720,7 @@ var require_Collection = __commonJS({ sourceObjects: /* @__PURE__ */ new Map() }); } - var isEmptyPath = (path94) => path94 == null || typeof path94 === "object" && !!path94[Symbol.iterator]().next().done; + var isEmptyPath = (path95) => path95 == null || typeof path95 === "object" && !!path95[Symbol.iterator]().next().done; var Collection = class extends Node.NodeBase { constructor(type, schema) { super(type); @@ -7750,11 +7750,11 @@ var require_Collection = __commonJS({ * be a Pair instance or a `{ key, value }` object, which may not have a key * that already exists in the map. */ - addIn(path94, value) { - if (isEmptyPath(path94)) + addIn(path95, value) { + if (isEmptyPath(path95)) this.add(value); else { - const [key, ...rest] = path94; + const [key, ...rest] = path95; const node = this.get(key, true); if (identity3.isCollection(node)) node.addIn(rest, value); @@ -7768,8 +7768,8 @@ var require_Collection = __commonJS({ * Removes a value from the collection. * @returns `true` if the item was found and removed. */ - deleteIn(path94) { - const [key, ...rest] = path94; + deleteIn(path95) { + const [key, ...rest] = path95; if (rest.length === 0) return this.delete(key); const node = this.get(key, true); @@ -7783,8 +7783,8 @@ var require_Collection = __commonJS({ * scalar values from their surrounding node; to disable set `keepScalar` to * `true` (collections are always returned intact). */ - getIn(path94, keepScalar) { - const [key, ...rest] = path94; + getIn(path95, keepScalar) { + const [key, ...rest] = path95; const node = this.get(key, true); if (rest.length === 0) return !keepScalar && identity3.isScalar(node) ? node.value : node; @@ -7802,8 +7802,8 @@ var require_Collection = __commonJS({ /** * Checks if the collection includes a value with the key `key`. */ - hasIn(path94) { - const [key, ...rest] = path94; + hasIn(path95) { + const [key, ...rest] = path95; if (rest.length === 0) return this.has(key); const node = this.get(key, true); @@ -7813,8 +7813,8 @@ var require_Collection = __commonJS({ * Sets a value in this collection. For `!!set`, `value` needs to be a * boolean to add/remove the item from the set. */ - setIn(path94, value) { - const [key, ...rest] = path94; + setIn(path95, value) { + const [key, ...rest] = path95; if (rest.length === 0) { this.set(key, value); } else { @@ -10329,9 +10329,9 @@ var require_Document = __commonJS({ this.contents.add(value); } /** Adds a value to the document. */ - addIn(path94, value) { + addIn(path95, value) { if (assertCollection(this.contents)) - this.contents.addIn(path94, value); + this.contents.addIn(path95, value); } /** * Create a new `Alias` node, ensuring that the target `node` has the required anchor. @@ -10406,14 +10406,14 @@ var require_Document = __commonJS({ * Removes a value from the document. * @returns `true` if the item was found and removed. */ - deleteIn(path94) { - if (Collection.isEmptyPath(path94)) { + deleteIn(path95) { + if (Collection.isEmptyPath(path95)) { if (this.contents == null) return false; this.contents = null; return true; } - return assertCollection(this.contents) ? this.contents.deleteIn(path94) : false; + return assertCollection(this.contents) ? this.contents.deleteIn(path95) : false; } /** * Returns item at `key`, or `undefined` if not found. By default unwraps @@ -10428,10 +10428,10 @@ var require_Document = __commonJS({ * scalar values from their surrounding node; to disable set `keepScalar` to * `true` (collections are always returned intact). */ - getIn(path94, keepScalar) { - if (Collection.isEmptyPath(path94)) + getIn(path95, keepScalar) { + if (Collection.isEmptyPath(path95)) return !keepScalar && identity3.isScalar(this.contents) ? this.contents.value : this.contents; - return identity3.isCollection(this.contents) ? this.contents.getIn(path94, keepScalar) : void 0; + return identity3.isCollection(this.contents) ? this.contents.getIn(path95, keepScalar) : void 0; } /** * Checks if the document includes a value with the key `key`. @@ -10442,10 +10442,10 @@ var require_Document = __commonJS({ /** * Checks if the document includes a value at `path`. */ - hasIn(path94) { - if (Collection.isEmptyPath(path94)) + hasIn(path95) { + if (Collection.isEmptyPath(path95)) return this.contents !== void 0; - return identity3.isCollection(this.contents) ? this.contents.hasIn(path94) : false; + return identity3.isCollection(this.contents) ? this.contents.hasIn(path95) : false; } /** * Sets a value in this document. For `!!set`, `value` needs to be a @@ -10462,13 +10462,13 @@ var require_Document = __commonJS({ * Sets a value in this document. For `!!set`, `value` needs to be a * boolean to add/remove the item from the set. */ - setIn(path94, value) { - if (Collection.isEmptyPath(path94)) { + setIn(path95, value) { + if (Collection.isEmptyPath(path95)) { this.contents = value; } else if (this.contents == null) { - this.contents = Collection.collectionFromPath(this.schema, Array.from(path94), value); + this.contents = Collection.collectionFromPath(this.schema, Array.from(path95), value); } else if (assertCollection(this.contents)) { - this.contents.setIn(path94, value); + this.contents.setIn(path95, value); } } /** @@ -12428,9 +12428,9 @@ var require_cst_visit = __commonJS({ visit.BREAK = BREAK; visit.SKIP = SKIP; visit.REMOVE = REMOVE; - visit.itemAtPath = (cst, path94) => { + visit.itemAtPath = (cst, path95) => { let item = cst; - for (const [field, index] of path94) { + for (const [field, index] of path95) { const tok = item?.[field]; if (tok && "items" in tok) { item = tok.items[index]; @@ -12439,23 +12439,23 @@ var require_cst_visit = __commonJS({ } return item; }; - visit.parentCollection = (cst, path94) => { - const parent = visit.itemAtPath(cst, path94.slice(0, -1)); - const field = path94[path94.length - 1][0]; + visit.parentCollection = (cst, path95) => { + const parent = visit.itemAtPath(cst, path95.slice(0, -1)); + const field = path95[path95.length - 1][0]; const coll = parent?.[field]; if (coll && "items" in coll) return coll; throw new Error("Parent collection not found"); }; - function _visit(path94, item, visitor) { - let ctrl = visitor(item, path94); + function _visit(path95, item, visitor) { + let ctrl = visitor(item, path95); if (typeof ctrl === "symbol") return ctrl; for (const field of ["key", "value"]) { const token = item[field]; if (token && "items" in token) { for (let i2 = 0; i2 < token.items.length; ++i2) { - const ci = _visit(Object.freeze(path94.concat([[field, i2]])), token.items[i2], visitor); + const ci = _visit(Object.freeze(path95.concat([[field, i2]])), token.items[i2], visitor); if (typeof ci === "number") i2 = ci - 1; else if (ci === BREAK) @@ -12466,10 +12466,10 @@ var require_cst_visit = __commonJS({ } } if (typeof ctrl === "function" && field === "key") - ctrl = ctrl(item, path94); + ctrl = ctrl(item, path95); } } - return typeof ctrl === "function" ? ctrl(item, path94) : ctrl; + return typeof ctrl === "function" ? ctrl(item, path95) : ctrl; } exports.visit = visit; } @@ -14475,8 +14475,8 @@ var require_utils2 = __commonJS({ } return output; }; - exports.basename = (path94, { windows } = {}) => { - const segs = path94.split(windows ? /[\\/]/ : "/"); + exports.basename = (path95, { windows } = {}) => { + const segs = path95.split(windows ? /[\\/]/ : "/"); const last = segs[segs.length - 1]; if (last === "") { return segs[segs.length - 2]; @@ -15976,7 +15976,7 @@ var require_windows = __commonJS({ module.exports = isexe; isexe.sync = sync; var fs76 = __require("fs"); - function checkPathExt(path94, options) { + function checkPathExt(path95, options) { var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT; if (!pathext) { return true; @@ -15987,25 +15987,25 @@ var require_windows = __commonJS({ } for (var i2 = 0; i2 < pathext.length; i2++) { var p = pathext[i2].toLowerCase(); - if (p && path94.substr(-p.length).toLowerCase() === p) { + if (p && path95.substr(-p.length).toLowerCase() === p) { return true; } } return false; } - function checkStat(stat2, path94, options) { + function checkStat(stat2, path95, options) { if (!stat2.isSymbolicLink() && !stat2.isFile()) { return false; } - return checkPathExt(path94, options); + return checkPathExt(path95, options); } - function isexe(path94, options, cb) { - fs76.stat(path94, function(er, stat2) { - cb(er, er ? false : checkStat(stat2, path94, options)); + function isexe(path95, options, cb) { + fs76.stat(path95, function(er, stat2) { + cb(er, er ? false : checkStat(stat2, path95, options)); }); } - function sync(path94, options) { - return checkStat(fs76.statSync(path94), path94, options); + function sync(path95, options) { + return checkStat(fs76.statSync(path95), path95, options); } } }); @@ -16016,13 +16016,13 @@ var require_mode = __commonJS({ module.exports = isexe; isexe.sync = sync; var fs76 = __require("fs"); - function isexe(path94, options, cb) { - fs76.stat(path94, function(er, stat2) { + function isexe(path95, options, cb) { + fs76.stat(path95, function(er, stat2) { cb(er, er ? false : checkStat(stat2, options)); }); } - function sync(path94, options) { - return checkStat(fs76.statSync(path94), options); + function sync(path95, options) { + return checkStat(fs76.statSync(path95), options); } function checkStat(stat2, options) { return stat2.isFile() && checkMode(stat2, options); @@ -16055,7 +16055,7 @@ var require_isexe = __commonJS({ } module.exports = isexe; isexe.sync = sync; - function isexe(path94, options, cb) { + function isexe(path95, options, cb) { if (typeof options === "function") { cb = options; options = {}; @@ -16064,17 +16064,17 @@ var require_isexe = __commonJS({ if (typeof Promise !== "function") { throw new TypeError("callback not provided"); } - return new Promise(function(resolve21, reject) { - isexe(path94, options || {}, function(er, is) { + return new Promise(function(resolve22, reject) { + isexe(path95, options || {}, function(er, is) { if (er) { reject(er); } else { - resolve21(is); + resolve22(is); } }); }); } - core(path94, options || {}, function(er, is) { + core(path95, options || {}, function(er, is) { if (er) { if (er.code === "EACCES" || options && options.ignoreErrors) { er = null; @@ -16084,9 +16084,9 @@ var require_isexe = __commonJS({ cb(er, is); }); } - function sync(path94, options) { + function sync(path95, options) { try { - return core.sync(path94, options || {}); + return core.sync(path95, options || {}); } catch (er) { if (options && options.ignoreErrors || er.code === "EACCES") { return false; @@ -16102,7 +16102,7 @@ var require_isexe = __commonJS({ var require_which = __commonJS({ "../node_modules/.pnpm/which@2.0.2/node_modules/which/which.js"(exports, module) { var isWindows = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys"; - var path94 = __require("path"); + var path95 = __require("path"); var COLON = isWindows ? ";" : ":"; var isexe = require_isexe(); var getNotFoundError = (cmd) => Object.assign(new Error(`not found: ${cmd}`), { code: "ENOENT" }); @@ -16135,27 +16135,27 @@ var require_which = __commonJS({ opt = {}; const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt); const found = []; - const step = (i2) => new Promise((resolve21, reject) => { + const step = (i2) => new Promise((resolve22, reject) => { if (i2 === pathEnv.length) - return opt.all && found.length ? resolve21(found) : reject(getNotFoundError(cmd)); + return opt.all && found.length ? resolve22(found) : reject(getNotFoundError(cmd)); const ppRaw = pathEnv[i2]; const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw; - const pCmd = path94.join(pathPart, cmd); + const pCmd = path95.join(pathPart, cmd); const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd; - resolve21(subStep(p, i2, 0)); + resolve22(subStep(p, i2, 0)); }); - const subStep = (p, i2, ii) => new Promise((resolve21, reject) => { + const subStep = (p, i2, ii) => new Promise((resolve22, reject) => { if (ii === pathExt.length) - return resolve21(step(i2 + 1)); + return resolve22(step(i2 + 1)); const ext = pathExt[ii]; isexe(p + ext, { pathExt: pathExtExe }, (er, is) => { if (!er && is) { if (opt.all) found.push(p + ext); else - return resolve21(p + ext); + return resolve22(p + ext); } - return resolve21(subStep(p, i2, ii + 1)); + return resolve22(subStep(p, i2, ii + 1)); }); }); return cb ? step(0).then((res) => cb(null, res), cb) : step(0); @@ -16167,7 +16167,7 @@ var require_which = __commonJS({ for (let i2 = 0; i2 < pathEnv.length; i2++) { const ppRaw = pathEnv[i2]; const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw; - const pCmd = path94.join(pathPart, cmd); + const pCmd = path95.join(pathPart, cmd); const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd; for (let j = 0; j < pathExt.length; j++) { const cur = p + pathExt[j]; @@ -16215,7 +16215,7 @@ var require_path_key = __commonJS({ var require_resolveCommand = __commonJS({ "../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/resolveCommand.js"(exports, module) { "use strict"; - var path94 = __require("path"); + var path95 = __require("path"); var which = require_which(); var getPathKey = require_path_key(); function resolveCommandAttempt(parsed, withoutPathExt) { @@ -16233,7 +16233,7 @@ var require_resolveCommand = __commonJS({ try { resolved = which.sync(parsed.command, { path: env[getPathKey({ env })], - pathExt: withoutPathExt ? path94.delimiter : void 0 + pathExt: withoutPathExt ? path95.delimiter : void 0 }); } catch (e) { } finally { @@ -16242,7 +16242,7 @@ var require_resolveCommand = __commonJS({ } } if (resolved) { - resolved = path94.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved); + resolved = path95.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved); } return resolved; } @@ -16296,8 +16296,8 @@ var require_shebang_command = __commonJS({ if (!match) { return null; } - const [path94, argument] = match[0].replace(/#! ?/, "").split(" "); - const binary = path94.split("/").pop(); + const [path95, argument] = match[0].replace(/#! ?/, "").split(" "); + const binary = path95.split("/").pop(); if (binary === "env") { return argument; } @@ -16332,7 +16332,7 @@ var require_readShebang = __commonJS({ var require_parse2 = __commonJS({ "../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/parse.js"(exports, module) { "use strict"; - var path94 = __require("path"); + var path95 = __require("path"); var resolveCommand = require_resolveCommand(); var escape2 = require_escape(); var readShebang = require_readShebang(); @@ -16357,7 +16357,7 @@ var require_parse2 = __commonJS({ const needsShell = !isExecutableRegExp.test(commandFile); if (parsed.options.forceShell || needsShell) { const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile); - parsed.command = path94.normalize(parsed.command); + parsed.command = path95.normalize(parsed.command); parsed.command = escape2.command(parsed.command); parsed.args = parsed.args.map((arg) => escape2.argument(arg, needsDoubleEscapeMetaChars)); const shellCommand = [parsed.command].concat(parsed.args).join(" "); @@ -17236,10 +17236,10 @@ function mergeDefs(...defs) { function cloneDef(schema) { return mergeDefs(schema._zod.def); } -function getElementAtPath(obj, path94) { - if (!path94) +function getElementAtPath(obj, path95) { + if (!path95) return obj; - return path94.reduce((acc, key) => acc?.[key], obj); + return path95.reduce((acc, key) => acc?.[key], obj); } function promiseAllObject(promisesObj) { const keys = Object.keys(promisesObj); @@ -17648,11 +17648,11 @@ function explicitlyAborted(x, startIndex = 0) { } return false; } -function prefixIssues(path94, issues) { +function prefixIssues(path95, issues) { return issues.map((iss) => { var _a3; (_a3 = iss).path ?? (_a3.path = []); - iss.path.unshift(path94); + iss.path.unshift(path95); return iss; }); } @@ -17799,16 +17799,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) { } function formatError(error51, mapper = (issue2) => issue2.message) { const fieldErrors = { _errors: [] }; - const processError = (error52, path94 = []) => { + const processError = (error52, path95 = []) => { for (const issue2 of error52.issues) { if (issue2.code === "invalid_union" && issue2.errors.length) { - issue2.errors.map((issues) => processError({ issues }, [...path94, ...issue2.path])); + issue2.errors.map((issues) => processError({ issues }, [...path95, ...issue2.path])); } else if (issue2.code === "invalid_key") { - processError({ issues: issue2.issues }, [...path94, ...issue2.path]); + processError({ issues: issue2.issues }, [...path95, ...issue2.path]); } else if (issue2.code === "invalid_element") { - processError({ issues: issue2.issues }, [...path94, ...issue2.path]); + processError({ issues: issue2.issues }, [...path95, ...issue2.path]); } else { - const fullpath = [...path94, ...issue2.path]; + const fullpath = [...path95, ...issue2.path]; if (fullpath.length === 0) { fieldErrors._errors.push(mapper(issue2)); } else { @@ -17835,17 +17835,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) { } function treeifyError(error51, mapper = (issue2) => issue2.message) { const result = { errors: [] }; - const processError = (error52, path94 = []) => { + const processError = (error52, path95 = []) => { var _a3, _b; for (const issue2 of error52.issues) { if (issue2.code === "invalid_union" && issue2.errors.length) { - issue2.errors.map((issues) => processError({ issues }, [...path94, ...issue2.path])); + issue2.errors.map((issues) => processError({ issues }, [...path95, ...issue2.path])); } else if (issue2.code === "invalid_key") { - processError({ issues: issue2.issues }, [...path94, ...issue2.path]); + processError({ issues: issue2.issues }, [...path95, ...issue2.path]); } else if (issue2.code === "invalid_element") { - processError({ issues: issue2.issues }, [...path94, ...issue2.path]); + processError({ issues: issue2.issues }, [...path95, ...issue2.path]); } else { - const fullpath = [...path94, ...issue2.path]; + const fullpath = [...path95, ...issue2.path]; if (fullpath.length === 0) { result.errors.push(mapper(issue2)); continue; @@ -17877,8 +17877,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) { } function toDotPath(_path) { const segs = []; - const path94 = _path.map((seg) => typeof seg === "object" ? seg.key : seg); - for (const seg of path94) { + const path95 = _path.map((seg) => typeof seg === "object" ? seg.key : seg); + for (const seg of path95) { if (typeof seg === "number") segs.push(`[${seg}]`); else if (typeof seg === "symbol") @@ -30570,13 +30570,13 @@ function resolveRef(ref, ctx) { if (!ref.startsWith("#")) { throw new Error("External $ref is not supported, only local refs (#/...) are allowed"); } - const path94 = ref.slice(1).split("/").filter(Boolean); - if (path94.length === 0) { + const path95 = ref.slice(1).split("/").filter(Boolean); + if (path95.length === 0) { return ctx.rootSchema; } const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions"; - if (path94[0] === defsKey) { - const key = path94[1]; + if (path95[0] === defsKey) { + const key = path95[1]; if (!key || !ctx.defs[key]) { throw new Error(`Reference not found: ${ref}`); } @@ -32581,12 +32581,12 @@ var StdioServerTransport = class { this.onclose?.(); } send(message) { - return new Promise((resolve21) => { + return new Promise((resolve22) => { const json2 = serializeMessage(message); if (this._stdout.write(json2)) { - resolve21(); + resolve22(); } else { - this._stdout.once("drain", resolve21); + this._stdout.once("drain", resolve22); } }); } @@ -33184,7 +33184,7 @@ var Protocol = class { return; } const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3; - await new Promise((resolve21) => setTimeout(resolve21, pollInterval)); + await new Promise((resolve22) => setTimeout(resolve22, pollInterval)); options?.signal?.throwIfAborted(); } } catch (error51) { @@ -33201,7 +33201,7 @@ var Protocol = class { */ request(request, resultSchema, options) { const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {}; - return new Promise((resolve21, reject) => { + return new Promise((resolve22, reject) => { const earlyReject = (error51) => { reject(error51); }; @@ -33279,7 +33279,7 @@ var Protocol = class { if (!parseResult.success) { reject(parseResult.error); } else { - resolve21(parseResult.data); + resolve22(parseResult.data); } } catch (error51) { reject(error51); @@ -33540,12 +33540,12 @@ var Protocol = class { } } catch { } - return new Promise((resolve21, reject) => { + return new Promise((resolve22, reject) => { if (signal.aborted) { reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled")); return; } - const timeoutId = setTimeout(resolve21, interval); + const timeoutId = setTimeout(resolve22, interval); signal.addEventListener("abort", () => { clearTimeout(timeoutId); reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled")); @@ -41305,8 +41305,8 @@ var disconnect = (anyProcess) => { // ../node_modules/.pnpm/execa@9.6.1/node_modules/execa/lib/utils/deferred.js var createDeferred = () => { const methods = {}; - const promise2 = new Promise((resolve21, reject) => { - Object.assign(methods, { resolve: resolve21, reject }); + const promise2 = new Promise((resolve22, reject) => { + Object.assign(methods, { resolve: resolve22, reject }); }); return Object.assign(promise2, methods); }; @@ -44138,13 +44138,13 @@ var logOutputSync = ({ serializedResult, fdNumber, state, verboseInfo, encoding, } }; var writeToFiles = (serializedResult, stdioItems, outputFiles) => { - for (const { path: path94, append } of stdioItems.filter(({ type }) => FILE_TYPES.has(type))) { - const pathString = typeof path94 === "string" ? path94 : path94.toString(); + for (const { path: path95, append } of stdioItems.filter(({ type }) => FILE_TYPES.has(type))) { + const pathString = typeof path95 === "string" ? path95 : path95.toString(); if (append || outputFiles.has(pathString)) { - appendFileSync(path94, serializedResult); + appendFileSync(path95, serializedResult); } else { outputFiles.add(pathString); - writeFileSync(path94, serializedResult); + writeFileSync(path95, serializedResult); } } }; @@ -45948,11 +45948,11 @@ var addConcurrentStream = (concurrentStreams, stream, waitName) => { const promises = weakMap.get(stream); const promise2 = createDeferred(); promises.push(promise2); - const resolve21 = promise2.resolve.bind(promise2); - return { resolve: resolve21, promises }; + const resolve22 = promise2.resolve.bind(promise2); + return { resolve: resolve22, promises }; }; -var waitForConcurrentStreams = async ({ resolve: resolve21, promises }, subprocess) => { - resolve21(); +var waitForConcurrentStreams = async ({ resolve: resolve22, promises }, subprocess) => { + resolve22(); const [isSubprocessExit] = await Promise.race([ Promise.allSettled([true, subprocess]), Promise.all([false, ...promises]) @@ -46583,7 +46583,7 @@ function gitLockBackoffMs(attempt, random = Math.random) { return Math.floor(random() * window2); } function defaultGitLockSleep(ms) { - return new Promise((resolve21) => setTimeout(resolve21, ms)); + return new Promise((resolve22) => setTimeout(resolve22, ms)); } function isGitLockContention(value) { const stderr = typeof value === "string" ? value : String( @@ -49333,9 +49333,9 @@ function dedupePaths(paths) { } // src/tools/gather-retro-inputs.ts -var import_yaml25 = __toESM(require_dist2(), 1); +var import_yaml26 = __toESM(require_dist2(), 1); import { promises as fs41 } from "node:fs"; -import * as path51 from "node:path"; +import * as path52 from "node:path"; // src/schemas/cycle-state.ts import { promises as fs31 } from "node:fs"; @@ -49657,7 +49657,7 @@ async function computeSkillEffectiveness(opts) { // src/tools/write-native-story.ts import { createHash as createHash3 } from "node:crypto"; import { existsSync as existsSync2 } from "node:fs"; -import * as path47 from "node:path"; +import * as path48 from "node:path"; // src/adapters/native/parse-native-story.ts import { createHash } from "node:crypto"; @@ -50813,13 +50813,111 @@ async function resolveWorkspace(opts) { // src/validators/discipline-resolvability.ts import { promises as fs36 } from "node:fs"; +import * as path45 from "node:path"; + +// src/lib/find-package-root.ts +var import_yaml23 = __toESM(require_dist2(), 1); import * as path44 from "node:path"; +import { accessSync, readFileSync as readFileSync4, readdirSync as readdirSync2 } from "node:fs"; +function hasLocalVitest(dir) { + try { + accessSync(path44.join(dir, "node_modules", ".bin", "vitest")); + return true; + } catch { + return false; + } +} +function findVitestInWorkspaceMembers(workspaceRoot) { + try { + const yaml = readFileSync4( + path44.join(workspaceRoot, "pnpm-workspace.yaml"), + "utf8" + ); + const parsed = (0, import_yaml23.parse)(yaml); + const packages = parsed?.packages; + if (!Array.isArray(packages)) return { ok: false }; + for (const pattern of packages) { + if (typeof pattern !== "string") continue; + const segments = pattern.split("/"); + const hasGlob = segments.some((s) => s === "*" || s === "**"); + if (!hasGlob) { + const memberDir = path44.join(workspaceRoot, pattern); + if (hasLocalVitest(memberDir)) { + return { ok: true, packageRoot: memberDir }; + } + } else { + const parentSegments = segments.slice(0, segments.indexOf("*")); + const parentDir = path44.join(workspaceRoot, ...parentSegments); + try { + const entries = readdirSync2(parentDir, { withFileTypes: true }); + for (const entry of entries) { + if (!entry.isDirectory()) continue; + const memberDir = path44.join(parentDir, entry.name); + if (hasLocalVitest(memberDir)) { + return { ok: true, packageRoot: memberDir }; + } + } + } catch { + } + } + } + } catch { + } + return { ok: false }; +} +function findWorkspaceYamlInSubtree(root, maxDepth) { + if (maxDepth < 0) return null; + let entries; + try { + entries = readdirSync2(root, { withFileTypes: true }); + } catch { + return null; + } + if (entries.some((e) => e.isFile() && e.name === "pnpm-workspace.yaml")) { + return root; + } + for (const entry of entries) { + if (!entry.isDirectory()) continue; + const name = entry.name; + if (name === "node_modules" || name === ".git") continue; + const found = findWorkspaceYamlInSubtree(path44.join(root, name), maxDepth - 1); + if (found !== null) return found; + } + return null; +} +function findPackageRoot(opts) { + const checkRootAbs = path44.resolve(opts.checkRoot); + let dir = path44.dirname(opts.testFilePathAbs); + const isWithinCheckRoot = (d) => d === checkRootAbs || d.startsWith(checkRootAbs + path44.sep); + while (isWithinCheckRoot(dir)) { + try { + accessSync(path44.join(dir, "package.json")); + const memberResult = findVitestInWorkspaceMembers(dir); + if (memberResult.ok) { + return memberResult; + } + return { ok: true, packageRoot: dir }; + } catch { + } + const parent = path44.dirname(dir); + if (parent === dir) break; + dir = parent; + } + const workspaceYamlDir = findWorkspaceYamlInSubtree(checkRootAbs, 4); + if (workspaceYamlDir !== null) { + const memberResult = findVitestInWorkspaceMembers(workspaceYamlDir); + if (memberResult.ok) return memberResult; + } + return { ok: false }; +} + +// src/validators/discipline-resolvability.ts function isWellFormedTarget(target) { const t = target.trim(); if (t.length === 0) return false; if (/\s/.test(t)) return false; if (t.startsWith("-")) return false; - if (path44.isAbsolute(t)) return false; + if (path45.isAbsolute(t)) return false; return true; } function isRunnableTestTarget(target) { @@ -50847,7 +50945,7 @@ async function resolveDisciplinePaths(story, targetRepoRoot) { }); } else { for (const cited of citedSources) { - const abs = path44.resolve(targetRepoRoot, cited); + const abs = path45.resolve(targetRepoRoot, cited); if (await statOrNull(abs) === null) { reasons.push({ code: "unresolvable-cited-source", @@ -50881,7 +50979,7 @@ async function resolveDisciplinePaths(story, targetRepoRoot) { if (!v) continue; if (v.type !== "artifact") continue; if (!isWellFormedTarget(v.target)) continue; - const abs = path44.resolve(targetRepoRoot, v.target); + const abs = path45.resolve(targetRepoRoot, v.target); if (await statOrNull(abs) === null) { reasons.push({ code: "unresolvable-verification-target", @@ -50890,13 +50988,29 @@ async function resolveDisciplinePaths(story, targetRepoRoot) { }); } } + for (let i2 = 0; i2 < story.acceptance_criteria.length; i2++) { + const v = story.acceptance_criteria[i2].verification; + if (!v) continue; + if (v.type !== "vitest") continue; + if (!isWellFormedTarget(v.target)) continue; + if (!isRunnableTestTarget(v.target)) continue; + const testFilePathAbs = path45.resolve(targetRepoRoot, v.target); + const pkg = findPackageRoot({ testFilePathAbs, checkRoot: targetRepoRoot }); + if (!pkg.ok) { + reasons.push({ + code: "unresolvable-test-target", + field: `acceptance_criteria[${i2}].verification.target`, + detail: `AC${i2 + 1} vitest verification target '${v.target}' cannot resolve to a runnable package: no package.json was found between it (looked at '${testFilePathAbs}') and the repo root ('${targetRepoRoot}'). The test FILE need not exist yet (the build creates it), but a package must enclose it \u2014 this is the same upward walk the reviewer uses, so a target that fails here would also fail at review. A common cause is a wrong-prefix path (e.g. 'mcp-server/tests/x.test.ts' instead of 'plugins/flow/mcp-server/tests/x.test.ts'); fix the prefix so the target lands under a real package.` + }); + } + } return reasons; } // src/tools/scan-sources.ts -var import_yaml23 = __toESM(require_dist2(), 1); +var import_yaml24 = __toESM(require_dist2(), 1); import { promises as fs38 } from "node:fs"; -import * as path46 from "node:path"; +import * as path47 from "node:path"; // src/lib/extract-dep-refs.ts var NATIVE_REF_RE2 = /^native:[0-9A-HJKMNP-TV-Z]{26}$/; @@ -50942,7 +51056,7 @@ function extractDepRefsFromSpecBody(body) { // src/tools/classify-story-lane.ts var import_picomatch3 = __toESM(require_picomatch2(), 1); import { promises as fs37 } from "node:fs"; -import * as path45 from "node:path"; +import * as path46 from "node:path"; var SECURITY_SENSITIVE_PATTERNS = [ /migrations?\//i, /\.sql$/i, @@ -51090,7 +51204,7 @@ function applyHint(classified, authorHint, fullResultFn) { } async function matchSpecialistByCitedSources(citedSources, targetRepoRoot) { if (citedSources.length === 0) return null; - const teamDir = path45.join(targetRepoRoot, "team"); + const teamDir = path46.join(targetRepoRoot, "team"); let entries; try { entries = await fs37.readdir(teamDir); @@ -51103,7 +51217,7 @@ async function matchSpecialistByCitedSources(citedSources, targetRepoRoot) { for (const entry of entries.sort()) { if (SKIP_DIRS.has(entry) || entry.startsWith(".")) continue; if (BACKBONE_ROLES.has(entry)) continue; - const personaPath = path45.join(teamDir, entry, "PERSONA.md"); + const personaPath = path46.join(teamDir, entry, "PERSONA.md"); let raw; try { raw = await fs37.readFile(personaPath, "utf8"); @@ -51265,8 +51379,8 @@ async function statOrNull2(absPath) { } } function repoRelativePath(rawPath, targetRepoRoot) { - const rel = path46.relative(targetRepoRoot, rawPath); - if (rel.startsWith("..") || path46.isAbsolute(rel)) { + const rel = path47.relative(targetRepoRoot, rawPath); + if (rel.startsWith("..") || path47.isAbsolute(rel)) { return rawPath; } return rel; @@ -51334,7 +51448,7 @@ async function writeDepsDriftBlockedManifest(story, driftDetail, absBlockedPath, ] }); const blockedManifest = ExecutionManifestSchema.parse(blockedManifestRaw); - const yamlText = (0, import_yaml23.stringify)(blockedManifest, { lineWidth: 0 }); + const yamlText = (0, import_yaml24.stringify)(blockedManifest, { lineWidth: 0 }); await writeManagedFile({ absPath: absBlockedPath, contents: yamlText, @@ -51425,10 +51539,10 @@ async function scanSources(opts) { filesSeenCount: listingStats.filesSeenCount, filesRejected: listingStats.filesRejected }; - const stateRoot = path46.join(targetRepoRoot, ".flow", "state"); + const stateRoot = path47.join(targetRepoRoot, ".flow", "state"); { - const toDoDir = path46.join(stateRoot, "to-do"); - const blockedDir = path46.join(stateRoot, "blocked"); + const toDoDir = path47.join(stateRoot, "to-do"); + const blockedDir = path47.join(stateRoot, "blocked"); let toDoFiles = []; let blockedFiles = []; try { @@ -51447,14 +51561,14 @@ async function scanSources(opts) { console.warn( `[scanSources] Ref ${ref} exists in both to-do/ and blocked/ \u2014 recovering by removing stale blocked/ manifest (to-do/ wins).` ); - await fs38.unlink(path46.join(blockedDir, blockedFile)); + await fs38.unlink(path47.join(blockedDir, blockedFile)); } } } for (const story of sourceStories) { let currentState = null; for (const stateName of STATE_NAMES) { - const absPath = path46.join(stateRoot, stateName, `${story.ref}.yaml`); + const absPath = path47.join(stateRoot, stateName, `${story.ref}.yaml`); const s = await statOrNull2(absPath); if (s !== null) { currentState = stateName; @@ -51469,9 +51583,9 @@ async function scanSources(opts) { continue; } if (currentState === "blocked") { - const absBlockedPath = path46.join(stateRoot, "blocked", `${story.ref}.yaml`); + const absBlockedPath = path47.join(stateRoot, "blocked", `${story.ref}.yaml`); const rawBlocked = await fs38.readFile(absBlockedPath, "utf8"); - const parsedBlocked = (0, import_yaml23.parse)(rawBlocked); + const parsedBlocked = (0, import_yaml24.parse)(rawBlocked); const existingBlockedHash = parsedBlocked["source_hash"]; if (existingBlockedHash === story.source_hash) { result.skippedRefs.push({ ref: story.ref, reason: "not-in-to-do" }); @@ -51501,10 +51615,10 @@ async function scanSources(opts) { } const disciplineResult = await runFullDisciplineGate(story, activeAdapter, targetRepoRoot); if (disciplineResult === null) { - const absToDoPathNew = path46.join(stateRoot, "to-do", `${story.ref}.yaml`); + const absToDoPathNew = path47.join(stateRoot, "to-do", `${story.ref}.yaml`); const riskFields = await computeAuthorTimeRiskFields(story, targetRepoRoot, pluginRoot); const manifest = composeManifest(story, activeAdapterName, targetRepoRoot, riskFields); - const yamlText = (0, import_yaml23.stringify)(manifest, { lineWidth: 0 }); + const yamlText = (0, import_yaml24.stringify)(manifest, { lineWidth: 0 }); await writeManagedFile({ absPath: absToDoPathNew, contents: yamlText, @@ -51538,7 +51652,7 @@ async function scanSources(opts) { })) }); const blockedManifest = ExecutionManifestSchema.parse(blockedManifestRaw); - const yamlText = (0, import_yaml23.stringify)(blockedManifest, { lineWidth: 0 }); + const yamlText = (0, import_yaml24.stringify)(blockedManifest, { lineWidth: 0 }); await writeManagedFile({ absPath: absBlockedPath, contents: yamlText, @@ -51558,7 +51672,7 @@ async function scanSources(opts) { if (currentState === null) { const driftDetail = await checkDepsDrift(story); if (driftDetail !== null) { - const absBlockedPath = path46.join(stateRoot, "blocked", `${story.ref}.yaml`); + const absBlockedPath = path47.join(stateRoot, "blocked", `${story.ref}.yaml`); await writeDepsDriftBlockedManifest( story, driftDetail, @@ -51611,8 +51725,8 @@ async function scanSources(opts) { })) }); const blockedManifest = ExecutionManifestSchema.parse(blockedManifestRaw); - const absBlockedPath = path46.join(stateRoot, "blocked", `${story.ref}.yaml`); - const yamlText = (0, import_yaml23.stringify)(blockedManifest, { lineWidth: 0 }); + const absBlockedPath = path47.join(stateRoot, "blocked", `${story.ref}.yaml`); + const yamlText = (0, import_yaml24.stringify)(blockedManifest, { lineWidth: 0 }); await writeManagedFile({ absPath: absBlockedPath, contents: yamlText, @@ -51623,11 +51737,11 @@ async function scanSources(opts) { continue; } } - const absToDoPath = path46.join(stateRoot, "to-do", `${story.ref}.yaml`); + const absToDoPath = path47.join(stateRoot, "to-do", `${story.ref}.yaml`); if (currentState === null) { const riskFields = await computeAuthorTimeRiskFields(story, targetRepoRoot, pluginRoot); const manifest = composeManifest(story, activeAdapterName, targetRepoRoot, riskFields); - const yamlText = (0, import_yaml23.stringify)(manifest, { lineWidth: 0 }); + const yamlText = (0, import_yaml24.stringify)(manifest, { lineWidth: 0 }); await writeManagedFile({ absPath: absToDoPath, contents: yamlText, @@ -51650,7 +51764,7 @@ async function scanSources(opts) { } let existingManifest; try { - const parsed = (0, import_yaml23.parse)(rawText); + const parsed = (0, import_yaml24.parse)(rawText); existingManifest = parseExecutionManifest(parsed, { absPath: absToDoPath }); } catch (err) { const detailMessage = err instanceof Error ? err.message : String(err); @@ -51664,7 +51778,7 @@ async function scanSources(opts) { if (existingManifest.source_hash !== story.source_hash) { const driftDetail = await checkDepsDrift(story); if (driftDetail !== null) { - const absBlockedPath = path46.join(stateRoot, "blocked", `${story.ref}.yaml`); + const absBlockedPath = path47.join(stateRoot, "blocked", `${story.ref}.yaml`); await writeDepsDriftBlockedManifest( story, driftDetail, @@ -51691,7 +51805,7 @@ async function scanSources(opts) { source_hash: story.source_hash, source_path: repoRelativePath(story.raw_path, targetRepoRoot) }; - const yamlText = (0, import_yaml23.stringify)(stripUndefined4(updatedManifest), { + const yamlText = (0, import_yaml24.stringify)(stripUndefined4(updatedManifest), { lineWidth: 0 }); await writeManagedFile({ @@ -51904,7 +52018,7 @@ function resolveDefaultDefinitionOfDone(targetRepoRoot, execSyncImpl, existsImpl return PROJECT_AGNOSTIC_DEFINITION_OF_DONE; } const checkExists = existsImpl ?? existsSync2; - const sentinelPath = path47.join(targetRepoRoot, FLOW_REPO_DISK_SENTINEL); + const sentinelPath = path48.join(targetRepoRoot, FLOW_REPO_DISK_SENTINEL); if (checkExists(sentinelPath)) { return FLOW_DEFINITION_OF_DONE; } @@ -51978,7 +52092,7 @@ function renderNativeStoryBody(input, resolvedDod = FLOW_DEFINITION_OF_DONE) { } async function writeNativeStory(rawInput, seams) { const input = WriteNativeStoryInputSchema.parse(rawInput); - const targetRepoRoot = path47.resolve(input.targetRepoRoot); + const targetRepoRoot = path48.resolve(input.targetRepoRoot); const workspace = await resolveWorkspace({ targetRepoRoot }); if (workspace.activeAdapterName !== "native") { throw new WrongAdapterError({ @@ -52005,8 +52119,8 @@ async function writeNativeStory(rawInput, seams) { } async function renderGateWriteNativeStory(input, targetRepoRoot, agent = "author", execSyncImpl, existsImpl) { const newUlid = ulid3(); - const storiesDir = path47.join(targetRepoRoot, ".flow", "native-stories"); - const absPath = path47.join(storiesDir, `${newUlid}.md`); + const storiesDir = path48.join(targetRepoRoot, ".flow", "native-stories"); + const absPath = path48.join(storiesDir, `${newUlid}.md`); const ref = `native:${newUlid}`; const resolvedDod = resolveDefaultDefinitionOfDone(targetRepoRoot, execSyncImpl, existsImpl); const candidate = inputToSourceStory(input, ref, absPath, resolvedDod); @@ -52090,9 +52204,9 @@ function inputToSourceStory(input, ref, absPath, resolvedDod = FLOW_DEFINITION_O } // src/tools/read-backlog-inventory.ts -var import_yaml24 = __toESM(require_dist2(), 1); +var import_yaml25 = __toESM(require_dist2(), 1); import { promises as fs39 } from "node:fs"; -import * as path48 from "node:path"; +import * as path49 from "node:path"; var ReadBacklogInventoryInputSchema = external_exports.object({ targetRepoRoot: external_exports.string().min(1), /** @@ -52118,15 +52232,15 @@ function extractH1Title(content, fallback) { } async function readBacklogInventory(rawInput) { const input = ReadBacklogInventoryInputSchema.parse(rawInput); - const targetRepoRoot = path48.resolve(input.targetRepoRoot); + const targetRepoRoot = path49.resolve(input.targetRepoRoot); const workspace = await resolveWorkspace({ targetRepoRoot }); const isNative = workspace.activeAdapterName === "native"; - const stateRoot = path48.join(targetRepoRoot, ".flow", "state"); - const doneDir = path48.join(stateRoot, "done"); + const stateRoot = path49.join(targetRepoRoot, ".flow", "state"); + const doneDir = path49.join(stateRoot, "done"); const inventory = []; const seenRefs = /* @__PURE__ */ new Set(); for (const stateName of STATE_NAMES) { - const stateDir = path48.join(stateRoot, stateName); + const stateDir = path49.join(stateRoot, stateName); let entries; try { entries = await fs39.readdir(stateDir); @@ -52135,14 +52249,14 @@ async function readBacklogInventory(rawInput) { } for (const filename of entries) { if (!filename.endsWith(".yaml") || filename.endsWith(".snapshot.yaml")) continue; - const absPath = path48.join(stateDir, filename); + const absPath = path49.join(stateDir, filename); const rawText = await fs39.readFile(absPath, "utf8"); - const parsed = (0, import_yaml24.parse)(rawText); + const parsed = (0, import_yaml25.parse)(rawText); const manifest = parseExecutionManifest(parsed, { absPath }); let depsReady = true; for (const dep of manifest.depends_on) { try { - await fs39.stat(path48.join(doneDir, `${dep}.yaml`)); + await fs39.stat(path49.join(doneDir, `${dep}.yaml`)); } catch { depsReady = false; break; @@ -52157,7 +52271,7 @@ async function readBacklogInventory(rawInput) { depsReady }; if (input.includeSpecText && (!input.ref || manifest.ref === input.ref)) { - const specAbs = path48.isAbsolute(manifest.source_path) ? manifest.source_path : path48.join(targetRepoRoot, manifest.source_path); + const specAbs = path49.isAbsolute(manifest.source_path) ? manifest.source_path : path49.join(targetRepoRoot, manifest.source_path); try { entry.specText = await fs39.readFile(specAbs, "utf8"); } catch { @@ -52169,7 +52283,7 @@ async function readBacklogInventory(rawInput) { } } if (isNative) { - const nativeStoriesDir2 = path48.join(targetRepoRoot, ".flow", "native-stories"); + const nativeStoriesDir2 = path49.join(targetRepoRoot, ".flow", "native-stories"); let nativeFiles; try { nativeFiles = await fs39.readdir(nativeStoriesDir2); @@ -52182,7 +52296,7 @@ async function readBacklogInventory(rawInput) { if (!ULID_PATTERN.test(basename6)) continue; const ref = `native:${basename6}`; if (seenRefs.has(ref)) continue; - const absPath = path48.join(nativeStoriesDir2, filename); + const absPath = path49.join(nativeStoriesDir2, filename); const content = await fs39.readFile(absPath, "utf8"); const title = extractH1Title(content, basename6); const entry = { @@ -52205,7 +52319,7 @@ async function readBacklogInventory(rawInput) { } // src/tools/record-maintainer-feedback.ts -import * as path49 from "node:path"; +import * as path50 from "node:path"; // src/schemas/maintainer-feedback.ts var MaintainerFeedbackItemSchema = external_exports.object({ @@ -52245,11 +52359,11 @@ function parseMaintainerFeedbackInput(input) { } // src/tools/record-maintainer-feedback.ts -var INBOX_SUBDIR = path49.join(".flow", "maintainer-inbox"); +var INBOX_SUBDIR = path50.join(".flow", "maintainer-inbox"); function maintainerInboxItemPath(targetRepoRoot, id, raisedAt) { const safeTs = raisedAt.replace(/[:.]/g, "-"); const filename = `${safeTs}-${id}.json`; - return path49.join(targetRepoRoot, INBOX_SUBDIR, filename); + return path50.join(targetRepoRoot, INBOX_SUBDIR, filename); } async function recordMaintainerFeedback(opts) { const { targetRepoRoot, item } = opts; @@ -52291,7 +52405,7 @@ async function recordMaintainerFeedback(opts) { // src/tools/analyze-team-fit.ts import { promises as fs40 } from "node:fs"; -import * as path50 from "node:path"; +import * as path51 from "node:path"; // src/lib/specialist-domain-map.ts var GENERALIST_BACKBONE_ROLES = /* @__PURE__ */ new Set([ @@ -52318,7 +52432,7 @@ var AnalyzeTeamFitInputSchema = external_exports.object({ }); var MONTH_BUCKET_REGEX = /^\d{4}-\d{2}\.jsonl$/; async function readTelemetrySummary(targetRepoRoot) { - const telemetryDir = path50.join(targetRepoRoot, ".flow", "telemetry"); + const telemetryDir = path51.join(targetRepoRoot, ".flow", "telemetry"); const stallsByDomain = /* @__PURE__ */ new Map(); const usefulWorkByRole = /* @__PURE__ */ new Map(); let entries; @@ -52329,7 +52443,7 @@ async function readTelemetrySummary(targetRepoRoot) { } for (const entry of entries) { if (!MONTH_BUCKET_REGEX.test(entry)) continue; - const filePath = path50.join(telemetryDir, entry); + const filePath = path51.join(telemetryDir, entry); let raw; try { raw = await fs40.readFile(filePath, "utf8"); @@ -52364,7 +52478,7 @@ async function readTelemetrySummary(targetRepoRoot) { return { stallsByDomain, usefulWorkByRole }; } async function readHiredRoles(targetRepoRoot) { - const teamDir = path50.join(targetRepoRoot, "team"); + const teamDir = path51.join(targetRepoRoot, "team"); let dirEntries; try { dirEntries = await fs40.readdir(teamDir); @@ -52377,13 +52491,13 @@ async function readHiredRoles(targetRepoRoot) { if (SKIP_DIRS.has(entry) || entry.startsWith(".")) continue; let stat2; try { - stat2 = await fs40.stat(path50.join(teamDir, entry)); + stat2 = await fs40.stat(path51.join(teamDir, entry)); } catch { continue; } if (!stat2.isDirectory()) continue; try { - await fs40.access(path50.join(teamDir, entry, "PERSONA.md")); + await fs40.access(path51.join(teamDir, entry, "PERSONA.md")); } catch { continue; } @@ -52395,7 +52509,7 @@ async function readHiredRoles(targetRepoRoot) { async function buildAvailableRoleSet(targetRepoRoot, pluginRoot) { const domainToRole = /* @__PURE__ */ new Map(); const availableRoleIds = /* @__PURE__ */ new Set(); - const catalogueDir = path50.join(pluginRoot, "catalogue"); + const catalogueDir = path51.join(pluginRoot, "catalogue"); let catalogueEntries = []; try { catalogueEntries = await fs40.readdir(catalogueDir); @@ -52406,14 +52520,14 @@ async function buildAvailableRoleSet(targetRepoRoot, pluginRoot) { const roleId = entry.slice(0, -3); if (GENERALIST_BACKBONE_ROLES.has(roleId)) continue; try { - const raw = await fs40.readFile(path50.join(catalogueDir, entry), "utf8"); - const role = parseCatalogueRole(raw, path50.join(catalogueDir, entry)); + const raw = await fs40.readFile(path51.join(catalogueDir, entry), "utf8"); + const role = parseCatalogueRole(raw, path51.join(catalogueDir, entry)); domainToRole.set(role.domain, role.role); availableRoleIds.add(role.role); } catch { } } - const customDir = path50.join(targetRepoRoot, "team", "custom"); + const customDir = path51.join(targetRepoRoot, "team", "custom"); let customEntries = []; try { customEntries = await fs40.readdir(customDir); @@ -52424,8 +52538,8 @@ async function buildAvailableRoleSet(targetRepoRoot, pluginRoot) { const roleId = entry.slice(0, -3); if (GENERALIST_BACKBONE_ROLES.has(roleId)) continue; try { - const raw = await fs40.readFile(path50.join(customDir, entry), "utf8"); - const role = parseCatalogueRole(raw, path50.join(customDir, entry)); + const raw = await fs40.readFile(path51.join(customDir, entry), "utf8"); + const role = parseCatalogueRole(raw, path51.join(customDir, entry)); domainToRole.set(role.domain, role.role); availableRoleIds.add(role.role); } catch { @@ -52585,7 +52699,7 @@ async function gatherRetroInputs(opts) { return { doneManifests, telemetrySummary, priorProposals, ruleRegistry, fireCountSignal, recurringFriction, skillEffectiveness, mechanicalFailuresDrafted, nearDuplicateLessonPairs, retirableLessons, crossRoleSharedLessons, teamFitSignal }; } async function gatherDoneManifests(targetRepoRoot, windowStartMs) { - const doneDir = path51.join(targetRepoRoot, ".flow", "state", "done"); + const doneDir = path52.join(targetRepoRoot, ".flow", "state", "done"); let entries; try { entries = await fs41.readdir(doneDir); @@ -52598,7 +52712,7 @@ async function gatherDoneManifests(targetRepoRoot, windowStartMs) { const manifestFiles = entries.filter((f) => f.endsWith(".yaml") && !f.endsWith(".snapshot.yaml")).sort(); const manifests = []; for (const file2 of manifestFiles) { - const absPath = path51.join(doneDir, file2); + const absPath = path52.join(doneDir, file2); if (windowStartMs !== null) { const stat2 = await fs41.stat(absPath); if (stat2.mtimeMs < windowStartMs) { @@ -52606,13 +52720,13 @@ async function gatherDoneManifests(targetRepoRoot, windowStartMs) { } } const raw = await fs41.readFile(absPath, "utf8"); - const parsed = (0, import_yaml25.parse)(raw); + const parsed = (0, import_yaml26.parse)(raw); manifests.push(parseExecutionManifest(parsed, { absPath })); } return manifests; } async function gatherTelemetry(targetRepoRoot, windowStartMs) { - const telemetryDir = path51.join(targetRepoRoot, ".flow", "telemetry"); + const telemetryDir = path52.join(targetRepoRoot, ".flow", "telemetry"); let entries; try { entries = await fs41.readdir(telemetryDir); @@ -52626,7 +52740,7 @@ async function gatherTelemetry(targetRepoRoot, windowStartMs) { const events = []; let skipped_count = 0; for (const file2 of files) { - const absPath = path51.join(telemetryDir, file2); + const absPath = path52.join(telemetryDir, file2); const raw = await fs41.readFile(absPath, "utf8"); const lines = raw.split("\n"); for (const line of lines) { @@ -52654,7 +52768,7 @@ async function gatherTelemetry(targetRepoRoot, windowStartMs) { return { events, skipped_count }; } async function gatherPriorProposals(targetRepoRoot) { - const proposalsDir = path51.join(targetRepoRoot, ".flow", "retro-proposals"); + const proposalsDir = path52.join(targetRepoRoot, ".flow", "retro-proposals"); let entries; try { entries = await fs41.readdir(proposalsDir); @@ -52665,7 +52779,7 @@ async function gatherPriorProposals(targetRepoRoot) { throw err; } const proposals = entries.filter((f) => f.endsWith(".md")).map((f) => ({ - path: path51.join(proposalsDir, f), + path: path52.join(proposalsDir, f), // The writer keys the filename by ISO timestamp (Story 6.3): // `.md`. Strip the `.md` suffix to recover it. iso_timestamp: f.slice(0, -".md".length) @@ -52674,7 +52788,7 @@ async function gatherPriorProposals(targetRepoRoot) { return proposals; } async function gatherRuleRegistry(targetRepoRoot) { - const registryPath = path51.join( + const registryPath = path52.join( targetRepoRoot, "docs", "discipline-rules.yaml" @@ -52835,7 +52949,7 @@ function recurringFrictionDedupKey(kind) { return `${RECURRING_FRICTION_DEDUP_PREFIX}${kind}`; } async function readExistingDedupKeys(targetRepoRoot) { - const inboxDir = path51.join(targetRepoRoot, ".flow", "maintainer-inbox"); + const inboxDir = path52.join(targetRepoRoot, ".flow", "maintainer-inbox"); let entries; try { entries = await fs41.readdir(inboxDir); @@ -52846,7 +52960,7 @@ async function readExistingDedupKeys(targetRepoRoot) { const keys = /* @__PURE__ */ new Set(); for (const file2 of entries) { if (!file2.endsWith(".json")) continue; - const absPath = path51.join(inboxDir, file2); + const absPath = path52.join(inboxDir, file2); let raw; try { raw = await fs41.readFile(absPath, "utf8"); @@ -52947,7 +53061,7 @@ function tokenise(text) { } var NEAR_DUPLICATE_THRESHOLD = 0.35; async function gatherNearDuplicateLessonPairs(targetRepoRoot) { - const teamDir = path51.join(targetRepoRoot, "team"); + const teamDir = path52.join(targetRepoRoot, "team"); let roleEntries; try { roleEntries = await fs41.readdir(teamDir); @@ -52961,12 +53075,12 @@ async function gatherNearDuplicateLessonPairs(targetRepoRoot) { if (SKIP_DIRS.has(entry) || entry.startsWith(".")) continue; let stat2; try { - stat2 = await fs41.stat(path51.join(teamDir, entry)); + stat2 = await fs41.stat(path52.join(teamDir, entry)); } catch { continue; } if (!stat2.isDirectory()) continue; - const personaPath = path51.join(teamDir, entry, "PERSONA.md"); + const personaPath = path52.join(teamDir, entry, "PERSONA.md"); let raw; try { raw = await fs41.readFile(personaPath, "utf8"); @@ -53006,7 +53120,7 @@ async function gatherNearDuplicateLessonPairs(targetRepoRoot) { return pairs; } async function gatherRetirableLessons(targetRepoRoot, opts = {}) { - const teamDir = path51.join(targetRepoRoot, "team"); + const teamDir = path52.join(targetRepoRoot, "team"); let roleEntries; try { roleEntries = await fs41.readdir(teamDir); @@ -53020,12 +53134,12 @@ async function gatherRetirableLessons(targetRepoRoot, opts = {}) { if (SKIP_DIRS.has(entry) || entry.startsWith(".")) continue; let stat2; try { - stat2 = await fs41.stat(path51.join(teamDir, entry)); + stat2 = await fs41.stat(path52.join(teamDir, entry)); } catch { continue; } if (!stat2.isDirectory()) continue; - const personaPath = path51.join(teamDir, entry, "PERSONA.md"); + const personaPath = path52.join(teamDir, entry, "PERSONA.md"); let raw; try { raw = await fs41.readFile(personaPath, "utf8"); @@ -53049,7 +53163,7 @@ async function gatherRetirableLessons(targetRepoRoot, opts = {}) { } var CROSS_ROLE_SIMILARITY_THRESHOLD = 0.35; async function gatherCrossRoleSharedLessons(targetRepoRoot) { - const teamDir = path51.join(targetRepoRoot, "team"); + const teamDir = path52.join(targetRepoRoot, "team"); let roleEntries; try { roleEntries = await fs41.readdir(teamDir); @@ -53063,12 +53177,12 @@ async function gatherCrossRoleSharedLessons(targetRepoRoot) { if (SKIP_DIRS.has(entry) || entry.startsWith(".")) continue; let stat2; try { - stat2 = await fs41.stat(path51.join(teamDir, entry)); + stat2 = await fs41.stat(path52.join(teamDir, entry)); } catch { continue; } if (!stat2.isDirectory()) continue; - const personaPath = path51.join(teamDir, entry, "PERSONA.md"); + const personaPath = path52.join(teamDir, entry, "PERSONA.md"); let raw; try { raw = await fs41.readFile(personaPath, "utf8"); @@ -53142,9 +53256,9 @@ function isEnoent10(err) { } // src/tools/list-claimable-todos.ts -var import_yaml26 = __toESM(require_dist2(), 1); +var import_yaml27 = __toESM(require_dist2(), 1); import { promises as fs42 } from "node:fs"; -import * as path52 from "node:path"; +import * as path53 from "node:path"; // src/lib/short-handle.ts function shortHandle(ref) { @@ -53159,10 +53273,10 @@ function shortHandle(ref) { // src/tools/list-claimable-todos.ts async function listClaimableTodos(opts) { const { targetRepoRoot } = opts; - const stateRoot = path52.join(targetRepoRoot, ".flow", "state"); - const todoDir = path52.join(stateRoot, "to-do"); - const inProgressDir = path52.join(stateRoot, "in-progress"); - const doneDir = path52.join(stateRoot, "done"); + const stateRoot = path53.join(targetRepoRoot, ".flow", "state"); + const todoDir = path53.join(stateRoot, "to-do"); + const inProgressDir = path53.join(stateRoot, "in-progress"); + const doneDir = path53.join(stateRoot, "done"); let todoEntries; try { todoEntries = await fs42.readdir(todoDir); @@ -53176,7 +53290,7 @@ async function listClaimableTodos(opts) { const yamlEntries = todoEntries.filter((f) => f.endsWith(".yaml")).sort(); const candidates = []; for (const entry of yamlEntries) { - const absPath = path52.join(todoDir, entry); + const absPath = path53.join(todoDir, entry); let raw; try { raw = await fs42.readFile(absPath, "utf8"); @@ -53186,14 +53300,14 @@ async function listClaimableTodos(opts) { } throw err; } - const parsed = (0, import_yaml26.parse)(raw); + const parsed = (0, import_yaml27.parse)(raw); const manifest = parseExecutionManifest(parsed, { absPath }); if (!isClaimable(manifest)) { continue; } let depsReady = true; for (const dep of manifest.depends_on) { - const depPath = path52.join(doneDir, `${dep}.yaml`); + const depPath = path53.join(doneDir, `${dep}.yaml`); try { await fs42.stat(depPath); } catch (err) { @@ -53324,7 +53438,7 @@ function renderBacklogDashboard(snapshot) { } // src/tools/get-status.ts -import * as path53 from "node:path"; +import * as path54 from "node:path"; // src/schemas/status-report.ts var ULID_REGEX2 = /^[0-9A-HJKMNP-TV-Z]{26}$/; @@ -53392,7 +53506,7 @@ async function getStatus(opts) { throw err; } } - const standardsPath = path53.join(workspace.targetRepoRoot, "docs", "standards.md"); + const standardsPath = path54.join(workspace.targetRepoRoot, "docs", "standards.md"); let standardsReport; try { await lookupStandards(workspace.targetRepoRoot); @@ -53432,7 +53546,7 @@ function renderStatus(report) { // src/tools/init-workspace.ts import { mkdir as mkdir2, access, readFile as readFile2 } from "node:fs/promises"; -import * as path54 from "node:path"; +import * as path55 from "node:path"; var STATE_DIRS = ["to-do", "in-progress", "blocked", "done"]; async function pathExists(p) { try { @@ -53447,8 +53561,8 @@ async function initWorkspace(args) { const root = args.targetRepoRoot; const created = []; const skipped = []; - const flowDir = path54.join(root, ".flow"); - const configPath = path54.join(flowDir, "config.yaml"); + const flowDir = path55.join(root, ".flow"); + const configPath = path55.join(flowDir, "config.yaml"); if (await pathExists(configPath)) { skipped.push(".flow/config.yaml"); } else { @@ -53463,7 +53577,7 @@ adapter_config: {} created.push(".flow/config.yaml"); } for (const sub of STATE_DIRS) { - const dir = path54.join(flowDir, "state", sub); + const dir = path55.join(flowDir, "state", sub); if (await pathExists(dir)) { skipped.push(`.flow/state/${sub}/`); } else { @@ -53472,7 +53586,7 @@ adapter_config: {} } } if (adapter === "native") { - const nativeStories = path54.join(flowDir, "native-stories"); + const nativeStories = path55.join(flowDir, "native-stories"); if (await pathExists(nativeStories)) { skipped.push(".flow/native-stories/"); } else { @@ -53480,12 +53594,12 @@ adapter_config: {} created.push(".flow/native-stories/"); } } - const standardsTarget = path54.join(root, "docs", "standards.md"); + const standardsTarget = path55.join(root, "docs", "standards.md"); if (await pathExists(standardsTarget)) { skipped.push("docs/standards.md"); } else { const template = await readFile2( - path54.join(args.pluginRoot, "docs", "standards-example.md"), + path55.join(args.pluginRoot, "docs", "standards-example.md"), "utf8" ); await writeManagedFile({ @@ -53497,7 +53611,7 @@ adapter_config: {} created.push("docs/standards.md"); } let claudeIgnored = false; - const gitignorePath = path54.join(root, ".gitignore"); + const gitignorePath = path55.join(root, ".gitignore"); const CLAUDE_IGNORE_RULE = ".claude/"; const existingGitignore = await pathExists(gitignorePath); const currentContent = existingGitignore ? await readFile2(gitignorePath, "utf8") : ""; @@ -53524,8 +53638,8 @@ adapter_config: {} adapter, created, skipped, - gitPresent: await pathExists(path54.join(root, ".git")), - teamPresent: await pathExists(path54.join(root, "team")), + gitPresent: await pathExists(path55.join(root, ".git")), + teamPresent: await pathExists(path55.join(root, "team")), configPath, claudeIgnored }; @@ -53580,8 +53694,8 @@ function renderInitWorkspace(result) { } // src/tools/open-cycle.ts -var import_yaml27 = __toESM(require_dist2(), 1); -import * as path55 from "node:path"; +var import_yaml28 = __toESM(require_dist2(), 1); +import * as path56 from "node:path"; function isoForFilename(iso) { return iso.replace(/[:.]/g, "-"); } @@ -53633,7 +53747,7 @@ async function archivePriorCycle(targetRepoRoot, priorCycle, openedAt) { done_manifests: inputs.doneManifests, retro_proposals: inputs.priorProposals.map((p) => ({ // Store repo-relative paths so the archive is portable. - path: path55.relative(targetRepoRoot, p.path), + path: path56.relative(targetRepoRoot, p.path), iso_timestamp: p.iso_timestamp })), telemetry_summary: { @@ -53642,21 +53756,21 @@ async function archivePriorCycle(targetRepoRoot, priorCycle, openedAt) { } }; const fileName = `${priorCycle.cycle_ulid}-${isoForFilename(openedAt)}.yaml`; - const absPath = path55.join(targetRepoRoot, ".flow", "cycle-archive", fileName); - await atomicWriteFile(absPath, (0, import_yaml27.stringify)(archiveRecord, { lineWidth: 0 })); + const absPath = path56.join(targetRepoRoot, ".flow", "cycle-archive", fileName); + await atomicWriteFile(absPath, (0, import_yaml28.stringify)(archiveRecord, { lineWidth: 0 })); return absPath; } // src/tools/get-team-snapshot.ts import { promises as fs44 } from "node:fs"; -import * as path57 from "node:path"; +import * as path58 from "node:path"; // src/lib/team-stats.ts import { promises as fs43 } from "node:fs"; -import * as path56 from "node:path"; +import * as path57 from "node:path"; var MONTH_BUCKET_REGEX2 = /^\d{4}-\d{2}\.jsonl$/; async function readTeamTelemetryStats(opts) { - const telemetryDir = path56.join(opts.targetRepoRoot, ".flow", "telemetry"); + const telemetryDir = path57.join(opts.targetRepoRoot, ".flow", "telemetry"); let entries; try { entries = await fs43.readdir(telemetryDir); @@ -53673,7 +53787,7 @@ async function readTeamTelemetryStats(opts) { if (!MONTH_BUCKET_REGEX2.test(entry)) { continue; } - const filePath = path56.join(telemetryDir, entry); + const filePath = path57.join(telemetryDir, entry); const raw = await fs43.readFile(filePath, "utf8"); const lines = raw.split("\n"); let fileHasMalformation = false; @@ -53753,7 +53867,7 @@ var TeamSnapshotSchema = external_exports.object({ async function getTeamSnapshot(opts) { const { targetRepoRoot } = opts; const knowledgeLimit = opts.knowledgeLimit ?? 3; - const teamDir = path57.join(targetRepoRoot, "team"); + const teamDir = path58.join(targetRepoRoot, "team"); let dirEntries; try { dirEntries = await fs44.readdir(teamDir); @@ -53777,7 +53891,7 @@ async function getTeamSnapshot(opts) { } let stat2; try { - stat2 = await fs44.stat(path57.join(teamDir, entry)); + stat2 = await fs44.stat(path58.join(teamDir, entry)); } catch { continue; } @@ -53913,9 +54027,9 @@ function isEnoent13(err) { // src/tools/lookup-role-by-domain.ts import { promises as fs45 } from "node:fs"; -import * as path58 from "node:path"; +import * as path59 from "node:path"; async function lookupRoleByDomain(opts) { - const teamDir = path58.join(opts.targetRepoRoot, "team"); + const teamDir = path59.join(opts.targetRepoRoot, "team"); try { await fs45.stat(teamDir); } catch (err) { @@ -53927,7 +54041,7 @@ async function lookupRoleByDomain(opts) { const entries = await fs45.readdir(teamDir); for (const entry of entries) { if (entry === "custom" || entry === "_archived") continue; - const subPath = path58.join(teamDir, entry); + const subPath = path59.join(teamDir, entry); let isDir = false; try { const stat2 = await fs45.stat(subPath); @@ -53960,9 +54074,9 @@ function isEnoent14(err) { } // src/tools/mark-withdrawn.ts -var import_yaml28 = __toESM(require_dist2(), 1); +var import_yaml29 = __toESM(require_dist2(), 1); import { promises as fs46 } from "node:fs"; -import * as path59 from "node:path"; +import * as path60 from "node:path"; var MarkWithdrawnInputSchema = external_exports.object({ targetRepoRoot: external_exports.string().min(1), ref: external_exports.string().min(1) @@ -53973,14 +54087,14 @@ function stripUndefined5(obj) { ); } function serialiseManifest(manifest) { - return (0, import_yaml28.stringify)( + return (0, import_yaml29.stringify)( stripUndefined5(manifest), { lineWidth: 0 } ); } async function markWithdrawn(rawInput) { const input = MarkWithdrawnInputSchema.parse(rawInput); - const targetRepoRoot = path59.resolve(input.targetRepoRoot); + const targetRepoRoot = path60.resolve(input.targetRepoRoot); const { ref } = input; const workspace = await resolveWorkspace({ targetRepoRoot }); if (workspace.activeAdapterName === "native") { @@ -53991,11 +54105,11 @@ async function markWithdrawn(rawInput) { toolName: "markWithdrawn" }); } - const stateRoot = path59.join(targetRepoRoot, ".flow", "state"); + const stateRoot = path60.join(targetRepoRoot, ".flow", "state"); let foundState = null; let foundAbsPath = null; for (const stateName of STATE_NAMES) { - const candidate = path59.join(stateRoot, stateName, `${ref}.yaml`); + const candidate = path60.join(stateRoot, stateName, `${ref}.yaml`); try { await fs46.stat(candidate); foundState = stateName; @@ -54007,12 +54121,12 @@ async function markWithdrawn(rawInput) { if (foundState === null || foundAbsPath === null) { throw new ManifestNotFoundError({ ref, - expectedAbsPath: path59.join(stateRoot, "to-do", `${ref}.yaml`), + expectedAbsPath: path60.join(stateRoot, "to-do", `${ref}.yaml`), fromState: "to-do" }); } const rawText = await fs46.readFile(foundAbsPath, "utf8"); - const parsed = (0, import_yaml28.parse)(rawText); + const parsed = (0, import_yaml29.parse)(rawText); const manifest = parseExecutionManifest(parsed, { absPath: foundAbsPath }); if (manifest.withdrawn === true) { return { @@ -54033,9 +54147,9 @@ async function markWithdrawn(rawInput) { } // src/tools/mark-story-ready.ts -var import_yaml29 = __toESM(require_dist2(), 1); +var import_yaml30 = __toESM(require_dist2(), 1); import { promises as fs47 } from "node:fs"; -import * as path60 from "node:path"; +import * as path61 from "node:path"; var MarkStoryReadyInputSchema = external_exports.object({ targetRepoRoot: external_exports.string().min(1), ref: external_exports.string().min(1), @@ -54049,14 +54163,14 @@ var MarkStoryReadyInputSchema = external_exports.object({ }); async function markStoryReady(rawInput) { const input = MarkStoryReadyInputSchema.parse(rawInput); - const targetRepoRoot = path60.resolve(input.targetRepoRoot); + const targetRepoRoot = path61.resolve(input.targetRepoRoot); const { ref, ready } = input; const sessionUlid = input.sessionUlid ?? "operator"; - const stateRoot = path60.join(targetRepoRoot, ".flow", "state"); + const stateRoot = path61.join(targetRepoRoot, ".flow", "state"); let foundState = null; let foundAbsPath = null; for (const stateName of STATE_NAMES) { - const candidate = path60.join(stateRoot, stateName, `${ref}.yaml`); + const candidate = path61.join(stateRoot, stateName, `${ref}.yaml`); try { await fs47.stat(candidate); foundState = stateName; @@ -54072,7 +54186,7 @@ async function markStoryReady(rawInput) { throw new NotAnEligibleBacklogItemError({ ref, foundState, reason: "not-in-to-do" }); } const rawText = await fs47.readFile(foundAbsPath, "utf8"); - const parsed = (0, import_yaml29.parse)(rawText); + const parsed = (0, import_yaml30.parse)(rawText); const manifest = parseExecutionManifest(parsed, { absPath: foundAbsPath }); if (manifest.withdrawn === true) { throw new NotAnEligibleBacklogItemError({ ref, foundState, reason: "withdrawn" }); @@ -54091,7 +54205,7 @@ async function markStoryReady(rawInput) { for (const stateName of STATE_NAMES) { if (stateName === "to-do") continue; try { - await fs47.stat(path60.join(stateRoot, stateName, `${ref}.yaml`)); + await fs47.stat(path61.join(stateRoot, stateName, `${ref}.yaml`)); } catch { continue; } @@ -54113,16 +54227,16 @@ async function markStoryReady(rawInput) { // src/tools/refresh-persona.ts import { promises as fs48 } from "node:fs"; -import * as path61 from "node:path"; +import * as path62 from "node:path"; async function refreshPersona(opts) { const pluginVersion = opts.pluginVersion ?? getPluginVersion(); - const customPath = path61.join( + const customPath = path62.join( opts.targetRepoRoot, "team", "custom", `${opts.role}.md` ); - const cataloguePath = path61.join( + const cataloguePath = path62.join( opts.pluginRoot, "catalogue", `${opts.role}.md` @@ -54154,7 +54268,7 @@ async function refreshPersona(opts) { throw err; } } - const personaPath = path61.join( + const personaPath = path62.join( opts.targetRepoRoot, "team", opts.role, @@ -54228,7 +54342,7 @@ function isEnoent15(err) { // src/tools/read-repo-signals.ts import { promises as fs49 } from "node:fs"; -import * as path62 from "node:path"; +import * as path63 from "node:path"; // src/lib/repo-signal-detectors.ts var LANG_FILE_MAP = { @@ -54312,7 +54426,7 @@ async function readRepoSignals(opts) { } } const dependencyManifests = detectDependencyManifests(topLevelLayout); - const readmePath = path62.join(opts.targetRepoRoot, "README.md"); + const readmePath = path63.join(opts.targetRepoRoot, "README.md"); let readmeExcerpt = ""; try { const raw = await fs49.readFile(readmePath, "utf8"); @@ -54342,7 +54456,7 @@ async function collectDepthOneManifests(repoRoot, rootDirents) { if (!dirent.isDirectory()) continue; if (dirent.name.startsWith(".") || dirent.name === "node_modules") continue; try { - const children = await fs49.readdir(path62.join(repoRoot, dirent.name)); + const children = await fs49.readdir(path63.join(repoRoot, dirent.name)); for (const child of children) { found.push(child); } @@ -54357,7 +54471,7 @@ function isEnoent16(err) { // src/tools/validate-planner-backlog.ts import { createHash as createHash4 } from "node:crypto"; -import * as path63 from "node:path"; +import * as path64 from "node:path"; var PendingStoryInputSchema = external_exports.object({ title: external_exports.string().min(1), narrative: external_exports.string().min(1), @@ -54449,7 +54563,7 @@ function pendingToEnrichedSourceStory(pending, index) { } async function validatePlannerBacklog(rawInput) { const input = ValidatePlannerBacklogInputSchema.parse(rawInput); - const targetRepoRoot = path63.resolve(input.targetRepoRoot); + const targetRepoRoot = path64.resolve(input.targetRepoRoot); const workspace = await resolveWorkspace({ targetRepoRoot }); if (workspace.activeAdapterName !== "native") { throw new WrongAdapterError({ @@ -54531,12 +54645,12 @@ async function validatePlannerBacklog(rawInput) { } // src/tools/claim-next-story.ts -import * as path67 from "node:path"; +import * as path68 from "node:path"; // src/lib/dep-merge-check.ts import { promises as fs50 } from "node:fs"; -import * as path64 from "node:path"; -var import_yaml30 = __toESM(require_dist2(), 1); +import * as path65 from "node:path"; +var import_yaml31 = __toESM(require_dist2(), 1); // src/lib/pr-body.ts function buildBranchSlug(opts) { @@ -54722,7 +54836,7 @@ async function isDependencyPrMerged(opts) { } async function areDependenciesMerged(opts) { const isMerged = opts.isMerged ?? isDependencyPrMerged; - const doneDir = path64.join(opts.targetRepoRoot, ".flow", "state", "done"); + const doneDir = path65.join(opts.targetRepoRoot, ".flow", "state", "done"); const seen = /* @__PURE__ */ new Map(); for (const dep of opts.deps) { const cached2 = seen.get(dep); @@ -54730,7 +54844,7 @@ async function areDependenciesMerged(opts) { if (!cached2) return false; continue; } - const depPath = path64.join(doneDir, `${dep}.yaml`); + const depPath = path65.join(doneDir, `${dep}.yaml`); let raw; try { raw = await fs50.readFile(depPath, "utf8"); @@ -54741,7 +54855,7 @@ async function areDependenciesMerged(opts) { let title; let prNumber; try { - const manifest = parseExecutionManifest((0, import_yaml30.parse)(raw), { absPath: depPath }); + const manifest = parseExecutionManifest((0, import_yaml31.parse)(raw), { absPath: depPath }); title = manifest.title; prNumber = manifest.pr_number; } catch { @@ -54773,7 +54887,7 @@ async function isOverlapBlockerInFlight(opts) { } async function anyOverlapBlockerInFlight(opts) { const isInFlight = opts.isInFlight ?? isOverlapBlockerInFlight; - const doneDir = path64.join(opts.targetRepoRoot, ".flow", "state", "done"); + const doneDir = path65.join(opts.targetRepoRoot, ".flow", "state", "done"); const seen = /* @__PURE__ */ new Map(); for (const ref of opts.blockers) { const cached2 = seen.get(ref); @@ -54781,7 +54895,7 @@ async function anyOverlapBlockerInFlight(opts) { if (cached2) return true; continue; } - const depPath = path64.join(doneDir, `${ref}.yaml`); + const depPath = path65.join(doneDir, `${ref}.yaml`); let raw; try { raw = await fs50.readFile(depPath, "utf8"); @@ -54791,7 +54905,7 @@ async function anyOverlapBlockerInFlight(opts) { } let prNumber; try { - const manifest = parseExecutionManifest((0, import_yaml30.parse)(raw), { + const manifest = parseExecutionManifest((0, import_yaml31.parse)(raw), { absPath: depPath }); prNumber = manifest.pr_number; @@ -54814,15 +54928,15 @@ async function anyOverlapBlockerInFlight(opts) { } // src/lib/cited-source-overlap.ts -var import_yaml31 = __toESM(require_dist2(), 1); +var import_yaml32 = __toESM(require_dist2(), 1); import { promises as fs51 } from "node:fs"; -import * as path65 from "node:path"; +import * as path66 from "node:path"; var STATE_DIRS2 = ["to-do", "in-progress", "done"]; async function loadOverlapUniverse(targetRepoRoot) { - const stateRoot = path65.join(targetRepoRoot, ".flow", "state"); + const stateRoot = path66.join(targetRepoRoot, ".flow", "state"); const stories = []; for (const location of STATE_DIRS2) { - const dir = path65.join(stateRoot, location); + const dir = path66.join(stateRoot, location); let entries; try { entries = await fs51.readdir(dir); @@ -54834,13 +54948,13 @@ async function loadOverlapUniverse(targetRepoRoot) { if (!entry.endsWith(".yaml") || entry.endsWith(".snapshot.yaml")) continue; let raw; try { - raw = await fs51.readFile(path65.join(dir, entry), "utf8"); + raw = await fs51.readFile(path66.join(dir, entry), "utf8"); } catch { continue; } let doc; try { - doc = (0, import_yaml31.parse)(raw); + doc = (0, import_yaml32.parse)(raw); } catch { continue; } @@ -54888,11 +55002,11 @@ function isEnoent17(err) { } // src/lib/session-liveness.ts -import * as path66 from "node:path"; +import * as path67 from "node:path"; import { promises as fs52 } from "node:fs"; var HEARTBEAT_STALE_MS = 30 * 6e4; function heartbeatFilePath(targetRepoRoot, sessionUlid) { - return path66.join( + return path67.join( targetRepoRoot, ".flow", "state", @@ -55110,7 +55224,7 @@ async function claimNextStory(opts) { throw err; } if (claimSucceeded) { - const manifestPath = path67.resolve( + const manifestPath = path68.resolve( targetRepoRoot, ".flow", "state", @@ -55152,7 +55266,7 @@ async function claimNextStory(opts) { } // src/tools/process-dev-transcript.ts -import * as path69 from "node:path"; +import * as path70 from "node:path"; // src/skills/handoff-parser.ts var HANDOFF_PHRASE_TEMPLATE = "Handoff to reviewer \u2014 story ready for review."; @@ -55177,9 +55291,9 @@ function parseHandoff(transcript, expectedRef) { // src/lib/read-dev-outcome-file.ts import { promises as fs53 } from "node:fs"; -import * as path68 from "node:path"; +import * as path69 from "node:path"; function devOutcomeFilePath(targetRepoRoot, sessionUlid, ref) { - return path68.join( + return path69.join( targetRepoRoot, ".flow", "state", @@ -55259,7 +55373,7 @@ async function processDevTranscript(opts) { await writeSessionHeartbeat(targetRepoRoot, sessionUlid); } catch { } - const manifestPath = path69.resolve( + const manifestPath = path70.resolve( targetRepoRoot, ".flow", "state", @@ -55434,7 +55548,7 @@ async function processReviewerTranscript(opts) { } // src/tools/run-dev-terminal-action.ts -import * as path73 from "node:path"; +import * as path74 from "node:path"; // src/lib/extract-acs-from-spec.ts import { promises as fs54 } from "node:fs"; @@ -55467,9 +55581,9 @@ async function extractAcsFromSpec(specPath) { } // src/lib/gh-error-map.ts -var import_yaml32 = __toESM(require_dist2(), 1); +var import_yaml33 = __toESM(require_dist2(), 1); import { promises as fs55 } from "node:fs"; -import * as path70 from "node:path"; +import * as path71 from "node:path"; // src/schemas/gh-error-map.ts var GhErrorMapEntrySchema = external_exports.object({ @@ -55485,7 +55599,7 @@ var GhErrorMapSchema = external_exports.object({ var cache = /* @__PURE__ */ new Map(); async function parseGhErrorMap(filePath) { const raw = await fs55.readFile(filePath, "utf8"); - const parsed = (0, import_yaml32.parse)(raw); + const parsed = (0, import_yaml33.parse)(raw); const result = GhErrorMapSchema.safeParse(parsed); if (!result.success) { const firstIssue = result.error.issues[0]; @@ -55528,7 +55642,7 @@ async function parseGhErrorMap(filePath) { return { entries }; } async function loadGhErrorMap(pluginRoot) { - const absPath = path70.resolve(pluginRoot, "permissions", "gh-error-map.yaml"); + const absPath = path71.resolve(pluginRoot, "permissions", "gh-error-map.yaml"); const cached2 = cache.get(absPath); if (cached2 !== void 0) { return cached2; @@ -55610,9 +55724,9 @@ async function gh(opts) { } // src/state/load-role-permissions.ts -var import_yaml33 = __toESM(require_dist2(), 1); +var import_yaml34 = __toESM(require_dist2(), 1); import { promises as fs56 } from "node:fs"; -import * as path71 from "node:path"; +import * as path72 from "node:path"; // src/schemas/role-permissions.ts var RolePermissionsSchema = external_exports.object({ @@ -55630,7 +55744,7 @@ function formatZodIssues5(issues) { return `${dottedPath}: ${first.message}`; } async function loadRolePermissions(opts) { - const specPath = path71.join(opts.pluginRoot, "permissions", `${opts.role}.yaml`); + const specPath = path72.join(opts.pluginRoot, "permissions", `${opts.role}.yaml`); let raw; try { raw = await fs56.readFile(specPath, "utf8"); @@ -55642,7 +55756,7 @@ async function loadRolePermissions(opts) { } let parsedYaml; try { - parsedYaml = (0, import_yaml33.parse)(raw); + parsedYaml = (0, import_yaml34.parse)(raw); } catch (err) { throw new RolePermissionsMalformedError({ specPath, @@ -55660,12 +55774,12 @@ async function loadRolePermissions(opts) { } // src/lib/run-project-build.ts -import * as path72 from "node:path"; +import * as path73 from "node:path"; var DEFAULT_BUILD_TEST_TIMEOUT_MS = 20 * 60 * 1e3; var PROJECT_BUILD_COMMAND = "pnpm"; var PROJECT_BUILD_ARGS = ["build"]; function deriveProjectBuildCwd(devWorkingDir) { - return path72.join(devWorkingDir, "plugins", "flow"); + return path73.join(devWorkingDir, "plugins", "flow"); } async function runProjectBuild(opts) { const execaImpl = opts.execaImpl ?? execa; @@ -55752,7 +55866,7 @@ async function runDevTerminalAction(opts) { } const branch = buildBranchSlug({ ref, title }); const manifest = await readManifest(manifestPath); - const specPath = path73.isAbsolute(manifest.source_path) ? manifest.source_path : path73.join(targetRepoRoot, manifest.source_path); + const specPath = path74.isAbsolute(manifest.source_path) ? manifest.source_path : path74.join(targetRepoRoot, manifest.source_path); const acs = opts.inlineAcs ? opts.inlineAcs : await extractAcsFromSpec(specPath); const gitRoot = targetRepoRoot; let committedPaths = ["."]; @@ -55938,7 +56052,7 @@ async function runDevTerminalAction(opts) { role: ROLE, ...execaImpl ? { execaImpl } : {} }); - const specPathForPr = path73.isAbsolute(manifest.source_path) ? path73.relative(targetRepoRoot, manifest.source_path) : manifest.source_path; + const specPathForPr = path74.isAbsolute(manifest.source_path) ? path74.relative(targetRepoRoot, manifest.source_path) : manifest.source_path; const manifestAcsByIndex = new Map( (manifest.acceptance_criteria ?? []).map((ac, i2) => [i2 + 1, ac]) ); @@ -56013,7 +56127,7 @@ async function runDevTerminalAction(opts) { JSON.stringify({ prUrl, prNumber, branch, commitSha: commitResult.commitSha }, null, 2) ); try { - const inProgressManifestPath = path73.join( + const inProgressManifestPath = path74.join( ledgerRoot, ".flow", "state", @@ -56035,24 +56149,22 @@ async function runDevTerminalAction(opts) { } // src/tools/run-reviewer-session.ts -import * as path75 from "node:path"; +import * as path76 from "node:path"; import * as fs58 from "node:fs/promises"; -import { accessSync, readFileSync as readFileSync4, readdirSync as readdirSync2 } from "node:fs"; -var import_yaml34 = __toESM(require_dist2(), 1); // src/lib/materialise-pr-branch-worktree.ts -import * as path74 from "node:path"; +import * as path75 from "node:path"; import * as fs57 from "node:fs/promises"; function reviewWorktreesRoot(targetRepoRoot, sessionUlid) { - return path74.join( - path74.dirname(targetRepoRoot), + return path75.join( + path75.dirname(targetRepoRoot), ".flow-worktrees", sessionUlid ); } function reviewWorktreePath(targetRepoRoot, sessionUlid, storyRef) { const storySlug = sanitiseRefForPathSegment(storyRef); - return path74.join( + return path75.join( reviewWorktreesRoot(targetRepoRoot, sessionUlid), `review-${storySlug}-worktree` ); @@ -56115,7 +56227,7 @@ async function materialisePrBranchWorktree(opts) { ); } const worktreePath = reviewWorktreePath(targetRepoRoot, sessionUlid, storyRef); - await fs57.mkdir(path74.dirname(worktreePath), { recursive: true }); + await fs57.mkdir(path75.dirname(worktreePath), { recursive: true }); let staleExists = false; try { await fs57.access(worktreePath); @@ -56228,7 +56340,7 @@ function classifyAc(bodyLines) { return { applicability: "manual-check-required" }; } async function runArtifactCheck(index, tag, artifactPath, checkRoot) { - const resolved = path75.resolve(checkRoot, artifactPath); + const resolved = path76.resolve(checkRoot, artifactPath); try { await fs58.access(resolved); return { @@ -56261,97 +56373,6 @@ function capString(s) { if (s.length <= STDOUT_STDERR_CAP) return s; return s.slice(0, STDOUT_STDERR_CAP) + TRUNCATION_MARKER; } -function hasLocalVitest(dir) { - try { - accessSync(path75.join(dir, "node_modules", ".bin", "vitest")); - return true; - } catch { - return false; - } -} -function findVitestInWorkspaceMembers(workspaceRoot) { - try { - const yaml = readFileSync4( - path75.join(workspaceRoot, "pnpm-workspace.yaml"), - "utf8" - ); - const parsed = (0, import_yaml34.parse)(yaml); - const packages = parsed?.packages; - if (!Array.isArray(packages)) return { ok: false }; - for (const pattern of packages) { - if (typeof pattern !== "string") continue; - const segments = pattern.split("/"); - const hasGlob = segments.some((s) => s === "*" || s === "**"); - if (!hasGlob) { - const memberDir = path75.join(workspaceRoot, pattern); - if (hasLocalVitest(memberDir)) { - return { ok: true, packageRoot: memberDir }; - } - } else { - const parentSegments = segments.slice(0, segments.indexOf("*")); - const parentDir = path75.join(workspaceRoot, ...parentSegments); - try { - const entries = readdirSync2(parentDir, { withFileTypes: true }); - for (const entry of entries) { - if (!entry.isDirectory()) continue; - const memberDir = path75.join(parentDir, entry.name); - if (hasLocalVitest(memberDir)) { - return { ok: true, packageRoot: memberDir }; - } - } - } catch { - } - } - } - } catch { - } - return { ok: false }; -} -function findWorkspaceYamlInSubtree(root, maxDepth) { - if (maxDepth < 0) return null; - let entries; - try { - entries = readdirSync2(root, { withFileTypes: true }); - } catch { - return null; - } - if (entries.some((e) => e.isFile() && e.name === "pnpm-workspace.yaml")) { - return root; - } - for (const entry of entries) { - if (!entry.isDirectory()) continue; - const name = entry.name; - if (name === "node_modules" || name === ".git") continue; - const found = findWorkspaceYamlInSubtree(path75.join(root, name), maxDepth - 1); - if (found !== null) return found; - } - return null; -} -function findPackageRoot(opts) { - const checkRootAbs = path75.resolve(opts.checkRoot); - let dir = path75.dirname(opts.testFilePathAbs); - const isWithinCheckRoot = (d) => d === checkRootAbs || d.startsWith(checkRootAbs + path75.sep); - while (isWithinCheckRoot(dir)) { - try { - accessSync(path75.join(dir, "package.json")); - const memberResult = findVitestInWorkspaceMembers(dir); - if (memberResult.ok) { - return memberResult; - } - return { ok: true, packageRoot: dir }; - } catch { - } - const parent = path75.dirname(dir); - if (parent === dir) break; - dir = parent; - } - const workspaceYamlDir = findWorkspaceYamlInSubtree(checkRootAbs, 4); - if (workspaceYamlDir !== null) { - const memberResult = findVitestInWorkspaceMembers(workspaceYamlDir); - if (memberResult.ok) return memberResult; - } - return { ok: false }; -} function stripAnsi(s) { return s.replace(/\[[0-9;]*m/g, ""); } @@ -56366,7 +56387,7 @@ function countExecutedTests(output) { return (passed ? Number(passed[1]) : 0) + (failed ? Number(failed[1]) : 0); } async function runVitestCheck(index, tag, testNameFilter, testFilePath, checkRoot, execaImpl) { - const testFilePathAbs = path75.resolve(checkRoot, testFilePath); + const testFilePathAbs = path76.resolve(checkRoot, testFilePath); const pkgRoot = findPackageRoot({ testFilePathAbs, checkRoot }); if (!pkgRoot.ok) { return { @@ -56381,8 +56402,8 @@ async function runVitestCheck(index, tag, testNameFilter, testFilePath, checkRoo exitCode: -1 }; } - const testFilePathAbs2 = path75.resolve(checkRoot, testFilePath); - const relativeToPackage = path75.relative(pkgRoot.packageRoot, testFilePathAbs2); + const testFilePathAbs2 = path76.resolve(checkRoot, testFilePath); + const relativeToPackage = path76.relative(pkgRoot.packageRoot, testFilePathAbs2); const looksLikeFilePath = (testFilePath.includes("/") || testFilePath.includes("\\")) && !relativeToPackage.startsWith("..") && relativeToPackage !== testFilePath; const vitestArgs = looksLikeFilePath ? ["vitest", "--run", relativeToPackage] : ["vitest", "--run", "-t", testNameFilter]; const result = await execaImpl("pnpm", vitestArgs, { @@ -56514,7 +56535,7 @@ async function runReviewerSession(opts) { } catch (err) { if (err instanceof StandardsDocMissingError) { const setupResultFilePath = reviewerResultFilePath(targetRepoRoot, sessionUlid, ref); - await fs58.mkdir(path75.dirname(setupResultFilePath), { recursive: true }); + await fs58.mkdir(path76.dirname(setupResultFilePath), { recursive: true }); const setupFileProjection = { sessionUlid, ref, @@ -56659,7 +56680,7 @@ async function runReviewerSession(opts) { }); } const resultFilePath = reviewerResultFilePath(targetRepoRoot, sessionUlid, ref); - await fs58.mkdir(path75.dirname(resultFilePath), { recursive: true }); + await fs58.mkdir(path76.dirname(resultFilePath), { recursive: true }); const fileProjection = { sessionUlid, ref, @@ -56687,7 +56708,7 @@ async function runReviewerSession(opts) { } // src/tools/post-reviewer-comments.ts -import * as path76 from "node:path"; +import * as path77 from "node:path"; // src/lib/compose-reviewer-summary.ts function composeVerdictLine(result) { @@ -56825,7 +56846,7 @@ async function stampRiskTierOnManifest(targetRepoRoot, ref, riskTier) { if (riskTier === void 0) { return; } - const manifestPath = path76.join( + const manifestPath = path77.join( targetRepoRoot, ".flow", "state", @@ -57195,7 +57216,7 @@ async function processReviewerYield(opts) { } // src/tools/compute-agreement.ts -import * as path77 from "node:path"; +import * as path78 from "node:path"; import { promises as fs59 } from "node:fs"; // src/lib/agreement.ts @@ -57232,7 +57253,7 @@ async function computeAgreement(opts) { reason: "must be a positive integer" }); } - const telemetryDir = path77.join(targetRepoRoot, ".flow", "telemetry"); + const telemetryDir = path78.join(targetRepoRoot, ".flow", "telemetry"); let jsonlFiles; try { if (readTelemetryDirImpl) { @@ -57254,7 +57275,7 @@ async function computeAgreement(opts) { const mergeActions = []; let malformed_lines = 0; for (const filename of jsonlFiles) { - const filePath = path77.join(telemetryDir, filename); + const filePath = path78.join(telemetryDir, filename); const raw = readFileImpl ? await readFileImpl(filePath) : await fs59.readFile(filePath, "utf8"); for (const rawLine of raw.split("\n")) { const line = rawLine.trim(); @@ -57372,7 +57393,7 @@ async function recordSkillInvoke(opts) { } // src/tools/run-auto-merge-gate.ts -import * as path78 from "node:path"; +import * as path79 from "node:path"; import { promises as fs60 } from "node:fs"; var import_yaml35 = __toESM(require_dist2(), 1); @@ -57427,7 +57448,7 @@ var AutoMergeGateResultSchema = external_exports.object({ chatLog: external_exports.array(external_exports.string()) }).strict(); async function loadWorkspaceConfig(targetRepoRoot) { - const configPath = path78.join(targetRepoRoot, ".flow", "config.yaml"); + const configPath = path79.join(targetRepoRoot, ".flow", "config.yaml"); let raw; try { raw = await fs60.readFile(configPath, "utf8"); @@ -57492,7 +57513,7 @@ async function waitForCiGreen(opts) { return { kind: "ci-status-unreadable", reason }; } if (Date.now() - start >= CI_GATE_TIMEOUT_MS) return "pending-timeout"; - await new Promise((resolve21) => setTimeout(resolve21, CI_GATE_POLL_INTERVAL_MS)); + await new Promise((resolve22) => setTimeout(resolve22, CI_GATE_POLL_INTERVAL_MS)); continue; } let rollup = []; @@ -57509,7 +57530,7 @@ async function waitForCiGreen(opts) { if (state === "green") return "green"; if (state === "failed") return "failed"; if (Date.now() - start >= CI_GATE_TIMEOUT_MS) return "pending-timeout"; - await new Promise((resolve21) => setTimeout(resolve21, CI_GATE_POLL_INTERVAL_MS)); + await new Promise((resolve22) => setTimeout(resolve22, CI_GATE_POLL_INTERVAL_MS)); } } async function runAutoMergeGate(opts) { @@ -57552,7 +57573,7 @@ async function runAutoMergeGate(opts) { pluginSettings = pluginSettings ?? await loadWorkspaceConfigFn(opts.targetRepoRoot); provisional_trust = pluginSettings.provisional_trust; } - const manifestPath = path78.join( + const manifestPath = path79.join( opts.targetRepoRoot, ".flow", "state", @@ -57725,7 +57746,7 @@ async function runAutoMergeGate(opts) { // src/tools/create-smoke-scratch-repo.ts import { promises as fs61 } from "node:fs"; import * as os from "node:os"; -import * as path79 from "node:path"; +import * as path80 from "node:path"; var CreateSmokeScratchRepoOptionsSchema = external_exports.object({ /** Short kebab-case label embedded in the scratch directory name. */ label: external_exports.string().regex(/^[a-z0-9-]+$/, "label must be kebab-case (lowercase letters, digits, hyphens)").min(1), @@ -57735,23 +57756,23 @@ var CreateSmokeScratchRepoOptionsSchema = external_exports.object({ async function createSmokeScratchRepo(opts) { const parsed = CreateSmokeScratchRepoOptionsSchema.parse(opts); const { label, parentDir } = parsed; - const standardsTemplatePath = path79.resolve( + const standardsTemplatePath = path80.resolve( getPluginRoot(), "docs", "standards-example.md" ); const scratchRoot = await fs61.mkdtemp( - path79.join(parentDir ?? os.tmpdir(), `flow-smoke-${label}-`) + path80.join(parentDir ?? os.tmpdir(), `flow-smoke-${label}-`) ); await gitInitWithEmptyCommit({ cwd: scratchRoot }); await writeManagedFile({ - absPath: path79.join(scratchRoot, ".flow", "config.yaml"), + absPath: path80.join(scratchRoot, ".flow", "config.yaml"), contents: "adapter: native\nstandards: {}\n", targetRepoRoot: scratchRoot }); const standardsContents = await fs61.readFile(standardsTemplatePath, "utf8"); await writeManagedFile({ - absPath: path79.join(scratchRoot, ".flow", "standards.md"), + absPath: path80.join(scratchRoot, ".flow", "standards.md"), contents: standardsContents, targetRepoRoot: scratchRoot }); @@ -57764,13 +57785,13 @@ async function createSmokeScratchRepo(opts) { // src/tools/scan-orphaned-in-progress.ts var import_yaml36 = __toESM(require_dist2(), 1); import { promises as fs62 } from "node:fs"; -import * as path80 from "node:path"; +import * as path81 from "node:path"; async function scanOrphanedInProgress(opts) { const { targetRepoRoot, sessionUlid } = opts; const execaImpl = opts.execaImpl ?? execa; const aliveCheck = opts.isSessionAliveImpl ?? isSessionAlive; - const inProgressDir = path80.join(targetRepoRoot, ".flow", "state", "in-progress"); - const sessionsDir = path80.join(targetRepoRoot, ".flow", "state", "sessions"); + const inProgressDir = path81.join(targetRepoRoot, ".flow", "state", "in-progress"); + const sessionsDir = path81.join(targetRepoRoot, ".flow", "state", "sessions"); let entries; try { entries = await fs62.readdir(inProgressDir); @@ -57783,7 +57804,7 @@ async function scanOrphanedInProgress(opts) { const yamlEntries = entries.filter((f) => f.endsWith(".yaml") && !f.endsWith(".snapshot.yaml")).sort(); const orphans = []; for (const entry of yamlEntries) { - const absPath = path80.join(inProgressDir, entry); + const absPath = path81.join(inProgressDir, entry); let raw; try { raw = await fs62.readFile(absPath, "utf8"); @@ -57809,7 +57830,7 @@ async function scanOrphanedInProgress(opts) { continue; } const staleUlid = manifest.claimed_by; - const transcriptPath = path80.join( + const transcriptPath = path81.join( sessionsDir, staleUlid, "dev-transcript.txt" @@ -57874,10 +57895,10 @@ function isEnoent18(err) { } // src/tools/reattach-orphan.ts -import * as path81 from "node:path"; +import * as path82 from "node:path"; async function reattachOrphan(opts) { const { targetRepoRoot, ref, currentSessionUlid } = opts; - const absPath = path81.join( + const absPath = path82.join( targetRepoRoot, ".flow", "state", @@ -57916,7 +57937,7 @@ async function reattachOrphan(opts) { } // src/tools/block-orphan-no-transcript.ts -import * as path82 from "node:path"; +import * as path83 from "node:path"; async function blockOrphanNoTranscript(opts) { const { targetRepoRoot, ref, staleUlid } = opts; await removeInProgressSnapshot({ targetRepoRoot, ref }); @@ -57926,7 +57947,7 @@ async function blockOrphanNoTranscript(opts) { from: "in-progress", to: "blocked" }); - const absBlockedPath = path82.join( + const absBlockedPath = path83.join( targetRepoRoot, ".flow", "state", @@ -57949,7 +57970,7 @@ async function blockOrphanNoTranscript(opts) { // src/tools/quality-lead-adjudicate.ts import { promises as fs63 } from "node:fs"; -import * as path83 from "node:path"; +import * as path84 from "node:path"; // src/schemas/adjudication-verdict.ts var ADJUDICATION_DECISIONS = ["ready", "escalate", "rework"]; @@ -58002,7 +58023,7 @@ function synthesiseDecision(input) { }; } function adjudicationVerdictFilePath(targetRepoRoot, sessionUlid, ref) { - return path83.join( + return path84.join( targetRepoRoot, ".flow", "state", @@ -58013,7 +58034,7 @@ function adjudicationVerdictFilePath(targetRepoRoot, sessionUlid, ref) { ); } async function adjudicateQualityLead(opts) { - const targetRepoRoot = path83.resolve(opts.targetRepoRoot); + const targetRepoRoot = path84.resolve(opts.targetRepoRoot); const { sessionUlid, ref } = opts; const round = opts.round ?? 1; const k = opts.k ?? DEFAULT_ADJUDICATION_K; @@ -58048,7 +58069,7 @@ async function adjudicateQualityLead(opts) { round }); const verdictFilePath = adjudicationVerdictFilePath(targetRepoRoot, sessionUlid, ref); - await fs63.mkdir(path83.dirname(verdictFilePath), { recursive: true }); + await fs63.mkdir(path84.dirname(verdictFilePath), { recursive: true }); await atomicWriteFile(verdictFilePath, JSON.stringify(verdict, null, 2)); await logTelemetryEvent({ targetRepoRoot, @@ -58074,8 +58095,8 @@ async function adjudicateQualityLead(opts) { // src/tools/review-maintainer-inbox.ts import { promises as fs64 } from "node:fs"; -import * as path84 from "node:path"; -var INBOX_SUBDIR2 = path84.join(".flow", "maintainer-inbox"); +import * as path85 from "node:path"; +var INBOX_SUBDIR2 = path85.join(".flow", "maintainer-inbox"); var MAX_TITLE_LENGTH = 120; var MAX_URL_BYTES2 = 8192; var SHORTENED_NOTE2 = "\n\n_(body shortened \u2014 see full detail in the maintainer inbox)_"; @@ -58130,7 +58151,7 @@ function buildStoredItemIssueUrl(owner, repo, item) { } async function reviewMaintainerInbox(opts) { const { targetRepoRoot } = opts; - const inboxDir = path84.join(targetRepoRoot, INBOX_SUBDIR2); + const inboxDir = path85.join(targetRepoRoot, INBOX_SUBDIR2); let filenames; try { const entries = await fs64.readdir(inboxDir); @@ -58149,7 +58170,7 @@ async function reviewMaintainerInbox(opts) { const items = []; let malformedCount = 0; for (const filename of filenames) { - const absPath = path84.join(inboxDir, filename); + const absPath = path85.join(inboxDir, filename); let raw; try { raw = await fs64.readFile(absPath, "utf8"); @@ -58203,8 +58224,8 @@ async function reviewMaintainerInbox(opts) { // src/tools/dismiss-maintainer-feedback.ts import { promises as fs65 } from "node:fs"; -import * as path85 from "node:path"; -var INBOX_SUBDIR3 = path85.join(".flow", "maintainer-inbox"); +import * as path86 from "node:path"; +var INBOX_SUBDIR3 = path86.join(".flow", "maintainer-inbox"); var DISMISSED_SUBDIR = "dismissed"; var ULID_PATTERN2 = /^[0-9A-HJKMNP-TV-Z]{26}$/; async function dismissMaintainerFeedback(opts) { @@ -58212,7 +58233,7 @@ async function dismissMaintainerFeedback(opts) { if (typeof id !== "string" || !ULID_PATTERN2.test(id)) { throw new InvalidMaintainerFeedbackIdError({ id: String(id) }); } - const inboxDir = path85.join(targetRepoRoot, INBOX_SUBDIR3); + const inboxDir = path86.join(targetRepoRoot, INBOX_SUBDIR3); let filenames; try { filenames = await fs65.readdir(inboxDir); @@ -58230,8 +58251,8 @@ async function dismissMaintainerFeedback(opts) { if (match === void 0) { return { ok: true, dismissed: false, id, noop: true }; } - const sourcePath = path85.join(inboxDir, match); - const archivedPath = path85.join(inboxDir, DISMISSED_SUBDIR, match); + const sourcePath = path86.join(inboxDir, match); + const archivedPath = path86.join(inboxDir, DISMISSED_SUBDIR, match); let contents; try { contents = await fs65.readFile(sourcePath, "utf8"); @@ -58250,10 +58271,10 @@ async function dismissMaintainerFeedback(opts) { // src/tools/resolve-lens-roles.ts var import_yaml37 = __toESM(require_dist2(), 1); import { promises as fs66 } from "node:fs"; -import * as path86 from "node:path"; +import * as path87 from "node:path"; async function resolveLensRoles(opts) { const { targetRepoRoot } = opts; - const teamDir = path86.join(targetRepoRoot, "team"); + const teamDir = path87.join(targetRepoRoot, "team"); let dirEntries; try { dirEntries = await fs66.readdir(teamDir); @@ -58272,7 +58293,7 @@ async function resolveLensRoles(opts) { } let stat2; try { - stat2 = await fs66.stat(path86.join(teamDir, entry)); + stat2 = await fs66.stat(path87.join(teamDir, entry)); } catch { continue; } @@ -58280,7 +58301,7 @@ async function resolveLensRoles(opts) { continue; } try { - await fs66.access(path86.join(teamDir, entry, "PERSONA.md")); + await fs66.access(path87.join(teamDir, entry, "PERSONA.md")); } catch { continue; } @@ -58294,7 +58315,7 @@ async function resolveLensRoles(opts) { return { lensRoles, hiredRoles }; } async function readRoleCapabilities(teamDir, roleId) { - const personaPath = path86.join(teamDir, roleId, "PERSONA.md"); + const personaPath = path87.join(teamDir, roleId, "PERSONA.md"); let raw; try { raw = await fs66.readFile(personaPath, "utf8"); @@ -58337,14 +58358,14 @@ function isEnoent19(err) { // src/tools/resolve-run-slot.ts var import_yaml38 = __toESM(require_dist2(), 1); import { promises as fs67 } from "node:fs"; -import * as path87 from "node:path"; +import * as path88 from "node:path"; var RUN_JOB_GENERALISTS = { build: "generalist-dev", review: "generalist-reviewer" }; async function resolveRunSlot(opts) { const { targetRepoRoot, job } = opts; - const teamDir = path87.join(targetRepoRoot, "team"); + const teamDir = path88.join(targetRepoRoot, "team"); const defaultRole = RUN_JOB_GENERALISTS[job]; let dirEntries; try { @@ -58364,7 +58385,7 @@ async function resolveRunSlot(opts) { } let stat2; try { - stat2 = await fs67.stat(path87.join(teamDir, entry)); + stat2 = await fs67.stat(path88.join(teamDir, entry)); } catch { continue; } @@ -58372,7 +58393,7 @@ async function resolveRunSlot(opts) { continue; } try { - await fs67.access(path87.join(teamDir, entry, "PERSONA.md")); + await fs67.access(path88.join(teamDir, entry, "PERSONA.md")); } catch { continue; } @@ -58392,7 +58413,7 @@ async function resolveRunSlot(opts) { return { role: qualifiedRoles[0], isDefault: false }; } async function readRunJobs(teamDir, roleId) { - const personaPath = path87.join(teamDir, roleId, "PERSONA.md"); + const personaPath = path88.join(teamDir, roleId, "PERSONA.md"); let raw; try { raw = await fs67.readFile(personaPath, "utf8"); @@ -58430,7 +58451,7 @@ function isEnoent20(err) { } // src/tools/recall-lesson.ts -import * as path88 from "node:path"; +import * as path89 from "node:path"; import { promises as fs68 } from "node:fs"; var import_yaml39 = __toESM(require_dist2(), 1); var _LESSON_BLOCK_PREFIX = LESSON_BLOCK_PREFIX; @@ -58450,7 +58471,7 @@ async function recallLesson(opts) { }; try { const updatedBody = updateLessonInBody(knowledgeBody, updatedLesson); - const personaPath = path88.join(targetRepoRoot, "team", role, "PERSONA.md"); + const personaPath = path89.join(targetRepoRoot, "team", role, "PERSONA.md"); const rawPersona = await fs68.readFile(personaPath, "utf8"); const parsed = parsePersonaFile(rawPersona, personaPath); const newContents = reconstructPersonaFile4(parsed, updatedBody); @@ -58485,7 +58506,7 @@ async function recallLesson(opts) { }; const relPath = `team/${role}/_archived/${id}.json`; await writeManagedFile({ - absPath: path88.join(targetRepoRoot, relPath), + absPath: path89.join(targetRepoRoot, relPath), contents: JSON.stringify(updated, null, 2) + "\n", targetRepoRoot, mcpToolContext: { toolName: TOOL_NAME9, role } @@ -58755,7 +58776,7 @@ async function summariseRetroProposal(opts) { // src/tools/discard-draft.ts import { promises as fs70 } from "node:fs"; -import * as path89 from "node:path"; +import * as path90 from "node:path"; var import_yaml42 = __toESM(require_dist2(), 1); var DiscardDraftInputSchema = external_exports.object({ targetRepoRoot: external_exports.string().min(1), @@ -58763,13 +58784,13 @@ var DiscardDraftInputSchema = external_exports.object({ }); async function discardDraft(rawInput) { const input = DiscardDraftInputSchema.parse(rawInput); - const targetRepoRoot = path89.resolve(input.targetRepoRoot); + const targetRepoRoot = path90.resolve(input.targetRepoRoot); const { ref } = input; - const stateRoot = path89.join(targetRepoRoot, ".flow", "state"); + const stateRoot = path90.join(targetRepoRoot, ".flow", "state"); let foundState = null; let foundAbsPath = null; for (const stateName of STATE_NAMES) { - const candidate = path89.join(stateRoot, stateName, `${ref}.yaml`); + const candidate = path90.join(stateRoot, stateName, `${ref}.yaml`); try { await fs70.stat(candidate); foundState = stateName; @@ -58803,8 +58824,8 @@ async function discardDraft(rawInput) { throw new NotAnEligibleDraftError({ ref, foundState, reason: "not-in-to-do" }); } const ulid4 = ref.startsWith("native:") ? ref.slice("native:".length) : ref; - const nativeStoriesDir2 = path89.join(targetRepoRoot, ".flow", "native-stories"); - const sourceDraftPath = path89.join(nativeStoriesDir2, `${ulid4}.md`); + const nativeStoriesDir2 = path90.join(targetRepoRoot, ".flow", "native-stories"); + const sourceDraftPath = path90.join(nativeStoriesDir2, `${ulid4}.md`); await fs70.unlink(foundAbsPath); try { await fs70.unlink(sourceDraftPath); @@ -58827,7 +58848,7 @@ function isEnoent21(err) { // src/tools/requeue-blocked-story.ts import { promises as fs71 } from "node:fs"; -import * as path90 from "node:path"; +import * as path91 from "node:path"; var import_yaml43 = __toESM(require_dist2(), 1); var RequeueBlockedStoryInputSchema = external_exports.object({ targetRepoRoot: external_exports.string().min(1), @@ -58840,13 +58861,13 @@ function stripUndefined6(obj) { } async function requeueBlockedStory(rawInput) { const input = RequeueBlockedStoryInputSchema.parse(rawInput); - const targetRepoRoot = path90.resolve(input.targetRepoRoot); + const targetRepoRoot = path91.resolve(input.targetRepoRoot); const { ref } = input; - const stateRoot = path90.join(targetRepoRoot, ".flow", "state"); + const stateRoot = path91.join(targetRepoRoot, ".flow", "state"); let foundState = null; let foundAbsPath = null; for (const stateName of STATE_NAMES) { - const candidate = path90.join(stateRoot, stateName, `${ref}.yaml`); + const candidate = path91.join(stateRoot, stateName, `${ref}.yaml`); try { await fs71.stat(candidate); foundState = stateName; @@ -58896,9 +58917,9 @@ async function requeueBlockedStory(rawInput) { // src/tools/help-advisor.ts var import_yaml44 = __toESM(require_dist2(), 1); import { promises as fs72 } from "node:fs"; -import * as path91 from "node:path"; +import * as path92 from "node:path"; async function hasHiredTeam(targetRepoRoot) { - const teamDir = path91.join(targetRepoRoot, "team"); + const teamDir = path92.join(targetRepoRoot, "team"); const SKIP_DIRS = /* @__PURE__ */ new Set(["custom", "_archived"]); let entries; try { @@ -58915,7 +58936,7 @@ async function hasHiredTeam(targetRepoRoot) { } let stat2; try { - stat2 = await fs72.stat(path91.join(teamDir, entry)); + stat2 = await fs72.stat(path92.join(teamDir, entry)); } catch { continue; } @@ -58926,10 +58947,10 @@ async function hasHiredTeam(targetRepoRoot) { return false; } async function readBacklogSummary(targetRepoRoot) { - const stateRoot = path91.join(targetRepoRoot, ".flow", "state"); - const todoDir = path91.join(stateRoot, "to-do"); - const inProgressDir = path91.join(stateRoot, "in-progress"); - const doneDir = path91.join(stateRoot, "done"); + const stateRoot = path92.join(targetRepoRoot, ".flow", "state"); + const todoDir = path92.join(stateRoot, "to-do"); + const inProgressDir = path92.join(stateRoot, "in-progress"); + const doneDir = path92.join(stateRoot, "done"); let todoEntries; try { todoEntries = await fs72.readdir(todoDir); @@ -58944,7 +58965,7 @@ async function readBacklogSummary(targetRepoRoot) { let readyAndClaimable = 0; let parkedDrafts = 0; for (const entry of yamlEntries) { - const absPath = path91.join(todoDir, entry); + const absPath = path92.join(todoDir, entry); let raw; try { raw = await fs72.readFile(absPath, "utf8"); @@ -58965,7 +58986,7 @@ async function readBacklogSummary(targetRepoRoot) { } let depsReady = true; for (const dep of manifest.depends_on) { - const depPath = path91.join(doneDir, `${dep}.yaml`); + const depPath = path92.join(doneDir, `${dep}.yaml`); try { await fs72.stat(depPath); } catch (err) { @@ -59088,7 +59109,7 @@ async function matchStorySpecialist(opts) { // src/tools/record-specialist-engagement.ts var import_yaml46 = __toESM(require_dist2(), 1); import { promises as fs74 } from "node:fs"; -import * as path92 from "node:path"; +import * as path93 from "node:path"; function stripUndefined7(obj) { return Object.fromEntries( Object.entries(obj).filter(([, v]) => v !== void 0) @@ -59096,7 +59117,7 @@ function stripUndefined7(obj) { } async function recordSpecialistEngagement(opts) { const { targetRepoRoot, ref, sessionUlid, specialistRole } = opts; - const absPath = path92.join( + const absPath = path93.join( targetRepoRoot, ".flow", "state", @@ -60796,7 +60817,7 @@ ${linkBlock}` // src/lib/lifecycle-log.ts import * as fs75 from "node:fs"; -import * as path93 from "node:path"; +import * as path94 from "node:path"; import * as os2 from "node:os"; import { execSync } from "node:child_process"; function resolvePgid() { @@ -60826,7 +60847,7 @@ function createLifecycleLog(opts) { let disabled = false; let stream = null; try { - fs75.mkdirSync(path93.dirname(logPath), { recursive: true }); + fs75.mkdirSync(path94.dirname(logPath), { recursive: true }); stream = fs75.createWriteStream(logPath, { flags: "a" }); stream.on("error", () => { disabled = true; @@ -60878,7 +60899,7 @@ function resolveLogPath(explicitPath) { if (explicitPath) return explicitPath; const envPath = process.env["CREW_MCP_LIFECYCLE_LOG"] ?? process.env["CREW_MCP_DIAG"]; if (envPath) return envPath; - return path93.join(os2.homedir(), ".flow", "mcp-lifecycle.log"); + return path94.join(os2.homedir(), ".flow", "mcp-lifecycle.log"); } // src/index.ts diff --git a/plugins/flow/mcp-server/src/__tests__/planning-friction-emission.test.ts b/plugins/flow/mcp-server/src/__tests__/planning-friction-emission.test.ts index fcfa3a95..ab32cd2f 100644 --- a/plugins/flow/mcp-server/src/__tests__/planning-friction-emission.test.ts +++ b/plugins/flow/mcp-server/src/__tests__/planning-friction-emission.test.ts @@ -96,6 +96,8 @@ async function readFrictionEvents(root: string): Promise { await fs.mkdir(path.join(root, ".flow", "native-stories"), { recursive: true }); + // Story native:01KVS2MG — package.json so shape-valid vitest: targets resolve to a package + await atomicWriteFile(path.join(root, "package.json"), `{ "name": "fixture" }\n`); } /** Seed a repo-relative file under `root`. */ diff --git a/plugins/flow/mcp-server/src/adapters/adapter.ts b/plugins/flow/mcp-server/src/adapters/adapter.ts index 9bf2ffb0..d86f59c1 100644 --- a/plugins/flow/mcp-server/src/adapters/adapter.ts +++ b/plugins/flow/mcp-server/src/adapters/adapter.ts @@ -110,6 +110,15 @@ export type AC = { * source file rather than a recognised runnable test (conventionally named * `.test.ts` / `.spec.ts` or living under a `__tests__/` directory). A * source-file proof is structurally guaranteed to verify nothing. + * + * Story native:01KVS2MG — vitest-target resolvability check: + * - `unresolvable-test-target`: a shape-valid `vitest:` target has NO + * `package.json` between it and the repo root (e.g. a wrong-prefix + * `mcp-server/tests/x.test.ts` instead of + * `plugins/flow/mcp-server/tests/x.test.ts`). The test FILE need not exist + * yet (the build creates it), but a runnable package must enclose it — this + * is the same upward `findPackageRoot` walk the reviewer uses, so it catches + * wrong-path markers at author/scan time before a build is wasted. */ export type DisciplineViolationReason = { code: @@ -124,7 +133,8 @@ export type DisciplineViolationReason = { | "invalid-verification-target" | "unresolvable-verification-target" | "placeholder-risk" - | "non-runnable-test-target"; + | "non-runnable-test-target" + | "unresolvable-test-target"; field: string; detail: string; }; diff --git a/plugins/flow/mcp-server/src/lib/find-package-root.ts b/plugins/flow/mcp-server/src/lib/find-package-root.ts new file mode 100644 index 00000000..777d0ee2 --- /dev/null +++ b/plugins/flow/mcp-server/src/lib/find-package-root.ts @@ -0,0 +1,210 @@ +/** + * `findPackageRoot` — walk up from a test-file path to the nearest enclosing + * `package.json`, with pnpm-workspace-aware delegation. + * + * Extracted from `tools/run-reviewer-session.ts` (Story native:01KVS2MG) so the + * SAME package-resolution walk backs BOTH the review-time vitest check AND the + * author/scan-time discipline resolvability check. Keeping one implementation + * means author-time and review-time package resolution cannot diverge — a story + * whose `vitest:` target resolves to a runnable package at author time will + * resolve to the same package root when the reviewer runs it. + * + * `run-reviewer-session.ts` re-exports `findPackageRoot` from this module so it + * stays importable by name from there (other call sites depend on that import + * path). + * + * Story 5.27 — AC1, AC2 (original walk). + * Story native:01KT6QGBWP7KJDVMHQK3MEKDXP — workspace-root vitest delegation. + * Story native:01KV6S35N4VF64WZT99SMZSFRJ — test-name-pattern fallback. + */ + +import * as path from "node:path"; +import { accessSync, readFileSync, readdirSync } from "node:fs"; +import { parse as parseYaml } from "yaml"; + +/** + * Check whether a directory has the vitest binary available locally. + * + * Returns true when `/node_modules/.bin/vitest` is accessible. + * Used by `findPackageRoot` to skip workspace roots that don't install vitest + * directly (e.g. `plugins/flow/` which delegates to its `mcp-server` sub-package). + */ +function hasLocalVitest(dir: string): boolean { + try { + accessSync(path.join(dir, "node_modules", ".bin", "vitest")); + return true; + } catch { + return false; + } +} + +/** + * Given a pnpm workspace root (`dir`), parse `pnpm-workspace.yaml` and return + * the first workspace-member directory that has vitest installed locally. + * + * Returns `{ ok: true, packageRoot }` on success or `{ ok: false }` when no + * workspace member with vitest is found. Only single-level glob patterns + * (e.g. `"mcp-server"` or `"packages/*"`) are supported — deep globs are + * skipped. Fail-soft: any parse / access error returns `{ ok: false }`. + */ +function findVitestInWorkspaceMembers( + workspaceRoot: string, +): { ok: true; packageRoot: string } | { ok: false } { + try { + const yaml = readFileSync( + path.join(workspaceRoot, "pnpm-workspace.yaml"), + "utf8", + ); + const parsed = parseYaml(yaml) as { packages?: unknown }; + const packages = parsed?.packages; + if (!Array.isArray(packages)) return { ok: false }; + + for (const pattern of packages) { + if (typeof pattern !== "string") continue; + // Only handle simple (non-glob) patterns like "mcp-server" and single-level + // globs like "packages/*". + const segments = pattern.split("/"); + const hasGlob = segments.some((s) => s === "*" || s === "**"); + if (!hasGlob) { + // Direct member: `/` + const memberDir = path.join(workspaceRoot, pattern); + if (hasLocalVitest(memberDir)) { + return { ok: true, packageRoot: memberDir }; + } + } else { + // Single-level glob like "packages/*": scan the parent directory. + const parentSegments = segments.slice(0, segments.indexOf("*")); + const parentDir = path.join(workspaceRoot, ...parentSegments); + try { + const entries = readdirSync(parentDir, { withFileTypes: true }); + for (const entry of entries) { + if (!entry.isDirectory()) continue; + const memberDir = path.join(parentDir, entry.name); + if (hasLocalVitest(memberDir)) { + return { ok: true, packageRoot: memberDir }; + } + } + } catch { + // parent directory not readable — skip this pattern. + } + } + } + } catch { + // pnpm-workspace.yaml not present or not parseable — not a workspace root. + } + return { ok: false }; +} + +/** + * Scan the subtree rooted at `root` (bounded by `maxDepth`) for the first + * `pnpm-workspace.yaml` found. Returns the directory path if found, null + * otherwise. Skips `node_modules` and `.git` to avoid unbounded traversal. + * + * Used as a fallback by `findPackageRoot` when the upward walk from the test + * file path finds no `package.json` within `checkRoot`. This happens when the + * `vitest:` AC marker is a test-name pattern (e.g. `"AC1 — valid vitest target"`) + * rather than a repo-relative file path — the dirname walk resolves to + * `checkRoot` itself and finds no `package.json` there. A downward scan for + * `pnpm-workspace.yaml` recovers the correct member package in that case. + * + * Story native:01KV6S35N4VF64WZT99SMZSFRJ — test-name-pattern vitest markers. + */ +function findWorkspaceYamlInSubtree(root: string, maxDepth: number): string | null { + if (maxDepth < 0) return null; + let entries: import("node:fs").Dirent[]; + try { + entries = readdirSync(root, { withFileTypes: true }) as import("node:fs").Dirent[]; + } catch { + return null; // not readable — skip + } + if (entries.some((e) => e.isFile() && e.name === "pnpm-workspace.yaml")) { + return root; + } + for (const entry of entries) { + if (!entry.isDirectory()) continue; + const name = entry.name as string; + if (name === "node_modules" || name === ".git") continue; + const found = findWorkspaceYamlInSubtree(path.join(root, name), maxDepth - 1); + if (found !== null) return found; + } + return null; +} + +/** + * Walk up from `testFilePathAbs` to find the nearest enclosing `package.json`. + * + * Starts at `path.dirname(testFilePathAbs)` and walks toward the filesystem + * root, stopping (inclusively) at `checkRoot`. Returns `{ ok: true, packageRoot }` + * if found, `{ ok: false }` if the walk exhausts `checkRoot` without finding one. + * + * Workspace-root handling: when the walk finds a `package.json` that has a + * sibling `pnpm-workspace.yaml`, the directory is treated as a workspace root. + * In that case the function searches the workspace members (listed in the YAML) + * for one that has vitest installed locally (`node_modules/.bin/vitest`) and + * returns that member instead. This covers the case where a `vitest:` AC marker + * targets a source file in a sub-directory of a pnpm workspace root whose root + * package delegates vitest to a member package (e.g. `plugins/flow/workflows/ + * run.workflow.js` → `plugins/flow/` workspace root → member `mcp-server`). + * + * Fallback for test-name-pattern markers: when the upward walk finds no + * `package.json` within `checkRoot` (which happens when the `vitest:` marker is + * a test-name string like `"AC1 — valid vitest target"` rather than a file path, + * so `testFilePathAbs` = `checkRoot/` and dirname = `checkRoot`), this + * function scans the `checkRoot` subtree (bounded to depth 4) for a + * `pnpm-workspace.yaml` and delegates to `findVitestInWorkspaceMembers`. This + * lets test-name-pattern markers resolve the correct package root in pnpm + * workspace repos. See Story native:01KV6S35N4VF64WZT99SMZSFRJ. + * + * Guard: `d === checkRootAbs || d.startsWith(checkRootAbs + path.sep)` prevents + * false-positive prefix matches on sibling paths (e.g. `/tmp/checker` when + * checkRoot is `/tmp/check`). ESM — uses `accessSync` from "node:fs" (top-level + * import), NOT `require(...)`. + * + * Story 5.27 — AC1, AC2. + * Story native:01KT6QGBWP7KJDVMHQK3MEKDXP — workspace-root vitest delegation. + */ +export function findPackageRoot(opts: { + testFilePathAbs: string; + checkRoot: string; +}): { ok: true; packageRoot: string } | { ok: false } { + const checkRootAbs = path.resolve(opts.checkRoot); + let dir = path.dirname(opts.testFilePathAbs); + + const isWithinCheckRoot = (d: string): boolean => + d === checkRootAbs || d.startsWith(checkRootAbs + path.sep); + + while (isWithinCheckRoot(dir)) { + try { + accessSync(path.join(dir, "package.json")); + // Found a package.json. Check whether this is a pnpm workspace root + // (has a sibling pnpm-workspace.yaml). If so, look for a workspace member + // that has vitest installed — that member is the correct vitest root. + const memberResult = findVitestInWorkspaceMembers(dir); + if (memberResult.ok) { + return memberResult; + } + // Not a workspace root, or no member has vitest installed — use this + // package root directly (the vitest binary may not be installed in fixture + // environments; the caller handles the missing-vitest failure). + return { ok: true, packageRoot: dir }; + } catch { + // package.json not present here — walk up. + } + const parent = path.dirname(dir); + if (parent === dir) break; // filesystem root reached + dir = parent; + } + + // Fallback: the upward walk found no package.json within checkRoot. + // This happens when the vitest: marker is a test-name pattern (no directory + // component), so testFilePathAbs resolves to checkRoot/ and dirname + // is checkRoot itself, which has no package.json. Scan downward from checkRoot + // for a pnpm-workspace.yaml and try its workspace members. + const workspaceYamlDir = findWorkspaceYamlInSubtree(checkRootAbs, 4); + if (workspaceYamlDir !== null) { + const memberResult = findVitestInWorkspaceMembers(workspaceYamlDir); + if (memberResult.ok) return memberResult; + } + + return { ok: false }; +} diff --git a/plugins/flow/mcp-server/src/skills/__tests__/plan-reopen.integration.test.ts b/plugins/flow/mcp-server/src/skills/__tests__/plan-reopen.integration.test.ts index c5ac692b..33c11c0b 100644 --- a/plugins/flow/mcp-server/src/skills/__tests__/plan-reopen.integration.test.ts +++ b/plugins/flow/mcp-server/src/skills/__tests__/plan-reopen.integration.test.ts @@ -73,6 +73,8 @@ afterEach(async () => { async function copyFixture(fixturePath: string): Promise { const dest = path.join(scratch, path.basename(fixturePath)); await fs.cp(fixturePath, dest, { recursive: true }); + // Story native:01KVS2MG — package.json so shape-valid vitest: targets resolve to a package + await atomicWriteFile(path.join(dest, "package.json"), `{ "name": "fixture" }\n`); return dest; } diff --git a/plugins/flow/mcp-server/src/tools/__tests__/author-seam.test.ts b/plugins/flow/mcp-server/src/tools/__tests__/author-seam.test.ts index d472e819..da34bfca 100644 --- a/plugins/flow/mcp-server/src/tools/__tests__/author-seam.test.ts +++ b/plugins/flow/mcp-server/src/tools/__tests__/author-seam.test.ts @@ -141,6 +141,9 @@ beforeEach(async () => { // disk. Seed the cited path both candidates reference so the Tier-0 T0-5 check // passes. (Their verification targets are vitest:, which is not existence-checked.) await atomicWriteFile(path.join(root, "src", "state", "ledger.ts"), "// seeded\n"); + // Story native:01KVS2MG — package.json at the workspace root so the + // candidates' `vitest:` targets resolve to a package. + await atomicWriteFile(path.join(root, "package.json"), `{ "name": "fixture" }\n`); }); afterEach(async () => { diff --git a/plugins/flow/mcp-server/src/tools/__tests__/bmad-to-native-ingest.test.ts b/plugins/flow/mcp-server/src/tools/__tests__/bmad-to-native-ingest.test.ts index b53f7b9f..546efb57 100644 --- a/plugins/flow/mcp-server/src/tools/__tests__/bmad-to-native-ingest.test.ts +++ b/plugins/flow/mcp-server/src/tools/__tests__/bmad-to-native-ingest.test.ts @@ -128,6 +128,9 @@ beforeEach(async () => { ); // Seed the resolvable paths the clean enricher cites/references. await seedFile("src/parser.ts"); + // Story native:01KVS2MG — package.json at the workspace root so the enricher's + // `vitest:` targets (e.g. `src/__tests__/*.test.ts`) resolve to a package. + await atomicWriteFile(path.join(root, "package.json"), `{ "name": "fixture" }\n`); // Reset the BMad adapter's per-process bound context between tests so the // ref-index does not leak across tmpdirs. resetBmadAdapter(); diff --git a/plugins/flow/mcp-server/src/tools/__tests__/claim-complete-loop.integration.test.ts b/plugins/flow/mcp-server/src/tools/__tests__/claim-complete-loop.integration.test.ts index e3bdbf80..e82680b2 100644 --- a/plugins/flow/mcp-server/src/tools/__tests__/claim-complete-loop.integration.test.ts +++ b/plugins/flow/mcp-server/src/tools/__tests__/claim-complete-loop.integration.test.ts @@ -122,6 +122,9 @@ async function buildIntegrationWorkspace(scratch: string): Promise { "adapter: native\nadapter_config: {}\n", ); + // Story native:01KVS2MG — package.json so shape-valid vitest: targets resolve to a package + await atomicWriteFile(path.join(root, "package.json"), `{ "name": "fixture" }\n`); + // Native stories directory const storiesDir = path.join(root, ".flow", "native-stories"); await fs.mkdir(storiesDir, { recursive: true }); diff --git a/plugins/flow/mcp-server/src/tools/__tests__/classify-story-lane.test.ts b/plugins/flow/mcp-server/src/tools/__tests__/classify-story-lane.test.ts index c8da1ae4..aed111a9 100644 --- a/plugins/flow/mcp-server/src/tools/__tests__/classify-story-lane.test.ts +++ b/plugins/flow/mcp-server/src/tools/__tests__/classify-story-lane.test.ts @@ -398,6 +398,8 @@ describe("AC4 (integration): scanSources persists lane field; hint downgrade-onl path.join(root, ".flow", "config.yaml"), `adapter: native\nadapter_config: {}\n`, ); + // Story native:01KVS2MG — package.json so shape-valid vitest: targets resolve to a package + await atomicWriteFile(path.join(root, "package.json"), `{ "name": "fixture" }\n`); for (const { ulid, citedSources } of stories) { // Seed cited source files so T0-5 resolvability check passes. for (const src of citedSources) { diff --git a/plugins/flow/mcp-server/src/tools/__tests__/inner-cycle.integration.test.ts b/plugins/flow/mcp-server/src/tools/__tests__/inner-cycle.integration.test.ts index f3613e50..6bd1194e 100644 --- a/plugins/flow/mcp-server/src/tools/__tests__/inner-cycle.integration.test.ts +++ b/plugins/flow/mcp-server/src/tools/__tests__/inner-cycle.integration.test.ts @@ -679,6 +679,9 @@ async function buildTwoStoryWorkspace(scratch: string): Promise<{ "adapter: native\nadapter_config: {}\n", ); + // Story native:01KVS2MG — package.json so shape-valid vitest: targets resolve to a package + await atomicWriteFile(path.join(root, "package.json"), `{ "name": "fixture" }\n`); + // Native stories directory const storiesDir = path.join(root, ".flow", "native-stories"); await fs.mkdir(storiesDir, { recursive: true }); diff --git a/plugins/flow/mcp-server/src/tools/__tests__/risk-reasoning-enforcement.test.ts b/plugins/flow/mcp-server/src/tools/__tests__/risk-reasoning-enforcement.test.ts index b3931bbb..0d265bf7 100644 --- a/plugins/flow/mcp-server/src/tools/__tests__/risk-reasoning-enforcement.test.ts +++ b/plugins/flow/mcp-server/src/tools/__tests__/risk-reasoning-enforcement.test.ts @@ -125,6 +125,8 @@ beforeEach(async () => { path.join(root, ".flow", "config.yaml"), `adapter: native\nadapter_config: {}\n`, ); + // Story native:01KVS2MG — package.json so shape-valid vitest: targets resolve to a package + await atomicWriteFile(path.join(root, "package.json"), `{ "name": "fixture" }\n`); // Seed files cited by the test candidates so T0-5 resolvability passes. await seedFile("src/ui/greeting.ts"); await seedFile("src/state/ledger.ts"); diff --git a/plugins/flow/mcp-server/src/tools/__tests__/scan-sources-expected-work-counters.test.ts b/plugins/flow/mcp-server/src/tools/__tests__/scan-sources-expected-work-counters.test.ts index de849d9e..d744ff38 100644 --- a/plugins/flow/mcp-server/src/tools/__tests__/scan-sources-expected-work-counters.test.ts +++ b/plugins/flow/mcp-server/src/tools/__tests__/scan-sources-expected-work-counters.test.ts @@ -85,6 +85,9 @@ async function writeFlowConfig(root: string): Promise { path.join(configDir, "config.yaml"), "adapter: native\nadapter_config: {}\n", ); + // Story native:01KVS2MG — package.json at the workspace root so freshly-scanned + // stories' shape-valid `vitest:` targets resolve to a runnable package. + await atomicWriteFile(path.join(root, "package.json"), `{ "name": "fixture" }\n`); } /** Write a native story file at the expected ULID path. */ diff --git a/plugins/flow/mcp-server/src/tools/__tests__/scan-sources.test.ts b/plugins/flow/mcp-server/src/tools/__tests__/scan-sources.test.ts index 9117063e..619a15e0 100644 --- a/plugins/flow/mcp-server/src/tools/__tests__/scan-sources.test.ts +++ b/plugins/flow/mcp-server/src/tools/__tests__/scan-sources.test.ts @@ -95,6 +95,9 @@ describe("scan-sources Story 9.1 (AC5) — fresh manifests default ready: false" // Story 10.3 — seed the cited source so the Tier-0 T0-5 resolvability check // passes at scan (cited paths must resolve on disk). await atomicWriteFile(path.join(root, "src", "handler.ts"), "// seeded\n"); + // Story native:01KVS2MG — package.json at the workspace root so the story's + // shape-valid `vitest:` target resolves to a runnable package at scan time. + await atomicWriteFile(path.join(root, "package.json"), `{ "name": "fixture" }\n`); // Seed a single source story. No pre-existing manifest → scan composes fresh. await atomicWriteFile(path.join(storiesDir, `${STORY_ULID}.md`), makeStoryBody()); @@ -142,6 +145,9 @@ describe("scan-sources Story native:01KT49G9B38NZ2QP16GY843KYK AC3 — idempoten // Seed the cited source so T0-5 resolvability passes. await atomicWriteFile(path.join(workspace, "src", "state", "ledger.ts"), "// seeded\n"); + // Story native:01KVS2MG — package.json at the workspace root so the story's + // shape-valid `vitest:` target resolves to a runnable package. + await atomicWriteFile(path.join(workspace, "package.json"), `{ "name": "fixture" }\n`); // Author a native story — writeNativeStory auto-materialises the manifest. const { ref } = await writeNativeStory({ diff --git a/plugins/flow/mcp-server/src/tools/__tests__/validate-planner-backlog.resolvability.test.ts b/plugins/flow/mcp-server/src/tools/__tests__/validate-planner-backlog.resolvability.test.ts index f0125420..92c9deb3 100644 --- a/plugins/flow/mcp-server/src/tools/__tests__/validate-planner-backlog.resolvability.test.ts +++ b/plugins/flow/mcp-server/src/tools/__tests__/validate-planner-backlog.resolvability.test.ts @@ -61,6 +61,9 @@ beforeEach(async () => { path.join(root, ".flow", "config.yaml"), `adapter: native\nadapter_config: {}\n`, ); + // Story native:01KVS2MG — a package.json at the workspace root so shape-valid + // `vitest:` targets (e.g. `src/__tests__/*.test.ts`) resolve to a package. + await atomicWriteFile(path.join(root, "package.json"), `{ "name": "fixture" }\n`); }); afterEach(async () => { diff --git a/plugins/flow/mcp-server/src/tools/__tests__/write-native-story.test.ts b/plugins/flow/mcp-server/src/tools/__tests__/write-native-story.test.ts index fe0acca3..e6928341 100644 --- a/plugins/flow/mcp-server/src/tools/__tests__/write-native-story.test.ts +++ b/plugins/flow/mcp-server/src/tools/__tests__/write-native-story.test.ts @@ -108,6 +108,12 @@ beforeEach(async () => { ]) { await seedFile(rel); } + // Story native:01KVS2MG — the write-time gate now also resolves `vitest:` + // targets to a runnable package (a package.json must sit between the target + // and the repo root). Seed one at the workspace root so the passing-candidate + // fixtures' `src/__tests__/*.test.ts` targets resolve. (The test FILE itself + // still need not exist — only the enclosing package.) + await atomicWriteFile(path.join(root, "package.json"), `{ "name": "fixture" }\n`); }); afterEach(async () => { diff --git a/plugins/flow/mcp-server/src/tools/gather-retro-inputs.test.ts b/plugins/flow/mcp-server/src/tools/gather-retro-inputs.test.ts index 5c9b2c38..09e9d40a 100644 --- a/plugins/flow/mcp-server/src/tools/gather-retro-inputs.test.ts +++ b/plugins/flow/mcp-server/src/tools/gather-retro-inputs.test.ts @@ -89,6 +89,8 @@ async function setupNativeWorkspace(tmpRoot: string): Promise { path.join(tmpRoot, ".flow", "config.yaml"), "adapter: native\nadapter_config: {}\n", ); + // Story native:01KVS2MG — package.json so shape-valid vitest: targets resolve to a package + await atomicWriteFile(path.join(tmpRoot, "package.json"), `{ "name": "fixture" }\n`); // Seed the cited source that hardening stories reference. await atomicWriteFile( path.join(tmpRoot, "plugins", "flow", "mcp-server", "src", "tools", "gather-retro-inputs.ts"), diff --git a/plugins/flow/mcp-server/src/tools/run-reviewer-session.ts b/plugins/flow/mcp-server/src/tools/run-reviewer-session.ts index 570c183d..01e85038 100644 --- a/plugins/flow/mcp-server/src/tools/run-reviewer-session.ts +++ b/plugins/flow/mcp-server/src/tools/run-reviewer-session.ts @@ -33,9 +33,14 @@ import * as path from "node:path"; import * as fs from "node:fs/promises"; -import { accessSync, readFileSync, readdirSync } from "node:fs"; import { execa as defaultExeca } from "execa"; -import { parse as parseYaml } from "yaml"; +// `findPackageRoot` now lives in `../lib/find-package-root.js` (extracted in +// Story native:01KVS2MG) so the review-time vitest check and the author/scan-time +// discipline resolvability check share ONE walk and cannot diverge. It is +// re-exported here so existing callers (and an upcoming story) keep importing it +// by name from this module. +import { findPackageRoot } from "../lib/find-package-root.js"; +export { findPackageRoot }; import { resolveWorkspace } from "../state/workspace-resolver.js"; import { lookupStandards } from "../state/lookup-standards.js"; import { loadRolePermissions } from "../state/load-role-permissions.js"; @@ -264,193 +269,6 @@ function capString(s: string): string { return s.slice(0, STDOUT_STDERR_CAP) + TRUNCATION_MARKER; } -/** - * Check whether a directory has the vitest binary available locally. - * - * Returns true when `/node_modules/.bin/vitest` is accessible. - * Used by `findPackageRoot` to skip workspace roots that don't install vitest - * directly (e.g. `plugins/flow/` which delegates to its `mcp-server` sub-package). - */ -function hasLocalVitest(dir: string): boolean { - try { - accessSync(path.join(dir, "node_modules", ".bin", "vitest")); - return true; - } catch { - return false; - } -} - -/** - * Given a pnpm workspace root (`dir`), parse `pnpm-workspace.yaml` and return - * the first workspace-member directory that has vitest installed locally. - * - * Returns `{ ok: true, packageRoot }` on success or `{ ok: false }` when no - * workspace member with vitest is found. Only single-level glob patterns - * (e.g. `"mcp-server"` or `"packages/*"`) are supported — deep globs are - * skipped. Fail-soft: any parse / access error returns `{ ok: false }`. - */ -function findVitestInWorkspaceMembers( - workspaceRoot: string, -): { ok: true; packageRoot: string } | { ok: false } { - try { - const yaml = readFileSync( - path.join(workspaceRoot, "pnpm-workspace.yaml"), - "utf8", - ); - const parsed = parseYaml(yaml) as { packages?: unknown }; - const packages = parsed?.packages; - if (!Array.isArray(packages)) return { ok: false }; - - for (const pattern of packages) { - if (typeof pattern !== "string") continue; - // Only handle simple (non-glob) patterns like "mcp-server" and single-level - // globs like "packages/*". - const segments = pattern.split("/"); - const hasGlob = segments.some((s) => s === "*" || s === "**"); - if (!hasGlob) { - // Direct member: `/` - const memberDir = path.join(workspaceRoot, pattern); - if (hasLocalVitest(memberDir)) { - return { ok: true, packageRoot: memberDir }; - } - } else { - // Single-level glob like "packages/*": scan the parent directory. - const parentSegments = segments.slice(0, segments.indexOf("*")); - const parentDir = path.join(workspaceRoot, ...parentSegments); - try { - const entries = readdirSync(parentDir, { withFileTypes: true }); - for (const entry of entries) { - if (!entry.isDirectory()) continue; - const memberDir = path.join(parentDir, entry.name); - if (hasLocalVitest(memberDir)) { - return { ok: true, packageRoot: memberDir }; - } - } - } catch { - // parent directory not readable — skip this pattern. - } - } - } - } catch { - // pnpm-workspace.yaml not present or not parseable — not a workspace root. - } - return { ok: false }; -} - -/** - * Scan the subtree rooted at `root` (bounded by `maxDepth`) for the first - * `pnpm-workspace.yaml` found. Returns the directory path if found, null - * otherwise. Skips `node_modules` and `.git` to avoid unbounded traversal. - * - * Used as a fallback by `findPackageRoot` when the upward walk from the test - * file path finds no `package.json` within `checkRoot`. This happens when the - * `vitest:` AC marker is a test-name pattern (e.g. `"AC1 — valid vitest target"`) - * rather than a repo-relative file path — the dirname walk resolves to - * `checkRoot` itself and finds no `package.json` there. A downward scan for - * `pnpm-workspace.yaml` recovers the correct member package in that case. - * - * Story native:01KV6S35N4VF64WZT99SMZSFRJ — test-name-pattern vitest markers. - */ -function findWorkspaceYamlInSubtree(root: string, maxDepth: number): string | null { - if (maxDepth < 0) return null; - let entries: import("node:fs").Dirent[]; - try { - entries = readdirSync(root, { withFileTypes: true }) as import("node:fs").Dirent[]; - } catch { - return null; // not readable — skip - } - if (entries.some((e) => e.isFile() && e.name === "pnpm-workspace.yaml")) { - return root; - } - for (const entry of entries) { - if (!entry.isDirectory()) continue; - const name = entry.name as string; - if (name === "node_modules" || name === ".git") continue; - const found = findWorkspaceYamlInSubtree(path.join(root, name), maxDepth - 1); - if (found !== null) return found; - } - return null; -} - -/** - * Walk up from `testFilePathAbs` to find the nearest enclosing `package.json`. - * - * Starts at `path.dirname(testFilePathAbs)` and walks toward the filesystem - * root, stopping (inclusively) at `checkRoot`. Returns `{ ok: true, packageRoot }` - * if found, `{ ok: false }` if the walk exhausts `checkRoot` without finding one. - * - * Workspace-root handling: when the walk finds a `package.json` that has a - * sibling `pnpm-workspace.yaml`, the directory is treated as a workspace root. - * In that case the function searches the workspace members (listed in the YAML) - * for one that has vitest installed locally (`node_modules/.bin/vitest`) and - * returns that member instead. This covers the case where a `vitest:` AC marker - * targets a source file in a sub-directory of a pnpm workspace root whose root - * package delegates vitest to a member package (e.g. `plugins/flow/workflows/ - * run.workflow.js` → `plugins/flow/` workspace root → member `mcp-server`). - * - * Fallback for test-name-pattern markers: when the upward walk finds no - * `package.json` within `checkRoot` (which happens when the `vitest:` marker is - * a test-name string like `"AC1 — valid vitest target"` rather than a file path, - * so `testFilePathAbs` = `checkRoot/` and dirname = `checkRoot`), this - * function scans the `checkRoot` subtree (bounded to depth 4) for a - * `pnpm-workspace.yaml` and delegates to `findVitestInWorkspaceMembers`. This - * lets test-name-pattern markers resolve the correct package root in pnpm - * workspace repos. See Story native:01KV6S35N4VF64WZT99SMZSFRJ. - * - * Guard: `d === checkRootAbs || d.startsWith(checkRootAbs + path.sep)` prevents - * false-positive prefix matches on sibling paths (e.g. `/tmp/checker` when - * checkRoot is `/tmp/check`). ESM — uses `accessSync` from "node:fs" (top-level - * import), NOT `require(...)`. - * - * Story 5.27 — AC1, AC2. - * Story native:01KT6QGBWP7KJDVMHQK3MEKDXP — workspace-root vitest delegation. - */ -export function findPackageRoot(opts: { - testFilePathAbs: string; - checkRoot: string; -}): { ok: true; packageRoot: string } | { ok: false } { - const checkRootAbs = path.resolve(opts.checkRoot); - let dir = path.dirname(opts.testFilePathAbs); - - const isWithinCheckRoot = (d: string): boolean => - d === checkRootAbs || d.startsWith(checkRootAbs + path.sep); - - while (isWithinCheckRoot(dir)) { - try { - accessSync(path.join(dir, "package.json")); - // Found a package.json. Check whether this is a pnpm workspace root - // (has a sibling pnpm-workspace.yaml). If so, look for a workspace member - // that has vitest installed — that member is the correct vitest root. - const memberResult = findVitestInWorkspaceMembers(dir); - if (memberResult.ok) { - return memberResult; - } - // Not a workspace root, or no member has vitest installed — use this - // package root directly (the vitest binary may not be installed in fixture - // environments; the caller handles the missing-vitest failure). - return { ok: true, packageRoot: dir }; - } catch { - // package.json not present here — walk up. - } - const parent = path.dirname(dir); - if (parent === dir) break; // filesystem root reached - dir = parent; - } - - // Fallback: the upward walk found no package.json within checkRoot. - // This happens when the vitest: marker is a test-name pattern (no directory - // component), so testFilePathAbs resolves to checkRoot/ and dirname - // is checkRoot itself, which has no package.json. Scan downward from checkRoot - // for a pnpm-workspace.yaml and try its workspace members. - const workspaceYamlDir = findWorkspaceYamlInSubtree(checkRootAbs, 4); - if (workspaceYamlDir !== null) { - const memberResult = findVitestInWorkspaceMembers(workspaceYamlDir); - if (memberResult.ok) return memberResult; - } - - return { ok: false }; -} - /** Strip ANSI escape codes so vitest's summary line can be parsed as plain text. */ function stripAnsi(s: string): string { // eslint-disable-next-line no-control-regex diff --git a/plugins/flow/mcp-server/src/validators/__tests__/discipline-resolvability.parity.test.ts b/plugins/flow/mcp-server/src/validators/__tests__/discipline-resolvability.parity.test.ts index 92fb4b13..163a1172 100644 --- a/plugins/flow/mcp-server/src/validators/__tests__/discipline-resolvability.parity.test.ts +++ b/plugins/flow/mcp-server/src/validators/__tests__/discipline-resolvability.parity.test.ts @@ -100,6 +100,10 @@ beforeEach(async () => { path.join(root, ".flow", "config.yaml"), `adapter: native\nadapter_config: {}\n`, ); + // Story native:01KVS2MG — package.json at the workspace root so shape-valid + // `vitest:` targets resolve to a runnable package (parity holds for the new + // resolvability check too, since both paths share the same walk). + await atomicWriteFile(path.join(root, "package.json"), `{ "name": "fixture" }\n`); }); afterEach(async () => { diff --git a/plugins/flow/mcp-server/src/validators/__tests__/discipline-runnable-test-kind.test.ts b/plugins/flow/mcp-server/src/validators/__tests__/discipline-runnable-test-kind.test.ts index eeb55944..e0ddaff2 100644 --- a/plugins/flow/mcp-server/src/validators/__tests__/discipline-runnable-test-kind.test.ts +++ b/plugins/flow/mcp-server/src/validators/__tests__/discipline-runnable-test-kind.test.ts @@ -77,6 +77,10 @@ beforeEach(async () => { ); // Seed the cited source so the cited-source check never false-fires. await seedFile("src/validators/discipline-resolvability.ts"); + // Seed a package.json at the repo root so the vitest-target resolvability + // check (Story native:01KVS2MG) resolves a package for default targets like + // `src/validators/__tests__/foo.test.ts` (findPackageRoot walks up to here). + await atomicWriteFile(path.join(root, "package.json"), `{ "name": "fixture" }\n`); }); afterEach(async () => { @@ -386,6 +390,31 @@ describe("back-compat — artifact: proof is unaffected by the runnable-test-kin expect(kindViolations).toHaveLength(0); }); + it("does not emit unresolvable-test-target for a BMad story (non-enriched, gated by isEnrichedStory)", async () => { + // A BMad story whose vitest: target would NOT resolve to a package must still + // be skipped entirely — resolveDisciplinePaths returns [] for non-enriched. + const bmadStory: SourceStory = { + ref: "bmad:2.7", + title: "BMad story with wrong-prefix vitest target", + narrative: "As a user, I want things.", + acceptance_criteria: [ + { + text: "Given X When Y Then Z.", + kind: "integration", + // wrong-prefix target with no package above it — would fire for native. + verification: { type: "vitest", target: "mcp-server/tests/x.test.ts" }, + }, + ], + depends_on: [], + raw_path: "/fake/bmad.md", + raw_frontmatter: {}, + source_hash: "c".repeat(64), + }; + + const violations = await resolveDisciplinePaths(bmadStory, root); + expect(violations).toHaveLength(0); + }); + it("does not emit non-runnable-test-target for a BMad story (non-enriched, gated by isEnrichedStory)", async () => { // BMad stories are non-enriched (bmad: ref). resolveDisciplinePaths returns // [] immediately for non-enriched stories — no new check fires. @@ -412,3 +441,130 @@ describe("back-compat — artifact: proof is unaffected by the runnable-test-kin expect(violations).toHaveLength(0); }); }); + +// --------------------------------------------------------------------------- +// Story native:01KVS2MG — vitest-target resolvability check +// +// A shape-valid `vitest:` target is additionally required to resolve to a +// runnable PACKAGE: a package.json must exist between the target path and the +// repo root (the same `findPackageRoot` walk the reviewer uses). The test FILE +// itself need NOT pre-exist (the build creates it) — only the package above it. +// These tests use a separate scratch tree per case so we control exactly where +// (if anywhere) a package.json sits above the target. +// --------------------------------------------------------------------------- + +describe("Story native:01KVS2MG — vitest target must resolve to a runnable package", () => { + let repoRoot: string; + + beforeEach(async () => { + const scratch = await fs.mkdtemp(path.join(os.tmpdir(), "flow-resolvable-test-target-")); + repoRoot = path.join(scratch, "repo"); + // Mirror the real monorepo layout: the package lives under + // plugins/flow/mcp-server, NOT directly at the repo root. So the repo root + // itself has NO package.json — a wrong-prefix target that lands directly + // under the root cannot resolve a package. + await fs.mkdir(path.join(repoRoot, "plugins", "flow", "mcp-server", "src"), { + recursive: true, + }); + await atomicWriteFile( + path.join(repoRoot, "plugins", "flow", "mcp-server", "package.json"), + `{ "name": "@flow/mcp-server" }\n`, + ); + }); + + afterEach(async () => { + await fs.rm(path.dirname(repoRoot), { recursive: true, force: true }); + }); + + /** Build a native story with a single vitest: AC, citing a seeded source. */ + async function makeResolvabilityStory(target: string): Promise { + // Seed a cited source under the real package so the cited-source check passes. + const citedRel = "plugins/flow/mcp-server/src/feature.ts"; + await atomicWriteFile(path.join(repoRoot, citedRel), "// seeded\n"); + return { + ref: "native:RESOLVABLETEST000000000001", + title: "Resolvability test story", + narrative: "As an operator, I want the check to gate wrong-path targets.", + acceptance_criteria: [ + { + text: "Given a vitest target When the story is saved Then resolvability is checked.", + kind: "integration", + verification: { type: "vitest", target }, + }, + ], + tasks: [{ text: "Implement", ac_refs: ["AC1"] }], + cited_sources: [citedRel], + depends_on: [], + raw_path: "/fake/story.md", + raw_frontmatter: {}, + source_hash: "d".repeat(64), + }; + } + + // AC1 — a wrong-prefix target with NO package.json between it and the repo + // root is refused with an `unresolvable-test-target` violation naming the + // offending target, and nothing else fires (no build wasted). + it("AC1: refuses a wrong-prefix vitest target that resolves to no package", async () => { + // Wrong prefix: 'mcp-server/...' instead of 'plugins/flow/mcp-server/...'. + // No package.json exists at repoRoot/mcp-server or above (up to repoRoot). + const wrongPrefix = "mcp-server/tests/x.test.ts"; + const story = await makeResolvabilityStory(wrongPrefix); + + const violations = await resolveDisciplinePaths(story, repoRoot); + + const unresolvable = violations.filter((v) => v.code === "unresolvable-test-target"); + expect(unresolvable).toHaveLength(1); + // Names the offending target. + expect(unresolvable[0]!.detail).toContain(wrongPrefix); + // Names the AC. + expect(unresolvable[0]!.detail).toMatch(/AC1/); + // The shape check is NOT what fired — the path IS a runnable-test shape. + expect(violations.some((v) => v.code === "non-runnable-test-target")).toBe(false); + }); + + it("AC1: the violation array is non-empty so the save/scan gate refuses before any build", async () => { + const story = await makeResolvabilityStory("mcp-server/tests/y.test.ts"); + const violations = await resolveDisciplinePaths(story, repoRoot); + // A non-empty array is the signal the write gate and scan path both check + // before materialising the manifest. + expect(violations.length).toBeGreaterThan(0); + expect(violations.some((v) => v.code === "unresolvable-test-target")).toBe(true); + }); + + // AC2 — a NOT-YET-EXISTING test file under a real package PASSES. The check + // verifies a package resolves, not that the test file already exists. + it("AC2: passes a not-yet-existing test file under a real package", async () => { + // This .test.ts does not exist on disk, but plugins/flow/mcp-server has a + // package.json that resolves above it. + const target = "plugins/flow/mcp-server/src/__tests__/not-yet-created.test.ts"; + const story = await makeResolvabilityStory(target); + + // Sanity: the test file genuinely does not exist yet. + await expect( + fs.stat(path.join(repoRoot, target)), + ).rejects.toBeTruthy(); + + const violations = await resolveDisciplinePaths(story, repoRoot); + expect(violations.filter((v) => v.code === "unresolvable-test-target")).toHaveLength(0); + // And it does not trip any other discipline check either. + expect(violations).toHaveLength(0); + }); + + // AC3 — a shape-invalid target (not under __tests__/, not ending .test/.spec) + // still fails with the EXISTING non-runnable-test-target violation, NOT the + // new resolvability one (no regression to the shape check; no double-report). + it("AC3: a shape-invalid target still fails non-runnable-test-target, not unresolvable-test-target", async () => { + // Ordinary source file under the real package — shape-invalid as a test. + const sourceFile = "plugins/flow/mcp-server/src/feature.ts"; + const story = await makeResolvabilityStory(sourceFile); + + const violations = await resolveDisciplinePaths(story, repoRoot); + + const shape = violations.filter((v) => v.code === "non-runnable-test-target"); + expect(shape).toHaveLength(1); + expect(shape[0]!.detail).toContain(sourceFile); + // The resolvability check must NOT also fire — the shape error is the single + // actionable signal for a malformed/source-file target. + expect(violations.some((v) => v.code === "unresolvable-test-target")).toBe(false); + }); +}); diff --git a/plugins/flow/mcp-server/src/validators/discipline-resolvability.ts b/plugins/flow/mcp-server/src/validators/discipline-resolvability.ts index 763cebae..3125d253 100644 --- a/plugins/flow/mcp-server/src/validators/discipline-resolvability.ts +++ b/plugins/flow/mcp-server/src/validators/discipline-resolvability.ts @@ -30,6 +30,7 @@ import { promises as fs } from "node:fs"; import * as path from "node:path"; import type { DisciplineViolationReason, SourceStory } from "../adapters/adapter.js"; import { isEnrichedStory } from "./planning-discipline.js"; +import { findPackageRoot } from "../lib/find-package-root.js"; /** * Reject an obviously non-path verification target — the part of T0-6 that @@ -183,5 +184,37 @@ export async function resolveDisciplinePaths( } } + // Resolvability-check `vitest:` targets (Story native:01KVS2MG). A `vitest:` + // target's test FILE need NOT pre-exist (the build creates it — see module + // header), but a runnable *package* must enclose it: the reviewer walks up + // from the test path to the nearest `package.json` (`findPackageRoot`) and + // fails the AC when none resolves. We run that SAME walk here so a wrong-prefix + // target (e.g. `mcp-server/tests/x.test.ts` instead of + // `plugins/flow/mcp-server/tests/x.test.ts`) — which has NO package.json + // between it and the repo root — is refused at author/scan time, BEFORE a + // build is wasted, with an `unresolvable-test-target` violation. Sharing the + // walk means author-time and review-time package resolution cannot diverge. + // + // Critical asymmetry vs `artifact:` existence: we DO NOT require the test file + // to exist — only that a package.json resolves above it. A not-yet-existing + // test file under a real package therefore PASSES (preserves the + // build-creates-it intent). + for (let i = 0; i < story.acceptance_criteria.length; i++) { + const v = story.acceptance_criteria[i]!.verification; + if (!v) continue; + if (v.type !== "vitest") continue; // artifact targets handled above. + if (!isWellFormedTarget(v.target)) continue; // malformed → already reported. + if (!isRunnableTestTarget(v.target)) continue; // shape-bad → already reported. + const testFilePathAbs = path.resolve(targetRepoRoot, v.target); + const pkg = findPackageRoot({ testFilePathAbs, checkRoot: targetRepoRoot }); + if (!pkg.ok) { + reasons.push({ + code: "unresolvable-test-target", + field: `acceptance_criteria[${i}].verification.target`, + detail: `AC${i + 1} vitest verification target '${v.target}' cannot resolve to a runnable package: no package.json was found between it (looked at '${testFilePathAbs}') and the repo root ('${targetRepoRoot}'). The test FILE need not exist yet (the build creates it), but a package must enclose it — this is the same upward walk the reviewer uses, so a target that fails here would also fail at review. A common cause is a wrong-prefix path (e.g. 'mcp-server/tests/x.test.ts' instead of 'plugins/flow/mcp-server/tests/x.test.ts'); fix the prefix so the target lands under a real package.`, + }); + } + } + return reasons; } diff --git a/plugins/flow/mcp-server/tests/scan-sources.test.ts b/plugins/flow/mcp-server/tests/scan-sources.test.ts index 677d5769..fca40bbf 100644 --- a/plugins/flow/mcp-server/tests/scan-sources.test.ts +++ b/plugins/flow/mcp-server/tests/scan-sources.test.ts @@ -500,6 +500,8 @@ describe("Story 10.3 AC1/AC3 — Tier-0 fail-closed at scan (native workspace)", path.join(nativeScratch, ".flow", "config.yaml"), `adapter: native\nadapter_config: {}\n`, ); + // Story native:01KVS2MG — package.json so shape-valid vitest: targets resolve to a package + await atomicWriteFile(path.join(nativeScratch, "package.json"), `{ "name": "fixture" }\n`); }); afterEach(async () => { diff --git a/plugins/flow/mcp-server/tests/write-native-story.test.ts b/plugins/flow/mcp-server/tests/write-native-story.test.ts index ebfa6ff2..bae85437 100644 --- a/plugins/flow/mcp-server/tests/write-native-story.test.ts +++ b/plugins/flow/mcp-server/tests/write-native-story.test.ts @@ -65,6 +65,8 @@ beforeEach(async () => { ); // Create the native-stories directory so detect() returns true. await fs.mkdir(path.join(scratch, ".flow", "native-stories"), { recursive: true }); + // Story native:01KVS2MG — package.json so shape-valid vitest: targets resolve to a package + await atomicWriteFile(path.join(scratch, "package.json"), `{ "name": "fixture" }\n`); // Story 10.3 — writeNativeStory now resolves cited_sources on disk. Seed every // cited path the tests below reference so those writes are not (correctly) // rejected. All verification targets in this file are `vitest:` (not