@delorenj/pjangler 1.5.49 → 1.5.50
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/index.js +1065 -624
- package/dist/mcp-server.js +953 -512
- package/package.json +2 -1
package/dist/mcp-server.js
CHANGED
|
@@ -437,7 +437,7 @@ var init_executionBinding = __esm({
|
|
|
437
437
|
});
|
|
438
438
|
|
|
439
439
|
// src/project/manifestIndex.ts
|
|
440
|
-
import { basename as
|
|
440
|
+
import { basename as basename6, dirname as dirname9, join as join13, resolve as resolve7 } from "node:path";
|
|
441
441
|
function normalizeProjectId2(value) {
|
|
442
442
|
try {
|
|
443
443
|
return normalizeProjectId(value);
|
|
@@ -484,24 +484,24 @@ var init_manifestIndex = __esm({
|
|
|
484
484
|
});
|
|
485
485
|
|
|
486
486
|
// src/lifecycle/preflight.ts
|
|
487
|
-
import { createHash as
|
|
488
|
-
import { accessSync, constants, existsSync as existsSync9, lstatSync as
|
|
487
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
488
|
+
import { accessSync, constants as constants2, existsSync as existsSync9, lstatSync as lstatSync11, readFileSync as readFileSync12, readdirSync as readdirSync9, realpathSync as realpathSync5, statSync as statSync3 } from "node:fs";
|
|
489
489
|
import { tmpdir as tmpdir2, userInfo } from "node:os";
|
|
490
|
-
import { basename as
|
|
490
|
+
import { basename as basename7, delimiter, dirname as dirname10, isAbsolute as isAbsolute5, join as join14, relative as relative10, resolve as resolve8 } from "node:path";
|
|
491
491
|
import YAML5 from "yaml";
|
|
492
492
|
function containedBy(parent, candidate) {
|
|
493
493
|
const rel = relative10(resolve8(parent), resolve8(candidate));
|
|
494
494
|
return rel === "" || !rel.startsWith("..") && !isAbsolute5(rel);
|
|
495
495
|
}
|
|
496
496
|
function accountMiseShimsDir(home) {
|
|
497
|
-
return resolve8(
|
|
497
|
+
return resolve8(join14(home, ".local", "share", "mise", "shims"));
|
|
498
498
|
}
|
|
499
499
|
function inactiveMiseShim(candidate, shimsDir) {
|
|
500
500
|
if (dirname10(candidate) !== shimsDir) return void 0;
|
|
501
501
|
try {
|
|
502
|
-
if (!
|
|
502
|
+
if (!lstatSync11(candidate).isSymbolicLink()) return void 0;
|
|
503
503
|
const realPath = realpathSync5(candidate);
|
|
504
|
-
if (
|
|
504
|
+
if (basename7(realPath) !== "mise") return void 0;
|
|
505
505
|
return { path: candidate, realPath, reason: "inactive-mise-shim" };
|
|
506
506
|
} catch {
|
|
507
507
|
return void 0;
|
|
@@ -513,8 +513,8 @@ function resolveCopierOnPath(env2, shimsDir) {
|
|
|
513
513
|
const entry = rawEntry || process.cwd();
|
|
514
514
|
const candidate = resolve8(entry, process.platform === "win32" ? "copier.exe" : "copier");
|
|
515
515
|
try {
|
|
516
|
-
accessSync(candidate,
|
|
517
|
-
const stat =
|
|
516
|
+
accessSync(candidate, constants2.X_OK);
|
|
517
|
+
const stat = lstatSync11(candidate);
|
|
518
518
|
if (!stat.isFile() && !stat.isSymbolicLink()) continue;
|
|
519
519
|
} catch {
|
|
520
520
|
continue;
|
|
@@ -532,7 +532,7 @@ function describeSkipped(skipped) {
|
|
|
532
532
|
return skipped.map((entry) => `${entry.path} -> ${entry.realPath}`).join(", ");
|
|
533
533
|
}
|
|
534
534
|
function sha256(path) {
|
|
535
|
-
return
|
|
535
|
+
return createHash9("sha256").update(readFileSync12(path)).digest("base64url");
|
|
536
536
|
}
|
|
537
537
|
function fingerprint(path) {
|
|
538
538
|
const absolute = resolve8(path);
|
|
@@ -570,7 +570,7 @@ function sameFingerprint(expected) {
|
|
|
570
570
|
function consoleScriptContract(path) {
|
|
571
571
|
let text;
|
|
572
572
|
try {
|
|
573
|
-
text =
|
|
573
|
+
text = readFileSync12(path, "utf8").slice(0, 32 * 1024);
|
|
574
574
|
} catch (error) {
|
|
575
575
|
return { ok: false, error: `cannot read Copier launcher: ${error instanceof Error ? error.message : String(error)}` };
|
|
576
576
|
}
|
|
@@ -581,7 +581,7 @@ function consoleScriptContract(path) {
|
|
|
581
581
|
return { ok: false, error: "Copier launcher must use one absolute Python interpreter" };
|
|
582
582
|
}
|
|
583
583
|
const interpreterPath = resolve8(shebang[0]);
|
|
584
|
-
const interpreter =
|
|
584
|
+
const interpreter = basename7(interpreterPath);
|
|
585
585
|
if (!/^python(?:\d+(?:\.\d+)*)?$/.test(interpreter)) {
|
|
586
586
|
return { ok: false, error: "Copier launcher is not an absolute Python console script" };
|
|
587
587
|
}
|
|
@@ -592,16 +592,16 @@ function consoleScriptContract(path) {
|
|
|
592
592
|
}
|
|
593
593
|
function defaultUvToolRoots(home) {
|
|
594
594
|
return [
|
|
595
|
-
|
|
596
|
-
|
|
595
|
+
join14(home, ".local", "share", "uv", "tools", "copier"),
|
|
596
|
+
join14(home, "Library", "Application Support", "uv", "tools", "copier")
|
|
597
597
|
].map((path) => resolve8(path));
|
|
598
598
|
}
|
|
599
599
|
function locateUvSitePackages(toolRoot) {
|
|
600
|
-
const lib =
|
|
600
|
+
const lib = join14(toolRoot, "lib");
|
|
601
601
|
const candidates = [];
|
|
602
|
-
for (const entry of
|
|
602
|
+
for (const entry of readdirSync9(lib, { withFileTypes: true })) {
|
|
603
603
|
if (!entry.isDirectory() || !/^python\d+(?:\.\d+)*$/.test(entry.name)) continue;
|
|
604
|
-
const sitePackages =
|
|
604
|
+
const sitePackages = join14(lib, entry.name, "site-packages");
|
|
605
605
|
if (existsSync9(sitePackages)) candidates.push(sitePackages);
|
|
606
606
|
}
|
|
607
607
|
if (candidates.length !== 1) throw new Error(`expected one UV Copier site-packages directory, found ${candidates.length}`);
|
|
@@ -618,13 +618,13 @@ function parseRecordLine(line) {
|
|
|
618
618
|
};
|
|
619
619
|
}
|
|
620
620
|
function attestUvCopier(candidate, realCandidate, home) {
|
|
621
|
-
const toolRoot = defaultUvToolRoots(home).find((root) => realCandidate ===
|
|
621
|
+
const toolRoot = defaultUvToolRoots(home).find((root) => realCandidate === join14(root, "bin", "copier"));
|
|
622
622
|
if (!toolRoot) {
|
|
623
623
|
return { ok: false, error: `refusing untrusted PATH-shadowed Copier executable: ${candidate}` };
|
|
624
624
|
}
|
|
625
625
|
const supportedEntries = new Set([
|
|
626
|
-
|
|
627
|
-
|
|
626
|
+
join14(home, ".local", "bin", "copier"),
|
|
627
|
+
join14(toolRoot, "bin", "copier")
|
|
628
628
|
].map((path) => resolve8(path)));
|
|
629
629
|
if (!supportedEntries.has(resolve8(candidate))) {
|
|
630
630
|
return { ok: false, error: `refusing non-canonical UV Copier entry point: ${candidate}` };
|
|
@@ -634,46 +634,46 @@ function attestUvCopier(candidate, realCandidate, home) {
|
|
|
634
634
|
if (!launcher.ok || !launcher.interpreter) {
|
|
635
635
|
return { ...launcher, executable: realCandidate, realExecutable: realCandidate };
|
|
636
636
|
}
|
|
637
|
-
const expectedInterpreter =
|
|
637
|
+
const expectedInterpreter = join14(toolRoot, "bin", basename7(launcher.interpreter));
|
|
638
638
|
if (resolve8(launcher.interpreter) !== resolve8(expectedInterpreter)) {
|
|
639
639
|
return { ok: false, error: "UV Copier launcher interpreter is outside the attested tool environment" };
|
|
640
640
|
}
|
|
641
641
|
const interpreterReal = realpathSync5(launcher.interpreter);
|
|
642
642
|
const uvPythonRoots = [
|
|
643
|
-
|
|
644
|
-
|
|
643
|
+
join14(home, ".local", "share", "uv", "python"),
|
|
644
|
+
join14(home, "Library", "Application Support", "uv", "python")
|
|
645
645
|
];
|
|
646
646
|
if (!uvPythonRoots.some((root) => containedBy(root, interpreterReal))) {
|
|
647
647
|
return { ok: false, error: "UV Copier interpreter is not managed by the canonical UV Python installation" };
|
|
648
648
|
}
|
|
649
|
-
const receiptPath =
|
|
650
|
-
const receipt =
|
|
649
|
+
const receiptPath = join14(toolRoot, "uv-receipt.toml");
|
|
650
|
+
const receipt = readFileSync12(receiptPath, "utf8");
|
|
651
651
|
if (!/requirements\s*=\s*\[[\s\S]*?name\s*=\s*["']copier["']/.test(receipt) || !/entrypoints\s*=\s*\[[\s\S]*?name\s*=\s*["']copier["'][\s\S]*?from\s*=\s*["']copier["']/.test(receipt)) {
|
|
652
652
|
return { ok: false, error: "UV tool receipt does not bind the copier entry point to the Copier package" };
|
|
653
653
|
}
|
|
654
654
|
const sitePackages = locateUvSitePackages(toolRoot);
|
|
655
|
-
const distInfos =
|
|
655
|
+
const distInfos = readdirSync9(sitePackages, { withFileTypes: true }).filter((entry) => entry.isDirectory() && /^copier-[^-]+\.dist-info$/i.test(entry.name)).map((entry) => join14(sitePackages, entry.name));
|
|
656
656
|
if (distInfos.length !== 1) {
|
|
657
657
|
return { ok: false, error: `expected one installed Copier distribution, found ${distInfos.length}` };
|
|
658
658
|
}
|
|
659
659
|
const distInfo = realpathSync5(distInfos[0]);
|
|
660
|
-
const metadataPath =
|
|
661
|
-
const entryPointsPath =
|
|
662
|
-
const recordPath =
|
|
663
|
-
const metadata =
|
|
660
|
+
const metadataPath = join14(distInfo, "METADATA");
|
|
661
|
+
const entryPointsPath = join14(distInfo, "entry_points.txt");
|
|
662
|
+
const recordPath = join14(distInfo, "RECORD");
|
|
663
|
+
const metadata = readFileSync12(metadataPath, "utf8");
|
|
664
664
|
const name = metadata.match(/^Name:\s*(.+)$/mi)?.[1]?.trim();
|
|
665
665
|
const version = metadata.match(/^Version:\s*(.+)$/mi)?.[1]?.trim();
|
|
666
666
|
if (name?.toLowerCase() !== "copier" || !version || !/^9(?:\.|$)/.test(version)) {
|
|
667
667
|
return { ok: false, error: "installed distribution is not a Copier 9 package" };
|
|
668
668
|
}
|
|
669
|
-
const entryPoints =
|
|
669
|
+
const entryPoints = readFileSync12(entryPointsPath, "utf8");
|
|
670
670
|
if (!/^copier\s*=\s*copier\.__main__:CopierApp\.run\s*$/m.test(entryPoints)) {
|
|
671
671
|
return { ok: false, error: "installed Copier distribution has an unexpected console entry point" };
|
|
672
672
|
}
|
|
673
|
-
const recordEntries =
|
|
673
|
+
const recordEntries = readFileSync12(recordPath, "utf8").split(/\r?\n/).map(parseRecordLine).filter((entry) => Boolean(entry));
|
|
674
674
|
const selected = recordEntries.filter((entry) => {
|
|
675
675
|
const normalized = entry.relativePath.replaceAll("\\", "/");
|
|
676
|
-
return normalized.startsWith("copier/") || normalized === `${
|
|
676
|
+
return normalized.startsWith("copier/") || normalized === `${basename7(distInfo)}/METADATA` || normalized === `${basename7(distInfo)}/entry_points.txt` || normalized === "../../../bin/copier";
|
|
677
677
|
});
|
|
678
678
|
if (!selected.some((entry) => entry.relativePath === "../../../bin/copier") || !selected.some((entry) => entry.relativePath.replaceAll("\\", "/") === "copier/__main__.py")) {
|
|
679
679
|
return { ok: false, error: "Copier RECORD does not bind its launcher and package entry point" };
|
|
@@ -690,7 +690,7 @@ function attestUvCopier(candidate, realCandidate, home) {
|
|
|
690
690
|
}
|
|
691
691
|
attestedFiles.set(resolve8(path), fingerprint(path));
|
|
692
692
|
}
|
|
693
|
-
for (const path of [realCandidate,
|
|
693
|
+
for (const path of [realCandidate, join14(toolRoot, "pyvenv.cfg"), receiptPath, metadataPath, entryPointsPath, recordPath]) {
|
|
694
694
|
attestedFiles.set(resolve8(path), fingerprint(path));
|
|
695
695
|
}
|
|
696
696
|
const identity = {
|
|
@@ -765,18 +765,18 @@ function regularContainedFile(root, path, label) {
|
|
|
765
765
|
const rootReal = realpathSync5(root);
|
|
766
766
|
const fileReal = realpathSync5(path);
|
|
767
767
|
if (!containedBy(rootReal, fileReal)) return { ok: false, error: `${label} escapes its vendored template root` };
|
|
768
|
-
if (!
|
|
768
|
+
if (!lstatSync11(path).isFile()) return { ok: false, error: `${label} is not a regular file` };
|
|
769
769
|
return { ok: true };
|
|
770
770
|
} catch (error) {
|
|
771
771
|
return { ok: false, error: `${label} is unavailable: ${error instanceof Error ? error.message : String(error)}` };
|
|
772
772
|
}
|
|
773
773
|
}
|
|
774
774
|
function parseCopierConfig(templateRoot, label) {
|
|
775
|
-
const configPath =
|
|
775
|
+
const configPath = join14(templateRoot, "copier.yml");
|
|
776
776
|
const file = regularContainedFile(templateRoot, configPath, `${label} copier.yml`);
|
|
777
777
|
if (!file.ok) return { result: file };
|
|
778
778
|
try {
|
|
779
|
-
const parsed = YAML5.parse(
|
|
779
|
+
const parsed = YAML5.parse(readFileSync12(configPath, "utf8"));
|
|
780
780
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
781
781
|
return { result: { ok: false, error: `${label} copier.yml must contain a mapping` } };
|
|
782
782
|
}
|
|
@@ -794,13 +794,13 @@ function parseCopierConfig(templateRoot, label) {
|
|
|
794
794
|
}
|
|
795
795
|
function requireFiles(templateRoot, files, label) {
|
|
796
796
|
for (const rel of files) {
|
|
797
|
-
const result2 = regularContainedFile(templateRoot,
|
|
797
|
+
const result2 = regularContainedFile(templateRoot, join14(templateRoot, rel), `${label} ${rel}`);
|
|
798
798
|
if (!result2.ok) return result2;
|
|
799
799
|
}
|
|
800
800
|
return { ok: true };
|
|
801
801
|
}
|
|
802
802
|
function preflightCommonProjectTemplate(pjanglerRoot) {
|
|
803
|
-
const templateRoot =
|
|
803
|
+
const templateRoot = join14(resolve8(pjanglerRoot), "templates", "commonproject");
|
|
804
804
|
const parsed = parseCopierConfig(templateRoot, "CommonProject template");
|
|
805
805
|
if (!parsed.result.ok) return parsed.result;
|
|
806
806
|
const files = requireFiles(templateRoot, [
|
|
@@ -811,7 +811,7 @@ function preflightCommonProjectTemplate(pjanglerRoot) {
|
|
|
811
811
|
"template/mise.toml.jinja"
|
|
812
812
|
], "CommonProject template");
|
|
813
813
|
if (!files.ok) return files;
|
|
814
|
-
const projectJson =
|
|
814
|
+
const projectJson = readFileSync12(join14(templateRoot, "template", ".project.json.jinja"), "utf8");
|
|
815
815
|
for (const key of ["project_name", "repo_path", "ticket_provider", "agents"]) {
|
|
816
816
|
if (!projectJson.includes(`"${key}"`)) return { ok: false, error: `CommonProject projection is missing ${key}` };
|
|
817
817
|
}
|
|
@@ -836,12 +836,12 @@ var init_preflight = __esm({
|
|
|
836
836
|
});
|
|
837
837
|
|
|
838
838
|
// src/project/boardUrl.ts
|
|
839
|
-
import { existsSync as existsSync10, readFileSync as
|
|
840
|
-
import { dirname as dirname11, isAbsolute as isAbsolute6, join as
|
|
839
|
+
import { existsSync as existsSync10, readFileSync as readFileSync13, statSync as statSync4 } from "node:fs";
|
|
840
|
+
import { dirname as dirname11, isAbsolute as isAbsolute6, join as join15, resolve as resolve9 } from "node:path";
|
|
841
841
|
function findProjectRoot(from) {
|
|
842
842
|
let dir = resolve9(from);
|
|
843
843
|
for (; ; ) {
|
|
844
|
-
if (existsSync10(
|
|
844
|
+
if (existsSync10(join15(dir, ".project.json"))) return dir;
|
|
845
845
|
const parent = dirname11(dir);
|
|
846
846
|
if (parent === dir) return void 0;
|
|
847
847
|
dir = parent;
|
|
@@ -854,8 +854,8 @@ var init_boardUrl = __esm({
|
|
|
854
854
|
});
|
|
855
855
|
|
|
856
856
|
// src/utils/version.ts
|
|
857
|
-
import { readFileSync as
|
|
858
|
-
import { dirname as dirname12, join as
|
|
857
|
+
import { readFileSync as readFileSync14 } from "node:fs";
|
|
858
|
+
import { dirname as dirname12, join as join16 } from "node:path";
|
|
859
859
|
import { fileURLToPath } from "node:url";
|
|
860
860
|
var PJANGLER_VERSION;
|
|
861
861
|
var init_version = __esm({
|
|
@@ -866,7 +866,7 @@ var init_version = __esm({
|
|
|
866
866
|
let dir = dirname12(fileURLToPath(import.meta.url));
|
|
867
867
|
for (let i = 0; i < 4; i++) {
|
|
868
868
|
try {
|
|
869
|
-
const raw =
|
|
869
|
+
const raw = readFileSync14(join16(dir, "package.json"), "utf8");
|
|
870
870
|
return JSON.parse(raw).version ?? "0.0.0";
|
|
871
871
|
} catch {
|
|
872
872
|
const parent = dirname12(dir);
|
|
@@ -896,9 +896,9 @@ var init_boardQuery = __esm({
|
|
|
896
896
|
|
|
897
897
|
// src/project/index.ts
|
|
898
898
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
899
|
-
import { chmodSync as chmodSync4, closeSync as
|
|
899
|
+
import { chmodSync as chmodSync4, closeSync as closeSync3, copyFileSync as copyFileSync2, existsSync as existsSync11, fchmodSync as fchmodSync2, fsyncSync, lstatSync as lstatSync12, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync3, openSync as openSync3, readFileSync as readFileSync15, realpathSync as realpathSync6, renameSync as renameSync5, rmSync as rmSync6, statSync as statSync5, unlinkSync as unlinkSync7, writeFileSync as writeFileSync10 } from "node:fs";
|
|
900
900
|
import { homedir as homedir5, tmpdir as tmpdir3 } from "node:os";
|
|
901
|
-
import { basename as
|
|
901
|
+
import { basename as basename8, delimiter as delimiter2, dirname as dirname13, isAbsolute as isAbsolute7, join as join17, relative as relative11, resolve as resolve10, sep as sep3, win32 } from "node:path";
|
|
902
902
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
903
903
|
import YAML6 from "yaml";
|
|
904
904
|
function normalizeProjectType(value, label = "Project type") {
|
|
@@ -908,7 +908,7 @@ function normalizeProjectType(value, label = "Project type") {
|
|
|
908
908
|
}
|
|
909
909
|
function looksLikeObsidianPlugin(dir) {
|
|
910
910
|
try {
|
|
911
|
-
const manifest = JSON.parse(
|
|
911
|
+
const manifest = JSON.parse(readFileSync15(join17(dir, "manifest.json"), "utf8"));
|
|
912
912
|
return isRecord2(manifest) && typeof manifest.id === "string" && typeof manifest.minAppVersion === "string";
|
|
913
913
|
} catch {
|
|
914
914
|
return false;
|
|
@@ -916,10 +916,10 @@ function looksLikeObsidianPlugin(dir) {
|
|
|
916
916
|
}
|
|
917
917
|
function assertTemplateOffersProjectType(pjanglerRoot, projectType) {
|
|
918
918
|
if (projectType === "base") return;
|
|
919
|
-
const copierYml =
|
|
919
|
+
const copierYml = join17(pjanglerRoot, "templates", "commonproject", "copier.yml");
|
|
920
920
|
let choices;
|
|
921
921
|
try {
|
|
922
|
-
const parsed = YAML6.parse(
|
|
922
|
+
const parsed = YAML6.parse(readFileSync15(copierYml, "utf8"));
|
|
923
923
|
choices = isRecord2(parsed) && isRecord2(parsed.project_type) ? parsed.project_type.choices : void 0;
|
|
924
924
|
} catch {
|
|
925
925
|
choices = void 0;
|
|
@@ -942,9 +942,9 @@ function carriedAutomation(automation) {
|
|
|
942
942
|
return Object.keys(rest).length ? { automation: rest } : {};
|
|
943
943
|
}
|
|
944
944
|
function synchronizeCopierIdentity(manifestPath, manifest) {
|
|
945
|
-
const answersPath =
|
|
945
|
+
const answersPath = join17(dirname13(manifestPath), ".copier-answers.yml");
|
|
946
946
|
if (!existsSync11(answersPath)) return [];
|
|
947
|
-
const current =
|
|
947
|
+
const current = readFileSync15(answersPath, "utf8");
|
|
948
948
|
const document = YAML6.parseDocument(current);
|
|
949
949
|
if (document.errors.length) return [];
|
|
950
950
|
const name = String(document.get("project_name") ?? "");
|
|
@@ -954,7 +954,7 @@ function synchronizeCopierIdentity(manifestPath, manifest) {
|
|
|
954
954
|
document.set("project_description", manifest.project_description);
|
|
955
955
|
const next = String(document);
|
|
956
956
|
if (next === current) return [];
|
|
957
|
-
|
|
957
|
+
writeFileSync10(answersPath, next, "utf8");
|
|
958
958
|
return [answersPath];
|
|
959
959
|
}
|
|
960
960
|
function projectRegistryPath(env2 = process.env) {
|
|
@@ -978,7 +978,7 @@ function loadProjectRegistry(path = projectRegistryPath()) {
|
|
|
978
978
|
return registry2;
|
|
979
979
|
}
|
|
980
980
|
if (!existsSync11(path)) return emptyProjectRegistry();
|
|
981
|
-
const raw = YAML6.parse(
|
|
981
|
+
const raw = YAML6.parse(readFileSync15(path, "utf8"));
|
|
982
982
|
if (raw == null) return emptyProjectRegistry();
|
|
983
983
|
if (!isRecord2(raw)) throw new Error(`Project registry must be a mapping: ${path}`);
|
|
984
984
|
const registry = raw;
|
|
@@ -1046,11 +1046,11 @@ function mergeYamlMapping(document, path, desired, ownedKeys = []) {
|
|
|
1046
1046
|
}
|
|
1047
1047
|
function fsyncDirectory(path) {
|
|
1048
1048
|
try {
|
|
1049
|
-
const fd =
|
|
1049
|
+
const fd = openSync3(path, "r");
|
|
1050
1050
|
try {
|
|
1051
1051
|
fsyncSync(fd);
|
|
1052
1052
|
} finally {
|
|
1053
|
-
|
|
1053
|
+
closeSync3(fd);
|
|
1054
1054
|
}
|
|
1055
1055
|
} catch {
|
|
1056
1056
|
}
|
|
@@ -1064,14 +1064,14 @@ function saveProjectRegistry(registry, path = projectRegistryPath()) {
|
|
|
1064
1064
|
return;
|
|
1065
1065
|
}
|
|
1066
1066
|
validateProjectRegistry(registry);
|
|
1067
|
-
|
|
1067
|
+
mkdirSync8(dirname13(path), { recursive: true });
|
|
1068
1068
|
let text;
|
|
1069
1069
|
let mode = 420;
|
|
1070
1070
|
if (existsSync11(path)) {
|
|
1071
|
-
const stat =
|
|
1071
|
+
const stat = lstatSync12(path);
|
|
1072
1072
|
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`Project registry must be a regular file: ${path}`);
|
|
1073
1073
|
mode = stat.mode & 511;
|
|
1074
|
-
const current =
|
|
1074
|
+
const current = readFileSync15(path, "utf8");
|
|
1075
1075
|
const document = YAML6.parseDocument(current);
|
|
1076
1076
|
if (document.errors.length) throw new Error(`Project registry YAML is invalid: ${path}`);
|
|
1077
1077
|
setYamlLeaf(document, ["schema_version"], registry.schema_version);
|
|
@@ -1092,22 +1092,22 @@ function saveProjectRegistry(registry, path = projectRegistryPath()) {
|
|
|
1092
1092
|
const temp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
1093
1093
|
let fd;
|
|
1094
1094
|
try {
|
|
1095
|
-
fd =
|
|
1096
|
-
|
|
1097
|
-
|
|
1095
|
+
fd = openSync3(temp, "wx", mode);
|
|
1096
|
+
writeFileSync10(fd, text, "utf8");
|
|
1097
|
+
fchmodSync2(fd, mode);
|
|
1098
1098
|
fsyncSync(fd);
|
|
1099
|
-
|
|
1099
|
+
closeSync3(fd);
|
|
1100
1100
|
fd = void 0;
|
|
1101
1101
|
renameSync5(temp, path);
|
|
1102
1102
|
chmodSync4(path, mode);
|
|
1103
1103
|
fsyncDirectory(dirname13(path));
|
|
1104
1104
|
} catch (error) {
|
|
1105
1105
|
if (fd !== void 0) try {
|
|
1106
|
-
|
|
1106
|
+
closeSync3(fd);
|
|
1107
1107
|
} catch {
|
|
1108
1108
|
}
|
|
1109
1109
|
try {
|
|
1110
|
-
|
|
1110
|
+
unlinkSync7(temp);
|
|
1111
1111
|
} catch {
|
|
1112
1112
|
}
|
|
1113
1113
|
throw error;
|
|
@@ -1215,18 +1215,18 @@ function ticketProviderKeyVars(provider, workspace) {
|
|
|
1215
1215
|
return ticketProviderKeyGroups(provider, workspace).flat();
|
|
1216
1216
|
}
|
|
1217
1217
|
function ticketProviderSecretsPath(env2 = process.env) {
|
|
1218
|
-
const base = env2.XDG_CONFIG_HOME ||
|
|
1219
|
-
return
|
|
1218
|
+
const base = env2.XDG_CONFIG_HOME || join17(env2.HOME || homedir5(), ".config");
|
|
1219
|
+
return join17(base, "zshyzsh", "secrets.zsh");
|
|
1220
1220
|
}
|
|
1221
1221
|
function ticketProviderFleetEnvPath(env2 = process.env) {
|
|
1222
|
-
return env2.HERMES_FLEET_ENV?.trim() ||
|
|
1222
|
+
return env2.HERMES_FLEET_ENV?.trim() || join17(env2.HOME || homedir5(), ".hermes", "fleet.env");
|
|
1223
1223
|
}
|
|
1224
1224
|
function readShellAssignments(path, keys) {
|
|
1225
1225
|
const found = {};
|
|
1226
1226
|
if (!existsSync11(path)) return found;
|
|
1227
1227
|
let text;
|
|
1228
1228
|
try {
|
|
1229
|
-
text =
|
|
1229
|
+
text = readFileSync15(path, "utf8");
|
|
1230
1230
|
} catch {
|
|
1231
1231
|
return found;
|
|
1232
1232
|
}
|
|
@@ -1262,7 +1262,7 @@ function resolveTicketProviderCredentials(input) {
|
|
|
1262
1262
|
}
|
|
1263
1263
|
}
|
|
1264
1264
|
const candidates = [];
|
|
1265
|
-
if (input.repoPath) candidates.push(
|
|
1265
|
+
if (input.repoPath) candidates.push(join17(input.repoPath, ".env"));
|
|
1266
1266
|
candidates.push(ticketProviderFleetEnvPath(env2));
|
|
1267
1267
|
candidates.push(ticketProviderSecretsPath(env2));
|
|
1268
1268
|
for (const candidate of candidates) {
|
|
@@ -1280,19 +1280,19 @@ function resolveTicketProviderAdapter(provider, env2 = process.env) {
|
|
|
1280
1280
|
const file = `${provider}.sh`;
|
|
1281
1281
|
const candidates = [];
|
|
1282
1282
|
const override = env2[TICKET_PROVIDER_ADAPTERS_ENV];
|
|
1283
|
-
if (override) candidates.push(
|
|
1284
|
-
const relativeRoot =
|
|
1283
|
+
if (override) candidates.push(join17(override, file));
|
|
1284
|
+
const relativeRoot = join17("krebs", "adapters", "tp");
|
|
1285
1285
|
try {
|
|
1286
1286
|
let dir = dirname13(fileURLToPath2(import.meta.url));
|
|
1287
1287
|
for (let depth = 0; depth < 8; depth++) {
|
|
1288
|
-
candidates.push(
|
|
1288
|
+
candidates.push(join17(dir, relativeRoot, file));
|
|
1289
1289
|
const parent = dirname13(dir);
|
|
1290
1290
|
if (parent === dir) break;
|
|
1291
1291
|
dir = parent;
|
|
1292
1292
|
}
|
|
1293
1293
|
} catch {
|
|
1294
1294
|
}
|
|
1295
|
-
candidates.push(
|
|
1295
|
+
candidates.push(join17(homedir5(), "code", "33GOD", relativeRoot, file));
|
|
1296
1296
|
return candidates.find((candidate) => existsSync11(candidate));
|
|
1297
1297
|
}
|
|
1298
1298
|
function provisionTicketProviderBoard(action, env2 = process.env) {
|
|
@@ -1310,7 +1310,7 @@ function provisionTicketProviderBoard(action, env2 = process.env) {
|
|
|
1310
1310
|
ok: true,
|
|
1311
1311
|
skipped: true,
|
|
1312
1312
|
logs: [
|
|
1313
|
-
`ticket-provider: ${names} not set; skipping ${provider} board creation (state stays "planned"). Set it in the environment, ${
|
|
1313
|
+
`ticket-provider: ${names} not set; skipping ${provider} board creation (state stays "planned"). Set it in the environment, ${join17(action.repoPath, ".env")}, ${ticketProviderFleetEnvPath(env2)}, or ${ticketProviderSecretsPath(env2)} \u2014 or pass --board-id to link an existing board.`
|
|
1314
1314
|
]
|
|
1315
1315
|
};
|
|
1316
1316
|
}
|
|
@@ -1324,12 +1324,12 @@ function provisionTicketProviderBoard(action, env2 = process.env) {
|
|
|
1324
1324
|
};
|
|
1325
1325
|
}
|
|
1326
1326
|
const redact = (text) => Object.values(values).reduce((acc, secret) => secret ? acc.split(secret).join("***") : acc, text);
|
|
1327
|
-
const staging =
|
|
1327
|
+
const staging = mkdtempSync3(join17(tmpdir3(), "pjangler-tp-"));
|
|
1328
1328
|
try {
|
|
1329
|
-
const providersDir =
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1329
|
+
const providersDir = join17(staging, ".tp", "adapters");
|
|
1330
|
+
mkdirSync8(providersDir, { recursive: true });
|
|
1331
|
+
writeFileSync10(
|
|
1332
|
+
join17(staging, ".project.json"),
|
|
1333
1333
|
`${JSON.stringify(
|
|
1334
1334
|
{
|
|
1335
1335
|
project_name: action.boardName,
|
|
@@ -1348,7 +1348,7 @@ function provisionTicketProviderBoard(action, env2 = process.env) {
|
|
|
1348
1348
|
`,
|
|
1349
1349
|
"utf8"
|
|
1350
1350
|
);
|
|
1351
|
-
const staged =
|
|
1351
|
+
const staged = join17(providersDir, `${provider}.sh`);
|
|
1352
1352
|
copyFileSync2(adapter, staged);
|
|
1353
1353
|
const childEnv = { ...env2, ...values, TICKET_PROVIDER: provider };
|
|
1354
1354
|
if (provider === "plane" && action.workspace) childEnv.PLANE_WORKSPACE = action.workspace;
|
|
@@ -1437,7 +1437,7 @@ function prospectiveRealPath(path) {
|
|
|
1437
1437
|
while (!existsSync11(cursor)) {
|
|
1438
1438
|
const parent = dirname13(cursor);
|
|
1439
1439
|
if (parent === cursor) return resolve10(path);
|
|
1440
|
-
suffix.unshift(
|
|
1440
|
+
suffix.unshift(basename8(cursor));
|
|
1441
1441
|
cursor = parent;
|
|
1442
1442
|
}
|
|
1443
1443
|
return resolve10(realpathSync6(cursor), ...suffix);
|
|
@@ -1464,7 +1464,7 @@ function resolveAgentHooksLayer2(input, env2 = process.env) {
|
|
|
1464
1464
|
const override = env2.PJ_AGENT_HOOKS_LAYER;
|
|
1465
1465
|
if (override === "0" || override === "false") return false;
|
|
1466
1466
|
if (override === "1" || override === "true") return true;
|
|
1467
|
-
return !existsSync11(
|
|
1467
|
+
return !existsSync11(join17(homedir5(), ".agents", "hooks"));
|
|
1468
1468
|
}
|
|
1469
1469
|
function jsonStable(value) {
|
|
1470
1470
|
return JSON.stringify(value, (_key, item) => isRecord2(item) ? Object.fromEntries(Object.keys(item).sort().map((key) => [key, item[key]])) : item);
|
|
@@ -1497,10 +1497,10 @@ function resolveSourceSkillPath(sourceSkill, env2 = process.env) {
|
|
|
1497
1497
|
const expanded = expandHome(sourceSkill);
|
|
1498
1498
|
const direct = resolve10(expanded);
|
|
1499
1499
|
if (existsSync11(direct)) return direct;
|
|
1500
|
-
const name =
|
|
1500
|
+
const name = basename8(sourceSkill);
|
|
1501
1501
|
const roots = sourceSkillRoots(env2);
|
|
1502
1502
|
for (const root of roots) {
|
|
1503
|
-
const candidate =
|
|
1503
|
+
const candidate = join17(root, name);
|
|
1504
1504
|
if (existsSync11(candidate)) return candidate;
|
|
1505
1505
|
}
|
|
1506
1506
|
const searched = roots.length ? ` Searched roots: ${roots.join(", ")}.` : "";
|
|
@@ -1510,8 +1510,8 @@ function resolveSourceSkillPath(sourceSkill, env2 = process.env) {
|
|
|
1510
1510
|
function planProjectInit(input) {
|
|
1511
1511
|
if (!input.name.trim()) throw new Error("Project name is required");
|
|
1512
1512
|
const targetDir = resolve10(input.targetDir ?? defaultProjectTargetDir(input.name, input.cwd));
|
|
1513
|
-
const localManifestPath =
|
|
1514
|
-
const localManifestContent = existsSync11(localManifestPath) ?
|
|
1513
|
+
const localManifestPath = join17(targetDir, ".project.json");
|
|
1514
|
+
const localManifestContent = existsSync11(localManifestPath) ? readFileSync15(localManifestPath, "utf8") : void 0;
|
|
1515
1515
|
const localManifest = localManifestContent === void 0 ? void 0 : JSON.parse(localManifestContent);
|
|
1516
1516
|
const localId = localManifest?.project_id ?? localManifest?.project_slug;
|
|
1517
1517
|
const slug = normalizeProjectId(input.projectSlug ?? localId ?? slugifyProjectName(input.name));
|
|
@@ -1539,7 +1539,7 @@ function planProjectInit(input) {
|
|
|
1539
1539
|
const recordedTypeValue = existing?.template?.commonproject?.project_type;
|
|
1540
1540
|
const recordedType = recordedTypeValue === void 0 ? void 0 : normalizeProjectType(recordedTypeValue, `Recorded project type of ${slug}`);
|
|
1541
1541
|
const requestedType = input.projectType === void 0 ? void 0 : normalizeProjectType(input.projectType);
|
|
1542
|
-
const freshRender = scaffold && localManifest === void 0 && !existsSync11(
|
|
1542
|
+
const freshRender = scaffold && localManifest === void 0 && !existsSync11(join17(targetDir, ".copier-answers.yml"));
|
|
1543
1543
|
if (requestedType && !freshRender && requestedType !== (recordedType ?? "base")) {
|
|
1544
1544
|
const adoptingPlugin = !scaffold && recordedType === void 0 && requestedType === "obsidian-plugin" && looksLikeObsidianPlugin(targetDir);
|
|
1545
1545
|
if (!adoptingPlugin) {
|
|
@@ -1637,7 +1637,7 @@ function planProjectInit(input) {
|
|
|
1637
1637
|
}));
|
|
1638
1638
|
}
|
|
1639
1639
|
actions.push(
|
|
1640
|
-
{ kind: "project.write-manifest", path:
|
|
1640
|
+
{ kind: "project.write-manifest", path: join17(targetDir, ".project.json"), manifest, expectedContent: localManifestContent },
|
|
1641
1641
|
{
|
|
1642
1642
|
kind: "ticket-provider.create-or-link",
|
|
1643
1643
|
enabled: boardEnabled,
|
|
@@ -1692,12 +1692,12 @@ function linkTicketProviderBoard(plan, action, boardId, identifier, now = /* @__
|
|
|
1692
1692
|
plan.manifest.ticket_provider = manifestProvider;
|
|
1693
1693
|
action.boardId = boardId;
|
|
1694
1694
|
action.state = manifestProvider.state;
|
|
1695
|
-
const manifestPath =
|
|
1695
|
+
const manifestPath = join17(action.repoPath, ".project.json");
|
|
1696
1696
|
let next;
|
|
1697
1697
|
if (existsSync11(manifestPath)) {
|
|
1698
1698
|
let existing = {};
|
|
1699
1699
|
try {
|
|
1700
|
-
const parsed = JSON.parse(
|
|
1700
|
+
const parsed = JSON.parse(readFileSync15(manifestPath, "utf8"));
|
|
1701
1701
|
if (isRecord2(parsed)) existing = parsed;
|
|
1702
1702
|
} catch {
|
|
1703
1703
|
existing = {};
|
|
@@ -1705,13 +1705,13 @@ function linkTicketProviderBoard(plan, action, boardId, identifier, now = /* @__
|
|
|
1705
1705
|
const existingProvider = isRecord2(existing.ticket_provider) ? existing.ticket_provider : {};
|
|
1706
1706
|
next = { ...existing, ticket_provider: { ...existingProvider, ...manifestProvider } };
|
|
1707
1707
|
} else {
|
|
1708
|
-
|
|
1708
|
+
mkdirSync8(dirname13(manifestPath), { recursive: true });
|
|
1709
1709
|
next = plan.manifest;
|
|
1710
1710
|
}
|
|
1711
1711
|
const text = `${JSON.stringify(next, null, 2)}
|
|
1712
1712
|
`;
|
|
1713
|
-
if (!existsSync11(manifestPath) ||
|
|
1714
|
-
|
|
1713
|
+
if (!existsSync11(manifestPath) || readFileSync15(manifestPath, "utf8") !== text) {
|
|
1714
|
+
writeFileSync10(manifestPath, text, "utf8");
|
|
1715
1715
|
return [manifestPath];
|
|
1716
1716
|
}
|
|
1717
1717
|
return [];
|
|
@@ -1739,7 +1739,7 @@ async function executeProjectInitPlan(plan, options = {}) {
|
|
|
1739
1739
|
logs.push(
|
|
1740
1740
|
action.data.agent_hooks_layer === "false" ? "commonproject: agent-hooks layer skipped (global ~/.agents/hooks detected \u2014 no per-user CLI injection)" : "commonproject: agent-hooks layer included"
|
|
1741
1741
|
);
|
|
1742
|
-
|
|
1742
|
+
mkdirSync8(dirname13(action.targetDir), { recursive: true });
|
|
1743
1743
|
const before = snapshotTree(action.targetDir);
|
|
1744
1744
|
const copierExecutable = options.trustedCopier?.executable ?? action.command[0];
|
|
1745
1745
|
const copierEnv = { ...process.env };
|
|
@@ -1771,22 +1771,22 @@ async function executeProjectInitPlan(plan, options = {}) {
|
|
|
1771
1771
|
break;
|
|
1772
1772
|
}
|
|
1773
1773
|
const projectType = action.data.project_type ?? "base";
|
|
1774
|
-
const missing = (PROJECT_TYPE_SENTINELS[projectType] ?? []).filter((rel) => !existsSync11(
|
|
1774
|
+
const missing = (PROJECT_TYPE_SENTINELS[projectType] ?? []).filter((rel) => !existsSync11(join17(action.targetDir, rel)));
|
|
1775
1775
|
if (missing.length) {
|
|
1776
1776
|
errors.push(`copier rendered no ${projectType} project: ${missing.join(", ")} missing from ${action.targetDir}`);
|
|
1777
1777
|
break;
|
|
1778
1778
|
}
|
|
1779
1779
|
if (projectType !== "base") logs.push(`commonproject: rendered as ${projectType}`);
|
|
1780
1780
|
} else if (action.kind === "project.write-manifest") {
|
|
1781
|
-
if (action.expectedContent !== void 0 && (!existsSync11(action.path) ||
|
|
1781
|
+
if (action.expectedContent !== void 0 && (!existsSync11(action.path) || readFileSync15(action.path, "utf8") !== action.expectedContent)) {
|
|
1782
1782
|
errors.push(`Manifest changed after planning: ${action.path}; re-plan before applying`);
|
|
1783
1783
|
break;
|
|
1784
1784
|
}
|
|
1785
|
-
|
|
1785
|
+
mkdirSync8(dirname13(action.path), { recursive: true });
|
|
1786
1786
|
let value = action.manifest;
|
|
1787
1787
|
if (existsSync11(action.path)) {
|
|
1788
1788
|
try {
|
|
1789
|
-
const currentValue = JSON.parse(
|
|
1789
|
+
const currentValue = JSON.parse(readFileSync15(action.path, "utf8"));
|
|
1790
1790
|
if (isRecord2(currentValue)) {
|
|
1791
1791
|
const currentNotebook = isRecord2(currentValue.notebook) ? currentValue.notebook : {};
|
|
1792
1792
|
const desiredNotebook = isRecord2(value.notebook) ? value.notebook : {};
|
|
@@ -1814,9 +1814,9 @@ async function executeProjectInitPlan(plan, options = {}) {
|
|
|
1814
1814
|
value.project_id = normalizeProjectId(value.project_id ?? action.manifest.project_id);
|
|
1815
1815
|
const next = `${JSON.stringify(value, null, 2)}
|
|
1816
1816
|
`;
|
|
1817
|
-
const current = existsSync11(action.path) ?
|
|
1817
|
+
const current = existsSync11(action.path) ? readFileSync15(action.path, "utf8") : void 0;
|
|
1818
1818
|
if (current !== next) {
|
|
1819
|
-
|
|
1819
|
+
writeFileSync10(action.path, next, "utf8");
|
|
1820
1820
|
changedFiles.push(action.path);
|
|
1821
1821
|
}
|
|
1822
1822
|
changedFiles.push(...synchronizeCopierIdentity(action.path, action.manifest));
|
|
@@ -1897,7 +1897,7 @@ function getProject(registry, slug) {
|
|
|
1897
1897
|
return project;
|
|
1898
1898
|
}
|
|
1899
1899
|
function buildCommonProjectCopierAction(input) {
|
|
1900
|
-
const templateDir =
|
|
1900
|
+
const templateDir = join17(input.pjanglerRoot, "templates", "commonproject");
|
|
1901
1901
|
const data = {
|
|
1902
1902
|
project_name: input.projectName,
|
|
1903
1903
|
project_description: input.projectDescription ?? "",
|
|
@@ -1928,7 +1928,7 @@ function buildCommonProjectCopierAction(input) {
|
|
|
1928
1928
|
function resolvePjanglerRoot() {
|
|
1929
1929
|
let dir = dirname13(fileURLToPath2(import.meta.url));
|
|
1930
1930
|
while (dir !== dirname13(dir)) {
|
|
1931
|
-
if (existsSync11(
|
|
1931
|
+
if (existsSync11(join17(dir, "package.json")) && existsSync11(join17(dir, "templates", "commonproject", "copier.yml"))) return dir;
|
|
1932
1932
|
dir = dirname13(dir);
|
|
1933
1933
|
}
|
|
1934
1934
|
return resolve10(process.cwd());
|
|
@@ -2005,7 +2005,7 @@ function validateProjectRecord(project, key) {
|
|
|
2005
2005
|
}
|
|
2006
2006
|
function expandHome(path) {
|
|
2007
2007
|
if (path === "~") return homedir5();
|
|
2008
|
-
if (path.startsWith("~/")) return
|
|
2008
|
+
if (path.startsWith("~/")) return join17(homedir5(), path.slice(2));
|
|
2009
2009
|
return path;
|
|
2010
2010
|
}
|
|
2011
2011
|
function isRecord2(value) {
|
|
@@ -2032,7 +2032,7 @@ var init_project = __esm({
|
|
|
2032
2032
|
"obsidian-plugin": ["typescript", "javascript"]
|
|
2033
2033
|
};
|
|
2034
2034
|
PROJECT_TYPE_SENTINELS = {
|
|
2035
|
-
"obsidian-plugin": ["manifest.json",
|
|
2035
|
+
"obsidian-plugin": ["manifest.json", join17("src", "main.ts")]
|
|
2036
2036
|
};
|
|
2037
2037
|
BOARD_URL_DEPRECATION_WARNING = "boardUrl is deprecated and ignored; board URLs are derived at runtime and are never persisted.";
|
|
2038
2038
|
PROJECT_IDENTIFIER_SOURCES = ["provider", "proposed"];
|
|
@@ -2041,8 +2041,8 @@ var init_project = __esm({
|
|
|
2041
2041
|
IDENTIFIER_REPAIR_COMMAND = "pj project identity --all --apply";
|
|
2042
2042
|
DEFAULT_SOURCE_SKILL_ROOTS = [
|
|
2043
2043
|
"/home/delorenj/code/skillex/all-skills",
|
|
2044
|
-
|
|
2045
|
-
|
|
2044
|
+
join17(homedir5(), ".agents", "skills"),
|
|
2045
|
+
join17(homedir5(), ".codex", "skills")
|
|
2046
2046
|
];
|
|
2047
2047
|
PROJECT_REGISTRY_OWNED_KEYS = [
|
|
2048
2048
|
"name",
|
|
@@ -2074,7 +2074,7 @@ var init_project = __esm({
|
|
|
2074
2074
|
import { randomUUID } from "node:crypto";
|
|
2075
2075
|
import { isDeepStrictEqual } from "node:util";
|
|
2076
2076
|
import { isIP as isIP2 } from "node:net";
|
|
2077
|
-
import { closeSync as
|
|
2077
|
+
import { closeSync as closeSync4, existsSync as existsSync12, fchmodSync as fchmodSync3, fsyncSync as fsyncSync2, lstatSync as lstatSync13, openSync as openSync4, readFileSync as readFileSync16, realpathSync as realpathSync7, renameSync as renameSync6, writeFileSync as writeFileSync11 } from "node:fs";
|
|
2078
2078
|
import { dirname as dirname14, resolve as resolve11 } from "node:path";
|
|
2079
2079
|
function isRecord3(value) {
|
|
2080
2080
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
@@ -2088,7 +2088,7 @@ function readManifest(repoPath) {
|
|
|
2088
2088
|
if (!existsSync12(path)) return null;
|
|
2089
2089
|
let parsed;
|
|
2090
2090
|
try {
|
|
2091
|
-
parsed = JSON.parse(
|
|
2091
|
+
parsed = JSON.parse(readFileSync16(path, "utf8"));
|
|
2092
2092
|
} catch {
|
|
2093
2093
|
throw new NotebookError("INVALID_INPUT", `${path} is not valid JSON`);
|
|
2094
2094
|
}
|
|
@@ -2330,7 +2330,7 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
|
|
|
2330
2330
|
const changed = [];
|
|
2331
2331
|
const manifestPath = resolve11(resolved.project.repo_path, ".project.json");
|
|
2332
2332
|
if (isRegistryServiceLocation(resolved.registry_path)) {
|
|
2333
|
-
const before = existsSync12(manifestPath) ?
|
|
2333
|
+
const before = existsSync12(manifestPath) ? readFileSync16(manifestPath, "utf8") : null;
|
|
2334
2334
|
const declaredPolicy = resolved.manifest?.notebook?.policy ?? {};
|
|
2335
2335
|
const nextProject = {
|
|
2336
2336
|
...resolved.project,
|
|
@@ -2342,10 +2342,10 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
|
|
|
2342
2342
|
resolved.registry = loadProjectRegistry(resolved.registry_path);
|
|
2343
2343
|
resolved.project = registryProjectBySlug(resolved.registry, nextProject.slug);
|
|
2344
2344
|
resolved.manifest = readManifest(nextProject.repo_path);
|
|
2345
|
-
const after = existsSync12(manifestPath) ?
|
|
2345
|
+
const after = existsSync12(manifestPath) ? readFileSync16(manifestPath, "utf8") : null;
|
|
2346
2346
|
return before === after ? [] : [manifestPath];
|
|
2347
2347
|
}
|
|
2348
|
-
const manifestRaw = existsSync12(manifestPath) ? JSON.parse(
|
|
2348
|
+
const manifestRaw = existsSync12(manifestPath) ? JSON.parse(readFileSync16(manifestPath, "utf8")) : {};
|
|
2349
2349
|
if (!isRecord3(manifestRaw)) throw new NotebookError("INVALID_INPUT", `${manifestPath} must contain a JSON object`);
|
|
2350
2350
|
validateManifestNotebookSurface(manifestRaw.notebook);
|
|
2351
2351
|
const manifestNotebook2 = isRecord3(manifestRaw.notebook) ? manifestRaw.notebook : {};
|
|
@@ -2378,29 +2378,29 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
|
|
|
2378
2378
|
};
|
|
2379
2379
|
const manifestText = `${JSON.stringify(manifestNext, null, 2)}
|
|
2380
2380
|
`;
|
|
2381
|
-
if (!existsSync12(manifestPath) ||
|
|
2381
|
+
if (!existsSync12(manifestPath) || readFileSync16(manifestPath, "utf8") !== manifestText) {
|
|
2382
2382
|
let mode = 420;
|
|
2383
2383
|
if (existsSync12(manifestPath)) {
|
|
2384
|
-
const current2 =
|
|
2384
|
+
const current2 = lstatSync13(manifestPath);
|
|
2385
2385
|
if (!current2.isFile() || current2.isSymbolicLink()) throw new NotebookError("CONFLICT", `${manifestPath} must be a regular non-symlink file`);
|
|
2386
2386
|
mode = current2.mode & 511;
|
|
2387
2387
|
}
|
|
2388
2388
|
const temp = resolve11(dirname14(manifestPath), `.${process.pid}.${randomUUID()}.project.json.tmp`);
|
|
2389
|
-
const fd =
|
|
2389
|
+
const fd = openSync4(temp, "wx", mode);
|
|
2390
2390
|
try {
|
|
2391
|
-
|
|
2392
|
-
|
|
2391
|
+
writeFileSync11(fd, manifestText, "utf8");
|
|
2392
|
+
fchmodSync3(fd, mode);
|
|
2393
2393
|
fsyncSync2(fd);
|
|
2394
2394
|
} finally {
|
|
2395
|
-
|
|
2395
|
+
closeSync4(fd);
|
|
2396
2396
|
}
|
|
2397
2397
|
renameSync6(temp, manifestPath);
|
|
2398
2398
|
try {
|
|
2399
|
-
const directory =
|
|
2399
|
+
const directory = openSync4(dirname14(manifestPath), "r");
|
|
2400
2400
|
try {
|
|
2401
2401
|
fsyncSync2(directory);
|
|
2402
2402
|
} finally {
|
|
2403
|
-
|
|
2403
|
+
closeSync4(directory);
|
|
2404
2404
|
}
|
|
2405
2405
|
} catch {
|
|
2406
2406
|
}
|
|
@@ -2409,9 +2409,9 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
|
|
|
2409
2409
|
const current = resolved.project.notebook;
|
|
2410
2410
|
resolved.project.notebook = { ...isRecord3(current) ? current : { state: "planned" }, ...binding };
|
|
2411
2411
|
const fileRegistry = !isRegistryServiceLocation(resolved.registry_path);
|
|
2412
|
-
const registryBefore = fileRegistry && existsSync12(resolved.registry_path) ?
|
|
2412
|
+
const registryBefore = fileRegistry && existsSync12(resolved.registry_path) ? readFileSync16(resolved.registry_path, "utf8") : null;
|
|
2413
2413
|
saveProjectRegistry(resolved.registry, resolved.registry_path);
|
|
2414
|
-
const registryAfter = fileRegistry && existsSync12(resolved.registry_path) ?
|
|
2414
|
+
const registryAfter = fileRegistry && existsSync12(resolved.registry_path) ? readFileSync16(resolved.registry_path, "utf8") : null;
|
|
2415
2415
|
if (registryBefore !== registryAfter) changed.push(resolved.registry_path);
|
|
2416
2416
|
resolved.manifest = manifestNext;
|
|
2417
2417
|
return changed;
|
|
@@ -2444,7 +2444,7 @@ var init_config = __esm({
|
|
|
2444
2444
|
});
|
|
2445
2445
|
|
|
2446
2446
|
// src/notebook/notes.ts
|
|
2447
|
-
import { createHash as
|
|
2447
|
+
import { createHash as createHash10, randomUUID as randomUUID2 } from "node:crypto";
|
|
2448
2448
|
function canonicalJson2(value) {
|
|
2449
2449
|
if (value === void 0 || typeof value === "function" || typeof value === "symbol" || typeof value === "bigint") throw new NotebookError("INVALID_INPUT", "Canonical JSON value is not JSON-compatible");
|
|
2450
2450
|
if (value === null || typeof value !== "object") {
|
|
@@ -2457,7 +2457,7 @@ function canonicalJson2(value) {
|
|
|
2457
2457
|
return `{${Object.keys(record3).filter((key) => record3[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson2(record3[key])}`).join(",")}}`;
|
|
2458
2458
|
}
|
|
2459
2459
|
function sha256Hex(value) {
|
|
2460
|
-
return
|
|
2460
|
+
return createHash10("sha256").update(value).digest("hex");
|
|
2461
2461
|
}
|
|
2462
2462
|
function base64UrlEncode(value) {
|
|
2463
2463
|
return Buffer.from(value, "utf8").toString("base64url");
|
|
@@ -2714,27 +2714,27 @@ var init_remote_mutation_schema = __esm({
|
|
|
2714
2714
|
});
|
|
2715
2715
|
|
|
2716
2716
|
// src/notebook/state.ts
|
|
2717
|
-
import { createHash as
|
|
2717
|
+
import { createHash as createHash11, randomUUID as randomUUID3 } from "node:crypto";
|
|
2718
2718
|
import {
|
|
2719
|
-
closeSync as
|
|
2720
|
-
constants as
|
|
2719
|
+
closeSync as closeSync5,
|
|
2720
|
+
constants as constants3,
|
|
2721
2721
|
existsSync as existsSync13,
|
|
2722
|
-
fchmodSync as
|
|
2723
|
-
fstatSync,
|
|
2722
|
+
fchmodSync as fchmodSync4,
|
|
2723
|
+
fstatSync as fstatSync2,
|
|
2724
2724
|
fsyncSync as fsyncSync3,
|
|
2725
|
-
lstatSync as
|
|
2726
|
-
mkdirSync as
|
|
2727
|
-
openSync as
|
|
2725
|
+
lstatSync as lstatSync14,
|
|
2726
|
+
mkdirSync as mkdirSync9,
|
|
2727
|
+
openSync as openSync5,
|
|
2728
2728
|
readSync,
|
|
2729
|
-
readdirSync as
|
|
2729
|
+
readdirSync as readdirSync10,
|
|
2730
2730
|
renameSync as renameSync7,
|
|
2731
|
-
unlinkSync as
|
|
2732
|
-
writeFileSync as
|
|
2731
|
+
unlinkSync as unlinkSync8,
|
|
2732
|
+
writeFileSync as writeFileSync12
|
|
2733
2733
|
} from "node:fs";
|
|
2734
2734
|
import { homedir as homedir6 } from "node:os";
|
|
2735
|
-
import { basename as
|
|
2735
|
+
import { basename as basename9, dirname as dirname15, join as join18, parse, relative as relative12, resolve as resolve12, sep as sep4 } from "node:path";
|
|
2736
2736
|
function notebookStateRoot(env2 = process.env) {
|
|
2737
|
-
const base = env2.XDG_STATE_HOME ||
|
|
2737
|
+
const base = env2.XDG_STATE_HOME || join18(env2.HOME || homedir6(), ".local", "state");
|
|
2738
2738
|
return resolve12(base, "pjangler", "notebook", NOTEBOOK_STATE_VERSION);
|
|
2739
2739
|
}
|
|
2740
2740
|
function assertDigest(value, label) {
|
|
@@ -2742,7 +2742,7 @@ function assertDigest(value, label) {
|
|
|
2742
2742
|
}
|
|
2743
2743
|
function projectStateDir(root, projectSlug) {
|
|
2744
2744
|
if (!/^[a-z0-9][a-z0-9._-]{0,127}$/iu.test(projectSlug)) throw new NotebookError("INVALID_INPUT", "Invalid project slug for Notebook state");
|
|
2745
|
-
return
|
|
2745
|
+
return join18(resolve12(root), "projects", sha256Hex(`pjangler-project-state-v1\0${projectSlug}`));
|
|
2746
2746
|
}
|
|
2747
2747
|
function assertContained(root, candidate) {
|
|
2748
2748
|
const rel = relative12(resolve12(root), resolve12(candidate));
|
|
@@ -2755,33 +2755,33 @@ function openPinnedDirectory(path, root, create) {
|
|
|
2755
2755
|
assertContained(absoluteRoot, absolute);
|
|
2756
2756
|
if (!existsSync13("/proc/self/fd")) throw new NotebookError("INTERNAL_ERROR", "Descriptor-pinned Notebook state requires procfs");
|
|
2757
2757
|
const parsed = parse(absolute);
|
|
2758
|
-
let fd =
|
|
2758
|
+
let fd = openSync5(parsed.root, DIRECTORY_OPEN_FLAGS);
|
|
2759
2759
|
let cursor = parsed.root;
|
|
2760
2760
|
try {
|
|
2761
2761
|
for (const part of absolute.slice(parsed.root.length).split(sep4).filter(Boolean)) {
|
|
2762
2762
|
const child = `/proc/self/fd/${fd}/${part}`;
|
|
2763
|
-
cursor =
|
|
2763
|
+
cursor = join18(cursor, part);
|
|
2764
2764
|
let childFd;
|
|
2765
2765
|
try {
|
|
2766
|
-
childFd =
|
|
2766
|
+
childFd = openSync5(child, DIRECTORY_OPEN_FLAGS);
|
|
2767
2767
|
} catch (error) {
|
|
2768
2768
|
if (!create || error.code !== "ENOENT") throw error;
|
|
2769
2769
|
try {
|
|
2770
|
-
|
|
2770
|
+
mkdirSync9(child, { mode: 448 });
|
|
2771
2771
|
} catch (mkdirError) {
|
|
2772
2772
|
if (mkdirError.code !== "EEXIST") throw mkdirError;
|
|
2773
2773
|
}
|
|
2774
|
-
childFd =
|
|
2774
|
+
childFd = openSync5(child, DIRECTORY_OPEN_FLAGS);
|
|
2775
2775
|
}
|
|
2776
|
-
|
|
2776
|
+
closeSync5(fd);
|
|
2777
2777
|
fd = childFd;
|
|
2778
|
-
const stat =
|
|
2778
|
+
const stat = fstatSync2(fd);
|
|
2779
2779
|
if (!stat.isDirectory()) throw new NotebookError("INTERNAL_ERROR", "Notebook state path component is not a directory");
|
|
2780
2780
|
if (cursor === absoluteRoot || relative12(absoluteRoot, cursor).startsWith("..") === false) assertOwned(stat);
|
|
2781
2781
|
}
|
|
2782
2782
|
return fd;
|
|
2783
2783
|
} catch (error) {
|
|
2784
|
-
|
|
2784
|
+
closeSync5(fd);
|
|
2785
2785
|
if (error.code === "ELOOP" || error.code === "ENOTDIR") {
|
|
2786
2786
|
throw new NotebookError("INTERNAL_ERROR", "Notebook state path contains a symlink component");
|
|
2787
2787
|
}
|
|
@@ -2789,7 +2789,7 @@ function openPinnedDirectory(path, root, create) {
|
|
|
2789
2789
|
}
|
|
2790
2790
|
}
|
|
2791
2791
|
function pinnedLeaf(parentFd, path) {
|
|
2792
|
-
return `/proc/self/fd/${parentFd}/${
|
|
2792
|
+
return `/proc/self/fd/${parentFd}/${basename9(path)}`;
|
|
2793
2793
|
}
|
|
2794
2794
|
function assertOwned(stat) {
|
|
2795
2795
|
if (typeof process.getuid === "function" && stat.uid !== process.getuid()) {
|
|
@@ -2802,13 +2802,13 @@ function ensureDirectory(path, root) {
|
|
|
2802
2802
|
assertContained(absoluteRoot, absolute);
|
|
2803
2803
|
const fd = openPinnedDirectory(absolute, absoluteRoot, true);
|
|
2804
2804
|
try {
|
|
2805
|
-
const stat =
|
|
2805
|
+
const stat = fstatSync2(fd);
|
|
2806
2806
|
if (!stat.isDirectory()) throw new NotebookError("INTERNAL_ERROR", "Notebook state directory is not a real directory");
|
|
2807
2807
|
assertOwned(stat);
|
|
2808
|
-
|
|
2808
|
+
fchmodSync4(fd, 448);
|
|
2809
2809
|
fsyncSync3(fd);
|
|
2810
2810
|
} finally {
|
|
2811
|
-
|
|
2811
|
+
closeSync5(fd);
|
|
2812
2812
|
}
|
|
2813
2813
|
}
|
|
2814
2814
|
function notebookStatePaths(root, projectSlug) {
|
|
@@ -2817,18 +2817,18 @@ function notebookStatePaths(root, projectSlug) {
|
|
|
2817
2817
|
return {
|
|
2818
2818
|
root: absoluteRoot,
|
|
2819
2819
|
project,
|
|
2820
|
-
baselines:
|
|
2821
|
-
claims:
|
|
2822
|
-
receipts:
|
|
2823
|
-
refusals:
|
|
2824
|
-
journals:
|
|
2825
|
-
locks:
|
|
2820
|
+
baselines: join18(project, "baselines"),
|
|
2821
|
+
claims: join18(project, "claims"),
|
|
2822
|
+
receipts: join18(project, "receipts"),
|
|
2823
|
+
refusals: join18(project, "refusals"),
|
|
2824
|
+
journals: join18(project, "journals"),
|
|
2825
|
+
locks: join18(project, "locks")
|
|
2826
2826
|
};
|
|
2827
2827
|
}
|
|
2828
2828
|
function ensureNotebookState(root, projectSlug) {
|
|
2829
2829
|
const paths = notebookStatePaths(root, projectSlug);
|
|
2830
2830
|
const { root: absoluteRoot, project } = paths;
|
|
2831
|
-
for (const path of [absoluteRoot,
|
|
2831
|
+
for (const path of [absoluteRoot, join18(absoluteRoot, "projects"), project, paths.baselines, paths.claims, paths.receipts, paths.refusals, paths.journals, paths.locks]) {
|
|
2832
2832
|
ensureDirectory(path, absoluteRoot);
|
|
2833
2833
|
}
|
|
2834
2834
|
return paths;
|
|
@@ -2844,15 +2844,15 @@ function fsyncDirectory2(path) {
|
|
|
2844
2844
|
fsyncSync3(fd);
|
|
2845
2845
|
} catch {
|
|
2846
2846
|
} finally {
|
|
2847
|
-
if (fd !== void 0)
|
|
2847
|
+
if (fd !== void 0) closeSync5(fd);
|
|
2848
2848
|
}
|
|
2849
2849
|
}
|
|
2850
2850
|
function readStateDirectory(path, root) {
|
|
2851
2851
|
const fd = openPinnedDirectory(path, root, false);
|
|
2852
2852
|
try {
|
|
2853
|
-
return
|
|
2853
|
+
return readdirSync10(`/proc/self/fd/${fd}`, { withFileTypes: true });
|
|
2854
2854
|
} finally {
|
|
2855
|
-
|
|
2855
|
+
closeSync5(fd);
|
|
2856
2856
|
}
|
|
2857
2857
|
}
|
|
2858
2858
|
function readNotebookStateDirectory(path, root) {
|
|
@@ -2866,22 +2866,22 @@ function unlinkStateFile(path, root, allowMissing = false) {
|
|
|
2866
2866
|
let fileFd;
|
|
2867
2867
|
try {
|
|
2868
2868
|
try {
|
|
2869
|
-
fileFd =
|
|
2869
|
+
fileFd = openSync5(target, constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
|
|
2870
2870
|
} catch (error) {
|
|
2871
2871
|
if (allowMissing && error.code === "ENOENT") return false;
|
|
2872
2872
|
throw error;
|
|
2873
2873
|
}
|
|
2874
|
-
const stat =
|
|
2874
|
+
const stat = fstatSync2(fileFd);
|
|
2875
2875
|
if (!stat.isFile() || (stat.mode & 511) !== 384) {
|
|
2876
2876
|
throw new NotebookError("CONFLICT", "Refusing to remove a suspect Notebook state entry; run pj notebook audit --json");
|
|
2877
2877
|
}
|
|
2878
2878
|
assertOwned(stat);
|
|
2879
|
-
|
|
2879
|
+
unlinkSync8(target);
|
|
2880
2880
|
fsyncSync3(parentFd);
|
|
2881
2881
|
return true;
|
|
2882
2882
|
} finally {
|
|
2883
|
-
if (fileFd !== void 0)
|
|
2884
|
-
|
|
2883
|
+
if (fileFd !== void 0) closeSync5(fileFd);
|
|
2884
|
+
closeSync5(parentFd);
|
|
2885
2885
|
}
|
|
2886
2886
|
}
|
|
2887
2887
|
function renameStateFile(source, target, root) {
|
|
@@ -2891,12 +2891,12 @@ function renameStateFile(source, target, root) {
|
|
|
2891
2891
|
const targetPath = pinnedLeaf(parentFd, target);
|
|
2892
2892
|
let sourceFd;
|
|
2893
2893
|
try {
|
|
2894
|
-
sourceFd =
|
|
2895
|
-
const stat =
|
|
2894
|
+
sourceFd = openSync5(sourcePath, constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
|
|
2895
|
+
const stat = fstatSync2(sourceFd);
|
|
2896
2896
|
if (!stat.isFile() || (stat.mode & 511) !== 384) throw new NotebookError("CONFLICT", "Notebook state rename source has an integrity finding");
|
|
2897
2897
|
assertOwned(stat);
|
|
2898
2898
|
try {
|
|
2899
|
-
|
|
2899
|
+
lstatSync14(targetPath);
|
|
2900
2900
|
throw new NotebookError("CONFLICT", "Notebook state rename target already exists");
|
|
2901
2901
|
} catch (error) {
|
|
2902
2902
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -2904,8 +2904,8 @@ function renameStateFile(source, target, root) {
|
|
|
2904
2904
|
renameSync7(sourcePath, targetPath);
|
|
2905
2905
|
fsyncSync3(parentFd);
|
|
2906
2906
|
} finally {
|
|
2907
|
-
if (sourceFd !== void 0)
|
|
2908
|
-
|
|
2907
|
+
if (sourceFd !== void 0) closeSync5(sourceFd);
|
|
2908
|
+
closeSync5(parentFd);
|
|
2909
2909
|
}
|
|
2910
2910
|
}
|
|
2911
2911
|
function atomicWriteJson(path, value, root, afterParentPinned) {
|
|
@@ -2914,34 +2914,34 @@ function atomicWriteJson(path, value, root, afterParentPinned) {
|
|
|
2914
2914
|
const text = jsonLine(value);
|
|
2915
2915
|
const parentFd = openPinnedDirectory(dirname15(path), root, false);
|
|
2916
2916
|
const target = pinnedLeaf(parentFd, path);
|
|
2917
|
-
const tempName = `.${
|
|
2917
|
+
const tempName = `.${basename9(path)}.${process.pid}.${randomUUID3()}.tmp`;
|
|
2918
2918
|
const temp = `/proc/self/fd/${parentFd}/${tempName}`;
|
|
2919
2919
|
let existingIdentity = null;
|
|
2920
2920
|
try {
|
|
2921
2921
|
afterParentPinned?.();
|
|
2922
2922
|
try {
|
|
2923
|
-
const existingFd =
|
|
2923
|
+
const existingFd = openSync5(target, constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
|
|
2924
2924
|
try {
|
|
2925
|
-
const existing =
|
|
2925
|
+
const existing = fstatSync2(existingFd);
|
|
2926
2926
|
if (!existing.isFile()) throw new NotebookError("INTERNAL_ERROR", "Refusing to replace a non-regular Notebook state file");
|
|
2927
2927
|
assertOwned(existing);
|
|
2928
2928
|
if ((existing.mode & 511) !== 384) throw new NotebookError("CONFLICT", "Refusing to replace a Notebook state file with unsafe permissions");
|
|
2929
2929
|
existingIdentity = { dev: existing.dev, ino: existing.ino };
|
|
2930
2930
|
} finally {
|
|
2931
|
-
|
|
2931
|
+
closeSync5(existingFd);
|
|
2932
2932
|
}
|
|
2933
2933
|
} catch (error) {
|
|
2934
2934
|
if (error.code !== "ENOENT") throw error;
|
|
2935
2935
|
}
|
|
2936
|
-
const fd =
|
|
2936
|
+
const fd = openSync5(temp, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | (constants3.O_NOFOLLOW ?? 0), 384);
|
|
2937
2937
|
try {
|
|
2938
|
-
|
|
2938
|
+
writeFileSync12(fd, text, "utf8");
|
|
2939
2939
|
fsyncSync3(fd);
|
|
2940
2940
|
} finally {
|
|
2941
|
-
|
|
2941
|
+
closeSync5(fd);
|
|
2942
2942
|
}
|
|
2943
2943
|
try {
|
|
2944
|
-
const current =
|
|
2944
|
+
const current = lstatSync14(target);
|
|
2945
2945
|
if (!existingIdentity || !current.isFile() || current.isSymbolicLink() || current.dev !== existingIdentity.dev || current.ino !== existingIdentity.ino) {
|
|
2946
2946
|
throw new NotebookError("CONFLICT", "Notebook state target changed during atomic update; preserving both entries for audit");
|
|
2947
2947
|
}
|
|
@@ -2952,12 +2952,12 @@ function atomicWriteJson(path, value, root, afterParentPinned) {
|
|
|
2952
2952
|
fsyncSync3(parentFd);
|
|
2953
2953
|
} catch (error) {
|
|
2954
2954
|
try {
|
|
2955
|
-
|
|
2955
|
+
unlinkSync8(temp);
|
|
2956
2956
|
} catch {
|
|
2957
2957
|
}
|
|
2958
2958
|
throw error;
|
|
2959
2959
|
} finally {
|
|
2960
|
-
|
|
2960
|
+
closeSync5(parentFd);
|
|
2961
2961
|
}
|
|
2962
2962
|
return Buffer.byteLength(text, "utf8");
|
|
2963
2963
|
}
|
|
@@ -2967,22 +2967,22 @@ function exclusiveWrite(path, text, root) {
|
|
|
2967
2967
|
const target = pinnedLeaf(parentFd, path);
|
|
2968
2968
|
let fd;
|
|
2969
2969
|
try {
|
|
2970
|
-
fd =
|
|
2970
|
+
fd = openSync5(target, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | (constants3.O_NOFOLLOW ?? 0), 384);
|
|
2971
2971
|
} catch (error) {
|
|
2972
|
-
|
|
2972
|
+
closeSync5(parentFd);
|
|
2973
2973
|
if (error.code === "EEXIST") return false;
|
|
2974
2974
|
throw error;
|
|
2975
2975
|
}
|
|
2976
2976
|
try {
|
|
2977
|
-
|
|
2977
|
+
writeFileSync12(fd, text, "utf8");
|
|
2978
2978
|
fsyncSync3(fd);
|
|
2979
2979
|
} finally {
|
|
2980
|
-
|
|
2980
|
+
closeSync5(fd);
|
|
2981
2981
|
}
|
|
2982
2982
|
try {
|
|
2983
2983
|
fsyncSync3(parentFd);
|
|
2984
2984
|
} finally {
|
|
2985
|
-
|
|
2985
|
+
closeSync5(parentFd);
|
|
2986
2986
|
}
|
|
2987
2987
|
return true;
|
|
2988
2988
|
}
|
|
@@ -3126,13 +3126,13 @@ function safeReadJson(path, maxBytes) {
|
|
|
3126
3126
|
}
|
|
3127
3127
|
let fd;
|
|
3128
3128
|
try {
|
|
3129
|
-
fd =
|
|
3129
|
+
fd = openSync5(pinnedLeaf(parentFd, path), constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
|
|
3130
3130
|
} catch (error) {
|
|
3131
|
-
|
|
3131
|
+
closeSync5(parentFd);
|
|
3132
3132
|
return { reason: error.code === "ELOOP" ? "non-regular" : "unreadable", bytes: 0 };
|
|
3133
3133
|
}
|
|
3134
3134
|
try {
|
|
3135
|
-
const stat =
|
|
3135
|
+
const stat = fstatSync2(fd);
|
|
3136
3136
|
if (!stat.isFile()) return { reason: "non-regular", bytes: 0 };
|
|
3137
3137
|
if ((stat.mode & 511) !== 384) return { reason: "unsafe-permissions", bytes: stat.size };
|
|
3138
3138
|
try {
|
|
@@ -3152,14 +3152,14 @@ function safeReadJson(path, maxBytes) {
|
|
|
3152
3152
|
if (total > maxBytes) return { reason: "oversize", bytes: total };
|
|
3153
3153
|
chunks.push(chunk.subarray(0, count));
|
|
3154
3154
|
}
|
|
3155
|
-
const after =
|
|
3155
|
+
const after = fstatSync2(fd);
|
|
3156
3156
|
if (after.size !== stat.size || total !== stat.size) return { reason: "unreadable", bytes: total };
|
|
3157
3157
|
return { value: JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(Buffer.concat(chunks, total))), bytes: total };
|
|
3158
3158
|
} catch (error) {
|
|
3159
3159
|
return { reason: error instanceof SyntaxError || error instanceof TypeError ? "invalid-json" : "unreadable", bytes: 0 };
|
|
3160
3160
|
} finally {
|
|
3161
|
-
|
|
3162
|
-
|
|
3161
|
+
closeSync5(fd);
|
|
3162
|
+
closeSync5(parentFd);
|
|
3163
3163
|
}
|
|
3164
3164
|
}
|
|
3165
3165
|
function safeEntryId(kind, name) {
|
|
@@ -3176,10 +3176,10 @@ function baselineReceiptByteCeiling(limits) {
|
|
|
3176
3176
|
function readSessionBaseline(root, projectSlug, sessionKey, limits) {
|
|
3177
3177
|
assertDigest(sessionKey, "session key");
|
|
3178
3178
|
const paths = ensureNotebookState(root, projectSlug);
|
|
3179
|
-
return readBaseline(
|
|
3179
|
+
return readBaseline(join18(paths.baselines, `${sessionKey}.json`), baselineReceiptByteCeiling(limits));
|
|
3180
3180
|
}
|
|
3181
3181
|
function acquireLock(paths, maxWaitMs) {
|
|
3182
|
-
const lock =
|
|
3182
|
+
const lock = join18(paths.locks, "admission.lock");
|
|
3183
3183
|
const deadline = Date.now() + Math.max(1, maxWaitMs);
|
|
3184
3184
|
const token = randomUUID3();
|
|
3185
3185
|
const record3 = () => ({
|
|
@@ -3209,7 +3209,7 @@ function acquireLock(paths, maxWaitMs) {
|
|
|
3209
3209
|
throw new NotebookError("CONFLICT", "Notebook state lock has an integrity finding; preserve it and run pj notebook audit --json");
|
|
3210
3210
|
}
|
|
3211
3211
|
if (Date.now() >= expiresAt) {
|
|
3212
|
-
const recovery =
|
|
3212
|
+
const recovery = join18(paths.locks, `recovery-${heldToken}.lock`);
|
|
3213
3213
|
const recoveryRecord = jsonLine({ schema_version: NOTEBOOK_SCHEMA_VERSION, stale_token: heldToken, recovery_token: token, expires_at: new Date(Date.now() + 5e3).toISOString() });
|
|
3214
3214
|
if (exclusiveWrite(recovery, recoveryRecord, paths.root)) {
|
|
3215
3215
|
try {
|
|
@@ -3217,7 +3217,7 @@ function acquireLock(paths, maxWaitMs) {
|
|
|
3217
3217
|
const currentToken = verify.value && typeof verify.value === "object" && !Array.isArray(verify.value) ? verify.value.token : null;
|
|
3218
3218
|
const currentExpiry = verify.value && typeof verify.value === "object" && !Array.isArray(verify.value) ? Date.parse(String(verify.value.expires_at ?? "")) : Number.NaN;
|
|
3219
3219
|
if (currentToken === heldToken && Number.isFinite(currentExpiry) && Date.now() >= currentExpiry) {
|
|
3220
|
-
const recovered =
|
|
3220
|
+
const recovered = join18(paths.locks, `.recovered-${heldToken}-${randomUUID3()}.json`);
|
|
3221
3221
|
renameStateFile(lock, recovered, paths.root);
|
|
3222
3222
|
unlinkStateFile(recovered, paths.root);
|
|
3223
3223
|
}
|
|
@@ -3297,7 +3297,7 @@ function scanAuxiliaryState(paths, limits) {
|
|
|
3297
3297
|
addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: "non-regular" });
|
|
3298
3298
|
continue;
|
|
3299
3299
|
}
|
|
3300
|
-
const read = safeReadJson(
|
|
3300
|
+
const read = safeReadJson(join18(specification.dir, entry.name), specification.maxBytes);
|
|
3301
3301
|
if (read.reason || read.value === void 0) {
|
|
3302
3302
|
addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: read.reason ?? "invalid-json" }, read.bytes);
|
|
3303
3303
|
continue;
|
|
@@ -3322,7 +3322,7 @@ function scanAuxiliaryState(paths, limits) {
|
|
|
3322
3322
|
addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: "non-regular" });
|
|
3323
3323
|
continue;
|
|
3324
3324
|
}
|
|
3325
|
-
const read = safeReadJson(
|
|
3325
|
+
const read = safeReadJson(join18(paths.journals, entry.name), limits.receipt_max_bytes);
|
|
3326
3326
|
if (read.reason || read.value === void 0) {
|
|
3327
3327
|
addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: read.reason ?? "invalid-json" }, read.bytes);
|
|
3328
3328
|
continue;
|
|
@@ -3351,7 +3351,7 @@ function scanAuxiliaryState(paths, limits) {
|
|
|
3351
3351
|
addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: "non-regular" });
|
|
3352
3352
|
continue;
|
|
3353
3353
|
}
|
|
3354
|
-
const read = safeReadJson(
|
|
3354
|
+
const read = safeReadJson(join18(paths.locks, entry.name), 8192);
|
|
3355
3355
|
if (read.reason || read.value === void 0) {
|
|
3356
3356
|
addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: read.reason ?? "invalid-json" }, read.bytes);
|
|
3357
3357
|
continue;
|
|
@@ -3390,7 +3390,7 @@ function scanReceipts(paths, limits) {
|
|
|
3390
3390
|
}
|
|
3391
3391
|
for (const entry of entries) {
|
|
3392
3392
|
const entryId = safeEntryId("receipts", entry.name);
|
|
3393
|
-
const path =
|
|
3393
|
+
const path = join18(paths.receipts, entry.name);
|
|
3394
3394
|
if (!entry.isFile() || entry.isSymbolicLink() || !/^[a-f0-9]{64}\.json$/u.test(entry.name)) {
|
|
3395
3395
|
addIntegrity({ entry_id: entryId, reason: "non-regular" });
|
|
3396
3396
|
continue;
|
|
@@ -3420,7 +3420,7 @@ function scanBaselines(paths, nowMs, limits, referenced) {
|
|
|
3420
3420
|
if (!existsSync13(paths.baselines)) return { current, stale };
|
|
3421
3421
|
for (const entry of readStateDirectory(paths.baselines, paths.root)) {
|
|
3422
3422
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
3423
|
-
const baseline = readBaseline(
|
|
3423
|
+
const baseline = readBaseline(join18(paths.baselines, entry.name), baselineReceiptByteCeiling(limits));
|
|
3424
3424
|
if (!baseline || referenced.has(baseline.session_key)) continue;
|
|
3425
3425
|
const expires = Date.parse(baseline.created_at) + limits.receiptless_session_retention_seconds * 1e3;
|
|
3426
3426
|
if (nowMs >= expires) stale += 1;
|
|
@@ -3434,7 +3434,7 @@ function scanRefusals(paths, nowMs, limits, scan) {
|
|
|
3434
3434
|
const entries = readStateDirectory(paths.refusals, paths.root).sort((a, b) => a.name.localeCompare(b.name, "en")).slice(0, limits.refusal_max_entries);
|
|
3435
3435
|
for (const entry of entries) {
|
|
3436
3436
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
3437
|
-
const read = safeReadJson(
|
|
3437
|
+
const read = safeReadJson(join18(paths.refusals, entry.name), limits.receipt_max_bytes);
|
|
3438
3438
|
const marker = read.value === void 0 ? null : parseRefusal(read.value);
|
|
3439
3439
|
if (!marker || `${marker.session_key}.json` !== entry.name) continue;
|
|
3440
3440
|
if (nowMs >= Date.parse(marker.baseline_created_at) + limits.receiptless_session_retention_seconds * 1e3) continue;
|
|
@@ -3517,7 +3517,7 @@ function listCaptureReceipts(root, projectSlug, limits, state) {
|
|
|
3517
3517
|
function readCaptureReceipt(root, projectSlug, receiptId, limits) {
|
|
3518
3518
|
if (!RECEIPT_ID_RE.test(receiptId)) throw new NotebookError("INVALID_INPUT", "Invalid receipt ID");
|
|
3519
3519
|
const paths = notebookStatePaths(root, projectSlug);
|
|
3520
|
-
const path =
|
|
3520
|
+
const path = join18(paths.receipts, `${receiptId}.json`);
|
|
3521
3521
|
if (!existsSync13(path)) throw new NotebookError("NOT_FOUND", `Capture receipt not found: ${receiptId}`);
|
|
3522
3522
|
const read = safeReadJson(path, limits.receipt_max_bytes);
|
|
3523
3523
|
const receipt = read.value === void 0 ? null : parseReceipt(read.value);
|
|
@@ -3530,7 +3530,7 @@ function writeReceipt(paths, receipt, limit) {
|
|
|
3530
3530
|
const bytes = Buffer.byteLength(text, "utf8");
|
|
3531
3531
|
if (bytes > limit) throw new NotebookError("CONFLICT", "Receipt transition exceeds its per-receipt ceiling");
|
|
3532
3532
|
if (receipt.serialized_bytes === bytes) {
|
|
3533
|
-
atomicWriteJson(
|
|
3533
|
+
atomicWriteJson(join18(paths.receipts, `${receipt.receipt_id}.json`), receipt, paths.root);
|
|
3534
3534
|
return receipt;
|
|
3535
3535
|
}
|
|
3536
3536
|
receipt.serialized_bytes = bytes;
|
|
@@ -3669,25 +3669,25 @@ function pruneNotebookState(root, projectSlug, limits, now = /* @__PURE__ */ new
|
|
|
3669
3669
|
const successCutoff = now.getTime() - limits.receipt_succeeded_retention_days * 864e5;
|
|
3670
3670
|
for (const receipt of scan.receipts) {
|
|
3671
3671
|
if (receipt.state !== "succeeded" || Date.parse(receipt.updated_at) > successCutoff) continue;
|
|
3672
|
-
const path =
|
|
3672
|
+
const path = join18(paths.receipts, `${receipt.receipt_id}.json`);
|
|
3673
3673
|
unlinkStateFile(path, paths.root);
|
|
3674
|
-
removed.push(safeEntryId("receipts",
|
|
3674
|
+
removed.push(safeEntryId("receipts", basename9(path)));
|
|
3675
3675
|
}
|
|
3676
3676
|
const remaining = scanReceipts(paths, limits);
|
|
3677
3677
|
const remainingAuxiliary = scanAuxiliaryState(paths, limits);
|
|
3678
3678
|
const referenced = /* @__PURE__ */ new Set([...remaining.referencedSessions, ...remainingAuxiliary.referencedSessions]);
|
|
3679
3679
|
for (const entry of readStateDirectory(paths.baselines, paths.root)) {
|
|
3680
3680
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
3681
|
-
const path =
|
|
3681
|
+
const path = join18(paths.baselines, entry.name);
|
|
3682
3682
|
const baseline = readBaseline(path, baselineReceiptByteCeiling(limits));
|
|
3683
3683
|
if (!baseline || referenced.has(baseline.session_key)) continue;
|
|
3684
3684
|
if (now.getTime() < Date.parse(baseline.created_at) + limits.receiptless_session_retention_seconds * 1e3) continue;
|
|
3685
3685
|
unlinkStateFile(path, paths.root);
|
|
3686
3686
|
removed.push(safeEntryId("baselines", entry.name));
|
|
3687
|
-
const claim =
|
|
3688
|
-
if (unlinkStateFile(claim, paths.root, true)) removed.push(safeEntryId("claims",
|
|
3689
|
-
const refusal =
|
|
3690
|
-
if (unlinkStateFile(refusal, paths.root, true)) removed.push(safeEntryId("refusals",
|
|
3687
|
+
const claim = join18(paths.claims, `${baseline.session_key}.overview`);
|
|
3688
|
+
if (unlinkStateFile(claim, paths.root, true)) removed.push(safeEntryId("claims", basename9(claim)));
|
|
3689
|
+
const refusal = join18(paths.refusals, `${baseline.session_key}.json`);
|
|
3690
|
+
if (unlinkStateFile(refusal, paths.root, true)) removed.push(safeEntryId("refusals", basename9(refusal)));
|
|
3691
3691
|
}
|
|
3692
3692
|
fsyncDirectory2(paths.project);
|
|
3693
3693
|
return removed;
|
|
@@ -3733,7 +3733,7 @@ var init_state = __esm({
|
|
|
3733
3733
|
"REMOTE_PROTOCOL_ERROR",
|
|
3734
3734
|
"INTERNAL_ERROR"
|
|
3735
3735
|
]);
|
|
3736
|
-
DIRECTORY_OPEN_FLAGS =
|
|
3736
|
+
DIRECTORY_OPEN_FLAGS = constants3.O_RDONLY | (constants3.O_DIRECTORY ?? 0) | (constants3.O_NOFOLLOW ?? 0);
|
|
3737
3737
|
}
|
|
3738
3738
|
});
|
|
3739
3739
|
|
|
@@ -4072,8 +4072,8 @@ var init_open_notebook_client = __esm({
|
|
|
4072
4072
|
|
|
4073
4073
|
// src/notebook/git-evidence.ts
|
|
4074
4074
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
4075
|
-
import { closeSync as
|
|
4076
|
-
import { extname, join as
|
|
4075
|
+
import { closeSync as closeSync6, constants as constants4, fstatSync as fstatSync3, lstatSync as lstatSync15, openSync as openSync6, readSync as readSync2, realpathSync as realpathSync8 } from "node:fs";
|
|
4076
|
+
import { extname, join as join20, relative as relative13, resolve as resolve13, sep as sep5 } from "node:path";
|
|
4077
4077
|
function git(repo, args, maxBuffer = 4 * 1024 * 1024, timeout = 5e3) {
|
|
4078
4078
|
const result2 = spawnSync8("git", args, { cwd: repo, encoding: "utf8", maxBuffer, timeout, shell: false });
|
|
4079
4079
|
return { ok: result2.status === 0, stdout: result2.stdout ?? "" };
|
|
@@ -4151,9 +4151,9 @@ function hasSymlinkComponent(root, candidate) {
|
|
|
4151
4151
|
const rel = relative13(root, candidate);
|
|
4152
4152
|
let cursor = root;
|
|
4153
4153
|
for (const part of rel.split(sep5).filter(Boolean)) {
|
|
4154
|
-
cursor =
|
|
4154
|
+
cursor = join20(cursor, part);
|
|
4155
4155
|
try {
|
|
4156
|
-
if (
|
|
4156
|
+
if (lstatSync15(cursor).isSymbolicLink()) return true;
|
|
4157
4157
|
} catch {
|
|
4158
4158
|
return true;
|
|
4159
4159
|
}
|
|
@@ -4165,12 +4165,12 @@ function readSafeEvidenceText(repoPath, relativePath2, maxBytes) {
|
|
|
4165
4165
|
if (!safe || hasSymlinkComponent(safe.root, safe.candidate)) return { status: "excluded", reason: "unsafe-path" };
|
|
4166
4166
|
let fd;
|
|
4167
4167
|
try {
|
|
4168
|
-
fd =
|
|
4168
|
+
fd = openSync6(safe.candidate, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
|
|
4169
4169
|
} catch {
|
|
4170
4170
|
return { status: "excluded", reason: "not-regular" };
|
|
4171
4171
|
}
|
|
4172
4172
|
try {
|
|
4173
|
-
const before =
|
|
4173
|
+
const before = fstatSync3(fd);
|
|
4174
4174
|
if (!before.isFile()) return { status: "excluded", reason: "not-regular" };
|
|
4175
4175
|
if (before.size > maxBytes) return { status: "excluded", reason: "oversize" };
|
|
4176
4176
|
let physical;
|
|
@@ -4191,7 +4191,7 @@ function readSafeEvidenceText(repoPath, relativePath2, maxBytes) {
|
|
|
4191
4191
|
if (total > maxBytes) return { status: "excluded", reason: "oversize" };
|
|
4192
4192
|
chunks.push(chunk.subarray(0, count));
|
|
4193
4193
|
}
|
|
4194
|
-
const after =
|
|
4194
|
+
const after = fstatSync3(fd);
|
|
4195
4195
|
if (after.dev !== before.dev || after.ino !== before.ino || after.size !== before.size || total !== before.size) return { status: "excluded", reason: "changed-during-read" };
|
|
4196
4196
|
const bytes = Buffer.concat(chunks, total);
|
|
4197
4197
|
if (bytes.includes(0)) return { status: "excluded", reason: "binary" };
|
|
@@ -4204,7 +4204,7 @@ function readSafeEvidenceText(repoPath, relativePath2, maxBytes) {
|
|
|
4204
4204
|
if (looksSecret(relativePath2, content)) return { status: "excluded", reason: "secret-like" };
|
|
4205
4205
|
return { status: "present", content, content_sha256: sha256Hex(bytes), bytes: total };
|
|
4206
4206
|
} finally {
|
|
4207
|
-
|
|
4207
|
+
closeSync6(fd);
|
|
4208
4208
|
}
|
|
4209
4209
|
}
|
|
4210
4210
|
function looksGenerated(path) {
|
|
@@ -4294,10 +4294,10 @@ var init_git_evidence = __esm({
|
|
|
4294
4294
|
|
|
4295
4295
|
// src/notebook/remote-mutation-journal.ts
|
|
4296
4296
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
4297
|
-
import { join as
|
|
4297
|
+
import { join as join21 } from "node:path";
|
|
4298
4298
|
function journalPath(root, projectSlug, operationId) {
|
|
4299
4299
|
if (!/^[a-f0-9-]{16,64}$/iu.test(operationId)) throw new NotebookError("INVALID_INPUT", "Invalid remote mutation operation ID");
|
|
4300
|
-
return
|
|
4300
|
+
return join21(ensureNotebookState(root, projectSlug).journals, `${operationId}.json`);
|
|
4301
4301
|
}
|
|
4302
4302
|
function remoteMutationJournalPath(root, projectSlug, operationId) {
|
|
4303
4303
|
return journalPath(root, projectSlug, operationId);
|
|
@@ -4317,7 +4317,7 @@ function listRemoteMutationJournals(root, projectSlug) {
|
|
|
4317
4317
|
if (!entry.isFile() || entry.isSymbolicLink() || !/^[a-f0-9-]{16,64}\.json$/iu.test(entry.name)) {
|
|
4318
4318
|
throw new NotebookError("CONFLICT", `Remote mutation journal state-integrity finding: journals/${entry.name}`);
|
|
4319
4319
|
}
|
|
4320
|
-
const read = readNotebookStateJson(
|
|
4320
|
+
const read = readNotebookStateJson(join21(paths.journals, entry.name), paths.root, 65536);
|
|
4321
4321
|
const journal = read.value === void 0 ? null : parseRemoteMutationJournal(read.value);
|
|
4322
4322
|
if (!journal || `${journal.operation_id}.json` !== entry.name) {
|
|
4323
4323
|
throw new NotebookError("CONFLICT", `Remote mutation journal state-integrity finding: journals/${entry.name}`);
|
|
@@ -4671,7 +4671,7 @@ var init_reconcile = __esm({
|
|
|
4671
4671
|
|
|
4672
4672
|
// src/notebook/entropy.ts
|
|
4673
4673
|
import { existsSync as existsSync16 } from "node:fs";
|
|
4674
|
-
import { createHash as
|
|
4674
|
+
import { createHash as createHash14 } from "node:crypto";
|
|
4675
4675
|
import { resolve as resolve16 } from "node:path";
|
|
4676
4676
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
4677
4677
|
function listAllTrackedDocuments(config) {
|
|
@@ -4726,7 +4726,7 @@ async function compactSessionCaptures(input) {
|
|
|
4726
4726
|
const toCompact = sessionHeaders.slice(maxRecent);
|
|
4727
4727
|
const notesToCompact = typeof input.client.hydrateNotes === "function" ? await input.client.hydrateNotes(toCompact) : toCompact;
|
|
4728
4728
|
const archiveTitle = "Session Captures Archive (Prior Sessions)";
|
|
4729
|
-
const slugHash =
|
|
4729
|
+
const slugHash = createHash14("sha256").update(`session-capture-archive:${input.projectSlug}`).digest("hex").slice(0, 32);
|
|
4730
4730
|
const archiveLogicalId = `user-note:v1:${slugHash}`;
|
|
4731
4731
|
let existingArchiveContent = "";
|
|
4732
4732
|
let existingArchiveId = null;
|
|
@@ -5294,7 +5294,7 @@ var init_capture = __esm({
|
|
|
5294
5294
|
|
|
5295
5295
|
// src/mcp-server.ts
|
|
5296
5296
|
import { existsSync as existsSync22, statSync as statSync8 } from "node:fs";
|
|
5297
|
-
import { dirname as dirname20, join as
|
|
5297
|
+
import { dirname as dirname20, join as join32, resolve as resolve22 } from "node:path";
|
|
5298
5298
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
5299
5299
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5300
5300
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -7274,20 +7274,20 @@ function planSkillRoots(repoRoot, options = {}) {
|
|
|
7274
7274
|
for (const name of readdirSync6(real).sort()) {
|
|
7275
7275
|
const path = join7(real, name);
|
|
7276
7276
|
const label = `${alias}/${name}`;
|
|
7277
|
-
const
|
|
7277
|
+
const entryStat2 = lstatSync6(path);
|
|
7278
7278
|
const rows = tracked.get(name) ?? [];
|
|
7279
|
-
const candidate = { alias, name, path, label, kind:
|
|
7279
|
+
const candidate = { alias, name, path, label, kind: entryStat2.isSymbolicLink() ? "link" : entryStat2.isDirectory() ? "directory" : "file", tracked: rows.map((row) => row.path) };
|
|
7280
7280
|
if (name === ".git") {
|
|
7281
7281
|
candidate.staticBlock = `${label} is git metadata; refusing to relocate another repository's work tree`;
|
|
7282
|
-
} else if (
|
|
7282
|
+
} else if (entryStat2.isSymbolicLink()) {
|
|
7283
7283
|
candidate.text = readlinkSync5(path);
|
|
7284
7284
|
candidate.resolution = resolveIn(diskLayout, path);
|
|
7285
7285
|
if (rows.length) candidate.staticBlock = `${label} -> ${candidate.text} is tracked in git; a tracked link cannot be attested BMAD installer output, so it is never untracked, moved or dropped`;
|
|
7286
|
-
} else if (!
|
|
7286
|
+
} else if (!entryStat2.isDirectory() && !entryStat2.isFile()) {
|
|
7287
7287
|
candidate.staticBlock = `${label} is a special file; refusing to relocate it`;
|
|
7288
7288
|
} else {
|
|
7289
7289
|
candidate.staticBlock = entryHazard(path, label);
|
|
7290
|
-
if (!candidate.staticBlock && rootDevice !== void 0 &&
|
|
7290
|
+
if (!candidate.staticBlock && rootDevice !== void 0 && entryStat2.dev !== rootDevice) candidate.staticBlock = `${label} is on a different filesystem than .agents/skills; refusing a copying move`;
|
|
7291
7291
|
const linkRow = rows.find((row) => row.mode === "120000");
|
|
7292
7292
|
const gitlink = rows.find((row) => row.mode === "160000");
|
|
7293
7293
|
if (!candidate.staticBlock && gitlink) candidate.staticBlock = `${label} holds a tracked submodule (${gitlink.path}); refusing to relocate it`;
|
|
@@ -9194,14 +9194,324 @@ function createG33CompanionCheck() {
|
|
|
9194
9194
|
}
|
|
9195
9195
|
|
|
9196
9196
|
// src/parity/rules.ts
|
|
9197
|
-
import { existsSync as existsSync8, lstatSync as
|
|
9198
|
-
|
|
9197
|
+
import { existsSync as existsSync8, lstatSync as lstatSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync11, readlinkSync as readlinkSync8, readdirSync as readdirSync8, realpathSync as realpathSync4, renameSync as renameSync4, symlinkSync as symlinkSync3, unlinkSync as unlinkSync6, writeFileSync as writeFileSync9, chmodSync as chmodSync3, copyFileSync, rmSync as rmSync5 } from "node:fs";
|
|
9198
|
+
|
|
9199
|
+
// src/bmad/setup-scripts.ts
|
|
9200
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
9201
|
+
import { closeSync as closeSync2, fchmodSync, fstatSync, linkSync, lstatSync as lstatSync9, mkdirSync as mkdirSync6, mkdtempSync as mkdtempSync2, openSync as openSync2, readFileSync as readFileSync9, readdirSync as readdirSync7, rmdirSync as rmdirSync3, unlinkSync as unlinkSync5, writeFileSync as writeFileSync7, constants } from "node:fs";
|
|
9202
|
+
import { basename as basename4, join as join10 } from "node:path";
|
|
9203
|
+
function entryStat(path) {
|
|
9204
|
+
try {
|
|
9205
|
+
return lstatSync9(path);
|
|
9206
|
+
} catch (error) {
|
|
9207
|
+
if (error.code === "ENOENT") return void 0;
|
|
9208
|
+
throw error;
|
|
9209
|
+
}
|
|
9210
|
+
}
|
|
9211
|
+
var RUNTIME_SCRIPTS = ["config_utils.py", "memlog.py", "render_skill.py", "resolve_config.py", "resolve_customization.py"];
|
|
9212
|
+
function manifestRows(text) {
|
|
9213
|
+
const rows = [];
|
|
9214
|
+
let row = [], field2 = "", quoted = false, closed = false, inQuotes = false;
|
|
9215
|
+
for (let i = 0; i < text.length; i++) {
|
|
9216
|
+
const c = text[i];
|
|
9217
|
+
if (inQuotes) {
|
|
9218
|
+
if (c === '"' && text[i + 1] === '"') {
|
|
9219
|
+
field2 += '"';
|
|
9220
|
+
i++;
|
|
9221
|
+
} else if (c === '"') {
|
|
9222
|
+
inQuotes = false;
|
|
9223
|
+
closed = true;
|
|
9224
|
+
} else field2 += c;
|
|
9225
|
+
} else if (c === '"' && !field2 && !closed) {
|
|
9226
|
+
inQuotes = true;
|
|
9227
|
+
quoted = true;
|
|
9228
|
+
} else if (c === ",") {
|
|
9229
|
+
row.push(field2);
|
|
9230
|
+
field2 = "";
|
|
9231
|
+
quoted = closed = false;
|
|
9232
|
+
} else if (c === "\n" || c === "\r") {
|
|
9233
|
+
if (c === "\r" && text[i + 1] === "\n") i++;
|
|
9234
|
+
row.push(field2);
|
|
9235
|
+
if (row.length !== 1 || row[0]) rows.push(row);
|
|
9236
|
+
row = [];
|
|
9237
|
+
field2 = "";
|
|
9238
|
+
quoted = closed = false;
|
|
9239
|
+
} else if (closed || c === '"' || quoted && c === "\r") throw new Error("Malformed BMAD file manifest CSV");
|
|
9240
|
+
else field2 += c;
|
|
9241
|
+
}
|
|
9242
|
+
if (inQuotes) throw new Error("Unterminated BMAD file manifest CSV field");
|
|
9243
|
+
if (row.length || field2 || closed) {
|
|
9244
|
+
row.push(field2);
|
|
9245
|
+
rows.push(row);
|
|
9246
|
+
}
|
|
9247
|
+
if (rows.some((r) => r.length !== 5) || rows[0]?.join("\0") !== "type\0name\0module\0path\0hash") {
|
|
9248
|
+
throw new Error("Invalid BMAD file manifest columns");
|
|
9249
|
+
}
|
|
9250
|
+
return rows.slice(1);
|
|
9251
|
+
}
|
|
9252
|
+
function sameIdentity(a, b) {
|
|
9253
|
+
return !!a && a.dev === b.dev && a.ino === b.ino && a.isDirectory() && !a.isSymbolicLink();
|
|
9254
|
+
}
|
|
9255
|
+
var DeclinableError = class extends Error {
|
|
9256
|
+
constructor(message, kind) {
|
|
9257
|
+
super(message);
|
|
9258
|
+
this.kind = kind;
|
|
9259
|
+
}
|
|
9260
|
+
};
|
|
9261
|
+
var FIELD_SEP = String.fromCharCode(0);
|
|
9262
|
+
var MANIFEST_UNUSABLE = "Installed BMAD file manifest is missing or not a real file; cannot attest recovered scripts";
|
|
9263
|
+
function inspectBmadSetupScripts(repoRoot, version, installedVersion) {
|
|
9264
|
+
const bmadDir = join10(repoRoot, "_bmad");
|
|
9265
|
+
const dest = join10(bmadDir, "scripts");
|
|
9266
|
+
try {
|
|
9267
|
+
const bmad = entryStat(bmadDir);
|
|
9268
|
+
if (!bmad) return { state: "decline", why: "_bmad is absent", missing: [...RUNTIME_SCRIPTS] };
|
|
9269
|
+
if (!bmad.isDirectory() || bmad.isSymbolicLink()) return { state: "unsafe", error: "_bmad is not a real directory" };
|
|
9270
|
+
const destStat = entryStat(dest);
|
|
9271
|
+
if (destStat && (!destStat.isDirectory() || destStat.isSymbolicLink())) return { state: "unsafe", error: "_bmad/scripts is not a real directory" };
|
|
9272
|
+
const missing = [];
|
|
9273
|
+
for (const name of RUNTIME_SCRIPTS) {
|
|
9274
|
+
const existing = destStat ? entryStat(join10(dest, name)) : void 0;
|
|
9275
|
+
if (!existing) missing.push(name);
|
|
9276
|
+
else if (!existing.isFile() || existing.isSymbolicLink()) return { state: "unsafe", error: `Existing _bmad/scripts/${name} is not a regular file; refusing overwrite` };
|
|
9277
|
+
}
|
|
9278
|
+
if (!missing.length) return { state: "present" };
|
|
9279
|
+
const configDir = entryStat(join10(bmadDir, "_config"));
|
|
9280
|
+
if (configDir && (!configDir.isDirectory() || configDir.isSymbolicLink())) return { state: "unsafe", error: MANIFEST_UNUSABLE };
|
|
9281
|
+
const manifestPath = join10(bmadDir, "_config", "files-manifest.csv");
|
|
9282
|
+
const manifestFile = configDir ? entryStat(manifestPath) : void 0;
|
|
9283
|
+
if (!manifestFile) return { state: "decline", why: "_bmad/_config/files-manifest.csv is absent, so recovered scripts cannot be attested", missing };
|
|
9284
|
+
if (!manifestFile.isFile() || manifestFile.isSymbolicLink()) return { state: "unsafe", error: MANIFEST_UNUSABLE };
|
|
9285
|
+
const installed = installedVersion?.();
|
|
9286
|
+
if (installed && installed !== version) {
|
|
9287
|
+
return { state: "decline", why: `this install is bmad-method ${installed}, not the pinned ${version} that recovery restores from`, missing };
|
|
9288
|
+
}
|
|
9289
|
+
const rows = manifestRows(readFileSync9(manifestPath, "utf8"));
|
|
9290
|
+
const unattested = missing.filter((name) => !rows.some((row) => row[3] === `scripts/${name}`));
|
|
9291
|
+
if (unattested.length) {
|
|
9292
|
+
return { state: "decline", why: `_bmad/_config/files-manifest.csv has no row for ${unattested.map((name) => `scripts/${name}`).join(", ")}, so recovered scripts cannot be attested`, missing };
|
|
9293
|
+
}
|
|
9294
|
+
return { state: "missing", missing };
|
|
9295
|
+
} catch (error) {
|
|
9296
|
+
return { state: "unsafe", error: error instanceof Error ? error.message : String(error) };
|
|
9297
|
+
}
|
|
9298
|
+
}
|
|
9299
|
+
function planBmadSetupScripts(repoRoot, pkg, version) {
|
|
9300
|
+
const dest = join10(repoRoot, "_bmad", "scripts");
|
|
9301
|
+
if (!pkg.root || !pkg.entry || pkg.version !== version) return { files: [], declinable: "package", error: `Pinned bmad-method@${version} package could not be resolved: ${pkg.reason ?? "version mismatch"}` };
|
|
9302
|
+
let fingerprint2;
|
|
9303
|
+
try {
|
|
9304
|
+
fingerprint2 = installerFingerprint(pkg.root);
|
|
9305
|
+
} catch (error) {
|
|
9306
|
+
return { files: [], declinable: "package", error: `Could not fingerprint pinned BMAD installer: ${String(error)}` };
|
|
9307
|
+
}
|
|
9308
|
+
if (VERIFIED_BMAD_INSTALLERS[fingerprint2] !== version) return { files: [], declinable: "package", error: `Unverified BMAD installer fingerprint ${fingerprint2}; refusing script recovery` };
|
|
9309
|
+
const source = join10(pkg.root, "src", "scripts");
|
|
9310
|
+
try {
|
|
9311
|
+
const bmad = lstatSync9(join10(repoRoot, "_bmad"));
|
|
9312
|
+
if (!bmad.isDirectory() || bmad.isSymbolicLink()) throw new Error("_bmad is not a real directory");
|
|
9313
|
+
const destStat = entryStat(dest);
|
|
9314
|
+
if (destStat && (!destStat.isDirectory() || destStat.isSymbolicLink())) throw new Error("_bmad/scripts is not a real directory");
|
|
9315
|
+
const names = readdirSync7(source).filter((name) => !name.startsWith(".") && !name.endsWith(".pyc") && name !== "tests" && name !== "__pycache__" && name !== ".pytest_cache").sort();
|
|
9316
|
+
if (names.join(FIELD_SEP) !== [...RUNTIME_SCRIPTS].sort().join(FIELD_SEP)) throw new DeclinableError("Pinned installer script inventory changed; refusing recovery", "package");
|
|
9317
|
+
const missing = [];
|
|
9318
|
+
for (const name of names) {
|
|
9319
|
+
const existing = entryStat(join10(dest, name));
|
|
9320
|
+
if (!existing) missing.push(name);
|
|
9321
|
+
else if (!existing.isFile() || existing.isSymbolicLink()) throw new Error(`Existing _bmad/scripts/${name} is not a regular file; refusing overwrite`);
|
|
9322
|
+
}
|
|
9323
|
+
const files = [];
|
|
9324
|
+
if (!missing.length) return { files, source };
|
|
9325
|
+
const manifestPath = join10(repoRoot, "_bmad", "_config", "files-manifest.csv");
|
|
9326
|
+
const manifestDir = entryStat(join10(repoRoot, "_bmad", "_config"));
|
|
9327
|
+
const manifestFile = entryStat(manifestPath);
|
|
9328
|
+
if (!manifestDir?.isDirectory() || manifestDir.isSymbolicLink() || !manifestFile?.isFile() || manifestFile.isSymbolicLink()) {
|
|
9329
|
+
throw new Error(MANIFEST_UNUSABLE);
|
|
9330
|
+
}
|
|
9331
|
+
const rows = manifestRows(readFileSync9(manifestPath, "utf8"));
|
|
9332
|
+
for (const name of missing) {
|
|
9333
|
+
const src = join10(source, name);
|
|
9334
|
+
if (!lstatSync9(src).isFile() || lstatSync9(src).isSymbolicLink()) throw new DeclinableError(`Installer script is not a regular file: ${name}`, "package");
|
|
9335
|
+
const matching = rows.filter((row) => row[3] === `scripts/${name}`);
|
|
9336
|
+
const expectedHash = createHash8("sha256").update(readFileSync9(src)).digest("hex");
|
|
9337
|
+
if (matching.length !== 1 || matching[0].join(FIELD_SEP) !== ["py", name.slice(0, -3), "scripts", `scripts/${name}`, expectedHash].join(FIELD_SEP)) {
|
|
9338
|
+
throw new DeclinableError(`Installed file manifest does not attest pinned script ${name}`, "attest");
|
|
9339
|
+
}
|
|
9340
|
+
files.push(join10(dest, name));
|
|
9341
|
+
}
|
|
9342
|
+
return { files, source };
|
|
9343
|
+
} catch (error) {
|
|
9344
|
+
return { files: [], ...error instanceof DeclinableError ? { declinable: error.kind } : {}, error: `BMAD script recovery refused: ${error instanceof Error ? error.message : String(error)}` };
|
|
9345
|
+
}
|
|
9346
|
+
}
|
|
9347
|
+
function applyBmadSetupScripts(repoRoot, pkg, version, afterPublish) {
|
|
9348
|
+
const plan = planBmadSetupScripts(repoRoot, pkg, version);
|
|
9349
|
+
if (plan.error || !plan.files.length) return { files: [], ...plan.error ? { error: plan.error } : {} };
|
|
9350
|
+
const parent = join10(repoRoot, "_bmad");
|
|
9351
|
+
const dest = join10(parent, "scripts");
|
|
9352
|
+
const expectedParent = entryStat(parent);
|
|
9353
|
+
const expectedDir = entryStat(dest);
|
|
9354
|
+
let stage;
|
|
9355
|
+
let stageFd;
|
|
9356
|
+
let parentFd;
|
|
9357
|
+
const created = [];
|
|
9358
|
+
const stagedIdentities = /* @__PURE__ */ new Map();
|
|
9359
|
+
const published = /* @__PURE__ */ new Map();
|
|
9360
|
+
let ambiguousPublication;
|
|
9361
|
+
const matchesPayload = (path, owned) => {
|
|
9362
|
+
const stat = entryStat(path);
|
|
9363
|
+
return !!stat?.isFile() && !stat.isSymbolicLink() && stat.dev === owned.dev && stat.ino === owned.ino && (stat.mode & 4095) === owned.mode && readFileSync9(path).equals(owned.bytes);
|
|
9364
|
+
};
|
|
9365
|
+
let madeDir = false;
|
|
9366
|
+
let dirFd;
|
|
9367
|
+
let result2 = { files: [] };
|
|
9368
|
+
try {
|
|
9369
|
+
parentFd = openSync2(parent, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
9370
|
+
const pinnedParent = fstatSync(parentFd);
|
|
9371
|
+
if (!sameIdentity(expectedParent, pinnedParent) || !sameIdentity(entryStat(parent), pinnedParent)) throw new Error("BMAD parent directory changed before staging");
|
|
9372
|
+
const anchoredParent = `/proc/self/fd/${parentFd}`;
|
|
9373
|
+
stage = mkdtempSync2(join10(anchoredParent, ".scripts-stage-"));
|
|
9374
|
+
stageFd = openSync2(stage, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
9375
|
+
const pinnedStage = fstatSync(stageFd);
|
|
9376
|
+
const stageName = basename4(stage);
|
|
9377
|
+
const anchoredStage = `/proc/self/fd/${stageFd}`;
|
|
9378
|
+
if (!sameIdentity(entryStat(join10(anchoredParent, stageName)), pinnedStage)) throw new Error("Staging directory changed during creation");
|
|
9379
|
+
for (const path of plan.files) {
|
|
9380
|
+
const name = basename4(path);
|
|
9381
|
+
const src = join10(plan.source, name);
|
|
9382
|
+
const staged = join10(anchoredStage, name);
|
|
9383
|
+
const fileFd = openSync2(staged, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 384);
|
|
9384
|
+
try {
|
|
9385
|
+
const stat = fstatSync(fileFd);
|
|
9386
|
+
const bytes = readFileSync9(src);
|
|
9387
|
+
const mode = lstatSync9(src).mode & 4095;
|
|
9388
|
+
stagedIdentities.set(name, { dev: stat.dev, ino: stat.ino, bytes, mode });
|
|
9389
|
+
writeFileSync7(fileFd, bytes);
|
|
9390
|
+
fchmodSync(fileFd, mode);
|
|
9391
|
+
} finally {
|
|
9392
|
+
closeSync2(fileFd);
|
|
9393
|
+
}
|
|
9394
|
+
}
|
|
9395
|
+
if (!sameIdentity(entryStat(parent), pinnedParent)) throw new Error("BMAD parent directory changed while staging");
|
|
9396
|
+
const current = planBmadSetupScripts(repoRoot, pkg, version);
|
|
9397
|
+
if (current.error || current.files.join("\0") !== plan.files.join("\0")) throw new Error(current.error ?? "Script destination changed while staging");
|
|
9398
|
+
const anchoredDest = join10(anchoredParent, "scripts");
|
|
9399
|
+
if (!entryStat(anchoredDest)) {
|
|
9400
|
+
mkdirSync6(anchoredDest);
|
|
9401
|
+
madeDir = true;
|
|
9402
|
+
}
|
|
9403
|
+
dirFd = openSync2(anchoredDest, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
9404
|
+
const pinned = fstatSync(dirFd);
|
|
9405
|
+
if (expectedDir && !sameIdentity(expectedDir, pinned)) throw new Error("Script directory changed while staging");
|
|
9406
|
+
if (!sameIdentity(entryStat(parent), pinnedParent) || !sameIdentity(entryStat(dest), pinned)) throw new Error("Script or parent directory changed during publication");
|
|
9407
|
+
const anchored = `/proc/self/fd/${dirFd}`;
|
|
9408
|
+
for (const path of plan.files) {
|
|
9409
|
+
const name = basename4(path);
|
|
9410
|
+
const staged = join10(anchoredStage, name);
|
|
9411
|
+
const output = join10(anchored, name);
|
|
9412
|
+
const owned = stagedIdentities.get(name);
|
|
9413
|
+
if (!sameIdentity(entryStat(parent), pinnedParent) || !sameIdentity(entryStat(dest), pinned)) throw new Error("Script or parent directory changed during publication");
|
|
9414
|
+
if (!matchesPayload(staged, owned)) throw new Error(`Staged script changed before publication: ${name}`);
|
|
9415
|
+
if (entryStat(output)) {
|
|
9416
|
+
ambiguousPublication = path;
|
|
9417
|
+
throw new Error(`Script destination appeared before publication: ${name}`);
|
|
9418
|
+
}
|
|
9419
|
+
try {
|
|
9420
|
+
linkSync(staged, output);
|
|
9421
|
+
} catch (error) {
|
|
9422
|
+
if (entryStat(output)) ambiguousPublication = path;
|
|
9423
|
+
throw error;
|
|
9424
|
+
}
|
|
9425
|
+
published.set(path, owned);
|
|
9426
|
+
created.push(path);
|
|
9427
|
+
if (!matchesPayload(output, owned)) throw new Error(`Published script changed during publication: ${name}`);
|
|
9428
|
+
afterPublish?.(created.length);
|
|
9429
|
+
}
|
|
9430
|
+
if (!sameIdentity(entryStat(parent), pinnedParent) || !sameIdentity(entryStat(dest), pinned)) throw new Error("Script or parent directory changed during publication");
|
|
9431
|
+
for (const path of created) {
|
|
9432
|
+
if (!matchesPayload(join10(anchored, basename4(path)), stagedIdentities.get(basename4(path)))) throw new Error(`Published script changed before completion: ${basename4(path)}`);
|
|
9433
|
+
}
|
|
9434
|
+
result2 = { files: created };
|
|
9435
|
+
} catch (error) {
|
|
9436
|
+
const recoveryErrors = [];
|
|
9437
|
+
for (const [path, owned] of [...published].reverse()) {
|
|
9438
|
+
try {
|
|
9439
|
+
const anchoredPath = join10(`/proc/self/fd/${dirFd}`, basename4(path));
|
|
9440
|
+
const stat = entryStat(anchoredPath);
|
|
9441
|
+
if (matchesPayload(anchoredPath, owned)) unlinkSync5(anchoredPath);
|
|
9442
|
+
else if (stat) recoveryErrors.push(`Concurrent replacement or mutation retained: ${path}`);
|
|
9443
|
+
else recoveryErrors.push(`Published file absent from pinned directory; ownership unresolved: ${path}`);
|
|
9444
|
+
} catch (recoveryError) {
|
|
9445
|
+
recoveryErrors.push(`Could not restore ${path}: ${String(recoveryError)}`);
|
|
9446
|
+
}
|
|
9447
|
+
}
|
|
9448
|
+
if (ambiguousPublication) recoveryErrors.push(`Publication not confirmed; destination retained or ownership unresolved: ${ambiguousPublication}`);
|
|
9449
|
+
if (madeDir && dirFd !== void 0 && parentFd !== void 0) {
|
|
9450
|
+
try {
|
|
9451
|
+
const pinned = fstatSync(dirFd);
|
|
9452
|
+
const anchoredDest = join10(`/proc/self/fd/${parentFd}`, "scripts");
|
|
9453
|
+
if (sameIdentity(entryStat(anchoredDest), pinned) && readdirSync7(anchoredDest).length === 0) rmdirSync3(anchoredDest);
|
|
9454
|
+
else recoveryErrors.push(`Created script directory moved or retained: ${dest}`);
|
|
9455
|
+
} catch (recoveryError) {
|
|
9456
|
+
recoveryErrors.push(`Could not remove empty script directory: ${String(recoveryError)}`);
|
|
9457
|
+
}
|
|
9458
|
+
}
|
|
9459
|
+
result2 = { files: [], error: `BMAD script recovery failed: ${error instanceof Error ? error.message : String(error)}${recoveryErrors.length ? `; recovery incomplete: ${recoveryErrors.join("; ")}` : ""}` };
|
|
9460
|
+
} finally {
|
|
9461
|
+
const cleanupErrors = [];
|
|
9462
|
+
if (stageFd !== void 0) {
|
|
9463
|
+
try {
|
|
9464
|
+
const pinnedStage = fstatSync(stageFd);
|
|
9465
|
+
const anchoredStage = `/proc/self/fd/${stageFd}`;
|
|
9466
|
+
for (const [name, owned] of stagedIdentities) {
|
|
9467
|
+
const staged = join10(anchoredStage, name);
|
|
9468
|
+
const stat = entryStat(staged);
|
|
9469
|
+
if (stat?.isFile() && !stat.isSymbolicLink() && stat.dev === owned.dev && stat.ino === owned.ino) unlinkSync5(staged);
|
|
9470
|
+
else cleanupErrors.push(`Staged file replaced, modified or absent: ${name}`);
|
|
9471
|
+
}
|
|
9472
|
+
const stagePath = join10(`/proc/self/fd/${parentFd}`, basename4(stage));
|
|
9473
|
+
if (sameIdentity(entryStat(stagePath), pinnedStage) && readdirSync7(anchoredStage).length === 0) rmdirSync3(stagePath);
|
|
9474
|
+
else cleanupErrors.push(`Staging directory moved or contains foreign entries: ${join10(parent, basename4(stage))}`);
|
|
9475
|
+
} catch (cleanupError) {
|
|
9476
|
+
cleanupErrors.push(`Could not clean owned staging directory: ${String(cleanupError)}`);
|
|
9477
|
+
}
|
|
9478
|
+
closeSync2(stageFd);
|
|
9479
|
+
} else if (stage) cleanupErrors.push(`Staging directory identity unavailable: ${join10(parent, basename4(stage))}`);
|
|
9480
|
+
if (cleanupErrors.length && result2.files.length) {
|
|
9481
|
+
for (const [path, owned] of [...published].reverse()) {
|
|
9482
|
+
try {
|
|
9483
|
+
const output = join10(`/proc/self/fd/${dirFd}`, basename4(path));
|
|
9484
|
+
if (matchesPayload(output, owned)) unlinkSync5(output);
|
|
9485
|
+
else cleanupErrors.push(`Concurrent replacement or mutation retained: ${path}`);
|
|
9486
|
+
} catch (error) {
|
|
9487
|
+
cleanupErrors.push(`Could not restore ${path}: ${String(error)}`);
|
|
9488
|
+
}
|
|
9489
|
+
}
|
|
9490
|
+
if (madeDir && dirFd !== void 0 && parentFd !== void 0) {
|
|
9491
|
+
try {
|
|
9492
|
+
const anchoredDest = join10(`/proc/self/fd/${parentFd}`, "scripts");
|
|
9493
|
+
if (sameIdentity(entryStat(anchoredDest), fstatSync(dirFd)) && readdirSync7(anchoredDest).length === 0) rmdirSync3(anchoredDest);
|
|
9494
|
+
else cleanupErrors.push(`Created script directory moved or retained: ${dest}`);
|
|
9495
|
+
} catch (error) {
|
|
9496
|
+
cleanupErrors.push(`Could not remove empty script directory: ${String(error)}`);
|
|
9497
|
+
}
|
|
9498
|
+
}
|
|
9499
|
+
}
|
|
9500
|
+
if (dirFd !== void 0) closeSync2(dirFd);
|
|
9501
|
+
if (parentFd !== void 0) closeSync2(parentFd);
|
|
9502
|
+
if (cleanupErrors.length) result2 = { files: [], error: `${result2.error ? `${result2.error}; ` : "BMAD script staging cleanup incomplete: "}recovery incomplete: ${cleanupErrors.join("; ")}` };
|
|
9503
|
+
}
|
|
9504
|
+
return result2;
|
|
9505
|
+
}
|
|
9506
|
+
|
|
9507
|
+
// src/parity/rules.ts
|
|
9508
|
+
import { basename as basename5, dirname as dirname8, join as join12, relative as relative9, resolve as resolve6 } from "node:path";
|
|
9199
9509
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
9200
9510
|
import YAML4 from "yaml";
|
|
9201
9511
|
|
|
9202
9512
|
// src/bmad/reserved-modules.ts
|
|
9203
|
-
import { existsSync as existsSync7, readFileSync as
|
|
9204
|
-
import { join as
|
|
9513
|
+
import { existsSync as existsSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "node:fs";
|
|
9514
|
+
import { join as join11 } from "node:path";
|
|
9205
9515
|
import YAML3 from "yaml";
|
|
9206
9516
|
var BMAD_RESERVED_DIR_NAMES = ["custom", "scripts", "_config", "_memory", "memory", "render"];
|
|
9207
9517
|
var indentOf = (line) => line.length - line.trimStart().length;
|
|
@@ -9258,19 +9568,19 @@ function stripReservedModuleEntries(raw) {
|
|
|
9258
9568
|
return { text: lines.filter((_, index) => !drop.has(index)).join(""), evicted };
|
|
9259
9569
|
}
|
|
9260
9570
|
function evictReservedModuleEntries(repoRoot, dryRun) {
|
|
9261
|
-
const manifestPath =
|
|
9571
|
+
const manifestPath = join11(repoRoot, "_bmad", "_config", "manifest.yaml");
|
|
9262
9572
|
const none = { evicted: [], changedFiles: [], details: [] };
|
|
9263
9573
|
if (!existsSync7(manifestPath)) return none;
|
|
9264
9574
|
let original;
|
|
9265
9575
|
try {
|
|
9266
|
-
original =
|
|
9576
|
+
original = readFileSync10(manifestPath, "utf8");
|
|
9267
9577
|
} catch {
|
|
9268
9578
|
return none;
|
|
9269
9579
|
}
|
|
9270
9580
|
const stripped = stripReservedModuleEntries(original);
|
|
9271
9581
|
if (!stripped) return none;
|
|
9272
9582
|
const names = stripped.evicted.map((name) => `"${name}"`).join(", ");
|
|
9273
|
-
if (!dryRun)
|
|
9583
|
+
if (!dryRun) writeFileSync8(manifestPath, stripped.text, "utf8");
|
|
9274
9584
|
return {
|
|
9275
9585
|
evicted: stripped.evicted,
|
|
9276
9586
|
changedFiles: [manifestPath],
|
|
@@ -9984,17 +10294,17 @@ function normalizeNewlines(value) {
|
|
|
9984
10294
|
return value.replace(/\r\n/g, "\n");
|
|
9985
10295
|
}
|
|
9986
10296
|
function readText(path) {
|
|
9987
|
-
return normalizeNewlines(
|
|
10297
|
+
return normalizeNewlines(readFileSync11(path, "utf8"));
|
|
9988
10298
|
}
|
|
9989
10299
|
function safeReadText2(path) {
|
|
9990
10300
|
return existsSync8(path) ? readText(path) : null;
|
|
9991
10301
|
}
|
|
9992
10302
|
function ensureParent(path) {
|
|
9993
|
-
|
|
10303
|
+
mkdirSync7(dirname8(path), { recursive: true });
|
|
9994
10304
|
}
|
|
9995
10305
|
function writeText(path, content) {
|
|
9996
10306
|
ensureParent(path);
|
|
9997
|
-
|
|
10307
|
+
writeFileSync9(path, content);
|
|
9998
10308
|
}
|
|
9999
10309
|
function tryParseJson(text) {
|
|
10000
10310
|
if (!text) return null;
|
|
@@ -10020,12 +10330,12 @@ function readSymlinkTarget(path) {
|
|
|
10020
10330
|
}
|
|
10021
10331
|
function ensureSymlink(path, target, dryRun) {
|
|
10022
10332
|
if (existsSync8(path)) {
|
|
10023
|
-
const stat =
|
|
10333
|
+
const stat = lstatSync10(path);
|
|
10024
10334
|
if (stat.isSymbolicLink()) {
|
|
10025
10335
|
const current = readSymlinkTarget(path);
|
|
10026
10336
|
if (current === target) return { changed: false };
|
|
10027
10337
|
if (!dryRun) {
|
|
10028
|
-
|
|
10338
|
+
unlinkSync6(path);
|
|
10029
10339
|
symlinkSync3(target, path);
|
|
10030
10340
|
}
|
|
10031
10341
|
return { changed: true };
|
|
@@ -10036,12 +10346,12 @@ function ensureSymlink(path, target, dryRun) {
|
|
|
10036
10346
|
return { changed: true };
|
|
10037
10347
|
}
|
|
10038
10348
|
function bootstrapAgentsFile(repoRoot, dryRun) {
|
|
10039
|
-
const agentsPath =
|
|
10349
|
+
const agentsPath = join12(repoRoot, "AGENTS.md");
|
|
10040
10350
|
if (existsSync8(agentsPath)) return { changedFiles: [], details: [] };
|
|
10041
10351
|
for (const file of ["CLAUDE.md", "GEMINI.md"]) {
|
|
10042
|
-
const source =
|
|
10352
|
+
const source = join12(repoRoot, file);
|
|
10043
10353
|
if (!existsSync8(source)) continue;
|
|
10044
|
-
const stat =
|
|
10354
|
+
const stat = lstatSync10(source);
|
|
10045
10355
|
if (stat.isSymbolicLink()) continue;
|
|
10046
10356
|
if (stat.isFile()) {
|
|
10047
10357
|
if (!dryRun) renameSync4(source, agentsPath);
|
|
@@ -10049,9 +10359,9 @@ function bootstrapAgentsFile(repoRoot, dryRun) {
|
|
|
10049
10359
|
}
|
|
10050
10360
|
return { changedFiles: [], details: [], blocked: `${file} exists but is not a regular file; cannot promote to AGENTS.md` };
|
|
10051
10361
|
}
|
|
10052
|
-
const readmePath =
|
|
10362
|
+
const readmePath = join12(repoRoot, "README.md");
|
|
10053
10363
|
if (existsSync8(readmePath)) {
|
|
10054
|
-
const stat =
|
|
10364
|
+
const stat = lstatSync10(readmePath);
|
|
10055
10365
|
if (!stat.isFile()) return { changedFiles: [], details: [], blocked: "README.md exists but is not a regular file; cannot copy to AGENTS.md" };
|
|
10056
10366
|
if (!dryRun) copyFileSync(readmePath, agentsPath);
|
|
10057
10367
|
return { changedFiles: [agentsPath], details: ["Copied README.md to AGENTS.md before wiring agent-file symlinks"] };
|
|
@@ -10089,11 +10399,11 @@ function yamlGet(text, keyPath) {
|
|
|
10089
10399
|
return "";
|
|
10090
10400
|
}
|
|
10091
10401
|
function discoverRoles(repoRoot) {
|
|
10092
|
-
const rolesDir =
|
|
10402
|
+
const rolesDir = join12(repoRoot, "agents", "hermes");
|
|
10093
10403
|
if (!existsSync8(rolesDir)) return [];
|
|
10094
|
-
return
|
|
10095
|
-
const roleDir =
|
|
10096
|
-
const roleYamlPath =
|
|
10404
|
+
return readdirSync8(rolesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => {
|
|
10405
|
+
const roleDir = join12(rolesDir, entry.name);
|
|
10406
|
+
const roleYamlPath = join12(roleDir, "role.yaml");
|
|
10097
10407
|
if (!existsSync8(roleYamlPath)) return null;
|
|
10098
10408
|
const text = readText(roleYamlPath);
|
|
10099
10409
|
const runtimeRepoRaw = yamlGet(text, "runtime.github_repo");
|
|
@@ -10126,26 +10436,26 @@ function discoverRoles(repoRoot) {
|
|
|
10126
10436
|
}).filter((value) => Boolean(value));
|
|
10127
10437
|
}
|
|
10128
10438
|
function templateScript(ctx, name) {
|
|
10129
|
-
const source =
|
|
10439
|
+
const source = join12(ctx.pjanglerRoot, ".mise", "scripts", name);
|
|
10130
10440
|
return existsSync8(source) ? readText(source) : void 0;
|
|
10131
10441
|
}
|
|
10132
10442
|
function templateVersioningScript(ctx) {
|
|
10133
10443
|
return templateScript(ctx, "versioning.sh");
|
|
10134
10444
|
}
|
|
10135
10445
|
function templateLinkAgentfilesScript(ctx) {
|
|
10136
|
-
const source =
|
|
10446
|
+
const source = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", ".mise", "scripts", "link-agentfiles.sh");
|
|
10137
10447
|
return existsSync8(source) ? readText(source) : templateScript(ctx, "link-agentfiles.sh");
|
|
10138
10448
|
}
|
|
10139
10449
|
function templateMaterializeEnvScript(ctx) {
|
|
10140
|
-
const source =
|
|
10450
|
+
const source = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", MATERIALIZE_ENV_SCRIPT_REL);
|
|
10141
10451
|
return existsSync8(source) ? readText(source) : void 0;
|
|
10142
10452
|
}
|
|
10143
10453
|
function resolveAgentHooksLayer(ctx) {
|
|
10144
10454
|
const override = process.env.PJ_AGENT_HOOKS_LAYER;
|
|
10145
10455
|
if (override === "0" || override === "false") return false;
|
|
10146
10456
|
if (override === "1" || override === "true") return true;
|
|
10147
|
-
if (existsSync8(
|
|
10148
|
-
return !existsSync8(
|
|
10457
|
+
if (existsSync8(join12(ctx.repoRoot, ".agents", "hooks", "sync.py"))) return true;
|
|
10458
|
+
return !existsSync8(join12(ctx.homeDir, ".agents", "hooks"));
|
|
10149
10459
|
}
|
|
10150
10460
|
function evaluateMiseConditionals(template, agentHooksLayer) {
|
|
10151
10461
|
const out = [];
|
|
@@ -10171,13 +10481,13 @@ function evaluateMiseConditionals(template, agentHooksLayer) {
|
|
|
10171
10481
|
}
|
|
10172
10482
|
function renderGeneratedProjectMiseToml(ctx, template) {
|
|
10173
10483
|
const project = readProjectJson(ctx);
|
|
10174
|
-
const projectName = String(project?.project_name ??
|
|
10484
|
+
const projectName = String(project?.project_name ?? basename5(ctx.repoRoot) ?? "project");
|
|
10175
10485
|
return evaluateMiseConditionals(template, resolveAgentHooksLayer(ctx)).replace(/\{%\s*raw\s*%\}([\s\S]*?)\{%\s*endraw\s*%\}/g, "$1").replace(/\{\{\s*project_name\s*\}\}/g, projectName);
|
|
10176
10486
|
}
|
|
10177
10487
|
function ensureMiseTomlFromTemplate(ctx, changedFiles) {
|
|
10178
|
-
const targetPath =
|
|
10488
|
+
const targetPath = join12(ctx.repoRoot, "mise.toml");
|
|
10179
10489
|
if (existsSync8(targetPath)) return null;
|
|
10180
|
-
const sourcePath =
|
|
10490
|
+
const sourcePath = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", "mise.toml.jinja");
|
|
10181
10491
|
if (!existsSync8(sourcePath)) return null;
|
|
10182
10492
|
const rendered = renderGeneratedProjectMiseToml(ctx, readText(sourcePath));
|
|
10183
10493
|
changedFiles.push(targetPath);
|
|
@@ -10185,12 +10495,12 @@ function ensureMiseTomlFromTemplate(ctx, changedFiles) {
|
|
|
10185
10495
|
return rendered;
|
|
10186
10496
|
}
|
|
10187
10497
|
function templateCommonProjectText(ctx, rel) {
|
|
10188
|
-
const path =
|
|
10498
|
+
const path = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", rel);
|
|
10189
10499
|
return existsSync8(path) ? readText(path) : void 0;
|
|
10190
10500
|
}
|
|
10191
10501
|
function lstatIfPresent(path) {
|
|
10192
10502
|
try {
|
|
10193
|
-
return
|
|
10503
|
+
return lstatSync10(path);
|
|
10194
10504
|
} catch (error) {
|
|
10195
10505
|
if (error.code === "ENOENT") return void 0;
|
|
10196
10506
|
throw error;
|
|
@@ -10198,7 +10508,7 @@ function lstatIfPresent(path) {
|
|
|
10198
10508
|
}
|
|
10199
10509
|
function templateVersionFilesConf(ctx, repoRoot) {
|
|
10200
10510
|
if (isObsidianPluginRepo(ctx, repoRoot)) return OBSIDIAN_VERSION_FILES_CONF;
|
|
10201
|
-
const packageJson =
|
|
10511
|
+
const packageJson = join12(repoRoot, "package.json");
|
|
10202
10512
|
return existsSync8(packageJson) ? "# mise-versioning manifest: <type> <path>\n# types: json toml cargo csproj gradle plain gittag\njson package.json\ngittag .\n" : "# mise-versioning manifest: <type> <path>\n# types: json toml cargo csproj gradle plain gittag\ngittag .\n";
|
|
10203
10513
|
}
|
|
10204
10514
|
var OBSIDIAN_VERSION_FILES_CONF = "# mise-versioning manifest: <type> <path>\n# types: json toml cargo csproj gradle plain gittag obsidian\n# Obsidian plugin: bump with `mise run version:bump`, release with `npm version` (bare X.Y.Z tag).\njson package.json\nobsidian manifest.json\n";
|
|
@@ -10208,7 +10518,7 @@ function recordedProjectType(ctx) {
|
|
|
10208
10518
|
function isObsidianPluginRepo(ctx, repoRoot) {
|
|
10209
10519
|
const recorded = recordedProjectType(ctx);
|
|
10210
10520
|
if (recorded !== void 0) return recorded === "obsidian-plugin";
|
|
10211
|
-
const manifest = record2(tryParseJson(safeReadText2(
|
|
10521
|
+
const manifest = record2(tryParseJson(safeReadText2(join12(repoRoot, "manifest.json"))));
|
|
10212
10522
|
return typeof manifest.id === "string" && typeof manifest.minAppVersion === "string";
|
|
10213
10523
|
}
|
|
10214
10524
|
function unsupportedVersionTypes(conf, script) {
|
|
@@ -10669,7 +10979,7 @@ function describeRewrite(before, after) {
|
|
|
10669
10979
|
return difference ? `migrate would still rewrite ${difference} in mise.toml` : "migrate would still re-lay out mise.toml (its meaning is unchanged)";
|
|
10670
10980
|
}
|
|
10671
10981
|
function readProjectJson(ctx) {
|
|
10672
|
-
return tryParseJson(safeReadText2(
|
|
10982
|
+
return tryParseJson(safeReadText2(join12(ctx.repoRoot, ".project.json")));
|
|
10673
10983
|
}
|
|
10674
10984
|
function readDeclaredAgents(ctx) {
|
|
10675
10985
|
const project = readProjectJson(ctx);
|
|
@@ -10686,7 +10996,7 @@ function readDeclaredAgents(ctx) {
|
|
|
10686
10996
|
});
|
|
10687
10997
|
}
|
|
10688
10998
|
function readRoleYamlAt(roleDir) {
|
|
10689
|
-
const roleYamlPath =
|
|
10999
|
+
const roleYamlPath = join12(roleDir, "role.yaml");
|
|
10690
11000
|
if (!existsSync8(roleYamlPath)) return null;
|
|
10691
11001
|
const text = readText(roleYamlPath);
|
|
10692
11002
|
return {
|
|
@@ -10698,7 +11008,7 @@ function readRoleYamlAt(roleDir) {
|
|
|
10698
11008
|
}
|
|
10699
11009
|
function declaredRoleIsUnprovisioned(repoRoot, roleDir) {
|
|
10700
11010
|
if (!roleDir) return true;
|
|
10701
|
-
return !existsSync8(
|
|
11011
|
+
return !existsSync8(join12(resolve6(repoRoot, roleDir), "role.yaml"));
|
|
10702
11012
|
}
|
|
10703
11013
|
function validateDeclaredAgent(ctx, declared) {
|
|
10704
11014
|
const details = [];
|
|
@@ -10723,11 +11033,11 @@ function validateDeclaredAgent(ctx, declared) {
|
|
|
10723
11033
|
details.push(`agents.${declared.agentId} should map to agent_id ${roleYaml.agentId}`);
|
|
10724
11034
|
}
|
|
10725
11035
|
if (roleYaml.providerName) {
|
|
10726
|
-
const dispatcher =
|
|
11036
|
+
const dispatcher = join12(roleDir, ".scripts", "lib", "ticket-provider.sh");
|
|
10727
11037
|
if (!existsSync8(dispatcher)) {
|
|
10728
11038
|
details.push(`agents.${declared.agentId} provider dispatcher ${relative9(ctx.repoRoot, dispatcher)} missing`);
|
|
10729
11039
|
}
|
|
10730
|
-
const provider =
|
|
11040
|
+
const provider = join12(roleDir, ".scripts", "providers", `${roleYaml.providerName}.sh`);
|
|
10731
11041
|
if (!existsSync8(provider)) {
|
|
10732
11042
|
details.push(`agents.${declared.agentId} provider script ${relative9(ctx.repoRoot, provider)} missing`);
|
|
10733
11043
|
}
|
|
@@ -10743,7 +11053,7 @@ function repoPathNamesRoot(value, repoRoot, homeDir) {
|
|
|
10743
11053
|
function canonicalProjectJson(ctx) {
|
|
10744
11054
|
const roles = discoverRoles(ctx.repoRoot);
|
|
10745
11055
|
const existing = readProjectJson(ctx) ?? {};
|
|
10746
|
-
const slug = normalizeProjectId(existing.project_id ?? existing.project_slug ?? slugifyRepoName(
|
|
11056
|
+
const slug = normalizeProjectId(existing.project_id ?? existing.project_slug ?? slugifyRepoName(basename5(ctx.repoRoot)));
|
|
10747
11057
|
const firstRole = roles[0];
|
|
10748
11058
|
const ticketProvider = {
|
|
10749
11059
|
...existing.ticket_provider,
|
|
@@ -10813,8 +11123,8 @@ function canonicalProjectJson(ctx) {
|
|
|
10813
11123
|
};
|
|
10814
11124
|
}
|
|
10815
11125
|
function projectJsonFinding(ctx) {
|
|
10816
|
-
const projectPath =
|
|
10817
|
-
const planeJsonPath =
|
|
11126
|
+
const projectPath = join12(ctx.repoRoot, ".project.json");
|
|
11127
|
+
const planeJsonPath = join12(ctx.repoRoot, ".plane.json");
|
|
10818
11128
|
const details = [];
|
|
10819
11129
|
const data = readProjectJson(ctx);
|
|
10820
11130
|
const roles = discoverRoles(ctx.repoRoot);
|
|
@@ -10885,7 +11195,7 @@ var BMAD_DIST_TAGS_TTL_MS = 60 * 60 * 1e3;
|
|
|
10885
11195
|
var DEFAULT_BMAD_MODULES = ["bmm", "bmb", "cis"];
|
|
10886
11196
|
var BMAD_INSTALL_TOOLS = SUPPORTED_BMAD_TOOLS;
|
|
10887
11197
|
function manifestBmadModules(repoRoot) {
|
|
10888
|
-
const manifestPath =
|
|
11198
|
+
const manifestPath = join12(repoRoot, "_bmad", "_config", "manifest.yaml");
|
|
10889
11199
|
const raw = safeReadText2(manifestPath);
|
|
10890
11200
|
if (raw === null) return { status: "absent" };
|
|
10891
11201
|
try {
|
|
@@ -10913,7 +11223,7 @@ function manifestBmadModules(repoRoot) {
|
|
|
10913
11223
|
}
|
|
10914
11224
|
}
|
|
10915
11225
|
function configuredBmadModuleSelection(repoRoot) {
|
|
10916
|
-
const raw = safeReadText2(
|
|
11226
|
+
const raw = safeReadText2(join12(repoRoot, "_bmad", "config.toml"));
|
|
10917
11227
|
if (raw === null) return void 0;
|
|
10918
11228
|
const modules = [...raw.matchAll(/^\[modules\.([A-Za-z0-9][A-Za-z0-9_-]*)\]\s*$/gm)].map((match) => match[1]);
|
|
10919
11229
|
return { modules: Array.from(new Set(modules)).filter((name) => name !== "g33"), hasG33: modules.includes("g33") };
|
|
@@ -10924,24 +11234,60 @@ function selectedBmadModules(repoRoot) {
|
|
|
10924
11234
|
if (manifest.status === "invalid") throw new Error(manifest.error);
|
|
10925
11235
|
return configuredBmadModuleSelection(repoRoot)?.modules ?? [...DEFAULT_BMAD_MODULES];
|
|
10926
11236
|
}
|
|
10927
|
-
|
|
10928
|
-
|
|
10929
|
-
|
|
10930
|
-
|
|
10931
|
-
|
|
10932
|
-
|
|
10933
|
-
|
|
11237
|
+
var BMAD_RUNTIME_SCRIPT_SENTINELS = ["resolve_config.py", "config_utils.py"];
|
|
11238
|
+
function consolidatedBmadConfig(repoRoot) {
|
|
11239
|
+
const path = join12(repoRoot, "_bmad", "config.yaml");
|
|
11240
|
+
if (!existsSync8(path)) return { status: "absent" };
|
|
11241
|
+
let parsed;
|
|
11242
|
+
try {
|
|
11243
|
+
parsed = YAML4.parse(readText(path));
|
|
11244
|
+
} catch (error) {
|
|
11245
|
+
return { status: "unusable", reason: `could not be parsed: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}` };
|
|
11246
|
+
}
|
|
11247
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
11248
|
+
return { status: "unusable", reason: "is not a YAML mapping" };
|
|
11249
|
+
}
|
|
11250
|
+
return { status: "mapping", sections: new Set(Object.keys(parsed)) };
|
|
11251
|
+
}
|
|
11252
|
+
function bmadSentinelGaps(repoRoot, modules = selectedBmadModules(repoRoot)) {
|
|
11253
|
+
const bmad = join12(repoRoot, "_bmad");
|
|
11254
|
+
const gaps = [];
|
|
11255
|
+
const absent = (...segments) => !existsSync8(join12(bmad, ...segments));
|
|
11256
|
+
if (absent("config.toml")) gaps.push({ path: "config.toml", detail: "_bmad/config.toml" });
|
|
11257
|
+
for (const script of BMAD_RUNTIME_SCRIPT_SENTINELS) {
|
|
11258
|
+
if (absent("scripts", script)) gaps.push({ path: join12("scripts", script), detail: `_bmad/scripts/${script}` });
|
|
11259
|
+
}
|
|
11260
|
+
if (absent("_config", "manifest.yaml")) {
|
|
11261
|
+
gaps.push({
|
|
11262
|
+
path: join12("_config", "manifest.yaml"),
|
|
11263
|
+
detail: existsSync8(bmad) ? "_bmad/_config/manifest.yaml (the installer's install record). BMB setup's `--also-remove _config` cleanup is the likely cause: it deletes all of _bmad/_config. Restore it with: pj migrate bmad.scaffold" : "_bmad/_config/manifest.yaml"
|
|
11264
|
+
});
|
|
11265
|
+
}
|
|
11266
|
+
let consolidated;
|
|
11267
|
+
for (const module of modules) {
|
|
11268
|
+
if (!absent(module, "config.yaml")) continue;
|
|
11269
|
+
consolidated ??= consolidatedBmadConfig(repoRoot);
|
|
11270
|
+
if (consolidated.status === "mapping" && consolidated.sections.has(module)) continue;
|
|
11271
|
+
const why = consolidated.status === "absent" ? "" : consolidated.status === "unusable" ? ` (and _bmad/config.yaml ${consolidated.reason}, so it cannot supply a ${module} section)` : ` (and _bmad/config.yaml has no ${module} section)`;
|
|
11272
|
+
gaps.push({ path: join12(module, "config.yaml"), detail: `_bmad/${module}/config.yaml${why}` });
|
|
11273
|
+
}
|
|
11274
|
+
return gaps;
|
|
11275
|
+
}
|
|
11276
|
+
function bmadScaffoldIssues(repoRoot, modules) {
|
|
11277
|
+
const gaps = bmadSentinelGaps(repoRoot, modules);
|
|
11278
|
+
const nameIssues = bmadProjectNameIssues(repoRoot);
|
|
11279
|
+
return { gaps, nameIssues, details: [...gaps.map((gap) => gap.detail), ...nameIssues.details] };
|
|
10934
11280
|
}
|
|
10935
11281
|
function canonicalBmadProjectName(repoRoot) {
|
|
10936
11282
|
const project = readProjectJson({ repoRoot });
|
|
10937
11283
|
const declared = typeof project?.project_name === "string" ? project.project_name.trim() : "";
|
|
10938
|
-
return declared ||
|
|
11284
|
+
return declared || basename5(repoRoot);
|
|
10939
11285
|
}
|
|
10940
11286
|
function bmadProjectNameIssues(repoRoot) {
|
|
10941
11287
|
const expected = canonicalBmadProjectName(repoRoot);
|
|
10942
11288
|
const paths = [];
|
|
10943
11289
|
const details = [];
|
|
10944
|
-
const configToml =
|
|
11290
|
+
const configToml = join12(repoRoot, "_bmad", "config.toml");
|
|
10945
11291
|
const tomlText = safeReadText2(configToml);
|
|
10946
11292
|
const tomlMatch = tomlText?.match(/^project_name\s*=\s*"((?:\\.|[^"\\])*)"\s*$/m);
|
|
10947
11293
|
let tomlName;
|
|
@@ -10956,16 +11302,16 @@ function bmadProjectNameIssues(repoRoot) {
|
|
|
10956
11302
|
paths.push(configToml);
|
|
10957
11303
|
details.push(`_bmad/config.toml project_name must be ${JSON.stringify(expected)}`);
|
|
10958
11304
|
}
|
|
10959
|
-
const bmadRoot =
|
|
11305
|
+
const bmadRoot = join12(repoRoot, "_bmad");
|
|
10960
11306
|
if (existsSync8(bmadRoot)) {
|
|
10961
11307
|
let declared;
|
|
10962
11308
|
try {
|
|
10963
11309
|
declared = selectedBmadModules(repoRoot);
|
|
10964
11310
|
} catch {
|
|
10965
|
-
declared =
|
|
11311
|
+
declared = readdirSync8(bmadRoot);
|
|
10966
11312
|
}
|
|
10967
11313
|
for (const name of new Set(declared)) {
|
|
10968
|
-
const configPath =
|
|
11314
|
+
const configPath = join12(bmadRoot, name, "config.yaml");
|
|
10969
11315
|
const raw = safeReadText2(configPath);
|
|
10970
11316
|
if (raw === null) continue;
|
|
10971
11317
|
let actual;
|
|
@@ -10985,7 +11331,7 @@ function bmadProjectNameIssues(repoRoot) {
|
|
|
10985
11331
|
function isCurrentActivation(path, activations) {
|
|
10986
11332
|
if (activations.owned.has(path)) return true;
|
|
10987
11333
|
try {
|
|
10988
|
-
if (activations.owned.has(
|
|
11334
|
+
if (activations.owned.has(join12(realpathSync4(dirname8(path)), basename5(path)))) return true;
|
|
10989
11335
|
return activations.targets.has(realpathSync4(path));
|
|
10990
11336
|
} catch {
|
|
10991
11337
|
return false;
|
|
@@ -10998,26 +11344,26 @@ async function skillActivations(ctx) {
|
|
|
10998
11344
|
function evictLegacyBmadPackState(ctx, changedFiles, activations) {
|
|
10999
11345
|
const details = [];
|
|
11000
11346
|
const skillDirs = [
|
|
11001
|
-
|
|
11002
|
-
...SUPPORTED_CLI_ROOTS.map((root) =>
|
|
11347
|
+
join12(ctx.repoRoot, ".agents", "skills"),
|
|
11348
|
+
...SUPPORTED_CLI_ROOTS.map((root) => join12(ctx.repoRoot, root, "skills"))
|
|
11003
11349
|
];
|
|
11004
11350
|
for (const dir of skillDirs) {
|
|
11005
11351
|
const dirStat = lstatIfPresent(dir);
|
|
11006
11352
|
if (!dirStat || dirStat.isSymbolicLink() || !dirStat.isDirectory()) continue;
|
|
11007
11353
|
let names;
|
|
11008
11354
|
try {
|
|
11009
|
-
names =
|
|
11355
|
+
names = readdirSync8(dir);
|
|
11010
11356
|
} catch {
|
|
11011
11357
|
continue;
|
|
11012
11358
|
}
|
|
11013
11359
|
for (const name of names) {
|
|
11014
11360
|
if (!name.startsWith(BMAD_SKILL_NAME_PREFIX)) continue;
|
|
11015
|
-
const path =
|
|
11361
|
+
const path = join12(dir, name);
|
|
11016
11362
|
if (!lstatIfPresent(path)?.isSymbolicLink()) continue;
|
|
11017
11363
|
if (isCurrentActivation(path, activations)) continue;
|
|
11018
11364
|
changedFiles.push(path);
|
|
11019
11365
|
details.push(`removed retired BMAD pack symlink ${relative9(ctx.repoRoot, path)}`);
|
|
11020
|
-
if (!ctx.dryRun)
|
|
11366
|
+
if (!ctx.dryRun) unlinkSync6(path);
|
|
11021
11367
|
}
|
|
11022
11368
|
}
|
|
11023
11369
|
return details;
|
|
@@ -11030,10 +11376,11 @@ function bmadInstallerInvocation(version = BMAD_INSTALLER_VERSION) {
|
|
|
11030
11376
|
prefixArgs: ["-y", `${BMAD_NPM_PACKAGE}@${version}`]
|
|
11031
11377
|
};
|
|
11032
11378
|
}
|
|
11033
|
-
function resolveBmadInstallerPackage(version = BMAD_INSTALLER_VERSION) {
|
|
11379
|
+
function resolveBmadInstallerPackage(version = BMAD_INSTALLER_VERSION, options = {}) {
|
|
11380
|
+
const timeoutMs = options.timeoutMs ?? 18e4;
|
|
11034
11381
|
const packageAt = (dir) => {
|
|
11035
11382
|
try {
|
|
11036
|
-
const parsed = JSON.parse(
|
|
11383
|
+
const parsed = JSON.parse(readFileSync11(join12(dir, "package.json"), "utf8"));
|
|
11037
11384
|
return parsed.name === BMAD_NPM_PACKAGE ? typeof parsed.version === "string" ? parsed.version : "unknown" : void 0;
|
|
11038
11385
|
} catch {
|
|
11039
11386
|
return void 0;
|
|
@@ -11065,12 +11412,13 @@ function resolveBmadInstallerPackage(version = BMAD_INSTALLER_VERSION) {
|
|
|
11065
11412
|
`node -e "process.stdout.write(require('fs').realpathSync(process.argv[1]))" "$(command -v ${BMAD_NPM_PACKAGE})"`
|
|
11066
11413
|
], {
|
|
11067
11414
|
encoding: "utf8",
|
|
11068
|
-
timeout:
|
|
11415
|
+
timeout: timeoutMs,
|
|
11069
11416
|
env: { ...process.env, npm_config_update_notifier: "false", npm_config_fund: "false" }
|
|
11070
11417
|
});
|
|
11071
11418
|
const entry = probe.stdout?.trim().split("\n").pop()?.trim();
|
|
11072
11419
|
if (probe.status !== 0 || !entry) {
|
|
11073
|
-
const
|
|
11420
|
+
const timedOut = probe.error?.code === "ETIMEDOUT";
|
|
11421
|
+
const detail = timedOut ? `timed out after ${Math.round(timeoutMs / 1e3)}s` : String(probe.stderr || probe.error?.message || `exit ${probe.status}`).split("\n").filter((line) => line.trim() && !line.startsWith("npm notice")).slice(-3).join(" ");
|
|
11074
11422
|
return { reason: `could not resolve ${BMAD_NPM_PACKAGE}@${version}: ${detail || "no installer path reported"}` };
|
|
11075
11423
|
}
|
|
11076
11424
|
const root = dirname8(dirname8(dirname8(entry)));
|
|
@@ -11078,6 +11426,34 @@ function resolveBmadInstallerPackage(version = BMAD_INSTALLER_VERSION) {
|
|
|
11078
11426
|
if (!found) return { reason: `${entry} is not inside a ${BMAD_NPM_PACKAGE} package` };
|
|
11079
11427
|
return { root, entry, version: found };
|
|
11080
11428
|
}
|
|
11429
|
+
var BMAD_SETUP_SCRIPTS_PROBE_TIMEOUT_MS = 1e4;
|
|
11430
|
+
var BMAD_SETUP_SCRIPTS_REMEDY = "re-run the BMAD installer (npx bmad-method install) or upgrade via `pj migrate bmad.version`";
|
|
11431
|
+
function bmadSetupScriptsState(repoRoot) {
|
|
11432
|
+
const inspection = inspectBmadSetupScripts(repoRoot, BMAD_INSTALLER_VERSION, () => readInstalledBmadVersion(repoRoot));
|
|
11433
|
+
const decline = (why, missing, remedy2) => ({
|
|
11434
|
+
kind: "decline",
|
|
11435
|
+
summary: `Setup script recovery unavailable: ${why}${missing.length ? ` (missing from _bmad/scripts: ${missing.join(", ")})` : ""}; ${remedy2}`
|
|
11436
|
+
});
|
|
11437
|
+
switch (inspection.state) {
|
|
11438
|
+
case "present":
|
|
11439
|
+
return { kind: "present" };
|
|
11440
|
+
case "unsafe":
|
|
11441
|
+
return { kind: "refuse", error: `BMAD script recovery refused: ${inspection.error}` };
|
|
11442
|
+
case "decline":
|
|
11443
|
+
return decline(inspection.why, inspection.missing, BMAD_SETUP_SCRIPTS_REMEDY);
|
|
11444
|
+
case "missing":
|
|
11445
|
+
break;
|
|
11446
|
+
}
|
|
11447
|
+
const override = Boolean(process.env.PJ_BMAD_INSTALLER?.trim());
|
|
11448
|
+
const remedy = override ? "unset PJ_BMAD_INSTALLER to enable recovery from the pinned package" : `retry when npm is reachable, or ${BMAD_SETUP_SCRIPTS_REMEDY}`;
|
|
11449
|
+
const pkg = resolveBmadInstallerPackage(BMAD_INSTALLER_VERSION, { timeoutMs: BMAD_SETUP_SCRIPTS_PROBE_TIMEOUT_MS });
|
|
11450
|
+
if (!pkg.root) return decline(pkg.reason ?? "installer is not a bmad-method package", inspection.missing, remedy);
|
|
11451
|
+
const plan = planBmadSetupScripts(repoRoot, pkg, BMAD_INSTALLER_VERSION);
|
|
11452
|
+
if (plan.error) {
|
|
11453
|
+
return plan.declinable ? decline(plan.error.replace(/^BMAD script recovery refused: /, ""), inspection.missing, plan.declinable === "attest" ? BMAD_SETUP_SCRIPTS_REMEDY : remedy) : { kind: "refuse", error: plan.error };
|
|
11454
|
+
}
|
|
11455
|
+
return plan.files.length ? { kind: "missing", pkg, files: plan.files } : { kind: "present" };
|
|
11456
|
+
}
|
|
11081
11457
|
function bmadInstallerArgs(repoRoot, modules = selectedBmadModules(repoRoot), extraArgs = []) {
|
|
11082
11458
|
const installerModules = modules.length ? modules.join(",") : "core";
|
|
11083
11459
|
return [
|
|
@@ -11096,7 +11472,7 @@ function bmadInstallerArgs(repoRoot, modules = selectedBmadModules(repoRoot), ex
|
|
|
11096
11472
|
}
|
|
11097
11473
|
function bmadUpgradeChannelArgs(repoRoot, version) {
|
|
11098
11474
|
if (!/^\d+\.\d+\.\d+$/.test(version)) return [];
|
|
11099
|
-
const raw = safeReadText2(
|
|
11475
|
+
const raw = safeReadText2(join12(repoRoot, "_bmad", "_config", "manifest.yaml"));
|
|
11100
11476
|
if (!raw) return [];
|
|
11101
11477
|
let modules;
|
|
11102
11478
|
try {
|
|
@@ -11166,7 +11542,7 @@ function bmadChannelTarget(distTags) {
|
|
|
11166
11542
|
return next ? { version: next, channel: BMAD_TARGET_CHANNEL } : void 0;
|
|
11167
11543
|
}
|
|
11168
11544
|
function readInstalledBmadVersion(repoRoot) {
|
|
11169
|
-
const raw = safeReadText2(
|
|
11545
|
+
const raw = safeReadText2(join12(repoRoot, "_bmad", "_config", "manifest.yaml"));
|
|
11170
11546
|
if (!raw) return void 0;
|
|
11171
11547
|
try {
|
|
11172
11548
|
const parsed = YAML4.parse(raw);
|
|
@@ -11177,8 +11553,8 @@ function readInstalledBmadVersion(repoRoot) {
|
|
|
11177
11553
|
}
|
|
11178
11554
|
}
|
|
11179
11555
|
function bmadCachePath(homeDir) {
|
|
11180
|
-
const cacheRoot = process.env.XDG_CACHE_HOME?.trim() ||
|
|
11181
|
-
return
|
|
11556
|
+
const cacheRoot = process.env.XDG_CACHE_HOME?.trim() || join12(homeDir, ".cache");
|
|
11557
|
+
return join12(cacheRoot, "pjangler", "bmad-dist-tags.json");
|
|
11182
11558
|
}
|
|
11183
11559
|
function readBmadDistTagsCache(homeDir) {
|
|
11184
11560
|
const raw = safeReadText2(bmadCachePath(homeDir));
|
|
@@ -11220,8 +11596,8 @@ function resolveBmadDistTags(homeDir) {
|
|
|
11220
11596
|
if (fetched) {
|
|
11221
11597
|
try {
|
|
11222
11598
|
const path = bmadCachePath(homeDir);
|
|
11223
|
-
|
|
11224
|
-
|
|
11599
|
+
mkdirSync7(dirname8(path), { recursive: true });
|
|
11600
|
+
writeFileSync9(path, JSON.stringify({ fetchedAt: Date.now(), distTags: fetched }, null, 2));
|
|
11225
11601
|
} catch {
|
|
11226
11602
|
}
|
|
11227
11603
|
return { distTags: fetched, stale: false };
|
|
@@ -11345,8 +11721,8 @@ var UNSUPPORTED_BMAD_ROOTS = {
|
|
|
11345
11721
|
".zencoder": "zencoder"
|
|
11346
11722
|
};
|
|
11347
11723
|
function unsupportedRootAttestation(repoRoot, rootName) {
|
|
11348
|
-
const root =
|
|
11349
|
-
const stat =
|
|
11724
|
+
const root = join12(repoRoot, rootName);
|
|
11725
|
+
const stat = lstatSync10(root);
|
|
11350
11726
|
if (!stat.isDirectory() || stat.isSymbolicLink()) return { safe: false, reason: `${rootName} is not a regular generated directory` };
|
|
11351
11727
|
const installerTool = UNSUPPORTED_BMAD_ROOTS[rootName];
|
|
11352
11728
|
if (!installedBmadTools(repoRoot).has(installerTool)) {
|
|
@@ -11365,7 +11741,7 @@ var OPTIONAL_TEMPLATE_SCRIPTS = [
|
|
|
11365
11741
|
function optionalTemplateScriptIssues(ctx) {
|
|
11366
11742
|
const issues = [];
|
|
11367
11743
|
for (const rel of OPTIONAL_TEMPLATE_SCRIPTS) {
|
|
11368
|
-
const path =
|
|
11744
|
+
const path = join12(ctx.repoRoot, rel);
|
|
11369
11745
|
if (!existsSync8(path)) continue;
|
|
11370
11746
|
const expected = templateCommonProjectText(ctx, rel);
|
|
11371
11747
|
if (expected === void 0) continue;
|
|
@@ -11376,7 +11752,7 @@ function optionalTemplateScriptIssues(ctx) {
|
|
|
11376
11752
|
function refreshOptionalTemplateScripts(ctx) {
|
|
11377
11753
|
const changed = [];
|
|
11378
11754
|
for (const rel of OPTIONAL_TEMPLATE_SCRIPTS) {
|
|
11379
|
-
const path =
|
|
11755
|
+
const path = join12(ctx.repoRoot, rel);
|
|
11380
11756
|
if (!existsSync8(path)) continue;
|
|
11381
11757
|
const expected = templateCommonProjectText(ctx, rel);
|
|
11382
11758
|
if (expected === void 0 || safeReadText2(path) === expected) continue;
|
|
@@ -11394,13 +11770,13 @@ function createMiseChecks() {
|
|
|
11394
11770
|
id: "mise.config-root",
|
|
11395
11771
|
title: "mise config_root + AGENTS link hooks",
|
|
11396
11772
|
audit: (ctx) => {
|
|
11397
|
-
const misePath =
|
|
11773
|
+
const misePath = join12(ctx.repoRoot, "mise.toml");
|
|
11398
11774
|
if (!existsSync8(misePath)) {
|
|
11399
11775
|
return { id: "mise.config-root", title: "mise config_root + AGENTS link hooks", status: "fail", summary: "mise.toml missing", details: [], fixable: true };
|
|
11400
11776
|
}
|
|
11401
11777
|
const text = readText(misePath);
|
|
11402
11778
|
const details = [];
|
|
11403
|
-
const linkAgentfilesPath =
|
|
11779
|
+
const linkAgentfilesPath = join12(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
|
|
11404
11780
|
if (!existsSync8(linkAgentfilesPath)) details.push(".mise/scripts/link-agentfiles.sh missing");
|
|
11405
11781
|
const toml = [];
|
|
11406
11782
|
const manual = [];
|
|
@@ -11448,7 +11824,7 @@ function createMiseChecks() {
|
|
|
11448
11824
|
};
|
|
11449
11825
|
},
|
|
11450
11826
|
migrate: (ctx, finding2) => {
|
|
11451
|
-
const path =
|
|
11827
|
+
const path = join12(ctx.repoRoot, "mise.toml");
|
|
11452
11828
|
const changedFiles = [];
|
|
11453
11829
|
const details = [];
|
|
11454
11830
|
if (!existsSync8(path)) {
|
|
@@ -11467,7 +11843,7 @@ function createMiseChecks() {
|
|
|
11467
11843
|
if (!ctx.dryRun) writeText(path, plan.text);
|
|
11468
11844
|
text = plan.text;
|
|
11469
11845
|
}
|
|
11470
|
-
const linkAgentfilesPath =
|
|
11846
|
+
const linkAgentfilesPath = join12(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
|
|
11471
11847
|
const expectedScript = templateLinkAgentfilesScript(ctx);
|
|
11472
11848
|
if (expectedScript === void 0) {
|
|
11473
11849
|
return { id: finding2.id, title: finding2.title, status: "blocked", summary: "pjangler install is missing .mise/scripts/link-agentfiles.sh \u2014 update @delorenj/pjangler (broken package)", changedFiles, details: [] };
|
|
@@ -11505,9 +11881,9 @@ function createMiseChecks() {
|
|
|
11505
11881
|
title: "managed mise versioning block",
|
|
11506
11882
|
audit: (ctx) => {
|
|
11507
11883
|
const details = [];
|
|
11508
|
-
const misePath =
|
|
11509
|
-
const versioningPath =
|
|
11510
|
-
const manifestPath =
|
|
11884
|
+
const misePath = join12(ctx.repoRoot, "mise.toml");
|
|
11885
|
+
const versioningPath = join12(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
|
|
11886
|
+
const manifestPath = join12(ctx.repoRoot, ".mise", "version-files.conf");
|
|
11511
11887
|
const text = safeReadText2(misePath);
|
|
11512
11888
|
if (!text?.includes("# >>> mise-versioning >>>")) details.push("mise versioning managed block missing");
|
|
11513
11889
|
if (!existsSync8(versioningPath)) details.push(".mise/scripts/versioning.sh missing");
|
|
@@ -11533,7 +11909,7 @@ function createMiseChecks() {
|
|
|
11533
11909
|
migrate: (ctx, finding2) => {
|
|
11534
11910
|
const changedFiles = [];
|
|
11535
11911
|
const details = [];
|
|
11536
|
-
const misePath =
|
|
11912
|
+
const misePath = join12(ctx.repoRoot, "mise.toml");
|
|
11537
11913
|
if (!existsSync8(misePath)) {
|
|
11538
11914
|
if (ensureMiseTomlFromTemplate(ctx, changedFiles) === null) {
|
|
11539
11915
|
return { id: finding2.id, title: finding2.title, status: "blocked", summary: "mise.toml missing and no generated-project mise template available to initialize from", changedFiles, details: [] };
|
|
@@ -11551,7 +11927,7 @@ function createMiseChecks() {
|
|
|
11551
11927
|
if (!changedFiles.includes(misePath)) changedFiles.push(misePath);
|
|
11552
11928
|
if (!ctx.dryRun) writeText(misePath, versioningPlan.text);
|
|
11553
11929
|
}
|
|
11554
|
-
const versioningPath =
|
|
11930
|
+
const versioningPath = join12(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
|
|
11555
11931
|
const expectedScript = templateVersioningScript(ctx);
|
|
11556
11932
|
if (expectedScript === void 0) {
|
|
11557
11933
|
return { id: finding2.id, title: finding2.title, status: "blocked", summary: "pjangler install is missing .mise/scripts/versioning.sh \u2014 update @delorenj/pjangler (broken package)", changedFiles, details: [] };
|
|
@@ -11563,7 +11939,7 @@ function createMiseChecks() {
|
|
|
11563
11939
|
chmodSync3(versioningPath, 493);
|
|
11564
11940
|
}
|
|
11565
11941
|
}
|
|
11566
|
-
const manifestPath =
|
|
11942
|
+
const manifestPath = join12(ctx.repoRoot, ".mise", "version-files.conf");
|
|
11567
11943
|
const expectedManifest = templateVersionFilesConf(ctx, ctx.repoRoot);
|
|
11568
11944
|
if (safeReadText2(manifestPath) !== expectedManifest) {
|
|
11569
11945
|
changedFiles.push(manifestPath);
|
|
@@ -11625,7 +12001,7 @@ var RETIRED_SKILL_SCRIPTS = [
|
|
|
11625
12001
|
".mise/scripts/unlink-project-skills-from-clis.sh"
|
|
11626
12002
|
];
|
|
11627
12003
|
function retiredSkillsScripts(ctx) {
|
|
11628
|
-
return RETIRED_SKILL_SCRIPTS.map((path) =>
|
|
12004
|
+
return RETIRED_SKILL_SCRIPTS.map((path) => join12(ctx.repoRoot, path)).filter((path) => Boolean(lstatIfPresent(path)));
|
|
11629
12005
|
}
|
|
11630
12006
|
function skillsWiringFindings(text) {
|
|
11631
12007
|
if (text === null) return { fixable: [], manual: [] };
|
|
@@ -11676,7 +12052,7 @@ function createAgentHooksChecks() {
|
|
|
11676
12052
|
title: "Skillex project skills",
|
|
11677
12053
|
audit: async (ctx) => {
|
|
11678
12054
|
const finding2 = await auditProjectSkills(ctx);
|
|
11679
|
-
const mise = safeReadText2(
|
|
12055
|
+
const mise = safeReadText2(join12(ctx.repoRoot, "mise.toml"));
|
|
11680
12056
|
const registryField = await legacyRegistryField(ctx);
|
|
11681
12057
|
const registryDrop = registryField ? await dropLegacyRegistryField({ ...ctx, dryRun: true }) : void 0;
|
|
11682
12058
|
const wiring = [...skillsWiringIssues(mise), ...registryField && !registryDrop?.blocked ? [`Drop the legacy "registry" field (${JSON.stringify(registryField.value)}) from .agents/skills.json; it pins resolution to the never-fetched ~/.agents/.cache registry clone`] : []];
|
|
@@ -11697,7 +12073,7 @@ function createAgentHooksChecks() {
|
|
|
11697
12073
|
const changedFiles = [];
|
|
11698
12074
|
const details = [];
|
|
11699
12075
|
const result2 = (status, summary) => ({ id: finding2.id, title: finding2.title, status, summary, changedFiles: [...new Set(changedFiles)], details });
|
|
11700
|
-
let currentMise = safeReadText2(
|
|
12076
|
+
let currentMise = safeReadText2(join12(ctx.repoRoot, "mise.toml"));
|
|
11701
12077
|
if (currentMise === null) {
|
|
11702
12078
|
const template = templateCommonProjectText(ctx, "mise.toml.jinja");
|
|
11703
12079
|
if (template === void 0) return result2("blocked", "Generated-project mise template is unavailable");
|
|
@@ -11712,9 +12088,9 @@ function createAgentHooksChecks() {
|
|
|
11712
12088
|
const stat = lstatIfPresent(path);
|
|
11713
12089
|
if (!stat || stat.isDirectory()) continue;
|
|
11714
12090
|
changedFiles.push(path);
|
|
11715
|
-
if (!ctx.dryRun)
|
|
12091
|
+
if (!ctx.dryRun) unlinkSync6(path);
|
|
11716
12092
|
}
|
|
11717
|
-
const misePath =
|
|
12093
|
+
const misePath = join12(ctx.repoRoot, "mise.toml");
|
|
11718
12094
|
const nextMise = upsertLinkAgentfilesBlock(currentMise, ctx);
|
|
11719
12095
|
if (safeReadText2(misePath) !== nextMise) {
|
|
11720
12096
|
changedFiles.push(misePath);
|
|
@@ -11745,9 +12121,9 @@ function createAgentHooksChecks() {
|
|
|
11745
12121
|
id: "sot.agent-symlinks",
|
|
11746
12122
|
title: "AGENTS/CLAUDE/GEMINI symlink contract",
|
|
11747
12123
|
audit: (ctx) => {
|
|
11748
|
-
const agentsPath =
|
|
12124
|
+
const agentsPath = join12(ctx.repoRoot, "AGENTS.md");
|
|
11749
12125
|
if (!existsSync8(agentsPath)) {
|
|
11750
|
-
const fallbackSources = ["CLAUDE.md", "GEMINI.md", "README.md"].filter((file) => existsSync8(
|
|
12126
|
+
const fallbackSources = ["CLAUDE.md", "GEMINI.md", "README.md"].filter((file) => existsSync8(join12(ctx.repoRoot, file)));
|
|
11751
12127
|
if (fallbackSources.length === 0) {
|
|
11752
12128
|
return { id: "sot.agent-symlinks", title: "AGENTS/CLAUDE/GEMINI symlink contract", status: "skip", summary: "AGENTS.md missing; symlink contract not applicable", details: [], fixable: false };
|
|
11753
12129
|
}
|
|
@@ -11762,7 +12138,7 @@ function createAgentHooksChecks() {
|
|
|
11762
12138
|
}
|
|
11763
12139
|
const details = [];
|
|
11764
12140
|
for (const file of ["CLAUDE.md", "GEMINI.md"]) {
|
|
11765
|
-
const full =
|
|
12141
|
+
const full = join12(ctx.repoRoot, file);
|
|
11766
12142
|
const target = readSymlinkTarget(full);
|
|
11767
12143
|
if (target !== "AGENTS.md") details.push(`${file} should be a symlink to AGENTS.md`);
|
|
11768
12144
|
}
|
|
@@ -11786,7 +12162,7 @@ function createAgentHooksChecks() {
|
|
|
11786
12162
|
return { id: finding2.id, title: finding2.title, status: "blocked", summary: "AGENTS.md missing; cannot derive canonical agent file", changedFiles, details: [bootstrap.blocked] };
|
|
11787
12163
|
}
|
|
11788
12164
|
for (const file of ["CLAUDE.md", "GEMINI.md"]) {
|
|
11789
|
-
const full =
|
|
12165
|
+
const full = join12(ctx.repoRoot, file);
|
|
11790
12166
|
const result2 = ensureSymlink(full, "AGENTS.md", ctx.dryRun);
|
|
11791
12167
|
if (result2.blocked) blockedDetails.push(result2.blocked);
|
|
11792
12168
|
if (result2.changed) changedFiles.push(full);
|
|
@@ -11813,7 +12189,7 @@ function createProjectJsonChecks() {
|
|
|
11813
12189
|
const changedFiles = [];
|
|
11814
12190
|
const blockedDetails = [];
|
|
11815
12191
|
const droppedDetails = [];
|
|
11816
|
-
const path =
|
|
12192
|
+
const path = join12(ctx.repoRoot, ".project.json");
|
|
11817
12193
|
const existing = readProjectJson(ctx) ?? {};
|
|
11818
12194
|
const canonical = canonicalProjectJson(ctx);
|
|
11819
12195
|
const preservedDetails = [];
|
|
@@ -11845,7 +12221,7 @@ function createProjectJsonChecks() {
|
|
|
11845
12221
|
changedFiles.push(path);
|
|
11846
12222
|
if (!ctx.dryRun) writeText(path, expected);
|
|
11847
12223
|
}
|
|
11848
|
-
const planeJson =
|
|
12224
|
+
const planeJson = join12(ctx.repoRoot, ".plane.json");
|
|
11849
12225
|
if (existsSync8(planeJson)) {
|
|
11850
12226
|
const backup = `${planeJson}.migrated-backup`;
|
|
11851
12227
|
if (existsSync8(backup)) {
|
|
@@ -11877,10 +12253,10 @@ function createMiseOpInjectChecks() {
|
|
|
11877
12253
|
const details = [];
|
|
11878
12254
|
let envOpNeedsHands = false;
|
|
11879
12255
|
const optedOut = envMaterializationOptedOut(ctx);
|
|
11880
|
-
const envOpPath =
|
|
12256
|
+
const envOpPath = join12(ctx.repoRoot, ".env.op");
|
|
11881
12257
|
const envOpExists = existsSync8(envOpPath);
|
|
11882
12258
|
const envOp = envOpExists ? readText(envOpPath) : void 0;
|
|
11883
|
-
const gitignore = safeReadText2(
|
|
12259
|
+
const gitignore = safeReadText2(join12(ctx.repoRoot, ".gitignore"));
|
|
11884
12260
|
if (!envOpExists) {
|
|
11885
12261
|
details.push(".env.op missing");
|
|
11886
12262
|
} else if (!envOp?.trim()) {
|
|
@@ -11900,7 +12276,7 @@ function createMiseOpInjectChecks() {
|
|
|
11900
12276
|
if (!gitignore?.includes(".env\n") && !gitignore?.includes(".env\r\n")) details.push(".gitignore should ignore .env");
|
|
11901
12277
|
if (!gitignore?.includes(".env.*")) details.push(".gitignore should ignore .env.*");
|
|
11902
12278
|
if (!gitignore?.includes("!.env.op")) details.push(".gitignore should unignore .env.op");
|
|
11903
|
-
const misePath =
|
|
12279
|
+
const misePath = join12(ctx.repoRoot, "mise.toml");
|
|
11904
12280
|
const miseText = safeReadText2(misePath);
|
|
11905
12281
|
if (!miseText) {
|
|
11906
12282
|
details.push("mise.toml missing for .env materialization hook");
|
|
@@ -11923,7 +12299,7 @@ function createMiseOpInjectChecks() {
|
|
|
11923
12299
|
else if (plan.text !== miseText && !mise.length) mise.push(describeRewrite(miseText, plan.text));
|
|
11924
12300
|
details.push(...mise);
|
|
11925
12301
|
}
|
|
11926
|
-
const materializePath =
|
|
12302
|
+
const materializePath = join12(ctx.repoRoot, MATERIALIZE_ENV_SCRIPT_REL);
|
|
11927
12303
|
const expectedMaterializer = templateMaterializeEnvScript(ctx);
|
|
11928
12304
|
if (optedOut) {
|
|
11929
12305
|
if (existsSync8(materializePath)) details.push(`${MATERIALIZE_ENV_SCRIPT_REL} is present, but .project.json declares secrets.materialize_env: false`);
|
|
@@ -11931,7 +12307,7 @@ function createMiseOpInjectChecks() {
|
|
|
11931
12307
|
details.push("pjangler package is missing the managed materialize-env.sh source");
|
|
11932
12308
|
} else if (safeReadText2(materializePath) !== expectedMaterializer) {
|
|
11933
12309
|
details.push(`${MATERIALIZE_ENV_SCRIPT_REL} missing or drifted`);
|
|
11934
|
-
} else if ((
|
|
12310
|
+
} else if ((lstatSync10(materializePath).mode & 73) === 0) {
|
|
11935
12311
|
details.push(`${MATERIALIZE_ENV_SCRIPT_REL} is not executable`);
|
|
11936
12312
|
}
|
|
11937
12313
|
return {
|
|
@@ -11947,8 +12323,8 @@ function createMiseOpInjectChecks() {
|
|
|
11947
12323
|
const changedFiles = [];
|
|
11948
12324
|
const details = [];
|
|
11949
12325
|
let envOpNeedsHands = false;
|
|
11950
|
-
const envOpPath =
|
|
11951
|
-
const canonicalEnvOpPath =
|
|
12326
|
+
const envOpPath = join12(ctx.repoRoot, ".env.op");
|
|
12327
|
+
const canonicalEnvOpPath = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", ".env.op");
|
|
11952
12328
|
if (!existsSync8(canonicalEnvOpPath)) {
|
|
11953
12329
|
return { id: finding2.id, title: finding2.title, status: "blocked", summary: "pjangler package is missing the neutral .env.op template", changedFiles: [], details: [] };
|
|
11954
12330
|
}
|
|
@@ -11977,7 +12353,7 @@ function createMiseOpInjectChecks() {
|
|
|
11977
12353
|
}
|
|
11978
12354
|
}
|
|
11979
12355
|
}
|
|
11980
|
-
const gitignorePath =
|
|
12356
|
+
const gitignorePath = join12(ctx.repoRoot, ".gitignore");
|
|
11981
12357
|
const gitignore = safeReadText2(gitignorePath) ?? "";
|
|
11982
12358
|
const requiredBlock = `# Secrets \u2014 .env is materialized from .env.op by \`op inject\` on mise enter,
|
|
11983
12359
|
# staged through a mktemp file and moved into place only on success.
|
|
@@ -11990,7 +12366,7 @@ function createMiseOpInjectChecks() {
|
|
|
11990
12366
|
changedFiles.push(gitignorePath);
|
|
11991
12367
|
if (!ctx.dryRun) writeText(gitignorePath, `${gitignore.replace(/\s*$/, "")}${gitignore.trim() ? "\n\n" : ""}${requiredBlock}`);
|
|
11992
12368
|
}
|
|
11993
|
-
const misePath =
|
|
12369
|
+
const misePath = join12(ctx.repoRoot, "mise.toml");
|
|
11994
12370
|
let currentMise = safeReadText2(misePath);
|
|
11995
12371
|
if (currentMise === null) {
|
|
11996
12372
|
const initialized = ensureMiseTomlFromTemplate(ctx, changedFiles);
|
|
@@ -12007,7 +12383,7 @@ function createMiseOpInjectChecks() {
|
|
|
12007
12383
|
if (!changedFiles.includes(misePath)) changedFiles.push(misePath);
|
|
12008
12384
|
if (!ctx.dryRun) writeText(misePath, opPlan.text);
|
|
12009
12385
|
}
|
|
12010
|
-
const materializePath =
|
|
12386
|
+
const materializePath = join12(ctx.repoRoot, MATERIALIZE_ENV_SCRIPT_REL);
|
|
12011
12387
|
const expectedMaterializer = templateMaterializeEnvScript(ctx);
|
|
12012
12388
|
if (optedOut) {
|
|
12013
12389
|
if (existsSync8(materializePath)) {
|
|
@@ -12016,7 +12392,7 @@ function createMiseOpInjectChecks() {
|
|
|
12016
12392
|
}
|
|
12017
12393
|
} else if (!expectedMaterializer) {
|
|
12018
12394
|
return { id: finding2.id, title: finding2.title, status: "blocked", summary: "pjangler package is missing materialize-env.sh", changedFiles: [], details: [] };
|
|
12019
|
-
} else if (safeReadText2(materializePath) !== expectedMaterializer || existsSync8(materializePath) && (
|
|
12395
|
+
} else if (safeReadText2(materializePath) !== expectedMaterializer || existsSync8(materializePath) && (lstatSync10(materializePath).mode & 73) === 0) {
|
|
12020
12396
|
changedFiles.push(materializePath);
|
|
12021
12397
|
if (!ctx.dryRun) {
|
|
12022
12398
|
writeText(materializePath, expectedMaterializer);
|
|
@@ -12078,7 +12454,7 @@ function createProjectProvenanceChecks() {
|
|
|
12078
12454
|
title: ".copier-answers.yml provenance + drift report",
|
|
12079
12455
|
audit: (ctx) => {
|
|
12080
12456
|
const details = [];
|
|
12081
|
-
const path =
|
|
12457
|
+
const path = join12(ctx.repoRoot, ".copier-answers.yml");
|
|
12082
12458
|
const text = safeReadText2(path);
|
|
12083
12459
|
const project = readProjectJson(ctx);
|
|
12084
12460
|
if (!text) {
|
|
@@ -12106,11 +12482,11 @@ function createProjectProvenanceChecks() {
|
|
|
12106
12482
|
migrate: (ctx, finding2) => {
|
|
12107
12483
|
const changedFiles = [];
|
|
12108
12484
|
const project = canonicalProjectJson(ctx);
|
|
12109
|
-
const path =
|
|
12485
|
+
const path = join12(ctx.repoRoot, ".copier-answers.yml");
|
|
12110
12486
|
const current = safeReadText2(path);
|
|
12111
12487
|
const existing = current ? YAML4.parseDocument(current) : void 0;
|
|
12112
12488
|
const document = existing && !existing.errors.length && YAML4.isMap(existing.contents) ? existing : new YAML4.Document({});
|
|
12113
|
-
document.set("_src_path",
|
|
12489
|
+
document.set("_src_path", join12(ctx.pjanglerRoot, "templates", "commonproject"));
|
|
12114
12490
|
document.set("project_description", String(project.project_description));
|
|
12115
12491
|
document.set("project_name", String(project.project_name));
|
|
12116
12492
|
document.set("ticket_provider", String(project.ticket_provider?.type ?? "plane"));
|
|
@@ -12135,9 +12511,9 @@ ${body}`;
|
|
|
12135
12511
|
}
|
|
12136
12512
|
function supportedCliProjectionIssues(repoRoot, plan) {
|
|
12137
12513
|
const issues = [];
|
|
12138
|
-
const managedSkills =
|
|
12514
|
+
const managedSkills = join12(repoRoot, ".agents", "skills");
|
|
12139
12515
|
for (const rootName of SUPPORTED_CLI_ROOTS) {
|
|
12140
|
-
const root =
|
|
12516
|
+
const root = join12(repoRoot, rootName);
|
|
12141
12517
|
const rootStat = lstatIfPresent(root);
|
|
12142
12518
|
if (!rootStat) {
|
|
12143
12519
|
issues.push(`${rootName} missing`);
|
|
@@ -12147,7 +12523,7 @@ function supportedCliProjectionIssues(repoRoot, plan) {
|
|
|
12147
12523
|
issues.push(`${rootName} must be a real configuration directory`);
|
|
12148
12524
|
continue;
|
|
12149
12525
|
}
|
|
12150
|
-
const skills =
|
|
12526
|
+
const skills = join12(root, "skills");
|
|
12151
12527
|
const skillsStat = lstatIfPresent(skills);
|
|
12152
12528
|
if (!skillsStat) {
|
|
12153
12529
|
issues.push(`${rootName}/skills missing`);
|
|
@@ -12184,9 +12560,9 @@ function supportedCliProjectionIssues(repoRoot, plan) {
|
|
|
12184
12560
|
issues.push(`${rootName}/skills is not a directory`);
|
|
12185
12561
|
continue;
|
|
12186
12562
|
}
|
|
12187
|
-
const hasGeneratedSkill = existsSync8(projectedSkills) &&
|
|
12188
|
-
const skillFile =
|
|
12189
|
-
return existsSync8(skillFile) &&
|
|
12563
|
+
const hasGeneratedSkill = existsSync8(projectedSkills) && readdirSync8(projectedSkills).some((name) => {
|
|
12564
|
+
const skillFile = join12(projectedSkills, name, "SKILL.md");
|
|
12565
|
+
return existsSync8(skillFile) && lstatSync10(skillFile).isFile();
|
|
12190
12566
|
});
|
|
12191
12567
|
if (!hasGeneratedSkill) issues.push(`${rootName}/skills contains no BMAD skill configuration`);
|
|
12192
12568
|
}
|
|
@@ -12212,7 +12588,7 @@ var LEGACY_CLI_GITIGNORE_LINES = /* @__PURE__ */ new Set([
|
|
|
12212
12588
|
"# some CLIs get a real directory here and some get a symlink."
|
|
12213
12589
|
]);
|
|
12214
12590
|
function supportedCliGitignoreIssues(repoRoot) {
|
|
12215
|
-
const lines = (safeReadText2(
|
|
12591
|
+
const lines = (safeReadText2(join12(repoRoot, ".gitignore")) ?? "").split(/\r?\n/);
|
|
12216
12592
|
const legacy = lines.filter((line) => LEGACY_CLI_GITIGNORE_LINES.has(line.trim()));
|
|
12217
12593
|
return [
|
|
12218
12594
|
...legacy.length ? [`.gitignore contains ${legacy.length} legacy client-root override line(s); only .agents is canonical`] : [],
|
|
@@ -12220,7 +12596,7 @@ function supportedCliGitignoreIssues(repoRoot) {
|
|
|
12220
12596
|
];
|
|
12221
12597
|
}
|
|
12222
12598
|
function ensureSupportedCliGitignore(ctx) {
|
|
12223
|
-
const path =
|
|
12599
|
+
const path = join12(ctx.repoRoot, ".gitignore");
|
|
12224
12600
|
const current = safeReadText2(path) ?? "";
|
|
12225
12601
|
const kept = current.split(/\r?\n/).filter((line) => {
|
|
12226
12602
|
const trimmed2 = line.trim();
|
|
@@ -12262,6 +12638,24 @@ function withInstallerSkillHandoff(check) {
|
|
|
12262
12638
|
}
|
|
12263
12639
|
};
|
|
12264
12640
|
}
|
|
12641
|
+
function withScaffoldPostcondition(check) {
|
|
12642
|
+
if (check.id !== "bmad.scaffold") return check;
|
|
12643
|
+
return {
|
|
12644
|
+
...check,
|
|
12645
|
+
migrate: async (ctx, finding2) => {
|
|
12646
|
+
const result2 = await check.migrate(ctx, finding2);
|
|
12647
|
+
if (ctx.dryRun || result2.status !== "applied" && result2.status !== "noop") return result2;
|
|
12648
|
+
const after = await check.audit(ctx);
|
|
12649
|
+
if (after.status === "pass" || after.status === "skip") return result2;
|
|
12650
|
+
return {
|
|
12651
|
+
...result2,
|
|
12652
|
+
status: "partial",
|
|
12653
|
+
summary: after.summary,
|
|
12654
|
+
details: [...result2.details, `still failing after the install: ${after.summary}`, ...after.details]
|
|
12655
|
+
};
|
|
12656
|
+
}
|
|
12657
|
+
};
|
|
12658
|
+
}
|
|
12265
12659
|
function createBmadChecks() {
|
|
12266
12660
|
return [
|
|
12267
12661
|
{
|
|
@@ -12279,15 +12673,8 @@ function createBmadChecks() {
|
|
|
12279
12673
|
fixable: false
|
|
12280
12674
|
};
|
|
12281
12675
|
}
|
|
12282
|
-
const targetRoot = join11(ctx.repoRoot, "_bmad");
|
|
12283
12676
|
const selectedModules = manifestSelection.status === "valid" ? manifestSelection.modules : configuredBmadModuleSelection(ctx.repoRoot)?.modules ?? [...DEFAULT_BMAD_MODULES];
|
|
12284
|
-
const
|
|
12285
|
-
const missing = sentinels.filter((file) => !existsSync8(join11(targetRoot, file)));
|
|
12286
|
-
const projectNameIssues = bmadProjectNameIssues(ctx.repoRoot);
|
|
12287
|
-
const details = [
|
|
12288
|
-
...missing.map((file) => `_bmad/${file}`),
|
|
12289
|
-
...projectNameIssues.details
|
|
12290
|
-
];
|
|
12677
|
+
const { details } = bmadScaffoldIssues(ctx.repoRoot, selectedModules);
|
|
12291
12678
|
return {
|
|
12292
12679
|
id: "bmad.scaffold",
|
|
12293
12680
|
title: "BMAD modules/docs scaffold",
|
|
@@ -12326,7 +12713,7 @@ function createBmadChecks() {
|
|
|
12326
12713
|
bmadCompanion: preservation
|
|
12327
12714
|
};
|
|
12328
12715
|
if (ctx.dryRun) {
|
|
12329
|
-
const
|
|
12716
|
+
const issues = bmadScaffoldIssues(ctx.repoRoot, selectedModules);
|
|
12330
12717
|
const companion = installedBmad ? await runG33PreUpdateCompanion(ctx.repoRoot) : await runG33Companion(ctx.repoRoot, true, "bmad-install");
|
|
12331
12718
|
if (installedBmad && ["unavailable", "conflict", "error"].includes(companion.status)) {
|
|
12332
12719
|
return {
|
|
@@ -12339,8 +12726,8 @@ function createBmadChecks() {
|
|
|
12339
12726
|
bmadCompanion: companion
|
|
12340
12727
|
};
|
|
12341
12728
|
}
|
|
12342
|
-
changedFiles.push(...
|
|
12343
|
-
changedFiles.push(...
|
|
12729
|
+
changedFiles.push(...issues.gaps.map((gap) => join12(ctx.repoRoot, "_bmad", gap.path)));
|
|
12730
|
+
changedFiles.push(...issues.nameIssues.paths);
|
|
12344
12731
|
const reservedPreview = installedBmad ? evictReservedModuleEntries(ctx.repoRoot, true) : void 0;
|
|
12345
12732
|
changedFiles.push(...reservedPreview?.changedFiles ?? []);
|
|
12346
12733
|
return {
|
|
@@ -12358,9 +12745,10 @@ function createBmadChecks() {
|
|
|
12358
12745
|
};
|
|
12359
12746
|
}
|
|
12360
12747
|
if (installedBmad && g33CompanionEnabled(ctx.repoRoot)) {
|
|
12748
|
+
const before2 = bmadScaffoldIssues(ctx.repoRoot, selectedModules);
|
|
12361
12749
|
const expectedChangedPaths2 = [
|
|
12362
|
-
...
|
|
12363
|
-
...
|
|
12750
|
+
...before2.gaps.map((gap) => join12(ctx.repoRoot, "_bmad", gap.path)),
|
|
12751
|
+
...before2.nameIssues.paths
|
|
12364
12752
|
];
|
|
12365
12753
|
const pkg = resolveBmadInstallerPackage(BMAD_INSTALLER_VERSION);
|
|
12366
12754
|
const scope = deriveBmadUpdateScope(ctx.repoRoot, { tools: BMAD_INSTALL_TOOLS, installer: pkg, companionSurfaces: G33_OWNED_SURFACES });
|
|
@@ -12401,16 +12789,17 @@ function createBmadChecks() {
|
|
|
12401
12789
|
bmadCompanion: update.companion
|
|
12402
12790
|
};
|
|
12403
12791
|
}
|
|
12792
|
+
const before = bmadScaffoldIssues(ctx.repoRoot, selectedModules);
|
|
12404
12793
|
const expectedChangedPaths = [
|
|
12405
|
-
...
|
|
12406
|
-
...
|
|
12794
|
+
...before.gaps.map((gap) => join12(ctx.repoRoot, "_bmad", gap.path)),
|
|
12795
|
+
...before.nameIssues.paths
|
|
12407
12796
|
];
|
|
12408
12797
|
const evicted = evictLegacyBmadPackState(ctx, changedFiles, await skillActivations(ctx));
|
|
12409
12798
|
const reserved = installedBmad ? evictReservedModuleEntries(ctx.repoRoot, false) : void 0;
|
|
12410
12799
|
if (reserved) evicted.unshift(...reserved.details);
|
|
12411
12800
|
const install = await runBmadInstall(ctx.repoRoot, selectedModules, BMAD_INSTALLER_VERSION, channelArgs);
|
|
12412
12801
|
if (!install.ok && reserved?.original !== void 0) {
|
|
12413
|
-
|
|
12802
|
+
writeFileSync9(reserved.changedFiles[0], reserved.original, "utf8");
|
|
12414
12803
|
evicted.push("restored _bmad/_config/manifest.yaml after the failed install");
|
|
12415
12804
|
}
|
|
12416
12805
|
if (!install.ok) {
|
|
@@ -12435,6 +12824,58 @@ function createBmadChecks() {
|
|
|
12435
12824
|
};
|
|
12436
12825
|
}
|
|
12437
12826
|
},
|
|
12827
|
+
{
|
|
12828
|
+
id: "bmad.setup-scripts",
|
|
12829
|
+
title: "BMAD ignored installer setup scripts",
|
|
12830
|
+
audit: (ctx) => {
|
|
12831
|
+
if (!isInstalledBmadProject(ctx.repoRoot)) return {
|
|
12832
|
+
id: "bmad.setup-scripts",
|
|
12833
|
+
title: "BMAD ignored installer setup scripts",
|
|
12834
|
+
status: "skip",
|
|
12835
|
+
summary: "No installed BMAD project",
|
|
12836
|
+
details: [],
|
|
12837
|
+
fixable: false
|
|
12838
|
+
};
|
|
12839
|
+
const state = bmadSetupScriptsState(ctx.repoRoot);
|
|
12840
|
+
const id = "bmad.setup-scripts", title = "BMAD ignored installer setup scripts";
|
|
12841
|
+
switch (state.kind) {
|
|
12842
|
+
case "present":
|
|
12843
|
+
return { id, title, status: "pass", summary: "BMAD installer scripts are present", details: [], fixable: false };
|
|
12844
|
+
case "decline":
|
|
12845
|
+
return { id, title, status: "skip", summary: state.summary, details: [], fixable: false };
|
|
12846
|
+
case "refuse":
|
|
12847
|
+
return { id, title, status: "fail", summary: state.error, details: [state.error], fixable: false };
|
|
12848
|
+
case "missing":
|
|
12849
|
+
return {
|
|
12850
|
+
id,
|
|
12851
|
+
title,
|
|
12852
|
+
status: "fail",
|
|
12853
|
+
summary: "Ignored BMAD installer scripts are missing",
|
|
12854
|
+
details: state.files.map((path) => relative9(ctx.repoRoot, path)),
|
|
12855
|
+
fixable: true
|
|
12856
|
+
};
|
|
12857
|
+
}
|
|
12858
|
+
},
|
|
12859
|
+
migrate: (ctx, finding2) => {
|
|
12860
|
+
const title = finding2.title, id = finding2.id;
|
|
12861
|
+
if (finding2.status === "skip") return { id, title, status: "skipped", summary: finding2.summary, details: [], changedFiles: [] };
|
|
12862
|
+
const state = bmadSetupScriptsState(ctx.repoRoot);
|
|
12863
|
+
if (state.kind === "decline") return { id, title, status: "skipped", summary: state.summary, details: [], changedFiles: [] };
|
|
12864
|
+
if (state.kind === "refuse") return { id, title, status: "blocked", summary: state.error, details: [state.error], changedFiles: [] };
|
|
12865
|
+
if (state.kind === "present") return { id, title, status: "noop", summary: "BMAD installer scripts already present", details: [], changedFiles: [] };
|
|
12866
|
+
const { pkg, files } = state;
|
|
12867
|
+
if (ctx.dryRun) return {
|
|
12868
|
+
id,
|
|
12869
|
+
title,
|
|
12870
|
+
status: "applied",
|
|
12871
|
+
summary: "Would restore ignored scripts from verified pinned bmad-method package",
|
|
12872
|
+
details: [`bmad-method@${BMAD_INSTALLER_VERSION} (${pkg.root})`],
|
|
12873
|
+
changedFiles: files
|
|
12874
|
+
};
|
|
12875
|
+
const applied = applyBmadSetupScripts(ctx.repoRoot, pkg, BMAD_INSTALLER_VERSION);
|
|
12876
|
+
return applied.error ? { id, title, status: "blocked", summary: applied.error, details: [applied.error], changedFiles: [] } : { id, title, status: "applied", summary: "Restored ignored BMAD installer scripts", details: [`bmad-method@${BMAD_INSTALLER_VERSION} (${pkg.root})`], changedFiles: applied.files };
|
|
12877
|
+
}
|
|
12878
|
+
},
|
|
12438
12879
|
{
|
|
12439
12880
|
id: "bmad.version",
|
|
12440
12881
|
title: "BMAD version currency",
|
|
@@ -12445,7 +12886,7 @@ function createBmadChecks() {
|
|
|
12445
12886
|
id: "bmad.version",
|
|
12446
12887
|
title: "BMAD version currency",
|
|
12447
12888
|
status: "skip",
|
|
12448
|
-
summary: existsSync8(
|
|
12889
|
+
summary: existsSync8(join12(ctx.repoRoot, "_bmad")) ? "BMAD installed but version manifest unreadable" : "No BMAD install present",
|
|
12449
12890
|
details: [],
|
|
12450
12891
|
fixable: false
|
|
12451
12892
|
};
|
|
@@ -12533,7 +12974,7 @@ function createBmadChecks() {
|
|
|
12533
12974
|
}
|
|
12534
12975
|
const installed = readInstalledBmadVersion(ctx.repoRoot);
|
|
12535
12976
|
const available = ctx.bmadVersionPin?.trim() ?? bmadChannelTarget(resolveBmadDistTags(ctx.homeDir)?.distTags)?.version;
|
|
12536
|
-
const manifestPath =
|
|
12977
|
+
const manifestPath = join12(ctx.repoRoot, "_bmad", "_config", "manifest.yaml");
|
|
12537
12978
|
const manifestSelection = manifestBmadModules(ctx.repoRoot);
|
|
12538
12979
|
if (manifestSelection.status === "invalid") {
|
|
12539
12980
|
return {
|
|
@@ -12631,7 +13072,7 @@ function createBmadChecks() {
|
|
|
12631
13072
|
const reserved = evictReservedModuleEntries(ctx.repoRoot, false);
|
|
12632
13073
|
const install = await runBmadInstall(ctx.repoRoot, selectedModules, available ?? BMAD_TARGET_CHANNEL, channelArgs);
|
|
12633
13074
|
if (!install.ok) {
|
|
12634
|
-
if (reserved.original !== void 0)
|
|
13075
|
+
if (reserved.original !== void 0) writeFileSync9(reserved.changedFiles[0], reserved.original, "utf8");
|
|
12635
13076
|
return {
|
|
12636
13077
|
id: finding2.id,
|
|
12637
13078
|
title: finding2.title,
|
|
@@ -12666,7 +13107,7 @@ function createBmadChecks() {
|
|
|
12666
13107
|
title: "Supported BMAD CLI projection roots",
|
|
12667
13108
|
audit: (ctx) => {
|
|
12668
13109
|
const unsupportedNames = Object.keys(UNSUPPORTED_BMAD_ROOTS);
|
|
12669
|
-
const present = unsupportedNames.filter((name) => existsSync8(
|
|
13110
|
+
const present = unsupportedNames.filter((name) => existsSync8(join12(ctx.repoRoot, name)));
|
|
12670
13111
|
const attestations = present.map((name) => ({ name, ...unsupportedRootAttestation(ctx.repoRoot, name) }));
|
|
12671
13112
|
const plan = planSkillRoots(ctx.repoRoot);
|
|
12672
13113
|
const supportedIssues = supportedCliProjectionIssues(ctx.repoRoot, plan);
|
|
@@ -12688,7 +13129,7 @@ function createBmadChecks() {
|
|
|
12688
13129
|
},
|
|
12689
13130
|
migrate: (ctx, finding2) => {
|
|
12690
13131
|
const unsupportedNames = Object.keys(UNSUPPORTED_BMAD_ROOTS);
|
|
12691
|
-
const present = unsupportedNames.filter((name) => existsSync8(
|
|
13132
|
+
const present = unsupportedNames.filter((name) => existsSync8(join12(ctx.repoRoot, name)));
|
|
12692
13133
|
const attestations = present.map((name) => ({ name, ...unsupportedRootAttestation(ctx.repoRoot, name) }));
|
|
12693
13134
|
const blocked = attestations.filter((entry) => !entry.safe);
|
|
12694
13135
|
if (blocked.length) {
|
|
@@ -12713,7 +13154,7 @@ function createBmadChecks() {
|
|
|
12713
13154
|
};
|
|
12714
13155
|
}
|
|
12715
13156
|
const gitignoreChanges = ensureSupportedCliGitignore(ctx);
|
|
12716
|
-
const removedRoots = attestations.map((entry) =>
|
|
13157
|
+
const removedRoots = attestations.map((entry) => join12(ctx.repoRoot, entry.name));
|
|
12717
13158
|
if (!ctx.dryRun) for (const path of removedRoots) rmSync5(path, { recursive: true, force: true });
|
|
12718
13159
|
const changedFiles = [.../* @__PURE__ */ new Set([...projectionResult.changedFiles, ...gitignoreChanges, ...removedRoots])].sort();
|
|
12719
13160
|
return {
|
|
@@ -12726,7 +13167,7 @@ function createBmadChecks() {
|
|
|
12726
13167
|
};
|
|
12727
13168
|
}
|
|
12728
13169
|
}
|
|
12729
|
-
].map(withInstallerSkillHandoff);
|
|
13170
|
+
].map(withInstallerSkillHandoff).map(withScaffoldPostcondition);
|
|
12730
13171
|
}
|
|
12731
13172
|
function createProjectMomoChecks() {
|
|
12732
13173
|
return [
|
|
@@ -13335,8 +13776,8 @@ var NodeRecipe = class extends Recipe {
|
|
|
13335
13776
|
// src/recipes/ProjectRecipe.ts
|
|
13336
13777
|
init_project();
|
|
13337
13778
|
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
13338
|
-
import { existsSync as existsSync18, lstatSync as
|
|
13339
|
-
import { dirname as dirname17, isAbsolute as isAbsolute10, join as
|
|
13779
|
+
import { existsSync as existsSync18, lstatSync as lstatSync17, readFileSync as readFileSync21, rmSync as rmSync8 } from "node:fs";
|
|
13780
|
+
import { dirname as dirname17, isAbsolute as isAbsolute10, join as join24, relative as relativePath, resolve as resolve18 } from "node:path";
|
|
13340
13781
|
|
|
13341
13782
|
// src/project/ticketScope.ts
|
|
13342
13783
|
function ticketScope(ticket) {
|
|
@@ -13353,8 +13794,8 @@ init_tree_diff();
|
|
|
13353
13794
|
|
|
13354
13795
|
// src/recipes/NotebookRecipe.ts
|
|
13355
13796
|
init_project();
|
|
13356
|
-
import { existsSync as existsSync17, readFileSync as
|
|
13357
|
-
import { join as
|
|
13797
|
+
import { existsSync as existsSync17, readFileSync as readFileSync20 } from "node:fs";
|
|
13798
|
+
import { join as join23 } from "node:path";
|
|
13358
13799
|
|
|
13359
13800
|
// src/notebook/checks.ts
|
|
13360
13801
|
init_config();
|
|
@@ -13362,8 +13803,8 @@ init_notes();
|
|
|
13362
13803
|
init_output();
|
|
13363
13804
|
init_state();
|
|
13364
13805
|
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
13365
|
-
import { existsSync as existsSync14, readFileSync as
|
|
13366
|
-
import { join as
|
|
13806
|
+
import { existsSync as existsSync14, readFileSync as readFileSync18 } from "node:fs";
|
|
13807
|
+
import { join as join19 } from "node:path";
|
|
13367
13808
|
var NOTEBOOK_RULE_IDS = [
|
|
13368
13809
|
"notebook.configuration",
|
|
13369
13810
|
"notebook.binding",
|
|
@@ -13380,10 +13821,10 @@ function result(check, status, summary, changedFiles = [], details = []) {
|
|
|
13380
13821
|
return { id: check.id, title: check.title, status, summary, changedFiles, details };
|
|
13381
13822
|
}
|
|
13382
13823
|
function manifestNotebook(repo) {
|
|
13383
|
-
const path =
|
|
13824
|
+
const path = join19(repo, ".project.json");
|
|
13384
13825
|
if (!existsSync14(path)) return null;
|
|
13385
13826
|
try {
|
|
13386
|
-
const parsed = JSON.parse(
|
|
13827
|
+
const parsed = JSON.parse(readFileSync18(path, "utf8"));
|
|
13387
13828
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
13388
13829
|
const notebook = parsed.notebook;
|
|
13389
13830
|
return notebook && typeof notebook === "object" && !Array.isArray(notebook) ? notebook : null;
|
|
@@ -13612,7 +14053,7 @@ function captureAudit(ctx) {
|
|
|
13612
14053
|
function overviewMigrate(ctx, current) {
|
|
13613
14054
|
if (ctx.dryRun) return result(current, "applied", "Would reconcile drifted Project Overview note");
|
|
13614
14055
|
if (!ctx.live) return result(current, "blocked", "Reconciling the remote Overview note requires --live");
|
|
13615
|
-
const pjanglerEntry = ctx.pjanglerRoot ?
|
|
14056
|
+
const pjanglerEntry = ctx.pjanglerRoot ? join19(ctx.pjanglerRoot, "dist", "index.js") : null;
|
|
13616
14057
|
const entry = pjanglerEntry && existsSync14(pjanglerEntry) ? pjanglerEntry : "pj";
|
|
13617
14058
|
const cmd = entry === "pj" ? ["pj"] : [process.execPath, entry];
|
|
13618
14059
|
const spawned = spawnSync7(cmd[0], [...cmd.slice(1), "notebook", "overview", ctx.repoRoot], {
|
|
@@ -13664,7 +14105,7 @@ import { resolve as resolve17 } from "node:path";
|
|
|
13664
14105
|
|
|
13665
14106
|
// src/notebook/journal-key.ts
|
|
13666
14107
|
init_types();
|
|
13667
|
-
import { createHash as
|
|
14108
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
13668
14109
|
function validateJournalPeriodKey(period, key) {
|
|
13669
14110
|
if (period === "monthly") {
|
|
13670
14111
|
if (!/^\d{4}-(?:0[1-9]|1[0-2])$/u.test(key)) throw new NotebookError("INVALID_INPUT", "Monthly period key must be YYYY-MM");
|
|
@@ -13683,7 +14124,7 @@ function validateJournalPeriodKey(period, key) {
|
|
|
13683
14124
|
}
|
|
13684
14125
|
function journalNoteOperationId(period, key) {
|
|
13685
14126
|
validateJournalPeriodKey(period, key);
|
|
13686
|
-
const bytes =
|
|
14127
|
+
const bytes = createHash12("sha256").update(`pjangler-dev-journal-v1\0infra\0${period}\0${key}`).digest().subarray(0, 16);
|
|
13687
14128
|
bytes[6] = bytes[6] & 15 | 80;
|
|
13688
14129
|
bytes[8] = bytes[8] & 63 | 128;
|
|
13689
14130
|
const hex = bytes.toString("hex");
|
|
@@ -13839,15 +14280,15 @@ init_notes();
|
|
|
13839
14280
|
init_state();
|
|
13840
14281
|
init_types();
|
|
13841
14282
|
import { spawn, spawnSync as spawnSync10 } from "node:child_process";
|
|
13842
|
-
import { createHash as
|
|
13843
|
-
import { chmodSync as chmodSync6, closeSync as
|
|
13844
|
-
import { basename as
|
|
14283
|
+
import { createHash as createHash13, randomUUID as randomUUID5 } from "node:crypto";
|
|
14284
|
+
import { chmodSync as chmodSync6, closeSync as closeSync7, constants as constants5, copyFileSync as copyFileSync3, existsSync as existsSync15, fstatSync as fstatSync4, fsyncSync as fsyncSync4, lstatSync as lstatSync16, mkdirSync as mkdirSync10, openSync as openSync7, readFileSync as readFileSync19, readSync as readSync3, readdirSync as readdirSync11, realpathSync as realpathSync10, renameSync as renameSync8, rmSync as rmSync7, symlinkSync as symlinkSync4, unlinkSync as unlinkSync9, writeFileSync as writeFileSync13 } from "node:fs";
|
|
14285
|
+
import { basename as basename10, dirname as dirname16, isAbsolute as isAbsolute8, join as join22, parse as parse3, relative as relative15, resolve as resolve15, sep as sep7 } from "node:path";
|
|
13845
14286
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
13846
14287
|
function bundledSkillCandidates() {
|
|
13847
14288
|
const candidates = [];
|
|
13848
14289
|
let cursor = dirname16(fileURLToPath3(import.meta.url));
|
|
13849
14290
|
for (let depth = 0; depth < 8; depth++) {
|
|
13850
|
-
candidates.push(
|
|
14291
|
+
candidates.push(join22(cursor, "dist", "assets", "project-notebook-skill"), join22(cursor, "assets", "project-notebook-skill"));
|
|
13851
14292
|
const parent = dirname16(cursor);
|
|
13852
14293
|
if (parent === cursor) break;
|
|
13853
14294
|
cursor = parent;
|
|
@@ -13865,14 +14306,14 @@ function assertOwnedSkillTree(source) {
|
|
|
13865
14306
|
assertNoSymlinkComponents(source);
|
|
13866
14307
|
const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
13867
14308
|
const walk = (directory) => {
|
|
13868
|
-
const directoryStat =
|
|
14309
|
+
const directoryStat = lstatSync16(directory);
|
|
13869
14310
|
if (!directoryStat.isDirectory() || directoryStat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill source contains a non-directory component");
|
|
13870
14311
|
if (uid !== void 0 && directoryStat.uid !== uid) throw new NotebookError("CONFLICT", "Project Notebook skill source is not owned by the current user");
|
|
13871
14312
|
if (directoryStat.mode & 3586) throw new NotebookError("CONFLICT", "Project Notebook skill source has unsafe directory mode bits");
|
|
13872
|
-
for (const entry of
|
|
14313
|
+
for (const entry of readdirSync11(directory, { withFileTypes: true })) {
|
|
13873
14314
|
if (entry.isDirectory() && (entry.name === "__pycache__" || entry.name === ".git" || entry.name === "node_modules")) continue;
|
|
13874
|
-
const path =
|
|
13875
|
-
const stat =
|
|
14315
|
+
const path = join22(directory, entry.name);
|
|
14316
|
+
const stat = lstatSync16(path);
|
|
13876
14317
|
if (stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill source contains a symlink");
|
|
13877
14318
|
if (entry.isDirectory()) walk(path);
|
|
13878
14319
|
else if (!entry.isFile()) throw new NotebookError("CONFLICT", "Project Notebook skill source contains a non-regular entry");
|
|
@@ -13887,18 +14328,18 @@ function assertOwnedSkillTree(source) {
|
|
|
13887
14328
|
function enumerateSkillPayload(source) {
|
|
13888
14329
|
const result2 = [];
|
|
13889
14330
|
const walk = (directory) => {
|
|
13890
|
-
for (const entry of
|
|
14331
|
+
for (const entry of readdirSync11(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name, "en"))) {
|
|
13891
14332
|
if (entry.isDirectory() && (entry.name === "__pycache__" || entry.name === ".git" || entry.name === "node_modules")) continue;
|
|
13892
|
-
const path =
|
|
14333
|
+
const path = join22(directory, entry.name);
|
|
13893
14334
|
const rel = relative15(source, path).split(sep7).join("/");
|
|
13894
14335
|
if (!rel.includes("/") && (rel === "export-manifest.json" || rel === "SHA256SUMS" || rel === ".source.yaml")) continue;
|
|
13895
14336
|
if (!safeSkillRelativePath(rel)) throw new NotebookError("CONFLICT", `Project Notebook skill export path is unsafe: ${rel}`);
|
|
13896
|
-
const stat =
|
|
14337
|
+
const stat = lstatSync16(path);
|
|
13897
14338
|
if (stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill export contains a symlink");
|
|
13898
14339
|
if (entry.isDirectory()) walk(path);
|
|
13899
14340
|
else if (entry.isFile()) {
|
|
13900
14341
|
const expectedMode = rel.endsWith(".sh") || rel.startsWith("scripts/") ? "0755" : "0644";
|
|
13901
|
-
result2.push({ path: rel, sha256:
|
|
14342
|
+
result2.push({ path: rel, sha256: createHash13("sha256").update(readFileSync19(path)).digest("hex"), mode: expectedMode });
|
|
13902
14343
|
} else throw new NotebookError("CONFLICT", "Project Notebook skill export contains a non-regular entry");
|
|
13903
14344
|
}
|
|
13904
14345
|
};
|
|
@@ -13906,9 +14347,9 @@ function enumerateSkillPayload(source) {
|
|
|
13906
14347
|
return result2.sort((a, b) => a.path.localeCompare(b.path, "en"));
|
|
13907
14348
|
}
|
|
13908
14349
|
function parsePackedManifest(source) {
|
|
13909
|
-
const manifestPath =
|
|
14350
|
+
const manifestPath = join22(source, "export-manifest.json");
|
|
13910
14351
|
if (!existsSync15(manifestPath)) return null;
|
|
13911
|
-
const value = JSON.parse(
|
|
14352
|
+
const value = JSON.parse(readFileSync19(manifestPath, "utf8"));
|
|
13912
14353
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new NotebookError("CONFLICT", "Project Notebook skill export manifest is invalid");
|
|
13913
14354
|
const manifest = value;
|
|
13914
14355
|
if (manifest.schema_version !== 1 || manifest.skill !== "project-notebook" || !Array.isArray(manifest.files)) throw new NotebookError("CONFLICT", "Project Notebook skill export manifest is incompatible");
|
|
@@ -13917,10 +14358,10 @@ function parsePackedManifest(source) {
|
|
|
13917
14358
|
if (!entry || typeof entry.path !== "string" || !/^[a-f0-9]{64}$/u.test(entry.sha256) || !/^(?:0644|0755)$/u.test(entry.mode)) throw new NotebookError("CONFLICT", "Project Notebook skill export entry is invalid");
|
|
13918
14359
|
if (!safeSkillRelativePath(entry.path) || paths.has(entry.path)) throw new NotebookError("CONFLICT", "Project Notebook skill export path is unsafe or duplicated");
|
|
13919
14360
|
paths.add(entry.path);
|
|
13920
|
-
const path =
|
|
13921
|
-
const stat =
|
|
14361
|
+
const path = join22(source, ...entry.path.split("/"));
|
|
14362
|
+
const stat = lstatSync16(path);
|
|
13922
14363
|
if (!stat.isFile() || stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill export contains a non-regular entry");
|
|
13923
|
-
const actual =
|
|
14364
|
+
const actual = createHash13("sha256").update(readFileSync19(path)).digest("hex");
|
|
13924
14365
|
if (actual !== entry.sha256) throw new NotebookError("CONFLICT", `Project Notebook skill digest mismatch: ${entry.path}`);
|
|
13925
14366
|
const actualMode = stat.mode & 511;
|
|
13926
14367
|
const executable = entry.mode === "0755";
|
|
@@ -13932,16 +14373,16 @@ function parsePackedManifest(source) {
|
|
|
13932
14373
|
if (JSON.stringify(actualPaths) !== JSON.stringify(manifest.files.map((entry) => entry.path))) throw new NotebookError("CONFLICT", "Project Notebook skill manifest does not exactly enumerate its payload");
|
|
13933
14374
|
const sums = `${manifest.files.map((entry) => `${entry.sha256} ${entry.path}`).join("\n")}
|
|
13934
14375
|
`;
|
|
13935
|
-
if (!existsSync15(
|
|
14376
|
+
if (!existsSync15(join22(source, "SHA256SUMS")) || readFileSync19(join22(source, "SHA256SUMS"), "utf8") !== sums) throw new NotebookError("CONFLICT", "Project Notebook skill SHA256SUMS is missing or stale");
|
|
13936
14377
|
return manifest;
|
|
13937
14378
|
}
|
|
13938
14379
|
function expectedPackedSkill() {
|
|
13939
14380
|
for (const candidate of bundledSkillCandidates()) {
|
|
13940
|
-
if (!existsSync15(
|
|
14381
|
+
if (!existsSync15(join22(candidate, "export-manifest.json"))) continue;
|
|
13941
14382
|
assertOwnedSkillTree(candidate);
|
|
13942
14383
|
const manifest = parsePackedManifest(candidate);
|
|
13943
14384
|
if (!manifest) continue;
|
|
13944
|
-
const digest =
|
|
14385
|
+
const digest = createHash13("sha256").update(JSON.stringify(manifest)).digest("hex");
|
|
13945
14386
|
return { source: candidate, manifest, digest };
|
|
13946
14387
|
}
|
|
13947
14388
|
throw new NotebookError("NOT_CONFIGURED", "PJ\xE1ngler package has no verified Project Notebook skill export");
|
|
@@ -14004,13 +14445,13 @@ function verifyProjectNotebookSkillExport(source) {
|
|
|
14004
14445
|
function resolveProjectNotebookSkillSource(env2 = process.env) {
|
|
14005
14446
|
if (env2.PJ_PROJECT_NOTEBOOK_SKILL_ROOT) {
|
|
14006
14447
|
const explicit = resolve15(env2.PJ_PROJECT_NOTEBOOK_SKILL_ROOT);
|
|
14007
|
-
if (!existsSync15(
|
|
14448
|
+
if (!existsSync15(join22(explicit, "SKILL.md"))) throw new NotebookError("NOT_CONFIGURED", "Configured Project Notebook skill source is unavailable");
|
|
14008
14449
|
verifyProjectNotebookSkillExport(explicit);
|
|
14009
14450
|
return explicit;
|
|
14010
14451
|
}
|
|
14011
14452
|
if (env2.PJ_SKILLS_REGISTRY_ROOT) {
|
|
14012
14453
|
const canonical = resolve15(env2.PJ_SKILLS_REGISTRY_ROOT, "all-skills", "project-notebook");
|
|
14013
|
-
if (existsSync15(
|
|
14454
|
+
if (existsSync15(join22(canonical, "SKILL.md"))) {
|
|
14014
14455
|
verifyProjectNotebookSkillExport(canonical);
|
|
14015
14456
|
return canonical;
|
|
14016
14457
|
}
|
|
@@ -14025,7 +14466,7 @@ var REPAIR_COMMAND = "pj notebook skill --apply";
|
|
|
14025
14466
|
function probeCanonicalSkillexRootProjection(skillsRoot, link) {
|
|
14026
14467
|
let rootLink;
|
|
14027
14468
|
try {
|
|
14028
|
-
rootLink =
|
|
14469
|
+
rootLink = lstatSync16(skillsRoot);
|
|
14029
14470
|
} catch (error) {
|
|
14030
14471
|
if (error.code === "ENOENT") return { state: "absent" };
|
|
14031
14472
|
throw error;
|
|
@@ -14038,15 +14479,15 @@ function probeCanonicalSkillexRootProjection(skillsRoot, link) {
|
|
|
14038
14479
|
if (uid !== void 0 && rootLink.uid !== uid) throw new Error("owner");
|
|
14039
14480
|
const globalRoot = realpathSync10(skillsRoot);
|
|
14040
14481
|
assertNoSymlinkComponents(globalRoot);
|
|
14041
|
-
const globalStat =
|
|
14482
|
+
const globalStat = lstatSync16(globalRoot);
|
|
14042
14483
|
if (!globalStat.isDirectory() || globalStat.isSymbolicLink()) throw new Error("root-type");
|
|
14043
14484
|
if (uid !== void 0 && globalStat.uid !== uid) throw new Error("root-owner");
|
|
14044
14485
|
if (globalStat.mode & 3586) throw new Error("root-mode");
|
|
14045
14486
|
const skillSetsRoot = dirname16(globalRoot);
|
|
14046
|
-
if (
|
|
14487
|
+
if (basename10(globalRoot) !== "global" || basename10(skillSetsRoot) !== "skill-sets") throw new Error("layout");
|
|
14047
14488
|
const checkoutRoot = dirname16(skillSetsRoot);
|
|
14048
|
-
const expectedSource =
|
|
14049
|
-
const linkStat =
|
|
14489
|
+
const expectedSource = join22(checkoutRoot, "all-skills", "project-notebook");
|
|
14490
|
+
const linkStat = lstatSync16(link);
|
|
14050
14491
|
if (!linkStat.isSymbolicLink()) throw new Error("projection-type");
|
|
14051
14492
|
if (uid !== void 0 && linkStat.uid !== uid) throw new Error("projection-owner");
|
|
14052
14493
|
const projectedSource = realpathSync10(link);
|
|
@@ -14080,44 +14521,44 @@ function installPackagedProjectNotebookSkill(input = {}) {
|
|
|
14080
14521
|
const source = input.source ?? resolveProjectNotebookSkillSource(env2);
|
|
14081
14522
|
if (!source) throw new NotebookError("NOT_CONFIGURED", "Project Notebook skill source is unavailable");
|
|
14082
14523
|
const manifest = verifyProjectNotebookSkillExport(source);
|
|
14083
|
-
const digest =
|
|
14524
|
+
const digest = createHash13("sha256").update(JSON.stringify(manifest)).digest("hex");
|
|
14084
14525
|
const home = env2.HOME;
|
|
14085
14526
|
if (!home || !resolve15(home).startsWith("/")) throw new NotebookError("NOT_CONFIGURED", "A trusted HOME is required to install the Project Notebook skill");
|
|
14086
|
-
const skillsRoot =
|
|
14087
|
-
const link =
|
|
14527
|
+
const skillsRoot = join22(home, ".agents", "skills");
|
|
14528
|
+
const link = join22(skillsRoot, "project-notebook");
|
|
14088
14529
|
const probe = probeCanonicalSkillexRootProjection(skillsRoot, link);
|
|
14089
14530
|
if (probe.state === "adopted") return { installed: false, path: link, digest };
|
|
14090
14531
|
if (probe.state === "declined") return { installed: false, path: link, digest, blocked: probe.block };
|
|
14091
|
-
const dataRoot = resolve15(env2.XDG_DATA_HOME ||
|
|
14092
|
-
const payload =
|
|
14532
|
+
const dataRoot = resolve15(env2.XDG_DATA_HOME || join22(home, ".local", "share"), "pjangler", "skills", "project-notebook");
|
|
14533
|
+
const payload = join22(dataRoot, `${PJANGLER_VERSION}-${digest}`);
|
|
14093
14534
|
assertNoSymlinkComponents(dirname16(dataRoot), true);
|
|
14094
|
-
|
|
14535
|
+
mkdirSync10(dataRoot, { recursive: true, mode: 448 });
|
|
14095
14536
|
assertNoSymlinkComponents(dataRoot);
|
|
14096
|
-
const dataStat =
|
|
14537
|
+
const dataStat = lstatSync16(dataRoot);
|
|
14097
14538
|
if (!dataStat.isDirectory() || dataStat.isSymbolicLink() || typeof process.getuid === "function" && dataStat.uid !== process.getuid()) throw new NotebookError("CONFLICT", "Project Notebook skill data root is not a current-user directory");
|
|
14098
14539
|
chmodSync6(dataRoot, 448);
|
|
14099
14540
|
if (!existsSync15(payload)) {
|
|
14100
|
-
const staging =
|
|
14101
|
-
|
|
14541
|
+
const staging = join22(dataRoot, `.staging-${randomUUID5()}`);
|
|
14542
|
+
mkdirSync10(staging, { recursive: false, mode: 448 });
|
|
14102
14543
|
try {
|
|
14103
14544
|
for (const entry of manifest.files) {
|
|
14104
|
-
const destination =
|
|
14105
|
-
|
|
14106
|
-
copyFileSync3(
|
|
14545
|
+
const destination = join22(staging, ...entry.path.split("/"));
|
|
14546
|
+
mkdirSync10(dirname16(destination), { recursive: true, mode: 493 });
|
|
14547
|
+
copyFileSync3(join22(source, ...entry.path.split("/")), destination);
|
|
14107
14548
|
const mode = entry.mode === "0755" ? 493 : 420;
|
|
14108
14549
|
chmodSync6(destination, mode);
|
|
14109
|
-
const fd =
|
|
14550
|
+
const fd = openSync7(destination, constants5.O_RDONLY | (constants5.O_NOFOLLOW ?? 0));
|
|
14110
14551
|
try {
|
|
14111
|
-
const current =
|
|
14552
|
+
const current = fstatSync4(fd);
|
|
14112
14553
|
if (!current.isFile()) throw new NotebookError("CONFLICT", `Installed skill entry is not regular: ${entry.path}`);
|
|
14113
14554
|
fsyncSync4(fd);
|
|
14114
14555
|
} finally {
|
|
14115
|
-
|
|
14556
|
+
closeSync7(fd);
|
|
14116
14557
|
}
|
|
14117
14558
|
}
|
|
14118
|
-
|
|
14559
|
+
writeFileSync13(join22(staging, "export-manifest.json"), `${JSON.stringify(manifest, null, 2)}
|
|
14119
14560
|
`, { mode: 420, flag: "wx" });
|
|
14120
|
-
|
|
14561
|
+
writeFileSync13(join22(staging, "SHA256SUMS"), `${manifest.files.map((entry) => `${entry.sha256} ${entry.path}`).join("\n")}
|
|
14121
14562
|
`, { mode: 420, flag: "wx" });
|
|
14122
14563
|
verifyProjectNotebookSkillExport(staging);
|
|
14123
14564
|
renameSync8(staging, payload);
|
|
@@ -14125,17 +14566,17 @@ function installPackagedProjectNotebookSkill(input = {}) {
|
|
|
14125
14566
|
if (existsSync15(staging)) rmSync7(staging, { recursive: true, force: true });
|
|
14126
14567
|
}
|
|
14127
14568
|
} else {
|
|
14128
|
-
const stat =
|
|
14569
|
+
const stat = lstatSync16(payload);
|
|
14129
14570
|
if (!stat.isDirectory() || stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Installed Project Notebook payload is not a real directory");
|
|
14130
14571
|
verifyProjectNotebookSkillExport(payload);
|
|
14131
14572
|
}
|
|
14132
14573
|
assertNoSymlinkComponents(skillsRoot, true);
|
|
14133
|
-
|
|
14574
|
+
mkdirSync10(skillsRoot, { recursive: true, mode: 448 });
|
|
14134
14575
|
assertNoSymlinkComponents(skillsRoot);
|
|
14135
14576
|
let linkExists = existsSync15(link);
|
|
14136
14577
|
if (!linkExists) {
|
|
14137
14578
|
try {
|
|
14138
|
-
|
|
14579
|
+
lstatSync16(link);
|
|
14139
14580
|
linkExists = true;
|
|
14140
14581
|
} catch {
|
|
14141
14582
|
}
|
|
@@ -14152,7 +14593,7 @@ function installPackagedProjectNotebookSkill(input = {}) {
|
|
|
14152
14593
|
repair: REPAIR_COMMAND
|
|
14153
14594
|
}
|
|
14154
14595
|
});
|
|
14155
|
-
const stat =
|
|
14596
|
+
const stat = lstatSync16(link);
|
|
14156
14597
|
if (!stat.isSymbolicLink()) return foreign(["the path is a real file or directory, not a PJ\xE1ngler-owned link"]);
|
|
14157
14598
|
const target = realpathSync10(link);
|
|
14158
14599
|
if (target !== realpathSync10(payload) && !isVerifiedCanonicalSkillexProjection(target)) {
|
|
@@ -14180,15 +14621,15 @@ function collectSupersededPayloads(dataRoot, keep) {
|
|
|
14180
14621
|
const removed = [];
|
|
14181
14622
|
let entries;
|
|
14182
14623
|
try {
|
|
14183
|
-
entries =
|
|
14624
|
+
entries = readdirSync11(dataRoot);
|
|
14184
14625
|
} catch {
|
|
14185
14626
|
return removed;
|
|
14186
14627
|
}
|
|
14187
14628
|
for (const name of entries) {
|
|
14188
14629
|
if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?-[0-9a-f]{64}$/u.test(name)) continue;
|
|
14189
|
-
const candidate =
|
|
14630
|
+
const candidate = join22(dataRoot, name);
|
|
14190
14631
|
try {
|
|
14191
|
-
const stat =
|
|
14632
|
+
const stat = lstatSync16(candidate);
|
|
14192
14633
|
if (!stat.isDirectory() || stat.isSymbolicLink()) continue;
|
|
14193
14634
|
if (kept.has(realpathSync10(candidate))) continue;
|
|
14194
14635
|
rmSync7(candidate, { recursive: true, force: true });
|
|
@@ -14202,8 +14643,8 @@ function repairProjectNotebookSkillProjection(input = {}) {
|
|
|
14202
14643
|
const env2 = input.env ?? process.env;
|
|
14203
14644
|
const home = env2.HOME;
|
|
14204
14645
|
if (!home || !resolve15(home).startsWith("/")) throw new NotebookError("NOT_CONFIGURED", "A trusted HOME is required to repair the Project Notebook skill projection");
|
|
14205
|
-
const skillsRoot =
|
|
14206
|
-
const link =
|
|
14646
|
+
const skillsRoot = join22(home, ".agents", "skills");
|
|
14647
|
+
const link = join22(skillsRoot, "project-notebook");
|
|
14207
14648
|
const probe = probeCanonicalSkillexRootProjection(skillsRoot, link);
|
|
14208
14649
|
if (probe.state === "adopted") return { status: "clean", summary: "Canonical Skillex projection already matches the version-pinned export", source: probe.source, drift: [], changed_files: [] };
|
|
14209
14650
|
if (probe.state !== "declined") {
|
|
@@ -14216,8 +14657,8 @@ function repairProjectNotebookSkillProjection(input = {}) {
|
|
|
14216
14657
|
assertOwnedSkillTree(source);
|
|
14217
14658
|
const present = [];
|
|
14218
14659
|
const walk = (directory) => {
|
|
14219
|
-
for (const entry of
|
|
14220
|
-
const path =
|
|
14660
|
+
for (const entry of readdirSync11(directory, { withFileTypes: true })) {
|
|
14661
|
+
const path = join22(directory, entry.name);
|
|
14221
14662
|
const rel = relative15(source, path).split(sep7).join("/");
|
|
14222
14663
|
if (entry.isDirectory()) {
|
|
14223
14664
|
walk(path);
|
|
@@ -14231,11 +14672,11 @@ function repairProjectNotebookSkillProjection(input = {}) {
|
|
|
14231
14672
|
walk(source);
|
|
14232
14673
|
const stale = present.filter((rel) => !wanted.has(rel)).sort();
|
|
14233
14674
|
const changed = expected.manifest.files.filter((entry) => {
|
|
14234
|
-
const path =
|
|
14675
|
+
const path = join22(source, ...entry.path.split("/"));
|
|
14235
14676
|
if (!existsSync15(path)) return true;
|
|
14236
|
-
const stat =
|
|
14677
|
+
const stat = lstatSync16(path);
|
|
14237
14678
|
if (!stat.isFile()) return true;
|
|
14238
|
-
if (
|
|
14679
|
+
if (createHash13("sha256").update(readFileSync19(path)).digest("hex") !== entry.sha256) return true;
|
|
14239
14680
|
const mode = stat.mode & 511;
|
|
14240
14681
|
const executable = entry.mode === "0755";
|
|
14241
14682
|
return executable && (mode & 64) === 0 || !executable && (mode & 73) !== 0;
|
|
@@ -14250,15 +14691,15 @@ function repairProjectNotebookSkillProjection(input = {}) {
|
|
|
14250
14691
|
summary: `Would restore ${affected.length} file(s) in ${source} from the version-pinned export`,
|
|
14251
14692
|
source,
|
|
14252
14693
|
drift: probe.block.details,
|
|
14253
|
-
changed_files: affected.map((rel) =>
|
|
14694
|
+
changed_files: affected.map((rel) => join22(source, ...rel.split("/")))
|
|
14254
14695
|
};
|
|
14255
14696
|
}
|
|
14256
|
-
for (const rel of stale) rmSync7(
|
|
14697
|
+
for (const rel of stale) rmSync7(join22(source, ...rel.split("/")), { force: true });
|
|
14257
14698
|
for (const rel of changed) {
|
|
14258
|
-
const destination =
|
|
14699
|
+
const destination = join22(source, ...rel.split("/"));
|
|
14259
14700
|
const entry = expected.manifest.files.find((item) => item.path === rel);
|
|
14260
|
-
|
|
14261
|
-
copyFileSync3(
|
|
14701
|
+
mkdirSync10(dirname16(destination), { recursive: true, mode: 493 });
|
|
14702
|
+
copyFileSync3(join22(expected.source, ...rel.split("/")), destination);
|
|
14262
14703
|
chmodSync6(destination, entry.mode === "0755" ? 493 : 420);
|
|
14263
14704
|
}
|
|
14264
14705
|
verifyProjectNotebookSkillExport(source);
|
|
@@ -14267,21 +14708,21 @@ function repairProjectNotebookSkillProjection(input = {}) {
|
|
|
14267
14708
|
summary: `Restored ${affected.length} file(s) in ${source} from the version-pinned export; commit the change in the Skillex checkout`,
|
|
14268
14709
|
source,
|
|
14269
14710
|
drift: probe.block.details,
|
|
14270
|
-
changed_files: affected.map((rel) =>
|
|
14711
|
+
changed_files: affected.map((rel) => join22(source, ...rel.split("/")))
|
|
14271
14712
|
};
|
|
14272
14713
|
}
|
|
14273
14714
|
function inspectProjectNotebookIntegration(env2 = process.env) {
|
|
14274
14715
|
const home = env2.HOME;
|
|
14275
14716
|
if (!home) return { skill_installed: false, hooks_projected: false, details: ["HOME is unavailable"] };
|
|
14276
|
-
const skillsRoot =
|
|
14277
|
-
const link =
|
|
14717
|
+
const skillsRoot = join22(home, ".agents", "skills");
|
|
14718
|
+
const link = join22(skillsRoot, "project-notebook");
|
|
14278
14719
|
try {
|
|
14279
14720
|
const expected = expectedPackedSkill();
|
|
14280
|
-
const dataRoot = resolve15(env2.XDG_DATA_HOME ||
|
|
14281
|
-
const expectedPayload =
|
|
14721
|
+
const dataRoot = resolve15(env2.XDG_DATA_HOME || join22(home, ".local", "share"), "pjangler", "skills", "project-notebook");
|
|
14722
|
+
const expectedPayload = join22(dataRoot, `${PJANGLER_VERSION}-${expected.digest}`);
|
|
14282
14723
|
const probe = probeCanonicalSkillexRootProjection(skillsRoot, link);
|
|
14283
14724
|
if (probe.state === "declined") return { skill_installed: false, hooks_projected: false, details: probe.block.details, blocked: probe.block };
|
|
14284
|
-
const stat =
|
|
14725
|
+
const stat = lstatSync16(link);
|
|
14285
14726
|
if (!stat.isSymbolicLink()) {
|
|
14286
14727
|
return {
|
|
14287
14728
|
skill_installed: false,
|
|
@@ -14318,8 +14759,8 @@ function inspectProjectNotebookIntegration(env2 = process.env) {
|
|
|
14318
14759
|
}
|
|
14319
14760
|
}
|
|
14320
14761
|
function projectorArguments(source, command, input) {
|
|
14321
|
-
const script =
|
|
14322
|
-
const args = [script, command, "--master",
|
|
14762
|
+
const script = join22(source, "scripts", "project-hooks.py");
|
|
14763
|
+
const args = [script, command, "--master", join22(source, "hooks", "hooks.master.json"), "--fragment", join22(source, "hooks", "claude.settings.json"), "--target", input.target];
|
|
14323
14764
|
if (command === "check") args.push("--json");
|
|
14324
14765
|
else if (input.stateHome) args.push("--state-home", input.stateHome);
|
|
14325
14766
|
return args;
|
|
@@ -14341,7 +14782,7 @@ function checkProjectNotebookHooks(input = {}) {
|
|
|
14341
14782
|
verifyProjectNotebookSkillExport(source);
|
|
14342
14783
|
const home = env2.HOME;
|
|
14343
14784
|
if (!home) throw new NotebookError("NOT_CONFIGURED", "HOME is required to check Project Notebook hooks");
|
|
14344
|
-
const target = resolve15(input.target ?? env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ??
|
|
14785
|
+
const target = resolve15(input.target ?? env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ?? join22(home, ".claude", "settings.json"));
|
|
14345
14786
|
const result2 = spawnSync10("/usr/bin/python3", ["-I", ...projectorArguments(source, "check", { target })], { encoding: "utf8", env: projectorEnvironment(env2), timeout: 5e3, maxBuffer: 1048576 });
|
|
14346
14787
|
if (result2.status !== 0 && result2.status !== 1) throw new NotebookError("CONFLICT", (result2.stderr || "Project Notebook projector check failed").trim().slice(0, 512));
|
|
14347
14788
|
try {
|
|
@@ -14359,14 +14800,14 @@ function installProjectNotebookIntegration(input = {}) {
|
|
|
14359
14800
|
const source = realpathSync10(skill.path);
|
|
14360
14801
|
const home = env2.HOME;
|
|
14361
14802
|
if (!home) throw new NotebookError("NOT_CONFIGURED", "HOME is required to install Project Notebook hooks");
|
|
14362
|
-
const target = resolve15(input.target ?? env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ??
|
|
14363
|
-
const stateHome = resolve15(env2.XDG_STATE_HOME ||
|
|
14803
|
+
const target = resolve15(input.target ?? env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ?? join22(home, ".claude", "settings.json"));
|
|
14804
|
+
const stateHome = resolve15(env2.XDG_STATE_HOME || join22(home, ".local", "state"));
|
|
14364
14805
|
const result2 = spawnSync10("/usr/bin/python3", ["-I", ...projectorArguments(source, "install", { target, stateHome })], { encoding: "utf8", env: projectorEnvironment(env2), timeout: 5e3, maxBuffer: 1048576 });
|
|
14365
14806
|
if (result2.status !== 0) {
|
|
14366
14807
|
if (skill.installed) {
|
|
14367
14808
|
try {
|
|
14368
|
-
const stat =
|
|
14369
|
-
if (stat.isSymbolicLink() && realpathSync10(skill.path) === source)
|
|
14809
|
+
const stat = lstatSync16(skill.path);
|
|
14810
|
+
if (stat.isSymbolicLink() && realpathSync10(skill.path) === source) unlinkSync9(skill.path);
|
|
14370
14811
|
} catch {
|
|
14371
14812
|
}
|
|
14372
14813
|
}
|
|
@@ -14383,10 +14824,10 @@ function assertNoSymlinkComponents(path, allowMissing = false) {
|
|
|
14383
14824
|
const root = parse3(absolute).root;
|
|
14384
14825
|
let cursor = root;
|
|
14385
14826
|
for (const component of absolute.slice(root.length).split(sep7).filter(Boolean)) {
|
|
14386
|
-
cursor =
|
|
14827
|
+
cursor = join22(cursor, component);
|
|
14387
14828
|
let stat;
|
|
14388
14829
|
try {
|
|
14389
|
-
stat =
|
|
14830
|
+
stat = lstatSync16(cursor);
|
|
14390
14831
|
} catch (error) {
|
|
14391
14832
|
if (allowMissing && error.code === "ENOENT") return;
|
|
14392
14833
|
throw error;
|
|
@@ -14992,17 +15433,17 @@ init_remote_mutation_journal();
|
|
|
14992
15433
|
function integrationEnvironment(module, ctx) {
|
|
14993
15434
|
const env2 = { ...module.environment, HOME: ctx.homeDir };
|
|
14994
15435
|
if (module.environment.HOME !== ctx.homeDir) {
|
|
14995
|
-
env2.XDG_DATA_HOME =
|
|
14996
|
-
env2.XDG_STATE_HOME =
|
|
14997
|
-
env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS =
|
|
15436
|
+
env2.XDG_DATA_HOME = join23(ctx.homeDir, ".local", "share");
|
|
15437
|
+
env2.XDG_STATE_HOME = join23(ctx.homeDir, ".local", "state");
|
|
15438
|
+
env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS = join23(ctx.homeDir, ".claude", "settings.json");
|
|
14998
15439
|
}
|
|
14999
15440
|
return env2;
|
|
15000
15441
|
}
|
|
15001
15442
|
function resolvedForPlan(plan) {
|
|
15002
|
-
const manifestPath =
|
|
15443
|
+
const manifestPath = join23(plan.project.repo_path, ".project.json");
|
|
15003
15444
|
let manifest = plan.manifest;
|
|
15004
15445
|
if (existsSync17(manifestPath)) {
|
|
15005
|
-
const parsed = JSON.parse(
|
|
15446
|
+
const parsed = JSON.parse(readFileSync20(manifestPath, "utf8"));
|
|
15006
15447
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`${manifestPath} must contain a JSON object`);
|
|
15007
15448
|
manifest = parsed;
|
|
15008
15449
|
}
|
|
@@ -15163,7 +15604,7 @@ function unsafeToRemove(targetDir) {
|
|
|
15163
15604
|
while (!seen.has(cursor)) {
|
|
15164
15605
|
seen.add(cursor);
|
|
15165
15606
|
try {
|
|
15166
|
-
const stat =
|
|
15607
|
+
const stat = lstatSync17(cursor);
|
|
15167
15608
|
if (stat.isSymbolicLink()) {
|
|
15168
15609
|
return cursor === absolute ? `${absolute} is a symlink; removing it would leave the tree it points at orphaned` : `${cursor} is a symlink on the path to ${absolute}; a recursive remove would traverse it`;
|
|
15169
15610
|
}
|
|
@@ -15191,12 +15632,12 @@ function publicMigration(report) {
|
|
|
15191
15632
|
};
|
|
15192
15633
|
}
|
|
15193
15634
|
function hasGitRepository(runtime, targetDir) {
|
|
15194
|
-
if (!existsSync18(
|
|
15635
|
+
if (!existsSync18(join24(targetDir, ".git"))) return false;
|
|
15195
15636
|
return runtime.runGit(targetDir, ["rev-parse", "--is-inside-work-tree"]).status === 0;
|
|
15196
15637
|
}
|
|
15197
15638
|
function refreshPlanFromCanonicalManifest(plan) {
|
|
15198
|
-
const manifestPath =
|
|
15199
|
-
const manifest = JSON.parse(
|
|
15639
|
+
const manifestPath = join24(plan.project.repo_path, ".project.json");
|
|
15640
|
+
const manifest = JSON.parse(readFileSync21(manifestPath, "utf8"));
|
|
15200
15641
|
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
15201
15642
|
throw new Error(`${manifestPath} must contain a JSON object`);
|
|
15202
15643
|
}
|
|
@@ -15427,7 +15868,7 @@ var ProjectRecipe = class extends Recipe {
|
|
|
15427
15868
|
if (hasGitRepository(this.runtime, targetDir)) {
|
|
15428
15869
|
phases.push({ id: "project.git", status: "unchanged", changedFiles: [], message: "Git repository already initialized" });
|
|
15429
15870
|
} else {
|
|
15430
|
-
const gitPath =
|
|
15871
|
+
const gitPath = join24(targetDir, ".git");
|
|
15431
15872
|
for (const { args, label, options } of [
|
|
15432
15873
|
{ args: ["init", "--initial-branch=main"], label: "git init" },
|
|
15433
15874
|
{ args: ["add", "-A"], label: "git add" },
|
|
@@ -15638,12 +16079,12 @@ var ProjectRecipe = class extends Recipe {
|
|
|
15638
16079
|
};
|
|
15639
16080
|
|
|
15640
16081
|
// src/recipes/registry.ts
|
|
15641
|
-
import { readFileSync as
|
|
15642
|
-
import { join as
|
|
16082
|
+
import { readFileSync as readFileSync22 } from "node:fs";
|
|
16083
|
+
import { join as join25 } from "node:path";
|
|
15643
16084
|
function projectWaivers(repoRoot) {
|
|
15644
16085
|
let waive;
|
|
15645
16086
|
try {
|
|
15646
|
-
waive = JSON.parse(
|
|
16087
|
+
waive = JSON.parse(readFileSync22(join25(repoRoot, ".project.json"), "utf8"))?.parity?.waive;
|
|
15647
16088
|
} catch {
|
|
15648
16089
|
return /* @__PURE__ */ new Map();
|
|
15649
16090
|
}
|
|
@@ -15984,8 +16425,8 @@ var recipeRegistry = new RecipeRegistry([
|
|
|
15984
16425
|
|
|
15985
16426
|
// src/commands/AgentHooksCommands.ts
|
|
15986
16427
|
import { homedir as homedir8 } from "node:os";
|
|
15987
|
-
import { join as
|
|
15988
|
-
import { existsSync as existsSync19, cpSync, mkdirSync as
|
|
16428
|
+
import { join as join26, dirname as dirname18 } from "node:path";
|
|
16429
|
+
import { existsSync as existsSync19, cpSync, mkdirSync as mkdirSync11, readFileSync as readFileSync23, writeFileSync as writeFileSync14 } from "node:fs";
|
|
15989
16430
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
15990
16431
|
init_project();
|
|
15991
16432
|
import { parse as parseToml4 } from "smol-toml";
|
|
@@ -15998,16 +16439,16 @@ function resolveTemplateRoot() {
|
|
|
15998
16439
|
try {
|
|
15999
16440
|
let dir = dirname18(fileURLToPath4(import.meta.url));
|
|
16000
16441
|
for (let i = 0; i < 8; i++) {
|
|
16001
|
-
candidates.push(
|
|
16442
|
+
candidates.push(join26(dir, "templates", "commonproject", "template"));
|
|
16002
16443
|
const parent = dirname18(dir);
|
|
16003
16444
|
if (parent === dir) break;
|
|
16004
16445
|
dir = parent;
|
|
16005
16446
|
}
|
|
16006
16447
|
} catch {
|
|
16007
16448
|
}
|
|
16008
|
-
candidates.push(
|
|
16449
|
+
candidates.push(join26(homedir8(), "code", "pjangler", "templates", "commonproject", "template"));
|
|
16009
16450
|
for (const c of candidates) {
|
|
16010
|
-
if (existsSync19(
|
|
16451
|
+
if (existsSync19(join26(c, ".agents", "hooks", "hooks.master.json"))) return c;
|
|
16011
16452
|
}
|
|
16012
16453
|
throw new Error(
|
|
16013
16454
|
"Could not locate the CommonProject template. Set PJANGLER_COMMONPROJECT_TEMPLATE to <repo>/templates/commonproject/template."
|
|
@@ -16033,15 +16474,15 @@ var CopyAgentHooksTree = class extends Command {
|
|
|
16033
16474
|
const created = [];
|
|
16034
16475
|
const skipped = [];
|
|
16035
16476
|
for (const { rel, dir } of items) {
|
|
16036
|
-
const src =
|
|
16037
|
-
const dest =
|
|
16477
|
+
const src = join26(templateRoot, rel);
|
|
16478
|
+
const dest = join26(this.context.targetDir, rel);
|
|
16038
16479
|
if (!existsSync19(src)) continue;
|
|
16039
16480
|
if (existsSync19(dest) && !this.context.force) {
|
|
16040
16481
|
skipped.push(rel);
|
|
16041
16482
|
continue;
|
|
16042
16483
|
}
|
|
16043
16484
|
if (!this.context.dryRun) {
|
|
16044
|
-
|
|
16485
|
+
mkdirSync11(dirname18(dest), { recursive: true });
|
|
16045
16486
|
cpSync(src, dest, { recursive: dir, force: true });
|
|
16046
16487
|
}
|
|
16047
16488
|
created.push(rel);
|
|
@@ -16062,14 +16503,14 @@ var WireMiseAgentHooks = class _WireMiseAgentHooks extends Command {
|
|
|
16062
16503
|
if (!resolveAgentHooksLayer2()) {
|
|
16063
16504
|
return { success: true, message: this.formatMessage(AGENT_HOOKS_SKIP_MESSAGE) };
|
|
16064
16505
|
}
|
|
16065
|
-
const misePath =
|
|
16506
|
+
const misePath = join26(this.context.targetDir, "mise.toml");
|
|
16066
16507
|
if (!existsSync19(misePath)) {
|
|
16067
16508
|
return {
|
|
16068
16509
|
success: false,
|
|
16069
16510
|
message: "\u26A0\uFE0F No mise.toml found \u2014 run `pj add mise` first, then re-run."
|
|
16070
16511
|
};
|
|
16071
16512
|
}
|
|
16072
|
-
let content =
|
|
16513
|
+
let content = readFileSync23(misePath, "utf8");
|
|
16073
16514
|
if (content.includes(_WireMiseAgentHooks.MARKER)) {
|
|
16074
16515
|
return { success: true, message: this.formatMessage("\u2713 mise.toml already wired for agent-hooks") };
|
|
16075
16516
|
}
|
|
@@ -16153,7 +16594,7 @@ ${leaveBlock}`);
|
|
|
16153
16594
|
if (!verdict.ok) {
|
|
16154
16595
|
return { success: false, message: `\u26A0\uFE0F mise.toml was not rewritten: ${verdict.reason}. Add the agent-hooks tasks by hand.` };
|
|
16155
16596
|
}
|
|
16156
|
-
if (!this.context.dryRun)
|
|
16597
|
+
if (!this.context.dryRun) writeFileSync14(misePath, content);
|
|
16157
16598
|
if (wiredHooks) {
|
|
16158
16599
|
return { success: true, message: this.formatMessage("\u2705 Wired mise.toml ([hooks] enter/leave + tasks)") };
|
|
16159
16600
|
}
|
|
@@ -16284,7 +16725,7 @@ if __name__ == "__main__":
|
|
|
16284
16725
|
|
|
16285
16726
|
// src/commands/AddMiseCodegraphScript.ts
|
|
16286
16727
|
import { chmodSync as chmodSync7 } from "fs";
|
|
16287
|
-
import { join as
|
|
16728
|
+
import { join as join27 } from "path";
|
|
16288
16729
|
var AddMiseCodegraphScript = class extends Command {
|
|
16289
16730
|
async invoke() {
|
|
16290
16731
|
const filePath = ".mise/scripts/codegraph.sh";
|
|
@@ -16338,7 +16779,7 @@ fi
|
|
|
16338
16779
|
`;
|
|
16339
16780
|
this.writeFile(filePath, content);
|
|
16340
16781
|
if (!this.context.dryRun) {
|
|
16341
|
-
chmodSync7(
|
|
16782
|
+
chmodSync7(join27(this.context.targetDir, filePath), 493);
|
|
16342
16783
|
}
|
|
16343
16784
|
return {
|
|
16344
16785
|
success: true,
|
|
@@ -16376,12 +16817,12 @@ SECRET_KEY=""
|
|
|
16376
16817
|
// src/parity/index.ts
|
|
16377
16818
|
import { existsSync as existsSync20 } from "node:fs";
|
|
16378
16819
|
import { homedir as homedir9 } from "node:os";
|
|
16379
|
-
import { dirname as dirname19, join as
|
|
16820
|
+
import { dirname as dirname19, join as join28, resolve as resolve19 } from "node:path";
|
|
16380
16821
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
16381
16822
|
function resolvePjanglerRoot2() {
|
|
16382
16823
|
let dir = dirname19(fileURLToPath5(import.meta.url));
|
|
16383
16824
|
while (dir !== dirname19(dir)) {
|
|
16384
|
-
if (existsSync20(
|
|
16825
|
+
if (existsSync20(join28(dir, "package.json")) && existsSync20(join28(dir, "templates", "commonproject", "copier.yml"))) return dir;
|
|
16385
16826
|
dir = dirname19(dir);
|
|
16386
16827
|
}
|
|
16387
16828
|
return resolve19(process.cwd());
|
|
@@ -16556,13 +16997,13 @@ init_version();
|
|
|
16556
16997
|
// src/project/info.ts
|
|
16557
16998
|
init_boardUrl();
|
|
16558
16999
|
init_project();
|
|
16559
|
-
import { readFileSync as
|
|
16560
|
-
import { join as
|
|
17000
|
+
import { readFileSync as readFileSync24 } from "node:fs";
|
|
17001
|
+
import { join as join29, resolve as resolve20 } from "node:path";
|
|
16561
17002
|
function readManifest2(root) {
|
|
16562
|
-
const path =
|
|
17003
|
+
const path = join29(root, ".project.json");
|
|
16563
17004
|
let manifest;
|
|
16564
17005
|
try {
|
|
16565
|
-
const value = JSON.parse(
|
|
17006
|
+
const value = JSON.parse(readFileSync24(path, "utf8"));
|
|
16566
17007
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("expected a JSON object");
|
|
16567
17008
|
manifest = value;
|
|
16568
17009
|
} catch (error) {
|
|
@@ -16603,7 +17044,7 @@ function readProjectInfo(input = {}) {
|
|
|
16603
17044
|
ok: true,
|
|
16604
17045
|
project_id: projectId,
|
|
16605
17046
|
repo_path: resolve20(root),
|
|
16606
|
-
manifest_path:
|
|
17047
|
+
manifest_path: join29(resolve20(root), ".project.json"),
|
|
16607
17048
|
manifest,
|
|
16608
17049
|
registry: { location, status: "indexed" }
|
|
16609
17050
|
};
|
|
@@ -16631,14 +17072,14 @@ function readProjectInfo(input = {}) {
|
|
|
16631
17072
|
}
|
|
16632
17073
|
|
|
16633
17074
|
// src/describe/index.ts
|
|
16634
|
-
import { existsSync as existsSync21, readFileSync as
|
|
16635
|
-
import { join as
|
|
17075
|
+
import { existsSync as existsSync21, readFileSync as readFileSync25, readdirSync as readdirSync12, statSync as statSync7 } from "node:fs";
|
|
17076
|
+
import { join as join31, resolve as resolve21 } from "node:path";
|
|
16636
17077
|
init_project();
|
|
16637
17078
|
|
|
16638
17079
|
// src/describe/activity.ts
|
|
16639
17080
|
import { spawn as spawn2, spawnSync as spawnSync14 } from "node:child_process";
|
|
16640
17081
|
import { statSync as statSync6 } from "node:fs";
|
|
16641
|
-
import { join as
|
|
17082
|
+
import { join as join30 } from "node:path";
|
|
16642
17083
|
var ACTIVE_WINDOW_SECONDS = 24 * 60 * 60;
|
|
16643
17084
|
var MAX_DIRTY_STATS = 500;
|
|
16644
17085
|
var GIT_TIMEOUT_MS = 5e3;
|
|
@@ -16767,7 +17208,7 @@ function uncommittedSource(repo, paths) {
|
|
|
16767
17208
|
let newest = 0;
|
|
16768
17209
|
for (const path of paths.slice(0, MAX_DIRTY_STATS)) {
|
|
16769
17210
|
try {
|
|
16770
|
-
const mtime = Math.floor(statSync6(
|
|
17211
|
+
const mtime = Math.floor(statSync6(join30(repo, path)).mtimeMs / 1e3);
|
|
16771
17212
|
if (mtime > newest) newest = mtime;
|
|
16772
17213
|
} catch {
|
|
16773
17214
|
}
|
|
@@ -16876,7 +17317,7 @@ var CONFIG_FILES = [
|
|
|
16876
17317
|
];
|
|
16877
17318
|
function readJson(path) {
|
|
16878
17319
|
try {
|
|
16879
|
-
const parsed = JSON.parse(
|
|
17320
|
+
const parsed = JSON.parse(readFileSync25(path, "utf8"));
|
|
16880
17321
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
16881
17322
|
} catch {
|
|
16882
17323
|
return void 0;
|
|
@@ -16898,23 +17339,23 @@ function describeType(repo) {
|
|
|
16898
17339
|
const evidence = [];
|
|
16899
17340
|
const note = (signal, file) => evidence.push(`${signal} (${file})`);
|
|
16900
17341
|
for (const marker of LANGUAGE_MARKERS) {
|
|
16901
|
-
if (!existsSync21(
|
|
17342
|
+
if (!existsSync21(join31(repo, marker.file))) continue;
|
|
16902
17343
|
if (!languages.includes(marker.language)) {
|
|
16903
17344
|
languages.push(marker.language);
|
|
16904
17345
|
note(marker.language, marker.file);
|
|
16905
17346
|
}
|
|
16906
17347
|
}
|
|
16907
17348
|
try {
|
|
16908
|
-
const dotnet =
|
|
17349
|
+
const dotnet = readdirSync12(repo).find((entry) => entry.endsWith(".csproj") || entry.endsWith(".sln"));
|
|
16909
17350
|
if (dotnet && !languages.includes("dotnet")) {
|
|
16910
17351
|
languages.push("dotnet");
|
|
16911
17352
|
note("dotnet", dotnet);
|
|
16912
17353
|
}
|
|
16913
17354
|
} catch {
|
|
16914
17355
|
}
|
|
16915
|
-
const pkg = readJson(
|
|
17356
|
+
const pkg = readJson(join31(repo, "package.json"));
|
|
16916
17357
|
if (pkg) {
|
|
16917
|
-
if (existsSync21(
|
|
17358
|
+
if (existsSync21(join31(repo, "tsconfig.json"))) {
|
|
16918
17359
|
const index = languages.indexOf("javascript");
|
|
16919
17360
|
if (index >= 0) languages.splice(index, 1);
|
|
16920
17361
|
if (!languages.includes("typescript")) {
|
|
@@ -16948,14 +17389,14 @@ function describeType(repo) {
|
|
|
16948
17389
|
["hermes-fleet-host", "agents/hermes"]
|
|
16949
17390
|
];
|
|
16950
17391
|
for (const [role, marker] of roleMarkers) {
|
|
16951
|
-
if (!existsSync21(
|
|
17392
|
+
if (!existsSync21(join31(repo, marker))) continue;
|
|
16952
17393
|
roles.push(role);
|
|
16953
17394
|
note(role, marker);
|
|
16954
17395
|
}
|
|
16955
17396
|
return { primaryLanguage: languages[0], languages, roles, evidence };
|
|
16956
17397
|
}
|
|
16957
17398
|
function describeIdentity(repo, registryPath) {
|
|
16958
|
-
const manifestPath =
|
|
17399
|
+
const manifestPath = join31(repo, ".project.json");
|
|
16959
17400
|
const manifest = readJson(manifestPath);
|
|
16960
17401
|
const drift = [];
|
|
16961
17402
|
let record3;
|
|
@@ -17048,12 +17489,12 @@ function describeSubsystems(repo, findings) {
|
|
|
17048
17489
|
const markers = SUBSYSTEM_MARKERS[metadata.id] ?? [];
|
|
17049
17490
|
const evidence = metadata.id === "notebook" ? (() => {
|
|
17050
17491
|
try {
|
|
17051
|
-
const manifest = JSON.parse(
|
|
17492
|
+
const manifest = JSON.parse(readFileSync25(join31(repo, ".project.json"), "utf8"));
|
|
17052
17493
|
return manifest.notebook && typeof manifest.notebook === "object" ? [".project.json#notebook"] : [];
|
|
17053
17494
|
} catch {
|
|
17054
17495
|
return [];
|
|
17055
17496
|
}
|
|
17056
|
-
})() : markers.filter((marker) => existsSync21(
|
|
17497
|
+
})() : markers.filter((marker) => existsSync21(join31(repo, marker)));
|
|
17057
17498
|
const rules = (byRecipe.get(metadata.id) ?? []).map((finding2) => ({
|
|
17058
17499
|
id: finding2.id,
|
|
17059
17500
|
title: finding2.title,
|
|
@@ -17072,7 +17513,7 @@ function describeNotebook(repo, registryPath) {
|
|
|
17072
17513
|
try {
|
|
17073
17514
|
const registry = loadProjectRegistry(registryPath);
|
|
17074
17515
|
const project = Object.values(registry.projects).find((entry) => resolve21(entry.repo_path) === resolve21(repo));
|
|
17075
|
-
const manifest = existsSync21(
|
|
17516
|
+
const manifest = existsSync21(join31(repo, ".project.json")) ? JSON.parse(readFileSync25(join31(repo, ".project.json"), "utf8")) : void 0;
|
|
17076
17517
|
const declared = Boolean(project?.notebook || manifest?.notebook && typeof manifest.notebook === "object");
|
|
17077
17518
|
if (!declared) return { declared: false, bindingState: null, notebookId: null, overviewNoteId: null, health: null, remoteCheck: "skip", captureAdmission: null };
|
|
17078
17519
|
const config = loadEffectiveNotebookConfig(repo, registryPath);
|
|
@@ -17090,7 +17531,7 @@ function describeNotebook(repo, registryPath) {
|
|
|
17090
17531
|
}
|
|
17091
17532
|
}
|
|
17092
17533
|
function describeConfigFiles(repo) {
|
|
17093
|
-
return CONFIG_FILES.filter((spec) => existsSync21(
|
|
17534
|
+
return CONFIG_FILES.filter((spec) => existsSync21(join31(repo, spec.path))).map((spec) => ({ path: spec.path, purpose: spec.purpose, subsystem: spec.subsystem }));
|
|
17094
17535
|
}
|
|
17095
17536
|
function describeNextSteps(description, findings) {
|
|
17096
17537
|
const steps = [];
|
|
@@ -17369,7 +17810,7 @@ function resolveTargetDir(targetDir) {
|
|
|
17369
17810
|
function resolvePjanglerRoot3() {
|
|
17370
17811
|
let dir = dirname20(fileURLToPath6(import.meta.url));
|
|
17371
17812
|
while (dir !== dirname20(dir)) {
|
|
17372
|
-
if (existsSync22(
|
|
17813
|
+
if (existsSync22(join32(dir, "package.json")) && existsSync22(join32(dir, "templates", "commonproject", "copier.yml"))) {
|
|
17373
17814
|
return dir;
|
|
17374
17815
|
}
|
|
17375
17816
|
dir = dirname20(dir);
|
|
@@ -17669,7 +18110,7 @@ server.registerTool(
|
|
|
17669
18110
|
if (!existsSync22(parentDir) || !statSync8(parentDir).isDirectory()) throw new Error(`Parent directory does not exist: ${parentDir}`);
|
|
17670
18111
|
const targetDir = resolveContainedPath(
|
|
17671
18112
|
parentDir,
|
|
17672
|
-
explicitTargetDir ??
|
|
18113
|
+
explicitTargetDir ?? join32(parentDir, projectSlug),
|
|
17673
18114
|
"Bootstrap target"
|
|
17674
18115
|
);
|
|
17675
18116
|
const overwrite = input.overwrite ?? input.force ?? false;
|
|
@@ -17792,7 +18233,7 @@ server.registerTool(
|
|
|
17792
18233
|
registryPath: input.registryPath,
|
|
17793
18234
|
force: input.force ?? false,
|
|
17794
18235
|
overwrite: input.force ?? false,
|
|
17795
|
-
scaffold: !(input.targetDir && existsSync22(
|
|
18236
|
+
scaffold: !(input.targetDir && existsSync22(join32(resolve22(input.targetDir), ".git")))
|
|
17796
18237
|
});
|
|
17797
18238
|
if (!input.apply) return asText(publicCompositeProjectResponse(publicProjectPlan(plan), plan));
|
|
17798
18239
|
const preflight = await preflightProjectApply(plan, resolvePjanglerRoot3());
|