@delorenj/pjangler 1.5.49 → 1.5.51
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 +1105 -637
- package/dist/mcp-server.js +981 -522
- package/package.json +3 -2
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,21 +1280,26 @@ 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
|
+
function providerBoardName(provider, name) {
|
|
1299
|
+
if (provider !== "plane") return name;
|
|
1300
|
+
const cleaned = name.replace(/[^\p{L}\p{N}_\s]+/gu, " ").replace(/\s+/g, " ").trim();
|
|
1301
|
+
return cleaned || name;
|
|
1302
|
+
}
|
|
1298
1303
|
function provisionTicketProviderBoard(action, env2 = process.env) {
|
|
1299
1304
|
const provider = action.provider;
|
|
1300
1305
|
const groups = ticketProviderKeyGroups(provider, action.workspace);
|
|
@@ -1310,7 +1315,7 @@ function provisionTicketProviderBoard(action, env2 = process.env) {
|
|
|
1310
1315
|
ok: true,
|
|
1311
1316
|
skipped: true,
|
|
1312
1317
|
logs: [
|
|
1313
|
-
`ticket-provider: ${names} not set; skipping ${provider} board creation (state stays "planned"). Set it in the environment, ${
|
|
1318
|
+
`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
1319
|
]
|
|
1315
1320
|
};
|
|
1316
1321
|
}
|
|
@@ -1324,12 +1329,12 @@ function provisionTicketProviderBoard(action, env2 = process.env) {
|
|
|
1324
1329
|
};
|
|
1325
1330
|
}
|
|
1326
1331
|
const redact = (text) => Object.values(values).reduce((acc, secret) => secret ? acc.split(secret).join("***") : acc, text);
|
|
1327
|
-
const staging =
|
|
1332
|
+
const staging = mkdtempSync3(join17(tmpdir3(), "pjangler-tp-"));
|
|
1328
1333
|
try {
|
|
1329
|
-
const providersDir =
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1334
|
+
const providersDir = join17(staging, ".tp", "adapters");
|
|
1335
|
+
mkdirSync8(providersDir, { recursive: true });
|
|
1336
|
+
writeFileSync10(
|
|
1337
|
+
join17(staging, ".project.json"),
|
|
1333
1338
|
`${JSON.stringify(
|
|
1334
1339
|
{
|
|
1335
1340
|
project_name: action.boardName,
|
|
@@ -1348,7 +1353,7 @@ function provisionTicketProviderBoard(action, env2 = process.env) {
|
|
|
1348
1353
|
`,
|
|
1349
1354
|
"utf8"
|
|
1350
1355
|
);
|
|
1351
|
-
const staged =
|
|
1356
|
+
const staged = join17(providersDir, `${provider}.sh`);
|
|
1352
1357
|
copyFileSync2(adapter, staged);
|
|
1353
1358
|
const childEnv = { ...env2, ...values, TICKET_PROVIDER: provider };
|
|
1354
1359
|
if (provider === "plane" && action.workspace) childEnv.PLANE_WORKSPACE = action.workspace;
|
|
@@ -1437,7 +1442,7 @@ function prospectiveRealPath(path) {
|
|
|
1437
1442
|
while (!existsSync11(cursor)) {
|
|
1438
1443
|
const parent = dirname13(cursor);
|
|
1439
1444
|
if (parent === cursor) return resolve10(path);
|
|
1440
|
-
suffix.unshift(
|
|
1445
|
+
suffix.unshift(basename8(cursor));
|
|
1441
1446
|
cursor = parent;
|
|
1442
1447
|
}
|
|
1443
1448
|
return resolve10(realpathSync6(cursor), ...suffix);
|
|
@@ -1464,7 +1469,7 @@ function resolveAgentHooksLayer2(input, env2 = process.env) {
|
|
|
1464
1469
|
const override = env2.PJ_AGENT_HOOKS_LAYER;
|
|
1465
1470
|
if (override === "0" || override === "false") return false;
|
|
1466
1471
|
if (override === "1" || override === "true") return true;
|
|
1467
|
-
return !existsSync11(
|
|
1472
|
+
return !existsSync11(join17(homedir5(), ".agents", "hooks"));
|
|
1468
1473
|
}
|
|
1469
1474
|
function jsonStable(value) {
|
|
1470
1475
|
return JSON.stringify(value, (_key, item) => isRecord2(item) ? Object.fromEntries(Object.keys(item).sort().map((key) => [key, item[key]])) : item);
|
|
@@ -1497,10 +1502,10 @@ function resolveSourceSkillPath(sourceSkill, env2 = process.env) {
|
|
|
1497
1502
|
const expanded = expandHome(sourceSkill);
|
|
1498
1503
|
const direct = resolve10(expanded);
|
|
1499
1504
|
if (existsSync11(direct)) return direct;
|
|
1500
|
-
const name =
|
|
1505
|
+
const name = basename8(sourceSkill);
|
|
1501
1506
|
const roots = sourceSkillRoots(env2);
|
|
1502
1507
|
for (const root of roots) {
|
|
1503
|
-
const candidate =
|
|
1508
|
+
const candidate = join17(root, name);
|
|
1504
1509
|
if (existsSync11(candidate)) return candidate;
|
|
1505
1510
|
}
|
|
1506
1511
|
const searched = roots.length ? ` Searched roots: ${roots.join(", ")}.` : "";
|
|
@@ -1510,8 +1515,8 @@ function resolveSourceSkillPath(sourceSkill, env2 = process.env) {
|
|
|
1510
1515
|
function planProjectInit(input) {
|
|
1511
1516
|
if (!input.name.trim()) throw new Error("Project name is required");
|
|
1512
1517
|
const targetDir = resolve10(input.targetDir ?? defaultProjectTargetDir(input.name, input.cwd));
|
|
1513
|
-
const localManifestPath =
|
|
1514
|
-
const localManifestContent = existsSync11(localManifestPath) ?
|
|
1518
|
+
const localManifestPath = join17(targetDir, ".project.json");
|
|
1519
|
+
const localManifestContent = existsSync11(localManifestPath) ? readFileSync15(localManifestPath, "utf8") : void 0;
|
|
1515
1520
|
const localManifest = localManifestContent === void 0 ? void 0 : JSON.parse(localManifestContent);
|
|
1516
1521
|
const localId = localManifest?.project_id ?? localManifest?.project_slug;
|
|
1517
1522
|
const slug = normalizeProjectId(input.projectSlug ?? localId ?? slugifyProjectName(input.name));
|
|
@@ -1539,7 +1544,7 @@ function planProjectInit(input) {
|
|
|
1539
1544
|
const recordedTypeValue = existing?.template?.commonproject?.project_type;
|
|
1540
1545
|
const recordedType = recordedTypeValue === void 0 ? void 0 : normalizeProjectType(recordedTypeValue, `Recorded project type of ${slug}`);
|
|
1541
1546
|
const requestedType = input.projectType === void 0 ? void 0 : normalizeProjectType(input.projectType);
|
|
1542
|
-
const freshRender = scaffold && localManifest === void 0 && !existsSync11(
|
|
1547
|
+
const freshRender = scaffold && localManifest === void 0 && !existsSync11(join17(targetDir, ".copier-answers.yml"));
|
|
1543
1548
|
if (requestedType && !freshRender && requestedType !== (recordedType ?? "base")) {
|
|
1544
1549
|
const adoptingPlugin = !scaffold && recordedType === void 0 && requestedType === "obsidian-plugin" && looksLikeObsidianPlugin(targetDir);
|
|
1545
1550
|
if (!adoptingPlugin) {
|
|
@@ -1637,7 +1642,7 @@ function planProjectInit(input) {
|
|
|
1637
1642
|
}));
|
|
1638
1643
|
}
|
|
1639
1644
|
actions.push(
|
|
1640
|
-
{ kind: "project.write-manifest", path:
|
|
1645
|
+
{ kind: "project.write-manifest", path: join17(targetDir, ".project.json"), manifest, expectedContent: localManifestContent },
|
|
1641
1646
|
{
|
|
1642
1647
|
kind: "ticket-provider.create-or-link",
|
|
1643
1648
|
enabled: boardEnabled,
|
|
@@ -1646,11 +1651,11 @@ function planProjectInit(input) {
|
|
|
1646
1651
|
workspace: project.ticket_provider.workspace ?? "33god",
|
|
1647
1652
|
identifier,
|
|
1648
1653
|
repoPath: targetDir,
|
|
1649
|
-
boardName: project.name,
|
|
1654
|
+
boardName: providerBoardName(project.ticket_provider.type, project.name),
|
|
1650
1655
|
description: project.description || `Ticket board for ${project.slug}`,
|
|
1651
1656
|
boardId: project.ticket_provider.board_id ?? "",
|
|
1652
1657
|
state: project.ticket_provider.board_id ? "linked" : "planned",
|
|
1653
|
-
reason: skipPlane ? "ticket-provider action disabled by skipPlane=true (--skip-board)" : project.ticket_provider.board_id ? "board already linked; no provider call" : !provisionTicketBoard ? "ticket-provider action requires explicit provisionTicketBoard=true" : `create or link the ${project.ticket_provider.type} board "${project.name}" (${identifier}) via the ticket-provider adapter`
|
|
1658
|
+
reason: skipPlane ? "ticket-provider action disabled by skipPlane=true (--skip-board)" : project.ticket_provider.board_id ? "board already linked; no provider call" : !provisionTicketBoard ? "ticket-provider action requires explicit provisionTicketBoard=true" : `create or link the ${project.ticket_provider.type} board "${providerBoardName(project.ticket_provider.type, project.name)}" (${identifier}) via the ticket-provider adapter`
|
|
1654
1659
|
}
|
|
1655
1660
|
);
|
|
1656
1661
|
return {
|
|
@@ -1692,12 +1697,12 @@ function linkTicketProviderBoard(plan, action, boardId, identifier, now = /* @__
|
|
|
1692
1697
|
plan.manifest.ticket_provider = manifestProvider;
|
|
1693
1698
|
action.boardId = boardId;
|
|
1694
1699
|
action.state = manifestProvider.state;
|
|
1695
|
-
const manifestPath =
|
|
1700
|
+
const manifestPath = join17(action.repoPath, ".project.json");
|
|
1696
1701
|
let next;
|
|
1697
1702
|
if (existsSync11(manifestPath)) {
|
|
1698
1703
|
let existing = {};
|
|
1699
1704
|
try {
|
|
1700
|
-
const parsed = JSON.parse(
|
|
1705
|
+
const parsed = JSON.parse(readFileSync15(manifestPath, "utf8"));
|
|
1701
1706
|
if (isRecord2(parsed)) existing = parsed;
|
|
1702
1707
|
} catch {
|
|
1703
1708
|
existing = {};
|
|
@@ -1705,13 +1710,13 @@ function linkTicketProviderBoard(plan, action, boardId, identifier, now = /* @__
|
|
|
1705
1710
|
const existingProvider = isRecord2(existing.ticket_provider) ? existing.ticket_provider : {};
|
|
1706
1711
|
next = { ...existing, ticket_provider: { ...existingProvider, ...manifestProvider } };
|
|
1707
1712
|
} else {
|
|
1708
|
-
|
|
1713
|
+
mkdirSync8(dirname13(manifestPath), { recursive: true });
|
|
1709
1714
|
next = plan.manifest;
|
|
1710
1715
|
}
|
|
1711
1716
|
const text = `${JSON.stringify(next, null, 2)}
|
|
1712
1717
|
`;
|
|
1713
|
-
if (!existsSync11(manifestPath) ||
|
|
1714
|
-
|
|
1718
|
+
if (!existsSync11(manifestPath) || readFileSync15(manifestPath, "utf8") !== text) {
|
|
1719
|
+
writeFileSync10(manifestPath, text, "utf8");
|
|
1715
1720
|
return [manifestPath];
|
|
1716
1721
|
}
|
|
1717
1722
|
return [];
|
|
@@ -1739,7 +1744,7 @@ async function executeProjectInitPlan(plan, options = {}) {
|
|
|
1739
1744
|
logs.push(
|
|
1740
1745
|
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
1746
|
);
|
|
1742
|
-
|
|
1747
|
+
mkdirSync8(dirname13(action.targetDir), { recursive: true });
|
|
1743
1748
|
const before = snapshotTree(action.targetDir);
|
|
1744
1749
|
const copierExecutable = options.trustedCopier?.executable ?? action.command[0];
|
|
1745
1750
|
const copierEnv = { ...process.env };
|
|
@@ -1771,22 +1776,22 @@ async function executeProjectInitPlan(plan, options = {}) {
|
|
|
1771
1776
|
break;
|
|
1772
1777
|
}
|
|
1773
1778
|
const projectType = action.data.project_type ?? "base";
|
|
1774
|
-
const missing = (PROJECT_TYPE_SENTINELS[projectType] ?? []).filter((rel) => !existsSync11(
|
|
1779
|
+
const missing = (PROJECT_TYPE_SENTINELS[projectType] ?? []).filter((rel) => !existsSync11(join17(action.targetDir, rel)));
|
|
1775
1780
|
if (missing.length) {
|
|
1776
1781
|
errors.push(`copier rendered no ${projectType} project: ${missing.join(", ")} missing from ${action.targetDir}`);
|
|
1777
1782
|
break;
|
|
1778
1783
|
}
|
|
1779
1784
|
if (projectType !== "base") logs.push(`commonproject: rendered as ${projectType}`);
|
|
1780
1785
|
} else if (action.kind === "project.write-manifest") {
|
|
1781
|
-
if (action.expectedContent !== void 0 && (!existsSync11(action.path) ||
|
|
1786
|
+
if (action.expectedContent !== void 0 && (!existsSync11(action.path) || readFileSync15(action.path, "utf8") !== action.expectedContent)) {
|
|
1782
1787
|
errors.push(`Manifest changed after planning: ${action.path}; re-plan before applying`);
|
|
1783
1788
|
break;
|
|
1784
1789
|
}
|
|
1785
|
-
|
|
1790
|
+
mkdirSync8(dirname13(action.path), { recursive: true });
|
|
1786
1791
|
let value = action.manifest;
|
|
1787
1792
|
if (existsSync11(action.path)) {
|
|
1788
1793
|
try {
|
|
1789
|
-
const currentValue = JSON.parse(
|
|
1794
|
+
const currentValue = JSON.parse(readFileSync15(action.path, "utf8"));
|
|
1790
1795
|
if (isRecord2(currentValue)) {
|
|
1791
1796
|
const currentNotebook = isRecord2(currentValue.notebook) ? currentValue.notebook : {};
|
|
1792
1797
|
const desiredNotebook = isRecord2(value.notebook) ? value.notebook : {};
|
|
@@ -1814,9 +1819,9 @@ async function executeProjectInitPlan(plan, options = {}) {
|
|
|
1814
1819
|
value.project_id = normalizeProjectId(value.project_id ?? action.manifest.project_id);
|
|
1815
1820
|
const next = `${JSON.stringify(value, null, 2)}
|
|
1816
1821
|
`;
|
|
1817
|
-
const current = existsSync11(action.path) ?
|
|
1822
|
+
const current = existsSync11(action.path) ? readFileSync15(action.path, "utf8") : void 0;
|
|
1818
1823
|
if (current !== next) {
|
|
1819
|
-
|
|
1824
|
+
writeFileSync10(action.path, next, "utf8");
|
|
1820
1825
|
changedFiles.push(action.path);
|
|
1821
1826
|
}
|
|
1822
1827
|
changedFiles.push(...synchronizeCopierIdentity(action.path, action.manifest));
|
|
@@ -1897,7 +1902,7 @@ function getProject(registry, slug) {
|
|
|
1897
1902
|
return project;
|
|
1898
1903
|
}
|
|
1899
1904
|
function buildCommonProjectCopierAction(input) {
|
|
1900
|
-
const templateDir =
|
|
1905
|
+
const templateDir = join17(input.pjanglerRoot, "templates", "commonproject");
|
|
1901
1906
|
const data = {
|
|
1902
1907
|
project_name: input.projectName,
|
|
1903
1908
|
project_description: input.projectDescription ?? "",
|
|
@@ -1928,7 +1933,7 @@ function buildCommonProjectCopierAction(input) {
|
|
|
1928
1933
|
function resolvePjanglerRoot() {
|
|
1929
1934
|
let dir = dirname13(fileURLToPath2(import.meta.url));
|
|
1930
1935
|
while (dir !== dirname13(dir)) {
|
|
1931
|
-
if (existsSync11(
|
|
1936
|
+
if (existsSync11(join17(dir, "package.json")) && existsSync11(join17(dir, "templates", "commonproject", "copier.yml"))) return dir;
|
|
1932
1937
|
dir = dirname13(dir);
|
|
1933
1938
|
}
|
|
1934
1939
|
return resolve10(process.cwd());
|
|
@@ -2005,7 +2010,7 @@ function validateProjectRecord(project, key) {
|
|
|
2005
2010
|
}
|
|
2006
2011
|
function expandHome(path) {
|
|
2007
2012
|
if (path === "~") return homedir5();
|
|
2008
|
-
if (path.startsWith("~/")) return
|
|
2013
|
+
if (path.startsWith("~/")) return join17(homedir5(), path.slice(2));
|
|
2009
2014
|
return path;
|
|
2010
2015
|
}
|
|
2011
2016
|
function isRecord2(value) {
|
|
@@ -2032,7 +2037,7 @@ var init_project = __esm({
|
|
|
2032
2037
|
"obsidian-plugin": ["typescript", "javascript"]
|
|
2033
2038
|
};
|
|
2034
2039
|
PROJECT_TYPE_SENTINELS = {
|
|
2035
|
-
"obsidian-plugin": ["manifest.json",
|
|
2040
|
+
"obsidian-plugin": ["manifest.json", join17("src", "main.ts")]
|
|
2036
2041
|
};
|
|
2037
2042
|
BOARD_URL_DEPRECATION_WARNING = "boardUrl is deprecated and ignored; board URLs are derived at runtime and are never persisted.";
|
|
2038
2043
|
PROJECT_IDENTIFIER_SOURCES = ["provider", "proposed"];
|
|
@@ -2041,8 +2046,8 @@ var init_project = __esm({
|
|
|
2041
2046
|
IDENTIFIER_REPAIR_COMMAND = "pj project identity --all --apply";
|
|
2042
2047
|
DEFAULT_SOURCE_SKILL_ROOTS = [
|
|
2043
2048
|
"/home/delorenj/code/skillex/all-skills",
|
|
2044
|
-
|
|
2045
|
-
|
|
2049
|
+
join17(homedir5(), ".agents", "skills"),
|
|
2050
|
+
join17(homedir5(), ".codex", "skills")
|
|
2046
2051
|
];
|
|
2047
2052
|
PROJECT_REGISTRY_OWNED_KEYS = [
|
|
2048
2053
|
"name",
|
|
@@ -2074,7 +2079,7 @@ var init_project = __esm({
|
|
|
2074
2079
|
import { randomUUID } from "node:crypto";
|
|
2075
2080
|
import { isDeepStrictEqual } from "node:util";
|
|
2076
2081
|
import { isIP as isIP2 } from "node:net";
|
|
2077
|
-
import { closeSync as
|
|
2082
|
+
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
2083
|
import { dirname as dirname14, resolve as resolve11 } from "node:path";
|
|
2079
2084
|
function isRecord3(value) {
|
|
2080
2085
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
@@ -2088,7 +2093,7 @@ function readManifest(repoPath) {
|
|
|
2088
2093
|
if (!existsSync12(path)) return null;
|
|
2089
2094
|
let parsed;
|
|
2090
2095
|
try {
|
|
2091
|
-
parsed = JSON.parse(
|
|
2096
|
+
parsed = JSON.parse(readFileSync16(path, "utf8"));
|
|
2092
2097
|
} catch {
|
|
2093
2098
|
throw new NotebookError("INVALID_INPUT", `${path} is not valid JSON`);
|
|
2094
2099
|
}
|
|
@@ -2330,7 +2335,7 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
|
|
|
2330
2335
|
const changed = [];
|
|
2331
2336
|
const manifestPath = resolve11(resolved.project.repo_path, ".project.json");
|
|
2332
2337
|
if (isRegistryServiceLocation(resolved.registry_path)) {
|
|
2333
|
-
const before = existsSync12(manifestPath) ?
|
|
2338
|
+
const before = existsSync12(manifestPath) ? readFileSync16(manifestPath, "utf8") : null;
|
|
2334
2339
|
const declaredPolicy = resolved.manifest?.notebook?.policy ?? {};
|
|
2335
2340
|
const nextProject = {
|
|
2336
2341
|
...resolved.project,
|
|
@@ -2342,10 +2347,10 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
|
|
|
2342
2347
|
resolved.registry = loadProjectRegistry(resolved.registry_path);
|
|
2343
2348
|
resolved.project = registryProjectBySlug(resolved.registry, nextProject.slug);
|
|
2344
2349
|
resolved.manifest = readManifest(nextProject.repo_path);
|
|
2345
|
-
const after = existsSync12(manifestPath) ?
|
|
2350
|
+
const after = existsSync12(manifestPath) ? readFileSync16(manifestPath, "utf8") : null;
|
|
2346
2351
|
return before === after ? [] : [manifestPath];
|
|
2347
2352
|
}
|
|
2348
|
-
const manifestRaw = existsSync12(manifestPath) ? JSON.parse(
|
|
2353
|
+
const manifestRaw = existsSync12(manifestPath) ? JSON.parse(readFileSync16(manifestPath, "utf8")) : {};
|
|
2349
2354
|
if (!isRecord3(manifestRaw)) throw new NotebookError("INVALID_INPUT", `${manifestPath} must contain a JSON object`);
|
|
2350
2355
|
validateManifestNotebookSurface(manifestRaw.notebook);
|
|
2351
2356
|
const manifestNotebook2 = isRecord3(manifestRaw.notebook) ? manifestRaw.notebook : {};
|
|
@@ -2378,29 +2383,29 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
|
|
|
2378
2383
|
};
|
|
2379
2384
|
const manifestText = `${JSON.stringify(manifestNext, null, 2)}
|
|
2380
2385
|
`;
|
|
2381
|
-
if (!existsSync12(manifestPath) ||
|
|
2386
|
+
if (!existsSync12(manifestPath) || readFileSync16(manifestPath, "utf8") !== manifestText) {
|
|
2382
2387
|
let mode = 420;
|
|
2383
2388
|
if (existsSync12(manifestPath)) {
|
|
2384
|
-
const current2 =
|
|
2389
|
+
const current2 = lstatSync13(manifestPath);
|
|
2385
2390
|
if (!current2.isFile() || current2.isSymbolicLink()) throw new NotebookError("CONFLICT", `${manifestPath} must be a regular non-symlink file`);
|
|
2386
2391
|
mode = current2.mode & 511;
|
|
2387
2392
|
}
|
|
2388
2393
|
const temp = resolve11(dirname14(manifestPath), `.${process.pid}.${randomUUID()}.project.json.tmp`);
|
|
2389
|
-
const fd =
|
|
2394
|
+
const fd = openSync4(temp, "wx", mode);
|
|
2390
2395
|
try {
|
|
2391
|
-
|
|
2392
|
-
|
|
2396
|
+
writeFileSync11(fd, manifestText, "utf8");
|
|
2397
|
+
fchmodSync3(fd, mode);
|
|
2393
2398
|
fsyncSync2(fd);
|
|
2394
2399
|
} finally {
|
|
2395
|
-
|
|
2400
|
+
closeSync4(fd);
|
|
2396
2401
|
}
|
|
2397
2402
|
renameSync6(temp, manifestPath);
|
|
2398
2403
|
try {
|
|
2399
|
-
const directory =
|
|
2404
|
+
const directory = openSync4(dirname14(manifestPath), "r");
|
|
2400
2405
|
try {
|
|
2401
2406
|
fsyncSync2(directory);
|
|
2402
2407
|
} finally {
|
|
2403
|
-
|
|
2408
|
+
closeSync4(directory);
|
|
2404
2409
|
}
|
|
2405
2410
|
} catch {
|
|
2406
2411
|
}
|
|
@@ -2409,9 +2414,9 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
|
|
|
2409
2414
|
const current = resolved.project.notebook;
|
|
2410
2415
|
resolved.project.notebook = { ...isRecord3(current) ? current : { state: "planned" }, ...binding };
|
|
2411
2416
|
const fileRegistry = !isRegistryServiceLocation(resolved.registry_path);
|
|
2412
|
-
const registryBefore = fileRegistry && existsSync12(resolved.registry_path) ?
|
|
2417
|
+
const registryBefore = fileRegistry && existsSync12(resolved.registry_path) ? readFileSync16(resolved.registry_path, "utf8") : null;
|
|
2413
2418
|
saveProjectRegistry(resolved.registry, resolved.registry_path);
|
|
2414
|
-
const registryAfter = fileRegistry && existsSync12(resolved.registry_path) ?
|
|
2419
|
+
const registryAfter = fileRegistry && existsSync12(resolved.registry_path) ? readFileSync16(resolved.registry_path, "utf8") : null;
|
|
2415
2420
|
if (registryBefore !== registryAfter) changed.push(resolved.registry_path);
|
|
2416
2421
|
resolved.manifest = manifestNext;
|
|
2417
2422
|
return changed;
|
|
@@ -2444,7 +2449,7 @@ var init_config = __esm({
|
|
|
2444
2449
|
});
|
|
2445
2450
|
|
|
2446
2451
|
// src/notebook/notes.ts
|
|
2447
|
-
import { createHash as
|
|
2452
|
+
import { createHash as createHash10, randomUUID as randomUUID2 } from "node:crypto";
|
|
2448
2453
|
function canonicalJson2(value) {
|
|
2449
2454
|
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
2455
|
if (value === null || typeof value !== "object") {
|
|
@@ -2457,7 +2462,7 @@ function canonicalJson2(value) {
|
|
|
2457
2462
|
return `{${Object.keys(record3).filter((key) => record3[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson2(record3[key])}`).join(",")}}`;
|
|
2458
2463
|
}
|
|
2459
2464
|
function sha256Hex(value) {
|
|
2460
|
-
return
|
|
2465
|
+
return createHash10("sha256").update(value).digest("hex");
|
|
2461
2466
|
}
|
|
2462
2467
|
function base64UrlEncode(value) {
|
|
2463
2468
|
return Buffer.from(value, "utf8").toString("base64url");
|
|
@@ -2714,27 +2719,27 @@ var init_remote_mutation_schema = __esm({
|
|
|
2714
2719
|
});
|
|
2715
2720
|
|
|
2716
2721
|
// src/notebook/state.ts
|
|
2717
|
-
import { createHash as
|
|
2722
|
+
import { createHash as createHash11, randomUUID as randomUUID3 } from "node:crypto";
|
|
2718
2723
|
import {
|
|
2719
|
-
closeSync as
|
|
2720
|
-
constants as
|
|
2724
|
+
closeSync as closeSync5,
|
|
2725
|
+
constants as constants3,
|
|
2721
2726
|
existsSync as existsSync13,
|
|
2722
|
-
fchmodSync as
|
|
2723
|
-
fstatSync,
|
|
2727
|
+
fchmodSync as fchmodSync4,
|
|
2728
|
+
fstatSync as fstatSync2,
|
|
2724
2729
|
fsyncSync as fsyncSync3,
|
|
2725
|
-
lstatSync as
|
|
2726
|
-
mkdirSync as
|
|
2727
|
-
openSync as
|
|
2730
|
+
lstatSync as lstatSync14,
|
|
2731
|
+
mkdirSync as mkdirSync9,
|
|
2732
|
+
openSync as openSync5,
|
|
2728
2733
|
readSync,
|
|
2729
|
-
readdirSync as
|
|
2734
|
+
readdirSync as readdirSync10,
|
|
2730
2735
|
renameSync as renameSync7,
|
|
2731
|
-
unlinkSync as
|
|
2732
|
-
writeFileSync as
|
|
2736
|
+
unlinkSync as unlinkSync8,
|
|
2737
|
+
writeFileSync as writeFileSync12
|
|
2733
2738
|
} from "node:fs";
|
|
2734
2739
|
import { homedir as homedir6 } from "node:os";
|
|
2735
|
-
import { basename as
|
|
2740
|
+
import { basename as basename9, dirname as dirname15, join as join18, parse, relative as relative12, resolve as resolve12, sep as sep4 } from "node:path";
|
|
2736
2741
|
function notebookStateRoot(env2 = process.env) {
|
|
2737
|
-
const base = env2.XDG_STATE_HOME ||
|
|
2742
|
+
const base = env2.XDG_STATE_HOME || join18(env2.HOME || homedir6(), ".local", "state");
|
|
2738
2743
|
return resolve12(base, "pjangler", "notebook", NOTEBOOK_STATE_VERSION);
|
|
2739
2744
|
}
|
|
2740
2745
|
function assertDigest(value, label) {
|
|
@@ -2742,7 +2747,7 @@ function assertDigest(value, label) {
|
|
|
2742
2747
|
}
|
|
2743
2748
|
function projectStateDir(root, projectSlug) {
|
|
2744
2749
|
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
|
|
2750
|
+
return join18(resolve12(root), "projects", sha256Hex(`pjangler-project-state-v1\0${projectSlug}`));
|
|
2746
2751
|
}
|
|
2747
2752
|
function assertContained(root, candidate) {
|
|
2748
2753
|
const rel = relative12(resolve12(root), resolve12(candidate));
|
|
@@ -2755,33 +2760,33 @@ function openPinnedDirectory(path, root, create) {
|
|
|
2755
2760
|
assertContained(absoluteRoot, absolute);
|
|
2756
2761
|
if (!existsSync13("/proc/self/fd")) throw new NotebookError("INTERNAL_ERROR", "Descriptor-pinned Notebook state requires procfs");
|
|
2757
2762
|
const parsed = parse(absolute);
|
|
2758
|
-
let fd =
|
|
2763
|
+
let fd = openSync5(parsed.root, DIRECTORY_OPEN_FLAGS);
|
|
2759
2764
|
let cursor = parsed.root;
|
|
2760
2765
|
try {
|
|
2761
2766
|
for (const part of absolute.slice(parsed.root.length).split(sep4).filter(Boolean)) {
|
|
2762
2767
|
const child = `/proc/self/fd/${fd}/${part}`;
|
|
2763
|
-
cursor =
|
|
2768
|
+
cursor = join18(cursor, part);
|
|
2764
2769
|
let childFd;
|
|
2765
2770
|
try {
|
|
2766
|
-
childFd =
|
|
2771
|
+
childFd = openSync5(child, DIRECTORY_OPEN_FLAGS);
|
|
2767
2772
|
} catch (error) {
|
|
2768
2773
|
if (!create || error.code !== "ENOENT") throw error;
|
|
2769
2774
|
try {
|
|
2770
|
-
|
|
2775
|
+
mkdirSync9(child, { mode: 448 });
|
|
2771
2776
|
} catch (mkdirError) {
|
|
2772
2777
|
if (mkdirError.code !== "EEXIST") throw mkdirError;
|
|
2773
2778
|
}
|
|
2774
|
-
childFd =
|
|
2779
|
+
childFd = openSync5(child, DIRECTORY_OPEN_FLAGS);
|
|
2775
2780
|
}
|
|
2776
|
-
|
|
2781
|
+
closeSync5(fd);
|
|
2777
2782
|
fd = childFd;
|
|
2778
|
-
const stat =
|
|
2783
|
+
const stat = fstatSync2(fd);
|
|
2779
2784
|
if (!stat.isDirectory()) throw new NotebookError("INTERNAL_ERROR", "Notebook state path component is not a directory");
|
|
2780
2785
|
if (cursor === absoluteRoot || relative12(absoluteRoot, cursor).startsWith("..") === false) assertOwned(stat);
|
|
2781
2786
|
}
|
|
2782
2787
|
return fd;
|
|
2783
2788
|
} catch (error) {
|
|
2784
|
-
|
|
2789
|
+
closeSync5(fd);
|
|
2785
2790
|
if (error.code === "ELOOP" || error.code === "ENOTDIR") {
|
|
2786
2791
|
throw new NotebookError("INTERNAL_ERROR", "Notebook state path contains a symlink component");
|
|
2787
2792
|
}
|
|
@@ -2789,7 +2794,7 @@ function openPinnedDirectory(path, root, create) {
|
|
|
2789
2794
|
}
|
|
2790
2795
|
}
|
|
2791
2796
|
function pinnedLeaf(parentFd, path) {
|
|
2792
|
-
return `/proc/self/fd/${parentFd}/${
|
|
2797
|
+
return `/proc/self/fd/${parentFd}/${basename9(path)}`;
|
|
2793
2798
|
}
|
|
2794
2799
|
function assertOwned(stat) {
|
|
2795
2800
|
if (typeof process.getuid === "function" && stat.uid !== process.getuid()) {
|
|
@@ -2802,13 +2807,13 @@ function ensureDirectory(path, root) {
|
|
|
2802
2807
|
assertContained(absoluteRoot, absolute);
|
|
2803
2808
|
const fd = openPinnedDirectory(absolute, absoluteRoot, true);
|
|
2804
2809
|
try {
|
|
2805
|
-
const stat =
|
|
2810
|
+
const stat = fstatSync2(fd);
|
|
2806
2811
|
if (!stat.isDirectory()) throw new NotebookError("INTERNAL_ERROR", "Notebook state directory is not a real directory");
|
|
2807
2812
|
assertOwned(stat);
|
|
2808
|
-
|
|
2813
|
+
fchmodSync4(fd, 448);
|
|
2809
2814
|
fsyncSync3(fd);
|
|
2810
2815
|
} finally {
|
|
2811
|
-
|
|
2816
|
+
closeSync5(fd);
|
|
2812
2817
|
}
|
|
2813
2818
|
}
|
|
2814
2819
|
function notebookStatePaths(root, projectSlug) {
|
|
@@ -2817,18 +2822,18 @@ function notebookStatePaths(root, projectSlug) {
|
|
|
2817
2822
|
return {
|
|
2818
2823
|
root: absoluteRoot,
|
|
2819
2824
|
project,
|
|
2820
|
-
baselines:
|
|
2821
|
-
claims:
|
|
2822
|
-
receipts:
|
|
2823
|
-
refusals:
|
|
2824
|
-
journals:
|
|
2825
|
-
locks:
|
|
2825
|
+
baselines: join18(project, "baselines"),
|
|
2826
|
+
claims: join18(project, "claims"),
|
|
2827
|
+
receipts: join18(project, "receipts"),
|
|
2828
|
+
refusals: join18(project, "refusals"),
|
|
2829
|
+
journals: join18(project, "journals"),
|
|
2830
|
+
locks: join18(project, "locks")
|
|
2826
2831
|
};
|
|
2827
2832
|
}
|
|
2828
2833
|
function ensureNotebookState(root, projectSlug) {
|
|
2829
2834
|
const paths = notebookStatePaths(root, projectSlug);
|
|
2830
2835
|
const { root: absoluteRoot, project } = paths;
|
|
2831
|
-
for (const path of [absoluteRoot,
|
|
2836
|
+
for (const path of [absoluteRoot, join18(absoluteRoot, "projects"), project, paths.baselines, paths.claims, paths.receipts, paths.refusals, paths.journals, paths.locks]) {
|
|
2832
2837
|
ensureDirectory(path, absoluteRoot);
|
|
2833
2838
|
}
|
|
2834
2839
|
return paths;
|
|
@@ -2844,15 +2849,15 @@ function fsyncDirectory2(path) {
|
|
|
2844
2849
|
fsyncSync3(fd);
|
|
2845
2850
|
} catch {
|
|
2846
2851
|
} finally {
|
|
2847
|
-
if (fd !== void 0)
|
|
2852
|
+
if (fd !== void 0) closeSync5(fd);
|
|
2848
2853
|
}
|
|
2849
2854
|
}
|
|
2850
2855
|
function readStateDirectory(path, root) {
|
|
2851
2856
|
const fd = openPinnedDirectory(path, root, false);
|
|
2852
2857
|
try {
|
|
2853
|
-
return
|
|
2858
|
+
return readdirSync10(`/proc/self/fd/${fd}`, { withFileTypes: true });
|
|
2854
2859
|
} finally {
|
|
2855
|
-
|
|
2860
|
+
closeSync5(fd);
|
|
2856
2861
|
}
|
|
2857
2862
|
}
|
|
2858
2863
|
function readNotebookStateDirectory(path, root) {
|
|
@@ -2866,22 +2871,22 @@ function unlinkStateFile(path, root, allowMissing = false) {
|
|
|
2866
2871
|
let fileFd;
|
|
2867
2872
|
try {
|
|
2868
2873
|
try {
|
|
2869
|
-
fileFd =
|
|
2874
|
+
fileFd = openSync5(target, constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
|
|
2870
2875
|
} catch (error) {
|
|
2871
2876
|
if (allowMissing && error.code === "ENOENT") return false;
|
|
2872
2877
|
throw error;
|
|
2873
2878
|
}
|
|
2874
|
-
const stat =
|
|
2879
|
+
const stat = fstatSync2(fileFd);
|
|
2875
2880
|
if (!stat.isFile() || (stat.mode & 511) !== 384) {
|
|
2876
2881
|
throw new NotebookError("CONFLICT", "Refusing to remove a suspect Notebook state entry; run pj notebook audit --json");
|
|
2877
2882
|
}
|
|
2878
2883
|
assertOwned(stat);
|
|
2879
|
-
|
|
2884
|
+
unlinkSync8(target);
|
|
2880
2885
|
fsyncSync3(parentFd);
|
|
2881
2886
|
return true;
|
|
2882
2887
|
} finally {
|
|
2883
|
-
if (fileFd !== void 0)
|
|
2884
|
-
|
|
2888
|
+
if (fileFd !== void 0) closeSync5(fileFd);
|
|
2889
|
+
closeSync5(parentFd);
|
|
2885
2890
|
}
|
|
2886
2891
|
}
|
|
2887
2892
|
function renameStateFile(source, target, root) {
|
|
@@ -2891,12 +2896,12 @@ function renameStateFile(source, target, root) {
|
|
|
2891
2896
|
const targetPath = pinnedLeaf(parentFd, target);
|
|
2892
2897
|
let sourceFd;
|
|
2893
2898
|
try {
|
|
2894
|
-
sourceFd =
|
|
2895
|
-
const stat =
|
|
2899
|
+
sourceFd = openSync5(sourcePath, constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
|
|
2900
|
+
const stat = fstatSync2(sourceFd);
|
|
2896
2901
|
if (!stat.isFile() || (stat.mode & 511) !== 384) throw new NotebookError("CONFLICT", "Notebook state rename source has an integrity finding");
|
|
2897
2902
|
assertOwned(stat);
|
|
2898
2903
|
try {
|
|
2899
|
-
|
|
2904
|
+
lstatSync14(targetPath);
|
|
2900
2905
|
throw new NotebookError("CONFLICT", "Notebook state rename target already exists");
|
|
2901
2906
|
} catch (error) {
|
|
2902
2907
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -2904,8 +2909,8 @@ function renameStateFile(source, target, root) {
|
|
|
2904
2909
|
renameSync7(sourcePath, targetPath);
|
|
2905
2910
|
fsyncSync3(parentFd);
|
|
2906
2911
|
} finally {
|
|
2907
|
-
if (sourceFd !== void 0)
|
|
2908
|
-
|
|
2912
|
+
if (sourceFd !== void 0) closeSync5(sourceFd);
|
|
2913
|
+
closeSync5(parentFd);
|
|
2909
2914
|
}
|
|
2910
2915
|
}
|
|
2911
2916
|
function atomicWriteJson(path, value, root, afterParentPinned) {
|
|
@@ -2914,34 +2919,34 @@ function atomicWriteJson(path, value, root, afterParentPinned) {
|
|
|
2914
2919
|
const text = jsonLine(value);
|
|
2915
2920
|
const parentFd = openPinnedDirectory(dirname15(path), root, false);
|
|
2916
2921
|
const target = pinnedLeaf(parentFd, path);
|
|
2917
|
-
const tempName = `.${
|
|
2922
|
+
const tempName = `.${basename9(path)}.${process.pid}.${randomUUID3()}.tmp`;
|
|
2918
2923
|
const temp = `/proc/self/fd/${parentFd}/${tempName}`;
|
|
2919
2924
|
let existingIdentity = null;
|
|
2920
2925
|
try {
|
|
2921
2926
|
afterParentPinned?.();
|
|
2922
2927
|
try {
|
|
2923
|
-
const existingFd =
|
|
2928
|
+
const existingFd = openSync5(target, constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
|
|
2924
2929
|
try {
|
|
2925
|
-
const existing =
|
|
2930
|
+
const existing = fstatSync2(existingFd);
|
|
2926
2931
|
if (!existing.isFile()) throw new NotebookError("INTERNAL_ERROR", "Refusing to replace a non-regular Notebook state file");
|
|
2927
2932
|
assertOwned(existing);
|
|
2928
2933
|
if ((existing.mode & 511) !== 384) throw new NotebookError("CONFLICT", "Refusing to replace a Notebook state file with unsafe permissions");
|
|
2929
2934
|
existingIdentity = { dev: existing.dev, ino: existing.ino };
|
|
2930
2935
|
} finally {
|
|
2931
|
-
|
|
2936
|
+
closeSync5(existingFd);
|
|
2932
2937
|
}
|
|
2933
2938
|
} catch (error) {
|
|
2934
2939
|
if (error.code !== "ENOENT") throw error;
|
|
2935
2940
|
}
|
|
2936
|
-
const fd =
|
|
2941
|
+
const fd = openSync5(temp, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | (constants3.O_NOFOLLOW ?? 0), 384);
|
|
2937
2942
|
try {
|
|
2938
|
-
|
|
2943
|
+
writeFileSync12(fd, text, "utf8");
|
|
2939
2944
|
fsyncSync3(fd);
|
|
2940
2945
|
} finally {
|
|
2941
|
-
|
|
2946
|
+
closeSync5(fd);
|
|
2942
2947
|
}
|
|
2943
2948
|
try {
|
|
2944
|
-
const current =
|
|
2949
|
+
const current = lstatSync14(target);
|
|
2945
2950
|
if (!existingIdentity || !current.isFile() || current.isSymbolicLink() || current.dev !== existingIdentity.dev || current.ino !== existingIdentity.ino) {
|
|
2946
2951
|
throw new NotebookError("CONFLICT", "Notebook state target changed during atomic update; preserving both entries for audit");
|
|
2947
2952
|
}
|
|
@@ -2952,12 +2957,12 @@ function atomicWriteJson(path, value, root, afterParentPinned) {
|
|
|
2952
2957
|
fsyncSync3(parentFd);
|
|
2953
2958
|
} catch (error) {
|
|
2954
2959
|
try {
|
|
2955
|
-
|
|
2960
|
+
unlinkSync8(temp);
|
|
2956
2961
|
} catch {
|
|
2957
2962
|
}
|
|
2958
2963
|
throw error;
|
|
2959
2964
|
} finally {
|
|
2960
|
-
|
|
2965
|
+
closeSync5(parentFd);
|
|
2961
2966
|
}
|
|
2962
2967
|
return Buffer.byteLength(text, "utf8");
|
|
2963
2968
|
}
|
|
@@ -2967,22 +2972,22 @@ function exclusiveWrite(path, text, root) {
|
|
|
2967
2972
|
const target = pinnedLeaf(parentFd, path);
|
|
2968
2973
|
let fd;
|
|
2969
2974
|
try {
|
|
2970
|
-
fd =
|
|
2975
|
+
fd = openSync5(target, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | (constants3.O_NOFOLLOW ?? 0), 384);
|
|
2971
2976
|
} catch (error) {
|
|
2972
|
-
|
|
2977
|
+
closeSync5(parentFd);
|
|
2973
2978
|
if (error.code === "EEXIST") return false;
|
|
2974
2979
|
throw error;
|
|
2975
2980
|
}
|
|
2976
2981
|
try {
|
|
2977
|
-
|
|
2982
|
+
writeFileSync12(fd, text, "utf8");
|
|
2978
2983
|
fsyncSync3(fd);
|
|
2979
2984
|
} finally {
|
|
2980
|
-
|
|
2985
|
+
closeSync5(fd);
|
|
2981
2986
|
}
|
|
2982
2987
|
try {
|
|
2983
2988
|
fsyncSync3(parentFd);
|
|
2984
2989
|
} finally {
|
|
2985
|
-
|
|
2990
|
+
closeSync5(parentFd);
|
|
2986
2991
|
}
|
|
2987
2992
|
return true;
|
|
2988
2993
|
}
|
|
@@ -3126,13 +3131,13 @@ function safeReadJson(path, maxBytes) {
|
|
|
3126
3131
|
}
|
|
3127
3132
|
let fd;
|
|
3128
3133
|
try {
|
|
3129
|
-
fd =
|
|
3134
|
+
fd = openSync5(pinnedLeaf(parentFd, path), constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
|
|
3130
3135
|
} catch (error) {
|
|
3131
|
-
|
|
3136
|
+
closeSync5(parentFd);
|
|
3132
3137
|
return { reason: error.code === "ELOOP" ? "non-regular" : "unreadable", bytes: 0 };
|
|
3133
3138
|
}
|
|
3134
3139
|
try {
|
|
3135
|
-
const stat =
|
|
3140
|
+
const stat = fstatSync2(fd);
|
|
3136
3141
|
if (!stat.isFile()) return { reason: "non-regular", bytes: 0 };
|
|
3137
3142
|
if ((stat.mode & 511) !== 384) return { reason: "unsafe-permissions", bytes: stat.size };
|
|
3138
3143
|
try {
|
|
@@ -3152,14 +3157,14 @@ function safeReadJson(path, maxBytes) {
|
|
|
3152
3157
|
if (total > maxBytes) return { reason: "oversize", bytes: total };
|
|
3153
3158
|
chunks.push(chunk.subarray(0, count));
|
|
3154
3159
|
}
|
|
3155
|
-
const after =
|
|
3160
|
+
const after = fstatSync2(fd);
|
|
3156
3161
|
if (after.size !== stat.size || total !== stat.size) return { reason: "unreadable", bytes: total };
|
|
3157
3162
|
return { value: JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(Buffer.concat(chunks, total))), bytes: total };
|
|
3158
3163
|
} catch (error) {
|
|
3159
3164
|
return { reason: error instanceof SyntaxError || error instanceof TypeError ? "invalid-json" : "unreadable", bytes: 0 };
|
|
3160
3165
|
} finally {
|
|
3161
|
-
|
|
3162
|
-
|
|
3166
|
+
closeSync5(fd);
|
|
3167
|
+
closeSync5(parentFd);
|
|
3163
3168
|
}
|
|
3164
3169
|
}
|
|
3165
3170
|
function safeEntryId(kind, name) {
|
|
@@ -3176,10 +3181,10 @@ function baselineReceiptByteCeiling(limits) {
|
|
|
3176
3181
|
function readSessionBaseline(root, projectSlug, sessionKey, limits) {
|
|
3177
3182
|
assertDigest(sessionKey, "session key");
|
|
3178
3183
|
const paths = ensureNotebookState(root, projectSlug);
|
|
3179
|
-
return readBaseline(
|
|
3184
|
+
return readBaseline(join18(paths.baselines, `${sessionKey}.json`), baselineReceiptByteCeiling(limits));
|
|
3180
3185
|
}
|
|
3181
3186
|
function acquireLock(paths, maxWaitMs) {
|
|
3182
|
-
const lock =
|
|
3187
|
+
const lock = join18(paths.locks, "admission.lock");
|
|
3183
3188
|
const deadline = Date.now() + Math.max(1, maxWaitMs);
|
|
3184
3189
|
const token = randomUUID3();
|
|
3185
3190
|
const record3 = () => ({
|
|
@@ -3209,7 +3214,7 @@ function acquireLock(paths, maxWaitMs) {
|
|
|
3209
3214
|
throw new NotebookError("CONFLICT", "Notebook state lock has an integrity finding; preserve it and run pj notebook audit --json");
|
|
3210
3215
|
}
|
|
3211
3216
|
if (Date.now() >= expiresAt) {
|
|
3212
|
-
const recovery =
|
|
3217
|
+
const recovery = join18(paths.locks, `recovery-${heldToken}.lock`);
|
|
3213
3218
|
const recoveryRecord = jsonLine({ schema_version: NOTEBOOK_SCHEMA_VERSION, stale_token: heldToken, recovery_token: token, expires_at: new Date(Date.now() + 5e3).toISOString() });
|
|
3214
3219
|
if (exclusiveWrite(recovery, recoveryRecord, paths.root)) {
|
|
3215
3220
|
try {
|
|
@@ -3217,7 +3222,7 @@ function acquireLock(paths, maxWaitMs) {
|
|
|
3217
3222
|
const currentToken = verify.value && typeof verify.value === "object" && !Array.isArray(verify.value) ? verify.value.token : null;
|
|
3218
3223
|
const currentExpiry = verify.value && typeof verify.value === "object" && !Array.isArray(verify.value) ? Date.parse(String(verify.value.expires_at ?? "")) : Number.NaN;
|
|
3219
3224
|
if (currentToken === heldToken && Number.isFinite(currentExpiry) && Date.now() >= currentExpiry) {
|
|
3220
|
-
const recovered =
|
|
3225
|
+
const recovered = join18(paths.locks, `.recovered-${heldToken}-${randomUUID3()}.json`);
|
|
3221
3226
|
renameStateFile(lock, recovered, paths.root);
|
|
3222
3227
|
unlinkStateFile(recovered, paths.root);
|
|
3223
3228
|
}
|
|
@@ -3297,7 +3302,7 @@ function scanAuxiliaryState(paths, limits) {
|
|
|
3297
3302
|
addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: "non-regular" });
|
|
3298
3303
|
continue;
|
|
3299
3304
|
}
|
|
3300
|
-
const read = safeReadJson(
|
|
3305
|
+
const read = safeReadJson(join18(specification.dir, entry.name), specification.maxBytes);
|
|
3301
3306
|
if (read.reason || read.value === void 0) {
|
|
3302
3307
|
addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: read.reason ?? "invalid-json" }, read.bytes);
|
|
3303
3308
|
continue;
|
|
@@ -3322,7 +3327,7 @@ function scanAuxiliaryState(paths, limits) {
|
|
|
3322
3327
|
addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: "non-regular" });
|
|
3323
3328
|
continue;
|
|
3324
3329
|
}
|
|
3325
|
-
const read = safeReadJson(
|
|
3330
|
+
const read = safeReadJson(join18(paths.journals, entry.name), limits.receipt_max_bytes);
|
|
3326
3331
|
if (read.reason || read.value === void 0) {
|
|
3327
3332
|
addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: read.reason ?? "invalid-json" }, read.bytes);
|
|
3328
3333
|
continue;
|
|
@@ -3351,7 +3356,7 @@ function scanAuxiliaryState(paths, limits) {
|
|
|
3351
3356
|
addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: "non-regular" });
|
|
3352
3357
|
continue;
|
|
3353
3358
|
}
|
|
3354
|
-
const read = safeReadJson(
|
|
3359
|
+
const read = safeReadJson(join18(paths.locks, entry.name), 8192);
|
|
3355
3360
|
if (read.reason || read.value === void 0) {
|
|
3356
3361
|
addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: read.reason ?? "invalid-json" }, read.bytes);
|
|
3357
3362
|
continue;
|
|
@@ -3390,7 +3395,7 @@ function scanReceipts(paths, limits) {
|
|
|
3390
3395
|
}
|
|
3391
3396
|
for (const entry of entries) {
|
|
3392
3397
|
const entryId = safeEntryId("receipts", entry.name);
|
|
3393
|
-
const path =
|
|
3398
|
+
const path = join18(paths.receipts, entry.name);
|
|
3394
3399
|
if (!entry.isFile() || entry.isSymbolicLink() || !/^[a-f0-9]{64}\.json$/u.test(entry.name)) {
|
|
3395
3400
|
addIntegrity({ entry_id: entryId, reason: "non-regular" });
|
|
3396
3401
|
continue;
|
|
@@ -3420,7 +3425,7 @@ function scanBaselines(paths, nowMs, limits, referenced) {
|
|
|
3420
3425
|
if (!existsSync13(paths.baselines)) return { current, stale };
|
|
3421
3426
|
for (const entry of readStateDirectory(paths.baselines, paths.root)) {
|
|
3422
3427
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
3423
|
-
const baseline = readBaseline(
|
|
3428
|
+
const baseline = readBaseline(join18(paths.baselines, entry.name), baselineReceiptByteCeiling(limits));
|
|
3424
3429
|
if (!baseline || referenced.has(baseline.session_key)) continue;
|
|
3425
3430
|
const expires = Date.parse(baseline.created_at) + limits.receiptless_session_retention_seconds * 1e3;
|
|
3426
3431
|
if (nowMs >= expires) stale += 1;
|
|
@@ -3434,7 +3439,7 @@ function scanRefusals(paths, nowMs, limits, scan) {
|
|
|
3434
3439
|
const entries = readStateDirectory(paths.refusals, paths.root).sort((a, b) => a.name.localeCompare(b.name, "en")).slice(0, limits.refusal_max_entries);
|
|
3435
3440
|
for (const entry of entries) {
|
|
3436
3441
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
3437
|
-
const read = safeReadJson(
|
|
3442
|
+
const read = safeReadJson(join18(paths.refusals, entry.name), limits.receipt_max_bytes);
|
|
3438
3443
|
const marker = read.value === void 0 ? null : parseRefusal(read.value);
|
|
3439
3444
|
if (!marker || `${marker.session_key}.json` !== entry.name) continue;
|
|
3440
3445
|
if (nowMs >= Date.parse(marker.baseline_created_at) + limits.receiptless_session_retention_seconds * 1e3) continue;
|
|
@@ -3517,7 +3522,7 @@ function listCaptureReceipts(root, projectSlug, limits, state) {
|
|
|
3517
3522
|
function readCaptureReceipt(root, projectSlug, receiptId, limits) {
|
|
3518
3523
|
if (!RECEIPT_ID_RE.test(receiptId)) throw new NotebookError("INVALID_INPUT", "Invalid receipt ID");
|
|
3519
3524
|
const paths = notebookStatePaths(root, projectSlug);
|
|
3520
|
-
const path =
|
|
3525
|
+
const path = join18(paths.receipts, `${receiptId}.json`);
|
|
3521
3526
|
if (!existsSync13(path)) throw new NotebookError("NOT_FOUND", `Capture receipt not found: ${receiptId}`);
|
|
3522
3527
|
const read = safeReadJson(path, limits.receipt_max_bytes);
|
|
3523
3528
|
const receipt = read.value === void 0 ? null : parseReceipt(read.value);
|
|
@@ -3530,7 +3535,7 @@ function writeReceipt(paths, receipt, limit) {
|
|
|
3530
3535
|
const bytes = Buffer.byteLength(text, "utf8");
|
|
3531
3536
|
if (bytes > limit) throw new NotebookError("CONFLICT", "Receipt transition exceeds its per-receipt ceiling");
|
|
3532
3537
|
if (receipt.serialized_bytes === bytes) {
|
|
3533
|
-
atomicWriteJson(
|
|
3538
|
+
atomicWriteJson(join18(paths.receipts, `${receipt.receipt_id}.json`), receipt, paths.root);
|
|
3534
3539
|
return receipt;
|
|
3535
3540
|
}
|
|
3536
3541
|
receipt.serialized_bytes = bytes;
|
|
@@ -3669,25 +3674,25 @@ function pruneNotebookState(root, projectSlug, limits, now = /* @__PURE__ */ new
|
|
|
3669
3674
|
const successCutoff = now.getTime() - limits.receipt_succeeded_retention_days * 864e5;
|
|
3670
3675
|
for (const receipt of scan.receipts) {
|
|
3671
3676
|
if (receipt.state !== "succeeded" || Date.parse(receipt.updated_at) > successCutoff) continue;
|
|
3672
|
-
const path =
|
|
3677
|
+
const path = join18(paths.receipts, `${receipt.receipt_id}.json`);
|
|
3673
3678
|
unlinkStateFile(path, paths.root);
|
|
3674
|
-
removed.push(safeEntryId("receipts",
|
|
3679
|
+
removed.push(safeEntryId("receipts", basename9(path)));
|
|
3675
3680
|
}
|
|
3676
3681
|
const remaining = scanReceipts(paths, limits);
|
|
3677
3682
|
const remainingAuxiliary = scanAuxiliaryState(paths, limits);
|
|
3678
3683
|
const referenced = /* @__PURE__ */ new Set([...remaining.referencedSessions, ...remainingAuxiliary.referencedSessions]);
|
|
3679
3684
|
for (const entry of readStateDirectory(paths.baselines, paths.root)) {
|
|
3680
3685
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
3681
|
-
const path =
|
|
3686
|
+
const path = join18(paths.baselines, entry.name);
|
|
3682
3687
|
const baseline = readBaseline(path, baselineReceiptByteCeiling(limits));
|
|
3683
3688
|
if (!baseline || referenced.has(baseline.session_key)) continue;
|
|
3684
3689
|
if (now.getTime() < Date.parse(baseline.created_at) + limits.receiptless_session_retention_seconds * 1e3) continue;
|
|
3685
3690
|
unlinkStateFile(path, paths.root);
|
|
3686
3691
|
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",
|
|
3692
|
+
const claim = join18(paths.claims, `${baseline.session_key}.overview`);
|
|
3693
|
+
if (unlinkStateFile(claim, paths.root, true)) removed.push(safeEntryId("claims", basename9(claim)));
|
|
3694
|
+
const refusal = join18(paths.refusals, `${baseline.session_key}.json`);
|
|
3695
|
+
if (unlinkStateFile(refusal, paths.root, true)) removed.push(safeEntryId("refusals", basename9(refusal)));
|
|
3691
3696
|
}
|
|
3692
3697
|
fsyncDirectory2(paths.project);
|
|
3693
3698
|
return removed;
|
|
@@ -3733,7 +3738,7 @@ var init_state = __esm({
|
|
|
3733
3738
|
"REMOTE_PROTOCOL_ERROR",
|
|
3734
3739
|
"INTERNAL_ERROR"
|
|
3735
3740
|
]);
|
|
3736
|
-
DIRECTORY_OPEN_FLAGS =
|
|
3741
|
+
DIRECTORY_OPEN_FLAGS = constants3.O_RDONLY | (constants3.O_DIRECTORY ?? 0) | (constants3.O_NOFOLLOW ?? 0);
|
|
3737
3742
|
}
|
|
3738
3743
|
});
|
|
3739
3744
|
|
|
@@ -4072,8 +4077,8 @@ var init_open_notebook_client = __esm({
|
|
|
4072
4077
|
|
|
4073
4078
|
// src/notebook/git-evidence.ts
|
|
4074
4079
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
4075
|
-
import { closeSync as
|
|
4076
|
-
import { extname, join as
|
|
4080
|
+
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";
|
|
4081
|
+
import { extname, join as join20, relative as relative13, resolve as resolve13, sep as sep5 } from "node:path";
|
|
4077
4082
|
function git(repo, args, maxBuffer = 4 * 1024 * 1024, timeout = 5e3) {
|
|
4078
4083
|
const result2 = spawnSync8("git", args, { cwd: repo, encoding: "utf8", maxBuffer, timeout, shell: false });
|
|
4079
4084
|
return { ok: result2.status === 0, stdout: result2.stdout ?? "" };
|
|
@@ -4151,9 +4156,9 @@ function hasSymlinkComponent(root, candidate) {
|
|
|
4151
4156
|
const rel = relative13(root, candidate);
|
|
4152
4157
|
let cursor = root;
|
|
4153
4158
|
for (const part of rel.split(sep5).filter(Boolean)) {
|
|
4154
|
-
cursor =
|
|
4159
|
+
cursor = join20(cursor, part);
|
|
4155
4160
|
try {
|
|
4156
|
-
if (
|
|
4161
|
+
if (lstatSync15(cursor).isSymbolicLink()) return true;
|
|
4157
4162
|
} catch {
|
|
4158
4163
|
return true;
|
|
4159
4164
|
}
|
|
@@ -4165,12 +4170,12 @@ function readSafeEvidenceText(repoPath, relativePath2, maxBytes) {
|
|
|
4165
4170
|
if (!safe || hasSymlinkComponent(safe.root, safe.candidate)) return { status: "excluded", reason: "unsafe-path" };
|
|
4166
4171
|
let fd;
|
|
4167
4172
|
try {
|
|
4168
|
-
fd =
|
|
4173
|
+
fd = openSync6(safe.candidate, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
|
|
4169
4174
|
} catch {
|
|
4170
4175
|
return { status: "excluded", reason: "not-regular" };
|
|
4171
4176
|
}
|
|
4172
4177
|
try {
|
|
4173
|
-
const before =
|
|
4178
|
+
const before = fstatSync3(fd);
|
|
4174
4179
|
if (!before.isFile()) return { status: "excluded", reason: "not-regular" };
|
|
4175
4180
|
if (before.size > maxBytes) return { status: "excluded", reason: "oversize" };
|
|
4176
4181
|
let physical;
|
|
@@ -4191,7 +4196,7 @@ function readSafeEvidenceText(repoPath, relativePath2, maxBytes) {
|
|
|
4191
4196
|
if (total > maxBytes) return { status: "excluded", reason: "oversize" };
|
|
4192
4197
|
chunks.push(chunk.subarray(0, count));
|
|
4193
4198
|
}
|
|
4194
|
-
const after =
|
|
4199
|
+
const after = fstatSync3(fd);
|
|
4195
4200
|
if (after.dev !== before.dev || after.ino !== before.ino || after.size !== before.size || total !== before.size) return { status: "excluded", reason: "changed-during-read" };
|
|
4196
4201
|
const bytes = Buffer.concat(chunks, total);
|
|
4197
4202
|
if (bytes.includes(0)) return { status: "excluded", reason: "binary" };
|
|
@@ -4204,7 +4209,7 @@ function readSafeEvidenceText(repoPath, relativePath2, maxBytes) {
|
|
|
4204
4209
|
if (looksSecret(relativePath2, content)) return { status: "excluded", reason: "secret-like" };
|
|
4205
4210
|
return { status: "present", content, content_sha256: sha256Hex(bytes), bytes: total };
|
|
4206
4211
|
} finally {
|
|
4207
|
-
|
|
4212
|
+
closeSync6(fd);
|
|
4208
4213
|
}
|
|
4209
4214
|
}
|
|
4210
4215
|
function looksGenerated(path) {
|
|
@@ -4294,10 +4299,10 @@ var init_git_evidence = __esm({
|
|
|
4294
4299
|
|
|
4295
4300
|
// src/notebook/remote-mutation-journal.ts
|
|
4296
4301
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
4297
|
-
import { join as
|
|
4302
|
+
import { join as join21 } from "node:path";
|
|
4298
4303
|
function journalPath(root, projectSlug, operationId) {
|
|
4299
4304
|
if (!/^[a-f0-9-]{16,64}$/iu.test(operationId)) throw new NotebookError("INVALID_INPUT", "Invalid remote mutation operation ID");
|
|
4300
|
-
return
|
|
4305
|
+
return join21(ensureNotebookState(root, projectSlug).journals, `${operationId}.json`);
|
|
4301
4306
|
}
|
|
4302
4307
|
function remoteMutationJournalPath(root, projectSlug, operationId) {
|
|
4303
4308
|
return journalPath(root, projectSlug, operationId);
|
|
@@ -4317,7 +4322,7 @@ function listRemoteMutationJournals(root, projectSlug) {
|
|
|
4317
4322
|
if (!entry.isFile() || entry.isSymbolicLink() || !/^[a-f0-9-]{16,64}\.json$/iu.test(entry.name)) {
|
|
4318
4323
|
throw new NotebookError("CONFLICT", `Remote mutation journal state-integrity finding: journals/${entry.name}`);
|
|
4319
4324
|
}
|
|
4320
|
-
const read = readNotebookStateJson(
|
|
4325
|
+
const read = readNotebookStateJson(join21(paths.journals, entry.name), paths.root, 65536);
|
|
4321
4326
|
const journal = read.value === void 0 ? null : parseRemoteMutationJournal(read.value);
|
|
4322
4327
|
if (!journal || `${journal.operation_id}.json` !== entry.name) {
|
|
4323
4328
|
throw new NotebookError("CONFLICT", `Remote mutation journal state-integrity finding: journals/${entry.name}`);
|
|
@@ -4671,7 +4676,7 @@ var init_reconcile = __esm({
|
|
|
4671
4676
|
|
|
4672
4677
|
// src/notebook/entropy.ts
|
|
4673
4678
|
import { existsSync as existsSync16 } from "node:fs";
|
|
4674
|
-
import { createHash as
|
|
4679
|
+
import { createHash as createHash14 } from "node:crypto";
|
|
4675
4680
|
import { resolve as resolve16 } from "node:path";
|
|
4676
4681
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
4677
4682
|
function listAllTrackedDocuments(config) {
|
|
@@ -4726,7 +4731,7 @@ async function compactSessionCaptures(input) {
|
|
|
4726
4731
|
const toCompact = sessionHeaders.slice(maxRecent);
|
|
4727
4732
|
const notesToCompact = typeof input.client.hydrateNotes === "function" ? await input.client.hydrateNotes(toCompact) : toCompact;
|
|
4728
4733
|
const archiveTitle = "Session Captures Archive (Prior Sessions)";
|
|
4729
|
-
const slugHash =
|
|
4734
|
+
const slugHash = createHash14("sha256").update(`session-capture-archive:${input.projectSlug}`).digest("hex").slice(0, 32);
|
|
4730
4735
|
const archiveLogicalId = `user-note:v1:${slugHash}`;
|
|
4731
4736
|
let existingArchiveContent = "";
|
|
4732
4737
|
let existingArchiveId = null;
|
|
@@ -5294,7 +5299,7 @@ var init_capture = __esm({
|
|
|
5294
5299
|
|
|
5295
5300
|
// src/mcp-server.ts
|
|
5296
5301
|
import { existsSync as existsSync22, statSync as statSync8 } from "node:fs";
|
|
5297
|
-
import { dirname as dirname20, join as
|
|
5302
|
+
import { dirname as dirname20, join as join32, resolve as resolve22 } from "node:path";
|
|
5298
5303
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
5299
5304
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5300
5305
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -5360,6 +5365,15 @@ function isHostScoped(finding2) {
|
|
|
5360
5365
|
function gatesProject(finding2) {
|
|
5361
5366
|
return finding2.status === "fail" && !isHostScoped(finding2);
|
|
5362
5367
|
}
|
|
5368
|
+
var FINDING_DETAIL_LIMIT = 20;
|
|
5369
|
+
function describeFinding(finding2) {
|
|
5370
|
+
const details = (finding2.details ?? []).map((line) => line.trimEnd()).filter((line) => line.trim().length > 0);
|
|
5371
|
+
if (details.length === 0) return `${finding2.id}: ${finding2.summary}`;
|
|
5372
|
+
const shown = details.slice(0, FINDING_DETAIL_LIMIT).map((line) => ` - ${line.replace(/\r?\n/g, "\n ")}`);
|
|
5373
|
+
const omitted = details.length - shown.length;
|
|
5374
|
+
if (omitted > 0) shown.push(` - (+${omitted} more; run pj audit --rules ${finding2.id} for the full list)`);
|
|
5375
|
+
return [`${finding2.id}: ${finding2.summary}`, ...shown].join("\n");
|
|
5376
|
+
}
|
|
5363
5377
|
|
|
5364
5378
|
// src/recipes/Recipe.ts
|
|
5365
5379
|
function commandStatus(result2, dryRun) {
|
|
@@ -5418,7 +5432,7 @@ var Recipe = class {
|
|
|
5418
5432
|
}
|
|
5419
5433
|
if (!finding2.fixable) {
|
|
5420
5434
|
phases.push({ id: check.id, status: "failed", changedFiles: [], message: finding2.summary });
|
|
5421
|
-
errors.push(
|
|
5435
|
+
errors.push(describeFinding({ id: check.id, summary: finding2.summary, details: finding2.details }));
|
|
5422
5436
|
break;
|
|
5423
5437
|
}
|
|
5424
5438
|
const migrated = await check.migrate(ctx, finding2);
|
|
@@ -5428,7 +5442,7 @@ var Recipe = class {
|
|
|
5428
5442
|
logs.push(`${check.id}: ${migrated.summary}`);
|
|
5429
5443
|
changedFiles.push(...actualChanges);
|
|
5430
5444
|
if (status === "failed") {
|
|
5431
|
-
errors.push(
|
|
5445
|
+
errors.push(describeFinding({ id: check.id, summary: migrated.summary, details: migrated.details }));
|
|
5432
5446
|
break;
|
|
5433
5447
|
}
|
|
5434
5448
|
if (!ctx.dryRun) {
|
|
@@ -6868,7 +6882,11 @@ function attestBmadInstallerFiles(inventory, base, files, label) {
|
|
|
6868
6882
|
|
|
6869
6883
|
// src/parity/skill-roots.ts
|
|
6870
6884
|
var CANONICAL_CLI_SKILLS_ALIAS = "../.agents/skills";
|
|
6871
|
-
var
|
|
6885
|
+
var SKILLEX_ONLY_ALIASES_AFTER = { ".kimi-code/skills": ".kimi/skills" };
|
|
6886
|
+
var SUPPORTED_SKILLS_ALIASES = SUPPORTED_CLIS.flatMap((cli) => {
|
|
6887
|
+
const follower = SKILLEX_ONLY_ALIASES_AFTER[cli.skillsRoot];
|
|
6888
|
+
return follower ? [cli.skillsRoot, follower] : [cli.skillsRoot];
|
|
6889
|
+
});
|
|
6872
6890
|
var SKILL_ROOTS_QUARANTINE = ".pjangler-quarantine";
|
|
6873
6891
|
function lstatOrUndefined(path) {
|
|
6874
6892
|
try {
|
|
@@ -7274,20 +7292,20 @@ function planSkillRoots(repoRoot, options = {}) {
|
|
|
7274
7292
|
for (const name of readdirSync6(real).sort()) {
|
|
7275
7293
|
const path = join7(real, name);
|
|
7276
7294
|
const label = `${alias}/${name}`;
|
|
7277
|
-
const
|
|
7295
|
+
const entryStat2 = lstatSync6(path);
|
|
7278
7296
|
const rows = tracked.get(name) ?? [];
|
|
7279
|
-
const candidate = { alias, name, path, label, kind:
|
|
7297
|
+
const candidate = { alias, name, path, label, kind: entryStat2.isSymbolicLink() ? "link" : entryStat2.isDirectory() ? "directory" : "file", tracked: rows.map((row) => row.path) };
|
|
7280
7298
|
if (name === ".git") {
|
|
7281
7299
|
candidate.staticBlock = `${label} is git metadata; refusing to relocate another repository's work tree`;
|
|
7282
|
-
} else if (
|
|
7300
|
+
} else if (entryStat2.isSymbolicLink()) {
|
|
7283
7301
|
candidate.text = readlinkSync5(path);
|
|
7284
7302
|
candidate.resolution = resolveIn(diskLayout, path);
|
|
7285
7303
|
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 (!
|
|
7304
|
+
} else if (!entryStat2.isDirectory() && !entryStat2.isFile()) {
|
|
7287
7305
|
candidate.staticBlock = `${label} is a special file; refusing to relocate it`;
|
|
7288
7306
|
} else {
|
|
7289
7307
|
candidate.staticBlock = entryHazard(path, label);
|
|
7290
|
-
if (!candidate.staticBlock && rootDevice !== void 0 &&
|
|
7308
|
+
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
7309
|
const linkRow = rows.find((row) => row.mode === "120000");
|
|
7292
7310
|
const gitlink = rows.find((row) => row.mode === "160000");
|
|
7293
7311
|
if (!candidate.staticBlock && gitlink) candidate.staticBlock = `${label} holds a tracked submodule (${gitlink.path}); refusing to relocate it`;
|
|
@@ -7975,7 +7993,7 @@ function readHandoffRecord(ctx) {
|
|
|
7975
7993
|
}
|
|
7976
7994
|
function writeHandoffRecord(ctx, names) {
|
|
7977
7995
|
const path = handoffRecordPath(ctx);
|
|
7978
|
-
mkdirSync5(dirname6(path), { recursive: true });
|
|
7996
|
+
mkdirSync5(dirname6(path), { recursive: true, mode: 448 });
|
|
7979
7997
|
const temporary = `${path}.${process.pid}.tmp`;
|
|
7980
7998
|
writeFileSync5(temporary, `${JSON.stringify({ project: realpathOf(ctx.repoRoot) ?? resolve4(ctx.repoRoot), excluded: [...new Set(names)].sort() }, null, 2)}
|
|
7981
7999
|
`);
|
|
@@ -9194,14 +9212,324 @@ function createG33CompanionCheck() {
|
|
|
9194
9212
|
}
|
|
9195
9213
|
|
|
9196
9214
|
// src/parity/rules.ts
|
|
9197
|
-
import { existsSync as existsSync8, lstatSync as
|
|
9198
|
-
|
|
9215
|
+
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";
|
|
9216
|
+
|
|
9217
|
+
// src/bmad/setup-scripts.ts
|
|
9218
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
9219
|
+
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";
|
|
9220
|
+
import { basename as basename4, join as join10 } from "node:path";
|
|
9221
|
+
function entryStat(path) {
|
|
9222
|
+
try {
|
|
9223
|
+
return lstatSync9(path);
|
|
9224
|
+
} catch (error) {
|
|
9225
|
+
if (error.code === "ENOENT") return void 0;
|
|
9226
|
+
throw error;
|
|
9227
|
+
}
|
|
9228
|
+
}
|
|
9229
|
+
var RUNTIME_SCRIPTS = ["config_utils.py", "memlog.py", "render_skill.py", "resolve_config.py", "resolve_customization.py"];
|
|
9230
|
+
function manifestRows(text) {
|
|
9231
|
+
const rows = [];
|
|
9232
|
+
let row = [], field2 = "", quoted = false, closed = false, inQuotes = false;
|
|
9233
|
+
for (let i = 0; i < text.length; i++) {
|
|
9234
|
+
const c = text[i];
|
|
9235
|
+
if (inQuotes) {
|
|
9236
|
+
if (c === '"' && text[i + 1] === '"') {
|
|
9237
|
+
field2 += '"';
|
|
9238
|
+
i++;
|
|
9239
|
+
} else if (c === '"') {
|
|
9240
|
+
inQuotes = false;
|
|
9241
|
+
closed = true;
|
|
9242
|
+
} else field2 += c;
|
|
9243
|
+
} else if (c === '"' && !field2 && !closed) {
|
|
9244
|
+
inQuotes = true;
|
|
9245
|
+
quoted = true;
|
|
9246
|
+
} else if (c === ",") {
|
|
9247
|
+
row.push(field2);
|
|
9248
|
+
field2 = "";
|
|
9249
|
+
quoted = closed = false;
|
|
9250
|
+
} else if (c === "\n" || c === "\r") {
|
|
9251
|
+
if (c === "\r" && text[i + 1] === "\n") i++;
|
|
9252
|
+
row.push(field2);
|
|
9253
|
+
if (row.length !== 1 || row[0]) rows.push(row);
|
|
9254
|
+
row = [];
|
|
9255
|
+
field2 = "";
|
|
9256
|
+
quoted = closed = false;
|
|
9257
|
+
} else if (closed || c === '"' || quoted && c === "\r") throw new Error("Malformed BMAD file manifest CSV");
|
|
9258
|
+
else field2 += c;
|
|
9259
|
+
}
|
|
9260
|
+
if (inQuotes) throw new Error("Unterminated BMAD file manifest CSV field");
|
|
9261
|
+
if (row.length || field2 || closed) {
|
|
9262
|
+
row.push(field2);
|
|
9263
|
+
rows.push(row);
|
|
9264
|
+
}
|
|
9265
|
+
if (rows.some((r) => r.length !== 5) || rows[0]?.join("\0") !== "type\0name\0module\0path\0hash") {
|
|
9266
|
+
throw new Error("Invalid BMAD file manifest columns");
|
|
9267
|
+
}
|
|
9268
|
+
return rows.slice(1);
|
|
9269
|
+
}
|
|
9270
|
+
function sameIdentity(a, b) {
|
|
9271
|
+
return !!a && a.dev === b.dev && a.ino === b.ino && a.isDirectory() && !a.isSymbolicLink();
|
|
9272
|
+
}
|
|
9273
|
+
var DeclinableError = class extends Error {
|
|
9274
|
+
constructor(message, kind) {
|
|
9275
|
+
super(message);
|
|
9276
|
+
this.kind = kind;
|
|
9277
|
+
}
|
|
9278
|
+
};
|
|
9279
|
+
var FIELD_SEP = String.fromCharCode(0);
|
|
9280
|
+
var MANIFEST_UNUSABLE = "Installed BMAD file manifest is missing or not a real file; cannot attest recovered scripts";
|
|
9281
|
+
function inspectBmadSetupScripts(repoRoot, version, installedVersion) {
|
|
9282
|
+
const bmadDir = join10(repoRoot, "_bmad");
|
|
9283
|
+
const dest = join10(bmadDir, "scripts");
|
|
9284
|
+
try {
|
|
9285
|
+
const bmad = entryStat(bmadDir);
|
|
9286
|
+
if (!bmad) return { state: "decline", why: "_bmad is absent", missing: [...RUNTIME_SCRIPTS] };
|
|
9287
|
+
if (!bmad.isDirectory() || bmad.isSymbolicLink()) return { state: "unsafe", error: "_bmad is not a real directory" };
|
|
9288
|
+
const destStat = entryStat(dest);
|
|
9289
|
+
if (destStat && (!destStat.isDirectory() || destStat.isSymbolicLink())) return { state: "unsafe", error: "_bmad/scripts is not a real directory" };
|
|
9290
|
+
const missing = [];
|
|
9291
|
+
for (const name of RUNTIME_SCRIPTS) {
|
|
9292
|
+
const existing = destStat ? entryStat(join10(dest, name)) : void 0;
|
|
9293
|
+
if (!existing) missing.push(name);
|
|
9294
|
+
else if (!existing.isFile() || existing.isSymbolicLink()) return { state: "unsafe", error: `Existing _bmad/scripts/${name} is not a regular file; refusing overwrite` };
|
|
9295
|
+
}
|
|
9296
|
+
if (!missing.length) return { state: "present" };
|
|
9297
|
+
const configDir = entryStat(join10(bmadDir, "_config"));
|
|
9298
|
+
if (configDir && (!configDir.isDirectory() || configDir.isSymbolicLink())) return { state: "unsafe", error: MANIFEST_UNUSABLE };
|
|
9299
|
+
const manifestPath = join10(bmadDir, "_config", "files-manifest.csv");
|
|
9300
|
+
const manifestFile = configDir ? entryStat(manifestPath) : void 0;
|
|
9301
|
+
if (!manifestFile) return { state: "decline", why: "_bmad/_config/files-manifest.csv is absent, so recovered scripts cannot be attested", missing };
|
|
9302
|
+
if (!manifestFile.isFile() || manifestFile.isSymbolicLink()) return { state: "unsafe", error: MANIFEST_UNUSABLE };
|
|
9303
|
+
const installed = installedVersion?.();
|
|
9304
|
+
if (installed && installed !== version) {
|
|
9305
|
+
return { state: "decline", why: `this install is bmad-method ${installed}, not the pinned ${version} that recovery restores from`, missing };
|
|
9306
|
+
}
|
|
9307
|
+
const rows = manifestRows(readFileSync9(manifestPath, "utf8"));
|
|
9308
|
+
const unattested = missing.filter((name) => !rows.some((row) => row[3] === `scripts/${name}`));
|
|
9309
|
+
if (unattested.length) {
|
|
9310
|
+
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 };
|
|
9311
|
+
}
|
|
9312
|
+
return { state: "missing", missing };
|
|
9313
|
+
} catch (error) {
|
|
9314
|
+
return { state: "unsafe", error: error instanceof Error ? error.message : String(error) };
|
|
9315
|
+
}
|
|
9316
|
+
}
|
|
9317
|
+
function planBmadSetupScripts(repoRoot, pkg, version) {
|
|
9318
|
+
const dest = join10(repoRoot, "_bmad", "scripts");
|
|
9319
|
+
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"}` };
|
|
9320
|
+
let fingerprint2;
|
|
9321
|
+
try {
|
|
9322
|
+
fingerprint2 = installerFingerprint(pkg.root);
|
|
9323
|
+
} catch (error) {
|
|
9324
|
+
return { files: [], declinable: "package", error: `Could not fingerprint pinned BMAD installer: ${String(error)}` };
|
|
9325
|
+
}
|
|
9326
|
+
if (VERIFIED_BMAD_INSTALLERS[fingerprint2] !== version) return { files: [], declinable: "package", error: `Unverified BMAD installer fingerprint ${fingerprint2}; refusing script recovery` };
|
|
9327
|
+
const source = join10(pkg.root, "src", "scripts");
|
|
9328
|
+
try {
|
|
9329
|
+
const bmad = lstatSync9(join10(repoRoot, "_bmad"));
|
|
9330
|
+
if (!bmad.isDirectory() || bmad.isSymbolicLink()) throw new Error("_bmad is not a real directory");
|
|
9331
|
+
const destStat = entryStat(dest);
|
|
9332
|
+
if (destStat && (!destStat.isDirectory() || destStat.isSymbolicLink())) throw new Error("_bmad/scripts is not a real directory");
|
|
9333
|
+
const names = readdirSync7(source).filter((name) => !name.startsWith(".") && !name.endsWith(".pyc") && name !== "tests" && name !== "__pycache__" && name !== ".pytest_cache").sort();
|
|
9334
|
+
if (names.join(FIELD_SEP) !== [...RUNTIME_SCRIPTS].sort().join(FIELD_SEP)) throw new DeclinableError("Pinned installer script inventory changed; refusing recovery", "package");
|
|
9335
|
+
const missing = [];
|
|
9336
|
+
for (const name of names) {
|
|
9337
|
+
const existing = entryStat(join10(dest, name));
|
|
9338
|
+
if (!existing) missing.push(name);
|
|
9339
|
+
else if (!existing.isFile() || existing.isSymbolicLink()) throw new Error(`Existing _bmad/scripts/${name} is not a regular file; refusing overwrite`);
|
|
9340
|
+
}
|
|
9341
|
+
const files = [];
|
|
9342
|
+
if (!missing.length) return { files, source };
|
|
9343
|
+
const manifestPath = join10(repoRoot, "_bmad", "_config", "files-manifest.csv");
|
|
9344
|
+
const manifestDir = entryStat(join10(repoRoot, "_bmad", "_config"));
|
|
9345
|
+
const manifestFile = entryStat(manifestPath);
|
|
9346
|
+
if (!manifestDir?.isDirectory() || manifestDir.isSymbolicLink() || !manifestFile?.isFile() || manifestFile.isSymbolicLink()) {
|
|
9347
|
+
throw new Error(MANIFEST_UNUSABLE);
|
|
9348
|
+
}
|
|
9349
|
+
const rows = manifestRows(readFileSync9(manifestPath, "utf8"));
|
|
9350
|
+
for (const name of missing) {
|
|
9351
|
+
const src = join10(source, name);
|
|
9352
|
+
if (!lstatSync9(src).isFile() || lstatSync9(src).isSymbolicLink()) throw new DeclinableError(`Installer script is not a regular file: ${name}`, "package");
|
|
9353
|
+
const matching = rows.filter((row) => row[3] === `scripts/${name}`);
|
|
9354
|
+
const expectedHash = createHash8("sha256").update(readFileSync9(src)).digest("hex");
|
|
9355
|
+
if (matching.length !== 1 || matching[0].join(FIELD_SEP) !== ["py", name.slice(0, -3), "scripts", `scripts/${name}`, expectedHash].join(FIELD_SEP)) {
|
|
9356
|
+
throw new DeclinableError(`Installed file manifest does not attest pinned script ${name}`, "attest");
|
|
9357
|
+
}
|
|
9358
|
+
files.push(join10(dest, name));
|
|
9359
|
+
}
|
|
9360
|
+
return { files, source };
|
|
9361
|
+
} catch (error) {
|
|
9362
|
+
return { files: [], ...error instanceof DeclinableError ? { declinable: error.kind } : {}, error: `BMAD script recovery refused: ${error instanceof Error ? error.message : String(error)}` };
|
|
9363
|
+
}
|
|
9364
|
+
}
|
|
9365
|
+
function applyBmadSetupScripts(repoRoot, pkg, version, afterPublish) {
|
|
9366
|
+
const plan = planBmadSetupScripts(repoRoot, pkg, version);
|
|
9367
|
+
if (plan.error || !plan.files.length) return { files: [], ...plan.error ? { error: plan.error } : {} };
|
|
9368
|
+
const parent = join10(repoRoot, "_bmad");
|
|
9369
|
+
const dest = join10(parent, "scripts");
|
|
9370
|
+
const expectedParent = entryStat(parent);
|
|
9371
|
+
const expectedDir = entryStat(dest);
|
|
9372
|
+
let stage;
|
|
9373
|
+
let stageFd;
|
|
9374
|
+
let parentFd;
|
|
9375
|
+
const created = [];
|
|
9376
|
+
const stagedIdentities = /* @__PURE__ */ new Map();
|
|
9377
|
+
const published = /* @__PURE__ */ new Map();
|
|
9378
|
+
let ambiguousPublication;
|
|
9379
|
+
const matchesPayload = (path, owned) => {
|
|
9380
|
+
const stat = entryStat(path);
|
|
9381
|
+
return !!stat?.isFile() && !stat.isSymbolicLink() && stat.dev === owned.dev && stat.ino === owned.ino && (stat.mode & 4095) === owned.mode && readFileSync9(path).equals(owned.bytes);
|
|
9382
|
+
};
|
|
9383
|
+
let madeDir = false;
|
|
9384
|
+
let dirFd;
|
|
9385
|
+
let result2 = { files: [] };
|
|
9386
|
+
try {
|
|
9387
|
+
parentFd = openSync2(parent, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
9388
|
+
const pinnedParent = fstatSync(parentFd);
|
|
9389
|
+
if (!sameIdentity(expectedParent, pinnedParent) || !sameIdentity(entryStat(parent), pinnedParent)) throw new Error("BMAD parent directory changed before staging");
|
|
9390
|
+
const anchoredParent = `/proc/self/fd/${parentFd}`;
|
|
9391
|
+
stage = mkdtempSync2(join10(anchoredParent, ".scripts-stage-"));
|
|
9392
|
+
stageFd = openSync2(stage, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
9393
|
+
const pinnedStage = fstatSync(stageFd);
|
|
9394
|
+
const stageName = basename4(stage);
|
|
9395
|
+
const anchoredStage = `/proc/self/fd/${stageFd}`;
|
|
9396
|
+
if (!sameIdentity(entryStat(join10(anchoredParent, stageName)), pinnedStage)) throw new Error("Staging directory changed during creation");
|
|
9397
|
+
for (const path of plan.files) {
|
|
9398
|
+
const name = basename4(path);
|
|
9399
|
+
const src = join10(plan.source, name);
|
|
9400
|
+
const staged = join10(anchoredStage, name);
|
|
9401
|
+
const fileFd = openSync2(staged, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 384);
|
|
9402
|
+
try {
|
|
9403
|
+
const stat = fstatSync(fileFd);
|
|
9404
|
+
const bytes = readFileSync9(src);
|
|
9405
|
+
const mode = lstatSync9(src).mode & 4095;
|
|
9406
|
+
stagedIdentities.set(name, { dev: stat.dev, ino: stat.ino, bytes, mode });
|
|
9407
|
+
writeFileSync7(fileFd, bytes);
|
|
9408
|
+
fchmodSync(fileFd, mode);
|
|
9409
|
+
} finally {
|
|
9410
|
+
closeSync2(fileFd);
|
|
9411
|
+
}
|
|
9412
|
+
}
|
|
9413
|
+
if (!sameIdentity(entryStat(parent), pinnedParent)) throw new Error("BMAD parent directory changed while staging");
|
|
9414
|
+
const current = planBmadSetupScripts(repoRoot, pkg, version);
|
|
9415
|
+
if (current.error || current.files.join("\0") !== plan.files.join("\0")) throw new Error(current.error ?? "Script destination changed while staging");
|
|
9416
|
+
const anchoredDest = join10(anchoredParent, "scripts");
|
|
9417
|
+
if (!entryStat(anchoredDest)) {
|
|
9418
|
+
mkdirSync6(anchoredDest);
|
|
9419
|
+
madeDir = true;
|
|
9420
|
+
}
|
|
9421
|
+
dirFd = openSync2(anchoredDest, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
9422
|
+
const pinned = fstatSync(dirFd);
|
|
9423
|
+
if (expectedDir && !sameIdentity(expectedDir, pinned)) throw new Error("Script directory changed while staging");
|
|
9424
|
+
if (!sameIdentity(entryStat(parent), pinnedParent) || !sameIdentity(entryStat(dest), pinned)) throw new Error("Script or parent directory changed during publication");
|
|
9425
|
+
const anchored = `/proc/self/fd/${dirFd}`;
|
|
9426
|
+
for (const path of plan.files) {
|
|
9427
|
+
const name = basename4(path);
|
|
9428
|
+
const staged = join10(anchoredStage, name);
|
|
9429
|
+
const output = join10(anchored, name);
|
|
9430
|
+
const owned = stagedIdentities.get(name);
|
|
9431
|
+
if (!sameIdentity(entryStat(parent), pinnedParent) || !sameIdentity(entryStat(dest), pinned)) throw new Error("Script or parent directory changed during publication");
|
|
9432
|
+
if (!matchesPayload(staged, owned)) throw new Error(`Staged script changed before publication: ${name}`);
|
|
9433
|
+
if (entryStat(output)) {
|
|
9434
|
+
ambiguousPublication = path;
|
|
9435
|
+
throw new Error(`Script destination appeared before publication: ${name}`);
|
|
9436
|
+
}
|
|
9437
|
+
try {
|
|
9438
|
+
linkSync(staged, output);
|
|
9439
|
+
} catch (error) {
|
|
9440
|
+
if (entryStat(output)) ambiguousPublication = path;
|
|
9441
|
+
throw error;
|
|
9442
|
+
}
|
|
9443
|
+
published.set(path, owned);
|
|
9444
|
+
created.push(path);
|
|
9445
|
+
if (!matchesPayload(output, owned)) throw new Error(`Published script changed during publication: ${name}`);
|
|
9446
|
+
afterPublish?.(created.length);
|
|
9447
|
+
}
|
|
9448
|
+
if (!sameIdentity(entryStat(parent), pinnedParent) || !sameIdentity(entryStat(dest), pinned)) throw new Error("Script or parent directory changed during publication");
|
|
9449
|
+
for (const path of created) {
|
|
9450
|
+
if (!matchesPayload(join10(anchored, basename4(path)), stagedIdentities.get(basename4(path)))) throw new Error(`Published script changed before completion: ${basename4(path)}`);
|
|
9451
|
+
}
|
|
9452
|
+
result2 = { files: created };
|
|
9453
|
+
} catch (error) {
|
|
9454
|
+
const recoveryErrors = [];
|
|
9455
|
+
for (const [path, owned] of [...published].reverse()) {
|
|
9456
|
+
try {
|
|
9457
|
+
const anchoredPath = join10(`/proc/self/fd/${dirFd}`, basename4(path));
|
|
9458
|
+
const stat = entryStat(anchoredPath);
|
|
9459
|
+
if (matchesPayload(anchoredPath, owned)) unlinkSync5(anchoredPath);
|
|
9460
|
+
else if (stat) recoveryErrors.push(`Concurrent replacement or mutation retained: ${path}`);
|
|
9461
|
+
else recoveryErrors.push(`Published file absent from pinned directory; ownership unresolved: ${path}`);
|
|
9462
|
+
} catch (recoveryError) {
|
|
9463
|
+
recoveryErrors.push(`Could not restore ${path}: ${String(recoveryError)}`);
|
|
9464
|
+
}
|
|
9465
|
+
}
|
|
9466
|
+
if (ambiguousPublication) recoveryErrors.push(`Publication not confirmed; destination retained or ownership unresolved: ${ambiguousPublication}`);
|
|
9467
|
+
if (madeDir && dirFd !== void 0 && parentFd !== void 0) {
|
|
9468
|
+
try {
|
|
9469
|
+
const pinned = fstatSync(dirFd);
|
|
9470
|
+
const anchoredDest = join10(`/proc/self/fd/${parentFd}`, "scripts");
|
|
9471
|
+
if (sameIdentity(entryStat(anchoredDest), pinned) && readdirSync7(anchoredDest).length === 0) rmdirSync3(anchoredDest);
|
|
9472
|
+
else recoveryErrors.push(`Created script directory moved or retained: ${dest}`);
|
|
9473
|
+
} catch (recoveryError) {
|
|
9474
|
+
recoveryErrors.push(`Could not remove empty script directory: ${String(recoveryError)}`);
|
|
9475
|
+
}
|
|
9476
|
+
}
|
|
9477
|
+
result2 = { files: [], error: `BMAD script recovery failed: ${error instanceof Error ? error.message : String(error)}${recoveryErrors.length ? `; recovery incomplete: ${recoveryErrors.join("; ")}` : ""}` };
|
|
9478
|
+
} finally {
|
|
9479
|
+
const cleanupErrors = [];
|
|
9480
|
+
if (stageFd !== void 0) {
|
|
9481
|
+
try {
|
|
9482
|
+
const pinnedStage = fstatSync(stageFd);
|
|
9483
|
+
const anchoredStage = `/proc/self/fd/${stageFd}`;
|
|
9484
|
+
for (const [name, owned] of stagedIdentities) {
|
|
9485
|
+
const staged = join10(anchoredStage, name);
|
|
9486
|
+
const stat = entryStat(staged);
|
|
9487
|
+
if (stat?.isFile() && !stat.isSymbolicLink() && stat.dev === owned.dev && stat.ino === owned.ino) unlinkSync5(staged);
|
|
9488
|
+
else cleanupErrors.push(`Staged file replaced, modified or absent: ${name}`);
|
|
9489
|
+
}
|
|
9490
|
+
const stagePath = join10(`/proc/self/fd/${parentFd}`, basename4(stage));
|
|
9491
|
+
if (sameIdentity(entryStat(stagePath), pinnedStage) && readdirSync7(anchoredStage).length === 0) rmdirSync3(stagePath);
|
|
9492
|
+
else cleanupErrors.push(`Staging directory moved or contains foreign entries: ${join10(parent, basename4(stage))}`);
|
|
9493
|
+
} catch (cleanupError) {
|
|
9494
|
+
cleanupErrors.push(`Could not clean owned staging directory: ${String(cleanupError)}`);
|
|
9495
|
+
}
|
|
9496
|
+
closeSync2(stageFd);
|
|
9497
|
+
} else if (stage) cleanupErrors.push(`Staging directory identity unavailable: ${join10(parent, basename4(stage))}`);
|
|
9498
|
+
if (cleanupErrors.length && result2.files.length) {
|
|
9499
|
+
for (const [path, owned] of [...published].reverse()) {
|
|
9500
|
+
try {
|
|
9501
|
+
const output = join10(`/proc/self/fd/${dirFd}`, basename4(path));
|
|
9502
|
+
if (matchesPayload(output, owned)) unlinkSync5(output);
|
|
9503
|
+
else cleanupErrors.push(`Concurrent replacement or mutation retained: ${path}`);
|
|
9504
|
+
} catch (error) {
|
|
9505
|
+
cleanupErrors.push(`Could not restore ${path}: ${String(error)}`);
|
|
9506
|
+
}
|
|
9507
|
+
}
|
|
9508
|
+
if (madeDir && dirFd !== void 0 && parentFd !== void 0) {
|
|
9509
|
+
try {
|
|
9510
|
+
const anchoredDest = join10(`/proc/self/fd/${parentFd}`, "scripts");
|
|
9511
|
+
if (sameIdentity(entryStat(anchoredDest), fstatSync(dirFd)) && readdirSync7(anchoredDest).length === 0) rmdirSync3(anchoredDest);
|
|
9512
|
+
else cleanupErrors.push(`Created script directory moved or retained: ${dest}`);
|
|
9513
|
+
} catch (error) {
|
|
9514
|
+
cleanupErrors.push(`Could not remove empty script directory: ${String(error)}`);
|
|
9515
|
+
}
|
|
9516
|
+
}
|
|
9517
|
+
}
|
|
9518
|
+
if (dirFd !== void 0) closeSync2(dirFd);
|
|
9519
|
+
if (parentFd !== void 0) closeSync2(parentFd);
|
|
9520
|
+
if (cleanupErrors.length) result2 = { files: [], error: `${result2.error ? `${result2.error}; ` : "BMAD script staging cleanup incomplete: "}recovery incomplete: ${cleanupErrors.join("; ")}` };
|
|
9521
|
+
}
|
|
9522
|
+
return result2;
|
|
9523
|
+
}
|
|
9524
|
+
|
|
9525
|
+
// src/parity/rules.ts
|
|
9526
|
+
import { basename as basename5, dirname as dirname8, join as join12, relative as relative9, resolve as resolve6 } from "node:path";
|
|
9199
9527
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
9200
9528
|
import YAML4 from "yaml";
|
|
9201
9529
|
|
|
9202
9530
|
// src/bmad/reserved-modules.ts
|
|
9203
|
-
import { existsSync as existsSync7, readFileSync as
|
|
9204
|
-
import { join as
|
|
9531
|
+
import { existsSync as existsSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "node:fs";
|
|
9532
|
+
import { join as join11 } from "node:path";
|
|
9205
9533
|
import YAML3 from "yaml";
|
|
9206
9534
|
var BMAD_RESERVED_DIR_NAMES = ["custom", "scripts", "_config", "_memory", "memory", "render"];
|
|
9207
9535
|
var indentOf = (line) => line.length - line.trimStart().length;
|
|
@@ -9258,19 +9586,19 @@ function stripReservedModuleEntries(raw) {
|
|
|
9258
9586
|
return { text: lines.filter((_, index) => !drop.has(index)).join(""), evicted };
|
|
9259
9587
|
}
|
|
9260
9588
|
function evictReservedModuleEntries(repoRoot, dryRun) {
|
|
9261
|
-
const manifestPath =
|
|
9589
|
+
const manifestPath = join11(repoRoot, "_bmad", "_config", "manifest.yaml");
|
|
9262
9590
|
const none = { evicted: [], changedFiles: [], details: [] };
|
|
9263
9591
|
if (!existsSync7(manifestPath)) return none;
|
|
9264
9592
|
let original;
|
|
9265
9593
|
try {
|
|
9266
|
-
original =
|
|
9594
|
+
original = readFileSync10(manifestPath, "utf8");
|
|
9267
9595
|
} catch {
|
|
9268
9596
|
return none;
|
|
9269
9597
|
}
|
|
9270
9598
|
const stripped = stripReservedModuleEntries(original);
|
|
9271
9599
|
if (!stripped) return none;
|
|
9272
9600
|
const names = stripped.evicted.map((name) => `"${name}"`).join(", ");
|
|
9273
|
-
if (!dryRun)
|
|
9601
|
+
if (!dryRun) writeFileSync8(manifestPath, stripped.text, "utf8");
|
|
9274
9602
|
return {
|
|
9275
9603
|
evicted: stripped.evicted,
|
|
9276
9604
|
changedFiles: [manifestPath],
|
|
@@ -9984,17 +10312,17 @@ function normalizeNewlines(value) {
|
|
|
9984
10312
|
return value.replace(/\r\n/g, "\n");
|
|
9985
10313
|
}
|
|
9986
10314
|
function readText(path) {
|
|
9987
|
-
return normalizeNewlines(
|
|
10315
|
+
return normalizeNewlines(readFileSync11(path, "utf8"));
|
|
9988
10316
|
}
|
|
9989
10317
|
function safeReadText2(path) {
|
|
9990
10318
|
return existsSync8(path) ? readText(path) : null;
|
|
9991
10319
|
}
|
|
9992
10320
|
function ensureParent(path) {
|
|
9993
|
-
|
|
10321
|
+
mkdirSync7(dirname8(path), { recursive: true });
|
|
9994
10322
|
}
|
|
9995
10323
|
function writeText(path, content) {
|
|
9996
10324
|
ensureParent(path);
|
|
9997
|
-
|
|
10325
|
+
writeFileSync9(path, content);
|
|
9998
10326
|
}
|
|
9999
10327
|
function tryParseJson(text) {
|
|
10000
10328
|
if (!text) return null;
|
|
@@ -10020,12 +10348,12 @@ function readSymlinkTarget(path) {
|
|
|
10020
10348
|
}
|
|
10021
10349
|
function ensureSymlink(path, target, dryRun) {
|
|
10022
10350
|
if (existsSync8(path)) {
|
|
10023
|
-
const stat =
|
|
10351
|
+
const stat = lstatSync10(path);
|
|
10024
10352
|
if (stat.isSymbolicLink()) {
|
|
10025
10353
|
const current = readSymlinkTarget(path);
|
|
10026
10354
|
if (current === target) return { changed: false };
|
|
10027
10355
|
if (!dryRun) {
|
|
10028
|
-
|
|
10356
|
+
unlinkSync6(path);
|
|
10029
10357
|
symlinkSync3(target, path);
|
|
10030
10358
|
}
|
|
10031
10359
|
return { changed: true };
|
|
@@ -10036,12 +10364,12 @@ function ensureSymlink(path, target, dryRun) {
|
|
|
10036
10364
|
return { changed: true };
|
|
10037
10365
|
}
|
|
10038
10366
|
function bootstrapAgentsFile(repoRoot, dryRun) {
|
|
10039
|
-
const agentsPath =
|
|
10367
|
+
const agentsPath = join12(repoRoot, "AGENTS.md");
|
|
10040
10368
|
if (existsSync8(agentsPath)) return { changedFiles: [], details: [] };
|
|
10041
10369
|
for (const file of ["CLAUDE.md", "GEMINI.md"]) {
|
|
10042
|
-
const source =
|
|
10370
|
+
const source = join12(repoRoot, file);
|
|
10043
10371
|
if (!existsSync8(source)) continue;
|
|
10044
|
-
const stat =
|
|
10372
|
+
const stat = lstatSync10(source);
|
|
10045
10373
|
if (stat.isSymbolicLink()) continue;
|
|
10046
10374
|
if (stat.isFile()) {
|
|
10047
10375
|
if (!dryRun) renameSync4(source, agentsPath);
|
|
@@ -10049,9 +10377,9 @@ function bootstrapAgentsFile(repoRoot, dryRun) {
|
|
|
10049
10377
|
}
|
|
10050
10378
|
return { changedFiles: [], details: [], blocked: `${file} exists but is not a regular file; cannot promote to AGENTS.md` };
|
|
10051
10379
|
}
|
|
10052
|
-
const readmePath =
|
|
10380
|
+
const readmePath = join12(repoRoot, "README.md");
|
|
10053
10381
|
if (existsSync8(readmePath)) {
|
|
10054
|
-
const stat =
|
|
10382
|
+
const stat = lstatSync10(readmePath);
|
|
10055
10383
|
if (!stat.isFile()) return { changedFiles: [], details: [], blocked: "README.md exists but is not a regular file; cannot copy to AGENTS.md" };
|
|
10056
10384
|
if (!dryRun) copyFileSync(readmePath, agentsPath);
|
|
10057
10385
|
return { changedFiles: [agentsPath], details: ["Copied README.md to AGENTS.md before wiring agent-file symlinks"] };
|
|
@@ -10089,11 +10417,11 @@ function yamlGet(text, keyPath) {
|
|
|
10089
10417
|
return "";
|
|
10090
10418
|
}
|
|
10091
10419
|
function discoverRoles(repoRoot) {
|
|
10092
|
-
const rolesDir =
|
|
10420
|
+
const rolesDir = join12(repoRoot, "agents", "hermes");
|
|
10093
10421
|
if (!existsSync8(rolesDir)) return [];
|
|
10094
|
-
return
|
|
10095
|
-
const roleDir =
|
|
10096
|
-
const roleYamlPath =
|
|
10422
|
+
return readdirSync8(rolesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => {
|
|
10423
|
+
const roleDir = join12(rolesDir, entry.name);
|
|
10424
|
+
const roleYamlPath = join12(roleDir, "role.yaml");
|
|
10097
10425
|
if (!existsSync8(roleYamlPath)) return null;
|
|
10098
10426
|
const text = readText(roleYamlPath);
|
|
10099
10427
|
const runtimeRepoRaw = yamlGet(text, "runtime.github_repo");
|
|
@@ -10126,26 +10454,26 @@ function discoverRoles(repoRoot) {
|
|
|
10126
10454
|
}).filter((value) => Boolean(value));
|
|
10127
10455
|
}
|
|
10128
10456
|
function templateScript(ctx, name) {
|
|
10129
|
-
const source =
|
|
10457
|
+
const source = join12(ctx.pjanglerRoot, ".mise", "scripts", name);
|
|
10130
10458
|
return existsSync8(source) ? readText(source) : void 0;
|
|
10131
10459
|
}
|
|
10132
10460
|
function templateVersioningScript(ctx) {
|
|
10133
10461
|
return templateScript(ctx, "versioning.sh");
|
|
10134
10462
|
}
|
|
10135
10463
|
function templateLinkAgentfilesScript(ctx) {
|
|
10136
|
-
const source =
|
|
10464
|
+
const source = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", ".mise", "scripts", "link-agentfiles.sh");
|
|
10137
10465
|
return existsSync8(source) ? readText(source) : templateScript(ctx, "link-agentfiles.sh");
|
|
10138
10466
|
}
|
|
10139
10467
|
function templateMaterializeEnvScript(ctx) {
|
|
10140
|
-
const source =
|
|
10468
|
+
const source = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", MATERIALIZE_ENV_SCRIPT_REL);
|
|
10141
10469
|
return existsSync8(source) ? readText(source) : void 0;
|
|
10142
10470
|
}
|
|
10143
10471
|
function resolveAgentHooksLayer(ctx) {
|
|
10144
10472
|
const override = process.env.PJ_AGENT_HOOKS_LAYER;
|
|
10145
10473
|
if (override === "0" || override === "false") return false;
|
|
10146
10474
|
if (override === "1" || override === "true") return true;
|
|
10147
|
-
if (existsSync8(
|
|
10148
|
-
return !existsSync8(
|
|
10475
|
+
if (existsSync8(join12(ctx.repoRoot, ".agents", "hooks", "sync.py"))) return true;
|
|
10476
|
+
return !existsSync8(join12(ctx.homeDir, ".agents", "hooks"));
|
|
10149
10477
|
}
|
|
10150
10478
|
function evaluateMiseConditionals(template, agentHooksLayer) {
|
|
10151
10479
|
const out = [];
|
|
@@ -10171,13 +10499,13 @@ function evaluateMiseConditionals(template, agentHooksLayer) {
|
|
|
10171
10499
|
}
|
|
10172
10500
|
function renderGeneratedProjectMiseToml(ctx, template) {
|
|
10173
10501
|
const project = readProjectJson(ctx);
|
|
10174
|
-
const projectName = String(project?.project_name ??
|
|
10502
|
+
const projectName = String(project?.project_name ?? basename5(ctx.repoRoot) ?? "project");
|
|
10175
10503
|
return evaluateMiseConditionals(template, resolveAgentHooksLayer(ctx)).replace(/\{%\s*raw\s*%\}([\s\S]*?)\{%\s*endraw\s*%\}/g, "$1").replace(/\{\{\s*project_name\s*\}\}/g, projectName);
|
|
10176
10504
|
}
|
|
10177
10505
|
function ensureMiseTomlFromTemplate(ctx, changedFiles) {
|
|
10178
|
-
const targetPath =
|
|
10506
|
+
const targetPath = join12(ctx.repoRoot, "mise.toml");
|
|
10179
10507
|
if (existsSync8(targetPath)) return null;
|
|
10180
|
-
const sourcePath =
|
|
10508
|
+
const sourcePath = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", "mise.toml.jinja");
|
|
10181
10509
|
if (!existsSync8(sourcePath)) return null;
|
|
10182
10510
|
const rendered = renderGeneratedProjectMiseToml(ctx, readText(sourcePath));
|
|
10183
10511
|
changedFiles.push(targetPath);
|
|
@@ -10185,12 +10513,12 @@ function ensureMiseTomlFromTemplate(ctx, changedFiles) {
|
|
|
10185
10513
|
return rendered;
|
|
10186
10514
|
}
|
|
10187
10515
|
function templateCommonProjectText(ctx, rel) {
|
|
10188
|
-
const path =
|
|
10516
|
+
const path = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", rel);
|
|
10189
10517
|
return existsSync8(path) ? readText(path) : void 0;
|
|
10190
10518
|
}
|
|
10191
10519
|
function lstatIfPresent(path) {
|
|
10192
10520
|
try {
|
|
10193
|
-
return
|
|
10521
|
+
return lstatSync10(path);
|
|
10194
10522
|
} catch (error) {
|
|
10195
10523
|
if (error.code === "ENOENT") return void 0;
|
|
10196
10524
|
throw error;
|
|
@@ -10198,7 +10526,7 @@ function lstatIfPresent(path) {
|
|
|
10198
10526
|
}
|
|
10199
10527
|
function templateVersionFilesConf(ctx, repoRoot) {
|
|
10200
10528
|
if (isObsidianPluginRepo(ctx, repoRoot)) return OBSIDIAN_VERSION_FILES_CONF;
|
|
10201
|
-
const packageJson =
|
|
10529
|
+
const packageJson = join12(repoRoot, "package.json");
|
|
10202
10530
|
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
10531
|
}
|
|
10204
10532
|
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 +10536,7 @@ function recordedProjectType(ctx) {
|
|
|
10208
10536
|
function isObsidianPluginRepo(ctx, repoRoot) {
|
|
10209
10537
|
const recorded = recordedProjectType(ctx);
|
|
10210
10538
|
if (recorded !== void 0) return recorded === "obsidian-plugin";
|
|
10211
|
-
const manifest = record2(tryParseJson(safeReadText2(
|
|
10539
|
+
const manifest = record2(tryParseJson(safeReadText2(join12(repoRoot, "manifest.json"))));
|
|
10212
10540
|
return typeof manifest.id === "string" && typeof manifest.minAppVersion === "string";
|
|
10213
10541
|
}
|
|
10214
10542
|
function unsupportedVersionTypes(conf, script) {
|
|
@@ -10669,7 +10997,7 @@ function describeRewrite(before, after) {
|
|
|
10669
10997
|
return difference ? `migrate would still rewrite ${difference} in mise.toml` : "migrate would still re-lay out mise.toml (its meaning is unchanged)";
|
|
10670
10998
|
}
|
|
10671
10999
|
function readProjectJson(ctx) {
|
|
10672
|
-
return tryParseJson(safeReadText2(
|
|
11000
|
+
return tryParseJson(safeReadText2(join12(ctx.repoRoot, ".project.json")));
|
|
10673
11001
|
}
|
|
10674
11002
|
function readDeclaredAgents(ctx) {
|
|
10675
11003
|
const project = readProjectJson(ctx);
|
|
@@ -10686,7 +11014,7 @@ function readDeclaredAgents(ctx) {
|
|
|
10686
11014
|
});
|
|
10687
11015
|
}
|
|
10688
11016
|
function readRoleYamlAt(roleDir) {
|
|
10689
|
-
const roleYamlPath =
|
|
11017
|
+
const roleYamlPath = join12(roleDir, "role.yaml");
|
|
10690
11018
|
if (!existsSync8(roleYamlPath)) return null;
|
|
10691
11019
|
const text = readText(roleYamlPath);
|
|
10692
11020
|
return {
|
|
@@ -10698,7 +11026,7 @@ function readRoleYamlAt(roleDir) {
|
|
|
10698
11026
|
}
|
|
10699
11027
|
function declaredRoleIsUnprovisioned(repoRoot, roleDir) {
|
|
10700
11028
|
if (!roleDir) return true;
|
|
10701
|
-
return !existsSync8(
|
|
11029
|
+
return !existsSync8(join12(resolve6(repoRoot, roleDir), "role.yaml"));
|
|
10702
11030
|
}
|
|
10703
11031
|
function validateDeclaredAgent(ctx, declared) {
|
|
10704
11032
|
const details = [];
|
|
@@ -10723,11 +11051,11 @@ function validateDeclaredAgent(ctx, declared) {
|
|
|
10723
11051
|
details.push(`agents.${declared.agentId} should map to agent_id ${roleYaml.agentId}`);
|
|
10724
11052
|
}
|
|
10725
11053
|
if (roleYaml.providerName) {
|
|
10726
|
-
const dispatcher =
|
|
11054
|
+
const dispatcher = join12(roleDir, ".scripts", "lib", "ticket-provider.sh");
|
|
10727
11055
|
if (!existsSync8(dispatcher)) {
|
|
10728
11056
|
details.push(`agents.${declared.agentId} provider dispatcher ${relative9(ctx.repoRoot, dispatcher)} missing`);
|
|
10729
11057
|
}
|
|
10730
|
-
const provider =
|
|
11058
|
+
const provider = join12(roleDir, ".scripts", "providers", `${roleYaml.providerName}.sh`);
|
|
10731
11059
|
if (!existsSync8(provider)) {
|
|
10732
11060
|
details.push(`agents.${declared.agentId} provider script ${relative9(ctx.repoRoot, provider)} missing`);
|
|
10733
11061
|
}
|
|
@@ -10743,7 +11071,7 @@ function repoPathNamesRoot(value, repoRoot, homeDir) {
|
|
|
10743
11071
|
function canonicalProjectJson(ctx) {
|
|
10744
11072
|
const roles = discoverRoles(ctx.repoRoot);
|
|
10745
11073
|
const existing = readProjectJson(ctx) ?? {};
|
|
10746
|
-
const slug = normalizeProjectId(existing.project_id ?? existing.project_slug ?? slugifyRepoName(
|
|
11074
|
+
const slug = normalizeProjectId(existing.project_id ?? existing.project_slug ?? slugifyRepoName(basename5(ctx.repoRoot)));
|
|
10747
11075
|
const firstRole = roles[0];
|
|
10748
11076
|
const ticketProvider = {
|
|
10749
11077
|
...existing.ticket_provider,
|
|
@@ -10813,8 +11141,8 @@ function canonicalProjectJson(ctx) {
|
|
|
10813
11141
|
};
|
|
10814
11142
|
}
|
|
10815
11143
|
function projectJsonFinding(ctx) {
|
|
10816
|
-
const projectPath =
|
|
10817
|
-
const planeJsonPath =
|
|
11144
|
+
const projectPath = join12(ctx.repoRoot, ".project.json");
|
|
11145
|
+
const planeJsonPath = join12(ctx.repoRoot, ".plane.json");
|
|
10818
11146
|
const details = [];
|
|
10819
11147
|
const data = readProjectJson(ctx);
|
|
10820
11148
|
const roles = discoverRoles(ctx.repoRoot);
|
|
@@ -10885,7 +11213,7 @@ var BMAD_DIST_TAGS_TTL_MS = 60 * 60 * 1e3;
|
|
|
10885
11213
|
var DEFAULT_BMAD_MODULES = ["bmm", "bmb", "cis"];
|
|
10886
11214
|
var BMAD_INSTALL_TOOLS = SUPPORTED_BMAD_TOOLS;
|
|
10887
11215
|
function manifestBmadModules(repoRoot) {
|
|
10888
|
-
const manifestPath =
|
|
11216
|
+
const manifestPath = join12(repoRoot, "_bmad", "_config", "manifest.yaml");
|
|
10889
11217
|
const raw = safeReadText2(manifestPath);
|
|
10890
11218
|
if (raw === null) return { status: "absent" };
|
|
10891
11219
|
try {
|
|
@@ -10913,7 +11241,7 @@ function manifestBmadModules(repoRoot) {
|
|
|
10913
11241
|
}
|
|
10914
11242
|
}
|
|
10915
11243
|
function configuredBmadModuleSelection(repoRoot) {
|
|
10916
|
-
const raw = safeReadText2(
|
|
11244
|
+
const raw = safeReadText2(join12(repoRoot, "_bmad", "config.toml"));
|
|
10917
11245
|
if (raw === null) return void 0;
|
|
10918
11246
|
const modules = [...raw.matchAll(/^\[modules\.([A-Za-z0-9][A-Za-z0-9_-]*)\]\s*$/gm)].map((match) => match[1]);
|
|
10919
11247
|
return { modules: Array.from(new Set(modules)).filter((name) => name !== "g33"), hasG33: modules.includes("g33") };
|
|
@@ -10924,24 +11252,60 @@ function selectedBmadModules(repoRoot) {
|
|
|
10924
11252
|
if (manifest.status === "invalid") throw new Error(manifest.error);
|
|
10925
11253
|
return configuredBmadModuleSelection(repoRoot)?.modules ?? [...DEFAULT_BMAD_MODULES];
|
|
10926
11254
|
}
|
|
10927
|
-
|
|
10928
|
-
|
|
10929
|
-
|
|
10930
|
-
|
|
10931
|
-
|
|
10932
|
-
|
|
10933
|
-
|
|
11255
|
+
var BMAD_RUNTIME_SCRIPT_SENTINELS = ["resolve_config.py", "config_utils.py"];
|
|
11256
|
+
function consolidatedBmadConfig(repoRoot) {
|
|
11257
|
+
const path = join12(repoRoot, "_bmad", "config.yaml");
|
|
11258
|
+
if (!existsSync8(path)) return { status: "absent" };
|
|
11259
|
+
let parsed;
|
|
11260
|
+
try {
|
|
11261
|
+
parsed = YAML4.parse(readText(path));
|
|
11262
|
+
} catch (error) {
|
|
11263
|
+
return { status: "unusable", reason: `could not be parsed: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}` };
|
|
11264
|
+
}
|
|
11265
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
11266
|
+
return { status: "unusable", reason: "is not a YAML mapping" };
|
|
11267
|
+
}
|
|
11268
|
+
return { status: "mapping", sections: new Set(Object.keys(parsed)) };
|
|
11269
|
+
}
|
|
11270
|
+
function bmadSentinelGaps(repoRoot, modules = selectedBmadModules(repoRoot)) {
|
|
11271
|
+
const bmad = join12(repoRoot, "_bmad");
|
|
11272
|
+
const gaps = [];
|
|
11273
|
+
const absent = (...segments) => !existsSync8(join12(bmad, ...segments));
|
|
11274
|
+
if (absent("config.toml")) gaps.push({ path: "config.toml", detail: "_bmad/config.toml" });
|
|
11275
|
+
for (const script of BMAD_RUNTIME_SCRIPT_SENTINELS) {
|
|
11276
|
+
if (absent("scripts", script)) gaps.push({ path: join12("scripts", script), detail: `_bmad/scripts/${script}` });
|
|
11277
|
+
}
|
|
11278
|
+
if (absent("_config", "manifest.yaml")) {
|
|
11279
|
+
gaps.push({
|
|
11280
|
+
path: join12("_config", "manifest.yaml"),
|
|
11281
|
+
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"
|
|
11282
|
+
});
|
|
11283
|
+
}
|
|
11284
|
+
let consolidated;
|
|
11285
|
+
for (const module of modules) {
|
|
11286
|
+
if (!absent(module, "config.yaml")) continue;
|
|
11287
|
+
consolidated ??= consolidatedBmadConfig(repoRoot);
|
|
11288
|
+
if (consolidated.status === "mapping" && consolidated.sections.has(module)) continue;
|
|
11289
|
+
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)`;
|
|
11290
|
+
gaps.push({ path: join12(module, "config.yaml"), detail: `_bmad/${module}/config.yaml${why}` });
|
|
11291
|
+
}
|
|
11292
|
+
return gaps;
|
|
11293
|
+
}
|
|
11294
|
+
function bmadScaffoldIssues(repoRoot, modules) {
|
|
11295
|
+
const gaps = bmadSentinelGaps(repoRoot, modules);
|
|
11296
|
+
const nameIssues = bmadProjectNameIssues(repoRoot);
|
|
11297
|
+
return { gaps, nameIssues, details: [...gaps.map((gap) => gap.detail), ...nameIssues.details] };
|
|
10934
11298
|
}
|
|
10935
11299
|
function canonicalBmadProjectName(repoRoot) {
|
|
10936
11300
|
const project = readProjectJson({ repoRoot });
|
|
10937
11301
|
const declared = typeof project?.project_name === "string" ? project.project_name.trim() : "";
|
|
10938
|
-
return declared ||
|
|
11302
|
+
return declared || basename5(repoRoot);
|
|
10939
11303
|
}
|
|
10940
11304
|
function bmadProjectNameIssues(repoRoot) {
|
|
10941
11305
|
const expected = canonicalBmadProjectName(repoRoot);
|
|
10942
11306
|
const paths = [];
|
|
10943
11307
|
const details = [];
|
|
10944
|
-
const configToml =
|
|
11308
|
+
const configToml = join12(repoRoot, "_bmad", "config.toml");
|
|
10945
11309
|
const tomlText = safeReadText2(configToml);
|
|
10946
11310
|
const tomlMatch = tomlText?.match(/^project_name\s*=\s*"((?:\\.|[^"\\])*)"\s*$/m);
|
|
10947
11311
|
let tomlName;
|
|
@@ -10956,16 +11320,16 @@ function bmadProjectNameIssues(repoRoot) {
|
|
|
10956
11320
|
paths.push(configToml);
|
|
10957
11321
|
details.push(`_bmad/config.toml project_name must be ${JSON.stringify(expected)}`);
|
|
10958
11322
|
}
|
|
10959
|
-
const bmadRoot =
|
|
11323
|
+
const bmadRoot = join12(repoRoot, "_bmad");
|
|
10960
11324
|
if (existsSync8(bmadRoot)) {
|
|
10961
11325
|
let declared;
|
|
10962
11326
|
try {
|
|
10963
11327
|
declared = selectedBmadModules(repoRoot);
|
|
10964
11328
|
} catch {
|
|
10965
|
-
declared =
|
|
11329
|
+
declared = readdirSync8(bmadRoot);
|
|
10966
11330
|
}
|
|
10967
11331
|
for (const name of new Set(declared)) {
|
|
10968
|
-
const configPath =
|
|
11332
|
+
const configPath = join12(bmadRoot, name, "config.yaml");
|
|
10969
11333
|
const raw = safeReadText2(configPath);
|
|
10970
11334
|
if (raw === null) continue;
|
|
10971
11335
|
let actual;
|
|
@@ -10985,7 +11349,7 @@ function bmadProjectNameIssues(repoRoot) {
|
|
|
10985
11349
|
function isCurrentActivation(path, activations) {
|
|
10986
11350
|
if (activations.owned.has(path)) return true;
|
|
10987
11351
|
try {
|
|
10988
|
-
if (activations.owned.has(
|
|
11352
|
+
if (activations.owned.has(join12(realpathSync4(dirname8(path)), basename5(path)))) return true;
|
|
10989
11353
|
return activations.targets.has(realpathSync4(path));
|
|
10990
11354
|
} catch {
|
|
10991
11355
|
return false;
|
|
@@ -10998,26 +11362,26 @@ async function skillActivations(ctx) {
|
|
|
10998
11362
|
function evictLegacyBmadPackState(ctx, changedFiles, activations) {
|
|
10999
11363
|
const details = [];
|
|
11000
11364
|
const skillDirs = [
|
|
11001
|
-
|
|
11002
|
-
...SUPPORTED_CLI_ROOTS.map((root) =>
|
|
11365
|
+
join12(ctx.repoRoot, ".agents", "skills"),
|
|
11366
|
+
...SUPPORTED_CLI_ROOTS.map((root) => join12(ctx.repoRoot, root, "skills"))
|
|
11003
11367
|
];
|
|
11004
11368
|
for (const dir of skillDirs) {
|
|
11005
11369
|
const dirStat = lstatIfPresent(dir);
|
|
11006
11370
|
if (!dirStat || dirStat.isSymbolicLink() || !dirStat.isDirectory()) continue;
|
|
11007
11371
|
let names;
|
|
11008
11372
|
try {
|
|
11009
|
-
names =
|
|
11373
|
+
names = readdirSync8(dir);
|
|
11010
11374
|
} catch {
|
|
11011
11375
|
continue;
|
|
11012
11376
|
}
|
|
11013
11377
|
for (const name of names) {
|
|
11014
11378
|
if (!name.startsWith(BMAD_SKILL_NAME_PREFIX)) continue;
|
|
11015
|
-
const path =
|
|
11379
|
+
const path = join12(dir, name);
|
|
11016
11380
|
if (!lstatIfPresent(path)?.isSymbolicLink()) continue;
|
|
11017
11381
|
if (isCurrentActivation(path, activations)) continue;
|
|
11018
11382
|
changedFiles.push(path);
|
|
11019
11383
|
details.push(`removed retired BMAD pack symlink ${relative9(ctx.repoRoot, path)}`);
|
|
11020
|
-
if (!ctx.dryRun)
|
|
11384
|
+
if (!ctx.dryRun) unlinkSync6(path);
|
|
11021
11385
|
}
|
|
11022
11386
|
}
|
|
11023
11387
|
return details;
|
|
@@ -11030,10 +11394,11 @@ function bmadInstallerInvocation(version = BMAD_INSTALLER_VERSION) {
|
|
|
11030
11394
|
prefixArgs: ["-y", `${BMAD_NPM_PACKAGE}@${version}`]
|
|
11031
11395
|
};
|
|
11032
11396
|
}
|
|
11033
|
-
function resolveBmadInstallerPackage(version = BMAD_INSTALLER_VERSION) {
|
|
11397
|
+
function resolveBmadInstallerPackage(version = BMAD_INSTALLER_VERSION, options = {}) {
|
|
11398
|
+
const timeoutMs = options.timeoutMs ?? 18e4;
|
|
11034
11399
|
const packageAt = (dir) => {
|
|
11035
11400
|
try {
|
|
11036
|
-
const parsed = JSON.parse(
|
|
11401
|
+
const parsed = JSON.parse(readFileSync11(join12(dir, "package.json"), "utf8"));
|
|
11037
11402
|
return parsed.name === BMAD_NPM_PACKAGE ? typeof parsed.version === "string" ? parsed.version : "unknown" : void 0;
|
|
11038
11403
|
} catch {
|
|
11039
11404
|
return void 0;
|
|
@@ -11065,12 +11430,13 @@ function resolveBmadInstallerPackage(version = BMAD_INSTALLER_VERSION) {
|
|
|
11065
11430
|
`node -e "process.stdout.write(require('fs').realpathSync(process.argv[1]))" "$(command -v ${BMAD_NPM_PACKAGE})"`
|
|
11066
11431
|
], {
|
|
11067
11432
|
encoding: "utf8",
|
|
11068
|
-
timeout:
|
|
11433
|
+
timeout: timeoutMs,
|
|
11069
11434
|
env: { ...process.env, npm_config_update_notifier: "false", npm_config_fund: "false" }
|
|
11070
11435
|
});
|
|
11071
11436
|
const entry = probe.stdout?.trim().split("\n").pop()?.trim();
|
|
11072
11437
|
if (probe.status !== 0 || !entry) {
|
|
11073
|
-
const
|
|
11438
|
+
const timedOut = probe.error?.code === "ETIMEDOUT";
|
|
11439
|
+
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
11440
|
return { reason: `could not resolve ${BMAD_NPM_PACKAGE}@${version}: ${detail || "no installer path reported"}` };
|
|
11075
11441
|
}
|
|
11076
11442
|
const root = dirname8(dirname8(dirname8(entry)));
|
|
@@ -11078,6 +11444,34 @@ function resolveBmadInstallerPackage(version = BMAD_INSTALLER_VERSION) {
|
|
|
11078
11444
|
if (!found) return { reason: `${entry} is not inside a ${BMAD_NPM_PACKAGE} package` };
|
|
11079
11445
|
return { root, entry, version: found };
|
|
11080
11446
|
}
|
|
11447
|
+
var BMAD_SETUP_SCRIPTS_PROBE_TIMEOUT_MS = 1e4;
|
|
11448
|
+
var BMAD_SETUP_SCRIPTS_REMEDY = "re-run the BMAD installer (npx bmad-method install) or upgrade via `pj migrate bmad.version`";
|
|
11449
|
+
function bmadSetupScriptsState(repoRoot) {
|
|
11450
|
+
const inspection = inspectBmadSetupScripts(repoRoot, BMAD_INSTALLER_VERSION, () => readInstalledBmadVersion(repoRoot));
|
|
11451
|
+
const decline = (why, missing, remedy2) => ({
|
|
11452
|
+
kind: "decline",
|
|
11453
|
+
summary: `Setup script recovery unavailable: ${why}${missing.length ? ` (missing from _bmad/scripts: ${missing.join(", ")})` : ""}; ${remedy2}`
|
|
11454
|
+
});
|
|
11455
|
+
switch (inspection.state) {
|
|
11456
|
+
case "present":
|
|
11457
|
+
return { kind: "present" };
|
|
11458
|
+
case "unsafe":
|
|
11459
|
+
return { kind: "refuse", error: `BMAD script recovery refused: ${inspection.error}` };
|
|
11460
|
+
case "decline":
|
|
11461
|
+
return decline(inspection.why, inspection.missing, BMAD_SETUP_SCRIPTS_REMEDY);
|
|
11462
|
+
case "missing":
|
|
11463
|
+
break;
|
|
11464
|
+
}
|
|
11465
|
+
const override = Boolean(process.env.PJ_BMAD_INSTALLER?.trim());
|
|
11466
|
+
const remedy = override ? "unset PJ_BMAD_INSTALLER to enable recovery from the pinned package" : `retry when npm is reachable, or ${BMAD_SETUP_SCRIPTS_REMEDY}`;
|
|
11467
|
+
const pkg = resolveBmadInstallerPackage(BMAD_INSTALLER_VERSION, { timeoutMs: BMAD_SETUP_SCRIPTS_PROBE_TIMEOUT_MS });
|
|
11468
|
+
if (!pkg.root) return decline(pkg.reason ?? "installer is not a bmad-method package", inspection.missing, remedy);
|
|
11469
|
+
const plan = planBmadSetupScripts(repoRoot, pkg, BMAD_INSTALLER_VERSION);
|
|
11470
|
+
if (plan.error) {
|
|
11471
|
+
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 };
|
|
11472
|
+
}
|
|
11473
|
+
return plan.files.length ? { kind: "missing", pkg, files: plan.files } : { kind: "present" };
|
|
11474
|
+
}
|
|
11081
11475
|
function bmadInstallerArgs(repoRoot, modules = selectedBmadModules(repoRoot), extraArgs = []) {
|
|
11082
11476
|
const installerModules = modules.length ? modules.join(",") : "core";
|
|
11083
11477
|
return [
|
|
@@ -11096,7 +11490,7 @@ function bmadInstallerArgs(repoRoot, modules = selectedBmadModules(repoRoot), ex
|
|
|
11096
11490
|
}
|
|
11097
11491
|
function bmadUpgradeChannelArgs(repoRoot, version) {
|
|
11098
11492
|
if (!/^\d+\.\d+\.\d+$/.test(version)) return [];
|
|
11099
|
-
const raw = safeReadText2(
|
|
11493
|
+
const raw = safeReadText2(join12(repoRoot, "_bmad", "_config", "manifest.yaml"));
|
|
11100
11494
|
if (!raw) return [];
|
|
11101
11495
|
let modules;
|
|
11102
11496
|
try {
|
|
@@ -11166,7 +11560,7 @@ function bmadChannelTarget(distTags) {
|
|
|
11166
11560
|
return next ? { version: next, channel: BMAD_TARGET_CHANNEL } : void 0;
|
|
11167
11561
|
}
|
|
11168
11562
|
function readInstalledBmadVersion(repoRoot) {
|
|
11169
|
-
const raw = safeReadText2(
|
|
11563
|
+
const raw = safeReadText2(join12(repoRoot, "_bmad", "_config", "manifest.yaml"));
|
|
11170
11564
|
if (!raw) return void 0;
|
|
11171
11565
|
try {
|
|
11172
11566
|
const parsed = YAML4.parse(raw);
|
|
@@ -11177,8 +11571,8 @@ function readInstalledBmadVersion(repoRoot) {
|
|
|
11177
11571
|
}
|
|
11178
11572
|
}
|
|
11179
11573
|
function bmadCachePath(homeDir) {
|
|
11180
|
-
const cacheRoot = process.env.XDG_CACHE_HOME?.trim() ||
|
|
11181
|
-
return
|
|
11574
|
+
const cacheRoot = process.env.XDG_CACHE_HOME?.trim() || join12(homeDir, ".cache");
|
|
11575
|
+
return join12(cacheRoot, "pjangler", "bmad-dist-tags.json");
|
|
11182
11576
|
}
|
|
11183
11577
|
function readBmadDistTagsCache(homeDir) {
|
|
11184
11578
|
const raw = safeReadText2(bmadCachePath(homeDir));
|
|
@@ -11220,8 +11614,8 @@ function resolveBmadDistTags(homeDir) {
|
|
|
11220
11614
|
if (fetched) {
|
|
11221
11615
|
try {
|
|
11222
11616
|
const path = bmadCachePath(homeDir);
|
|
11223
|
-
|
|
11224
|
-
|
|
11617
|
+
mkdirSync7(dirname8(path), { recursive: true });
|
|
11618
|
+
writeFileSync9(path, JSON.stringify({ fetchedAt: Date.now(), distTags: fetched }, null, 2));
|
|
11225
11619
|
} catch {
|
|
11226
11620
|
}
|
|
11227
11621
|
return { distTags: fetched, stale: false };
|
|
@@ -11345,8 +11739,8 @@ var UNSUPPORTED_BMAD_ROOTS = {
|
|
|
11345
11739
|
".zencoder": "zencoder"
|
|
11346
11740
|
};
|
|
11347
11741
|
function unsupportedRootAttestation(repoRoot, rootName) {
|
|
11348
|
-
const root =
|
|
11349
|
-
const stat =
|
|
11742
|
+
const root = join12(repoRoot, rootName);
|
|
11743
|
+
const stat = lstatSync10(root);
|
|
11350
11744
|
if (!stat.isDirectory() || stat.isSymbolicLink()) return { safe: false, reason: `${rootName} is not a regular generated directory` };
|
|
11351
11745
|
const installerTool = UNSUPPORTED_BMAD_ROOTS[rootName];
|
|
11352
11746
|
if (!installedBmadTools(repoRoot).has(installerTool)) {
|
|
@@ -11365,7 +11759,7 @@ var OPTIONAL_TEMPLATE_SCRIPTS = [
|
|
|
11365
11759
|
function optionalTemplateScriptIssues(ctx) {
|
|
11366
11760
|
const issues = [];
|
|
11367
11761
|
for (const rel of OPTIONAL_TEMPLATE_SCRIPTS) {
|
|
11368
|
-
const path =
|
|
11762
|
+
const path = join12(ctx.repoRoot, rel);
|
|
11369
11763
|
if (!existsSync8(path)) continue;
|
|
11370
11764
|
const expected = templateCommonProjectText(ctx, rel);
|
|
11371
11765
|
if (expected === void 0) continue;
|
|
@@ -11376,7 +11770,7 @@ function optionalTemplateScriptIssues(ctx) {
|
|
|
11376
11770
|
function refreshOptionalTemplateScripts(ctx) {
|
|
11377
11771
|
const changed = [];
|
|
11378
11772
|
for (const rel of OPTIONAL_TEMPLATE_SCRIPTS) {
|
|
11379
|
-
const path =
|
|
11773
|
+
const path = join12(ctx.repoRoot, rel);
|
|
11380
11774
|
if (!existsSync8(path)) continue;
|
|
11381
11775
|
const expected = templateCommonProjectText(ctx, rel);
|
|
11382
11776
|
if (expected === void 0 || safeReadText2(path) === expected) continue;
|
|
@@ -11394,13 +11788,13 @@ function createMiseChecks() {
|
|
|
11394
11788
|
id: "mise.config-root",
|
|
11395
11789
|
title: "mise config_root + AGENTS link hooks",
|
|
11396
11790
|
audit: (ctx) => {
|
|
11397
|
-
const misePath =
|
|
11791
|
+
const misePath = join12(ctx.repoRoot, "mise.toml");
|
|
11398
11792
|
if (!existsSync8(misePath)) {
|
|
11399
11793
|
return { id: "mise.config-root", title: "mise config_root + AGENTS link hooks", status: "fail", summary: "mise.toml missing", details: [], fixable: true };
|
|
11400
11794
|
}
|
|
11401
11795
|
const text = readText(misePath);
|
|
11402
11796
|
const details = [];
|
|
11403
|
-
const linkAgentfilesPath =
|
|
11797
|
+
const linkAgentfilesPath = join12(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
|
|
11404
11798
|
if (!existsSync8(linkAgentfilesPath)) details.push(".mise/scripts/link-agentfiles.sh missing");
|
|
11405
11799
|
const toml = [];
|
|
11406
11800
|
const manual = [];
|
|
@@ -11448,7 +11842,7 @@ function createMiseChecks() {
|
|
|
11448
11842
|
};
|
|
11449
11843
|
},
|
|
11450
11844
|
migrate: (ctx, finding2) => {
|
|
11451
|
-
const path =
|
|
11845
|
+
const path = join12(ctx.repoRoot, "mise.toml");
|
|
11452
11846
|
const changedFiles = [];
|
|
11453
11847
|
const details = [];
|
|
11454
11848
|
if (!existsSync8(path)) {
|
|
@@ -11467,7 +11861,7 @@ function createMiseChecks() {
|
|
|
11467
11861
|
if (!ctx.dryRun) writeText(path, plan.text);
|
|
11468
11862
|
text = plan.text;
|
|
11469
11863
|
}
|
|
11470
|
-
const linkAgentfilesPath =
|
|
11864
|
+
const linkAgentfilesPath = join12(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
|
|
11471
11865
|
const expectedScript = templateLinkAgentfilesScript(ctx);
|
|
11472
11866
|
if (expectedScript === void 0) {
|
|
11473
11867
|
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 +11899,9 @@ function createMiseChecks() {
|
|
|
11505
11899
|
title: "managed mise versioning block",
|
|
11506
11900
|
audit: (ctx) => {
|
|
11507
11901
|
const details = [];
|
|
11508
|
-
const misePath =
|
|
11509
|
-
const versioningPath =
|
|
11510
|
-
const manifestPath =
|
|
11902
|
+
const misePath = join12(ctx.repoRoot, "mise.toml");
|
|
11903
|
+
const versioningPath = join12(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
|
|
11904
|
+
const manifestPath = join12(ctx.repoRoot, ".mise", "version-files.conf");
|
|
11511
11905
|
const text = safeReadText2(misePath);
|
|
11512
11906
|
if (!text?.includes("# >>> mise-versioning >>>")) details.push("mise versioning managed block missing");
|
|
11513
11907
|
if (!existsSync8(versioningPath)) details.push(".mise/scripts/versioning.sh missing");
|
|
@@ -11533,7 +11927,7 @@ function createMiseChecks() {
|
|
|
11533
11927
|
migrate: (ctx, finding2) => {
|
|
11534
11928
|
const changedFiles = [];
|
|
11535
11929
|
const details = [];
|
|
11536
|
-
const misePath =
|
|
11930
|
+
const misePath = join12(ctx.repoRoot, "mise.toml");
|
|
11537
11931
|
if (!existsSync8(misePath)) {
|
|
11538
11932
|
if (ensureMiseTomlFromTemplate(ctx, changedFiles) === null) {
|
|
11539
11933
|
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 +11945,7 @@ function createMiseChecks() {
|
|
|
11551
11945
|
if (!changedFiles.includes(misePath)) changedFiles.push(misePath);
|
|
11552
11946
|
if (!ctx.dryRun) writeText(misePath, versioningPlan.text);
|
|
11553
11947
|
}
|
|
11554
|
-
const versioningPath =
|
|
11948
|
+
const versioningPath = join12(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
|
|
11555
11949
|
const expectedScript = templateVersioningScript(ctx);
|
|
11556
11950
|
if (expectedScript === void 0) {
|
|
11557
11951
|
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 +11957,7 @@ function createMiseChecks() {
|
|
|
11563
11957
|
chmodSync3(versioningPath, 493);
|
|
11564
11958
|
}
|
|
11565
11959
|
}
|
|
11566
|
-
const manifestPath =
|
|
11960
|
+
const manifestPath = join12(ctx.repoRoot, ".mise", "version-files.conf");
|
|
11567
11961
|
const expectedManifest = templateVersionFilesConf(ctx, ctx.repoRoot);
|
|
11568
11962
|
if (safeReadText2(manifestPath) !== expectedManifest) {
|
|
11569
11963
|
changedFiles.push(manifestPath);
|
|
@@ -11625,7 +12019,7 @@ var RETIRED_SKILL_SCRIPTS = [
|
|
|
11625
12019
|
".mise/scripts/unlink-project-skills-from-clis.sh"
|
|
11626
12020
|
];
|
|
11627
12021
|
function retiredSkillsScripts(ctx) {
|
|
11628
|
-
return RETIRED_SKILL_SCRIPTS.map((path) =>
|
|
12022
|
+
return RETIRED_SKILL_SCRIPTS.map((path) => join12(ctx.repoRoot, path)).filter((path) => Boolean(lstatIfPresent(path)));
|
|
11629
12023
|
}
|
|
11630
12024
|
function skillsWiringFindings(text) {
|
|
11631
12025
|
if (text === null) return { fixable: [], manual: [] };
|
|
@@ -11676,7 +12070,7 @@ function createAgentHooksChecks() {
|
|
|
11676
12070
|
title: "Skillex project skills",
|
|
11677
12071
|
audit: async (ctx) => {
|
|
11678
12072
|
const finding2 = await auditProjectSkills(ctx);
|
|
11679
|
-
const mise = safeReadText2(
|
|
12073
|
+
const mise = safeReadText2(join12(ctx.repoRoot, "mise.toml"));
|
|
11680
12074
|
const registryField = await legacyRegistryField(ctx);
|
|
11681
12075
|
const registryDrop = registryField ? await dropLegacyRegistryField({ ...ctx, dryRun: true }) : void 0;
|
|
11682
12076
|
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 +12091,7 @@ function createAgentHooksChecks() {
|
|
|
11697
12091
|
const changedFiles = [];
|
|
11698
12092
|
const details = [];
|
|
11699
12093
|
const result2 = (status, summary) => ({ id: finding2.id, title: finding2.title, status, summary, changedFiles: [...new Set(changedFiles)], details });
|
|
11700
|
-
let currentMise = safeReadText2(
|
|
12094
|
+
let currentMise = safeReadText2(join12(ctx.repoRoot, "mise.toml"));
|
|
11701
12095
|
if (currentMise === null) {
|
|
11702
12096
|
const template = templateCommonProjectText(ctx, "mise.toml.jinja");
|
|
11703
12097
|
if (template === void 0) return result2("blocked", "Generated-project mise template is unavailable");
|
|
@@ -11712,9 +12106,9 @@ function createAgentHooksChecks() {
|
|
|
11712
12106
|
const stat = lstatIfPresent(path);
|
|
11713
12107
|
if (!stat || stat.isDirectory()) continue;
|
|
11714
12108
|
changedFiles.push(path);
|
|
11715
|
-
if (!ctx.dryRun)
|
|
12109
|
+
if (!ctx.dryRun) unlinkSync6(path);
|
|
11716
12110
|
}
|
|
11717
|
-
const misePath =
|
|
12111
|
+
const misePath = join12(ctx.repoRoot, "mise.toml");
|
|
11718
12112
|
const nextMise = upsertLinkAgentfilesBlock(currentMise, ctx);
|
|
11719
12113
|
if (safeReadText2(misePath) !== nextMise) {
|
|
11720
12114
|
changedFiles.push(misePath);
|
|
@@ -11745,9 +12139,9 @@ function createAgentHooksChecks() {
|
|
|
11745
12139
|
id: "sot.agent-symlinks",
|
|
11746
12140
|
title: "AGENTS/CLAUDE/GEMINI symlink contract",
|
|
11747
12141
|
audit: (ctx) => {
|
|
11748
|
-
const agentsPath =
|
|
12142
|
+
const agentsPath = join12(ctx.repoRoot, "AGENTS.md");
|
|
11749
12143
|
if (!existsSync8(agentsPath)) {
|
|
11750
|
-
const fallbackSources = ["CLAUDE.md", "GEMINI.md", "README.md"].filter((file) => existsSync8(
|
|
12144
|
+
const fallbackSources = ["CLAUDE.md", "GEMINI.md", "README.md"].filter((file) => existsSync8(join12(ctx.repoRoot, file)));
|
|
11751
12145
|
if (fallbackSources.length === 0) {
|
|
11752
12146
|
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
12147
|
}
|
|
@@ -11762,7 +12156,7 @@ function createAgentHooksChecks() {
|
|
|
11762
12156
|
}
|
|
11763
12157
|
const details = [];
|
|
11764
12158
|
for (const file of ["CLAUDE.md", "GEMINI.md"]) {
|
|
11765
|
-
const full =
|
|
12159
|
+
const full = join12(ctx.repoRoot, file);
|
|
11766
12160
|
const target = readSymlinkTarget(full);
|
|
11767
12161
|
if (target !== "AGENTS.md") details.push(`${file} should be a symlink to AGENTS.md`);
|
|
11768
12162
|
}
|
|
@@ -11786,7 +12180,7 @@ function createAgentHooksChecks() {
|
|
|
11786
12180
|
return { id: finding2.id, title: finding2.title, status: "blocked", summary: "AGENTS.md missing; cannot derive canonical agent file", changedFiles, details: [bootstrap.blocked] };
|
|
11787
12181
|
}
|
|
11788
12182
|
for (const file of ["CLAUDE.md", "GEMINI.md"]) {
|
|
11789
|
-
const full =
|
|
12183
|
+
const full = join12(ctx.repoRoot, file);
|
|
11790
12184
|
const result2 = ensureSymlink(full, "AGENTS.md", ctx.dryRun);
|
|
11791
12185
|
if (result2.blocked) blockedDetails.push(result2.blocked);
|
|
11792
12186
|
if (result2.changed) changedFiles.push(full);
|
|
@@ -11813,7 +12207,7 @@ function createProjectJsonChecks() {
|
|
|
11813
12207
|
const changedFiles = [];
|
|
11814
12208
|
const blockedDetails = [];
|
|
11815
12209
|
const droppedDetails = [];
|
|
11816
|
-
const path =
|
|
12210
|
+
const path = join12(ctx.repoRoot, ".project.json");
|
|
11817
12211
|
const existing = readProjectJson(ctx) ?? {};
|
|
11818
12212
|
const canonical = canonicalProjectJson(ctx);
|
|
11819
12213
|
const preservedDetails = [];
|
|
@@ -11845,7 +12239,7 @@ function createProjectJsonChecks() {
|
|
|
11845
12239
|
changedFiles.push(path);
|
|
11846
12240
|
if (!ctx.dryRun) writeText(path, expected);
|
|
11847
12241
|
}
|
|
11848
|
-
const planeJson =
|
|
12242
|
+
const planeJson = join12(ctx.repoRoot, ".plane.json");
|
|
11849
12243
|
if (existsSync8(planeJson)) {
|
|
11850
12244
|
const backup = `${planeJson}.migrated-backup`;
|
|
11851
12245
|
if (existsSync8(backup)) {
|
|
@@ -11877,10 +12271,10 @@ function createMiseOpInjectChecks() {
|
|
|
11877
12271
|
const details = [];
|
|
11878
12272
|
let envOpNeedsHands = false;
|
|
11879
12273
|
const optedOut = envMaterializationOptedOut(ctx);
|
|
11880
|
-
const envOpPath =
|
|
12274
|
+
const envOpPath = join12(ctx.repoRoot, ".env.op");
|
|
11881
12275
|
const envOpExists = existsSync8(envOpPath);
|
|
11882
12276
|
const envOp = envOpExists ? readText(envOpPath) : void 0;
|
|
11883
|
-
const gitignore = safeReadText2(
|
|
12277
|
+
const gitignore = safeReadText2(join12(ctx.repoRoot, ".gitignore"));
|
|
11884
12278
|
if (!envOpExists) {
|
|
11885
12279
|
details.push(".env.op missing");
|
|
11886
12280
|
} else if (!envOp?.trim()) {
|
|
@@ -11900,7 +12294,7 @@ function createMiseOpInjectChecks() {
|
|
|
11900
12294
|
if (!gitignore?.includes(".env\n") && !gitignore?.includes(".env\r\n")) details.push(".gitignore should ignore .env");
|
|
11901
12295
|
if (!gitignore?.includes(".env.*")) details.push(".gitignore should ignore .env.*");
|
|
11902
12296
|
if (!gitignore?.includes("!.env.op")) details.push(".gitignore should unignore .env.op");
|
|
11903
|
-
const misePath =
|
|
12297
|
+
const misePath = join12(ctx.repoRoot, "mise.toml");
|
|
11904
12298
|
const miseText = safeReadText2(misePath);
|
|
11905
12299
|
if (!miseText) {
|
|
11906
12300
|
details.push("mise.toml missing for .env materialization hook");
|
|
@@ -11923,7 +12317,7 @@ function createMiseOpInjectChecks() {
|
|
|
11923
12317
|
else if (plan.text !== miseText && !mise.length) mise.push(describeRewrite(miseText, plan.text));
|
|
11924
12318
|
details.push(...mise);
|
|
11925
12319
|
}
|
|
11926
|
-
const materializePath =
|
|
12320
|
+
const materializePath = join12(ctx.repoRoot, MATERIALIZE_ENV_SCRIPT_REL);
|
|
11927
12321
|
const expectedMaterializer = templateMaterializeEnvScript(ctx);
|
|
11928
12322
|
if (optedOut) {
|
|
11929
12323
|
if (existsSync8(materializePath)) details.push(`${MATERIALIZE_ENV_SCRIPT_REL} is present, but .project.json declares secrets.materialize_env: false`);
|
|
@@ -11931,7 +12325,7 @@ function createMiseOpInjectChecks() {
|
|
|
11931
12325
|
details.push("pjangler package is missing the managed materialize-env.sh source");
|
|
11932
12326
|
} else if (safeReadText2(materializePath) !== expectedMaterializer) {
|
|
11933
12327
|
details.push(`${MATERIALIZE_ENV_SCRIPT_REL} missing or drifted`);
|
|
11934
|
-
} else if ((
|
|
12328
|
+
} else if ((lstatSync10(materializePath).mode & 73) === 0) {
|
|
11935
12329
|
details.push(`${MATERIALIZE_ENV_SCRIPT_REL} is not executable`);
|
|
11936
12330
|
}
|
|
11937
12331
|
return {
|
|
@@ -11947,8 +12341,8 @@ function createMiseOpInjectChecks() {
|
|
|
11947
12341
|
const changedFiles = [];
|
|
11948
12342
|
const details = [];
|
|
11949
12343
|
let envOpNeedsHands = false;
|
|
11950
|
-
const envOpPath =
|
|
11951
|
-
const canonicalEnvOpPath =
|
|
12344
|
+
const envOpPath = join12(ctx.repoRoot, ".env.op");
|
|
12345
|
+
const canonicalEnvOpPath = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", ".env.op");
|
|
11952
12346
|
if (!existsSync8(canonicalEnvOpPath)) {
|
|
11953
12347
|
return { id: finding2.id, title: finding2.title, status: "blocked", summary: "pjangler package is missing the neutral .env.op template", changedFiles: [], details: [] };
|
|
11954
12348
|
}
|
|
@@ -11977,7 +12371,7 @@ function createMiseOpInjectChecks() {
|
|
|
11977
12371
|
}
|
|
11978
12372
|
}
|
|
11979
12373
|
}
|
|
11980
|
-
const gitignorePath =
|
|
12374
|
+
const gitignorePath = join12(ctx.repoRoot, ".gitignore");
|
|
11981
12375
|
const gitignore = safeReadText2(gitignorePath) ?? "";
|
|
11982
12376
|
const requiredBlock = `# Secrets \u2014 .env is materialized from .env.op by \`op inject\` on mise enter,
|
|
11983
12377
|
# staged through a mktemp file and moved into place only on success.
|
|
@@ -11990,7 +12384,7 @@ function createMiseOpInjectChecks() {
|
|
|
11990
12384
|
changedFiles.push(gitignorePath);
|
|
11991
12385
|
if (!ctx.dryRun) writeText(gitignorePath, `${gitignore.replace(/\s*$/, "")}${gitignore.trim() ? "\n\n" : ""}${requiredBlock}`);
|
|
11992
12386
|
}
|
|
11993
|
-
const misePath =
|
|
12387
|
+
const misePath = join12(ctx.repoRoot, "mise.toml");
|
|
11994
12388
|
let currentMise = safeReadText2(misePath);
|
|
11995
12389
|
if (currentMise === null) {
|
|
11996
12390
|
const initialized = ensureMiseTomlFromTemplate(ctx, changedFiles);
|
|
@@ -12007,7 +12401,7 @@ function createMiseOpInjectChecks() {
|
|
|
12007
12401
|
if (!changedFiles.includes(misePath)) changedFiles.push(misePath);
|
|
12008
12402
|
if (!ctx.dryRun) writeText(misePath, opPlan.text);
|
|
12009
12403
|
}
|
|
12010
|
-
const materializePath =
|
|
12404
|
+
const materializePath = join12(ctx.repoRoot, MATERIALIZE_ENV_SCRIPT_REL);
|
|
12011
12405
|
const expectedMaterializer = templateMaterializeEnvScript(ctx);
|
|
12012
12406
|
if (optedOut) {
|
|
12013
12407
|
if (existsSync8(materializePath)) {
|
|
@@ -12016,7 +12410,7 @@ function createMiseOpInjectChecks() {
|
|
|
12016
12410
|
}
|
|
12017
12411
|
} else if (!expectedMaterializer) {
|
|
12018
12412
|
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) && (
|
|
12413
|
+
} else if (safeReadText2(materializePath) !== expectedMaterializer || existsSync8(materializePath) && (lstatSync10(materializePath).mode & 73) === 0) {
|
|
12020
12414
|
changedFiles.push(materializePath);
|
|
12021
12415
|
if (!ctx.dryRun) {
|
|
12022
12416
|
writeText(materializePath, expectedMaterializer);
|
|
@@ -12078,7 +12472,7 @@ function createProjectProvenanceChecks() {
|
|
|
12078
12472
|
title: ".copier-answers.yml provenance + drift report",
|
|
12079
12473
|
audit: (ctx) => {
|
|
12080
12474
|
const details = [];
|
|
12081
|
-
const path =
|
|
12475
|
+
const path = join12(ctx.repoRoot, ".copier-answers.yml");
|
|
12082
12476
|
const text = safeReadText2(path);
|
|
12083
12477
|
const project = readProjectJson(ctx);
|
|
12084
12478
|
if (!text) {
|
|
@@ -12106,11 +12500,11 @@ function createProjectProvenanceChecks() {
|
|
|
12106
12500
|
migrate: (ctx, finding2) => {
|
|
12107
12501
|
const changedFiles = [];
|
|
12108
12502
|
const project = canonicalProjectJson(ctx);
|
|
12109
|
-
const path =
|
|
12503
|
+
const path = join12(ctx.repoRoot, ".copier-answers.yml");
|
|
12110
12504
|
const current = safeReadText2(path);
|
|
12111
12505
|
const existing = current ? YAML4.parseDocument(current) : void 0;
|
|
12112
12506
|
const document = existing && !existing.errors.length && YAML4.isMap(existing.contents) ? existing : new YAML4.Document({});
|
|
12113
|
-
document.set("_src_path",
|
|
12507
|
+
document.set("_src_path", join12(ctx.pjanglerRoot, "templates", "commonproject"));
|
|
12114
12508
|
document.set("project_description", String(project.project_description));
|
|
12115
12509
|
document.set("project_name", String(project.project_name));
|
|
12116
12510
|
document.set("ticket_provider", String(project.ticket_provider?.type ?? "plane"));
|
|
@@ -12135,9 +12529,9 @@ ${body}`;
|
|
|
12135
12529
|
}
|
|
12136
12530
|
function supportedCliProjectionIssues(repoRoot, plan) {
|
|
12137
12531
|
const issues = [];
|
|
12138
|
-
const managedSkills =
|
|
12532
|
+
const managedSkills = join12(repoRoot, ".agents", "skills");
|
|
12139
12533
|
for (const rootName of SUPPORTED_CLI_ROOTS) {
|
|
12140
|
-
const root =
|
|
12534
|
+
const root = join12(repoRoot, rootName);
|
|
12141
12535
|
const rootStat = lstatIfPresent(root);
|
|
12142
12536
|
if (!rootStat) {
|
|
12143
12537
|
issues.push(`${rootName} missing`);
|
|
@@ -12147,7 +12541,7 @@ function supportedCliProjectionIssues(repoRoot, plan) {
|
|
|
12147
12541
|
issues.push(`${rootName} must be a real configuration directory`);
|
|
12148
12542
|
continue;
|
|
12149
12543
|
}
|
|
12150
|
-
const skills =
|
|
12544
|
+
const skills = join12(root, "skills");
|
|
12151
12545
|
const skillsStat = lstatIfPresent(skills);
|
|
12152
12546
|
if (!skillsStat) {
|
|
12153
12547
|
issues.push(`${rootName}/skills missing`);
|
|
@@ -12184,9 +12578,9 @@ function supportedCliProjectionIssues(repoRoot, plan) {
|
|
|
12184
12578
|
issues.push(`${rootName}/skills is not a directory`);
|
|
12185
12579
|
continue;
|
|
12186
12580
|
}
|
|
12187
|
-
const hasGeneratedSkill = existsSync8(projectedSkills) &&
|
|
12188
|
-
const skillFile =
|
|
12189
|
-
return existsSync8(skillFile) &&
|
|
12581
|
+
const hasGeneratedSkill = existsSync8(projectedSkills) && readdirSync8(projectedSkills).some((name) => {
|
|
12582
|
+
const skillFile = join12(projectedSkills, name, "SKILL.md");
|
|
12583
|
+
return existsSync8(skillFile) && lstatSync10(skillFile).isFile();
|
|
12190
12584
|
});
|
|
12191
12585
|
if (!hasGeneratedSkill) issues.push(`${rootName}/skills contains no BMAD skill configuration`);
|
|
12192
12586
|
}
|
|
@@ -12212,7 +12606,7 @@ var LEGACY_CLI_GITIGNORE_LINES = /* @__PURE__ */ new Set([
|
|
|
12212
12606
|
"# some CLIs get a real directory here and some get a symlink."
|
|
12213
12607
|
]);
|
|
12214
12608
|
function supportedCliGitignoreIssues(repoRoot) {
|
|
12215
|
-
const lines = (safeReadText2(
|
|
12609
|
+
const lines = (safeReadText2(join12(repoRoot, ".gitignore")) ?? "").split(/\r?\n/);
|
|
12216
12610
|
const legacy = lines.filter((line) => LEGACY_CLI_GITIGNORE_LINES.has(line.trim()));
|
|
12217
12611
|
return [
|
|
12218
12612
|
...legacy.length ? [`.gitignore contains ${legacy.length} legacy client-root override line(s); only .agents is canonical`] : [],
|
|
@@ -12220,7 +12614,7 @@ function supportedCliGitignoreIssues(repoRoot) {
|
|
|
12220
12614
|
];
|
|
12221
12615
|
}
|
|
12222
12616
|
function ensureSupportedCliGitignore(ctx) {
|
|
12223
|
-
const path =
|
|
12617
|
+
const path = join12(ctx.repoRoot, ".gitignore");
|
|
12224
12618
|
const current = safeReadText2(path) ?? "";
|
|
12225
12619
|
const kept = current.split(/\r?\n/).filter((line) => {
|
|
12226
12620
|
const trimmed2 = line.trim();
|
|
@@ -12262,6 +12656,24 @@ function withInstallerSkillHandoff(check) {
|
|
|
12262
12656
|
}
|
|
12263
12657
|
};
|
|
12264
12658
|
}
|
|
12659
|
+
function withScaffoldPostcondition(check) {
|
|
12660
|
+
if (check.id !== "bmad.scaffold") return check;
|
|
12661
|
+
return {
|
|
12662
|
+
...check,
|
|
12663
|
+
migrate: async (ctx, finding2) => {
|
|
12664
|
+
const result2 = await check.migrate(ctx, finding2);
|
|
12665
|
+
if (ctx.dryRun || result2.status !== "applied" && result2.status !== "noop") return result2;
|
|
12666
|
+
const after = await check.audit(ctx);
|
|
12667
|
+
if (after.status === "pass" || after.status === "skip") return result2;
|
|
12668
|
+
return {
|
|
12669
|
+
...result2,
|
|
12670
|
+
status: "partial",
|
|
12671
|
+
summary: after.summary,
|
|
12672
|
+
details: [...result2.details, `still failing after the install: ${after.summary}`, ...after.details]
|
|
12673
|
+
};
|
|
12674
|
+
}
|
|
12675
|
+
};
|
|
12676
|
+
}
|
|
12265
12677
|
function createBmadChecks() {
|
|
12266
12678
|
return [
|
|
12267
12679
|
{
|
|
@@ -12279,15 +12691,8 @@ function createBmadChecks() {
|
|
|
12279
12691
|
fixable: false
|
|
12280
12692
|
};
|
|
12281
12693
|
}
|
|
12282
|
-
const targetRoot = join11(ctx.repoRoot, "_bmad");
|
|
12283
12694
|
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
|
-
];
|
|
12695
|
+
const { details } = bmadScaffoldIssues(ctx.repoRoot, selectedModules);
|
|
12291
12696
|
return {
|
|
12292
12697
|
id: "bmad.scaffold",
|
|
12293
12698
|
title: "BMAD modules/docs scaffold",
|
|
@@ -12326,7 +12731,7 @@ function createBmadChecks() {
|
|
|
12326
12731
|
bmadCompanion: preservation
|
|
12327
12732
|
};
|
|
12328
12733
|
if (ctx.dryRun) {
|
|
12329
|
-
const
|
|
12734
|
+
const issues = bmadScaffoldIssues(ctx.repoRoot, selectedModules);
|
|
12330
12735
|
const companion = installedBmad ? await runG33PreUpdateCompanion(ctx.repoRoot) : await runG33Companion(ctx.repoRoot, true, "bmad-install");
|
|
12331
12736
|
if (installedBmad && ["unavailable", "conflict", "error"].includes(companion.status)) {
|
|
12332
12737
|
return {
|
|
@@ -12339,8 +12744,8 @@ function createBmadChecks() {
|
|
|
12339
12744
|
bmadCompanion: companion
|
|
12340
12745
|
};
|
|
12341
12746
|
}
|
|
12342
|
-
changedFiles.push(...
|
|
12343
|
-
changedFiles.push(...
|
|
12747
|
+
changedFiles.push(...issues.gaps.map((gap) => join12(ctx.repoRoot, "_bmad", gap.path)));
|
|
12748
|
+
changedFiles.push(...issues.nameIssues.paths);
|
|
12344
12749
|
const reservedPreview = installedBmad ? evictReservedModuleEntries(ctx.repoRoot, true) : void 0;
|
|
12345
12750
|
changedFiles.push(...reservedPreview?.changedFiles ?? []);
|
|
12346
12751
|
return {
|
|
@@ -12358,9 +12763,10 @@ function createBmadChecks() {
|
|
|
12358
12763
|
};
|
|
12359
12764
|
}
|
|
12360
12765
|
if (installedBmad && g33CompanionEnabled(ctx.repoRoot)) {
|
|
12766
|
+
const before2 = bmadScaffoldIssues(ctx.repoRoot, selectedModules);
|
|
12361
12767
|
const expectedChangedPaths2 = [
|
|
12362
|
-
...
|
|
12363
|
-
...
|
|
12768
|
+
...before2.gaps.map((gap) => join12(ctx.repoRoot, "_bmad", gap.path)),
|
|
12769
|
+
...before2.nameIssues.paths
|
|
12364
12770
|
];
|
|
12365
12771
|
const pkg = resolveBmadInstallerPackage(BMAD_INSTALLER_VERSION);
|
|
12366
12772
|
const scope = deriveBmadUpdateScope(ctx.repoRoot, { tools: BMAD_INSTALL_TOOLS, installer: pkg, companionSurfaces: G33_OWNED_SURFACES });
|
|
@@ -12401,16 +12807,17 @@ function createBmadChecks() {
|
|
|
12401
12807
|
bmadCompanion: update.companion
|
|
12402
12808
|
};
|
|
12403
12809
|
}
|
|
12810
|
+
const before = bmadScaffoldIssues(ctx.repoRoot, selectedModules);
|
|
12404
12811
|
const expectedChangedPaths = [
|
|
12405
|
-
...
|
|
12406
|
-
...
|
|
12812
|
+
...before.gaps.map((gap) => join12(ctx.repoRoot, "_bmad", gap.path)),
|
|
12813
|
+
...before.nameIssues.paths
|
|
12407
12814
|
];
|
|
12408
12815
|
const evicted = evictLegacyBmadPackState(ctx, changedFiles, await skillActivations(ctx));
|
|
12409
12816
|
const reserved = installedBmad ? evictReservedModuleEntries(ctx.repoRoot, false) : void 0;
|
|
12410
12817
|
if (reserved) evicted.unshift(...reserved.details);
|
|
12411
12818
|
const install = await runBmadInstall(ctx.repoRoot, selectedModules, BMAD_INSTALLER_VERSION, channelArgs);
|
|
12412
12819
|
if (!install.ok && reserved?.original !== void 0) {
|
|
12413
|
-
|
|
12820
|
+
writeFileSync9(reserved.changedFiles[0], reserved.original, "utf8");
|
|
12414
12821
|
evicted.push("restored _bmad/_config/manifest.yaml after the failed install");
|
|
12415
12822
|
}
|
|
12416
12823
|
if (!install.ok) {
|
|
@@ -12435,6 +12842,58 @@ function createBmadChecks() {
|
|
|
12435
12842
|
};
|
|
12436
12843
|
}
|
|
12437
12844
|
},
|
|
12845
|
+
{
|
|
12846
|
+
id: "bmad.setup-scripts",
|
|
12847
|
+
title: "BMAD ignored installer setup scripts",
|
|
12848
|
+
audit: (ctx) => {
|
|
12849
|
+
if (!isInstalledBmadProject(ctx.repoRoot)) return {
|
|
12850
|
+
id: "bmad.setup-scripts",
|
|
12851
|
+
title: "BMAD ignored installer setup scripts",
|
|
12852
|
+
status: "skip",
|
|
12853
|
+
summary: "No installed BMAD project",
|
|
12854
|
+
details: [],
|
|
12855
|
+
fixable: false
|
|
12856
|
+
};
|
|
12857
|
+
const state = bmadSetupScriptsState(ctx.repoRoot);
|
|
12858
|
+
const id = "bmad.setup-scripts", title = "BMAD ignored installer setup scripts";
|
|
12859
|
+
switch (state.kind) {
|
|
12860
|
+
case "present":
|
|
12861
|
+
return { id, title, status: "pass", summary: "BMAD installer scripts are present", details: [], fixable: false };
|
|
12862
|
+
case "decline":
|
|
12863
|
+
return { id, title, status: "skip", summary: state.summary, details: [], fixable: false };
|
|
12864
|
+
case "refuse":
|
|
12865
|
+
return { id, title, status: "fail", summary: state.error, details: [state.error], fixable: false };
|
|
12866
|
+
case "missing":
|
|
12867
|
+
return {
|
|
12868
|
+
id,
|
|
12869
|
+
title,
|
|
12870
|
+
status: "fail",
|
|
12871
|
+
summary: "Ignored BMAD installer scripts are missing",
|
|
12872
|
+
details: state.files.map((path) => relative9(ctx.repoRoot, path)),
|
|
12873
|
+
fixable: true
|
|
12874
|
+
};
|
|
12875
|
+
}
|
|
12876
|
+
},
|
|
12877
|
+
migrate: (ctx, finding2) => {
|
|
12878
|
+
const title = finding2.title, id = finding2.id;
|
|
12879
|
+
if (finding2.status === "skip") return { id, title, status: "skipped", summary: finding2.summary, details: [], changedFiles: [] };
|
|
12880
|
+
const state = bmadSetupScriptsState(ctx.repoRoot);
|
|
12881
|
+
if (state.kind === "decline") return { id, title, status: "skipped", summary: state.summary, details: [], changedFiles: [] };
|
|
12882
|
+
if (state.kind === "refuse") return { id, title, status: "blocked", summary: state.error, details: [state.error], changedFiles: [] };
|
|
12883
|
+
if (state.kind === "present") return { id, title, status: "noop", summary: "BMAD installer scripts already present", details: [], changedFiles: [] };
|
|
12884
|
+
const { pkg, files } = state;
|
|
12885
|
+
if (ctx.dryRun) return {
|
|
12886
|
+
id,
|
|
12887
|
+
title,
|
|
12888
|
+
status: "applied",
|
|
12889
|
+
summary: "Would restore ignored scripts from verified pinned bmad-method package",
|
|
12890
|
+
details: [`bmad-method@${BMAD_INSTALLER_VERSION} (${pkg.root})`],
|
|
12891
|
+
changedFiles: files
|
|
12892
|
+
};
|
|
12893
|
+
const applied = applyBmadSetupScripts(ctx.repoRoot, pkg, BMAD_INSTALLER_VERSION);
|
|
12894
|
+
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 };
|
|
12895
|
+
}
|
|
12896
|
+
},
|
|
12438
12897
|
{
|
|
12439
12898
|
id: "bmad.version",
|
|
12440
12899
|
title: "BMAD version currency",
|
|
@@ -12445,7 +12904,7 @@ function createBmadChecks() {
|
|
|
12445
12904
|
id: "bmad.version",
|
|
12446
12905
|
title: "BMAD version currency",
|
|
12447
12906
|
status: "skip",
|
|
12448
|
-
summary: existsSync8(
|
|
12907
|
+
summary: existsSync8(join12(ctx.repoRoot, "_bmad")) ? "BMAD installed but version manifest unreadable" : "No BMAD install present",
|
|
12449
12908
|
details: [],
|
|
12450
12909
|
fixable: false
|
|
12451
12910
|
};
|
|
@@ -12533,7 +12992,7 @@ function createBmadChecks() {
|
|
|
12533
12992
|
}
|
|
12534
12993
|
const installed = readInstalledBmadVersion(ctx.repoRoot);
|
|
12535
12994
|
const available = ctx.bmadVersionPin?.trim() ?? bmadChannelTarget(resolveBmadDistTags(ctx.homeDir)?.distTags)?.version;
|
|
12536
|
-
const manifestPath =
|
|
12995
|
+
const manifestPath = join12(ctx.repoRoot, "_bmad", "_config", "manifest.yaml");
|
|
12537
12996
|
const manifestSelection = manifestBmadModules(ctx.repoRoot);
|
|
12538
12997
|
if (manifestSelection.status === "invalid") {
|
|
12539
12998
|
return {
|
|
@@ -12631,7 +13090,7 @@ function createBmadChecks() {
|
|
|
12631
13090
|
const reserved = evictReservedModuleEntries(ctx.repoRoot, false);
|
|
12632
13091
|
const install = await runBmadInstall(ctx.repoRoot, selectedModules, available ?? BMAD_TARGET_CHANNEL, channelArgs);
|
|
12633
13092
|
if (!install.ok) {
|
|
12634
|
-
if (reserved.original !== void 0)
|
|
13093
|
+
if (reserved.original !== void 0) writeFileSync9(reserved.changedFiles[0], reserved.original, "utf8");
|
|
12635
13094
|
return {
|
|
12636
13095
|
id: finding2.id,
|
|
12637
13096
|
title: finding2.title,
|
|
@@ -12666,7 +13125,7 @@ function createBmadChecks() {
|
|
|
12666
13125
|
title: "Supported BMAD CLI projection roots",
|
|
12667
13126
|
audit: (ctx) => {
|
|
12668
13127
|
const unsupportedNames = Object.keys(UNSUPPORTED_BMAD_ROOTS);
|
|
12669
|
-
const present = unsupportedNames.filter((name) => existsSync8(
|
|
13128
|
+
const present = unsupportedNames.filter((name) => existsSync8(join12(ctx.repoRoot, name)));
|
|
12670
13129
|
const attestations = present.map((name) => ({ name, ...unsupportedRootAttestation(ctx.repoRoot, name) }));
|
|
12671
13130
|
const plan = planSkillRoots(ctx.repoRoot);
|
|
12672
13131
|
const supportedIssues = supportedCliProjectionIssues(ctx.repoRoot, plan);
|
|
@@ -12688,7 +13147,7 @@ function createBmadChecks() {
|
|
|
12688
13147
|
},
|
|
12689
13148
|
migrate: (ctx, finding2) => {
|
|
12690
13149
|
const unsupportedNames = Object.keys(UNSUPPORTED_BMAD_ROOTS);
|
|
12691
|
-
const present = unsupportedNames.filter((name) => existsSync8(
|
|
13150
|
+
const present = unsupportedNames.filter((name) => existsSync8(join12(ctx.repoRoot, name)));
|
|
12692
13151
|
const attestations = present.map((name) => ({ name, ...unsupportedRootAttestation(ctx.repoRoot, name) }));
|
|
12693
13152
|
const blocked = attestations.filter((entry) => !entry.safe);
|
|
12694
13153
|
if (blocked.length) {
|
|
@@ -12713,7 +13172,7 @@ function createBmadChecks() {
|
|
|
12713
13172
|
};
|
|
12714
13173
|
}
|
|
12715
13174
|
const gitignoreChanges = ensureSupportedCliGitignore(ctx);
|
|
12716
|
-
const removedRoots = attestations.map((entry) =>
|
|
13175
|
+
const removedRoots = attestations.map((entry) => join12(ctx.repoRoot, entry.name));
|
|
12717
13176
|
if (!ctx.dryRun) for (const path of removedRoots) rmSync5(path, { recursive: true, force: true });
|
|
12718
13177
|
const changedFiles = [.../* @__PURE__ */ new Set([...projectionResult.changedFiles, ...gitignoreChanges, ...removedRoots])].sort();
|
|
12719
13178
|
return {
|
|
@@ -12726,7 +13185,7 @@ function createBmadChecks() {
|
|
|
12726
13185
|
};
|
|
12727
13186
|
}
|
|
12728
13187
|
}
|
|
12729
|
-
].map(withInstallerSkillHandoff);
|
|
13188
|
+
].map(withInstallerSkillHandoff).map(withScaffoldPostcondition);
|
|
12730
13189
|
}
|
|
12731
13190
|
function createProjectMomoChecks() {
|
|
12732
13191
|
return [
|
|
@@ -13335,8 +13794,8 @@ var NodeRecipe = class extends Recipe {
|
|
|
13335
13794
|
// src/recipes/ProjectRecipe.ts
|
|
13336
13795
|
init_project();
|
|
13337
13796
|
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
|
|
13797
|
+
import { existsSync as existsSync18, lstatSync as lstatSync17, readFileSync as readFileSync21, rmSync as rmSync8 } from "node:fs";
|
|
13798
|
+
import { dirname as dirname17, isAbsolute as isAbsolute10, join as join24, relative as relativePath, resolve as resolve18 } from "node:path";
|
|
13340
13799
|
|
|
13341
13800
|
// src/project/ticketScope.ts
|
|
13342
13801
|
function ticketScope(ticket) {
|
|
@@ -13353,8 +13812,8 @@ init_tree_diff();
|
|
|
13353
13812
|
|
|
13354
13813
|
// src/recipes/NotebookRecipe.ts
|
|
13355
13814
|
init_project();
|
|
13356
|
-
import { existsSync as existsSync17, readFileSync as
|
|
13357
|
-
import { join as
|
|
13815
|
+
import { existsSync as existsSync17, readFileSync as readFileSync20 } from "node:fs";
|
|
13816
|
+
import { join as join23 } from "node:path";
|
|
13358
13817
|
|
|
13359
13818
|
// src/notebook/checks.ts
|
|
13360
13819
|
init_config();
|
|
@@ -13362,8 +13821,8 @@ init_notes();
|
|
|
13362
13821
|
init_output();
|
|
13363
13822
|
init_state();
|
|
13364
13823
|
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
13365
|
-
import { existsSync as existsSync14, readFileSync as
|
|
13366
|
-
import { join as
|
|
13824
|
+
import { existsSync as existsSync14, readFileSync as readFileSync18 } from "node:fs";
|
|
13825
|
+
import { join as join19 } from "node:path";
|
|
13367
13826
|
var NOTEBOOK_RULE_IDS = [
|
|
13368
13827
|
"notebook.configuration",
|
|
13369
13828
|
"notebook.binding",
|
|
@@ -13380,10 +13839,10 @@ function result(check, status, summary, changedFiles = [], details = []) {
|
|
|
13380
13839
|
return { id: check.id, title: check.title, status, summary, changedFiles, details };
|
|
13381
13840
|
}
|
|
13382
13841
|
function manifestNotebook(repo) {
|
|
13383
|
-
const path =
|
|
13842
|
+
const path = join19(repo, ".project.json");
|
|
13384
13843
|
if (!existsSync14(path)) return null;
|
|
13385
13844
|
try {
|
|
13386
|
-
const parsed = JSON.parse(
|
|
13845
|
+
const parsed = JSON.parse(readFileSync18(path, "utf8"));
|
|
13387
13846
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
13388
13847
|
const notebook = parsed.notebook;
|
|
13389
13848
|
return notebook && typeof notebook === "object" && !Array.isArray(notebook) ? notebook : null;
|
|
@@ -13612,7 +14071,7 @@ function captureAudit(ctx) {
|
|
|
13612
14071
|
function overviewMigrate(ctx, current) {
|
|
13613
14072
|
if (ctx.dryRun) return result(current, "applied", "Would reconcile drifted Project Overview note");
|
|
13614
14073
|
if (!ctx.live) return result(current, "blocked", "Reconciling the remote Overview note requires --live");
|
|
13615
|
-
const pjanglerEntry = ctx.pjanglerRoot ?
|
|
14074
|
+
const pjanglerEntry = ctx.pjanglerRoot ? join19(ctx.pjanglerRoot, "dist", "index.js") : null;
|
|
13616
14075
|
const entry = pjanglerEntry && existsSync14(pjanglerEntry) ? pjanglerEntry : "pj";
|
|
13617
14076
|
const cmd = entry === "pj" ? ["pj"] : [process.execPath, entry];
|
|
13618
14077
|
const spawned = spawnSync7(cmd[0], [...cmd.slice(1), "notebook", "overview", ctx.repoRoot], {
|
|
@@ -13664,7 +14123,7 @@ import { resolve as resolve17 } from "node:path";
|
|
|
13664
14123
|
|
|
13665
14124
|
// src/notebook/journal-key.ts
|
|
13666
14125
|
init_types();
|
|
13667
|
-
import { createHash as
|
|
14126
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
13668
14127
|
function validateJournalPeriodKey(period, key) {
|
|
13669
14128
|
if (period === "monthly") {
|
|
13670
14129
|
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 +14142,7 @@ function validateJournalPeriodKey(period, key) {
|
|
|
13683
14142
|
}
|
|
13684
14143
|
function journalNoteOperationId(period, key) {
|
|
13685
14144
|
validateJournalPeriodKey(period, key);
|
|
13686
|
-
const bytes =
|
|
14145
|
+
const bytes = createHash12("sha256").update(`pjangler-dev-journal-v1\0infra\0${period}\0${key}`).digest().subarray(0, 16);
|
|
13687
14146
|
bytes[6] = bytes[6] & 15 | 80;
|
|
13688
14147
|
bytes[8] = bytes[8] & 63 | 128;
|
|
13689
14148
|
const hex = bytes.toString("hex");
|
|
@@ -13839,15 +14298,15 @@ init_notes();
|
|
|
13839
14298
|
init_state();
|
|
13840
14299
|
init_types();
|
|
13841
14300
|
import { spawn, spawnSync as spawnSync10 } from "node:child_process";
|
|
13842
|
-
import { createHash as
|
|
13843
|
-
import { chmodSync as chmodSync6, closeSync as
|
|
13844
|
-
import { basename as
|
|
14301
|
+
import { createHash as createHash13, randomUUID as randomUUID5 } from "node:crypto";
|
|
14302
|
+
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";
|
|
14303
|
+
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
14304
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
13846
14305
|
function bundledSkillCandidates() {
|
|
13847
14306
|
const candidates = [];
|
|
13848
14307
|
let cursor = dirname16(fileURLToPath3(import.meta.url));
|
|
13849
14308
|
for (let depth = 0; depth < 8; depth++) {
|
|
13850
|
-
candidates.push(
|
|
14309
|
+
candidates.push(join22(cursor, "dist", "assets", "project-notebook-skill"), join22(cursor, "assets", "project-notebook-skill"));
|
|
13851
14310
|
const parent = dirname16(cursor);
|
|
13852
14311
|
if (parent === cursor) break;
|
|
13853
14312
|
cursor = parent;
|
|
@@ -13865,14 +14324,14 @@ function assertOwnedSkillTree(source) {
|
|
|
13865
14324
|
assertNoSymlinkComponents(source);
|
|
13866
14325
|
const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
13867
14326
|
const walk = (directory) => {
|
|
13868
|
-
const directoryStat =
|
|
14327
|
+
const directoryStat = lstatSync16(directory);
|
|
13869
14328
|
if (!directoryStat.isDirectory() || directoryStat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill source contains a non-directory component");
|
|
13870
14329
|
if (uid !== void 0 && directoryStat.uid !== uid) throw new NotebookError("CONFLICT", "Project Notebook skill source is not owned by the current user");
|
|
13871
14330
|
if (directoryStat.mode & 3586) throw new NotebookError("CONFLICT", "Project Notebook skill source has unsafe directory mode bits");
|
|
13872
|
-
for (const entry of
|
|
14331
|
+
for (const entry of readdirSync11(directory, { withFileTypes: true })) {
|
|
13873
14332
|
if (entry.isDirectory() && (entry.name === "__pycache__" || entry.name === ".git" || entry.name === "node_modules")) continue;
|
|
13874
|
-
const path =
|
|
13875
|
-
const stat =
|
|
14333
|
+
const path = join22(directory, entry.name);
|
|
14334
|
+
const stat = lstatSync16(path);
|
|
13876
14335
|
if (stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill source contains a symlink");
|
|
13877
14336
|
if (entry.isDirectory()) walk(path);
|
|
13878
14337
|
else if (!entry.isFile()) throw new NotebookError("CONFLICT", "Project Notebook skill source contains a non-regular entry");
|
|
@@ -13887,18 +14346,18 @@ function assertOwnedSkillTree(source) {
|
|
|
13887
14346
|
function enumerateSkillPayload(source) {
|
|
13888
14347
|
const result2 = [];
|
|
13889
14348
|
const walk = (directory) => {
|
|
13890
|
-
for (const entry of
|
|
14349
|
+
for (const entry of readdirSync11(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name, "en"))) {
|
|
13891
14350
|
if (entry.isDirectory() && (entry.name === "__pycache__" || entry.name === ".git" || entry.name === "node_modules")) continue;
|
|
13892
|
-
const path =
|
|
14351
|
+
const path = join22(directory, entry.name);
|
|
13893
14352
|
const rel = relative15(source, path).split(sep7).join("/");
|
|
13894
14353
|
if (!rel.includes("/") && (rel === "export-manifest.json" || rel === "SHA256SUMS" || rel === ".source.yaml")) continue;
|
|
13895
14354
|
if (!safeSkillRelativePath(rel)) throw new NotebookError("CONFLICT", `Project Notebook skill export path is unsafe: ${rel}`);
|
|
13896
|
-
const stat =
|
|
14355
|
+
const stat = lstatSync16(path);
|
|
13897
14356
|
if (stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill export contains a symlink");
|
|
13898
14357
|
if (entry.isDirectory()) walk(path);
|
|
13899
14358
|
else if (entry.isFile()) {
|
|
13900
14359
|
const expectedMode = rel.endsWith(".sh") || rel.startsWith("scripts/") ? "0755" : "0644";
|
|
13901
|
-
result2.push({ path: rel, sha256:
|
|
14360
|
+
result2.push({ path: rel, sha256: createHash13("sha256").update(readFileSync19(path)).digest("hex"), mode: expectedMode });
|
|
13902
14361
|
} else throw new NotebookError("CONFLICT", "Project Notebook skill export contains a non-regular entry");
|
|
13903
14362
|
}
|
|
13904
14363
|
};
|
|
@@ -13906,9 +14365,9 @@ function enumerateSkillPayload(source) {
|
|
|
13906
14365
|
return result2.sort((a, b) => a.path.localeCompare(b.path, "en"));
|
|
13907
14366
|
}
|
|
13908
14367
|
function parsePackedManifest(source) {
|
|
13909
|
-
const manifestPath =
|
|
14368
|
+
const manifestPath = join22(source, "export-manifest.json");
|
|
13910
14369
|
if (!existsSync15(manifestPath)) return null;
|
|
13911
|
-
const value = JSON.parse(
|
|
14370
|
+
const value = JSON.parse(readFileSync19(manifestPath, "utf8"));
|
|
13912
14371
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new NotebookError("CONFLICT", "Project Notebook skill export manifest is invalid");
|
|
13913
14372
|
const manifest = value;
|
|
13914
14373
|
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 +14376,10 @@ function parsePackedManifest(source) {
|
|
|
13917
14376
|
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
14377
|
if (!safeSkillRelativePath(entry.path) || paths.has(entry.path)) throw new NotebookError("CONFLICT", "Project Notebook skill export path is unsafe or duplicated");
|
|
13919
14378
|
paths.add(entry.path);
|
|
13920
|
-
const path =
|
|
13921
|
-
const stat =
|
|
14379
|
+
const path = join22(source, ...entry.path.split("/"));
|
|
14380
|
+
const stat = lstatSync16(path);
|
|
13922
14381
|
if (!stat.isFile() || stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill export contains a non-regular entry");
|
|
13923
|
-
const actual =
|
|
14382
|
+
const actual = createHash13("sha256").update(readFileSync19(path)).digest("hex");
|
|
13924
14383
|
if (actual !== entry.sha256) throw new NotebookError("CONFLICT", `Project Notebook skill digest mismatch: ${entry.path}`);
|
|
13925
14384
|
const actualMode = stat.mode & 511;
|
|
13926
14385
|
const executable = entry.mode === "0755";
|
|
@@ -13932,16 +14391,16 @@ function parsePackedManifest(source) {
|
|
|
13932
14391
|
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
14392
|
const sums = `${manifest.files.map((entry) => `${entry.sha256} ${entry.path}`).join("\n")}
|
|
13934
14393
|
`;
|
|
13935
|
-
if (!existsSync15(
|
|
14394
|
+
if (!existsSync15(join22(source, "SHA256SUMS")) || readFileSync19(join22(source, "SHA256SUMS"), "utf8") !== sums) throw new NotebookError("CONFLICT", "Project Notebook skill SHA256SUMS is missing or stale");
|
|
13936
14395
|
return manifest;
|
|
13937
14396
|
}
|
|
13938
14397
|
function expectedPackedSkill() {
|
|
13939
14398
|
for (const candidate of bundledSkillCandidates()) {
|
|
13940
|
-
if (!existsSync15(
|
|
14399
|
+
if (!existsSync15(join22(candidate, "export-manifest.json"))) continue;
|
|
13941
14400
|
assertOwnedSkillTree(candidate);
|
|
13942
14401
|
const manifest = parsePackedManifest(candidate);
|
|
13943
14402
|
if (!manifest) continue;
|
|
13944
|
-
const digest =
|
|
14403
|
+
const digest = createHash13("sha256").update(JSON.stringify(manifest)).digest("hex");
|
|
13945
14404
|
return { source: candidate, manifest, digest };
|
|
13946
14405
|
}
|
|
13947
14406
|
throw new NotebookError("NOT_CONFIGURED", "PJ\xE1ngler package has no verified Project Notebook skill export");
|
|
@@ -14004,13 +14463,13 @@ function verifyProjectNotebookSkillExport(source) {
|
|
|
14004
14463
|
function resolveProjectNotebookSkillSource(env2 = process.env) {
|
|
14005
14464
|
if (env2.PJ_PROJECT_NOTEBOOK_SKILL_ROOT) {
|
|
14006
14465
|
const explicit = resolve15(env2.PJ_PROJECT_NOTEBOOK_SKILL_ROOT);
|
|
14007
|
-
if (!existsSync15(
|
|
14466
|
+
if (!existsSync15(join22(explicit, "SKILL.md"))) throw new NotebookError("NOT_CONFIGURED", "Configured Project Notebook skill source is unavailable");
|
|
14008
14467
|
verifyProjectNotebookSkillExport(explicit);
|
|
14009
14468
|
return explicit;
|
|
14010
14469
|
}
|
|
14011
14470
|
if (env2.PJ_SKILLS_REGISTRY_ROOT) {
|
|
14012
14471
|
const canonical = resolve15(env2.PJ_SKILLS_REGISTRY_ROOT, "all-skills", "project-notebook");
|
|
14013
|
-
if (existsSync15(
|
|
14472
|
+
if (existsSync15(join22(canonical, "SKILL.md"))) {
|
|
14014
14473
|
verifyProjectNotebookSkillExport(canonical);
|
|
14015
14474
|
return canonical;
|
|
14016
14475
|
}
|
|
@@ -14025,7 +14484,7 @@ var REPAIR_COMMAND = "pj notebook skill --apply";
|
|
|
14025
14484
|
function probeCanonicalSkillexRootProjection(skillsRoot, link) {
|
|
14026
14485
|
let rootLink;
|
|
14027
14486
|
try {
|
|
14028
|
-
rootLink =
|
|
14487
|
+
rootLink = lstatSync16(skillsRoot);
|
|
14029
14488
|
} catch (error) {
|
|
14030
14489
|
if (error.code === "ENOENT") return { state: "absent" };
|
|
14031
14490
|
throw error;
|
|
@@ -14038,15 +14497,15 @@ function probeCanonicalSkillexRootProjection(skillsRoot, link) {
|
|
|
14038
14497
|
if (uid !== void 0 && rootLink.uid !== uid) throw new Error("owner");
|
|
14039
14498
|
const globalRoot = realpathSync10(skillsRoot);
|
|
14040
14499
|
assertNoSymlinkComponents(globalRoot);
|
|
14041
|
-
const globalStat =
|
|
14500
|
+
const globalStat = lstatSync16(globalRoot);
|
|
14042
14501
|
if (!globalStat.isDirectory() || globalStat.isSymbolicLink()) throw new Error("root-type");
|
|
14043
14502
|
if (uid !== void 0 && globalStat.uid !== uid) throw new Error("root-owner");
|
|
14044
14503
|
if (globalStat.mode & 3586) throw new Error("root-mode");
|
|
14045
14504
|
const skillSetsRoot = dirname16(globalRoot);
|
|
14046
|
-
if (
|
|
14505
|
+
if (basename10(globalRoot) !== "global" || basename10(skillSetsRoot) !== "skill-sets") throw new Error("layout");
|
|
14047
14506
|
const checkoutRoot = dirname16(skillSetsRoot);
|
|
14048
|
-
const expectedSource =
|
|
14049
|
-
const linkStat =
|
|
14507
|
+
const expectedSource = join22(checkoutRoot, "all-skills", "project-notebook");
|
|
14508
|
+
const linkStat = lstatSync16(link);
|
|
14050
14509
|
if (!linkStat.isSymbolicLink()) throw new Error("projection-type");
|
|
14051
14510
|
if (uid !== void 0 && linkStat.uid !== uid) throw new Error("projection-owner");
|
|
14052
14511
|
const projectedSource = realpathSync10(link);
|
|
@@ -14080,44 +14539,44 @@ function installPackagedProjectNotebookSkill(input = {}) {
|
|
|
14080
14539
|
const source = input.source ?? resolveProjectNotebookSkillSource(env2);
|
|
14081
14540
|
if (!source) throw new NotebookError("NOT_CONFIGURED", "Project Notebook skill source is unavailable");
|
|
14082
14541
|
const manifest = verifyProjectNotebookSkillExport(source);
|
|
14083
|
-
const digest =
|
|
14542
|
+
const digest = createHash13("sha256").update(JSON.stringify(manifest)).digest("hex");
|
|
14084
14543
|
const home = env2.HOME;
|
|
14085
14544
|
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 =
|
|
14545
|
+
const skillsRoot = join22(home, ".agents", "skills");
|
|
14546
|
+
const link = join22(skillsRoot, "project-notebook");
|
|
14088
14547
|
const probe = probeCanonicalSkillexRootProjection(skillsRoot, link);
|
|
14089
14548
|
if (probe.state === "adopted") return { installed: false, path: link, digest };
|
|
14090
14549
|
if (probe.state === "declined") return { installed: false, path: link, digest, blocked: probe.block };
|
|
14091
|
-
const dataRoot = resolve15(env2.XDG_DATA_HOME ||
|
|
14092
|
-
const payload =
|
|
14550
|
+
const dataRoot = resolve15(env2.XDG_DATA_HOME || join22(home, ".local", "share"), "pjangler", "skills", "project-notebook");
|
|
14551
|
+
const payload = join22(dataRoot, `${PJANGLER_VERSION}-${digest}`);
|
|
14093
14552
|
assertNoSymlinkComponents(dirname16(dataRoot), true);
|
|
14094
|
-
|
|
14553
|
+
mkdirSync10(dataRoot, { recursive: true, mode: 448 });
|
|
14095
14554
|
assertNoSymlinkComponents(dataRoot);
|
|
14096
|
-
const dataStat =
|
|
14555
|
+
const dataStat = lstatSync16(dataRoot);
|
|
14097
14556
|
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
14557
|
chmodSync6(dataRoot, 448);
|
|
14099
14558
|
if (!existsSync15(payload)) {
|
|
14100
|
-
const staging =
|
|
14101
|
-
|
|
14559
|
+
const staging = join22(dataRoot, `.staging-${randomUUID5()}`);
|
|
14560
|
+
mkdirSync10(staging, { recursive: false, mode: 448 });
|
|
14102
14561
|
try {
|
|
14103
14562
|
for (const entry of manifest.files) {
|
|
14104
|
-
const destination =
|
|
14105
|
-
|
|
14106
|
-
copyFileSync3(
|
|
14563
|
+
const destination = join22(staging, ...entry.path.split("/"));
|
|
14564
|
+
mkdirSync10(dirname16(destination), { recursive: true, mode: 493 });
|
|
14565
|
+
copyFileSync3(join22(source, ...entry.path.split("/")), destination);
|
|
14107
14566
|
const mode = entry.mode === "0755" ? 493 : 420;
|
|
14108
14567
|
chmodSync6(destination, mode);
|
|
14109
|
-
const fd =
|
|
14568
|
+
const fd = openSync7(destination, constants5.O_RDONLY | (constants5.O_NOFOLLOW ?? 0));
|
|
14110
14569
|
try {
|
|
14111
|
-
const current =
|
|
14570
|
+
const current = fstatSync4(fd);
|
|
14112
14571
|
if (!current.isFile()) throw new NotebookError("CONFLICT", `Installed skill entry is not regular: ${entry.path}`);
|
|
14113
14572
|
fsyncSync4(fd);
|
|
14114
14573
|
} finally {
|
|
14115
|
-
|
|
14574
|
+
closeSync7(fd);
|
|
14116
14575
|
}
|
|
14117
14576
|
}
|
|
14118
|
-
|
|
14577
|
+
writeFileSync13(join22(staging, "export-manifest.json"), `${JSON.stringify(manifest, null, 2)}
|
|
14119
14578
|
`, { mode: 420, flag: "wx" });
|
|
14120
|
-
|
|
14579
|
+
writeFileSync13(join22(staging, "SHA256SUMS"), `${manifest.files.map((entry) => `${entry.sha256} ${entry.path}`).join("\n")}
|
|
14121
14580
|
`, { mode: 420, flag: "wx" });
|
|
14122
14581
|
verifyProjectNotebookSkillExport(staging);
|
|
14123
14582
|
renameSync8(staging, payload);
|
|
@@ -14125,17 +14584,17 @@ function installPackagedProjectNotebookSkill(input = {}) {
|
|
|
14125
14584
|
if (existsSync15(staging)) rmSync7(staging, { recursive: true, force: true });
|
|
14126
14585
|
}
|
|
14127
14586
|
} else {
|
|
14128
|
-
const stat =
|
|
14587
|
+
const stat = lstatSync16(payload);
|
|
14129
14588
|
if (!stat.isDirectory() || stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Installed Project Notebook payload is not a real directory");
|
|
14130
14589
|
verifyProjectNotebookSkillExport(payload);
|
|
14131
14590
|
}
|
|
14132
14591
|
assertNoSymlinkComponents(skillsRoot, true);
|
|
14133
|
-
|
|
14592
|
+
mkdirSync10(skillsRoot, { recursive: true, mode: 448 });
|
|
14134
14593
|
assertNoSymlinkComponents(skillsRoot);
|
|
14135
14594
|
let linkExists = existsSync15(link);
|
|
14136
14595
|
if (!linkExists) {
|
|
14137
14596
|
try {
|
|
14138
|
-
|
|
14597
|
+
lstatSync16(link);
|
|
14139
14598
|
linkExists = true;
|
|
14140
14599
|
} catch {
|
|
14141
14600
|
}
|
|
@@ -14152,7 +14611,7 @@ function installPackagedProjectNotebookSkill(input = {}) {
|
|
|
14152
14611
|
repair: REPAIR_COMMAND
|
|
14153
14612
|
}
|
|
14154
14613
|
});
|
|
14155
|
-
const stat =
|
|
14614
|
+
const stat = lstatSync16(link);
|
|
14156
14615
|
if (!stat.isSymbolicLink()) return foreign(["the path is a real file or directory, not a PJ\xE1ngler-owned link"]);
|
|
14157
14616
|
const target = realpathSync10(link);
|
|
14158
14617
|
if (target !== realpathSync10(payload) && !isVerifiedCanonicalSkillexProjection(target)) {
|
|
@@ -14180,15 +14639,15 @@ function collectSupersededPayloads(dataRoot, keep) {
|
|
|
14180
14639
|
const removed = [];
|
|
14181
14640
|
let entries;
|
|
14182
14641
|
try {
|
|
14183
|
-
entries =
|
|
14642
|
+
entries = readdirSync11(dataRoot);
|
|
14184
14643
|
} catch {
|
|
14185
14644
|
return removed;
|
|
14186
14645
|
}
|
|
14187
14646
|
for (const name of entries) {
|
|
14188
14647
|
if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?-[0-9a-f]{64}$/u.test(name)) continue;
|
|
14189
|
-
const candidate =
|
|
14648
|
+
const candidate = join22(dataRoot, name);
|
|
14190
14649
|
try {
|
|
14191
|
-
const stat =
|
|
14650
|
+
const stat = lstatSync16(candidate);
|
|
14192
14651
|
if (!stat.isDirectory() || stat.isSymbolicLink()) continue;
|
|
14193
14652
|
if (kept.has(realpathSync10(candidate))) continue;
|
|
14194
14653
|
rmSync7(candidate, { recursive: true, force: true });
|
|
@@ -14202,8 +14661,8 @@ function repairProjectNotebookSkillProjection(input = {}) {
|
|
|
14202
14661
|
const env2 = input.env ?? process.env;
|
|
14203
14662
|
const home = env2.HOME;
|
|
14204
14663
|
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 =
|
|
14664
|
+
const skillsRoot = join22(home, ".agents", "skills");
|
|
14665
|
+
const link = join22(skillsRoot, "project-notebook");
|
|
14207
14666
|
const probe = probeCanonicalSkillexRootProjection(skillsRoot, link);
|
|
14208
14667
|
if (probe.state === "adopted") return { status: "clean", summary: "Canonical Skillex projection already matches the version-pinned export", source: probe.source, drift: [], changed_files: [] };
|
|
14209
14668
|
if (probe.state !== "declined") {
|
|
@@ -14216,8 +14675,8 @@ function repairProjectNotebookSkillProjection(input = {}) {
|
|
|
14216
14675
|
assertOwnedSkillTree(source);
|
|
14217
14676
|
const present = [];
|
|
14218
14677
|
const walk = (directory) => {
|
|
14219
|
-
for (const entry of
|
|
14220
|
-
const path =
|
|
14678
|
+
for (const entry of readdirSync11(directory, { withFileTypes: true })) {
|
|
14679
|
+
const path = join22(directory, entry.name);
|
|
14221
14680
|
const rel = relative15(source, path).split(sep7).join("/");
|
|
14222
14681
|
if (entry.isDirectory()) {
|
|
14223
14682
|
walk(path);
|
|
@@ -14231,11 +14690,11 @@ function repairProjectNotebookSkillProjection(input = {}) {
|
|
|
14231
14690
|
walk(source);
|
|
14232
14691
|
const stale = present.filter((rel) => !wanted.has(rel)).sort();
|
|
14233
14692
|
const changed = expected.manifest.files.filter((entry) => {
|
|
14234
|
-
const path =
|
|
14693
|
+
const path = join22(source, ...entry.path.split("/"));
|
|
14235
14694
|
if (!existsSync15(path)) return true;
|
|
14236
|
-
const stat =
|
|
14695
|
+
const stat = lstatSync16(path);
|
|
14237
14696
|
if (!stat.isFile()) return true;
|
|
14238
|
-
if (
|
|
14697
|
+
if (createHash13("sha256").update(readFileSync19(path)).digest("hex") !== entry.sha256) return true;
|
|
14239
14698
|
const mode = stat.mode & 511;
|
|
14240
14699
|
const executable = entry.mode === "0755";
|
|
14241
14700
|
return executable && (mode & 64) === 0 || !executable && (mode & 73) !== 0;
|
|
@@ -14250,15 +14709,15 @@ function repairProjectNotebookSkillProjection(input = {}) {
|
|
|
14250
14709
|
summary: `Would restore ${affected.length} file(s) in ${source} from the version-pinned export`,
|
|
14251
14710
|
source,
|
|
14252
14711
|
drift: probe.block.details,
|
|
14253
|
-
changed_files: affected.map((rel) =>
|
|
14712
|
+
changed_files: affected.map((rel) => join22(source, ...rel.split("/")))
|
|
14254
14713
|
};
|
|
14255
14714
|
}
|
|
14256
|
-
for (const rel of stale) rmSync7(
|
|
14715
|
+
for (const rel of stale) rmSync7(join22(source, ...rel.split("/")), { force: true });
|
|
14257
14716
|
for (const rel of changed) {
|
|
14258
|
-
const destination =
|
|
14717
|
+
const destination = join22(source, ...rel.split("/"));
|
|
14259
14718
|
const entry = expected.manifest.files.find((item) => item.path === rel);
|
|
14260
|
-
|
|
14261
|
-
copyFileSync3(
|
|
14719
|
+
mkdirSync10(dirname16(destination), { recursive: true, mode: 493 });
|
|
14720
|
+
copyFileSync3(join22(expected.source, ...rel.split("/")), destination);
|
|
14262
14721
|
chmodSync6(destination, entry.mode === "0755" ? 493 : 420);
|
|
14263
14722
|
}
|
|
14264
14723
|
verifyProjectNotebookSkillExport(source);
|
|
@@ -14267,21 +14726,21 @@ function repairProjectNotebookSkillProjection(input = {}) {
|
|
|
14267
14726
|
summary: `Restored ${affected.length} file(s) in ${source} from the version-pinned export; commit the change in the Skillex checkout`,
|
|
14268
14727
|
source,
|
|
14269
14728
|
drift: probe.block.details,
|
|
14270
|
-
changed_files: affected.map((rel) =>
|
|
14729
|
+
changed_files: affected.map((rel) => join22(source, ...rel.split("/")))
|
|
14271
14730
|
};
|
|
14272
14731
|
}
|
|
14273
14732
|
function inspectProjectNotebookIntegration(env2 = process.env) {
|
|
14274
14733
|
const home = env2.HOME;
|
|
14275
14734
|
if (!home) return { skill_installed: false, hooks_projected: false, details: ["HOME is unavailable"] };
|
|
14276
|
-
const skillsRoot =
|
|
14277
|
-
const link =
|
|
14735
|
+
const skillsRoot = join22(home, ".agents", "skills");
|
|
14736
|
+
const link = join22(skillsRoot, "project-notebook");
|
|
14278
14737
|
try {
|
|
14279
14738
|
const expected = expectedPackedSkill();
|
|
14280
|
-
const dataRoot = resolve15(env2.XDG_DATA_HOME ||
|
|
14281
|
-
const expectedPayload =
|
|
14739
|
+
const dataRoot = resolve15(env2.XDG_DATA_HOME || join22(home, ".local", "share"), "pjangler", "skills", "project-notebook");
|
|
14740
|
+
const expectedPayload = join22(dataRoot, `${PJANGLER_VERSION}-${expected.digest}`);
|
|
14282
14741
|
const probe = probeCanonicalSkillexRootProjection(skillsRoot, link);
|
|
14283
14742
|
if (probe.state === "declined") return { skill_installed: false, hooks_projected: false, details: probe.block.details, blocked: probe.block };
|
|
14284
|
-
const stat =
|
|
14743
|
+
const stat = lstatSync16(link);
|
|
14285
14744
|
if (!stat.isSymbolicLink()) {
|
|
14286
14745
|
return {
|
|
14287
14746
|
skill_installed: false,
|
|
@@ -14318,8 +14777,8 @@ function inspectProjectNotebookIntegration(env2 = process.env) {
|
|
|
14318
14777
|
}
|
|
14319
14778
|
}
|
|
14320
14779
|
function projectorArguments(source, command, input) {
|
|
14321
|
-
const script =
|
|
14322
|
-
const args = [script, command, "--master",
|
|
14780
|
+
const script = join22(source, "scripts", "project-hooks.py");
|
|
14781
|
+
const args = [script, command, "--master", join22(source, "hooks", "hooks.master.json"), "--fragment", join22(source, "hooks", "claude.settings.json"), "--target", input.target];
|
|
14323
14782
|
if (command === "check") args.push("--json");
|
|
14324
14783
|
else if (input.stateHome) args.push("--state-home", input.stateHome);
|
|
14325
14784
|
return args;
|
|
@@ -14341,7 +14800,7 @@ function checkProjectNotebookHooks(input = {}) {
|
|
|
14341
14800
|
verifyProjectNotebookSkillExport(source);
|
|
14342
14801
|
const home = env2.HOME;
|
|
14343
14802
|
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 ??
|
|
14803
|
+
const target = resolve15(input.target ?? env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ?? join22(home, ".claude", "settings.json"));
|
|
14345
14804
|
const result2 = spawnSync10("/usr/bin/python3", ["-I", ...projectorArguments(source, "check", { target })], { encoding: "utf8", env: projectorEnvironment(env2), timeout: 5e3, maxBuffer: 1048576 });
|
|
14346
14805
|
if (result2.status !== 0 && result2.status !== 1) throw new NotebookError("CONFLICT", (result2.stderr || "Project Notebook projector check failed").trim().slice(0, 512));
|
|
14347
14806
|
try {
|
|
@@ -14359,14 +14818,14 @@ function installProjectNotebookIntegration(input = {}) {
|
|
|
14359
14818
|
const source = realpathSync10(skill.path);
|
|
14360
14819
|
const home = env2.HOME;
|
|
14361
14820
|
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 ||
|
|
14821
|
+
const target = resolve15(input.target ?? env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ?? join22(home, ".claude", "settings.json"));
|
|
14822
|
+
const stateHome = resolve15(env2.XDG_STATE_HOME || join22(home, ".local", "state"));
|
|
14364
14823
|
const result2 = spawnSync10("/usr/bin/python3", ["-I", ...projectorArguments(source, "install", { target, stateHome })], { encoding: "utf8", env: projectorEnvironment(env2), timeout: 5e3, maxBuffer: 1048576 });
|
|
14365
14824
|
if (result2.status !== 0) {
|
|
14366
14825
|
if (skill.installed) {
|
|
14367
14826
|
try {
|
|
14368
|
-
const stat =
|
|
14369
|
-
if (stat.isSymbolicLink() && realpathSync10(skill.path) === source)
|
|
14827
|
+
const stat = lstatSync16(skill.path);
|
|
14828
|
+
if (stat.isSymbolicLink() && realpathSync10(skill.path) === source) unlinkSync9(skill.path);
|
|
14370
14829
|
} catch {
|
|
14371
14830
|
}
|
|
14372
14831
|
}
|
|
@@ -14383,10 +14842,10 @@ function assertNoSymlinkComponents(path, allowMissing = false) {
|
|
|
14383
14842
|
const root = parse3(absolute).root;
|
|
14384
14843
|
let cursor = root;
|
|
14385
14844
|
for (const component of absolute.slice(root.length).split(sep7).filter(Boolean)) {
|
|
14386
|
-
cursor =
|
|
14845
|
+
cursor = join22(cursor, component);
|
|
14387
14846
|
let stat;
|
|
14388
14847
|
try {
|
|
14389
|
-
stat =
|
|
14848
|
+
stat = lstatSync16(cursor);
|
|
14390
14849
|
} catch (error) {
|
|
14391
14850
|
if (allowMissing && error.code === "ENOENT") return;
|
|
14392
14851
|
throw error;
|
|
@@ -14992,17 +15451,17 @@ init_remote_mutation_journal();
|
|
|
14992
15451
|
function integrationEnvironment(module, ctx) {
|
|
14993
15452
|
const env2 = { ...module.environment, HOME: ctx.homeDir };
|
|
14994
15453
|
if (module.environment.HOME !== ctx.homeDir) {
|
|
14995
|
-
env2.XDG_DATA_HOME =
|
|
14996
|
-
env2.XDG_STATE_HOME =
|
|
14997
|
-
env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS =
|
|
15454
|
+
env2.XDG_DATA_HOME = join23(ctx.homeDir, ".local", "share");
|
|
15455
|
+
env2.XDG_STATE_HOME = join23(ctx.homeDir, ".local", "state");
|
|
15456
|
+
env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS = join23(ctx.homeDir, ".claude", "settings.json");
|
|
14998
15457
|
}
|
|
14999
15458
|
return env2;
|
|
15000
15459
|
}
|
|
15001
15460
|
function resolvedForPlan(plan) {
|
|
15002
|
-
const manifestPath =
|
|
15461
|
+
const manifestPath = join23(plan.project.repo_path, ".project.json");
|
|
15003
15462
|
let manifest = plan.manifest;
|
|
15004
15463
|
if (existsSync17(manifestPath)) {
|
|
15005
|
-
const parsed = JSON.parse(
|
|
15464
|
+
const parsed = JSON.parse(readFileSync20(manifestPath, "utf8"));
|
|
15006
15465
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`${manifestPath} must contain a JSON object`);
|
|
15007
15466
|
manifest = parsed;
|
|
15008
15467
|
}
|
|
@@ -15163,7 +15622,7 @@ function unsafeToRemove(targetDir) {
|
|
|
15163
15622
|
while (!seen.has(cursor)) {
|
|
15164
15623
|
seen.add(cursor);
|
|
15165
15624
|
try {
|
|
15166
|
-
const stat =
|
|
15625
|
+
const stat = lstatSync17(cursor);
|
|
15167
15626
|
if (stat.isSymbolicLink()) {
|
|
15168
15627
|
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
15628
|
}
|
|
@@ -15191,12 +15650,12 @@ function publicMigration(report) {
|
|
|
15191
15650
|
};
|
|
15192
15651
|
}
|
|
15193
15652
|
function hasGitRepository(runtime, targetDir) {
|
|
15194
|
-
if (!existsSync18(
|
|
15653
|
+
if (!existsSync18(join24(targetDir, ".git"))) return false;
|
|
15195
15654
|
return runtime.runGit(targetDir, ["rev-parse", "--is-inside-work-tree"]).status === 0;
|
|
15196
15655
|
}
|
|
15197
15656
|
function refreshPlanFromCanonicalManifest(plan) {
|
|
15198
|
-
const manifestPath =
|
|
15199
|
-
const manifest = JSON.parse(
|
|
15657
|
+
const manifestPath = join24(plan.project.repo_path, ".project.json");
|
|
15658
|
+
const manifest = JSON.parse(readFileSync21(manifestPath, "utf8"));
|
|
15200
15659
|
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
15201
15660
|
throw new Error(`${manifestPath} must contain a JSON object`);
|
|
15202
15661
|
}
|
|
@@ -15410,12 +15869,12 @@ var ProjectRecipe = class extends Recipe {
|
|
|
15410
15869
|
changedFiles: result2.status === "applied" || result2.status === "partial" ? result2.changedFiles : [],
|
|
15411
15870
|
message: result2.summary
|
|
15412
15871
|
})));
|
|
15413
|
-
errors.push(...migrationReport.results.filter((result2) => result2.status === "blocked" || result2.status === "partial").map((result2) =>
|
|
15872
|
+
errors.push(...migrationReport.results.filter((result2) => result2.status === "blocked" || result2.status === "partial").map((result2) => describeFinding(result2)));
|
|
15414
15873
|
}
|
|
15415
15874
|
const eligibilityAudit = errors.length === 0 ? publicAudit(await this.registry.auditRecipes({ ...transactionContext, dryRun: true })) : void 0;
|
|
15416
15875
|
audit = eligibilityAudit;
|
|
15417
15876
|
if (eligibilityAudit && !eligibilityAudit.ok) {
|
|
15418
|
-
errors.push(...eligibilityAudit.rules.filter((finding2) => gatesProject(finding2)).map((finding2) =>
|
|
15877
|
+
errors.push(...eligibilityAudit.rules.filter((finding2) => gatesProject(finding2)).map((finding2) => describeFinding(finding2)));
|
|
15419
15878
|
}
|
|
15420
15879
|
phases.push({
|
|
15421
15880
|
id: "project.audit:eligibility",
|
|
@@ -15427,7 +15886,7 @@ var ProjectRecipe = class extends Recipe {
|
|
|
15427
15886
|
if (hasGitRepository(this.runtime, targetDir)) {
|
|
15428
15887
|
phases.push({ id: "project.git", status: "unchanged", changedFiles: [], message: "Git repository already initialized" });
|
|
15429
15888
|
} else {
|
|
15430
|
-
const gitPath =
|
|
15889
|
+
const gitPath = join24(targetDir, ".git");
|
|
15431
15890
|
for (const { args, label, options } of [
|
|
15432
15891
|
{ args: ["init", "--initial-branch=main"], label: "git init" },
|
|
15433
15892
|
{ args: ["add", "-A"], label: "git add" },
|
|
@@ -15506,7 +15965,7 @@ var ProjectRecipe = class extends Recipe {
|
|
|
15506
15965
|
transactionContext.notebookManifestBinding = projectedNotebook?.binding ? Object.freeze({ ...projectedNotebook.binding }) : null;
|
|
15507
15966
|
transactionContext.notebookObservation = Object.freeze(await notebookRecipe.observeExternal(plan, notebookPlan));
|
|
15508
15967
|
const candidateAudit = publicAudit(await this.registry.auditRecipes(transactionContext, ["notebook"]));
|
|
15509
|
-
if (!candidateAudit.ok) errors.push(...candidateAudit.rules.filter((item) => item.status === "fail" || item.status === "warn").map((item) =>
|
|
15968
|
+
if (!candidateAudit.ok) errors.push(...candidateAudit.rules.filter((item) => item.status === "fail" || item.status === "warn").map((item) => describeFinding(item)));
|
|
15510
15969
|
}
|
|
15511
15970
|
phases.push({
|
|
15512
15971
|
id: "project.external:notebook",
|
|
@@ -15562,7 +16021,7 @@ var ProjectRecipe = class extends Recipe {
|
|
|
15562
16021
|
}
|
|
15563
16022
|
audit = errors.length === 0 ? publicAudit(await this.registry.auditRecipes({ ...transactionContext, dryRun: true })) : audit;
|
|
15564
16023
|
if (errors.length === 0 && audit && !audit.ok) {
|
|
15565
|
-
errors.push(...audit.rules.filter((finding2) => gatesProject(finding2)).map((finding2) =>
|
|
16024
|
+
errors.push(...audit.rules.filter((finding2) => gatesProject(finding2)).map((finding2) => describeFinding(finding2)));
|
|
15566
16025
|
}
|
|
15567
16026
|
phases.push({
|
|
15568
16027
|
id: "project.audit",
|
|
@@ -15638,12 +16097,12 @@ var ProjectRecipe = class extends Recipe {
|
|
|
15638
16097
|
};
|
|
15639
16098
|
|
|
15640
16099
|
// src/recipes/registry.ts
|
|
15641
|
-
import { readFileSync as
|
|
15642
|
-
import { join as
|
|
16100
|
+
import { readFileSync as readFileSync22 } from "node:fs";
|
|
16101
|
+
import { join as join25 } from "node:path";
|
|
15643
16102
|
function projectWaivers(repoRoot) {
|
|
15644
16103
|
let waive;
|
|
15645
16104
|
try {
|
|
15646
|
-
waive = JSON.parse(
|
|
16105
|
+
waive = JSON.parse(readFileSync22(join25(repoRoot, ".project.json"), "utf8"))?.parity?.waive;
|
|
15647
16106
|
} catch {
|
|
15648
16107
|
return /* @__PURE__ */ new Map();
|
|
15649
16108
|
}
|
|
@@ -15984,8 +16443,8 @@ var recipeRegistry = new RecipeRegistry([
|
|
|
15984
16443
|
|
|
15985
16444
|
// src/commands/AgentHooksCommands.ts
|
|
15986
16445
|
import { homedir as homedir8 } from "node:os";
|
|
15987
|
-
import { join as
|
|
15988
|
-
import { existsSync as existsSync19, cpSync, mkdirSync as
|
|
16446
|
+
import { join as join26, dirname as dirname18 } from "node:path";
|
|
16447
|
+
import { existsSync as existsSync19, cpSync, mkdirSync as mkdirSync11, readFileSync as readFileSync23, writeFileSync as writeFileSync14 } from "node:fs";
|
|
15989
16448
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
15990
16449
|
init_project();
|
|
15991
16450
|
import { parse as parseToml4 } from "smol-toml";
|
|
@@ -15998,16 +16457,16 @@ function resolveTemplateRoot() {
|
|
|
15998
16457
|
try {
|
|
15999
16458
|
let dir = dirname18(fileURLToPath4(import.meta.url));
|
|
16000
16459
|
for (let i = 0; i < 8; i++) {
|
|
16001
|
-
candidates.push(
|
|
16460
|
+
candidates.push(join26(dir, "templates", "commonproject", "template"));
|
|
16002
16461
|
const parent = dirname18(dir);
|
|
16003
16462
|
if (parent === dir) break;
|
|
16004
16463
|
dir = parent;
|
|
16005
16464
|
}
|
|
16006
16465
|
} catch {
|
|
16007
16466
|
}
|
|
16008
|
-
candidates.push(
|
|
16467
|
+
candidates.push(join26(homedir8(), "code", "pjangler", "templates", "commonproject", "template"));
|
|
16009
16468
|
for (const c of candidates) {
|
|
16010
|
-
if (existsSync19(
|
|
16469
|
+
if (existsSync19(join26(c, ".agents", "hooks", "hooks.master.json"))) return c;
|
|
16011
16470
|
}
|
|
16012
16471
|
throw new Error(
|
|
16013
16472
|
"Could not locate the CommonProject template. Set PJANGLER_COMMONPROJECT_TEMPLATE to <repo>/templates/commonproject/template."
|
|
@@ -16033,15 +16492,15 @@ var CopyAgentHooksTree = class extends Command {
|
|
|
16033
16492
|
const created = [];
|
|
16034
16493
|
const skipped = [];
|
|
16035
16494
|
for (const { rel, dir } of items) {
|
|
16036
|
-
const src =
|
|
16037
|
-
const dest =
|
|
16495
|
+
const src = join26(templateRoot, rel);
|
|
16496
|
+
const dest = join26(this.context.targetDir, rel);
|
|
16038
16497
|
if (!existsSync19(src)) continue;
|
|
16039
16498
|
if (existsSync19(dest) && !this.context.force) {
|
|
16040
16499
|
skipped.push(rel);
|
|
16041
16500
|
continue;
|
|
16042
16501
|
}
|
|
16043
16502
|
if (!this.context.dryRun) {
|
|
16044
|
-
|
|
16503
|
+
mkdirSync11(dirname18(dest), { recursive: true });
|
|
16045
16504
|
cpSync(src, dest, { recursive: dir, force: true });
|
|
16046
16505
|
}
|
|
16047
16506
|
created.push(rel);
|
|
@@ -16062,14 +16521,14 @@ var WireMiseAgentHooks = class _WireMiseAgentHooks extends Command {
|
|
|
16062
16521
|
if (!resolveAgentHooksLayer2()) {
|
|
16063
16522
|
return { success: true, message: this.formatMessage(AGENT_HOOKS_SKIP_MESSAGE) };
|
|
16064
16523
|
}
|
|
16065
|
-
const misePath =
|
|
16524
|
+
const misePath = join26(this.context.targetDir, "mise.toml");
|
|
16066
16525
|
if (!existsSync19(misePath)) {
|
|
16067
16526
|
return {
|
|
16068
16527
|
success: false,
|
|
16069
16528
|
message: "\u26A0\uFE0F No mise.toml found \u2014 run `pj add mise` first, then re-run."
|
|
16070
16529
|
};
|
|
16071
16530
|
}
|
|
16072
|
-
let content =
|
|
16531
|
+
let content = readFileSync23(misePath, "utf8");
|
|
16073
16532
|
if (content.includes(_WireMiseAgentHooks.MARKER)) {
|
|
16074
16533
|
return { success: true, message: this.formatMessage("\u2713 mise.toml already wired for agent-hooks") };
|
|
16075
16534
|
}
|
|
@@ -16153,7 +16612,7 @@ ${leaveBlock}`);
|
|
|
16153
16612
|
if (!verdict.ok) {
|
|
16154
16613
|
return { success: false, message: `\u26A0\uFE0F mise.toml was not rewritten: ${verdict.reason}. Add the agent-hooks tasks by hand.` };
|
|
16155
16614
|
}
|
|
16156
|
-
if (!this.context.dryRun)
|
|
16615
|
+
if (!this.context.dryRun) writeFileSync14(misePath, content);
|
|
16157
16616
|
if (wiredHooks) {
|
|
16158
16617
|
return { success: true, message: this.formatMessage("\u2705 Wired mise.toml ([hooks] enter/leave + tasks)") };
|
|
16159
16618
|
}
|
|
@@ -16284,7 +16743,7 @@ if __name__ == "__main__":
|
|
|
16284
16743
|
|
|
16285
16744
|
// src/commands/AddMiseCodegraphScript.ts
|
|
16286
16745
|
import { chmodSync as chmodSync7 } from "fs";
|
|
16287
|
-
import { join as
|
|
16746
|
+
import { join as join27 } from "path";
|
|
16288
16747
|
var AddMiseCodegraphScript = class extends Command {
|
|
16289
16748
|
async invoke() {
|
|
16290
16749
|
const filePath = ".mise/scripts/codegraph.sh";
|
|
@@ -16338,7 +16797,7 @@ fi
|
|
|
16338
16797
|
`;
|
|
16339
16798
|
this.writeFile(filePath, content);
|
|
16340
16799
|
if (!this.context.dryRun) {
|
|
16341
|
-
chmodSync7(
|
|
16800
|
+
chmodSync7(join27(this.context.targetDir, filePath), 493);
|
|
16342
16801
|
}
|
|
16343
16802
|
return {
|
|
16344
16803
|
success: true,
|
|
@@ -16376,12 +16835,12 @@ SECRET_KEY=""
|
|
|
16376
16835
|
// src/parity/index.ts
|
|
16377
16836
|
import { existsSync as existsSync20 } from "node:fs";
|
|
16378
16837
|
import { homedir as homedir9 } from "node:os";
|
|
16379
|
-
import { dirname as dirname19, join as
|
|
16838
|
+
import { dirname as dirname19, join as join28, resolve as resolve19 } from "node:path";
|
|
16380
16839
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
16381
16840
|
function resolvePjanglerRoot2() {
|
|
16382
16841
|
let dir = dirname19(fileURLToPath5(import.meta.url));
|
|
16383
16842
|
while (dir !== dirname19(dir)) {
|
|
16384
|
-
if (existsSync20(
|
|
16843
|
+
if (existsSync20(join28(dir, "package.json")) && existsSync20(join28(dir, "templates", "commonproject", "copier.yml"))) return dir;
|
|
16385
16844
|
dir = dirname19(dir);
|
|
16386
16845
|
}
|
|
16387
16846
|
return resolve19(process.cwd());
|
|
@@ -16556,13 +17015,13 @@ init_version();
|
|
|
16556
17015
|
// src/project/info.ts
|
|
16557
17016
|
init_boardUrl();
|
|
16558
17017
|
init_project();
|
|
16559
|
-
import { readFileSync as
|
|
16560
|
-
import { join as
|
|
17018
|
+
import { readFileSync as readFileSync24 } from "node:fs";
|
|
17019
|
+
import { join as join29, resolve as resolve20 } from "node:path";
|
|
16561
17020
|
function readManifest2(root) {
|
|
16562
|
-
const path =
|
|
17021
|
+
const path = join29(root, ".project.json");
|
|
16563
17022
|
let manifest;
|
|
16564
17023
|
try {
|
|
16565
|
-
const value = JSON.parse(
|
|
17024
|
+
const value = JSON.parse(readFileSync24(path, "utf8"));
|
|
16566
17025
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("expected a JSON object");
|
|
16567
17026
|
manifest = value;
|
|
16568
17027
|
} catch (error) {
|
|
@@ -16603,7 +17062,7 @@ function readProjectInfo(input = {}) {
|
|
|
16603
17062
|
ok: true,
|
|
16604
17063
|
project_id: projectId,
|
|
16605
17064
|
repo_path: resolve20(root),
|
|
16606
|
-
manifest_path:
|
|
17065
|
+
manifest_path: join29(resolve20(root), ".project.json"),
|
|
16607
17066
|
manifest,
|
|
16608
17067
|
registry: { location, status: "indexed" }
|
|
16609
17068
|
};
|
|
@@ -16631,14 +17090,14 @@ function readProjectInfo(input = {}) {
|
|
|
16631
17090
|
}
|
|
16632
17091
|
|
|
16633
17092
|
// src/describe/index.ts
|
|
16634
|
-
import { existsSync as existsSync21, readFileSync as
|
|
16635
|
-
import { join as
|
|
17093
|
+
import { existsSync as existsSync21, readFileSync as readFileSync25, readdirSync as readdirSync12, statSync as statSync7 } from "node:fs";
|
|
17094
|
+
import { join as join31, resolve as resolve21 } from "node:path";
|
|
16636
17095
|
init_project();
|
|
16637
17096
|
|
|
16638
17097
|
// src/describe/activity.ts
|
|
16639
17098
|
import { spawn as spawn2, spawnSync as spawnSync14 } from "node:child_process";
|
|
16640
17099
|
import { statSync as statSync6 } from "node:fs";
|
|
16641
|
-
import { join as
|
|
17100
|
+
import { join as join30 } from "node:path";
|
|
16642
17101
|
var ACTIVE_WINDOW_SECONDS = 24 * 60 * 60;
|
|
16643
17102
|
var MAX_DIRTY_STATS = 500;
|
|
16644
17103
|
var GIT_TIMEOUT_MS = 5e3;
|
|
@@ -16767,7 +17226,7 @@ function uncommittedSource(repo, paths) {
|
|
|
16767
17226
|
let newest = 0;
|
|
16768
17227
|
for (const path of paths.slice(0, MAX_DIRTY_STATS)) {
|
|
16769
17228
|
try {
|
|
16770
|
-
const mtime = Math.floor(statSync6(
|
|
17229
|
+
const mtime = Math.floor(statSync6(join30(repo, path)).mtimeMs / 1e3);
|
|
16771
17230
|
if (mtime > newest) newest = mtime;
|
|
16772
17231
|
} catch {
|
|
16773
17232
|
}
|
|
@@ -16876,7 +17335,7 @@ var CONFIG_FILES = [
|
|
|
16876
17335
|
];
|
|
16877
17336
|
function readJson(path) {
|
|
16878
17337
|
try {
|
|
16879
|
-
const parsed = JSON.parse(
|
|
17338
|
+
const parsed = JSON.parse(readFileSync25(path, "utf8"));
|
|
16880
17339
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
16881
17340
|
} catch {
|
|
16882
17341
|
return void 0;
|
|
@@ -16898,23 +17357,23 @@ function describeType(repo) {
|
|
|
16898
17357
|
const evidence = [];
|
|
16899
17358
|
const note = (signal, file) => evidence.push(`${signal} (${file})`);
|
|
16900
17359
|
for (const marker of LANGUAGE_MARKERS) {
|
|
16901
|
-
if (!existsSync21(
|
|
17360
|
+
if (!existsSync21(join31(repo, marker.file))) continue;
|
|
16902
17361
|
if (!languages.includes(marker.language)) {
|
|
16903
17362
|
languages.push(marker.language);
|
|
16904
17363
|
note(marker.language, marker.file);
|
|
16905
17364
|
}
|
|
16906
17365
|
}
|
|
16907
17366
|
try {
|
|
16908
|
-
const dotnet =
|
|
17367
|
+
const dotnet = readdirSync12(repo).find((entry) => entry.endsWith(".csproj") || entry.endsWith(".sln"));
|
|
16909
17368
|
if (dotnet && !languages.includes("dotnet")) {
|
|
16910
17369
|
languages.push("dotnet");
|
|
16911
17370
|
note("dotnet", dotnet);
|
|
16912
17371
|
}
|
|
16913
17372
|
} catch {
|
|
16914
17373
|
}
|
|
16915
|
-
const pkg = readJson(
|
|
17374
|
+
const pkg = readJson(join31(repo, "package.json"));
|
|
16916
17375
|
if (pkg) {
|
|
16917
|
-
if (existsSync21(
|
|
17376
|
+
if (existsSync21(join31(repo, "tsconfig.json"))) {
|
|
16918
17377
|
const index = languages.indexOf("javascript");
|
|
16919
17378
|
if (index >= 0) languages.splice(index, 1);
|
|
16920
17379
|
if (!languages.includes("typescript")) {
|
|
@@ -16948,14 +17407,14 @@ function describeType(repo) {
|
|
|
16948
17407
|
["hermes-fleet-host", "agents/hermes"]
|
|
16949
17408
|
];
|
|
16950
17409
|
for (const [role, marker] of roleMarkers) {
|
|
16951
|
-
if (!existsSync21(
|
|
17410
|
+
if (!existsSync21(join31(repo, marker))) continue;
|
|
16952
17411
|
roles.push(role);
|
|
16953
17412
|
note(role, marker);
|
|
16954
17413
|
}
|
|
16955
17414
|
return { primaryLanguage: languages[0], languages, roles, evidence };
|
|
16956
17415
|
}
|
|
16957
17416
|
function describeIdentity(repo, registryPath) {
|
|
16958
|
-
const manifestPath =
|
|
17417
|
+
const manifestPath = join31(repo, ".project.json");
|
|
16959
17418
|
const manifest = readJson(manifestPath);
|
|
16960
17419
|
const drift = [];
|
|
16961
17420
|
let record3;
|
|
@@ -17048,12 +17507,12 @@ function describeSubsystems(repo, findings) {
|
|
|
17048
17507
|
const markers = SUBSYSTEM_MARKERS[metadata.id] ?? [];
|
|
17049
17508
|
const evidence = metadata.id === "notebook" ? (() => {
|
|
17050
17509
|
try {
|
|
17051
|
-
const manifest = JSON.parse(
|
|
17510
|
+
const manifest = JSON.parse(readFileSync25(join31(repo, ".project.json"), "utf8"));
|
|
17052
17511
|
return manifest.notebook && typeof manifest.notebook === "object" ? [".project.json#notebook"] : [];
|
|
17053
17512
|
} catch {
|
|
17054
17513
|
return [];
|
|
17055
17514
|
}
|
|
17056
|
-
})() : markers.filter((marker) => existsSync21(
|
|
17515
|
+
})() : markers.filter((marker) => existsSync21(join31(repo, marker)));
|
|
17057
17516
|
const rules = (byRecipe.get(metadata.id) ?? []).map((finding2) => ({
|
|
17058
17517
|
id: finding2.id,
|
|
17059
17518
|
title: finding2.title,
|
|
@@ -17072,7 +17531,7 @@ function describeNotebook(repo, registryPath) {
|
|
|
17072
17531
|
try {
|
|
17073
17532
|
const registry = loadProjectRegistry(registryPath);
|
|
17074
17533
|
const project = Object.values(registry.projects).find((entry) => resolve21(entry.repo_path) === resolve21(repo));
|
|
17075
|
-
const manifest = existsSync21(
|
|
17534
|
+
const manifest = existsSync21(join31(repo, ".project.json")) ? JSON.parse(readFileSync25(join31(repo, ".project.json"), "utf8")) : void 0;
|
|
17076
17535
|
const declared = Boolean(project?.notebook || manifest?.notebook && typeof manifest.notebook === "object");
|
|
17077
17536
|
if (!declared) return { declared: false, bindingState: null, notebookId: null, overviewNoteId: null, health: null, remoteCheck: "skip", captureAdmission: null };
|
|
17078
17537
|
const config = loadEffectiveNotebookConfig(repo, registryPath);
|
|
@@ -17090,7 +17549,7 @@ function describeNotebook(repo, registryPath) {
|
|
|
17090
17549
|
}
|
|
17091
17550
|
}
|
|
17092
17551
|
function describeConfigFiles(repo) {
|
|
17093
|
-
return CONFIG_FILES.filter((spec) => existsSync21(
|
|
17552
|
+
return CONFIG_FILES.filter((spec) => existsSync21(join31(repo, spec.path))).map((spec) => ({ path: spec.path, purpose: spec.purpose, subsystem: spec.subsystem }));
|
|
17094
17553
|
}
|
|
17095
17554
|
function describeNextSteps(description, findings) {
|
|
17096
17555
|
const steps = [];
|
|
@@ -17369,7 +17828,7 @@ function resolveTargetDir(targetDir) {
|
|
|
17369
17828
|
function resolvePjanglerRoot3() {
|
|
17370
17829
|
let dir = dirname20(fileURLToPath6(import.meta.url));
|
|
17371
17830
|
while (dir !== dirname20(dir)) {
|
|
17372
|
-
if (existsSync22(
|
|
17831
|
+
if (existsSync22(join32(dir, "package.json")) && existsSync22(join32(dir, "templates", "commonproject", "copier.yml"))) {
|
|
17373
17832
|
return dir;
|
|
17374
17833
|
}
|
|
17375
17834
|
dir = dirname20(dir);
|
|
@@ -17669,7 +18128,7 @@ server.registerTool(
|
|
|
17669
18128
|
if (!existsSync22(parentDir) || !statSync8(parentDir).isDirectory()) throw new Error(`Parent directory does not exist: ${parentDir}`);
|
|
17670
18129
|
const targetDir = resolveContainedPath(
|
|
17671
18130
|
parentDir,
|
|
17672
|
-
explicitTargetDir ??
|
|
18131
|
+
explicitTargetDir ?? join32(parentDir, projectSlug),
|
|
17673
18132
|
"Bootstrap target"
|
|
17674
18133
|
);
|
|
17675
18134
|
const overwrite = input.overwrite ?? input.force ?? false;
|
|
@@ -17792,7 +18251,7 @@ server.registerTool(
|
|
|
17792
18251
|
registryPath: input.registryPath,
|
|
17793
18252
|
force: input.force ?? false,
|
|
17794
18253
|
overwrite: input.force ?? false,
|
|
17795
|
-
scaffold: !(input.targetDir && existsSync22(
|
|
18254
|
+
scaffold: !(input.targetDir && existsSync22(join32(resolve22(input.targetDir), ".git")))
|
|
17796
18255
|
});
|
|
17797
18256
|
if (!input.apply) return asText(publicCompositeProjectResponse(publicProjectPlan(plan), plan));
|
|
17798
18257
|
const preflight = await preflightProjectApply(plan, resolvePjanglerRoot3());
|