@mstar-harness/cli 3.4.1 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mstar-harness.js +864 -228
- package/package.json +1 -1
package/dist/mstar-harness.js
CHANGED
|
@@ -2414,7 +2414,7 @@ var require_picocolors2 = __commonJS((exports, module) => {
|
|
|
2414
2414
|
});
|
|
2415
2415
|
|
|
2416
2416
|
// src/index.ts
|
|
2417
|
-
import { execFileSync as
|
|
2417
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
2418
2418
|
import fs8 from "fs";
|
|
2419
2419
|
import path12 from "path";
|
|
2420
2420
|
|
|
@@ -4073,33 +4073,35 @@ import { randomUUID } from "node:crypto";
|
|
|
4073
4073
|
import { basename, dirname, join, resolve } from "node:path";
|
|
4074
4074
|
import { readFileSync as readFileSync2, statSync } from "node:fs";
|
|
4075
4075
|
import { dirname as dirname2, isAbsolute, join as join2, relative, resolve as resolve2 } from "node:path";
|
|
4076
|
-
import { existsSync as
|
|
4076
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readdirSync as readdirSync5, readFileSync as readFileSync6, realpathSync as realpathSync2, statSync as statSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
4077
4077
|
import { execFileSync } from "node:child_process";
|
|
4078
|
-
import { basename as
|
|
4079
|
-
import { existsSync as
|
|
4080
|
-
import { join as
|
|
4078
|
+
import { basename as basename3, dirname as dirname5, isAbsolute as isAbsolute4, join as join9, relative as relative2, resolve as resolve7 } from "node:path";
|
|
4079
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, readdirSync as readdirSync4 } from "node:fs";
|
|
4080
|
+
import { basename as basename2, join as join8, resolve as resolve6 } from "node:path";
|
|
4081
4081
|
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync3 } from "node:fs";
|
|
4082
4082
|
import { mkdirSync as mkdirSync2, rmdirSync, statSync as statSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
4083
4083
|
import { dirname as dirname3, isAbsolute as isAbsolute2, join as join4, resolve as resolve3 } from "node:path";
|
|
4084
4084
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
4085
4085
|
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
4086
|
-
import { existsSync as existsSync3,
|
|
4087
|
-
import {
|
|
4086
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "node:fs";
|
|
4087
|
+
import { isAbsolute as isAbsolute3, join as join5, resolve as resolve4 } from "node:path";
|
|
4088
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync3, realpathSync } from "node:fs";
|
|
4089
|
+
import { dirname as dirname4, join as join7, resolve as resolve5, sep } from "node:path";
|
|
4088
4090
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
4089
|
-
import { existsSync as
|
|
4090
|
-
import { isAbsolute as
|
|
4091
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
4092
|
+
import { isAbsolute as isAbsolute5, resolve as resolve8 } from "node:path";
|
|
4091
4093
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
4092
|
-
import { mkdirSync as mkdirSync6, readdirSync as
|
|
4093
|
-
import { basename as
|
|
4094
|
+
import { mkdirSync as mkdirSync6, readdirSync as readdirSync6, readFileSync as readFileSync7, realpathSync as realpathSync3, statSync as statSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
4095
|
+
import { basename as basename4, dirname as dirname6, isAbsolute as isAbsolute6, join as join10, resolve as resolve9 } from "node:path";
|
|
4094
4096
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
4095
|
-
import { existsSync as
|
|
4096
|
-
import { basename as
|
|
4097
|
-
import { existsSync as
|
|
4098
|
-
import { basename as
|
|
4099
|
-
import { existsSync as
|
|
4100
|
-
import { join as
|
|
4101
|
-
import { readdirSync as
|
|
4102
|
-
import { isAbsolute as
|
|
4097
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync8, readdirSync as readdirSync8, readFileSync as readFileSync9, rmdirSync as rmdirSync2, rmSync, writeFileSync as writeFileSync6 } from "node:fs";
|
|
4098
|
+
import { basename as basename5, join as join12, resolve as resolve11, sep as sep3 } from "node:path";
|
|
4099
|
+
import { existsSync as existsSync9, readdirSync as readdirSync9, readFileSync as readFileSync10 } from "node:fs";
|
|
4100
|
+
import { basename as basename6, isAbsolute as isAbsolute8, join as join13, relative as relative4, resolve as resolve12, sep as sep4 } from "node:path";
|
|
4101
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
4102
|
+
import { join as join14 } from "node:path";
|
|
4103
|
+
import { readdirSync as readdirSync10 } from "node:fs";
|
|
4104
|
+
import { isAbsolute as isAbsolute9, join as join15 } from "node:path";
|
|
4103
4105
|
var SEVERITY_ORDER = ["critical", "high", "medium", "low", "nit"];
|
|
4104
4106
|
function readJson(filePath) {
|
|
4105
4107
|
if (!existsSync(filePath))
|
|
@@ -4620,6 +4622,160 @@ async function withStatusWriteLock(statusPath, fn, opts = {}) {
|
|
|
4620
4622
|
} catch {}
|
|
4621
4623
|
}
|
|
4622
4624
|
}
|
|
4625
|
+
var PLAN_SHAPED_KEY_RE = /^[0-9]{8}-[a-z0-9-]+$/;
|
|
4626
|
+
function resolveArtifactPath(harnessRoot, ref) {
|
|
4627
|
+
const { kind, key } = ref;
|
|
4628
|
+
if (kind === "json") {
|
|
4629
|
+
if (!isAbsolute3(key)) {
|
|
4630
|
+
throw new Error(`ArtifactStore json key must be an absolute path \u2014 got ${JSON.stringify(key)}`);
|
|
4631
|
+
}
|
|
4632
|
+
if (key.split(/[\\/]+/).includes("..")) {
|
|
4633
|
+
throw new Error(`ArtifactStore json key must not contain ".." segments \u2014 got ${JSON.stringify(key)}`);
|
|
4634
|
+
}
|
|
4635
|
+
return key;
|
|
4636
|
+
}
|
|
4637
|
+
assertSafePathComponent(key, "ArtifactStore key");
|
|
4638
|
+
if (kind === "status") {
|
|
4639
|
+
if (key !== "root") {
|
|
4640
|
+
throw new Error(`ArtifactStore status key must be "root" \u2014 got ${JSON.stringify(key)}`);
|
|
4641
|
+
}
|
|
4642
|
+
return join5(harnessRoot, "status.json");
|
|
4643
|
+
}
|
|
4644
|
+
if (kind === "snapshot") {
|
|
4645
|
+
return join5(resolveWorkflowDir(harnessRoot, { harnessDir: harnessRoot }), key, "snapshot.json");
|
|
4646
|
+
}
|
|
4647
|
+
if (kind === "residuals") {
|
|
4648
|
+
return join5(resolveProjectDir(harnessRoot, { harnessDir: harnessRoot }), key, "residuals.json");
|
|
4649
|
+
}
|
|
4650
|
+
if (PLAN_SHAPED_KEY_RE.test(key)) {
|
|
4651
|
+
return join5(harnessRoot, "sdd", key, "review", "report.json");
|
|
4652
|
+
}
|
|
4653
|
+
return join5(harnessRoot, "sdd", "_reviews", `${key}.json`);
|
|
4654
|
+
}
|
|
4655
|
+
function listDirNames(dir) {
|
|
4656
|
+
return readDirEntries(dir).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
4657
|
+
}
|
|
4658
|
+
function listJsonKeys(dir) {
|
|
4659
|
+
return readDirEntries(dir).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -".json".length));
|
|
4660
|
+
}
|
|
4661
|
+
function readDirEntries(dir) {
|
|
4662
|
+
try {
|
|
4663
|
+
return readdirSync2(dir, { withFileTypes: true });
|
|
4664
|
+
} catch (error) {
|
|
4665
|
+
const code = error.code;
|
|
4666
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
4667
|
+
return [];
|
|
4668
|
+
throw error;
|
|
4669
|
+
}
|
|
4670
|
+
}
|
|
4671
|
+
function tryResolveGetPath(root, kind, key) {
|
|
4672
|
+
try {
|
|
4673
|
+
return resolveArtifactPath(root, { kind, key });
|
|
4674
|
+
} catch {
|
|
4675
|
+
return;
|
|
4676
|
+
}
|
|
4677
|
+
}
|
|
4678
|
+
function createFsStore(harnessRoot) {
|
|
4679
|
+
const root = resolve4(harnessRoot);
|
|
4680
|
+
return {
|
|
4681
|
+
root,
|
|
4682
|
+
async put(doc) {
|
|
4683
|
+
if (doc.schema !== undefined) {
|
|
4684
|
+
throw new Error("FsStore does not persist schema ids \u2014 omit --schema or inject a store module that persists it");
|
|
4685
|
+
}
|
|
4686
|
+
writeJson(resolveArtifactPath(root, doc), doc.payload);
|
|
4687
|
+
},
|
|
4688
|
+
async get(ref) {
|
|
4689
|
+
const filePath = resolveArtifactPath(root, ref);
|
|
4690
|
+
if (!existsSync3(filePath))
|
|
4691
|
+
return;
|
|
4692
|
+
return readJson(filePath);
|
|
4693
|
+
},
|
|
4694
|
+
async delete(ref) {
|
|
4695
|
+
const filePath = resolveArtifactPath(root, ref);
|
|
4696
|
+
if (existsSync3(filePath))
|
|
4697
|
+
unlinkSync3(filePath);
|
|
4698
|
+
},
|
|
4699
|
+
async list(kind) {
|
|
4700
|
+
if (kind === "json") {
|
|
4701
|
+
throw new Error("ArtifactStore json keys are absolute paths and cannot be listed");
|
|
4702
|
+
}
|
|
4703
|
+
const keys = [];
|
|
4704
|
+
if (kind === "status") {
|
|
4705
|
+
if (existsSync3(resolveArtifactPath(root, { kind, key: "root" })))
|
|
4706
|
+
keys.push("root");
|
|
4707
|
+
} else if (kind === "snapshot" || kind === "residuals") {
|
|
4708
|
+
const baseDir = kind === "snapshot" ? resolveWorkflowDir(root, { harnessDir: root }) : resolveProjectDir(root, { harnessDir: root });
|
|
4709
|
+
for (const name of listDirNames(baseDir)) {
|
|
4710
|
+
const getPath = tryResolveGetPath(root, kind, name);
|
|
4711
|
+
if (getPath !== undefined && existsSync3(getPath))
|
|
4712
|
+
keys.push(name);
|
|
4713
|
+
}
|
|
4714
|
+
} else {
|
|
4715
|
+
const sddDir = join5(root, "sdd");
|
|
4716
|
+
for (const key of listJsonKeys(join5(sddDir, "_reviews"))) {
|
|
4717
|
+
if (!PLAN_SHAPED_KEY_RE.test(key) && tryResolveGetPath(root, kind, key) !== undefined) {
|
|
4718
|
+
keys.push(key);
|
|
4719
|
+
}
|
|
4720
|
+
}
|
|
4721
|
+
for (const name of listDirNames(sddDir)) {
|
|
4722
|
+
if (PLAN_SHAPED_KEY_RE.test(name)) {
|
|
4723
|
+
const getPath = tryResolveGetPath(root, kind, name);
|
|
4724
|
+
if (getPath !== undefined && existsSync3(getPath))
|
|
4725
|
+
keys.push(name);
|
|
4726
|
+
}
|
|
4727
|
+
}
|
|
4728
|
+
}
|
|
4729
|
+
return keys.sort().map((key) => ({ kind, key }));
|
|
4730
|
+
}
|
|
4731
|
+
};
|
|
4732
|
+
}
|
|
4733
|
+
var injectedStore;
|
|
4734
|
+
function setArtifactStore(store) {
|
|
4735
|
+
injectedStore = store;
|
|
4736
|
+
}
|
|
4737
|
+
function getArtifactStore() {
|
|
4738
|
+
if (injectedStore !== undefined)
|
|
4739
|
+
return injectedStore;
|
|
4740
|
+
const root = resolveHarnessDir(process.cwd());
|
|
4741
|
+
if (root === null) {
|
|
4742
|
+
throw new Error(`harness dir not found from ${resolve4(process.cwd())} \u2014 cannot create the default FsStore (run \`mstar harness scaffold\`, pass opts.harnessDir, or set MSTAR_HARNESS_DIR)`);
|
|
4743
|
+
}
|
|
4744
|
+
return createFsStore(root);
|
|
4745
|
+
}
|
|
4746
|
+
function assertFsStorePath(store, ref, expectedPath) {
|
|
4747
|
+
const root = store.root;
|
|
4748
|
+
if (typeof root !== "string")
|
|
4749
|
+
return;
|
|
4750
|
+
const storePath = resolveArtifactPath(root, ref);
|
|
4751
|
+
const expected = resolve4(expectedPath);
|
|
4752
|
+
if (storePath !== expected) {
|
|
4753
|
+
throw new Error(`routed writer path mismatch: the active FsStore resolves ${ref.kind}/${JSON.stringify(ref.key)} to ${JSON.stringify(storePath)} but the caller's target is ${JSON.stringify(expected)} \u2014 call setArtifactStore(createFsStore(<root>)) first when the write target differs from the active store's root`);
|
|
4754
|
+
}
|
|
4755
|
+
}
|
|
4756
|
+
var URI_SCHEME_RE = /^[A-Za-z][A-Za-z0-9+.-]*:/;
|
|
4757
|
+
function isArtifactStore(value) {
|
|
4758
|
+
return typeof value === "object" && value !== null && typeof value.put === "function" && typeof value.get === "function";
|
|
4759
|
+
}
|
|
4760
|
+
async function loadStoreModule(modulePath) {
|
|
4761
|
+
if (modulePath === "") {
|
|
4762
|
+
throw new Error("loadStoreModule: module path must not be empty");
|
|
4763
|
+
}
|
|
4764
|
+
if (URI_SCHEME_RE.test(modulePath)) {
|
|
4765
|
+
throw new Error(`loadStoreModule: only filesystem paths are allowed \u2014 got ${JSON.stringify(modulePath)} (URI schemes such as http:/https:/file: are rejected)`);
|
|
4766
|
+
}
|
|
4767
|
+
const resolved = resolve4(modulePath);
|
|
4768
|
+
if (!existsSync3(resolved)) {
|
|
4769
|
+
throw new Error(`loadStoreModule: module file not found \u2014 ${JSON.stringify(resolved)}`);
|
|
4770
|
+
}
|
|
4771
|
+
const mod = await import(resolved);
|
|
4772
|
+
const candidate = mod.createArtifactStore ?? mod.default ?? mod;
|
|
4773
|
+
const store = typeof candidate === "function" ? await candidate() : candidate;
|
|
4774
|
+
if (!isArtifactStore(store)) {
|
|
4775
|
+
throw new Error(`loadStoreModule: module ${JSON.stringify(resolved)} does not export an ArtifactStore \u2014 expected a createArtifactStore named export, a default factory, or a default object with put() and get() functions`);
|
|
4776
|
+
}
|
|
4777
|
+
return store;
|
|
4778
|
+
}
|
|
4623
4779
|
var BRANCH_FORMS_HINT = '"Working branch: <existing>" | "Working branch: create <new> from <base>" | "Branch policy: direct on <branch> \u2014 <reason>"';
|
|
4624
4780
|
var REQUIRED_FIELDS = [
|
|
4625
4781
|
{ key: "executeAs", label: "Execute as", code: "execute-as" },
|
|
@@ -5052,7 +5208,7 @@ function validateStatusV2(docOrPath, opts = {}) {
|
|
|
5052
5208
|
if (typeof docOrPath === "string") {
|
|
5053
5209
|
try {
|
|
5054
5210
|
doc = readJson(docOrPath);
|
|
5055
|
-
harnessDir = dirname4(
|
|
5211
|
+
harnessDir = dirname4(resolve5(docOrPath));
|
|
5056
5212
|
} catch (error) {
|
|
5057
5213
|
return {
|
|
5058
5214
|
ok: false,
|
|
@@ -5119,8 +5275,8 @@ function validateStatusV2(docOrPath, opts = {}) {
|
|
|
5119
5275
|
for (const entry of doc.workflows) {
|
|
5120
5276
|
if (!isPlainObject4(entry) || typeof entry.dir !== "string")
|
|
5121
5277
|
continue;
|
|
5122
|
-
const relSnapshot =
|
|
5123
|
-
const snapshotPath =
|
|
5278
|
+
const relSnapshot = join7(entry.dir, WORKFLOW_SNAPSHOT_FILE);
|
|
5279
|
+
const snapshotPath = join7(harnessDir, relSnapshot);
|
|
5124
5280
|
const label = typeof entry.id === "string" ? entry.id : relSnapshot;
|
|
5125
5281
|
let physical;
|
|
5126
5282
|
try {
|
|
@@ -5154,8 +5310,10 @@ function validateStatusV2(docOrPath, opts = {}) {
|
|
|
5154
5310
|
return { ok: violations.length === 0, violations };
|
|
5155
5311
|
}
|
|
5156
5312
|
var validateStatus = validateStatusV2;
|
|
5157
|
-
function registerWorkflowEntryLocked(statusPath, entry) {
|
|
5313
|
+
async function registerWorkflowEntryLocked(statusPath, entry) {
|
|
5158
5314
|
const harnessDir = dirname4(statusPath);
|
|
5315
|
+
const store = getArtifactStore();
|
|
5316
|
+
assertFsStorePath(store, { kind: "status", key: "root" }, statusPath);
|
|
5159
5317
|
const current = readJson(statusPath);
|
|
5160
5318
|
const fresh = Object.keys(current).length === 0;
|
|
5161
5319
|
const doc = fresh ? { version: 2, updated_at: todayString(), workflows: [] } : current;
|
|
@@ -5173,7 +5331,7 @@ function registerWorkflowEntryLocked(statusPath, entry) {
|
|
|
5173
5331
|
if (!gate.ok) {
|
|
5174
5332
|
throw new Error(`refusing to write invalid status.json: ${gate.violations.map((v) => v.message).join("; ")}`);
|
|
5175
5333
|
}
|
|
5176
|
-
|
|
5334
|
+
await store.put({ kind: "status", key: "root", payload: doc });
|
|
5177
5335
|
return doc;
|
|
5178
5336
|
}
|
|
5179
5337
|
var PROJECT_ROADMAP_FILE = "roadmap.md";
|
|
@@ -5246,9 +5404,12 @@ async function appendProjectRegisterEntries(opts) {
|
|
|
5246
5404
|
if (opts.entries.length === 0) {
|
|
5247
5405
|
throw new Error("refusing to append residual entries: entries must not be empty");
|
|
5248
5406
|
}
|
|
5249
|
-
const registerPath =
|
|
5407
|
+
const registerPath = resolve6(join8(opts.projectDir, PROJECT_REGISTER_FILE));
|
|
5408
|
+
const projectKey = basename2(resolve6(opts.projectDir));
|
|
5409
|
+
const store = getArtifactStore();
|
|
5410
|
+
assertFsStorePath(store, { kind: "residuals", key: projectKey }, registerPath);
|
|
5250
5411
|
mkdirSync4(opts.projectDir, { recursive: true });
|
|
5251
|
-
return withStatusWriteLock(registerPath, () => {
|
|
5412
|
+
return withStatusWriteLock(registerPath, async () => {
|
|
5252
5413
|
const doc = readJson(registerPath);
|
|
5253
5414
|
const entriesMap = doc.entries ?? {};
|
|
5254
5415
|
let key = opts.basePlanKey;
|
|
@@ -5283,14 +5444,17 @@ async function appendProjectRegisterEntries(opts) {
|
|
|
5283
5444
|
if (!gate.ok) {
|
|
5284
5445
|
throw new Error(`refusing to write invalid project register: ${gate.violations.map((v) => v.message).join("; ")}`);
|
|
5285
5446
|
}
|
|
5286
|
-
|
|
5447
|
+
await store.put({ kind: "residuals", key: projectKey, payload: register });
|
|
5287
5448
|
return { ok: true, key };
|
|
5288
5449
|
});
|
|
5289
5450
|
}
|
|
5290
5451
|
async function closeProjectRegisterEntry(opts) {
|
|
5291
|
-
const registerPath =
|
|
5452
|
+
const registerPath = resolve6(join8(opts.projectDir, PROJECT_REGISTER_FILE));
|
|
5453
|
+
const projectKey = basename2(resolve6(opts.projectDir));
|
|
5454
|
+
const store = getArtifactStore();
|
|
5455
|
+
assertFsStorePath(store, { kind: "residuals", key: projectKey }, registerPath);
|
|
5292
5456
|
mkdirSync4(opts.projectDir, { recursive: true });
|
|
5293
|
-
return withStatusWriteLock(registerPath, () => {
|
|
5457
|
+
return withStatusWriteLock(registerPath, async () => {
|
|
5294
5458
|
const doc = readJson(registerPath);
|
|
5295
5459
|
const planEntries = doc.entries?.[opts.planKey];
|
|
5296
5460
|
if (!Array.isArray(planEntries)) {
|
|
@@ -5310,7 +5474,7 @@ async function closeProjectRegisterEntry(opts) {
|
|
|
5310
5474
|
if (!gate.ok) {
|
|
5311
5475
|
throw new Error(`refusing to write invalid project register: ${gate.violations.map((v) => v.message).join("; ")}`);
|
|
5312
5476
|
}
|
|
5313
|
-
|
|
5477
|
+
await store.put({ kind: "residuals", key: projectKey, payload: register });
|
|
5314
5478
|
return { ok: true };
|
|
5315
5479
|
});
|
|
5316
5480
|
}
|
|
@@ -5363,15 +5527,15 @@ function techDebtRollup(projectDir) {
|
|
|
5363
5527
|
const items = [];
|
|
5364
5528
|
let entries;
|
|
5365
5529
|
try {
|
|
5366
|
-
entries =
|
|
5530
|
+
entries = readdirSync4(projectDir, { withFileTypes: true });
|
|
5367
5531
|
} catch {
|
|
5368
5532
|
entries = [];
|
|
5369
5533
|
}
|
|
5370
5534
|
for (const project of entries) {
|
|
5371
5535
|
if (!project.isDirectory())
|
|
5372
5536
|
continue;
|
|
5373
|
-
const registerPath =
|
|
5374
|
-
if (!
|
|
5537
|
+
const registerPath = join8(projectDir, project.name, PROJECT_REGISTER_FILE);
|
|
5538
|
+
if (!existsSync5(registerPath))
|
|
5375
5539
|
continue;
|
|
5376
5540
|
let register;
|
|
5377
5541
|
try {
|
|
@@ -5407,19 +5571,19 @@ function techDebtRollup(projectDir) {
|
|
|
5407
5571
|
return { computed, stored, checks, overall };
|
|
5408
5572
|
}
|
|
5409
5573
|
function resolveHarnessDir(startDir = process.cwd(), opts = {}) {
|
|
5410
|
-
const start =
|
|
5574
|
+
const start = resolve7(startDir);
|
|
5411
5575
|
const explicit = opts.harnessDir ?? process.env.MSTAR_HARNESS_DIR;
|
|
5412
5576
|
if (explicit)
|
|
5413
|
-
return
|
|
5414
|
-
const boundary =
|
|
5577
|
+
return resolve7(start, explicit);
|
|
5578
|
+
const boundary = resolve7(start, opts.workspaceRoot ?? defaultWorkspaceRoot(start));
|
|
5415
5579
|
const rc = loadMstarc(start, boundary);
|
|
5416
5580
|
if (rc !== null && rc.config.harnessDir)
|
|
5417
|
-
return
|
|
5581
|
+
return resolve7(rc.dir, rc.config.harnessDir);
|
|
5418
5582
|
let dir = start;
|
|
5419
5583
|
for (;; ) {
|
|
5420
5584
|
if (!isAtOrBelow2(dir, boundary))
|
|
5421
5585
|
return null;
|
|
5422
|
-
for (const candidate of [
|
|
5586
|
+
for (const candidate of [join9(dir, ".mstar"), join9(dir, ".agents"), join9(dir, ".plans"), join9(dir, "plans")]) {
|
|
5423
5587
|
if (isDirectory(candidate))
|
|
5424
5588
|
return candidate;
|
|
5425
5589
|
}
|
|
@@ -5445,19 +5609,19 @@ function defaultWorkspaceRoot(startDir) {
|
|
|
5445
5609
|
if (segment && segment !== ".")
|
|
5446
5610
|
boundary = dirname5(boundary);
|
|
5447
5611
|
}
|
|
5448
|
-
return
|
|
5612
|
+
return resolve7(boundary);
|
|
5449
5613
|
} catch {}
|
|
5450
5614
|
return startDir;
|
|
5451
5615
|
}
|
|
5452
5616
|
function isAtOrBelow2(dir, root) {
|
|
5453
5617
|
const rel = relative2(root, dir);
|
|
5454
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
5618
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute4(rel);
|
|
5455
5619
|
}
|
|
5456
5620
|
function mstarcDirOverride(harnessDir, key) {
|
|
5457
|
-
const dir =
|
|
5621
|
+
const dir = resolve7(harnessDir);
|
|
5458
5622
|
const rc = loadMstarc(dir, dirname5(dir));
|
|
5459
5623
|
const declared = rc?.config[key];
|
|
5460
|
-
return declared ?
|
|
5624
|
+
return declared ? resolve7(rc.dir, declared) : null;
|
|
5461
5625
|
}
|
|
5462
5626
|
function resolveSpecsDir(harnessDir, opts = {}) {
|
|
5463
5627
|
const declared = mstarcDirOverride(harnessDir, "specsDir");
|
|
@@ -5466,20 +5630,20 @@ function resolveSpecsDir(harnessDir, opts = {}) {
|
|
|
5466
5630
|
mkdirSync5(declared, { recursive: true });
|
|
5467
5631
|
return declared;
|
|
5468
5632
|
}
|
|
5469
|
-
const harness =
|
|
5633
|
+
const harness = resolve7(harnessDir);
|
|
5470
5634
|
const repoRoot = dirname5(harness);
|
|
5471
5635
|
const candidates = [
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5636
|
+
join9(harness, "specs"),
|
|
5637
|
+
join9(repoRoot, "docs", "specs"),
|
|
5638
|
+
join9(repoRoot, "specs"),
|
|
5639
|
+
join9(harness, "designs"),
|
|
5640
|
+
join9(repoRoot, "designs")
|
|
5477
5641
|
];
|
|
5478
5642
|
for (const candidate of candidates) {
|
|
5479
5643
|
if (isDirectory(candidate) && hasFiles(candidate))
|
|
5480
5644
|
return candidate;
|
|
5481
5645
|
}
|
|
5482
|
-
const fallback =
|
|
5646
|
+
const fallback = join9(harness, "specs");
|
|
5483
5647
|
if (opts.create !== false)
|
|
5484
5648
|
mkdirSync5(fallback, { recursive: true });
|
|
5485
5649
|
return fallback;
|
|
@@ -5491,18 +5655,18 @@ function assertSafePathComponent(value, what) {
|
|
|
5491
5655
|
}
|
|
5492
5656
|
function resolveSddDir(harnessDir, planId) {
|
|
5493
5657
|
assertSafePathComponent(planId, "planId");
|
|
5494
|
-
const base =
|
|
5658
|
+
const base = resolve7(harnessDir);
|
|
5495
5659
|
const declared = mstarcDirOverride(base, "sddDir");
|
|
5496
|
-
const sddBase = declared !== null ? declared :
|
|
5497
|
-
return
|
|
5660
|
+
const sddBase = declared !== null ? declared : join9(base, "sdd");
|
|
5661
|
+
return join9(sddBase, planId);
|
|
5498
5662
|
}
|
|
5499
5663
|
function resolveHarnessSubdir(startDir, opts, key, fallback) {
|
|
5500
5664
|
const harness = resolveHarnessDir(startDir, opts);
|
|
5501
5665
|
if (harness === null) {
|
|
5502
|
-
throw new Error(`harness dir not found from ${
|
|
5666
|
+
throw new Error(`harness dir not found from ${resolve7(startDir)} \u2014 cannot resolve the ${fallback} dir (run \`mstar harness scaffold\`, pass opts.harnessDir, or set MSTAR_HARNESS_DIR)`);
|
|
5503
5667
|
}
|
|
5504
5668
|
const declared = mstarcDirOverride(harness, key);
|
|
5505
|
-
return declared !== null ? declared :
|
|
5669
|
+
return declared !== null ? declared : join9(resolve7(harness), fallback);
|
|
5506
5670
|
}
|
|
5507
5671
|
function resolveWorkflowDir(startDir = process.cwd(), opts = {}) {
|
|
5508
5672
|
return resolveHarnessSubdir(startDir, opts, "workflowDir", "workflows");
|
|
@@ -5517,13 +5681,13 @@ var EMPTY_STATUS_TEMPLATE = {
|
|
|
5517
5681
|
};
|
|
5518
5682
|
var SCAFFOLD_DIRS = ["plans", "iterations", "knowledge", "specs", "sdd"];
|
|
5519
5683
|
function resolveScaffoldDirs(root) {
|
|
5520
|
-
const start =
|
|
5521
|
-
const boundary =
|
|
5684
|
+
const start = resolve7(root);
|
|
5685
|
+
const boundary = resolve7(start, defaultWorkspaceRoot(start));
|
|
5522
5686
|
const rc = loadMstarc(start, boundary);
|
|
5523
5687
|
const explicit = process.env.MSTAR_HARNESS_DIR;
|
|
5524
|
-
const harnessDir = explicit ?
|
|
5688
|
+
const harnessDir = explicit ? resolve7(start, explicit) : rc !== null && rc.config.harnessDir ? resolve7(rc.dir, rc.config.harnessDir) : join9(start, ".mstar");
|
|
5525
5689
|
const declaredProjectDir = mstarcDirOverride(harnessDir, "projectDir");
|
|
5526
|
-
const projectDir = declaredProjectDir !== null ? declaredProjectDir :
|
|
5690
|
+
const projectDir = declaredProjectDir !== null ? declaredProjectDir : join9(harnessDir, "projects");
|
|
5527
5691
|
return { harnessDir, projectDir };
|
|
5528
5692
|
}
|
|
5529
5693
|
var ROADMAP_TEMPLATE = `---
|
|
@@ -5545,18 +5709,18 @@ var EMPTY_REGISTER_TEMPLATE = {
|
|
|
5545
5709
|
function scaffoldHarness(root) {
|
|
5546
5710
|
const { harnessDir, projectDir } = resolveScaffoldDirs(root);
|
|
5547
5711
|
for (const dir of SCAFFOLD_DIRS)
|
|
5548
|
-
mkdirSync5(
|
|
5549
|
-
const statusPath =
|
|
5712
|
+
mkdirSync5(join9(harnessDir, dir), { recursive: true });
|
|
5713
|
+
const statusPath = join9(harnessDir, "status.json");
|
|
5550
5714
|
if (Object.keys(readJson(statusPath)).length === 0)
|
|
5551
5715
|
writeJson(statusPath, EMPTY_STATUS_TEMPLATE);
|
|
5552
|
-
const defaultProjectDir =
|
|
5716
|
+
const defaultProjectDir = join9(projectDir, _DEFAULT_PROJECT);
|
|
5553
5717
|
mkdirSync5(defaultProjectDir, { recursive: true });
|
|
5554
|
-
const roadmapPath =
|
|
5555
|
-
if (!
|
|
5718
|
+
const roadmapPath = join9(defaultProjectDir, PROJECT_ROADMAP_FILE);
|
|
5719
|
+
if (!existsSync6(roadmapPath)) {
|
|
5556
5720
|
const created = new Date().toISOString().slice(0, 10);
|
|
5557
5721
|
writeFileSync3(roadmapPath, ROADMAP_TEMPLATE.replace("{created_at}", created), "utf8");
|
|
5558
5722
|
}
|
|
5559
|
-
const registerPath =
|
|
5723
|
+
const registerPath = join9(defaultProjectDir, PROJECT_REGISTER_FILE);
|
|
5560
5724
|
if (Object.keys(readJson(registerPath)).length === 0)
|
|
5561
5725
|
writeJson(registerPath, EMPTY_REGISTER_TEMPLATE);
|
|
5562
5726
|
return harnessDir;
|
|
@@ -5596,7 +5760,7 @@ function emitGitignoreSnippet(kind) {
|
|
|
5596
5760
|
function detectHarnessKind(harnessDir) {
|
|
5597
5761
|
if (!harnessDir)
|
|
5598
5762
|
return null;
|
|
5599
|
-
const name =
|
|
5763
|
+
const name = basename3(resolve7(harnessDir));
|
|
5600
5764
|
if (name === ".mstar")
|
|
5601
5765
|
return "mstar";
|
|
5602
5766
|
if (name === ".agents")
|
|
@@ -5612,9 +5776,9 @@ function isDirectory(dir) {
|
|
|
5612
5776
|
}
|
|
5613
5777
|
function hasFiles(dir) {
|
|
5614
5778
|
try {
|
|
5615
|
-
for (const entry of
|
|
5779
|
+
for (const entry of readdirSync5(dir, { withFileTypes: true })) {
|
|
5616
5780
|
if (entry.isDirectory()) {
|
|
5617
|
-
if (hasFiles(
|
|
5781
|
+
if (hasFiles(join9(dir, entry.name)))
|
|
5618
5782
|
return true;
|
|
5619
5783
|
} else if (entry.isFile()) {
|
|
5620
5784
|
return true;
|
|
@@ -5675,10 +5839,10 @@ function l1PreDispatchCheck(input, opts = {}) {
|
|
|
5675
5839
|
if (leaseWorkingBranch.trim() === "") {
|
|
5676
5840
|
violations.push(violation7("high", "worktree.l1.lease-branch-missing", `execution_lease.working_branch is empty for plan "${planId}"`, "record the lease working_branch before dispatch"));
|
|
5677
5841
|
}
|
|
5678
|
-
if (controlWorktreePath !== "" && leaseWorktreePath !== "" &&
|
|
5842
|
+
if (controlWorktreePath !== "" && leaseWorktreePath !== "" && resolve8(controlWorktreePath) === resolve8(leaseWorktreePath)) {
|
|
5679
5843
|
violations.push(violation7("critical", "worktree.l1.lease-equals-control", `execution_lease.worktree_path "${leaseWorktreePath}" equals metadata.control_worktree_path \u2014 the feature worktree MUST differ from the control worktree (L1 isolation; product edits never land in the control checkout)`, "use a distinct feature worktree for the plan (git worktree add <path> <branch>) and update the lease"));
|
|
5680
5844
|
}
|
|
5681
|
-
if (leaseWorktreePath !== "" && !
|
|
5845
|
+
if (leaseWorktreePath !== "" && !existsSync7(leaseWorktreePath)) {
|
|
5682
5846
|
violations.push(violation7("high", "worktree.l1.feature-missing", `feature worktree directory "${leaseWorktreePath}" does not exist for plan "${planId}"`, `create it before dispatch: git worktree add ${leaseWorktreePath} <working-branch>`));
|
|
5683
5847
|
} else if (leaseWorktreePath !== "" && leaseWorkingBranch !== "") {
|
|
5684
5848
|
const probe = probeBranch(leaseWorktreePath, opts);
|
|
@@ -5702,17 +5866,17 @@ function l2PreDispatchCheck(input, opts = {}) {
|
|
|
5702
5866
|
violations.push(violation7("high", "worktree.l2.track-invalid", `track ${index + 1} is missing worktreePath and/or workingBranch`, "fill both fields for every track"));
|
|
5703
5867
|
return;
|
|
5704
5868
|
}
|
|
5705
|
-
if (!
|
|
5869
|
+
if (!isAbsolute5(track.worktreePath)) {
|
|
5706
5870
|
violations.push(violation7("high", "worktree.l2.track-path-relative", `track ${index + 1} worktreePath "${track.worktreePath}" is not an absolute path \u2014 L2 tracks MUST use absolute worktree checkout paths (consistent with the lease validator's absolute worktree_path enforcement)`, `use an absolute path for track ${index + 1} (e.g. /Users/<you>/worktrees/<branch>)`));
|
|
5707
5871
|
return;
|
|
5708
5872
|
}
|
|
5709
|
-
const normalized =
|
|
5873
|
+
const normalized = resolve8(track.worktreePath);
|
|
5710
5874
|
if (seenPaths.has(normalized)) {
|
|
5711
5875
|
violations.push(violation7("high", "worktree.l2.track-path-collision", `duplicate worktreePath "${track.worktreePath}" across parallel tracks \u2014 L2 parallel-writable isolation requires a distinct absolute Worktree path per track (N parallel invokes \u2260 isolation)`, "give every parallel track its own git worktree checkout"));
|
|
5712
5876
|
return;
|
|
5713
5877
|
}
|
|
5714
5878
|
seenPaths.add(normalized);
|
|
5715
|
-
if (!
|
|
5879
|
+
if (!existsSync7(track.worktreePath)) {
|
|
5716
5880
|
violations.push(violation7("high", "worktree.l2.track-missing", `track worktree directory "${track.worktreePath}" does not exist`, `create it before dispatch: git worktree add ${track.worktreePath} ${track.workingBranch}`));
|
|
5717
5881
|
return;
|
|
5718
5882
|
}
|
|
@@ -5763,7 +5927,7 @@ function isFile2(file) {
|
|
|
5763
5927
|
return false;
|
|
5764
5928
|
}
|
|
5765
5929
|
}
|
|
5766
|
-
var GIT_CAPTURE_MAX_BYTES =
|
|
5930
|
+
var GIT_CAPTURE_MAX_BYTES = 67108864;
|
|
5767
5931
|
function gitOut(cwd, args) {
|
|
5768
5932
|
try {
|
|
5769
5933
|
return execFileSync3("git", args, {
|
|
@@ -5777,14 +5941,14 @@ function gitOut(cwd, args) {
|
|
|
5777
5941
|
}
|
|
5778
5942
|
}
|
|
5779
5943
|
function probeHarnessWithStatus(root) {
|
|
5780
|
-
if (isFile2(
|
|
5781
|
-
return
|
|
5782
|
-
if (isFile2(
|
|
5783
|
-
return
|
|
5784
|
-
if (hasWorkflowSnapshot(
|
|
5785
|
-
return
|
|
5786
|
-
if (hasWorkflowSnapshot(
|
|
5787
|
-
return
|
|
5944
|
+
if (isFile2(join10(root, ".mstar", "status.json")))
|
|
5945
|
+
return join10(root, ".mstar");
|
|
5946
|
+
if (isFile2(join10(root, ".agents", "status.json")))
|
|
5947
|
+
return join10(root, ".agents");
|
|
5948
|
+
if (hasWorkflowSnapshot(join10(root, ".mstar")))
|
|
5949
|
+
return join10(root, ".mstar");
|
|
5950
|
+
if (hasWorkflowSnapshot(join10(root, ".agents")))
|
|
5951
|
+
return join10(root, ".agents");
|
|
5788
5952
|
return null;
|
|
5789
5953
|
}
|
|
5790
5954
|
function hasWorkflowSnapshot(harnessDir) {
|
|
@@ -5792,13 +5956,13 @@ function hasWorkflowSnapshot(harnessDir) {
|
|
|
5792
5956
|
try {
|
|
5793
5957
|
workflowsDir = resolveWorkflowDir(harnessDir, { harnessDir });
|
|
5794
5958
|
} catch {
|
|
5795
|
-
workflowsDir =
|
|
5959
|
+
workflowsDir = join10(harnessDir, "workflows");
|
|
5796
5960
|
}
|
|
5797
5961
|
if (!isDirectory2(workflowsDir))
|
|
5798
5962
|
return false;
|
|
5799
5963
|
try {
|
|
5800
|
-
for (const entry of
|
|
5801
|
-
if (entry.isDirectory() && isFile2(
|
|
5964
|
+
for (const entry of readdirSync6(workflowsDir, { withFileTypes: true })) {
|
|
5965
|
+
if (entry.isDirectory() && isFile2(join10(workflowsDir, entry.name, "snapshot.json")))
|
|
5802
5966
|
return true;
|
|
5803
5967
|
}
|
|
5804
5968
|
} catch {
|
|
@@ -5811,14 +5975,14 @@ function isLinkedWorktree(root) {
|
|
|
5811
5975
|
const commonRaw = gitOut(root, ["rev-parse", "--git-common-dir"]);
|
|
5812
5976
|
if (gitDirRaw === null || commonRaw === null)
|
|
5813
5977
|
return false;
|
|
5814
|
-
const gitDir =
|
|
5815
|
-
const common2 =
|
|
5978
|
+
const gitDir = isAbsolute6(gitDirRaw) ? gitDirRaw : join10(root, gitDirRaw);
|
|
5979
|
+
const common2 = isAbsolute6(commonRaw) ? commonRaw : join10(root, commonRaw);
|
|
5816
5980
|
if (gitDir.includes("/.git/worktrees/") || gitDir.includes("/worktrees/"))
|
|
5817
5981
|
return true;
|
|
5818
5982
|
try {
|
|
5819
5983
|
const gdParent = realpathSync3(dirname6(gitDir));
|
|
5820
5984
|
const cmAbs = realpathSync3(common2);
|
|
5821
|
-
return
|
|
5985
|
+
return join10(gdParent, basename4(gitDir)) !== cmAbs && gitDir !== cmAbs;
|
|
5822
5986
|
} catch {
|
|
5823
5987
|
return false;
|
|
5824
5988
|
}
|
|
@@ -5826,7 +5990,7 @@ function isLinkedWorktree(root) {
|
|
|
5826
5990
|
function sddWorkspace(planId, opts = {}) {
|
|
5827
5991
|
if (!planId) {
|
|
5828
5992
|
throw new SddScriptError(`usage: mstar sdd workspace PLAN_ID [CONTROL_ROOT]
|
|
5829
|
-
|
|
5993
|
+
Set MSTAR_CONTROL_ROOT=<control_worktree_path> when running from a feature worktree.`, 2);
|
|
5830
5994
|
}
|
|
5831
5995
|
const cwd = opts.cwd ?? process.cwd();
|
|
5832
5996
|
const controlRoot = opts.controlRoot ?? (process.env.MSTAR_CONTROL_ROOT || undefined);
|
|
@@ -5842,35 +6006,35 @@ function sddWorkspace(planId, opts = {}) {
|
|
|
5842
6006
|
}
|
|
5843
6007
|
if (!controlRoot && isLinkedWorktree(root)) {
|
|
5844
6008
|
throw new SddScriptError(`mstar sdd workspace: linked worktree at ${root} has no {HARNESS_DIR}/status.json (default gitignore).
|
|
5845
|
-
|
|
5846
|
-
|
|
6009
|
+
Refusing to create a second SDD tree under the feature checkout.
|
|
6010
|
+
Re-run with MSTAR_CONTROL_ROOT=<control_worktree_path> or: mstar sdd workspace ${planId} <control_worktree_path>
|
|
5847
6011
|
` + ` See mstar-branch-worktree \u00abHarness path SSOT under default gitignore\u00bb.`, 1);
|
|
5848
6012
|
}
|
|
5849
6013
|
const harnessOverride = opts.harnessDir ?? (process.env.MSTAR_HARNESS_DIR || undefined);
|
|
5850
6014
|
let harnessDir;
|
|
5851
6015
|
if (harnessOverride) {
|
|
5852
|
-
harnessDir =
|
|
6016
|
+
harnessDir = resolve9(root, harnessOverride);
|
|
5853
6017
|
} else {
|
|
5854
6018
|
const rc = findMstarc(root, root);
|
|
5855
6019
|
const rcHarnessDir = rc !== null ? parseMstarc(readFileSync7(rc, "utf8")).harnessDir : undefined;
|
|
5856
6020
|
if (rcHarnessDir) {
|
|
5857
|
-
harnessDir =
|
|
6021
|
+
harnessDir = resolve9(rc !== null ? dirname6(rc) : root, rcHarnessDir);
|
|
5858
6022
|
} else {
|
|
5859
6023
|
const probed = probeHarnessWithStatus(root);
|
|
5860
6024
|
if (probed) {
|
|
5861
6025
|
harnessDir = probed;
|
|
5862
|
-
} else if (isDirectory2(
|
|
5863
|
-
harnessDir =
|
|
5864
|
-
} else if (isDirectory2(
|
|
5865
|
-
harnessDir =
|
|
6026
|
+
} else if (isDirectory2(join10(root, ".mstar"))) {
|
|
6027
|
+
harnessDir = join10(root, ".mstar");
|
|
6028
|
+
} else if (isDirectory2(join10(root, ".agents"))) {
|
|
6029
|
+
harnessDir = join10(root, ".agents");
|
|
5866
6030
|
} else {
|
|
5867
|
-
harnessDir =
|
|
6031
|
+
harnessDir = join10(root, ".mstar");
|
|
5868
6032
|
}
|
|
5869
6033
|
}
|
|
5870
6034
|
}
|
|
5871
6035
|
const sddDir = resolveSddDir(harnessDir, planId);
|
|
5872
6036
|
mkdirSync6(sddDir, { recursive: true });
|
|
5873
|
-
writeFileSync4(
|
|
6037
|
+
writeFileSync4(join10(sddDir, ".gitignore"), `*
|
|
5874
6038
|
`);
|
|
5875
6039
|
return realpathSync3(sddDir);
|
|
5876
6040
|
}
|
|
@@ -5893,7 +6057,7 @@ function taskBrief(planFile, taskN, outFile, opts = {}) {
|
|
|
5893
6057
|
throw new SddScriptError("mstar sdd task-brief: set SDD_DIR or pass OUTFILE (run mstar sdd workspace PLAN_ID first)", 2);
|
|
5894
6058
|
}
|
|
5895
6059
|
mkdirSync6(sddDir, { recursive: true });
|
|
5896
|
-
out =
|
|
6060
|
+
out = join10(sddDir, `task-${taskN}-brief.md`);
|
|
5897
6061
|
}
|
|
5898
6062
|
const records = content.endsWith(`
|
|
5899
6063
|
`) ? content.split(`
|
|
@@ -5946,7 +6110,7 @@ function reviewPackage(base, head, outFile, opts = {}) {
|
|
|
5946
6110
|
mkdirSync6(sddDir, { recursive: true });
|
|
5947
6111
|
const shortBase = gitOut(cwd, ["rev-parse", "--short", base]) ?? base;
|
|
5948
6112
|
const shortHead = gitOut(cwd, ["rev-parse", "--short", head]) ?? head;
|
|
5949
|
-
out =
|
|
6113
|
+
out = join10(sddDir, `review-${shortBase}..${shortHead}.diff`);
|
|
5950
6114
|
}
|
|
5951
6115
|
const run = (args) => execFileSync3("git", args, { cwd, maxBuffer: GIT_CAPTURE_MAX_BYTES });
|
|
5952
6116
|
const parts = [
|
|
@@ -6634,7 +6798,7 @@ function scanSecrets(files) {
|
|
|
6634
6798
|
unreadableFiles++;
|
|
6635
6799
|
continue;
|
|
6636
6800
|
}
|
|
6637
|
-
const base =
|
|
6801
|
+
const base = basename5(file);
|
|
6638
6802
|
for (const entry of NEVER_COMMIT_FILENAMES) {
|
|
6639
6803
|
if (entry.re.test(base))
|
|
6640
6804
|
findings.push({ file, line: 1, type: entry.type });
|
|
@@ -6686,12 +6850,12 @@ var LOCKFILE_NAMES = [
|
|
|
6686
6850
|
function rootLockfiles(root) {
|
|
6687
6851
|
let entries;
|
|
6688
6852
|
try {
|
|
6689
|
-
entries =
|
|
6853
|
+
entries = readdirSync8(root, { withFileTypes: true });
|
|
6690
6854
|
} catch {
|
|
6691
6855
|
return [];
|
|
6692
6856
|
}
|
|
6693
6857
|
const names = new Set(LOCKFILE_NAMES);
|
|
6694
|
-
const present = entries.filter((entry) => entry.isFile() && names.has(entry.name)).map((entry) =>
|
|
6858
|
+
const present = entries.filter((entry) => entry.isFile() && names.has(entry.name)).map((entry) => join12(root, entry.name));
|
|
6695
6859
|
if (present.length === 0)
|
|
6696
6860
|
return [];
|
|
6697
6861
|
try {
|
|
@@ -6700,7 +6864,7 @@ function rootLockfiles(root) {
|
|
|
6700
6864
|
encoding: "utf8",
|
|
6701
6865
|
stdio: ["ignore", "pipe", "ignore"]
|
|
6702
6866
|
}).split("\x00").filter((f) => f !== ""));
|
|
6703
|
-
return present.filter((p) => tracked.has(
|
|
6867
|
+
return present.filter((p) => tracked.has(basename5(p)));
|
|
6704
6868
|
} catch {
|
|
6705
6869
|
return present;
|
|
6706
6870
|
}
|
|
@@ -6716,17 +6880,17 @@ function supplyChainChecks(repoRoot) {
|
|
|
6716
6880
|
findings.push({ kind: "lockfile-duplicate", file: lockfiles.map((f) => f.replace(`${repoRoot}/`, "")).join(", ") });
|
|
6717
6881
|
violations.push(violation9("medium", "audit.supply.lockfile-duplicate", `multiple lockfiles at ${repoRoot}: ${lockfiles.join(", ")}`, "keep exactly one lockfile per package manager"));
|
|
6718
6882
|
}
|
|
6719
|
-
const workflowsDir =
|
|
6883
|
+
const workflowsDir = join12(repoRoot, ".github", "workflows");
|
|
6720
6884
|
let wfEntries = [];
|
|
6721
6885
|
try {
|
|
6722
|
-
wfEntries =
|
|
6886
|
+
wfEntries = readdirSync8(workflowsDir, { withFileTypes: true });
|
|
6723
6887
|
} catch {
|
|
6724
6888
|
wfEntries = [];
|
|
6725
6889
|
}
|
|
6726
6890
|
for (const entry of wfEntries) {
|
|
6727
6891
|
if (!entry.isFile() || !/\.(?:ya?ml)$/.test(entry.name))
|
|
6728
6892
|
continue;
|
|
6729
|
-
const wfPath =
|
|
6893
|
+
const wfPath = join12(workflowsDir, entry.name);
|
|
6730
6894
|
const relPath = `.github/workflows/${entry.name}`;
|
|
6731
6895
|
let text;
|
|
6732
6896
|
try {
|
|
@@ -6883,9 +7047,9 @@ function scaffoldAuditPlan(outDir, findings, options = {}) {
|
|
|
6883
7047
|
const date = options.date ?? new Date().toISOString().slice(0, 10);
|
|
6884
7048
|
const plannedAt = options.plannedAt ?? { commit: options.repoShortSha ?? "unknown", date };
|
|
6885
7049
|
mkdirSync8(outDir, { recursive: true });
|
|
6886
|
-
const existingReadme =
|
|
6887
|
-
const carried =
|
|
6888
|
-
const existing =
|
|
7050
|
+
const existingReadme = join12(outDir, "README.md");
|
|
7051
|
+
const carried = existsSync8(existingReadme) ? extractSecurityDispositionSections(readFileSync9(existingReadme, "utf8")) : { needsVerification: [], hardeningChecked: [] };
|
|
7052
|
+
const existing = readdirSync8(outDir).filter((f) => /^\d{3}-.*\.md$/.test(f));
|
|
6889
7053
|
let next = existing.reduce((max, f) => Math.max(max, Number(f.slice(0, 3))), 0) + 1;
|
|
6890
7054
|
const redactedFindings = findings.map(redactFinding);
|
|
6891
7055
|
const written = [];
|
|
@@ -6901,13 +7065,13 @@ function scaffoldAuditPlan(outDir, findings, options = {}) {
|
|
|
6901
7065
|
}
|
|
6902
7066
|
usedSlugs.add(slug);
|
|
6903
7067
|
const file = `${num}-${slug}.md`;
|
|
6904
|
-
writeFileSync6(
|
|
7068
|
+
writeFileSync6(join12(outDir, file), renderPlanFile(finding, plannedAt));
|
|
6905
7069
|
written.push(file);
|
|
6906
7070
|
next++;
|
|
6907
7071
|
}
|
|
6908
7072
|
const all = [...existing, ...written].sort();
|
|
6909
7073
|
const rows = all.map((file) => {
|
|
6910
|
-
const summary = readPlanFileSummary(
|
|
7074
|
+
const summary = readPlanFileSummary(join12(outDir, file));
|
|
6911
7075
|
const fields = summary.fields;
|
|
6912
7076
|
return {
|
|
6913
7077
|
num: file.slice(0, 3),
|
|
@@ -6941,7 +7105,7 @@ function scaffoldAuditPlan(outDir, findings, options = {}) {
|
|
|
6941
7105
|
});
|
|
6942
7106
|
const needsVerificationLines = options.needsVerification !== undefined ? options.needsVerification.map((nv) => `- ${escapeCell(redactText(nv.lead))}: ${escapeCell(redactText(nv.how))}${nv.evidence ? ` (${escapeCell(redactText(nv.evidence))})` : ""}`) : carried.needsVerification;
|
|
6943
7107
|
const hardeningCheckedLines = options.hardeningChecked !== undefined ? options.hardeningChecked.map((hc) => `- ${hc.kind}: ${escapeCell(redactText(hc.text))}`) : carried.hardeningChecked;
|
|
6944
|
-
writeFileSync6(
|
|
7108
|
+
writeFileSync6(join12(outDir, "README.md"), renderIndex({
|
|
6945
7109
|
date,
|
|
6946
7110
|
repoName: options.repoName ?? "repo",
|
|
6947
7111
|
repoShortSha: options.repoShortSha ?? "unknown",
|
|
@@ -6950,7 +7114,7 @@ function scaffoldAuditPlan(outDir, findings, options = {}) {
|
|
|
6950
7114
|
needsVerification: needsVerificationLines,
|
|
6951
7115
|
hardeningChecked: hardeningCheckedLines
|
|
6952
7116
|
}));
|
|
6953
|
-
return { outDir:
|
|
7117
|
+
return { outDir: resolve11(outDir), date, files: written, nextNumber: next };
|
|
6954
7118
|
}
|
|
6955
7119
|
async function promoteAuditPlans(outDir, selected, options) {
|
|
6956
7120
|
if (selected.length === 0) {
|
|
@@ -6959,19 +7123,19 @@ async function promoteAuditPlans(outDir, selected, options) {
|
|
|
6959
7123
|
if (typeof options.harnessDir !== "string" || options.harnessDir.trim() === "") {
|
|
6960
7124
|
throw new Error("promoteAuditPlans: options.harnessDir is required (must contain status.json + workflows/)");
|
|
6961
7125
|
}
|
|
6962
|
-
const workflowId = options.workflowId ??
|
|
7126
|
+
const workflowId = options.workflowId ?? basename5(resolve11(outDir));
|
|
6963
7127
|
assertSafePathComponent(workflowId, "workflow id");
|
|
6964
|
-
const harnessDir =
|
|
6965
|
-
const statusPath =
|
|
6966
|
-
const workflowDir =
|
|
6967
|
-
const snapshotPath =
|
|
7128
|
+
const harnessDir = resolve11(options.harnessDir);
|
|
7129
|
+
const statusPath = join12(harnessDir, "status.json");
|
|
7130
|
+
const workflowDir = join12(harnessDir, "workflows", workflowId);
|
|
7131
|
+
const snapshotPath = join12(workflowDir, WORKFLOW_SNAPSHOT_FILE);
|
|
6968
7132
|
const planFiles = resolveSelectedPlanFiles(outDir, selected);
|
|
6969
7133
|
const indexRows = readExecutionOrderIndex(outDir);
|
|
6970
7134
|
const plans = planFiles.map((planFile) => {
|
|
6971
7135
|
const stem = planFile.replace(/\.md$/, "");
|
|
6972
7136
|
const num = stem.slice(0, 3);
|
|
6973
7137
|
const indexRow = indexRows.get(num);
|
|
6974
|
-
const title = indexRow?.title ?? readPlanFileSummary(
|
|
7138
|
+
const title = indexRow?.title ?? readPlanFileSummary(join12(outDir, planFile)).title;
|
|
6975
7139
|
return {
|
|
6976
7140
|
id: stem,
|
|
6977
7141
|
title,
|
|
@@ -6999,18 +7163,18 @@ async function promoteAuditPlans(outDir, selected, options) {
|
|
|
6999
7163
|
if (!entryGate.ok) {
|
|
7000
7164
|
throw new Error(`refusing to register invalid workflow entry: ${entryGate.violations.map((v) => v.message).join("; ")}`);
|
|
7001
7165
|
}
|
|
7002
|
-
await withStatusWriteLock(statusPath, () => {
|
|
7003
|
-
if (
|
|
7004
|
-
throw new Error(`refusing to promote audit plans: workflow ${JSON.stringify(workflowId)} already exists
|
|
7166
|
+
await withStatusWriteLock(statusPath, async () => {
|
|
7167
|
+
if (existsSync8(snapshotPath)) {
|
|
7168
|
+
throw new Error(`refusing to promote audit plans: workflow ${JSON.stringify(workflowId)} already exists (snapshot at ${snapshotPath}) \u2014 re-promote would drop its registered plan rows; ` + `remove that workflow before promoting again`);
|
|
7005
7169
|
}
|
|
7006
7170
|
mkdirSync8(workflowDir, { recursive: true });
|
|
7007
7171
|
try {
|
|
7008
7172
|
writeJson(snapshotPath, snapshot);
|
|
7009
|
-
registerWorkflowEntryLocked(statusPath, entry);
|
|
7173
|
+
await registerWorkflowEntryLocked(statusPath, entry);
|
|
7010
7174
|
} catch (error) {
|
|
7011
7175
|
rmSync(snapshotPath, { force: true });
|
|
7012
7176
|
try {
|
|
7013
|
-
if (
|
|
7177
|
+
if (readdirSync8(workflowDir).length === 0) {
|
|
7014
7178
|
rmdirSync2(workflowDir);
|
|
7015
7179
|
}
|
|
7016
7180
|
} catch {}
|
|
@@ -7021,7 +7185,7 @@ async function promoteAuditPlans(outDir, selected, options) {
|
|
|
7021
7185
|
return { workflowId, snapshotPath };
|
|
7022
7186
|
}
|
|
7023
7187
|
function resolveSelectedPlanFiles(outDir, selected) {
|
|
7024
|
-
const files =
|
|
7188
|
+
const files = readdirSync8(outDir).filter((f) => /^\d{3}-.*\.md$/.test(f)).sort();
|
|
7025
7189
|
const byNum = new Map;
|
|
7026
7190
|
const byStem = new Map;
|
|
7027
7191
|
for (const file of files) {
|
|
@@ -7036,7 +7200,7 @@ function resolveSelectedPlanFiles(outDir, selected) {
|
|
|
7036
7200
|
for (const id of selected) {
|
|
7037
7201
|
const file = byNum.get(id) ?? byStem.get(id) ?? byStem.get(id.replace(/\.md$/, ""));
|
|
7038
7202
|
if (file === undefined) {
|
|
7039
|
-
throw new Error(`promoteAuditPlans: selected plan ${JSON.stringify(id)} does not match any NNN-*.md file in ${
|
|
7203
|
+
throw new Error(`promoteAuditPlans: selected plan ${JSON.stringify(id)} does not match any NNN-*.md file in ${resolve11(outDir)}`);
|
|
7040
7204
|
}
|
|
7041
7205
|
if (!seen.has(file)) {
|
|
7042
7206
|
seen.add(file);
|
|
@@ -7046,7 +7210,7 @@ function resolveSelectedPlanFiles(outDir, selected) {
|
|
|
7046
7210
|
return resolved;
|
|
7047
7211
|
}
|
|
7048
7212
|
function readExecutionOrderIndex(outDir) {
|
|
7049
|
-
const readmePath =
|
|
7213
|
+
const readmePath = join12(outDir, "README.md");
|
|
7050
7214
|
let text;
|
|
7051
7215
|
try {
|
|
7052
7216
|
text = readFileSync9(readmePath, "utf8");
|
|
@@ -7075,7 +7239,7 @@ function readExecutionOrderIndex(outDir) {
|
|
|
7075
7239
|
return rows;
|
|
7076
7240
|
}
|
|
7077
7241
|
function planFileRel(outDir, planFile) {
|
|
7078
|
-
const resolved =
|
|
7242
|
+
const resolved = resolve11(outDir);
|
|
7079
7243
|
const parts = resolved.split(sep3);
|
|
7080
7244
|
const plansIdx = parts.lastIndexOf("plans");
|
|
7081
7245
|
if (plansIdx >= 0) {
|
|
@@ -7327,14 +7491,14 @@ function collectKnowledgeDocs(dir) {
|
|
|
7327
7491
|
const current = stack.pop();
|
|
7328
7492
|
let entries;
|
|
7329
7493
|
try {
|
|
7330
|
-
entries =
|
|
7494
|
+
entries = readdirSync9(current, { withFileTypes: true });
|
|
7331
7495
|
} catch {
|
|
7332
7496
|
continue;
|
|
7333
7497
|
}
|
|
7334
7498
|
for (const entry of entries) {
|
|
7335
7499
|
if (entry.isSymbolicLink())
|
|
7336
7500
|
continue;
|
|
7337
|
-
const full =
|
|
7501
|
+
const full = join13(current, entry.name);
|
|
7338
7502
|
if (entry.isDirectory()) {
|
|
7339
7503
|
stack.push(full);
|
|
7340
7504
|
} else if (entry.name.endsWith(".md") && entry.name !== "README.md" && entry.name !== "index.md") {
|
|
@@ -7354,8 +7518,8 @@ function normalizeIndexRef(cell) {
|
|
|
7354
7518
|
}
|
|
7355
7519
|
function assertIndexRows(knowledgeDir) {
|
|
7356
7520
|
const violations = [];
|
|
7357
|
-
const readmePath =
|
|
7358
|
-
if (!
|
|
7521
|
+
const readmePath = join13(knowledgeDir, "README.md");
|
|
7522
|
+
if (!existsSync9(readmePath)) {
|
|
7359
7523
|
violations.push(violation10("medium", "compound.index.missing-readme", `missing ${readmePath} \u2014 the knowledge index is required (mstar-compound Phase 6: every doc gets a README.md row)`, "create knowledge/README.md with a Document / Source Plan / Description / Status table"));
|
|
7360
7524
|
return { ok: false, violations };
|
|
7361
7525
|
}
|
|
@@ -7379,12 +7543,12 @@ function assertIndexRows(knowledgeDir) {
|
|
|
7379
7543
|
return { ok: violations.length === 0, violations };
|
|
7380
7544
|
}
|
|
7381
7545
|
function isFileLikeRoot(root) {
|
|
7382
|
-
return /^[^.]*\.[A-Za-z0-9]{1,10}$/.test(
|
|
7546
|
+
return /^[^.]*\.[A-Za-z0-9]{1,10}$/.test(basename6(root));
|
|
7383
7547
|
}
|
|
7384
7548
|
function scopeGuard(path2, allowedRoots) {
|
|
7385
|
-
const resolved =
|
|
7549
|
+
const resolved = resolve12(path2);
|
|
7386
7550
|
for (const root of allowedRoots) {
|
|
7387
|
-
const r =
|
|
7551
|
+
const r = resolve12(root);
|
|
7388
7552
|
if (isFileLikeRoot(r)) {
|
|
7389
7553
|
if (resolved === r)
|
|
7390
7554
|
return { ok: true, violations: [] };
|
|
@@ -7685,8 +7849,8 @@ function validateRoleMapping(rolesDir, options = {}) {
|
|
|
7685
7849
|
const violations = [];
|
|
7686
7850
|
const referenceById = new Map(mapping.map((m) => [m.agentId, m.reference]));
|
|
7687
7851
|
for (const { agentId, reference } of mapping) {
|
|
7688
|
-
if (!
|
|
7689
|
-
violations.push(violation12("medium", "roles.mapping.reference.missing", `role "${agentId}" maps to ${reference} which does not exist under ${rolesDir} (mstar-roles \u00a7 Role Reference Mapping)`, `create ${
|
|
7852
|
+
if (!existsSync10(join14(rolesDir, reference))) {
|
|
7853
|
+
violations.push(violation12("medium", "roles.mapping.reference.missing", `role "${agentId}" maps to ${reference} which does not exist under ${rolesDir} (mstar-roles \u00a7 Role Reference Mapping)`, `create ${join14(rolesDir, reference)} or fix the mapping row`));
|
|
7690
7854
|
}
|
|
7691
7855
|
}
|
|
7692
7856
|
for (const { family, memberIds } of families) {
|
|
@@ -7912,9 +8076,135 @@ function computePrTally(input) {
|
|
|
7912
8076
|
const scorePct = Math.max(0, 100 - 40 * mustFix - 15 * shouldFix - 3 * nit - 10 * unverified);
|
|
7913
8077
|
const verdict = mustFix >= 1 ? "blocked" : shouldFix >= 1 ? "needs fixes" : "ship it";
|
|
7914
8078
|
const chatHeader = `${verdict} \u00b7 ${scorePct}%
|
|
7915
|
-
|
|
8079
|
+
must-fix=${mustFix} should-fix=${shouldFix} nit=${nit} unverified=${unverified}`;
|
|
7916
8080
|
return { verdict, scorePct, tally: { mustFix, shouldFix, nit, unverified }, chatHeader };
|
|
7917
8081
|
}
|
|
8082
|
+
var REVIEW_SCHEMA_ID = "mstar.review/v1";
|
|
8083
|
+
var INSPECTOR_VERDICTS = ["comment", "request_changes", "approve"];
|
|
8084
|
+
var INSPECTOR_SEVERITIES = ["critical", "warning", "suggestion", "info"];
|
|
8085
|
+
function isPlainObject7(value) {
|
|
8086
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8087
|
+
}
|
|
8088
|
+
var TALLY_COUNT_KEYS = ["mustFix", "shouldFix", "nit", "unverified"];
|
|
8089
|
+
function checkProvidedTallyShape(tally, violations) {
|
|
8090
|
+
if (typeof tally.verdict !== "string" || !PR_VERDICTS.includes(tally.verdict)) {
|
|
8091
|
+
violations.push(violation14("high", "review.tally-malformed", `tally.verdict "${String(tally.verdict)}" is not one of ${JSON.stringify(PR_VERDICTS)}`, `use one of: ${PR_VERDICTS.join(" | ")}`));
|
|
8092
|
+
}
|
|
8093
|
+
if (typeof tally.scorePct !== "number" || !Number.isInteger(tally.scorePct) || tally.scorePct < 0 || tally.scorePct > 100) {
|
|
8094
|
+
violations.push(violation14("high", "review.tally-malformed", `tally.scorePct must be an integer in [0, 100] - got ${String(tally.scorePct)}`));
|
|
8095
|
+
}
|
|
8096
|
+
if (!isPlainObject7(tally.tally)) {
|
|
8097
|
+
violations.push(violation14("high", "review.tally-malformed", "tally.tally must be an object carrying the four class counts"));
|
|
8098
|
+
} else {
|
|
8099
|
+
for (const key of TALLY_COUNT_KEYS) {
|
|
8100
|
+
const count = tally.tally[key];
|
|
8101
|
+
if (typeof count !== "number" || !Number.isInteger(count) || count < 0) {
|
|
8102
|
+
violations.push(violation14("high", "review.tally-malformed", `tally.tally.${key} must be a non-negative integer - got ${String(count)}`));
|
|
8103
|
+
}
|
|
8104
|
+
}
|
|
8105
|
+
}
|
|
8106
|
+
if (typeof tally.chatHeader !== "string") {
|
|
8107
|
+
violations.push(violation14("high", "review.tally-malformed", `tally.chatHeader must be a string - got ${typeof tally.chatHeader}`));
|
|
8108
|
+
}
|
|
8109
|
+
}
|
|
8110
|
+
function validateMstarReviewV1(doc) {
|
|
8111
|
+
const violations = [];
|
|
8112
|
+
if (!isPlainObject7(doc)) {
|
|
8113
|
+
return {
|
|
8114
|
+
ok: false,
|
|
8115
|
+
violations: [violation14("high", "review.not-object", "review document must be a JSON object")]
|
|
8116
|
+
};
|
|
8117
|
+
}
|
|
8118
|
+
if (doc.schema === undefined) {
|
|
8119
|
+
violations.push(violation14("high", "review.missing-schema", "missing required field: schema"));
|
|
8120
|
+
} else if (doc.schema !== REVIEW_SCHEMA_ID) {
|
|
8121
|
+
violations.push(violation14("high", "review.invalid-schema", `schema "${String(doc.schema)}" is not "${REVIEW_SCHEMA_ID}"`));
|
|
8122
|
+
}
|
|
8123
|
+
if (doc.verdict === undefined) {
|
|
8124
|
+
violations.push(violation14("high", "review.missing-verdict", "missing required field: verdict"));
|
|
8125
|
+
} else if (typeof doc.verdict !== "string") {
|
|
8126
|
+
violations.push(violation14("high", "review.invalid-verdict", `verdict must be a string - got ${typeof doc.verdict}`));
|
|
8127
|
+
} else if (INSPECTOR_VERDICTS.includes(doc.verdict)) {
|
|
8128
|
+
violations.push(violation14("high", "review.inspector-vocab", `verdict "${doc.verdict}" is inspector M1 vocab - harness verdicts are ${JSON.stringify(PR_VERDICTS)}`, `use one of: ${PR_VERDICTS.join(" | ")}`));
|
|
8129
|
+
} else if (!PR_VERDICTS.includes(doc.verdict)) {
|
|
8130
|
+
violations.push(violation14("high", "review.invalid-verdict", `verdict "${doc.verdict}" is not one of ${JSON.stringify(PR_VERDICTS)}`, `use one of: ${PR_VERDICTS.join(" | ")}`));
|
|
8131
|
+
}
|
|
8132
|
+
if (doc.summary_md === undefined || typeof doc.summary_md !== "string" || doc.summary_md.trim() === "") {
|
|
8133
|
+
violations.push(violation14("high", "review.missing-summary", "summary_md must be a non-empty string"));
|
|
8134
|
+
}
|
|
8135
|
+
if (doc.findings === undefined || !Array.isArray(doc.findings)) {
|
|
8136
|
+
violations.push(violation14("high", "review.findings-not-array", "findings must be an array"));
|
|
8137
|
+
} else {
|
|
8138
|
+
doc.findings.forEach((finding, index) => {
|
|
8139
|
+
if (!isPlainObject7(finding)) {
|
|
8140
|
+
violations.push(violation14("high", "review.invalid-finding", `findings[${index}] must be an object`));
|
|
8141
|
+
return;
|
|
8142
|
+
}
|
|
8143
|
+
if (finding.severity !== undefined) {
|
|
8144
|
+
violations.push(violation14("high", "review.inspector-vocab", `findings[${index}] carries inspector M1 field "severity" - harness merge classes are ${JSON.stringify(MERGE_CLASSES)}`, "use mergeClass: must-fix | should-fix | nit"));
|
|
8145
|
+
}
|
|
8146
|
+
if (finding.mergeClass === undefined) {
|
|
8147
|
+
violations.push(violation14("high", "review.missing-merge-class", `findings[${index}] missing required field: mergeClass`));
|
|
8148
|
+
} else if (typeof finding.mergeClass !== "string") {
|
|
8149
|
+
violations.push(violation14("high", "review.invalid-merge-class", `findings[${index}].mergeClass must be a string - got ${typeof finding.mergeClass}`));
|
|
8150
|
+
} else if (INSPECTOR_SEVERITIES.includes(finding.mergeClass)) {
|
|
8151
|
+
violations.push(violation14("high", "review.inspector-vocab", `findings[${index}].mergeClass "${finding.mergeClass}" is inspector M1 severity vocab - harness merge classes are ${JSON.stringify(MERGE_CLASSES)}`, `use one of: ${MERGE_CLASSES.join(" | ")}`));
|
|
8152
|
+
} else if (!MERGE_CLASSES.includes(finding.mergeClass)) {
|
|
8153
|
+
violations.push(violation14("high", "review.invalid-merge-class", `findings[${index}].mergeClass "${finding.mergeClass}" is not one of ${JSON.stringify(MERGE_CLASSES)}`, `use one of: ${MERGE_CLASSES.join(" | ")}`));
|
|
8154
|
+
}
|
|
8155
|
+
if (finding.title === undefined || typeof finding.title !== "string" || finding.title.trim() === "") {
|
|
8156
|
+
violations.push(violation14("high", "review.empty-title", `findings[${index}].title must be a non-empty string`));
|
|
8157
|
+
}
|
|
8158
|
+
if (finding.body === undefined || typeof finding.body !== "string" || finding.body.trim() === "") {
|
|
8159
|
+
violations.push(violation14("high", "review.empty-body", `findings[${index}].body must be a non-empty string`));
|
|
8160
|
+
}
|
|
8161
|
+
if (finding.category !== undefined && typeof finding.category !== "string") {
|
|
8162
|
+
violations.push(violation14("high", "review.invalid-category", `findings[${index}].category must be a string`));
|
|
8163
|
+
}
|
|
8164
|
+
if (finding.file_path !== undefined && finding.file_path !== null && typeof finding.file_path !== "string") {
|
|
8165
|
+
violations.push(violation14("high", "review.invalid-file-path", `findings[${index}].file_path must be a string or null`));
|
|
8166
|
+
}
|
|
8167
|
+
if (finding.line_start !== undefined && finding.line_start !== null && typeof finding.line_start !== "number") {
|
|
8168
|
+
violations.push(violation14("high", "review.invalid-line-start", `findings[${index}].line_start must be a number or null`));
|
|
8169
|
+
}
|
|
8170
|
+
if (finding.line_end !== undefined && finding.line_end !== null && typeof finding.line_end !== "number") {
|
|
8171
|
+
violations.push(violation14("high", "review.invalid-line-end", `findings[${index}].line_end must be a number or null`));
|
|
8172
|
+
}
|
|
8173
|
+
if (finding.fingerprint_hint !== undefined && typeof finding.fingerprint_hint !== "string") {
|
|
8174
|
+
violations.push(violation14("high", "review.invalid-fingerprint-hint", `findings[${index}].fingerprint_hint must be a string`));
|
|
8175
|
+
}
|
|
8176
|
+
});
|
|
8177
|
+
}
|
|
8178
|
+
if (doc.tally !== undefined) {
|
|
8179
|
+
if (!isPlainObject7(doc.tally)) {
|
|
8180
|
+
violations.push(violation14("high", "review.invalid-tally", "tally must be a PrTallyResult object"));
|
|
8181
|
+
} else {
|
|
8182
|
+
checkProvidedTallyShape(doc.tally, violations);
|
|
8183
|
+
if (doc.tally.verdict !== doc.verdict) {
|
|
8184
|
+
violations.push(violation14("high", "review.verdict-tally-mismatch", `tally.verdict "${String(doc.tally.verdict)}" does not equal the top-level verdict "${String(doc.verdict)}" - the envelope verdict and tally must agree (consistency rule)`));
|
|
8185
|
+
}
|
|
8186
|
+
}
|
|
8187
|
+
}
|
|
8188
|
+
if (doc.target !== undefined) {
|
|
8189
|
+
if (!isPlainObject7(doc.target)) {
|
|
8190
|
+
violations.push(violation14("high", "review.invalid-target", "target must be an object"));
|
|
8191
|
+
} else {
|
|
8192
|
+
if (doc.target.owner !== undefined && typeof doc.target.owner !== "string") {
|
|
8193
|
+
violations.push(violation14("high", "review.invalid-target", "target.owner must be a string"));
|
|
8194
|
+
}
|
|
8195
|
+
if (doc.target.repo !== undefined && typeof doc.target.repo !== "string") {
|
|
8196
|
+
violations.push(violation14("high", "review.invalid-target", "target.repo must be a string"));
|
|
8197
|
+
}
|
|
8198
|
+
if (doc.target.pr !== undefined && typeof doc.target.pr !== "number") {
|
|
8199
|
+
violations.push(violation14("high", "review.invalid-target", "target.pr must be a number"));
|
|
8200
|
+
}
|
|
8201
|
+
if (doc.target.head_sha !== undefined && typeof doc.target.head_sha !== "string") {
|
|
8202
|
+
violations.push(violation14("high", "review.invalid-target", "target.head_sha must be a string"));
|
|
8203
|
+
}
|
|
8204
|
+
}
|
|
8205
|
+
}
|
|
8206
|
+
return { ok: violations.length === 0, violations };
|
|
8207
|
+
}
|
|
7918
8208
|
var SHORT_SHA_WIDTH = 7;
|
|
7919
8209
|
var DATE_RE6 = /^\d{4}-\d{2}-\d{2}$/;
|
|
7920
8210
|
function todayString4() {
|
|
@@ -7962,7 +8252,7 @@ function prReviewReportPath(opts) {
|
|
|
7962
8252
|
const sameStem = new RegExp(`^${escaped}(?:-r([0-9]+))?\\.md$`);
|
|
7963
8253
|
let dirents;
|
|
7964
8254
|
try {
|
|
7965
|
-
dirents =
|
|
8255
|
+
dirents = readdirSync10(opts.reportsDir, { withFileTypes: true });
|
|
7966
8256
|
} catch (error) {
|
|
7967
8257
|
if (error.code === "ENOENT")
|
|
7968
8258
|
dirents = [];
|
|
@@ -7978,7 +8268,7 @@ function prReviewReportPath(opts) {
|
|
|
7978
8268
|
}
|
|
7979
8269
|
const revision = maxRevision + 1;
|
|
7980
8270
|
const name = revision === 1 ? `${finalStem}.md` : `${finalStem}-r${revision}.md`;
|
|
7981
|
-
return
|
|
8271
|
+
return join15(opts.reportsDir, name);
|
|
7982
8272
|
}
|
|
7983
8273
|
var PR_TIERS = ["quick", "default", "deep"];
|
|
7984
8274
|
function violation14(severity, code, message, fix) {
|
|
@@ -8248,10 +8538,10 @@ function prReviewSeatPrompt(opts) {
|
|
|
8248
8538
|
}
|
|
8249
8539
|
const skillRoot = opts.skillRoot.trim();
|
|
8250
8540
|
const worktreePath = opts.worktreePath.trim();
|
|
8251
|
-
if (!
|
|
8541
|
+
if (!isAbsolute9(skillRoot)) {
|
|
8252
8542
|
throw new TypeError(`prReviewSeatPrompt: skillRoot must be an absolute path - got ${JSON.stringify(opts.skillRoot)}`);
|
|
8253
8543
|
}
|
|
8254
|
-
if (!
|
|
8544
|
+
if (!isAbsolute9(worktreePath)) {
|
|
8255
8545
|
throw new TypeError(`prReviewSeatPrompt: worktreePath must be an absolute path - got ${JSON.stringify(opts.worktreePath)}`);
|
|
8256
8546
|
}
|
|
8257
8547
|
const slug = `${domain}-${seat}`;
|
|
@@ -8275,14 +8565,14 @@ function prReviewSeatPrompt(opts) {
|
|
|
8275
8565
|
lines.push("");
|
|
8276
8566
|
lines.push("## Read first");
|
|
8277
8567
|
lines.push("");
|
|
8278
|
-
const prReviewRef =
|
|
8568
|
+
const prReviewRef = join15(skillRoot, "references", "pr-review.md");
|
|
8279
8569
|
const sections = opts.stage === 1 ? tier === "quick" ? "Scoping, Evidence rules" : "Review pipeline, Worktree isolation, Scoping, Evidence rules" : "Merge class, Attack and vet, Evidence rules, Sizing & change shape";
|
|
8280
8570
|
lines.push(`1. \`${prReviewRef}\` \u2014 read at least these sections: ${sections}.`);
|
|
8281
8571
|
lines.push(`2. The review worktree: \`${worktreePath}\` \u2014 your ONLY working directory this session; read-only (no edits, no fixes, no stash, no commits, no posts).`);
|
|
8282
8572
|
if (opts.stage === 2) {
|
|
8283
|
-
lines.push(`3. \`${
|
|
8573
|
+
lines.push(`3. \`${join15(skillRoot, "references", "finding-format.md")}\` \u2014 the template every finding follows.`);
|
|
8284
8574
|
if (opts.securitySeat === true) {
|
|
8285
|
-
lines.push(`4. \`${
|
|
8575
|
+
lines.push(`4. \`${join15(skillRoot, "references", "security-review.md")}\` \u2014 the security lens.`);
|
|
8286
8576
|
}
|
|
8287
8577
|
}
|
|
8288
8578
|
lines.push("");
|
|
@@ -8452,28 +8742,30 @@ function resolvePrReviewTier(input) {
|
|
|
8452
8742
|
}
|
|
8453
8743
|
|
|
8454
8744
|
// ../engine/dist/engine.js
|
|
8455
|
-
import { existsSync as
|
|
8745
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync3, readFileSync as readFileSync8, renameSync as renameSync2, unlinkSync as unlinkSync4, writeFileSync as writeFileSync5 } from "node:fs";
|
|
8456
8746
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
8457
|
-
import { basename as
|
|
8747
|
+
import { basename as basename7, dirname as dirname7, join as join3, resolve as resolve10 } from "node:path";
|
|
8458
8748
|
import { fileURLToPath } from "node:url";
|
|
8459
8749
|
import { readFileSync as readFileSync22, statSync as statSync5 } from "node:fs";
|
|
8460
|
-
import { dirname as dirname22, isAbsolute as
|
|
8461
|
-
import { existsSync as
|
|
8750
|
+
import { dirname as dirname22, isAbsolute as isAbsolute7, join as join22, relative as relative3, resolve as resolve22 } from "node:path";
|
|
8751
|
+
import { existsSync as existsSync62, mkdirSync as mkdirSync52, readdirSync as readdirSync52, readFileSync as readFileSync62, realpathSync as realpathSync22, statSync as statSync32, writeFileSync as writeFileSync32 } from "node:fs";
|
|
8462
8752
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
8463
|
-
import { basename as
|
|
8753
|
+
import { basename as basename32, dirname as dirname52, isAbsolute as isAbsolute42, join as join92, relative as relative22, resolve as resolve72 } from "node:path";
|
|
8464
8754
|
import { mkdirSync as mkdirSync22, rmdirSync as rmdirSync3, statSync as statSync22, unlinkSync as unlinkSync22, writeFileSync as writeFileSync22 } from "node:fs";
|
|
8465
8755
|
import { dirname as dirname32, isAbsolute as isAbsolute22, join as join42, resolve as resolve32 } from "node:path";
|
|
8466
8756
|
import { setTimeout as sleep2 } from "node:timers/promises";
|
|
8467
8757
|
import { AsyncLocalStorage as AsyncLocalStorage3 } from "node:async_hooks";
|
|
8468
|
-
import { existsSync as existsSync32,
|
|
8469
|
-
import {
|
|
8758
|
+
import { existsSync as existsSync32, readdirSync as readdirSync22, unlinkSync as unlinkSync32 } from "node:fs";
|
|
8759
|
+
import { isAbsolute as isAbsolute32, join as join52, resolve as resolve42 } from "node:path";
|
|
8760
|
+
import { existsSync as existsSync42, readFileSync as readFileSync42, readdirSync as readdirSync32, realpathSync as realpathSync4 } from "node:fs";
|
|
8761
|
+
import { dirname as dirname42, join as join72, resolve as resolve52, sep as sep2 } from "node:path";
|
|
8470
8762
|
import { mkdirSync as mkdirSync32 } from "node:fs";
|
|
8471
|
-
import { join as
|
|
8472
|
-
import { copyFileSync, mkdirSync as mkdirSync7, readFileSync as readFileSync82, readdirSync as
|
|
8473
|
-
import { dirname as dirname72, isAbsolute as
|
|
8763
|
+
import { join as join6 } from "node:path";
|
|
8764
|
+
import { copyFileSync, mkdirSync as mkdirSync7, readFileSync as readFileSync82, readdirSync as readdirSync7, writeFileSync as writeFileSync52 } from "node:fs";
|
|
8765
|
+
import { dirname as dirname72, isAbsolute as isAbsolute72, join as join11, relative as relative32, resolve as resolve102, sep as sep22 } from "node:path";
|
|
8474
8766
|
var SEVERITY_ORDER2 = ["critical", "high", "medium", "low", "nit"];
|
|
8475
8767
|
function readJson2(filePath) {
|
|
8476
|
-
if (!
|
|
8768
|
+
if (!existsSync11(filePath))
|
|
8477
8769
|
return {};
|
|
8478
8770
|
const content = readFileSync8(filePath, "utf8").trim();
|
|
8479
8771
|
if (!content)
|
|
@@ -8487,23 +8779,23 @@ function readJson2(filePath) {
|
|
|
8487
8779
|
function writeJson2(filePath, value) {
|
|
8488
8780
|
const parent = dirname7(filePath);
|
|
8489
8781
|
mkdirSync3(parent, { recursive: true });
|
|
8490
|
-
const tmp = join3(parent, `.${
|
|
8782
|
+
const tmp = join3(parent, `.${basename7(filePath)}.${process.pid}.${randomUUID2()}.tmp`);
|
|
8491
8783
|
try {
|
|
8492
8784
|
writeFileSync5(tmp, `${JSON.stringify(value, null, 2)}
|
|
8493
8785
|
`, "utf8");
|
|
8494
8786
|
renameSync2(tmp, filePath);
|
|
8495
8787
|
} catch (error) {
|
|
8496
8788
|
try {
|
|
8497
|
-
|
|
8789
|
+
unlinkSync4(tmp);
|
|
8498
8790
|
} catch {}
|
|
8499
8791
|
throw error;
|
|
8500
8792
|
}
|
|
8501
8793
|
}
|
|
8502
8794
|
function resolveProjectRoot(startDir = process.cwd()) {
|
|
8503
|
-
const start =
|
|
8795
|
+
const start = resolve10(startDir);
|
|
8504
8796
|
let dir = start;
|
|
8505
8797
|
for (;; ) {
|
|
8506
|
-
if (
|
|
8798
|
+
if (existsSync11(join3(dir, "package.json")) || existsSync11(join3(dir, "bun.lock")))
|
|
8507
8799
|
return dir;
|
|
8508
8800
|
const parent = dirname7(dir);
|
|
8509
8801
|
if (parent === dir)
|
|
@@ -8514,7 +8806,7 @@ function resolveProjectRoot(startDir = process.cwd()) {
|
|
|
8514
8806
|
function findRootPackageJson(startDir) {
|
|
8515
8807
|
let dir = startDir;
|
|
8516
8808
|
for (;; ) {
|
|
8517
|
-
const candidate =
|
|
8809
|
+
const candidate = resolve10(dir, "package.json");
|
|
8518
8810
|
try {
|
|
8519
8811
|
const pkg = JSON.parse(readFileSync8(candidate, "utf8"));
|
|
8520
8812
|
if (pkg.name === "morning-star")
|
|
@@ -8629,7 +8921,7 @@ function loadMstarc2(startDir, boundary) {
|
|
|
8629
8921
|
}
|
|
8630
8922
|
function isAtOrBelow3(dir, root) {
|
|
8631
8923
|
const rel = relative3(root, dir);
|
|
8632
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
8924
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute7(rel);
|
|
8633
8925
|
}
|
|
8634
8926
|
function parseCompassFrontmatterText2(content, filePath) {
|
|
8635
8927
|
const lines = content.split(/\r?\n/);
|
|
@@ -8838,6 +9130,137 @@ async function withStatusWriteLock2(statusPath, fn, opts = {}) {
|
|
|
8838
9130
|
} catch {}
|
|
8839
9131
|
}
|
|
8840
9132
|
}
|
|
9133
|
+
var PLAN_SHAPED_KEY_RE2 = /^[0-9]{8}-[a-z0-9-]+$/;
|
|
9134
|
+
function resolveArtifactPath2(harnessRoot, ref) {
|
|
9135
|
+
const { kind, key } = ref;
|
|
9136
|
+
if (kind === "json") {
|
|
9137
|
+
if (!isAbsolute32(key)) {
|
|
9138
|
+
throw new Error(`ArtifactStore json key must be an absolute path \u2014 got ${JSON.stringify(key)}`);
|
|
9139
|
+
}
|
|
9140
|
+
if (key.split(/[\\/]+/).includes("..")) {
|
|
9141
|
+
throw new Error(`ArtifactStore json key must not contain ".." segments \u2014 got ${JSON.stringify(key)}`);
|
|
9142
|
+
}
|
|
9143
|
+
return key;
|
|
9144
|
+
}
|
|
9145
|
+
assertSafePathComponent2(key, "ArtifactStore key");
|
|
9146
|
+
if (kind === "status") {
|
|
9147
|
+
if (key !== "root") {
|
|
9148
|
+
throw new Error(`ArtifactStore status key must be "root" \u2014 got ${JSON.stringify(key)}`);
|
|
9149
|
+
}
|
|
9150
|
+
return join52(harnessRoot, "status.json");
|
|
9151
|
+
}
|
|
9152
|
+
if (kind === "snapshot") {
|
|
9153
|
+
return join52(resolveWorkflowDir2(harnessRoot, { harnessDir: harnessRoot }), key, "snapshot.json");
|
|
9154
|
+
}
|
|
9155
|
+
if (kind === "residuals") {
|
|
9156
|
+
return join52(resolveProjectDir2(harnessRoot, { harnessDir: harnessRoot }), key, "residuals.json");
|
|
9157
|
+
}
|
|
9158
|
+
if (PLAN_SHAPED_KEY_RE2.test(key)) {
|
|
9159
|
+
return join52(harnessRoot, "sdd", key, "review", "report.json");
|
|
9160
|
+
}
|
|
9161
|
+
return join52(harnessRoot, "sdd", "_reviews", `${key}.json`);
|
|
9162
|
+
}
|
|
9163
|
+
function listDirNames2(dir) {
|
|
9164
|
+
return readDirEntries2(dir).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
9165
|
+
}
|
|
9166
|
+
function listJsonKeys2(dir) {
|
|
9167
|
+
return readDirEntries2(dir).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -".json".length));
|
|
9168
|
+
}
|
|
9169
|
+
function readDirEntries2(dir) {
|
|
9170
|
+
try {
|
|
9171
|
+
return readdirSync22(dir, { withFileTypes: true });
|
|
9172
|
+
} catch (error) {
|
|
9173
|
+
const code = error.code;
|
|
9174
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
9175
|
+
return [];
|
|
9176
|
+
throw error;
|
|
9177
|
+
}
|
|
9178
|
+
}
|
|
9179
|
+
function tryResolveGetPath2(root, kind, key) {
|
|
9180
|
+
try {
|
|
9181
|
+
return resolveArtifactPath2(root, { kind, key });
|
|
9182
|
+
} catch {
|
|
9183
|
+
return;
|
|
9184
|
+
}
|
|
9185
|
+
}
|
|
9186
|
+
function createFsStore2(harnessRoot) {
|
|
9187
|
+
const root = resolve42(harnessRoot);
|
|
9188
|
+
return {
|
|
9189
|
+
root,
|
|
9190
|
+
async put(doc) {
|
|
9191
|
+
if (doc.schema !== undefined) {
|
|
9192
|
+
throw new Error("FsStore does not persist schema ids \u2014 omit --schema or inject a store module that persists it");
|
|
9193
|
+
}
|
|
9194
|
+
writeJson2(resolveArtifactPath2(root, doc), doc.payload);
|
|
9195
|
+
},
|
|
9196
|
+
async get(ref) {
|
|
9197
|
+
const filePath = resolveArtifactPath2(root, ref);
|
|
9198
|
+
if (!existsSync32(filePath))
|
|
9199
|
+
return;
|
|
9200
|
+
return readJson2(filePath);
|
|
9201
|
+
},
|
|
9202
|
+
async delete(ref) {
|
|
9203
|
+
const filePath = resolveArtifactPath2(root, ref);
|
|
9204
|
+
if (existsSync32(filePath))
|
|
9205
|
+
unlinkSync32(filePath);
|
|
9206
|
+
},
|
|
9207
|
+
async list(kind) {
|
|
9208
|
+
if (kind === "json") {
|
|
9209
|
+
throw new Error("ArtifactStore json keys are absolute paths and cannot be listed");
|
|
9210
|
+
}
|
|
9211
|
+
const keys = [];
|
|
9212
|
+
if (kind === "status") {
|
|
9213
|
+
if (existsSync32(resolveArtifactPath2(root, { kind, key: "root" })))
|
|
9214
|
+
keys.push("root");
|
|
9215
|
+
} else if (kind === "snapshot" || kind === "residuals") {
|
|
9216
|
+
const baseDir = kind === "snapshot" ? resolveWorkflowDir2(root, { harnessDir: root }) : resolveProjectDir2(root, { harnessDir: root });
|
|
9217
|
+
for (const name of listDirNames2(baseDir)) {
|
|
9218
|
+
const getPath = tryResolveGetPath2(root, kind, name);
|
|
9219
|
+
if (getPath !== undefined && existsSync32(getPath))
|
|
9220
|
+
keys.push(name);
|
|
9221
|
+
}
|
|
9222
|
+
} else {
|
|
9223
|
+
const sddDir = join52(root, "sdd");
|
|
9224
|
+
for (const key of listJsonKeys2(join52(sddDir, "_reviews"))) {
|
|
9225
|
+
if (!PLAN_SHAPED_KEY_RE2.test(key) && tryResolveGetPath2(root, kind, key) !== undefined) {
|
|
9226
|
+
keys.push(key);
|
|
9227
|
+
}
|
|
9228
|
+
}
|
|
9229
|
+
for (const name of listDirNames2(sddDir)) {
|
|
9230
|
+
if (PLAN_SHAPED_KEY_RE2.test(name)) {
|
|
9231
|
+
const getPath = tryResolveGetPath2(root, kind, name);
|
|
9232
|
+
if (getPath !== undefined && existsSync32(getPath))
|
|
9233
|
+
keys.push(name);
|
|
9234
|
+
}
|
|
9235
|
+
}
|
|
9236
|
+
}
|
|
9237
|
+
return keys.sort().map((key) => ({ kind, key }));
|
|
9238
|
+
}
|
|
9239
|
+
};
|
|
9240
|
+
}
|
|
9241
|
+
var injectedStore2;
|
|
9242
|
+
function setArtifactStore2(store) {
|
|
9243
|
+
injectedStore2 = store;
|
|
9244
|
+
}
|
|
9245
|
+
function getArtifactStore2() {
|
|
9246
|
+
if (injectedStore2 !== undefined)
|
|
9247
|
+
return injectedStore2;
|
|
9248
|
+
const root = resolveHarnessDir2(process.cwd());
|
|
9249
|
+
if (root === null) {
|
|
9250
|
+
throw new Error(`harness dir not found from ${resolve42(process.cwd())} \u2014 cannot create the default FsStore (run \`mstar harness scaffold\`, pass opts.harnessDir, or set MSTAR_HARNESS_DIR)`);
|
|
9251
|
+
}
|
|
9252
|
+
return createFsStore2(root);
|
|
9253
|
+
}
|
|
9254
|
+
function assertFsStorePath2(store, ref, expectedPath) {
|
|
9255
|
+
const root = store.root;
|
|
9256
|
+
if (typeof root !== "string")
|
|
9257
|
+
return;
|
|
9258
|
+
const storePath = resolveArtifactPath2(root, ref);
|
|
9259
|
+
const expected = resolve42(expectedPath);
|
|
9260
|
+
if (storePath !== expected) {
|
|
9261
|
+
throw new Error(`routed writer path mismatch: the active FsStore resolves ${ref.kind}/${JSON.stringify(ref.key)} to ${JSON.stringify(storePath)} but the caller's target is ${JSON.stringify(expected)} \u2014 call setArtifactStore(createFsStore(<root>)) first when the write target differs from the active store's root`);
|
|
9262
|
+
}
|
|
9263
|
+
}
|
|
8841
9264
|
var WORKFLOW_SNAPSHOT_FILE2 = "snapshot.json";
|
|
8842
9265
|
var WORKFLOW_LIFECYCLE_STATUSES2 = ["running", "paused", "completed", "failed", "stopped"];
|
|
8843
9266
|
var WORKFLOW_TERMINAL_STATUSES2 = ["completed", "failed", "stopped"];
|
|
@@ -8954,10 +9377,12 @@ async function writeWorkflowSnapshot(snapshot, dir) {
|
|
|
8954
9377
|
const detail = gate2.violations.map((v) => v.message).join("; ");
|
|
8955
9378
|
throw new Error(`refusing to write invalid workflow snapshot: ${detail}`);
|
|
8956
9379
|
}
|
|
8957
|
-
const snapshotPath =
|
|
9380
|
+
const snapshotPath = join6(dir, WORKFLOW_SNAPSHOT_FILE2);
|
|
9381
|
+
const store = getArtifactStore2();
|
|
9382
|
+
assertFsStorePath2(store, { kind: "snapshot", key: snapshot.id }, snapshotPath);
|
|
8958
9383
|
mkdirSync32(dir, { recursive: true });
|
|
8959
|
-
await withStatusWriteLock2(snapshotPath, () => {
|
|
8960
|
-
|
|
9384
|
+
await withStatusWriteLock2(snapshotPath, async () => {
|
|
9385
|
+
await store.put({ kind: "snapshot", key: snapshot.id, payload: snapshot });
|
|
8961
9386
|
});
|
|
8962
9387
|
}
|
|
8963
9388
|
var DATE_RE22 = /^\d{4}-\d{2}-\d{2}$/;
|
|
@@ -9109,7 +9534,7 @@ function validateStatusV22(docOrPath, opts = {}) {
|
|
|
9109
9534
|
if (typeof docOrPath === "string") {
|
|
9110
9535
|
try {
|
|
9111
9536
|
doc = readJson2(docOrPath);
|
|
9112
|
-
harnessDir = dirname42(
|
|
9537
|
+
harnessDir = dirname42(resolve52(docOrPath));
|
|
9113
9538
|
} catch (error) {
|
|
9114
9539
|
return {
|
|
9115
9540
|
ok: false,
|
|
@@ -9176,8 +9601,8 @@ function validateStatusV22(docOrPath, opts = {}) {
|
|
|
9176
9601
|
for (const entry of doc.workflows) {
|
|
9177
9602
|
if (!isPlainObject42(entry) || typeof entry.dir !== "string")
|
|
9178
9603
|
continue;
|
|
9179
|
-
const relSnapshot =
|
|
9180
|
-
const snapshotPath =
|
|
9604
|
+
const relSnapshot = join72(entry.dir, WORKFLOW_SNAPSHOT_FILE2);
|
|
9605
|
+
const snapshotPath = join72(harnessDir, relSnapshot);
|
|
9181
9606
|
const label = typeof entry.id === "string" ? entry.id : relSnapshot;
|
|
9182
9607
|
let physical;
|
|
9183
9608
|
try {
|
|
@@ -9270,19 +9695,19 @@ function validateProjectRegister2(doc) {
|
|
|
9270
9695
|
return { ok: violations.length === 0, violations };
|
|
9271
9696
|
}
|
|
9272
9697
|
function resolveHarnessDir2(startDir = process.cwd(), opts = {}) {
|
|
9273
|
-
const start =
|
|
9698
|
+
const start = resolve72(startDir);
|
|
9274
9699
|
const explicit = opts.harnessDir ?? process.env.MSTAR_HARNESS_DIR;
|
|
9275
9700
|
if (explicit)
|
|
9276
|
-
return
|
|
9277
|
-
const boundary =
|
|
9701
|
+
return resolve72(start, explicit);
|
|
9702
|
+
const boundary = resolve72(start, opts.workspaceRoot ?? defaultWorkspaceRoot2(start));
|
|
9278
9703
|
const rc = loadMstarc2(start, boundary);
|
|
9279
9704
|
if (rc !== null && rc.config.harnessDir)
|
|
9280
|
-
return
|
|
9705
|
+
return resolve72(rc.dir, rc.config.harnessDir);
|
|
9281
9706
|
let dir = start;
|
|
9282
9707
|
for (;; ) {
|
|
9283
9708
|
if (!isAtOrBelow22(dir, boundary))
|
|
9284
9709
|
return null;
|
|
9285
|
-
for (const candidate of [
|
|
9710
|
+
for (const candidate of [join92(dir, ".mstar"), join92(dir, ".agents"), join92(dir, ".plans"), join92(dir, "plans")]) {
|
|
9286
9711
|
if (isDirectory3(candidate))
|
|
9287
9712
|
return candidate;
|
|
9288
9713
|
}
|
|
@@ -9308,19 +9733,19 @@ function defaultWorkspaceRoot2(startDir) {
|
|
|
9308
9733
|
if (segment && segment !== ".")
|
|
9309
9734
|
boundary = dirname52(boundary);
|
|
9310
9735
|
}
|
|
9311
|
-
return
|
|
9736
|
+
return resolve72(boundary);
|
|
9312
9737
|
} catch {}
|
|
9313
9738
|
return startDir;
|
|
9314
9739
|
}
|
|
9315
9740
|
function isAtOrBelow22(dir, root) {
|
|
9316
9741
|
const rel = relative22(root, dir);
|
|
9317
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
9742
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute42(rel);
|
|
9318
9743
|
}
|
|
9319
9744
|
function mstarcDirOverride2(harnessDir, key) {
|
|
9320
|
-
const dir =
|
|
9745
|
+
const dir = resolve72(harnessDir);
|
|
9321
9746
|
const rc = loadMstarc2(dir, dirname52(dir));
|
|
9322
9747
|
const declared = rc?.config[key];
|
|
9323
|
-
return declared ?
|
|
9748
|
+
return declared ? resolve72(rc.dir, declared) : null;
|
|
9324
9749
|
}
|
|
9325
9750
|
function assertSafePathComponent2(value, what) {
|
|
9326
9751
|
if (value === "" || value === "." || value === ".." || !/^[A-Za-z0-9._-]+$/.test(value)) {
|
|
@@ -9330,10 +9755,10 @@ function assertSafePathComponent2(value, what) {
|
|
|
9330
9755
|
function resolveHarnessSubdir2(startDir, opts, key, fallback) {
|
|
9331
9756
|
const harness = resolveHarnessDir2(startDir, opts);
|
|
9332
9757
|
if (harness === null) {
|
|
9333
|
-
throw new Error(`harness dir not found from ${
|
|
9758
|
+
throw new Error(`harness dir not found from ${resolve72(startDir)} \u2014 cannot resolve the ${fallback} dir (run \`mstar harness scaffold\`, pass opts.harnessDir, or set MSTAR_HARNESS_DIR)`);
|
|
9334
9759
|
}
|
|
9335
9760
|
const declared = mstarcDirOverride2(harness, key);
|
|
9336
|
-
return declared !== null ? declared :
|
|
9761
|
+
return declared !== null ? declared : join92(resolve72(harness), fallback);
|
|
9337
9762
|
}
|
|
9338
9763
|
function resolveWorkflowDir2(startDir = process.cwd(), opts = {}) {
|
|
9339
9764
|
return resolveHarnessSubdir2(startDir, opts, "workflowDir", "workflows");
|
|
@@ -9373,7 +9798,6 @@ function isDirectory3(dir) {
|
|
|
9373
9798
|
return false;
|
|
9374
9799
|
}
|
|
9375
9800
|
}
|
|
9376
|
-
var GIT_CAPTURE_MAX_BYTES2 = 64 * 1024 * 1024;
|
|
9377
9801
|
var MIGRATE_STATUS_FILE = "status.json";
|
|
9378
9802
|
var ARCHIVED_STATUS_V1_FILE = "archived/status.v1.json";
|
|
9379
9803
|
var NOTES_LEDGER_FILE = "notes.jsonl";
|
|
@@ -9413,18 +9837,18 @@ function todayString3() {
|
|
|
9413
9837
|
return `${now.getFullYear()}-${month}-${day}`;
|
|
9414
9838
|
}
|
|
9415
9839
|
function scanCompasses(harnessDir) {
|
|
9416
|
-
const iterationsDir =
|
|
9840
|
+
const iterationsDir = join11(harnessDir, "iterations");
|
|
9417
9841
|
const out = [];
|
|
9418
9842
|
let entries;
|
|
9419
9843
|
try {
|
|
9420
|
-
entries =
|
|
9844
|
+
entries = readdirSync7(iterationsDir, { withFileTypes: true });
|
|
9421
9845
|
} catch {
|
|
9422
9846
|
return out;
|
|
9423
9847
|
}
|
|
9424
9848
|
for (const entry of entries) {
|
|
9425
9849
|
if (!entry.isDirectory())
|
|
9426
9850
|
continue;
|
|
9427
|
-
const compassPath =
|
|
9851
|
+
const compassPath = join11(iterationsDir, entry.name, "delivery-compass.md");
|
|
9428
9852
|
let content;
|
|
9429
9853
|
try {
|
|
9430
9854
|
content = readFileSync82(compassPath, "utf8");
|
|
@@ -9504,8 +9928,8 @@ function buildIterationSnapshot(compass, rowById, rootUpdatedAt) {
|
|
|
9504
9928
|
id: compass.id,
|
|
9505
9929
|
type: "iteration",
|
|
9506
9930
|
status,
|
|
9507
|
-
file:
|
|
9508
|
-
source:
|
|
9931
|
+
file: join11("workflows", compass.id, WORKFLOW_SNAPSHOT_FILE2),
|
|
9932
|
+
source: join11("iterations", compass.id, "delivery-compass.md"),
|
|
9509
9933
|
data: snapshot
|
|
9510
9934
|
};
|
|
9511
9935
|
}
|
|
@@ -9540,7 +9964,7 @@ function buildStandaloneSnapshot(row, rootUpdatedAt, migrationNotes) {
|
|
|
9540
9964
|
id,
|
|
9541
9965
|
type: "plan",
|
|
9542
9966
|
status,
|
|
9543
|
-
file:
|
|
9967
|
+
file: join11("workflows", id, WORKFLOW_SNAPSHOT_FILE2),
|
|
9544
9968
|
source: "status.json plans[] row",
|
|
9545
9969
|
data: snapshot
|
|
9546
9970
|
};
|
|
@@ -9648,7 +10072,7 @@ function buildRegister(residualFindings, byPlan, projectId, migratedAt) {
|
|
|
9648
10072
|
return null;
|
|
9649
10073
|
const doc = { entries };
|
|
9650
10074
|
return {
|
|
9651
|
-
file:
|
|
10075
|
+
file: join11("projects", projectId, PROJECT_REGISTER_FILE2),
|
|
9652
10076
|
source: "status.json residual_findings",
|
|
9653
10077
|
data: doc
|
|
9654
10078
|
};
|
|
@@ -9679,7 +10103,7 @@ function collectNotesFiles(snapshots) {
|
|
|
9679
10103
|
if (lines.length === 0)
|
|
9680
10104
|
continue;
|
|
9681
10105
|
out.push({
|
|
9682
|
-
file:
|
|
10106
|
+
file: join11(dirname72(snapshot.file), NOTES_LEDGER_FILE),
|
|
9683
10107
|
source,
|
|
9684
10108
|
lines
|
|
9685
10109
|
});
|
|
@@ -9687,11 +10111,11 @@ function collectNotesFiles(snapshots) {
|
|
|
9687
10111
|
return out;
|
|
9688
10112
|
}
|
|
9689
10113
|
function migrateHarnessTree(root, opts = {}) {
|
|
9690
|
-
const harnessDir =
|
|
10114
|
+
const harnessDir = resolve102(root);
|
|
9691
10115
|
const workflowDir = resolveWorkflowDir2(harnessDir, { harnessDir });
|
|
9692
10116
|
const projectDir = resolveProjectDir2(harnessDir, { harnessDir });
|
|
9693
10117
|
const projectId = opts.projectId ?? _DEFAULT_PROJECT2;
|
|
9694
|
-
const statusPath =
|
|
10118
|
+
const statusPath = join11(harnessDir, MIGRATE_STATUS_FILE);
|
|
9695
10119
|
const legacy = readJson2(statusPath);
|
|
9696
10120
|
if (legacy.version === 2) {
|
|
9697
10121
|
const updatedAt = typeof legacy.updated_at === "string" && legacy.updated_at !== "" ? legacy.updated_at : "1970-01-01";
|
|
@@ -9787,7 +10211,7 @@ function migrateHarnessTree(root, opts = {}) {
|
|
|
9787
10211
|
migrationNotes.push(`roadmap title sanitized for frontmatter (line breaks replaced with spaces): ${JSON.stringify(rawTitle)}`);
|
|
9788
10212
|
}
|
|
9789
10213
|
roadmap = {
|
|
9790
|
-
file:
|
|
10214
|
+
file: join11("projects", projectId, PROJECT_ROADMAP_FILE2),
|
|
9791
10215
|
source: "status.json metadata.program_roadmap",
|
|
9792
10216
|
content: buildRoadmap({ ...programRoadmap, title: sanitizedTitle }, projectId, migratedAt)
|
|
9793
10217
|
};
|
|
@@ -9836,19 +10260,19 @@ async function applyMigratePlan(plan) {
|
|
|
9836
10260
|
if (plan.dryRun) {
|
|
9837
10261
|
return { applied: false, message: `dry-run: ${plan.steps.length} steps planned (source \u2192 destination), zero writes` };
|
|
9838
10262
|
}
|
|
9839
|
-
const statusPath =
|
|
10263
|
+
const statusPath = join11(plan.root, MIGRATE_STATUS_FILE);
|
|
9840
10264
|
const current = readJson2(statusPath);
|
|
9841
10265
|
if (current.version === 2) {
|
|
9842
10266
|
return { applied: false, message: "no-op: status.json already at schema version 2 (migrated) \u2014 nothing to do" };
|
|
9843
10267
|
}
|
|
9844
|
-
const harnessRoot =
|
|
9845
|
-
const workflowRoot =
|
|
9846
|
-
const projectRoot =
|
|
9847
|
-
if (!
|
|
10268
|
+
const harnessRoot = resolve102(plan.root);
|
|
10269
|
+
const workflowRoot = resolve102(plan.workflowDir);
|
|
10270
|
+
const projectRoot = resolve102(plan.projectDir);
|
|
10271
|
+
if (!isAbsolute72(plan.workflowDir) || !isAbsolute72(plan.projectDir)) {
|
|
9848
10272
|
throw new Error(`refusing to apply migration: plan workflowDir/projectDir must be absolute (got ${JSON.stringify(plan.workflowDir)} / ${JSON.stringify(plan.projectDir)})`);
|
|
9849
10273
|
}
|
|
9850
|
-
const workflowTargetOf = (canonicalFile) =>
|
|
9851
|
-
const projectTargetOf = (canonicalFile) =>
|
|
10274
|
+
const workflowTargetOf = (canonicalFile) => join11(workflowRoot, relative32("workflows", canonicalFile));
|
|
10275
|
+
const projectTargetOf = (canonicalFile) => join11(projectRoot, relative32("projects", canonicalFile));
|
|
9852
10276
|
const allDestinations = [
|
|
9853
10277
|
plan.archive.file,
|
|
9854
10278
|
...plan.snapshots.map((snapshot) => snapshot.file),
|
|
@@ -9857,14 +10281,14 @@ async function applyMigratePlan(plan) {
|
|
|
9857
10281
|
...plan.roadmap !== null ? [plan.roadmap.file] : []
|
|
9858
10282
|
];
|
|
9859
10283
|
for (const destination of allDestinations) {
|
|
9860
|
-
const resolvedDest =
|
|
10284
|
+
const resolvedDest = resolve102(join11(plan.root, destination));
|
|
9861
10285
|
const inside = (dir) => resolvedDest === dir || resolvedDest.startsWith(`${dir}${sep22}`);
|
|
9862
10286
|
if (!inside(harnessRoot) && !inside(workflowRoot) && !inside(projectRoot)) {
|
|
9863
10287
|
throw new Error(`refusing to apply migration: destination escapes the harness dir (${JSON.stringify(destination)}) \u2014 every write must stay under ${JSON.stringify(plan.root)}, the workflow dir (${JSON.stringify(plan.workflowDir)}) or the project dir (${JSON.stringify(plan.projectDir)})`);
|
|
9864
10288
|
}
|
|
9865
10289
|
}
|
|
9866
|
-
mkdirSync7(
|
|
9867
|
-
copyFileSync(statusPath,
|
|
10290
|
+
mkdirSync7(join11(plan.root, dirname72(plan.archive.file)), { recursive: true });
|
|
10291
|
+
copyFileSync(statusPath, join11(plan.root, plan.archive.file));
|
|
9868
10292
|
for (const snapshot of plan.snapshots) {
|
|
9869
10293
|
await writeWorkflowSnapshot(snapshot.data, dirname72(workflowTargetOf(snapshot.file)));
|
|
9870
10294
|
}
|
|
@@ -9920,7 +10344,7 @@ var RISK_ENUM_RE2 = new RegExp(`^(?:${AUDIT_RISKS2.join("|")})(?:\\b|$)`);
|
|
|
9920
10344
|
var CONFIDENCE_ENUM_RE2 = new RegExp(`^(${[...AUDIT_CONFIDENCES2, "MEDIUM"].join("|")})\\b`, "i");
|
|
9921
10345
|
// src/commands/migrate.ts
|
|
9922
10346
|
var import_picocolors = __toESM(require_picocolors2(), 1);
|
|
9923
|
-
import { resolve as
|
|
10347
|
+
import { resolve as resolve13 } from "node:path";
|
|
9924
10348
|
function validatePlannedDocs(plan) {
|
|
9925
10349
|
const warnings = [];
|
|
9926
10350
|
for (const snapshot of plan.snapshots) {
|
|
@@ -9942,7 +10366,8 @@ function validatePlannedDocs(plan) {
|
|
|
9942
10366
|
return warnings;
|
|
9943
10367
|
}
|
|
9944
10368
|
async function runMigrateCommand(options) {
|
|
9945
|
-
const root = options.path ?
|
|
10369
|
+
const root = options.path ? resolve13(options.path) : resolveHarnessDir2() ?? process.cwd();
|
|
10370
|
+
setArtifactStore2(createFsStore2(root));
|
|
9946
10371
|
let plan;
|
|
9947
10372
|
try {
|
|
9948
10373
|
plan = migrateHarnessTree(root, { dryRun: options.dryRun === true });
|
|
@@ -10097,7 +10522,7 @@ function describeType(value) {
|
|
|
10097
10522
|
return "array";
|
|
10098
10523
|
return typeof value;
|
|
10099
10524
|
}
|
|
10100
|
-
function
|
|
10525
|
+
function isPlainObject8(value) {
|
|
10101
10526
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10102
10527
|
}
|
|
10103
10528
|
function parseScalar3(raw) {
|
|
@@ -10141,7 +10566,7 @@ function isValidMcpUrl(raw) {
|
|
|
10141
10566
|
return true;
|
|
10142
10567
|
}
|
|
10143
10568
|
function validateManifest(manifest, errors2, warnings) {
|
|
10144
|
-
if (!
|
|
10569
|
+
if (!isPlainObject8(manifest)) {
|
|
10145
10570
|
errors2.push("plugin.json: manifest must be a JSON object");
|
|
10146
10571
|
return;
|
|
10147
10572
|
}
|
|
@@ -10177,7 +10602,7 @@ function validateManifest(manifest, errors2, warnings) {
|
|
|
10177
10602
|
}
|
|
10178
10603
|
}
|
|
10179
10604
|
if (doc.author !== undefined) {
|
|
10180
|
-
if (!
|
|
10605
|
+
if (!isPlainObject8(doc.author)) {
|
|
10181
10606
|
errors2.push('plugin.json: "author" must be an object with optional string fields name/email/url');
|
|
10182
10607
|
} else {
|
|
10183
10608
|
const author = doc.author;
|
|
@@ -10200,11 +10625,11 @@ function validateManifest(manifest, errors2, warnings) {
|
|
|
10200
10625
|
}
|
|
10201
10626
|
}
|
|
10202
10627
|
if (doc.extensions !== undefined) {
|
|
10203
|
-
if (!
|
|
10628
|
+
if (!isPlainObject8(doc.extensions)) {
|
|
10204
10629
|
warnings.push('plugin.json: "extensions" is not an object \u2014 ignored');
|
|
10205
10630
|
} else {
|
|
10206
10631
|
for (const [namespace, value] of Object.entries(doc.extensions)) {
|
|
10207
|
-
if (!
|
|
10632
|
+
if (!isPlainObject8(value)) {
|
|
10208
10633
|
warnings.push(`plugin.json: "extensions.${namespace}" is not an object \u2014 ignored`);
|
|
10209
10634
|
}
|
|
10210
10635
|
}
|
|
@@ -10213,7 +10638,7 @@ function validateManifest(manifest, errors2, warnings) {
|
|
|
10213
10638
|
}
|
|
10214
10639
|
function validateMcpServer(name, entry, errors2) {
|
|
10215
10640
|
const prefix = `mcp.json: mcpServers.${name}`;
|
|
10216
|
-
if (!
|
|
10641
|
+
if (!isPlainObject8(entry)) {
|
|
10217
10642
|
errors2.push(`${prefix} must be an object`);
|
|
10218
10643
|
return;
|
|
10219
10644
|
}
|
|
@@ -10247,7 +10672,7 @@ function validateMcpServer(name, entry, errors2) {
|
|
|
10247
10672
|
}
|
|
10248
10673
|
}
|
|
10249
10674
|
if (server.env !== undefined) {
|
|
10250
|
-
if (!
|
|
10675
|
+
if (!isPlainObject8(server.env)) {
|
|
10251
10676
|
errors2.push(`${prefix}: "env" must be an object of strings`);
|
|
10252
10677
|
} else {
|
|
10253
10678
|
for (const [key, value] of Object.entries(server.env)) {
|
|
@@ -10286,7 +10711,7 @@ function validateMcpServer(name, entry, errors2) {
|
|
|
10286
10711
|
errors2.push(`${prefix}: "url" must be an absolute http(s) URL without user info or fragment; non-loopback endpoints must use https`);
|
|
10287
10712
|
}
|
|
10288
10713
|
if (server.headers !== undefined) {
|
|
10289
|
-
if (!
|
|
10714
|
+
if (!isPlainObject8(server.headers)) {
|
|
10290
10715
|
errors2.push(`${prefix}: "headers" must be an object of strings`);
|
|
10291
10716
|
} else {
|
|
10292
10717
|
const seen = new Set;
|
|
@@ -10323,7 +10748,7 @@ function validateMcp(root, manifestSchema, errors2) {
|
|
|
10323
10748
|
errors2.push(`mcp.json: ${error.message}`);
|
|
10324
10749
|
return;
|
|
10325
10750
|
}
|
|
10326
|
-
if (!
|
|
10751
|
+
if (!isPlainObject8(parsed)) {
|
|
10327
10752
|
errors2.push("mcp.json: configuration must be a JSON object");
|
|
10328
10753
|
return;
|
|
10329
10754
|
}
|
|
@@ -10346,7 +10771,7 @@ function validateMcp(root, manifestSchema, errors2) {
|
|
|
10346
10771
|
}
|
|
10347
10772
|
}
|
|
10348
10773
|
const servers = doc.mcpServers;
|
|
10349
|
-
if (!
|
|
10774
|
+
if (!isPlainObject8(servers)) {
|
|
10350
10775
|
errors2.push('mcp.json: "mcpServers" is required and must be an object');
|
|
10351
10776
|
return;
|
|
10352
10777
|
}
|
|
@@ -10431,7 +10856,7 @@ function validateAgentPlugin(root) {
|
|
|
10431
10856
|
return { ok: false, errors: errors2, warnings };
|
|
10432
10857
|
}
|
|
10433
10858
|
validateManifest(manifest, errors2, warnings);
|
|
10434
|
-
const manifestSchema =
|
|
10859
|
+
const manifestSchema = isPlainObject8(manifest) ? manifest["$schema"] : undefined;
|
|
10435
10860
|
validateMcp(root, manifestSchema, errors2);
|
|
10436
10861
|
validateSkills(root, errors2, warnings);
|
|
10437
10862
|
return { ok: errors2.length === 0, errors: errors2, warnings };
|
|
@@ -11706,6 +12131,68 @@ function getAdapter(target) {
|
|
|
11706
12131
|
return adapter;
|
|
11707
12132
|
}
|
|
11708
12133
|
|
|
12134
|
+
// src/global-cli.ts
|
|
12135
|
+
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
12136
|
+
var CLI_NAME = "mstar-harness";
|
|
12137
|
+
var NPM_INSTALL_TIMEOUT_MS = 300000;
|
|
12138
|
+
var NPM_INSTALL_TIMEOUT_ENV = "MSTAR_CLI_INSTALL_TIMEOUT_MS";
|
|
12139
|
+
function installTimeoutMs() {
|
|
12140
|
+
const raw = process.env[NPM_INSTALL_TIMEOUT_ENV];
|
|
12141
|
+
if (raw === undefined || raw.trim() === "")
|
|
12142
|
+
return NPM_INSTALL_TIMEOUT_MS;
|
|
12143
|
+
const parsed = Number(raw);
|
|
12144
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : NPM_INSTALL_TIMEOUT_MS;
|
|
12145
|
+
}
|
|
12146
|
+
function defaultDetectVersion() {
|
|
12147
|
+
try {
|
|
12148
|
+
const out = execFileSync9(CLI_NAME, ["--version"], { encoding: "utf8", env: process.env });
|
|
12149
|
+
return out.trim();
|
|
12150
|
+
} catch {
|
|
12151
|
+
return null;
|
|
12152
|
+
}
|
|
12153
|
+
}
|
|
12154
|
+
function defaultInstall(spec) {
|
|
12155
|
+
runCliCommand(["npm", "i", "-g", spec], { timeoutMs: installTimeoutMs() });
|
|
12156
|
+
}
|
|
12157
|
+
function ensureGlobalCli(opts) {
|
|
12158
|
+
const spec = `@mstar-harness/cli@${opts.version}`;
|
|
12159
|
+
const log = opts.log ?? ((line) => console.log(line));
|
|
12160
|
+
if (opts.noGlobalCli) {
|
|
12161
|
+
log("Skipping global CLI install (--no-global-cli).");
|
|
12162
|
+
return { action: "skipped", reason: "flag" };
|
|
12163
|
+
}
|
|
12164
|
+
if (opts.dryRun) {
|
|
12165
|
+
log(`Would run: npm i -g ${spec}`);
|
|
12166
|
+
return { action: "skipped", reason: "dry-run" };
|
|
12167
|
+
}
|
|
12168
|
+
const current = (opts.detectVersion ?? defaultDetectVersion)();
|
|
12169
|
+
if (current === opts.version) {
|
|
12170
|
+
log(`${CLI_NAME} ${current} already on PATH.`);
|
|
12171
|
+
return { action: "skipped", reason: "already-matching" };
|
|
12172
|
+
}
|
|
12173
|
+
try {
|
|
12174
|
+
(opts.install ?? defaultInstall)(spec);
|
|
12175
|
+
log(`Installed ${spec} globally.`);
|
|
12176
|
+
return { action: "installed", spec };
|
|
12177
|
+
} catch (err) {
|
|
12178
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
12179
|
+
const kind = /EACCES|EPERM/.test(message) ? "permission" : "npm";
|
|
12180
|
+
log(`Global CLI install failed: ${message}`);
|
|
12181
|
+
log(`Failure kind: ${kind} (retry: npm i -g ${spec})`);
|
|
12182
|
+
log(`Hint: run \`mstar-harness doctor\` to check the CLI on PATH.`);
|
|
12183
|
+
return { action: "failed", spec, message };
|
|
12184
|
+
}
|
|
12185
|
+
}
|
|
12186
|
+
function formatCliDoctorNote(detected, expected) {
|
|
12187
|
+
if (detected === null) {
|
|
12188
|
+
return `CLI on PATH: ${CLI_NAME} not found (expected version ${expected})`;
|
|
12189
|
+
}
|
|
12190
|
+
if (detected !== expected) {
|
|
12191
|
+
return `CLI on PATH: ${CLI_NAME} ${detected} (expected ${expected})`;
|
|
12192
|
+
}
|
|
12193
|
+
return `CLI on PATH: ${CLI_NAME} ${detected} (matching)`;
|
|
12194
|
+
}
|
|
12195
|
+
|
|
11709
12196
|
// src/types.ts
|
|
11710
12197
|
var SUPPORTED_TARGETS = ["opencode", "cursor", "codex", "zcode", "omp", "dsh"];
|
|
11711
12198
|
|
|
@@ -11811,6 +12298,7 @@ async function runInit5(options) {
|
|
|
11811
12298
|
for (const note of installResult.notes) {
|
|
11812
12299
|
console.log(` - ${note}`);
|
|
11813
12300
|
}
|
|
12301
|
+
ensureGlobalCli({ version: packageVersion, dryRun: !!options.dryRun, noGlobalCli: !!options.noGlobalCli });
|
|
11814
12302
|
return;
|
|
11815
12303
|
}
|
|
11816
12304
|
const useExplicitModels = hasExplicitModelFlags(options);
|
|
@@ -11854,12 +12342,14 @@ async function runInit5(options) {
|
|
|
11854
12342
|
console.log(` - ${roleId}: ${modelId}`);
|
|
11855
12343
|
}
|
|
11856
12344
|
}
|
|
12345
|
+
ensureGlobalCli({ version: packageVersion, dryRun: !!options.dryRun, noGlobalCli: !!options.noGlobalCli });
|
|
11857
12346
|
}
|
|
11858
12347
|
function runDoctor5(options) {
|
|
11859
12348
|
const target = options.target || "opencode";
|
|
11860
12349
|
const adapter = getAdapter(target);
|
|
11861
12350
|
const scope = options.scope || "project";
|
|
11862
12351
|
console.log(`Target: ${target}`);
|
|
12352
|
+
console.log(formatCliDoctorNote(defaultDetectVersion(), packageVersion));
|
|
11863
12353
|
if (adapter.mode === "install") {
|
|
11864
12354
|
const result = adapter.runInstallDoctor?.(scope);
|
|
11865
12355
|
if (!result) {
|
|
@@ -11947,7 +12437,7 @@ var CANONICAL_NEGATIONS = {
|
|
|
11947
12437
|
};
|
|
11948
12438
|
function gitWorkspaceRoot(startDir) {
|
|
11949
12439
|
try {
|
|
11950
|
-
const cdup =
|
|
12440
|
+
const cdup = execFileSync10("git", ["rev-parse", "--show-cdup"], {
|
|
11951
12441
|
cwd: startDir,
|
|
11952
12442
|
encoding: "utf8",
|
|
11953
12443
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -12196,8 +12686,12 @@ function runScaffold(pathArg) {
|
|
|
12196
12686
|
console.log(` skipped: ${item}`);
|
|
12197
12687
|
}
|
|
12198
12688
|
program2.name("mstar-harness").description("Morning Star harness CLI for target-based agent bootstrap").version(packageVersion);
|
|
12199
|
-
program2.command("init").description("Interactive/non-interactive setup for target agent bootstrap").option("-y, --yes", "Non-interactive mode").option("--target <target>", "Install target", "opencode").option("--scope <scope>", "Config scope: global|project (default: project)").option("--output <path>", "Config file path override, relative to project root").option("--dry-run", "Preview result without writing config").option("--no-fallbacks", "Skip installing the dsh-llm-fallbacks plugin (dsh target only)").option("--pm-model <model>", "Optional: model for project-manager (advanced override)").option("--strategic-models <a,b,c>", "Optional: models for architect/product-manager/prompt-engineer").option("--dev-models <a,b,c>", "Optional: models for fullstack-dev/fullstack-dev-2/frontend-dev").option("--qc-models <a,b,c>", "Optional: models for qc trio").option("--other-models <a,b,c>", "Optional: models for remaining roles").action(async (options) => {
|
|
12200
|
-
await runInit5({
|
|
12689
|
+
program2.command("init").description("Interactive/non-interactive setup for target agent bootstrap").option("-y, --yes", "Non-interactive mode").option("--target <target>", "Install target", "opencode").option("--scope <scope>", "Config scope: global|project (default: project)").option("--output <path>", "Config file path override, relative to project root").option("--dry-run", "Preview result without writing config").option("--no-fallbacks", "Skip installing the dsh-llm-fallbacks plugin (dsh target only)").option("--no-global-cli", "Skip installing the matching-version @mstar-harness/cli globally after init").option("--pm-model <model>", "Optional: model for project-manager (advanced override)").option("--strategic-models <a,b,c>", "Optional: models for architect/product-manager/prompt-engineer").option("--dev-models <a,b,c>", "Optional: models for fullstack-dev/fullstack-dev-2/frontend-dev").option("--qc-models <a,b,c>", "Optional: models for qc trio").option("--other-models <a,b,c>", "Optional: models for remaining roles").action(async (options) => {
|
|
12690
|
+
await runInit5({
|
|
12691
|
+
...options,
|
|
12692
|
+
noFallbacks: options.fallbacks === false,
|
|
12693
|
+
noGlobalCli: options.globalCli === false
|
|
12694
|
+
});
|
|
12201
12695
|
});
|
|
12202
12696
|
var harnessCommand = program2.command("harness").description("harness bootstrap / dir-resolution helpers (engine-backed)");
|
|
12203
12697
|
harnessCommand.command("scaffold").description("One-shot harness bootstrap: create the harness dir (default .mstar/, honoring .mstarc harness_dir/project_dir) " + "with dirs + v2 status.json + projects/_default/, append the canonical .gitignore snippet when absent " + "(skipped for non-.mstar layouts), and write a minimal {HARNESS_DIR}/AGENTS.md when absent").argument("[path]", "Root to scaffold (default: cwd)").action((pathArg) => {
|
|
@@ -12342,6 +12836,8 @@ statusCommand.command("backlog-register").description("Register deferred-PR back
|
|
|
12342
12836
|
}
|
|
12343
12837
|
const projectId = sanitizeProjectId(options.project ?? _DEFAULT_PROJECT);
|
|
12344
12838
|
const projectRoot = resolveProjectDir(process.cwd(), options.harness ? { harnessDir: options.harness } : {});
|
|
12839
|
+
if (options.harness)
|
|
12840
|
+
setArtifactStore(createFsStore(options.harness));
|
|
12345
12841
|
const projectDir = path12.join(projectRoot, projectId);
|
|
12346
12842
|
const registeredAt = todayString5();
|
|
12347
12843
|
const entries = entriesRaw.map((raw, index) => {
|
|
@@ -12367,6 +12863,8 @@ statusCommand.command("backlog-close").description("Close one project-register b
|
|
|
12367
12863
|
try {
|
|
12368
12864
|
const projectId = sanitizeProjectId(options.project ?? _DEFAULT_PROJECT);
|
|
12369
12865
|
const projectRoot = resolveProjectDir(process.cwd(), options.harness ? { harnessDir: options.harness } : {});
|
|
12866
|
+
if (options.harness)
|
|
12867
|
+
setArtifactStore(createFsStore(options.harness));
|
|
12370
12868
|
const projectDir = path12.join(projectRoot, projectId);
|
|
12371
12869
|
await closeProjectRegisterEntry({
|
|
12372
12870
|
projectDir,
|
|
@@ -12387,6 +12885,143 @@ statusCommand.command("archive-residuals").description("Removed in v3 \u2014 res
|
|
|
12387
12885
|
var migrateCommand = program2.command("migrate").description("Migrate a v1 {HARNESS_DIR} status.json tree to v2 (engine-backed; exit 0 ok/idempotent no-op, 1 plan-invalid, 2 apply-failure)").option("--dry-run", "Print the migration step plan (source \u2192 destination) + planned-document validation warnings without writing anything").option("--path <root>", "Harness root to migrate (default: resolved {HARNESS_DIR}, else cwd)").option("--json", "Machine-readable JSON output").action(async (options) => {
|
|
12388
12886
|
await runMigrateCommand(options);
|
|
12389
12887
|
});
|
|
12888
|
+
var persistCommand = program2.command("persist").description("Persist one JSON coordination doc through the ArtifactStore (status / snapshot / residuals / review / json). " + "Faces: put (default), get [--validate], list (keys only, no header), delete (idempotent). " + "The default FsStore resolves the harness dir from the cwd / MSTAR_HARNESS_DIR; --store <module> or " + "MSTAR_STORE_MODULE injects a module-backed store (filesystem paths only) for the process.");
|
|
12889
|
+
var PERSIST_KINDS = ["status", "snapshot", "residuals", "review", "json"];
|
|
12890
|
+
function parsePersistKind(kind) {
|
|
12891
|
+
if (PERSIST_KINDS.includes(kind))
|
|
12892
|
+
return kind;
|
|
12893
|
+
throw new SddScriptError(`usage: persist <kind> --key <key> [--file <path>|--stdin] [--store <module>] [--schema <id>]
|
|
12894
|
+
` + ` persist get <kind> --key <key> [--validate] [--store <module>]
|
|
12895
|
+
` + ` persist list <kind> [--store <module>]
|
|
12896
|
+
` + ` persist delete <kind> --key <key> [--store <module>]
|
|
12897
|
+
` + ` unknown kind ${JSON.stringify(kind)} \u2014 expected status | snapshot | residuals | review | json`, 2);
|
|
12898
|
+
}
|
|
12899
|
+
async function resolvePersistStore(storeFlag) {
|
|
12900
|
+
const modulePath = storeFlag ?? process.env.MSTAR_STORE_MODULE;
|
|
12901
|
+
if (modulePath === undefined)
|
|
12902
|
+
return;
|
|
12903
|
+
setArtifactStore(await loadStoreModule(modulePath));
|
|
12904
|
+
}
|
|
12905
|
+
function readPersistPayload(options) {
|
|
12906
|
+
if (options.file !== undefined && options.stdin === true) {
|
|
12907
|
+
throw new SddScriptError("usage: persist --file <path> and --stdin are mutually exclusive", 2);
|
|
12908
|
+
}
|
|
12909
|
+
if (options.file !== undefined) {
|
|
12910
|
+
const filePath = path12.resolve(options.file);
|
|
12911
|
+
if (!fs8.existsSync(filePath)) {
|
|
12912
|
+
throw new Error(`persist payload file not found: ${filePath}`);
|
|
12913
|
+
}
|
|
12914
|
+
return fs8.readFileSync(filePath, "utf8");
|
|
12915
|
+
}
|
|
12916
|
+
return fs8.readFileSync(0, "utf8");
|
|
12917
|
+
}
|
|
12918
|
+
function validatePersistPayload(kind, payload) {
|
|
12919
|
+
let gate2;
|
|
12920
|
+
if (kind === "status")
|
|
12921
|
+
gate2 = validateStatusV2(payload);
|
|
12922
|
+
else if (kind === "snapshot")
|
|
12923
|
+
gate2 = validateWorkflowSnapshot(payload);
|
|
12924
|
+
else if (kind === "residuals")
|
|
12925
|
+
gate2 = validateProjectRegister(payload);
|
|
12926
|
+
else if (kind === "review")
|
|
12927
|
+
gate2 = validateMstarReviewV1(payload);
|
|
12928
|
+
else
|
|
12929
|
+
return;
|
|
12930
|
+
if (gate2.ok)
|
|
12931
|
+
return;
|
|
12932
|
+
const detail = gate2.violations.map((v) => `[${v.severity}] ${v.code}: ${v.message}`).join("; ");
|
|
12933
|
+
throw new Error(`refusing to persist invalid ${kind} document: ${detail}`);
|
|
12934
|
+
}
|
|
12935
|
+
persistCommand.argument("<kind>", "status | snapshot | residuals | review | json").option("--key <key>", 'Stable key inside the kind (status: "root"; json: absolute file path)').option("--file <path>", "Payload JSON file (default: read stdin)").option("--stdin", "Read the payload JSON from stdin").option("--store <module>", "Store module path (filesystem only; overrides MSTAR_STORE_MODULE)").option("--schema <id>", "Optional schema id stored on the artifact doc (e.g. mstar.review/v1); stored only by store modules that persist it \u2014 the default FsStore rejects it (exit 1)").action(async (kind, options) => {
|
|
12936
|
+
try {
|
|
12937
|
+
const parsedKind = parsePersistKind(kind);
|
|
12938
|
+
if (options.key === undefined) {
|
|
12939
|
+
throw new SddScriptError("usage: persist <kind> --key <key> [--file <path>|--stdin] [--store <module>] [--schema <id>]", 2);
|
|
12940
|
+
}
|
|
12941
|
+
const raw = readPersistPayload(options);
|
|
12942
|
+
let payload;
|
|
12943
|
+
try {
|
|
12944
|
+
payload = JSON.parse(raw);
|
|
12945
|
+
} catch (error) {
|
|
12946
|
+
throw new Error(`persist payload is not valid JSON: ${error.message}`);
|
|
12947
|
+
}
|
|
12948
|
+
validatePersistPayload(parsedKind, payload);
|
|
12949
|
+
await resolvePersistStore(options.store);
|
|
12950
|
+
await getArtifactStore().put({
|
|
12951
|
+
kind: parsedKind,
|
|
12952
|
+
key: options.key,
|
|
12953
|
+
payload,
|
|
12954
|
+
...options.schema !== undefined ? { schema: options.schema } : {}
|
|
12955
|
+
});
|
|
12956
|
+
console.log(import_picocolors2.default.green(`persist ${parsedKind}/${options.key}: OK`));
|
|
12957
|
+
} catch (error) {
|
|
12958
|
+
failScript(error, "persist");
|
|
12959
|
+
}
|
|
12960
|
+
});
|
|
12961
|
+
persistCommand.command("get").description("Print the stored payload JSON for <kind>/<key>, or exit 1 when absent").argument("<kind>", "status | snapshot | residuals | review | json").option("--validate", "Run the kind's validator on the fetched payload (notes on stderr; invalid \u2192 exit 1)").action(async (kind, options, command) => {
|
|
12962
|
+
try {
|
|
12963
|
+
const parsedKind = parsePersistKind(kind);
|
|
12964
|
+
const parentOpts = command.parent?.opts() ?? {};
|
|
12965
|
+
const key = typeof parentOpts.key === "string" ? parentOpts.key : undefined;
|
|
12966
|
+
const store = typeof parentOpts.store === "string" ? parentOpts.store : undefined;
|
|
12967
|
+
if (key === undefined) {
|
|
12968
|
+
throw new SddScriptError("usage: persist get <kind> --key <key> [--validate] [--store <module>]", 2);
|
|
12969
|
+
}
|
|
12970
|
+
await resolvePersistStore(store);
|
|
12971
|
+
const payload = await getArtifactStore().get({ kind: parsedKind, key });
|
|
12972
|
+
if (payload === undefined) {
|
|
12973
|
+
throw new Error(`persist get ${parsedKind}/${key}: no stored document`);
|
|
12974
|
+
}
|
|
12975
|
+
if (options.validate === true) {
|
|
12976
|
+
validatePersistPayload(parsedKind, payload);
|
|
12977
|
+
console.error(parsedKind === "json" ? "json: parse-only" : "validation: ok");
|
|
12978
|
+
}
|
|
12979
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
12980
|
+
} catch (error) {
|
|
12981
|
+
failScript(error, "persist get");
|
|
12982
|
+
}
|
|
12983
|
+
});
|
|
12984
|
+
persistCommand.command("list").description("Print the stored keys for <kind>, one per line, ascending, no header (json is not listable)").argument("<kind>", "status | snapshot | residuals | review | json").action(async (kind, _options, command) => {
|
|
12985
|
+
try {
|
|
12986
|
+
const parsedKind = parsePersistKind(kind);
|
|
12987
|
+
if (parsedKind === "json") {
|
|
12988
|
+
throw new SddScriptError("ArtifactStore json keys are absolute paths and cannot be listed", 2);
|
|
12989
|
+
}
|
|
12990
|
+
const parentOpts = command.parent?.opts() ?? {};
|
|
12991
|
+
const store = typeof parentOpts.store === "string" ? parentOpts.store : undefined;
|
|
12992
|
+
await resolvePersistStore(store);
|
|
12993
|
+
const artifactStore = getArtifactStore();
|
|
12994
|
+
if (typeof artifactStore.list !== "function") {
|
|
12995
|
+
throw new SddScriptError("store does not support list", 2);
|
|
12996
|
+
}
|
|
12997
|
+
const refs = await artifactStore.list(parsedKind);
|
|
12998
|
+
for (const key of refs.map((ref) => ref.key).sort()) {
|
|
12999
|
+
console.log(key);
|
|
13000
|
+
}
|
|
13001
|
+
} catch (error) {
|
|
13002
|
+
failScript(error, "persist list");
|
|
13003
|
+
}
|
|
13004
|
+
});
|
|
13005
|
+
persistCommand.command("delete").description("Delete the stored document for <kind>/<key> (idempotent: absent is a no-op; no prompt)").argument("<kind>", "status | snapshot | residuals | review | json").action(async (kind, _options, command) => {
|
|
13006
|
+
try {
|
|
13007
|
+
const parsedKind = parsePersistKind(kind);
|
|
13008
|
+
const parentOpts = command.parent?.opts() ?? {};
|
|
13009
|
+
const key = typeof parentOpts.key === "string" ? parentOpts.key : undefined;
|
|
13010
|
+
const store = typeof parentOpts.store === "string" ? parentOpts.store : undefined;
|
|
13011
|
+
if (key === undefined) {
|
|
13012
|
+
throw new SddScriptError("usage: persist delete <kind> --key <key> [--store <module>]", 2);
|
|
13013
|
+
}
|
|
13014
|
+
await resolvePersistStore(store);
|
|
13015
|
+
const artifactStore = getArtifactStore();
|
|
13016
|
+
if (typeof artifactStore.delete !== "function") {
|
|
13017
|
+
throw new SddScriptError("store does not support delete", 2);
|
|
13018
|
+
}
|
|
13019
|
+
await artifactStore.delete({ kind: parsedKind, key });
|
|
13020
|
+
console.log(`deleted ${parsedKind}/${key}`);
|
|
13021
|
+
} catch (error) {
|
|
13022
|
+
failScript(error, "persist delete");
|
|
13023
|
+
}
|
|
13024
|
+
});
|
|
12390
13025
|
var leaseCommand = program2.command("lease").description("execution_lease / integration_merge_lease checks (engine-backed)");
|
|
12391
13026
|
function resolveLeaseHarnessDir(harnessArg) {
|
|
12392
13027
|
if (harnessArg)
|
|
@@ -13098,7 +13733,7 @@ function resolveAuditShortSha(cwd, override) {
|
|
|
13098
13733
|
if (override !== undefined && override !== "")
|
|
13099
13734
|
return override;
|
|
13100
13735
|
try {
|
|
13101
|
-
const out =
|
|
13736
|
+
const out = execFileSync10("git", ["rev-parse", "--short", "HEAD"], {
|
|
13102
13737
|
cwd,
|
|
13103
13738
|
encoding: "utf8",
|
|
13104
13739
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -13154,6 +13789,7 @@ auditCommand.command("promote").description("Promote selected audit plans into t
|
|
|
13154
13789
|
throw new Error(`audit dir not found: ${outDir}`);
|
|
13155
13790
|
}
|
|
13156
13791
|
const harnessDir = resolveLeaseHarnessDir(options.harness);
|
|
13792
|
+
setArtifactStore(createFsStore(harnessDir));
|
|
13157
13793
|
const result = await promoteAuditPlans(outDir, selected, {
|
|
13158
13794
|
harnessDir,
|
|
13159
13795
|
...options.workflow !== undefined ? { workflowId: options.workflow } : {}
|
|
@@ -13167,7 +13803,7 @@ auditCommand.command("promote").description("Promote selected audit plans into t
|
|
|
13167
13803
|
function listTrackedFiles(root) {
|
|
13168
13804
|
let out;
|
|
13169
13805
|
try {
|
|
13170
|
-
out =
|
|
13806
|
+
out = execFileSync10("git", ["ls-files", "-z", "--", "."], {
|
|
13171
13807
|
cwd: root,
|
|
13172
13808
|
encoding: "utf8",
|
|
13173
13809
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -13515,17 +14151,17 @@ prReviewCommand.command("validate-report").description("Validate a saved local P
|
|
|
13515
14151
|
}
|
|
13516
14152
|
});
|
|
13517
14153
|
function gitSync(args, cwd) {
|
|
13518
|
-
return
|
|
14154
|
+
return execFileSync10("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
13519
14155
|
}
|
|
13520
14156
|
function ghSync(args, input) {
|
|
13521
|
-
return
|
|
14157
|
+
return execFileSync10("gh", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], ...input !== undefined ? { input } : {} }).trim();
|
|
13522
14158
|
}
|
|
13523
14159
|
function cliToday() {
|
|
13524
14160
|
const iso = new Date().toISOString();
|
|
13525
14161
|
return iso.slice(0, 10);
|
|
13526
14162
|
}
|
|
13527
14163
|
function listLocalBranches(cwd) {
|
|
13528
|
-
const out =
|
|
14164
|
+
const out = execFileSync10("git", ["for-each-ref", "--format=%(refname:short)", "refs/heads"], {
|
|
13529
14165
|
cwd,
|
|
13530
14166
|
encoding: "utf8",
|
|
13531
14167
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -13534,7 +14170,7 @@ function listLocalBranches(cwd) {
|
|
|
13534
14170
|
}
|
|
13535
14171
|
function refResolves(ref, cwd) {
|
|
13536
14172
|
try {
|
|
13537
|
-
|
|
14173
|
+
execFileSync10("git", ["rev-parse", "--verify", "--quiet", ref], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
13538
14174
|
return true;
|
|
13539
14175
|
} catch {
|
|
13540
14176
|
return false;
|
|
@@ -13547,7 +14183,7 @@ function probeChangesetEmpty(diffCmdArgs, cwd, worktreePath) {
|
|
|
13547
14183
|
return false;
|
|
13548
14184
|
let untracked = "";
|
|
13549
14185
|
try {
|
|
13550
|
-
untracked =
|
|
14186
|
+
untracked = execFileSync10("git", ["ls-files", "--others", "--exclude-standard"], {
|
|
13551
14187
|
cwd: worktreePath,
|
|
13552
14188
|
encoding: "utf8",
|
|
13553
14189
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -13565,7 +14201,7 @@ function probeChangesetEmpty(diffCmdArgs, cwd, worktreePath) {
|
|
|
13565
14201
|
}
|
|
13566
14202
|
function gitIf(args, cwd) {
|
|
13567
14203
|
try {
|
|
13568
|
-
return
|
|
14204
|
+
return execFileSync10("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
13569
14205
|
} catch {
|
|
13570
14206
|
return "";
|
|
13571
14207
|
}
|
|
@@ -13690,7 +14326,7 @@ function foldIntoBody(body, entries) {
|
|
|
13690
14326
|
`);
|
|
13691
14327
|
}
|
|
13692
14328
|
function ghApi(apiPath, payload) {
|
|
13693
|
-
return
|
|
14329
|
+
return execFileSync10("gh", ["api", "--method", "POST", apiPath, "--input", "-"], {
|
|
13694
14330
|
encoding: "utf8",
|
|
13695
14331
|
stdio: ["ignore", "pipe", "pipe"],
|
|
13696
14332
|
input: payload
|
|
@@ -13744,7 +14380,7 @@ prReviewCommand.command("worktree-setup").description("Create the isolated revie
|
|
|
13744
14380
|
let headSpec = "";
|
|
13745
14381
|
if (mode === "pr") {
|
|
13746
14382
|
prNumber = Number(options.pr);
|
|
13747
|
-
baseRef =
|
|
14383
|
+
baseRef = execFileSync10("gh", ["pr", "view", String(prNumber), "--json", "baseRefName", "--jq", ".baseRefName"], {
|
|
13748
14384
|
encoding: "utf8",
|
|
13749
14385
|
stdio: ["ignore", "pipe", "pipe"]
|
|
13750
14386
|
}).trim();
|