@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/index.js
CHANGED
|
@@ -464,7 +464,7 @@ var init_executionBinding = __esm({
|
|
|
464
464
|
});
|
|
465
465
|
|
|
466
466
|
// src/project/manifestIndex.ts
|
|
467
|
-
import { basename as
|
|
467
|
+
import { basename as basename6, dirname as dirname10, join as join13, resolve as resolve8 } from "node:path";
|
|
468
468
|
function normalizeProjectId2(value) {
|
|
469
469
|
try {
|
|
470
470
|
return normalizeProjectId(value);
|
|
@@ -511,12 +511,12 @@ var init_manifestIndex = __esm({
|
|
|
511
511
|
});
|
|
512
512
|
|
|
513
513
|
// src/lifecycle/preflight.ts
|
|
514
|
-
import { createHash as
|
|
515
|
-
import { accessSync, constants as
|
|
516
|
-
import { basename as
|
|
514
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
515
|
+
import { accessSync, constants as constants3, existsSync as existsSync9, lstatSync as lstatSync11, readFileSync as readFileSync13, readdirSync as readdirSync9, realpathSync as realpathSync5, statSync as statSync3 } from "node:fs";
|
|
516
|
+
import { basename as basename7, delimiter, dirname as dirname11, isAbsolute as isAbsolute5, join as join14, relative as relative10, resolve as resolve9 } from "node:path";
|
|
517
517
|
import YAML5 from "yaml";
|
|
518
518
|
function sha256(path) {
|
|
519
|
-
return
|
|
519
|
+
return createHash9("sha256").update(readFileSync13(path)).digest("base64url");
|
|
520
520
|
}
|
|
521
521
|
function fingerprint(path) {
|
|
522
522
|
const absolute = resolve9(path);
|
|
@@ -570,15 +570,15 @@ var init_preflight = __esm({
|
|
|
570
570
|
});
|
|
571
571
|
|
|
572
572
|
// src/project/boardUrl.ts
|
|
573
|
-
import { existsSync as existsSync10, readFileSync as
|
|
573
|
+
import { existsSync as existsSync10, readFileSync as readFileSync14, statSync as statSync4 } from "node:fs";
|
|
574
574
|
import { homedir as homedir5 } from "node:os";
|
|
575
|
-
import { dirname as dirname12, isAbsolute as isAbsolute6, join as
|
|
575
|
+
import { dirname as dirname12, isAbsolute as isAbsolute6, join as join15, resolve as resolve10 } from "node:path";
|
|
576
576
|
function resolveTemplateConfigPath(env2 = process.env, home = homedir5()) {
|
|
577
577
|
const fromEnv = env2.HERMES_TEMPLATE_CONFIG;
|
|
578
578
|
if (fromEnv && fromEnv.trim()) return fromEnv.trim();
|
|
579
579
|
const xdg = env2.XDG_CONFIG_HOME?.trim();
|
|
580
|
-
const base = xdg && xdg.length ? xdg :
|
|
581
|
-
return
|
|
580
|
+
const base = xdg && xdg.length ? xdg : join15(home, ".config");
|
|
581
|
+
return join15(base, "hermes-agent-template", "config.toml");
|
|
582
582
|
}
|
|
583
583
|
function readTomlScalar(text2, section2, key) {
|
|
584
584
|
let inSection = false;
|
|
@@ -604,7 +604,7 @@ function readTomlScalar(text2, section2, key) {
|
|
|
604
604
|
function readTemplateConfig(env2, home) {
|
|
605
605
|
try {
|
|
606
606
|
const path = resolveTemplateConfigPath(env2, home);
|
|
607
|
-
return existsSync10(path) ?
|
|
607
|
+
return existsSync10(path) ? readFileSync14(path, "utf8") : void 0;
|
|
608
608
|
} catch {
|
|
609
609
|
return void 0;
|
|
610
610
|
}
|
|
@@ -668,7 +668,7 @@ function boardUrl(provider, options = {}) {
|
|
|
668
668
|
function findProjectRoot(from) {
|
|
669
669
|
let dir = resolve10(from);
|
|
670
670
|
for (; ; ) {
|
|
671
|
-
if (existsSync10(
|
|
671
|
+
if (existsSync10(join15(dir, ".project.json"))) return dir;
|
|
672
672
|
const parent = dirname12(dir);
|
|
673
673
|
if (parent === dir) return void 0;
|
|
674
674
|
dir = parent;
|
|
@@ -676,7 +676,7 @@ function findProjectRoot(from) {
|
|
|
676
676
|
}
|
|
677
677
|
function readTicketProvider(root) {
|
|
678
678
|
try {
|
|
679
|
-
const manifest = JSON.parse(
|
|
679
|
+
const manifest = JSON.parse(readFileSync14(join15(root, ".project.json"), "utf8"));
|
|
680
680
|
const provider = manifest.ticket_provider;
|
|
681
681
|
if (!provider || typeof provider !== "object") return void 0;
|
|
682
682
|
return provider;
|
|
@@ -688,16 +688,16 @@ function currentBranch(from) {
|
|
|
688
688
|
try {
|
|
689
689
|
let dir = resolve10(from);
|
|
690
690
|
for (; ; ) {
|
|
691
|
-
const dotgit =
|
|
691
|
+
const dotgit = join15(dir, ".git");
|
|
692
692
|
if (existsSync10(dotgit)) {
|
|
693
693
|
let gitDir = dotgit;
|
|
694
694
|
if (statSync4(dotgit).isFile()) {
|
|
695
|
-
const pointer = /^gitdir:\s*(.+)$/m.exec(
|
|
695
|
+
const pointer = /^gitdir:\s*(.+)$/m.exec(readFileSync14(dotgit, "utf8"));
|
|
696
696
|
if (!pointer) return void 0;
|
|
697
697
|
const target = pointer[1].trim();
|
|
698
698
|
gitDir = isAbsolute6(target) ? target : resolve10(dir, target);
|
|
699
699
|
}
|
|
700
|
-
const head =
|
|
700
|
+
const head = readFileSync14(join15(gitDir, "HEAD"), "utf8").trim();
|
|
701
701
|
const ref = /^ref:\s*refs\/heads\/(.+)$/.exec(head);
|
|
702
702
|
return ref ? ref[1].trim() : void 0;
|
|
703
703
|
}
|
|
@@ -726,8 +726,8 @@ var init_boardUrl = __esm({
|
|
|
726
726
|
});
|
|
727
727
|
|
|
728
728
|
// src/utils/version.ts
|
|
729
|
-
import { readFileSync as
|
|
730
|
-
import { dirname as dirname13, join as
|
|
729
|
+
import { readFileSync as readFileSync15 } from "node:fs";
|
|
730
|
+
import { dirname as dirname13, join as join16 } from "node:path";
|
|
731
731
|
import { fileURLToPath } from "node:url";
|
|
732
732
|
var PJANGLER_VERSION;
|
|
733
733
|
var init_version = __esm({
|
|
@@ -738,7 +738,7 @@ var init_version = __esm({
|
|
|
738
738
|
let dir = dirname13(fileURLToPath(import.meta.url));
|
|
739
739
|
for (let i = 0; i < 4; i++) {
|
|
740
740
|
try {
|
|
741
|
-
const raw =
|
|
741
|
+
const raw = readFileSync15(join16(dir, "package.json"), "utf8");
|
|
742
742
|
return JSON.parse(raw).version ?? "0.0.0";
|
|
743
743
|
} catch {
|
|
744
744
|
const parent = dirname13(dir);
|
|
@@ -755,17 +755,17 @@ var init_version = __esm({
|
|
|
755
755
|
|
|
756
756
|
// src/project/boardQuery.ts
|
|
757
757
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
758
|
-
import { readFileSync as
|
|
758
|
+
import { readFileSync as readFileSync16 } from "node:fs";
|
|
759
759
|
import { homedir as homedir6 } from "node:os";
|
|
760
|
-
import { basename as
|
|
760
|
+
import { basename as basename8, join as join17 } from "node:path";
|
|
761
761
|
function readProjectSlug(root) {
|
|
762
762
|
try {
|
|
763
|
-
const manifest = JSON.parse(
|
|
763
|
+
const manifest = JSON.parse(readFileSync16(join17(root, ".project.json"), "utf8"));
|
|
764
764
|
const slug = manifest.project_id ?? manifest.project_slug;
|
|
765
765
|
if (typeof slug === "string" && slug.trim()) return slug.trim().toLowerCase();
|
|
766
766
|
} catch {
|
|
767
767
|
}
|
|
768
|
-
return
|
|
768
|
+
return basename8(root).toLowerCase();
|
|
769
769
|
}
|
|
770
770
|
function resolveProject(cwd) {
|
|
771
771
|
const root = findProjectRoot(cwd);
|
|
@@ -780,12 +780,12 @@ function resolveProvider(handle) {
|
|
|
780
780
|
const type = handle.facts?.type?.trim().toLowerCase();
|
|
781
781
|
if (!type) {
|
|
782
782
|
throw new BoardError(
|
|
783
|
-
`no ticket board bound for ${handle.slug}: ${
|
|
783
|
+
`no ticket board bound for ${handle.slug}: ${join17(handle.root, ".project.json")} has no ticket_provider.type`
|
|
784
784
|
);
|
|
785
785
|
}
|
|
786
786
|
if (!BOARD_PROVIDERS.includes(type)) {
|
|
787
787
|
throw new BoardError(
|
|
788
|
-
`unsupported ticket_provider.type "${type}" in ${
|
|
788
|
+
`unsupported ticket_provider.type "${type}" in ${join17(handle.root, ".project.json")} \u2014 expected one of ${BOARD_PROVIDERS.join(", ")}`
|
|
789
789
|
);
|
|
790
790
|
}
|
|
791
791
|
return type;
|
|
@@ -802,7 +802,7 @@ function workspaceEnvKey(workspace) {
|
|
|
802
802
|
function dotenvValue(path, key) {
|
|
803
803
|
let text2;
|
|
804
804
|
try {
|
|
805
|
-
text2 =
|
|
805
|
+
text2 = readFileSync16(path, "utf8");
|
|
806
806
|
} catch {
|
|
807
807
|
return void 0;
|
|
808
808
|
}
|
|
@@ -837,7 +837,7 @@ function planeApiKeyNames(workspace) {
|
|
|
837
837
|
return ["PLANE_API_KEY", workspaceEnvKey(workspace)];
|
|
838
838
|
}
|
|
839
839
|
function resolvePlaneApiKey(workspace, env2 = process.env, home = homedir6()) {
|
|
840
|
-
const fleetEnv = env2.HERMES_FLEET_ENV?.trim() ||
|
|
840
|
+
const fleetEnv = env2.HERMES_FLEET_ENV?.trim() || join17(home, ".hermes", "fleet.env");
|
|
841
841
|
const scoped = workspaceEnvKey(workspace);
|
|
842
842
|
const candidates = [
|
|
843
843
|
env2.PLANE_API_KEY,
|
|
@@ -859,7 +859,7 @@ function planeClient(ctx) {
|
|
|
859
859
|
const board = ctx.facts.board_id?.trim();
|
|
860
860
|
if (!board) {
|
|
861
861
|
throw new BoardError(
|
|
862
|
-
`no board linked for ${ctx.slug}: ${
|
|
862
|
+
`no board linked for ${ctx.slug}: ${join17(ctx.root, ".project.json")} has an empty ticket_provider.board_id`
|
|
863
863
|
);
|
|
864
864
|
}
|
|
865
865
|
const workspace = planeWorkspace(ctx.facts, ctx.env, ctx.home);
|
|
@@ -869,7 +869,7 @@ function planeClient(ctx) {
|
|
|
869
869
|
const apiKey = resolvePlaneApiKey(workspace, ctx.env, ctx.home);
|
|
870
870
|
if (!apiKey) {
|
|
871
871
|
throw new BoardError(
|
|
872
|
-
`no Plane API key for workspace "${workspace}": set ${planeApiKeyNames(workspace).join(" or ")}, or add one to ${ctx.env.HERMES_FLEET_ENV?.trim() ||
|
|
872
|
+
`no Plane API key for workspace "${workspace}": set ${planeApiKeyNames(workspace).join(" or ")}, or add one to ${ctx.env.HERMES_FLEET_ENV?.trim() || join17(ctx.home, ".hermes", "fleet.env")}`
|
|
873
873
|
);
|
|
874
874
|
}
|
|
875
875
|
return {
|
|
@@ -1036,9 +1036,9 @@ var init_boardQuery = __esm({
|
|
|
1036
1036
|
|
|
1037
1037
|
// src/project/index.ts
|
|
1038
1038
|
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
1039
|
-
import { chmodSync as chmodSync4, closeSync as
|
|
1039
|
+
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 readFileSync17, realpathSync as realpathSync6, renameSync as renameSync5, rmSync as rmSync6, statSync as statSync5, unlinkSync as unlinkSync7, writeFileSync as writeFileSync10 } from "node:fs";
|
|
1040
1040
|
import { homedir as homedir7, tmpdir as tmpdir2 } from "node:os";
|
|
1041
|
-
import { basename as
|
|
1041
|
+
import { basename as basename9, delimiter as delimiter2, dirname as dirname14, isAbsolute as isAbsolute7, join as join18, relative as relative11, resolve as resolve11, sep as sep3, win32 } from "node:path";
|
|
1042
1042
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1043
1043
|
import YAML6 from "yaml";
|
|
1044
1044
|
function normalizeProjectType(value, label = "Project type") {
|
|
@@ -1048,7 +1048,7 @@ function normalizeProjectType(value, label = "Project type") {
|
|
|
1048
1048
|
}
|
|
1049
1049
|
function looksLikeObsidianPlugin(dir) {
|
|
1050
1050
|
try {
|
|
1051
|
-
const manifest = JSON.parse(
|
|
1051
|
+
const manifest = JSON.parse(readFileSync17(join18(dir, "manifest.json"), "utf8"));
|
|
1052
1052
|
return isRecord3(manifest) && typeof manifest.id === "string" && typeof manifest.minAppVersion === "string";
|
|
1053
1053
|
} catch {
|
|
1054
1054
|
return false;
|
|
@@ -1056,10 +1056,10 @@ function looksLikeObsidianPlugin(dir) {
|
|
|
1056
1056
|
}
|
|
1057
1057
|
function assertTemplateOffersProjectType(pjanglerRoot, projectType) {
|
|
1058
1058
|
if (projectType === "base") return;
|
|
1059
|
-
const copierYml =
|
|
1059
|
+
const copierYml = join18(pjanglerRoot, "templates", "commonproject", "copier.yml");
|
|
1060
1060
|
let choices;
|
|
1061
1061
|
try {
|
|
1062
|
-
const parsed = YAML6.parse(
|
|
1062
|
+
const parsed = YAML6.parse(readFileSync17(copierYml, "utf8"));
|
|
1063
1063
|
choices = isRecord3(parsed) && isRecord3(parsed.project_type) ? parsed.project_type.choices : void 0;
|
|
1064
1064
|
} catch {
|
|
1065
1065
|
choices = void 0;
|
|
@@ -1082,9 +1082,9 @@ function carriedAutomation(automation) {
|
|
|
1082
1082
|
return Object.keys(rest).length ? { automation: rest } : {};
|
|
1083
1083
|
}
|
|
1084
1084
|
function synchronizeCopierIdentity(manifestPath, manifest) {
|
|
1085
|
-
const answersPath =
|
|
1085
|
+
const answersPath = join18(dirname14(manifestPath), ".copier-answers.yml");
|
|
1086
1086
|
if (!existsSync11(answersPath)) return [];
|
|
1087
|
-
const current =
|
|
1087
|
+
const current = readFileSync17(answersPath, "utf8");
|
|
1088
1088
|
const document = YAML6.parseDocument(current);
|
|
1089
1089
|
if (document.errors.length) return [];
|
|
1090
1090
|
const name = String(document.get("project_name") ?? "");
|
|
@@ -1094,7 +1094,7 @@ function synchronizeCopierIdentity(manifestPath, manifest) {
|
|
|
1094
1094
|
document.set("project_description", manifest.project_description);
|
|
1095
1095
|
const next = String(document);
|
|
1096
1096
|
if (next === current) return [];
|
|
1097
|
-
|
|
1097
|
+
writeFileSync10(answersPath, next, "utf8");
|
|
1098
1098
|
return [answersPath];
|
|
1099
1099
|
}
|
|
1100
1100
|
function projectRegistryPath(env2 = process.env) {
|
|
@@ -1118,7 +1118,7 @@ function loadProjectRegistry(path = projectRegistryPath()) {
|
|
|
1118
1118
|
return registry2;
|
|
1119
1119
|
}
|
|
1120
1120
|
if (!existsSync11(path)) return emptyProjectRegistry();
|
|
1121
|
-
const raw = YAML6.parse(
|
|
1121
|
+
const raw = YAML6.parse(readFileSync17(path, "utf8"));
|
|
1122
1122
|
if (raw == null) return emptyProjectRegistry();
|
|
1123
1123
|
if (!isRecord3(raw)) throw new Error(`Project registry must be a mapping: ${path}`);
|
|
1124
1124
|
const registry = raw;
|
|
@@ -1186,11 +1186,11 @@ function mergeYamlMapping(document, path, desired, ownedKeys = []) {
|
|
|
1186
1186
|
}
|
|
1187
1187
|
function fsyncDirectory(path) {
|
|
1188
1188
|
try {
|
|
1189
|
-
const fd =
|
|
1189
|
+
const fd = openSync3(path, "r");
|
|
1190
1190
|
try {
|
|
1191
1191
|
fsyncSync(fd);
|
|
1192
1192
|
} finally {
|
|
1193
|
-
|
|
1193
|
+
closeSync3(fd);
|
|
1194
1194
|
}
|
|
1195
1195
|
} catch {
|
|
1196
1196
|
}
|
|
@@ -1204,14 +1204,14 @@ function saveProjectRegistry(registry, path = projectRegistryPath()) {
|
|
|
1204
1204
|
return;
|
|
1205
1205
|
}
|
|
1206
1206
|
validateProjectRegistry(registry);
|
|
1207
|
-
|
|
1207
|
+
mkdirSync8(dirname14(path), { recursive: true });
|
|
1208
1208
|
let text2;
|
|
1209
1209
|
let mode = 420;
|
|
1210
1210
|
if (existsSync11(path)) {
|
|
1211
|
-
const stat =
|
|
1211
|
+
const stat = lstatSync12(path);
|
|
1212
1212
|
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`Project registry must be a regular file: ${path}`);
|
|
1213
1213
|
mode = stat.mode & 511;
|
|
1214
|
-
const current =
|
|
1214
|
+
const current = readFileSync17(path, "utf8");
|
|
1215
1215
|
const document = YAML6.parseDocument(current);
|
|
1216
1216
|
if (document.errors.length) throw new Error(`Project registry YAML is invalid: ${path}`);
|
|
1217
1217
|
setYamlLeaf(document, ["schema_version"], registry.schema_version);
|
|
@@ -1232,22 +1232,22 @@ function saveProjectRegistry(registry, path = projectRegistryPath()) {
|
|
|
1232
1232
|
const temp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
1233
1233
|
let fd;
|
|
1234
1234
|
try {
|
|
1235
|
-
fd =
|
|
1236
|
-
|
|
1237
|
-
|
|
1235
|
+
fd = openSync3(temp, "wx", mode);
|
|
1236
|
+
writeFileSync10(fd, text2, "utf8");
|
|
1237
|
+
fchmodSync2(fd, mode);
|
|
1238
1238
|
fsyncSync(fd);
|
|
1239
|
-
|
|
1239
|
+
closeSync3(fd);
|
|
1240
1240
|
fd = void 0;
|
|
1241
1241
|
renameSync5(temp, path);
|
|
1242
1242
|
chmodSync4(path, mode);
|
|
1243
1243
|
fsyncDirectory(dirname14(path));
|
|
1244
1244
|
} catch (error) {
|
|
1245
1245
|
if (fd !== void 0) try {
|
|
1246
|
-
|
|
1246
|
+
closeSync3(fd);
|
|
1247
1247
|
} catch {
|
|
1248
1248
|
}
|
|
1249
1249
|
try {
|
|
1250
|
-
|
|
1250
|
+
unlinkSync7(temp);
|
|
1251
1251
|
} catch {
|
|
1252
1252
|
}
|
|
1253
1253
|
throw error;
|
|
@@ -1355,18 +1355,18 @@ function ticketProviderKeyVars(provider, workspace) {
|
|
|
1355
1355
|
return ticketProviderKeyGroups(provider, workspace).flat();
|
|
1356
1356
|
}
|
|
1357
1357
|
function ticketProviderSecretsPath(env2 = process.env) {
|
|
1358
|
-
const base = env2.XDG_CONFIG_HOME ||
|
|
1359
|
-
return
|
|
1358
|
+
const base = env2.XDG_CONFIG_HOME || join18(env2.HOME || homedir7(), ".config");
|
|
1359
|
+
return join18(base, "zshyzsh", "secrets.zsh");
|
|
1360
1360
|
}
|
|
1361
1361
|
function ticketProviderFleetEnvPath(env2 = process.env) {
|
|
1362
|
-
return env2.HERMES_FLEET_ENV?.trim() ||
|
|
1362
|
+
return env2.HERMES_FLEET_ENV?.trim() || join18(env2.HOME || homedir7(), ".hermes", "fleet.env");
|
|
1363
1363
|
}
|
|
1364
1364
|
function readShellAssignments(path, keys) {
|
|
1365
1365
|
const found = {};
|
|
1366
1366
|
if (!existsSync11(path)) return found;
|
|
1367
1367
|
let text2;
|
|
1368
1368
|
try {
|
|
1369
|
-
text2 =
|
|
1369
|
+
text2 = readFileSync17(path, "utf8");
|
|
1370
1370
|
} catch {
|
|
1371
1371
|
return found;
|
|
1372
1372
|
}
|
|
@@ -1402,7 +1402,7 @@ function resolveTicketProviderCredentials(input) {
|
|
|
1402
1402
|
}
|
|
1403
1403
|
}
|
|
1404
1404
|
const candidates = [];
|
|
1405
|
-
if (input.repoPath) candidates.push(
|
|
1405
|
+
if (input.repoPath) candidates.push(join18(input.repoPath, ".env"));
|
|
1406
1406
|
candidates.push(ticketProviderFleetEnvPath(env2));
|
|
1407
1407
|
candidates.push(ticketProviderSecretsPath(env2));
|
|
1408
1408
|
for (const candidate of candidates) {
|
|
@@ -1420,21 +1420,26 @@ function resolveTicketProviderAdapter(provider, env2 = process.env) {
|
|
|
1420
1420
|
const file = `${provider}.sh`;
|
|
1421
1421
|
const candidates = [];
|
|
1422
1422
|
const override = env2[TICKET_PROVIDER_ADAPTERS_ENV];
|
|
1423
|
-
if (override) candidates.push(
|
|
1424
|
-
const relativeRoot =
|
|
1423
|
+
if (override) candidates.push(join18(override, file));
|
|
1424
|
+
const relativeRoot = join18("krebs", "adapters", "tp");
|
|
1425
1425
|
try {
|
|
1426
1426
|
let dir = dirname14(fileURLToPath2(import.meta.url));
|
|
1427
1427
|
for (let depth = 0; depth < 8; depth++) {
|
|
1428
|
-
candidates.push(
|
|
1428
|
+
candidates.push(join18(dir, relativeRoot, file));
|
|
1429
1429
|
const parent = dirname14(dir);
|
|
1430
1430
|
if (parent === dir) break;
|
|
1431
1431
|
dir = parent;
|
|
1432
1432
|
}
|
|
1433
1433
|
} catch {
|
|
1434
1434
|
}
|
|
1435
|
-
candidates.push(
|
|
1435
|
+
candidates.push(join18(homedir7(), "code", "33GOD", relativeRoot, file));
|
|
1436
1436
|
return candidates.find((candidate) => existsSync11(candidate));
|
|
1437
1437
|
}
|
|
1438
|
+
function providerBoardName(provider, name) {
|
|
1439
|
+
if (provider !== "plane") return name;
|
|
1440
|
+
const cleaned = name.replace(/[^\p{L}\p{N}_\s]+/gu, " ").replace(/\s+/g, " ").trim();
|
|
1441
|
+
return cleaned || name;
|
|
1442
|
+
}
|
|
1438
1443
|
function provisionTicketProviderBoard(action, env2 = process.env) {
|
|
1439
1444
|
const provider = action.provider;
|
|
1440
1445
|
const groups = ticketProviderKeyGroups(provider, action.workspace);
|
|
@@ -1450,7 +1455,7 @@ function provisionTicketProviderBoard(action, env2 = process.env) {
|
|
|
1450
1455
|
ok: true,
|
|
1451
1456
|
skipped: true,
|
|
1452
1457
|
logs: [
|
|
1453
|
-
`ticket-provider: ${names} not set; skipping ${provider} board creation (state stays "planned"). Set it in the environment, ${
|
|
1458
|
+
`ticket-provider: ${names} not set; skipping ${provider} board creation (state stays "planned"). Set it in the environment, ${join18(action.repoPath, ".env")}, ${ticketProviderFleetEnvPath(env2)}, or ${ticketProviderSecretsPath(env2)} \u2014 or pass --board-id to link an existing board.`
|
|
1454
1459
|
]
|
|
1455
1460
|
};
|
|
1456
1461
|
}
|
|
@@ -1464,12 +1469,12 @@ function provisionTicketProviderBoard(action, env2 = process.env) {
|
|
|
1464
1469
|
};
|
|
1465
1470
|
}
|
|
1466
1471
|
const redact = (text2) => Object.values(values).reduce((acc, secret) => secret ? acc.split(secret).join("***") : acc, text2);
|
|
1467
|
-
const staging =
|
|
1472
|
+
const staging = mkdtempSync3(join18(tmpdir2(), "pjangler-tp-"));
|
|
1468
1473
|
try {
|
|
1469
|
-
const providersDir =
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1474
|
+
const providersDir = join18(staging, ".tp", "adapters");
|
|
1475
|
+
mkdirSync8(providersDir, { recursive: true });
|
|
1476
|
+
writeFileSync10(
|
|
1477
|
+
join18(staging, ".project.json"),
|
|
1473
1478
|
`${JSON.stringify(
|
|
1474
1479
|
{
|
|
1475
1480
|
project_name: action.boardName,
|
|
@@ -1488,7 +1493,7 @@ function provisionTicketProviderBoard(action, env2 = process.env) {
|
|
|
1488
1493
|
`,
|
|
1489
1494
|
"utf8"
|
|
1490
1495
|
);
|
|
1491
|
-
const staged =
|
|
1496
|
+
const staged = join18(providersDir, `${provider}.sh`);
|
|
1492
1497
|
copyFileSync2(adapter, staged);
|
|
1493
1498
|
const childEnv = { ...env2, ...values, TICKET_PROVIDER: provider };
|
|
1494
1499
|
if (provider === "plane" && action.workspace) childEnv.PLANE_WORKSPACE = action.workspace;
|
|
@@ -1584,7 +1589,7 @@ function resolveAgentHooksLayer2(input, env2 = process.env) {
|
|
|
1584
1589
|
const override = env2.PJ_AGENT_HOOKS_LAYER;
|
|
1585
1590
|
if (override === "0" || override === "false") return false;
|
|
1586
1591
|
if (override === "1" || override === "true") return true;
|
|
1587
|
-
return !existsSync11(
|
|
1592
|
+
return !existsSync11(join18(homedir7(), ".agents", "hooks"));
|
|
1588
1593
|
}
|
|
1589
1594
|
function jsonStable(value) {
|
|
1590
1595
|
return JSON.stringify(value, (_key, item) => isRecord3(item) ? Object.fromEntries(Object.keys(item).sort().map((key) => [key, item[key]])) : item);
|
|
@@ -1626,10 +1631,10 @@ function resolveSourceSkillPath(sourceSkill, env2 = process.env) {
|
|
|
1626
1631
|
const expanded = expandHome(sourceSkill);
|
|
1627
1632
|
const direct = resolve11(expanded);
|
|
1628
1633
|
if (existsSync11(direct)) return direct;
|
|
1629
|
-
const name =
|
|
1634
|
+
const name = basename9(sourceSkill);
|
|
1630
1635
|
const roots = sourceSkillRoots(env2);
|
|
1631
1636
|
for (const root of roots) {
|
|
1632
|
-
const candidate =
|
|
1637
|
+
const candidate = join18(root, name);
|
|
1633
1638
|
if (existsSync11(candidate)) return candidate;
|
|
1634
1639
|
}
|
|
1635
1640
|
const searched = roots.length ? ` Searched roots: ${roots.join(", ")}.` : "";
|
|
@@ -1639,8 +1644,8 @@ function resolveSourceSkillPath(sourceSkill, env2 = process.env) {
|
|
|
1639
1644
|
function planProjectInit(input) {
|
|
1640
1645
|
if (!input.name.trim()) throw new Error("Project name is required");
|
|
1641
1646
|
const targetDir = resolve11(input.targetDir ?? defaultProjectTargetDir(input.name, input.cwd));
|
|
1642
|
-
const localManifestPath =
|
|
1643
|
-
const localManifestContent = existsSync11(localManifestPath) ?
|
|
1647
|
+
const localManifestPath = join18(targetDir, ".project.json");
|
|
1648
|
+
const localManifestContent = existsSync11(localManifestPath) ? readFileSync17(localManifestPath, "utf8") : void 0;
|
|
1644
1649
|
const localManifest = localManifestContent === void 0 ? void 0 : JSON.parse(localManifestContent);
|
|
1645
1650
|
const localId = localManifest?.project_id ?? localManifest?.project_slug;
|
|
1646
1651
|
const slug = normalizeProjectId(input.projectSlug ?? localId ?? slugifyProjectName(input.name));
|
|
@@ -1668,7 +1673,7 @@ function planProjectInit(input) {
|
|
|
1668
1673
|
const recordedTypeValue = existing?.template?.commonproject?.project_type;
|
|
1669
1674
|
const recordedType = recordedTypeValue === void 0 ? void 0 : normalizeProjectType(recordedTypeValue, `Recorded project type of ${slug}`);
|
|
1670
1675
|
const requestedType = input.projectType === void 0 ? void 0 : normalizeProjectType(input.projectType);
|
|
1671
|
-
const freshRender = scaffold && localManifest === void 0 && !existsSync11(
|
|
1676
|
+
const freshRender = scaffold && localManifest === void 0 && !existsSync11(join18(targetDir, ".copier-answers.yml"));
|
|
1672
1677
|
if (requestedType && !freshRender && requestedType !== (recordedType ?? "base")) {
|
|
1673
1678
|
const adoptingPlugin = !scaffold && recordedType === void 0 && requestedType === "obsidian-plugin" && looksLikeObsidianPlugin(targetDir);
|
|
1674
1679
|
if (!adoptingPlugin) {
|
|
@@ -1766,7 +1771,7 @@ function planProjectInit(input) {
|
|
|
1766
1771
|
}));
|
|
1767
1772
|
}
|
|
1768
1773
|
actions.push(
|
|
1769
|
-
{ kind: "project.write-manifest", path:
|
|
1774
|
+
{ kind: "project.write-manifest", path: join18(targetDir, ".project.json"), manifest, expectedContent: localManifestContent },
|
|
1770
1775
|
{
|
|
1771
1776
|
kind: "ticket-provider.create-or-link",
|
|
1772
1777
|
enabled: boardEnabled,
|
|
@@ -1775,11 +1780,11 @@ function planProjectInit(input) {
|
|
|
1775
1780
|
workspace: project.ticket_provider.workspace ?? "33god",
|
|
1776
1781
|
identifier,
|
|
1777
1782
|
repoPath: targetDir,
|
|
1778
|
-
boardName: project.name,
|
|
1783
|
+
boardName: providerBoardName(project.ticket_provider.type, project.name),
|
|
1779
1784
|
description: project.description || `Ticket board for ${project.slug}`,
|
|
1780
1785
|
boardId: project.ticket_provider.board_id ?? "",
|
|
1781
1786
|
state: project.ticket_provider.board_id ? "linked" : "planned",
|
|
1782
|
-
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`
|
|
1787
|
+
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`
|
|
1783
1788
|
}
|
|
1784
1789
|
);
|
|
1785
1790
|
return {
|
|
@@ -1821,12 +1826,12 @@ function linkTicketProviderBoard(plan, action, boardId, identifier, now = /* @__
|
|
|
1821
1826
|
plan.manifest.ticket_provider = manifestProvider;
|
|
1822
1827
|
action.boardId = boardId;
|
|
1823
1828
|
action.state = manifestProvider.state;
|
|
1824
|
-
const manifestPath =
|
|
1829
|
+
const manifestPath = join18(action.repoPath, ".project.json");
|
|
1825
1830
|
let next;
|
|
1826
1831
|
if (existsSync11(manifestPath)) {
|
|
1827
1832
|
let existing = {};
|
|
1828
1833
|
try {
|
|
1829
|
-
const parsed = JSON.parse(
|
|
1834
|
+
const parsed = JSON.parse(readFileSync17(manifestPath, "utf8"));
|
|
1830
1835
|
if (isRecord3(parsed)) existing = parsed;
|
|
1831
1836
|
} catch {
|
|
1832
1837
|
existing = {};
|
|
@@ -1834,13 +1839,13 @@ function linkTicketProviderBoard(plan, action, boardId, identifier, now = /* @__
|
|
|
1834
1839
|
const existingProvider = isRecord3(existing.ticket_provider) ? existing.ticket_provider : {};
|
|
1835
1840
|
next = { ...existing, ticket_provider: { ...existingProvider, ...manifestProvider } };
|
|
1836
1841
|
} else {
|
|
1837
|
-
|
|
1842
|
+
mkdirSync8(dirname14(manifestPath), { recursive: true });
|
|
1838
1843
|
next = plan.manifest;
|
|
1839
1844
|
}
|
|
1840
1845
|
const text2 = `${JSON.stringify(next, null, 2)}
|
|
1841
1846
|
`;
|
|
1842
|
-
if (!existsSync11(manifestPath) ||
|
|
1843
|
-
|
|
1847
|
+
if (!existsSync11(manifestPath) || readFileSync17(manifestPath, "utf8") !== text2) {
|
|
1848
|
+
writeFileSync10(manifestPath, text2, "utf8");
|
|
1844
1849
|
return [manifestPath];
|
|
1845
1850
|
}
|
|
1846
1851
|
return [];
|
|
@@ -1868,7 +1873,7 @@ async function executeProjectInitPlan(plan, options = {}) {
|
|
|
1868
1873
|
logs.push(
|
|
1869
1874
|
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"
|
|
1870
1875
|
);
|
|
1871
|
-
|
|
1876
|
+
mkdirSync8(dirname14(action.targetDir), { recursive: true });
|
|
1872
1877
|
const before = snapshotTree(action.targetDir);
|
|
1873
1878
|
const copierExecutable = options.trustedCopier?.executable ?? action.command[0];
|
|
1874
1879
|
const copierEnv = { ...process.env };
|
|
@@ -1900,22 +1905,22 @@ async function executeProjectInitPlan(plan, options = {}) {
|
|
|
1900
1905
|
break;
|
|
1901
1906
|
}
|
|
1902
1907
|
const projectType = action.data.project_type ?? "base";
|
|
1903
|
-
const missing = (PROJECT_TYPE_SENTINELS[projectType] ?? []).filter((rel) => !existsSync11(
|
|
1908
|
+
const missing = (PROJECT_TYPE_SENTINELS[projectType] ?? []).filter((rel) => !existsSync11(join18(action.targetDir, rel)));
|
|
1904
1909
|
if (missing.length) {
|
|
1905
1910
|
errors.push(`copier rendered no ${projectType} project: ${missing.join(", ")} missing from ${action.targetDir}`);
|
|
1906
1911
|
break;
|
|
1907
1912
|
}
|
|
1908
1913
|
if (projectType !== "base") logs.push(`commonproject: rendered as ${projectType}`);
|
|
1909
1914
|
} else if (action.kind === "project.write-manifest") {
|
|
1910
|
-
if (action.expectedContent !== void 0 && (!existsSync11(action.path) ||
|
|
1915
|
+
if (action.expectedContent !== void 0 && (!existsSync11(action.path) || readFileSync17(action.path, "utf8") !== action.expectedContent)) {
|
|
1911
1916
|
errors.push(`Manifest changed after planning: ${action.path}; re-plan before applying`);
|
|
1912
1917
|
break;
|
|
1913
1918
|
}
|
|
1914
|
-
|
|
1919
|
+
mkdirSync8(dirname14(action.path), { recursive: true });
|
|
1915
1920
|
let value = action.manifest;
|
|
1916
1921
|
if (existsSync11(action.path)) {
|
|
1917
1922
|
try {
|
|
1918
|
-
const currentValue = JSON.parse(
|
|
1923
|
+
const currentValue = JSON.parse(readFileSync17(action.path, "utf8"));
|
|
1919
1924
|
if (isRecord3(currentValue)) {
|
|
1920
1925
|
const currentNotebook = isRecord3(currentValue.notebook) ? currentValue.notebook : {};
|
|
1921
1926
|
const desiredNotebook = isRecord3(value.notebook) ? value.notebook : {};
|
|
@@ -1943,9 +1948,9 @@ async function executeProjectInitPlan(plan, options = {}) {
|
|
|
1943
1948
|
value.project_id = normalizeProjectId(value.project_id ?? action.manifest.project_id);
|
|
1944
1949
|
const next = `${JSON.stringify(value, null, 2)}
|
|
1945
1950
|
`;
|
|
1946
|
-
const current = existsSync11(action.path) ?
|
|
1951
|
+
const current = existsSync11(action.path) ? readFileSync17(action.path, "utf8") : void 0;
|
|
1947
1952
|
if (current !== next) {
|
|
1948
|
-
|
|
1953
|
+
writeFileSync10(action.path, next, "utf8");
|
|
1949
1954
|
changedFiles.push(action.path);
|
|
1950
1955
|
}
|
|
1951
1956
|
changedFiles.push(...synchronizeCopierIdentity(action.path, action.manifest));
|
|
@@ -2128,7 +2133,7 @@ function doctorProjectRegistry(registryPath = projectRegistryPath(), slug) {
|
|
|
2128
2133
|
} else if (!statSync5(project.repo_path).isDirectory()) {
|
|
2129
2134
|
issues.push({ level: "error", slug: projectSlug, message: `repo_path is not a directory: ${project.repo_path}` });
|
|
2130
2135
|
} else {
|
|
2131
|
-
const manifestPath =
|
|
2136
|
+
const manifestPath = join18(project.repo_path, ".project.json");
|
|
2132
2137
|
if (!existsSync11(manifestPath)) issues.push({ level: "warn", slug: projectSlug, message: ".project.json is missing" });
|
|
2133
2138
|
}
|
|
2134
2139
|
for (const artifact of project.source_artifacts) {
|
|
@@ -2145,7 +2150,7 @@ function doctorProjectRegistry(registryPath = projectRegistryPath(), slug) {
|
|
|
2145
2150
|
};
|
|
2146
2151
|
}
|
|
2147
2152
|
function buildCommonProjectCopierAction(input) {
|
|
2148
|
-
const templateDir =
|
|
2153
|
+
const templateDir = join18(input.pjanglerRoot, "templates", "commonproject");
|
|
2149
2154
|
const data = {
|
|
2150
2155
|
project_name: input.projectName,
|
|
2151
2156
|
project_description: input.projectDescription ?? "",
|
|
@@ -2176,7 +2181,7 @@ function buildCommonProjectCopierAction(input) {
|
|
|
2176
2181
|
function resolvePjanglerRoot() {
|
|
2177
2182
|
let dir = dirname14(fileURLToPath2(import.meta.url));
|
|
2178
2183
|
while (dir !== dirname14(dir)) {
|
|
2179
|
-
if (existsSync11(
|
|
2184
|
+
if (existsSync11(join18(dir, "package.json")) && existsSync11(join18(dir, "templates", "commonproject", "copier.yml"))) return dir;
|
|
2180
2185
|
dir = dirname14(dir);
|
|
2181
2186
|
}
|
|
2182
2187
|
return resolve11(process.cwd());
|
|
@@ -2253,7 +2258,7 @@ function validateProjectRecord(project, key) {
|
|
|
2253
2258
|
}
|
|
2254
2259
|
function expandHome(path) {
|
|
2255
2260
|
if (path === "~") return homedir7();
|
|
2256
|
-
if (path.startsWith("~/")) return
|
|
2261
|
+
if (path.startsWith("~/")) return join18(homedir7(), path.slice(2));
|
|
2257
2262
|
return path;
|
|
2258
2263
|
}
|
|
2259
2264
|
function isRecord3(value) {
|
|
@@ -2280,7 +2285,7 @@ var init_project = __esm({
|
|
|
2280
2285
|
"obsidian-plugin": ["typescript", "javascript"]
|
|
2281
2286
|
};
|
|
2282
2287
|
PROJECT_TYPE_SENTINELS = {
|
|
2283
|
-
"obsidian-plugin": ["manifest.json",
|
|
2288
|
+
"obsidian-plugin": ["manifest.json", join18("src", "main.ts")]
|
|
2284
2289
|
};
|
|
2285
2290
|
BOARD_URL_DEPRECATION_WARNING = "boardUrl is deprecated and ignored; board URLs are derived at runtime and are never persisted.";
|
|
2286
2291
|
PROJECT_IDENTIFIER_SOURCES = ["provider", "proposed"];
|
|
@@ -2289,8 +2294,8 @@ var init_project = __esm({
|
|
|
2289
2294
|
IDENTIFIER_REPAIR_COMMAND = "pj project identity --all --apply";
|
|
2290
2295
|
DEFAULT_SOURCE_SKILL_ROOTS = [
|
|
2291
2296
|
"/home/delorenj/code/skillex/all-skills",
|
|
2292
|
-
|
|
2293
|
-
|
|
2297
|
+
join18(homedir7(), ".agents", "skills"),
|
|
2298
|
+
join18(homedir7(), ".codex", "skills")
|
|
2294
2299
|
];
|
|
2295
2300
|
PROJECT_REGISTRY_OWNED_KEYS = [
|
|
2296
2301
|
"name",
|
|
@@ -2322,7 +2327,7 @@ var init_project = __esm({
|
|
|
2322
2327
|
import { randomUUID } from "node:crypto";
|
|
2323
2328
|
import { isDeepStrictEqual } from "node:util";
|
|
2324
2329
|
import { isIP as isIP2 } from "node:net";
|
|
2325
|
-
import { closeSync as
|
|
2330
|
+
import { closeSync as closeSync4, existsSync as existsSync12, fchmodSync as fchmodSync3, fsyncSync as fsyncSync2, lstatSync as lstatSync13, openSync as openSync4, readFileSync as readFileSync18, realpathSync as realpathSync7, renameSync as renameSync6, writeFileSync as writeFileSync11 } from "node:fs";
|
|
2326
2331
|
import { dirname as dirname15, resolve as resolve12 } from "node:path";
|
|
2327
2332
|
function isRecord4(value) {
|
|
2328
2333
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
@@ -2336,7 +2341,7 @@ function readManifest(repoPath) {
|
|
|
2336
2341
|
if (!existsSync12(path)) return null;
|
|
2337
2342
|
let parsed;
|
|
2338
2343
|
try {
|
|
2339
|
-
parsed = JSON.parse(
|
|
2344
|
+
parsed = JSON.parse(readFileSync18(path, "utf8"));
|
|
2340
2345
|
} catch {
|
|
2341
2346
|
throw new NotebookError("INVALID_INPUT", `${path} is not valid JSON`);
|
|
2342
2347
|
}
|
|
@@ -2578,7 +2583,7 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
|
|
|
2578
2583
|
const changed = [];
|
|
2579
2584
|
const manifestPath = resolve12(resolved.project.repo_path, ".project.json");
|
|
2580
2585
|
if (isRegistryServiceLocation(resolved.registry_path)) {
|
|
2581
|
-
const before = existsSync12(manifestPath) ?
|
|
2586
|
+
const before = existsSync12(manifestPath) ? readFileSync18(manifestPath, "utf8") : null;
|
|
2582
2587
|
const declaredPolicy = resolved.manifest?.notebook?.policy ?? {};
|
|
2583
2588
|
const nextProject = {
|
|
2584
2589
|
...resolved.project,
|
|
@@ -2590,10 +2595,10 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
|
|
|
2590
2595
|
resolved.registry = loadProjectRegistry(resolved.registry_path);
|
|
2591
2596
|
resolved.project = registryProjectBySlug(resolved.registry, nextProject.slug);
|
|
2592
2597
|
resolved.manifest = readManifest(nextProject.repo_path);
|
|
2593
|
-
const after = existsSync12(manifestPath) ?
|
|
2598
|
+
const after = existsSync12(manifestPath) ? readFileSync18(manifestPath, "utf8") : null;
|
|
2594
2599
|
return before === after ? [] : [manifestPath];
|
|
2595
2600
|
}
|
|
2596
|
-
const manifestRaw = existsSync12(manifestPath) ? JSON.parse(
|
|
2601
|
+
const manifestRaw = existsSync12(manifestPath) ? JSON.parse(readFileSync18(manifestPath, "utf8")) : {};
|
|
2597
2602
|
if (!isRecord4(manifestRaw)) throw new NotebookError("INVALID_INPUT", `${manifestPath} must contain a JSON object`);
|
|
2598
2603
|
validateManifestNotebookSurface(manifestRaw.notebook);
|
|
2599
2604
|
const manifestNotebook2 = isRecord4(manifestRaw.notebook) ? manifestRaw.notebook : {};
|
|
@@ -2626,29 +2631,29 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
|
|
|
2626
2631
|
};
|
|
2627
2632
|
const manifestText = `${JSON.stringify(manifestNext, null, 2)}
|
|
2628
2633
|
`;
|
|
2629
|
-
if (!existsSync12(manifestPath) ||
|
|
2634
|
+
if (!existsSync12(manifestPath) || readFileSync18(manifestPath, "utf8") !== manifestText) {
|
|
2630
2635
|
let mode = 420;
|
|
2631
2636
|
if (existsSync12(manifestPath)) {
|
|
2632
|
-
const current2 =
|
|
2637
|
+
const current2 = lstatSync13(manifestPath);
|
|
2633
2638
|
if (!current2.isFile() || current2.isSymbolicLink()) throw new NotebookError("CONFLICT", `${manifestPath} must be a regular non-symlink file`);
|
|
2634
2639
|
mode = current2.mode & 511;
|
|
2635
2640
|
}
|
|
2636
2641
|
const temp = resolve12(dirname15(manifestPath), `.${process.pid}.${randomUUID()}.project.json.tmp`);
|
|
2637
|
-
const fd =
|
|
2642
|
+
const fd = openSync4(temp, "wx", mode);
|
|
2638
2643
|
try {
|
|
2639
|
-
|
|
2640
|
-
|
|
2644
|
+
writeFileSync11(fd, manifestText, "utf8");
|
|
2645
|
+
fchmodSync3(fd, mode);
|
|
2641
2646
|
fsyncSync2(fd);
|
|
2642
2647
|
} finally {
|
|
2643
|
-
|
|
2648
|
+
closeSync4(fd);
|
|
2644
2649
|
}
|
|
2645
2650
|
renameSync6(temp, manifestPath);
|
|
2646
2651
|
try {
|
|
2647
|
-
const directory =
|
|
2652
|
+
const directory = openSync4(dirname15(manifestPath), "r");
|
|
2648
2653
|
try {
|
|
2649
2654
|
fsyncSync2(directory);
|
|
2650
2655
|
} finally {
|
|
2651
|
-
|
|
2656
|
+
closeSync4(directory);
|
|
2652
2657
|
}
|
|
2653
2658
|
} catch {
|
|
2654
2659
|
}
|
|
@@ -2657,9 +2662,9 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
|
|
|
2657
2662
|
const current = resolved.project.notebook;
|
|
2658
2663
|
resolved.project.notebook = { ...isRecord4(current) ? current : { state: "planned" }, ...binding };
|
|
2659
2664
|
const fileRegistry = !isRegistryServiceLocation(resolved.registry_path);
|
|
2660
|
-
const registryBefore = fileRegistry && existsSync12(resolved.registry_path) ?
|
|
2665
|
+
const registryBefore = fileRegistry && existsSync12(resolved.registry_path) ? readFileSync18(resolved.registry_path, "utf8") : null;
|
|
2661
2666
|
saveProjectRegistry(resolved.registry, resolved.registry_path);
|
|
2662
|
-
const registryAfter = fileRegistry && existsSync12(resolved.registry_path) ?
|
|
2667
|
+
const registryAfter = fileRegistry && existsSync12(resolved.registry_path) ? readFileSync18(resolved.registry_path, "utf8") : null;
|
|
2663
2668
|
if (registryBefore !== registryAfter) changed.push(resolved.registry_path);
|
|
2664
2669
|
resolved.manifest = manifestNext;
|
|
2665
2670
|
return changed;
|
|
@@ -2692,7 +2697,7 @@ var init_config = __esm({
|
|
|
2692
2697
|
});
|
|
2693
2698
|
|
|
2694
2699
|
// src/notebook/notes.ts
|
|
2695
|
-
import { createHash as
|
|
2700
|
+
import { createHash as createHash10, randomUUID as randomUUID2 } from "node:crypto";
|
|
2696
2701
|
function canonicalJson2(value) {
|
|
2697
2702
|
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");
|
|
2698
2703
|
if (value === null || typeof value !== "object") {
|
|
@@ -2705,7 +2710,7 @@ function canonicalJson2(value) {
|
|
|
2705
2710
|
return `{${Object.keys(record3).filter((key) => record3[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson2(record3[key])}`).join(",")}}`;
|
|
2706
2711
|
}
|
|
2707
2712
|
function sha256Hex(value) {
|
|
2708
|
-
return
|
|
2713
|
+
return createHash10("sha256").update(value).digest("hex");
|
|
2709
2714
|
}
|
|
2710
2715
|
function base64UrlEncode(value) {
|
|
2711
2716
|
return Buffer.from(value, "utf8").toString("base64url");
|
|
@@ -3256,32 +3261,32 @@ var init_remote_mutation_schema = __esm({
|
|
|
3256
3261
|
});
|
|
3257
3262
|
|
|
3258
3263
|
// src/notebook/state.ts
|
|
3259
|
-
import { createHash as
|
|
3264
|
+
import { createHash as createHash11, randomUUID as randomUUID3 } from "node:crypto";
|
|
3260
3265
|
import {
|
|
3261
|
-
closeSync as
|
|
3262
|
-
constants as
|
|
3266
|
+
closeSync as closeSync5,
|
|
3267
|
+
constants as constants4,
|
|
3263
3268
|
existsSync as existsSync13,
|
|
3264
|
-
fchmodSync as
|
|
3265
|
-
fstatSync,
|
|
3269
|
+
fchmodSync as fchmodSync4,
|
|
3270
|
+
fstatSync as fstatSync2,
|
|
3266
3271
|
fsyncSync as fsyncSync3,
|
|
3267
|
-
lstatSync as
|
|
3268
|
-
mkdirSync as
|
|
3269
|
-
openSync as
|
|
3272
|
+
lstatSync as lstatSync14,
|
|
3273
|
+
mkdirSync as mkdirSync9,
|
|
3274
|
+
openSync as openSync5,
|
|
3270
3275
|
readSync,
|
|
3271
|
-
readdirSync as
|
|
3276
|
+
readdirSync as readdirSync10,
|
|
3272
3277
|
renameSync as renameSync7,
|
|
3273
|
-
unlinkSync as
|
|
3274
|
-
writeFileSync as
|
|
3278
|
+
unlinkSync as unlinkSync8,
|
|
3279
|
+
writeFileSync as writeFileSync12
|
|
3275
3280
|
} from "node:fs";
|
|
3276
3281
|
import { homedir as homedir8 } from "node:os";
|
|
3277
|
-
import { basename as
|
|
3282
|
+
import { basename as basename10, dirname as dirname16, join as join19, parse, relative as relative12, resolve as resolve13, sep as sep4 } from "node:path";
|
|
3278
3283
|
function notebookStateRoot(env2 = process.env) {
|
|
3279
|
-
const base = env2.XDG_STATE_HOME ||
|
|
3284
|
+
const base = env2.XDG_STATE_HOME || join19(env2.HOME || homedir8(), ".local", "state");
|
|
3280
3285
|
return resolve13(base, "pjangler", "notebook", NOTEBOOK_STATE_VERSION);
|
|
3281
3286
|
}
|
|
3282
3287
|
function deriveSessionKey(projectSlug, client, clientSessionId) {
|
|
3283
3288
|
if (!projectSlug || !client || !clientSessionId) throw new NotebookError("INVALID_INPUT", "project slug, client, and client session id are required");
|
|
3284
|
-
return
|
|
3289
|
+
return createHash11("sha256").update(`pjangler-session-v1\0${projectSlug}\0${client}\0${clientSessionId}`, "utf8").digest("hex");
|
|
3285
3290
|
}
|
|
3286
3291
|
function deriveReceiptId(sessionKey) {
|
|
3287
3292
|
assertDigest(sessionKey, "session key");
|
|
@@ -3295,7 +3300,7 @@ function assertDigest(value, label) {
|
|
|
3295
3300
|
}
|
|
3296
3301
|
function projectStateDir(root, projectSlug) {
|
|
3297
3302
|
if (!/^[a-z0-9][a-z0-9._-]{0,127}$/iu.test(projectSlug)) throw new NotebookError("INVALID_INPUT", "Invalid project slug for Notebook state");
|
|
3298
|
-
return
|
|
3303
|
+
return join19(resolve13(root), "projects", sha256Hex(`pjangler-project-state-v1\0${projectSlug}`));
|
|
3299
3304
|
}
|
|
3300
3305
|
function assertContained(root, candidate) {
|
|
3301
3306
|
const rel = relative12(resolve13(root), resolve13(candidate));
|
|
@@ -3308,33 +3313,33 @@ function openPinnedDirectory(path, root, create) {
|
|
|
3308
3313
|
assertContained(absoluteRoot, absolute);
|
|
3309
3314
|
if (!existsSync13("/proc/self/fd")) throw new NotebookError("INTERNAL_ERROR", "Descriptor-pinned Notebook state requires procfs");
|
|
3310
3315
|
const parsed = parse(absolute);
|
|
3311
|
-
let fd =
|
|
3316
|
+
let fd = openSync5(parsed.root, DIRECTORY_OPEN_FLAGS);
|
|
3312
3317
|
let cursor = parsed.root;
|
|
3313
3318
|
try {
|
|
3314
3319
|
for (const part of absolute.slice(parsed.root.length).split(sep4).filter(Boolean)) {
|
|
3315
3320
|
const child = `/proc/self/fd/${fd}/${part}`;
|
|
3316
|
-
cursor =
|
|
3321
|
+
cursor = join19(cursor, part);
|
|
3317
3322
|
let childFd;
|
|
3318
3323
|
try {
|
|
3319
|
-
childFd =
|
|
3324
|
+
childFd = openSync5(child, DIRECTORY_OPEN_FLAGS);
|
|
3320
3325
|
} catch (error) {
|
|
3321
3326
|
if (!create || error.code !== "ENOENT") throw error;
|
|
3322
3327
|
try {
|
|
3323
|
-
|
|
3328
|
+
mkdirSync9(child, { mode: 448 });
|
|
3324
3329
|
} catch (mkdirError) {
|
|
3325
3330
|
if (mkdirError.code !== "EEXIST") throw mkdirError;
|
|
3326
3331
|
}
|
|
3327
|
-
childFd =
|
|
3332
|
+
childFd = openSync5(child, DIRECTORY_OPEN_FLAGS);
|
|
3328
3333
|
}
|
|
3329
|
-
|
|
3334
|
+
closeSync5(fd);
|
|
3330
3335
|
fd = childFd;
|
|
3331
|
-
const stat =
|
|
3336
|
+
const stat = fstatSync2(fd);
|
|
3332
3337
|
if (!stat.isDirectory()) throw new NotebookError("INTERNAL_ERROR", "Notebook state path component is not a directory");
|
|
3333
3338
|
if (cursor === absoluteRoot || relative12(absoluteRoot, cursor).startsWith("..") === false) assertOwned(stat);
|
|
3334
3339
|
}
|
|
3335
3340
|
return fd;
|
|
3336
3341
|
} catch (error) {
|
|
3337
|
-
|
|
3342
|
+
closeSync5(fd);
|
|
3338
3343
|
if (error.code === "ELOOP" || error.code === "ENOTDIR") {
|
|
3339
3344
|
throw new NotebookError("INTERNAL_ERROR", "Notebook state path contains a symlink component");
|
|
3340
3345
|
}
|
|
@@ -3342,7 +3347,7 @@ function openPinnedDirectory(path, root, create) {
|
|
|
3342
3347
|
}
|
|
3343
3348
|
}
|
|
3344
3349
|
function pinnedLeaf(parentFd, path) {
|
|
3345
|
-
return `/proc/self/fd/${parentFd}/${
|
|
3350
|
+
return `/proc/self/fd/${parentFd}/${basename10(path)}`;
|
|
3346
3351
|
}
|
|
3347
3352
|
function assertOwned(stat) {
|
|
3348
3353
|
if (typeof process.getuid === "function" && stat.uid !== process.getuid()) {
|
|
@@ -3355,13 +3360,13 @@ function ensureDirectory(path, root) {
|
|
|
3355
3360
|
assertContained(absoluteRoot, absolute);
|
|
3356
3361
|
const fd = openPinnedDirectory(absolute, absoluteRoot, true);
|
|
3357
3362
|
try {
|
|
3358
|
-
const stat =
|
|
3363
|
+
const stat = fstatSync2(fd);
|
|
3359
3364
|
if (!stat.isDirectory()) throw new NotebookError("INTERNAL_ERROR", "Notebook state directory is not a real directory");
|
|
3360
3365
|
assertOwned(stat);
|
|
3361
|
-
|
|
3366
|
+
fchmodSync4(fd, 448);
|
|
3362
3367
|
fsyncSync3(fd);
|
|
3363
3368
|
} finally {
|
|
3364
|
-
|
|
3369
|
+
closeSync5(fd);
|
|
3365
3370
|
}
|
|
3366
3371
|
}
|
|
3367
3372
|
function notebookStatePaths(root, projectSlug) {
|
|
@@ -3370,18 +3375,18 @@ function notebookStatePaths(root, projectSlug) {
|
|
|
3370
3375
|
return {
|
|
3371
3376
|
root: absoluteRoot,
|
|
3372
3377
|
project,
|
|
3373
|
-
baselines:
|
|
3374
|
-
claims:
|
|
3375
|
-
receipts:
|
|
3376
|
-
refusals:
|
|
3377
|
-
journals:
|
|
3378
|
-
locks:
|
|
3378
|
+
baselines: join19(project, "baselines"),
|
|
3379
|
+
claims: join19(project, "claims"),
|
|
3380
|
+
receipts: join19(project, "receipts"),
|
|
3381
|
+
refusals: join19(project, "refusals"),
|
|
3382
|
+
journals: join19(project, "journals"),
|
|
3383
|
+
locks: join19(project, "locks")
|
|
3379
3384
|
};
|
|
3380
3385
|
}
|
|
3381
3386
|
function ensureNotebookState(root, projectSlug) {
|
|
3382
3387
|
const paths = notebookStatePaths(root, projectSlug);
|
|
3383
3388
|
const { root: absoluteRoot, project } = paths;
|
|
3384
|
-
for (const path of [absoluteRoot,
|
|
3389
|
+
for (const path of [absoluteRoot, join19(absoluteRoot, "projects"), project, paths.baselines, paths.claims, paths.receipts, paths.refusals, paths.journals, paths.locks]) {
|
|
3385
3390
|
ensureDirectory(path, absoluteRoot);
|
|
3386
3391
|
}
|
|
3387
3392
|
return paths;
|
|
@@ -3397,15 +3402,15 @@ function fsyncDirectory2(path) {
|
|
|
3397
3402
|
fsyncSync3(fd);
|
|
3398
3403
|
} catch {
|
|
3399
3404
|
} finally {
|
|
3400
|
-
if (fd !== void 0)
|
|
3405
|
+
if (fd !== void 0) closeSync5(fd);
|
|
3401
3406
|
}
|
|
3402
3407
|
}
|
|
3403
3408
|
function readStateDirectory(path, root) {
|
|
3404
3409
|
const fd = openPinnedDirectory(path, root, false);
|
|
3405
3410
|
try {
|
|
3406
|
-
return
|
|
3411
|
+
return readdirSync10(`/proc/self/fd/${fd}`, { withFileTypes: true });
|
|
3407
3412
|
} finally {
|
|
3408
|
-
|
|
3413
|
+
closeSync5(fd);
|
|
3409
3414
|
}
|
|
3410
3415
|
}
|
|
3411
3416
|
function readNotebookStateDirectory(path, root) {
|
|
@@ -3419,22 +3424,22 @@ function unlinkStateFile(path, root, allowMissing = false) {
|
|
|
3419
3424
|
let fileFd;
|
|
3420
3425
|
try {
|
|
3421
3426
|
try {
|
|
3422
|
-
fileFd =
|
|
3427
|
+
fileFd = openSync5(target, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
|
|
3423
3428
|
} catch (error) {
|
|
3424
3429
|
if (allowMissing && error.code === "ENOENT") return false;
|
|
3425
3430
|
throw error;
|
|
3426
3431
|
}
|
|
3427
|
-
const stat =
|
|
3432
|
+
const stat = fstatSync2(fileFd);
|
|
3428
3433
|
if (!stat.isFile() || (stat.mode & 511) !== 384) {
|
|
3429
3434
|
throw new NotebookError("CONFLICT", "Refusing to remove a suspect Notebook state entry; run pj notebook audit --json");
|
|
3430
3435
|
}
|
|
3431
3436
|
assertOwned(stat);
|
|
3432
|
-
|
|
3437
|
+
unlinkSync8(target);
|
|
3433
3438
|
fsyncSync3(parentFd);
|
|
3434
3439
|
return true;
|
|
3435
3440
|
} finally {
|
|
3436
|
-
if (fileFd !== void 0)
|
|
3437
|
-
|
|
3441
|
+
if (fileFd !== void 0) closeSync5(fileFd);
|
|
3442
|
+
closeSync5(parentFd);
|
|
3438
3443
|
}
|
|
3439
3444
|
}
|
|
3440
3445
|
function renameStateFile(source, target, root) {
|
|
@@ -3444,12 +3449,12 @@ function renameStateFile(source, target, root) {
|
|
|
3444
3449
|
const targetPath = pinnedLeaf(parentFd, target);
|
|
3445
3450
|
let sourceFd;
|
|
3446
3451
|
try {
|
|
3447
|
-
sourceFd =
|
|
3448
|
-
const stat =
|
|
3452
|
+
sourceFd = openSync5(sourcePath, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
|
|
3453
|
+
const stat = fstatSync2(sourceFd);
|
|
3449
3454
|
if (!stat.isFile() || (stat.mode & 511) !== 384) throw new NotebookError("CONFLICT", "Notebook state rename source has an integrity finding");
|
|
3450
3455
|
assertOwned(stat);
|
|
3451
3456
|
try {
|
|
3452
|
-
|
|
3457
|
+
lstatSync14(targetPath);
|
|
3453
3458
|
throw new NotebookError("CONFLICT", "Notebook state rename target already exists");
|
|
3454
3459
|
} catch (error) {
|
|
3455
3460
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -3457,8 +3462,8 @@ function renameStateFile(source, target, root) {
|
|
|
3457
3462
|
renameSync7(sourcePath, targetPath);
|
|
3458
3463
|
fsyncSync3(parentFd);
|
|
3459
3464
|
} finally {
|
|
3460
|
-
if (sourceFd !== void 0)
|
|
3461
|
-
|
|
3465
|
+
if (sourceFd !== void 0) closeSync5(sourceFd);
|
|
3466
|
+
closeSync5(parentFd);
|
|
3462
3467
|
}
|
|
3463
3468
|
}
|
|
3464
3469
|
function atomicWriteJson(path, value, root, afterParentPinned) {
|
|
@@ -3467,34 +3472,34 @@ function atomicWriteJson(path, value, root, afterParentPinned) {
|
|
|
3467
3472
|
const text2 = jsonLine(value);
|
|
3468
3473
|
const parentFd = openPinnedDirectory(dirname16(path), root, false);
|
|
3469
3474
|
const target = pinnedLeaf(parentFd, path);
|
|
3470
|
-
const tempName = `.${
|
|
3475
|
+
const tempName = `.${basename10(path)}.${process.pid}.${randomUUID3()}.tmp`;
|
|
3471
3476
|
const temp = `/proc/self/fd/${parentFd}/${tempName}`;
|
|
3472
3477
|
let existingIdentity = null;
|
|
3473
3478
|
try {
|
|
3474
3479
|
afterParentPinned?.();
|
|
3475
3480
|
try {
|
|
3476
|
-
const existingFd =
|
|
3481
|
+
const existingFd = openSync5(target, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
|
|
3477
3482
|
try {
|
|
3478
|
-
const existing =
|
|
3483
|
+
const existing = fstatSync2(existingFd);
|
|
3479
3484
|
if (!existing.isFile()) throw new NotebookError("INTERNAL_ERROR", "Refusing to replace a non-regular Notebook state file");
|
|
3480
3485
|
assertOwned(existing);
|
|
3481
3486
|
if ((existing.mode & 511) !== 384) throw new NotebookError("CONFLICT", "Refusing to replace a Notebook state file with unsafe permissions");
|
|
3482
3487
|
existingIdentity = { dev: existing.dev, ino: existing.ino };
|
|
3483
3488
|
} finally {
|
|
3484
|
-
|
|
3489
|
+
closeSync5(existingFd);
|
|
3485
3490
|
}
|
|
3486
3491
|
} catch (error) {
|
|
3487
3492
|
if (error.code !== "ENOENT") throw error;
|
|
3488
3493
|
}
|
|
3489
|
-
const fd =
|
|
3494
|
+
const fd = openSync5(temp, constants4.O_WRONLY | constants4.O_CREAT | constants4.O_EXCL | (constants4.O_NOFOLLOW ?? 0), 384);
|
|
3490
3495
|
try {
|
|
3491
|
-
|
|
3496
|
+
writeFileSync12(fd, text2, "utf8");
|
|
3492
3497
|
fsyncSync3(fd);
|
|
3493
3498
|
} finally {
|
|
3494
|
-
|
|
3499
|
+
closeSync5(fd);
|
|
3495
3500
|
}
|
|
3496
3501
|
try {
|
|
3497
|
-
const current =
|
|
3502
|
+
const current = lstatSync14(target);
|
|
3498
3503
|
if (!existingIdentity || !current.isFile() || current.isSymbolicLink() || current.dev !== existingIdentity.dev || current.ino !== existingIdentity.ino) {
|
|
3499
3504
|
throw new NotebookError("CONFLICT", "Notebook state target changed during atomic update; preserving both entries for audit");
|
|
3500
3505
|
}
|
|
@@ -3505,12 +3510,12 @@ function atomicWriteJson(path, value, root, afterParentPinned) {
|
|
|
3505
3510
|
fsyncSync3(parentFd);
|
|
3506
3511
|
} catch (error) {
|
|
3507
3512
|
try {
|
|
3508
|
-
|
|
3513
|
+
unlinkSync8(temp);
|
|
3509
3514
|
} catch {
|
|
3510
3515
|
}
|
|
3511
3516
|
throw error;
|
|
3512
3517
|
} finally {
|
|
3513
|
-
|
|
3518
|
+
closeSync5(parentFd);
|
|
3514
3519
|
}
|
|
3515
3520
|
return Buffer.byteLength(text2, "utf8");
|
|
3516
3521
|
}
|
|
@@ -3520,22 +3525,22 @@ function exclusiveWrite(path, text2, root) {
|
|
|
3520
3525
|
const target = pinnedLeaf(parentFd, path);
|
|
3521
3526
|
let fd;
|
|
3522
3527
|
try {
|
|
3523
|
-
fd =
|
|
3528
|
+
fd = openSync5(target, constants4.O_WRONLY | constants4.O_CREAT | constants4.O_EXCL | (constants4.O_NOFOLLOW ?? 0), 384);
|
|
3524
3529
|
} catch (error) {
|
|
3525
|
-
|
|
3530
|
+
closeSync5(parentFd);
|
|
3526
3531
|
if (error.code === "EEXIST") return false;
|
|
3527
3532
|
throw error;
|
|
3528
3533
|
}
|
|
3529
3534
|
try {
|
|
3530
|
-
|
|
3535
|
+
writeFileSync12(fd, text2, "utf8");
|
|
3531
3536
|
fsyncSync3(fd);
|
|
3532
3537
|
} finally {
|
|
3533
|
-
|
|
3538
|
+
closeSync5(fd);
|
|
3534
3539
|
}
|
|
3535
3540
|
try {
|
|
3536
3541
|
fsyncSync3(parentFd);
|
|
3537
3542
|
} finally {
|
|
3538
|
-
|
|
3543
|
+
closeSync5(parentFd);
|
|
3539
3544
|
}
|
|
3540
3545
|
return true;
|
|
3541
3546
|
}
|
|
@@ -3679,13 +3684,13 @@ function safeReadJson(path, maxBytes) {
|
|
|
3679
3684
|
}
|
|
3680
3685
|
let fd;
|
|
3681
3686
|
try {
|
|
3682
|
-
fd =
|
|
3687
|
+
fd = openSync5(pinnedLeaf(parentFd, path), constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
|
|
3683
3688
|
} catch (error) {
|
|
3684
|
-
|
|
3689
|
+
closeSync5(parentFd);
|
|
3685
3690
|
return { reason: error.code === "ELOOP" ? "non-regular" : "unreadable", bytes: 0 };
|
|
3686
3691
|
}
|
|
3687
3692
|
try {
|
|
3688
|
-
const stat =
|
|
3693
|
+
const stat = fstatSync2(fd);
|
|
3689
3694
|
if (!stat.isFile()) return { reason: "non-regular", bytes: 0 };
|
|
3690
3695
|
if ((stat.mode & 511) !== 384) return { reason: "unsafe-permissions", bytes: stat.size };
|
|
3691
3696
|
try {
|
|
@@ -3705,14 +3710,14 @@ function safeReadJson(path, maxBytes) {
|
|
|
3705
3710
|
if (total > maxBytes) return { reason: "oversize", bytes: total };
|
|
3706
3711
|
chunks.push(chunk.subarray(0, count));
|
|
3707
3712
|
}
|
|
3708
|
-
const after =
|
|
3713
|
+
const after = fstatSync2(fd);
|
|
3709
3714
|
if (after.size !== stat.size || total !== stat.size) return { reason: "unreadable", bytes: total };
|
|
3710
3715
|
return { value: JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(Buffer.concat(chunks, total))), bytes: total };
|
|
3711
3716
|
} catch (error) {
|
|
3712
3717
|
return { reason: error instanceof SyntaxError || error instanceof TypeError ? "invalid-json" : "unreadable", bytes: 0 };
|
|
3713
3718
|
} finally {
|
|
3714
|
-
|
|
3715
|
-
|
|
3719
|
+
closeSync5(fd);
|
|
3720
|
+
closeSync5(parentFd);
|
|
3716
3721
|
}
|
|
3717
3722
|
}
|
|
3718
3723
|
function safeEntryId(kind, name) {
|
|
@@ -3729,7 +3734,7 @@ function baselineReceiptByteCeiling(limits) {
|
|
|
3729
3734
|
function createSessionBaseline(root, input) {
|
|
3730
3735
|
assertDigest(input.session_key, "session key");
|
|
3731
3736
|
const paths = ensureNotebookState(root, input.project_slug);
|
|
3732
|
-
const path =
|
|
3737
|
+
const path = join19(paths.baselines, `${input.session_key}.json`);
|
|
3733
3738
|
const maxBytes = baselineReceiptByteCeiling(input.limits);
|
|
3734
3739
|
const existing = readBaseline(path, maxBytes);
|
|
3735
3740
|
if (existing) return { baseline: existing, created: false };
|
|
@@ -3770,7 +3775,7 @@ function createSessionBaseline(root, input) {
|
|
|
3770
3775
|
function createOverviewClaim(root, input) {
|
|
3771
3776
|
assertDigest(input.session_key, "session key");
|
|
3772
3777
|
const paths = ensureNotebookState(root, input.project_slug);
|
|
3773
|
-
const path =
|
|
3778
|
+
const path = join19(paths.claims, `${input.session_key}.overview`);
|
|
3774
3779
|
if (existsSync13(path)) {
|
|
3775
3780
|
const read = safeReadJson(path, 65536);
|
|
3776
3781
|
const existing = read.value === void 0 ? null : parseClaim(read.value);
|
|
@@ -3789,7 +3794,7 @@ function createOverviewClaim(root, input) {
|
|
|
3789
3794
|
function readOverviewClaim(root, projectSlug, sessionKey) {
|
|
3790
3795
|
assertDigest(sessionKey, "session key");
|
|
3791
3796
|
const paths = notebookStatePaths(root, projectSlug);
|
|
3792
|
-
const path =
|
|
3797
|
+
const path = join19(paths.claims, `${sessionKey}.overview`);
|
|
3793
3798
|
if (!existsSync13(path)) return null;
|
|
3794
3799
|
const read = safeReadJson(path, 65536);
|
|
3795
3800
|
return read.value === void 0 ? null : parseClaim(read.value);
|
|
@@ -3797,10 +3802,10 @@ function readOverviewClaim(root, projectSlug, sessionKey) {
|
|
|
3797
3802
|
function readSessionBaseline(root, projectSlug, sessionKey, limits) {
|
|
3798
3803
|
assertDigest(sessionKey, "session key");
|
|
3799
3804
|
const paths = ensureNotebookState(root, projectSlug);
|
|
3800
|
-
return readBaseline(
|
|
3805
|
+
return readBaseline(join19(paths.baselines, `${sessionKey}.json`), baselineReceiptByteCeiling(limits));
|
|
3801
3806
|
}
|
|
3802
3807
|
function acquireLock(paths, maxWaitMs) {
|
|
3803
|
-
const lock =
|
|
3808
|
+
const lock = join19(paths.locks, "admission.lock");
|
|
3804
3809
|
const deadline = Date.now() + Math.max(1, maxWaitMs);
|
|
3805
3810
|
const token = randomUUID3();
|
|
3806
3811
|
const record3 = () => ({
|
|
@@ -3830,7 +3835,7 @@ function acquireLock(paths, maxWaitMs) {
|
|
|
3830
3835
|
throw new NotebookError("CONFLICT", "Notebook state lock has an integrity finding; preserve it and run pj notebook audit --json");
|
|
3831
3836
|
}
|
|
3832
3837
|
if (Date.now() >= expiresAt) {
|
|
3833
|
-
const recovery =
|
|
3838
|
+
const recovery = join19(paths.locks, `recovery-${heldToken}.lock`);
|
|
3834
3839
|
const recoveryRecord = jsonLine({ schema_version: NOTEBOOK_SCHEMA_VERSION, stale_token: heldToken, recovery_token: token, expires_at: new Date(Date.now() + 5e3).toISOString() });
|
|
3835
3840
|
if (exclusiveWrite(recovery, recoveryRecord, paths.root)) {
|
|
3836
3841
|
try {
|
|
@@ -3838,7 +3843,7 @@ function acquireLock(paths, maxWaitMs) {
|
|
|
3838
3843
|
const currentToken = verify.value && typeof verify.value === "object" && !Array.isArray(verify.value) ? verify.value.token : null;
|
|
3839
3844
|
const currentExpiry = verify.value && typeof verify.value === "object" && !Array.isArray(verify.value) ? Date.parse(String(verify.value.expires_at ?? "")) : Number.NaN;
|
|
3840
3845
|
if (currentToken === heldToken && Number.isFinite(currentExpiry) && Date.now() >= currentExpiry) {
|
|
3841
|
-
const recovered =
|
|
3846
|
+
const recovered = join19(paths.locks, `.recovered-${heldToken}-${randomUUID3()}.json`);
|
|
3842
3847
|
renameStateFile(lock, recovered, paths.root);
|
|
3843
3848
|
unlinkStateFile(recovered, paths.root);
|
|
3844
3849
|
}
|
|
@@ -3918,7 +3923,7 @@ function scanAuxiliaryState(paths, limits) {
|
|
|
3918
3923
|
addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: "non-regular" });
|
|
3919
3924
|
continue;
|
|
3920
3925
|
}
|
|
3921
|
-
const read = safeReadJson(
|
|
3926
|
+
const read = safeReadJson(join19(specification.dir, entry.name), specification.maxBytes);
|
|
3922
3927
|
if (read.reason || read.value === void 0) {
|
|
3923
3928
|
addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: read.reason ?? "invalid-json" }, read.bytes);
|
|
3924
3929
|
continue;
|
|
@@ -3943,7 +3948,7 @@ function scanAuxiliaryState(paths, limits) {
|
|
|
3943
3948
|
addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: "non-regular" });
|
|
3944
3949
|
continue;
|
|
3945
3950
|
}
|
|
3946
|
-
const read = safeReadJson(
|
|
3951
|
+
const read = safeReadJson(join19(paths.journals, entry.name), limits.receipt_max_bytes);
|
|
3947
3952
|
if (read.reason || read.value === void 0) {
|
|
3948
3953
|
addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: read.reason ?? "invalid-json" }, read.bytes);
|
|
3949
3954
|
continue;
|
|
@@ -3972,7 +3977,7 @@ function scanAuxiliaryState(paths, limits) {
|
|
|
3972
3977
|
addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: "non-regular" });
|
|
3973
3978
|
continue;
|
|
3974
3979
|
}
|
|
3975
|
-
const read = safeReadJson(
|
|
3980
|
+
const read = safeReadJson(join19(paths.locks, entry.name), 8192);
|
|
3976
3981
|
if (read.reason || read.value === void 0) {
|
|
3977
3982
|
addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: read.reason ?? "invalid-json" }, read.bytes);
|
|
3978
3983
|
continue;
|
|
@@ -4011,7 +4016,7 @@ function scanReceipts(paths, limits) {
|
|
|
4011
4016
|
}
|
|
4012
4017
|
for (const entry of entries) {
|
|
4013
4018
|
const entryId = safeEntryId("receipts", entry.name);
|
|
4014
|
-
const path =
|
|
4019
|
+
const path = join19(paths.receipts, entry.name);
|
|
4015
4020
|
if (!entry.isFile() || entry.isSymbolicLink() || !/^[a-f0-9]{64}\.json$/u.test(entry.name)) {
|
|
4016
4021
|
addIntegrity({ entry_id: entryId, reason: "non-regular" });
|
|
4017
4022
|
continue;
|
|
@@ -4041,7 +4046,7 @@ function scanBaselines(paths, nowMs, limits, referenced) {
|
|
|
4041
4046
|
if (!existsSync13(paths.baselines)) return { current, stale };
|
|
4042
4047
|
for (const entry of readStateDirectory(paths.baselines, paths.root)) {
|
|
4043
4048
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
4044
|
-
const baseline = readBaseline(
|
|
4049
|
+
const baseline = readBaseline(join19(paths.baselines, entry.name), baselineReceiptByteCeiling(limits));
|
|
4045
4050
|
if (!baseline || referenced.has(baseline.session_key)) continue;
|
|
4046
4051
|
const expires = Date.parse(baseline.created_at) + limits.receiptless_session_retention_seconds * 1e3;
|
|
4047
4052
|
if (nowMs >= expires) stale += 1;
|
|
@@ -4055,7 +4060,7 @@ function scanRefusals(paths, nowMs, limits, scan) {
|
|
|
4055
4060
|
const entries = readStateDirectory(paths.refusals, paths.root).sort((a, b) => a.name.localeCompare(b.name, "en")).slice(0, limits.refusal_max_entries);
|
|
4056
4061
|
for (const entry of entries) {
|
|
4057
4062
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
4058
|
-
const read = safeReadJson(
|
|
4063
|
+
const read = safeReadJson(join19(paths.refusals, entry.name), limits.receipt_max_bytes);
|
|
4059
4064
|
const marker = read.value === void 0 ? null : parseRefusal(read.value);
|
|
4060
4065
|
if (!marker || `${marker.session_key}.json` !== entry.name) continue;
|
|
4061
4066
|
if (nowMs >= Date.parse(marker.baseline_created_at) + limits.receiptless_session_retention_seconds * 1e3) continue;
|
|
@@ -4151,7 +4156,7 @@ function recoveryActions(receiptId, repoPath) {
|
|
|
4151
4156
|
];
|
|
4152
4157
|
}
|
|
4153
4158
|
function removeValidRefusal(paths, sessionKey, limits) {
|
|
4154
|
-
const path =
|
|
4159
|
+
const path = join19(paths.refusals, `${sessionKey}.json`);
|
|
4155
4160
|
if (!existsSync13(path)) return;
|
|
4156
4161
|
const read = safeReadJson(path, limits.receipt_max_bytes);
|
|
4157
4162
|
const marker = read.value === void 0 ? null : parseRefusal(read.value);
|
|
@@ -4165,7 +4170,7 @@ function admitCaptureReceipt(input) {
|
|
|
4165
4170
|
const release = acquireLock(paths, Math.min(100, input.limits.hook_session_end_timeout_ms));
|
|
4166
4171
|
try {
|
|
4167
4172
|
const receiptId = deriveReceiptId(input.sessionKey);
|
|
4168
|
-
const receiptPath =
|
|
4173
|
+
const receiptPath = join19(paths.receipts, `${receiptId}.json`);
|
|
4169
4174
|
if (existsSync13(receiptPath)) {
|
|
4170
4175
|
const read = safeReadJson(receiptPath, input.limits.receipt_max_bytes);
|
|
4171
4176
|
const receipt = read.value === void 0 ? null : parseReceipt(read.value);
|
|
@@ -4177,15 +4182,15 @@ function admitCaptureReceipt(input) {
|
|
|
4177
4182
|
const scanBefore = scanReceipts(paths, input.limits);
|
|
4178
4183
|
const auxiliaryBefore = scanAuxiliaryState(paths, input.limits);
|
|
4179
4184
|
const referencedBefore = /* @__PURE__ */ new Set([...scanBefore.referencedSessions, ...auxiliaryBefore.referencedSessions]);
|
|
4180
|
-
const baselinePath =
|
|
4185
|
+
const baselinePath = join19(paths.baselines, `${input.sessionKey}.json`);
|
|
4181
4186
|
let baseline = readBaseline(baselinePath, baselineReceiptByteCeiling(input.limits));
|
|
4182
4187
|
if (baseline) {
|
|
4183
4188
|
const expired = now.getTime() >= Date.parse(baseline.created_at) + input.limits.receiptless_session_retention_seconds * 1e3;
|
|
4184
4189
|
if (expired && !referencedBefore.has(input.sessionKey) && scanBefore.integrityCount === 0 && auxiliaryBefore.integrityCount === 0) {
|
|
4185
4190
|
unlinkStateFile(baselinePath, paths.root);
|
|
4186
|
-
const claimPath =
|
|
4191
|
+
const claimPath = join19(paths.claims, `${input.sessionKey}.overview`);
|
|
4187
4192
|
unlinkStateFile(claimPath, paths.root, true);
|
|
4188
|
-
const refusalPath =
|
|
4193
|
+
const refusalPath = join19(paths.refusals, `${input.sessionKey}.json`);
|
|
4189
4194
|
unlinkStateFile(refusalPath, paths.root, true);
|
|
4190
4195
|
baseline = null;
|
|
4191
4196
|
}
|
|
@@ -4214,7 +4219,7 @@ function admitCaptureReceipt(input) {
|
|
|
4214
4219
|
const byteBlocked = summary.unresolved_bytes + candidate.bytes > input.limits.unresolved_receipt_max_bytes;
|
|
4215
4220
|
if (countBlocked || byteBlocked) {
|
|
4216
4221
|
const actions = recoveryActions(receiptId, input.repoPath);
|
|
4217
|
-
const refusalPath =
|
|
4222
|
+
const refusalPath = join19(paths.refusals, `${input.sessionKey}.json`);
|
|
4218
4223
|
const oldRead = existsSync13(refusalPath) ? safeReadJson(refusalPath, input.limits.receipt_max_bytes) : void 0;
|
|
4219
4224
|
const old = oldRead?.value === void 0 ? null : parseRefusal(oldRead.value);
|
|
4220
4225
|
const marker = {
|
|
@@ -4283,7 +4288,7 @@ function listCaptureReceipts(root, projectSlug, limits, state) {
|
|
|
4283
4288
|
function readCaptureReceipt(root, projectSlug, receiptId, limits) {
|
|
4284
4289
|
if (!RECEIPT_ID_RE.test(receiptId)) throw new NotebookError("INVALID_INPUT", "Invalid receipt ID");
|
|
4285
4290
|
const paths = notebookStatePaths(root, projectSlug);
|
|
4286
|
-
const path =
|
|
4291
|
+
const path = join19(paths.receipts, `${receiptId}.json`);
|
|
4287
4292
|
if (!existsSync13(path)) throw new NotebookError("NOT_FOUND", `Capture receipt not found: ${receiptId}`);
|
|
4288
4293
|
const read = safeReadJson(path, limits.receipt_max_bytes);
|
|
4289
4294
|
const receipt = read.value === void 0 ? null : parseReceipt(read.value);
|
|
@@ -4296,7 +4301,7 @@ function writeReceipt(paths, receipt, limit) {
|
|
|
4296
4301
|
const bytes = Buffer.byteLength(text2, "utf8");
|
|
4297
4302
|
if (bytes > limit) throw new NotebookError("CONFLICT", "Receipt transition exceeds its per-receipt ceiling");
|
|
4298
4303
|
if (receipt.serialized_bytes === bytes) {
|
|
4299
|
-
atomicWriteJson(
|
|
4304
|
+
atomicWriteJson(join19(paths.receipts, `${receipt.receipt_id}.json`), receipt, paths.root);
|
|
4300
4305
|
return receipt;
|
|
4301
4306
|
}
|
|
4302
4307
|
receipt.serialized_bytes = bytes;
|
|
@@ -4435,25 +4440,25 @@ function pruneNotebookState(root, projectSlug, limits, now = /* @__PURE__ */ new
|
|
|
4435
4440
|
const successCutoff = now.getTime() - limits.receipt_succeeded_retention_days * 864e5;
|
|
4436
4441
|
for (const receipt of scan.receipts) {
|
|
4437
4442
|
if (receipt.state !== "succeeded" || Date.parse(receipt.updated_at) > successCutoff) continue;
|
|
4438
|
-
const path =
|
|
4443
|
+
const path = join19(paths.receipts, `${receipt.receipt_id}.json`);
|
|
4439
4444
|
unlinkStateFile(path, paths.root);
|
|
4440
|
-
removed.push(safeEntryId("receipts",
|
|
4445
|
+
removed.push(safeEntryId("receipts", basename10(path)));
|
|
4441
4446
|
}
|
|
4442
4447
|
const remaining = scanReceipts(paths, limits);
|
|
4443
4448
|
const remainingAuxiliary = scanAuxiliaryState(paths, limits);
|
|
4444
4449
|
const referenced = /* @__PURE__ */ new Set([...remaining.referencedSessions, ...remainingAuxiliary.referencedSessions]);
|
|
4445
4450
|
for (const entry of readStateDirectory(paths.baselines, paths.root)) {
|
|
4446
4451
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
4447
|
-
const path =
|
|
4452
|
+
const path = join19(paths.baselines, entry.name);
|
|
4448
4453
|
const baseline = readBaseline(path, baselineReceiptByteCeiling(limits));
|
|
4449
4454
|
if (!baseline || referenced.has(baseline.session_key)) continue;
|
|
4450
4455
|
if (now.getTime() < Date.parse(baseline.created_at) + limits.receiptless_session_retention_seconds * 1e3) continue;
|
|
4451
4456
|
unlinkStateFile(path, paths.root);
|
|
4452
4457
|
removed.push(safeEntryId("baselines", entry.name));
|
|
4453
|
-
const claim =
|
|
4454
|
-
if (unlinkStateFile(claim, paths.root, true)) removed.push(safeEntryId("claims",
|
|
4455
|
-
const refusal =
|
|
4456
|
-
if (unlinkStateFile(refusal, paths.root, true)) removed.push(safeEntryId("refusals",
|
|
4458
|
+
const claim = join19(paths.claims, `${baseline.session_key}.overview`);
|
|
4459
|
+
if (unlinkStateFile(claim, paths.root, true)) removed.push(safeEntryId("claims", basename10(claim)));
|
|
4460
|
+
const refusal = join19(paths.refusals, `${baseline.session_key}.json`);
|
|
4461
|
+
if (unlinkStateFile(refusal, paths.root, true)) removed.push(safeEntryId("refusals", basename10(refusal)));
|
|
4457
4462
|
}
|
|
4458
4463
|
fsyncDirectory2(paths.project);
|
|
4459
4464
|
return removed;
|
|
@@ -4476,7 +4481,7 @@ function currentRetentionPressure(summary) {
|
|
|
4476
4481
|
}
|
|
4477
4482
|
function statePathForReceipt(root, projectSlug, receiptId) {
|
|
4478
4483
|
if (!RECEIPT_ID_RE.test(receiptId)) throw new NotebookError("INVALID_INPUT", "Invalid receipt ID");
|
|
4479
|
-
return
|
|
4484
|
+
return join19(projectStateDir(root, projectSlug), "receipts", `${receiptId}.json`);
|
|
4480
4485
|
}
|
|
4481
4486
|
var NOTEBOOK_STATE_VERSION, SESSION_KEY_RE, RECEIPT_ID_RE, GIT_OBJECT_RE, NOTEBOOK_ERROR_CODES, DIRECTORY_OPEN_FLAGS;
|
|
4482
4487
|
var init_state = __esm({
|
|
@@ -4503,7 +4508,7 @@ var init_state = __esm({
|
|
|
4503
4508
|
"REMOTE_PROTOCOL_ERROR",
|
|
4504
4509
|
"INTERNAL_ERROR"
|
|
4505
4510
|
]);
|
|
4506
|
-
DIRECTORY_OPEN_FLAGS =
|
|
4511
|
+
DIRECTORY_OPEN_FLAGS = constants4.O_RDONLY | (constants4.O_DIRECTORY ?? 0) | (constants4.O_NOFOLLOW ?? 0);
|
|
4507
4512
|
}
|
|
4508
4513
|
});
|
|
4509
4514
|
|
|
@@ -4842,8 +4847,8 @@ var init_open_notebook_client = __esm({
|
|
|
4842
4847
|
|
|
4843
4848
|
// src/notebook/git-evidence.ts
|
|
4844
4849
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
4845
|
-
import { closeSync as
|
|
4846
|
-
import { extname, join as
|
|
4850
|
+
import { closeSync as closeSync6, constants as constants5, fstatSync as fstatSync3, lstatSync as lstatSync15, openSync as openSync6, readSync as readSync2, realpathSync as realpathSync8 } from "node:fs";
|
|
4851
|
+
import { extname, join as join21, relative as relative13, resolve as resolve14, sep as sep5 } from "node:path";
|
|
4847
4852
|
function git(repo, args, maxBuffer = 4 * 1024 * 1024, timeout = 5e3) {
|
|
4848
4853
|
const result2 = spawnSync9("git", args, { cwd: repo, encoding: "utf8", maxBuffer, timeout, shell: false });
|
|
4849
4854
|
return { ok: result2.status === 0, stdout: result2.stdout ?? "" };
|
|
@@ -4921,9 +4926,9 @@ function hasSymlinkComponent(root, candidate) {
|
|
|
4921
4926
|
const rel = relative13(root, candidate);
|
|
4922
4927
|
let cursor = root;
|
|
4923
4928
|
for (const part of rel.split(sep5).filter(Boolean)) {
|
|
4924
|
-
cursor =
|
|
4929
|
+
cursor = join21(cursor, part);
|
|
4925
4930
|
try {
|
|
4926
|
-
if (
|
|
4931
|
+
if (lstatSync15(cursor).isSymbolicLink()) return true;
|
|
4927
4932
|
} catch {
|
|
4928
4933
|
return true;
|
|
4929
4934
|
}
|
|
@@ -4935,12 +4940,12 @@ function readSafeEvidenceText(repoPath, relativePath2, maxBytes) {
|
|
|
4935
4940
|
if (!safe || hasSymlinkComponent(safe.root, safe.candidate)) return { status: "excluded", reason: "unsafe-path" };
|
|
4936
4941
|
let fd;
|
|
4937
4942
|
try {
|
|
4938
|
-
fd =
|
|
4943
|
+
fd = openSync6(safe.candidate, constants5.O_RDONLY | (constants5.O_NOFOLLOW ?? 0));
|
|
4939
4944
|
} catch {
|
|
4940
4945
|
return { status: "excluded", reason: "not-regular" };
|
|
4941
4946
|
}
|
|
4942
4947
|
try {
|
|
4943
|
-
const before =
|
|
4948
|
+
const before = fstatSync3(fd);
|
|
4944
4949
|
if (!before.isFile()) return { status: "excluded", reason: "not-regular" };
|
|
4945
4950
|
if (before.size > maxBytes) return { status: "excluded", reason: "oversize" };
|
|
4946
4951
|
let physical;
|
|
@@ -4961,7 +4966,7 @@ function readSafeEvidenceText(repoPath, relativePath2, maxBytes) {
|
|
|
4961
4966
|
if (total > maxBytes) return { status: "excluded", reason: "oversize" };
|
|
4962
4967
|
chunks.push(chunk.subarray(0, count));
|
|
4963
4968
|
}
|
|
4964
|
-
const after =
|
|
4969
|
+
const after = fstatSync3(fd);
|
|
4965
4970
|
if (after.dev !== before.dev || after.ino !== before.ino || after.size !== before.size || total !== before.size) return { status: "excluded", reason: "changed-during-read" };
|
|
4966
4971
|
const bytes = Buffer.concat(chunks, total);
|
|
4967
4972
|
if (bytes.includes(0)) return { status: "excluded", reason: "binary" };
|
|
@@ -4974,7 +4979,7 @@ function readSafeEvidenceText(repoPath, relativePath2, maxBytes) {
|
|
|
4974
4979
|
if (looksSecret(relativePath2, content)) return { status: "excluded", reason: "secret-like" };
|
|
4975
4980
|
return { status: "present", content, content_sha256: sha256Hex(bytes), bytes: total };
|
|
4976
4981
|
} finally {
|
|
4977
|
-
|
|
4982
|
+
closeSync6(fd);
|
|
4978
4983
|
}
|
|
4979
4984
|
}
|
|
4980
4985
|
function looksGenerated(path) {
|
|
@@ -5064,10 +5069,10 @@ var init_git_evidence = __esm({
|
|
|
5064
5069
|
|
|
5065
5070
|
// src/notebook/remote-mutation-journal.ts
|
|
5066
5071
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
5067
|
-
import { join as
|
|
5072
|
+
import { join as join22 } from "node:path";
|
|
5068
5073
|
function journalPath(root, projectSlug, operationId) {
|
|
5069
5074
|
if (!/^[a-f0-9-]{16,64}$/iu.test(operationId)) throw new NotebookError("INVALID_INPUT", "Invalid remote mutation operation ID");
|
|
5070
|
-
return
|
|
5075
|
+
return join22(ensureNotebookState(root, projectSlug).journals, `${operationId}.json`);
|
|
5071
5076
|
}
|
|
5072
5077
|
function remoteMutationJournalPath(root, projectSlug, operationId) {
|
|
5073
5078
|
return journalPath(root, projectSlug, operationId);
|
|
@@ -5087,7 +5092,7 @@ function listRemoteMutationJournals(root, projectSlug) {
|
|
|
5087
5092
|
if (!entry.isFile() || entry.isSymbolicLink() || !/^[a-f0-9-]{16,64}\.json$/iu.test(entry.name)) {
|
|
5088
5093
|
throw new NotebookError("CONFLICT", `Remote mutation journal state-integrity finding: journals/${entry.name}`);
|
|
5089
5094
|
}
|
|
5090
|
-
const read = readNotebookStateJson(
|
|
5095
|
+
const read = readNotebookStateJson(join22(paths.journals, entry.name), paths.root, 65536);
|
|
5091
5096
|
const journal = read.value === void 0 ? null : parseRemoteMutationJournal(read.value);
|
|
5092
5097
|
if (!journal || `${journal.operation_id}.json` !== entry.name) {
|
|
5093
5098
|
throw new NotebookError("CONFLICT", `Remote mutation journal state-integrity finding: journals/${entry.name}`);
|
|
@@ -5441,7 +5446,7 @@ var init_reconcile = __esm({
|
|
|
5441
5446
|
|
|
5442
5447
|
// src/notebook/entropy.ts
|
|
5443
5448
|
import { existsSync as existsSync16 } from "node:fs";
|
|
5444
|
-
import { createHash as
|
|
5449
|
+
import { createHash as createHash14 } from "node:crypto";
|
|
5445
5450
|
import { resolve as resolve17 } from "node:path";
|
|
5446
5451
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
5447
5452
|
function listAllTrackedDocuments(config) {
|
|
@@ -5496,7 +5501,7 @@ async function compactSessionCaptures(input) {
|
|
|
5496
5501
|
const toCompact = sessionHeaders.slice(maxRecent);
|
|
5497
5502
|
const notesToCompact = typeof input.client.hydrateNotes === "function" ? await input.client.hydrateNotes(toCompact) : toCompact;
|
|
5498
5503
|
const archiveTitle = "Session Captures Archive (Prior Sessions)";
|
|
5499
|
-
const slugHash =
|
|
5504
|
+
const slugHash = createHash14("sha256").update(`session-capture-archive:${input.projectSlug}`).digest("hex").slice(0, 32);
|
|
5500
5505
|
const archiveLogicalId = `user-note:v1:${slugHash}`;
|
|
5501
5506
|
let existingArchiveContent = "";
|
|
5502
5507
|
let existingArchiveId = null;
|
|
@@ -6064,8 +6069,8 @@ var init_capture = __esm({
|
|
|
6064
6069
|
|
|
6065
6070
|
// src/index.ts
|
|
6066
6071
|
import { spawnSync as spawnSync18 } from "node:child_process";
|
|
6067
|
-
import { existsSync as existsSync26, readdirSync as
|
|
6068
|
-
import { basename as
|
|
6072
|
+
import { existsSync as existsSync26, readdirSync as readdirSync15, readFileSync as readFileSync33, statSync as statSync9 } from "node:fs";
|
|
6073
|
+
import { basename as basename13, join as join38, resolve as resolve25 } from "node:path";
|
|
6069
6074
|
import { Command as Command3, CommanderError, Option } from "commander";
|
|
6070
6075
|
|
|
6071
6076
|
// src/recipes/Recipe.ts
|
|
@@ -6128,6 +6133,15 @@ function isHostScoped(finding2) {
|
|
|
6128
6133
|
function gatesProject(finding2) {
|
|
6129
6134
|
return finding2.status === "fail" && !isHostScoped(finding2);
|
|
6130
6135
|
}
|
|
6136
|
+
var FINDING_DETAIL_LIMIT = 20;
|
|
6137
|
+
function describeFinding(finding2) {
|
|
6138
|
+
const details = (finding2.details ?? []).map((line) => line.trimEnd()).filter((line) => line.trim().length > 0);
|
|
6139
|
+
if (details.length === 0) return `${finding2.id}: ${finding2.summary}`;
|
|
6140
|
+
const shown = details.slice(0, FINDING_DETAIL_LIMIT).map((line) => ` - ${line.replace(/\r?\n/g, "\n ")}`);
|
|
6141
|
+
const omitted = details.length - shown.length;
|
|
6142
|
+
if (omitted > 0) shown.push(` - (+${omitted} more; run pj audit --rules ${finding2.id} for the full list)`);
|
|
6143
|
+
return [`${finding2.id}: ${finding2.summary}`, ...shown].join("\n");
|
|
6144
|
+
}
|
|
6131
6145
|
|
|
6132
6146
|
// src/recipes/Recipe.ts
|
|
6133
6147
|
function commandStatus(result2, dryRun) {
|
|
@@ -6186,7 +6200,7 @@ var Recipe = class {
|
|
|
6186
6200
|
}
|
|
6187
6201
|
if (!finding2.fixable) {
|
|
6188
6202
|
phases.push({ id: check.id, status: "failed", changedFiles: [], message: finding2.summary });
|
|
6189
|
-
errors.push(
|
|
6203
|
+
errors.push(describeFinding({ id: check.id, summary: finding2.summary, details: finding2.details }));
|
|
6190
6204
|
break;
|
|
6191
6205
|
}
|
|
6192
6206
|
const migrated = await check.migrate(ctx, finding2);
|
|
@@ -6196,7 +6210,7 @@ var Recipe = class {
|
|
|
6196
6210
|
logs.push(`${check.id}: ${migrated.summary}`);
|
|
6197
6211
|
changedFiles.push(...actualChanges);
|
|
6198
6212
|
if (status === "failed") {
|
|
6199
|
-
errors.push(
|
|
6213
|
+
errors.push(describeFinding({ id: check.id, summary: migrated.summary, details: migrated.details }));
|
|
6200
6214
|
break;
|
|
6201
6215
|
}
|
|
6202
6216
|
if (!ctx.dryRun) {
|
|
@@ -7636,7 +7650,11 @@ function attestBmadInstallerFiles(inventory, base, files, label) {
|
|
|
7636
7650
|
|
|
7637
7651
|
// src/parity/skill-roots.ts
|
|
7638
7652
|
var CANONICAL_CLI_SKILLS_ALIAS = "../.agents/skills";
|
|
7639
|
-
var
|
|
7653
|
+
var SKILLEX_ONLY_ALIASES_AFTER = { ".kimi-code/skills": ".kimi/skills" };
|
|
7654
|
+
var SUPPORTED_SKILLS_ALIASES = SUPPORTED_CLIS.flatMap((cli) => {
|
|
7655
|
+
const follower = SKILLEX_ONLY_ALIASES_AFTER[cli.skillsRoot];
|
|
7656
|
+
return follower ? [cli.skillsRoot, follower] : [cli.skillsRoot];
|
|
7657
|
+
});
|
|
7640
7658
|
var SKILL_ROOTS_QUARANTINE = ".pjangler-quarantine";
|
|
7641
7659
|
function lstatOrUndefined(path) {
|
|
7642
7660
|
try {
|
|
@@ -8042,20 +8060,20 @@ function planSkillRoots(repoRoot, options = {}) {
|
|
|
8042
8060
|
for (const name of readdirSync6(real).sort()) {
|
|
8043
8061
|
const path = join7(real, name);
|
|
8044
8062
|
const label = `${alias}/${name}`;
|
|
8045
|
-
const
|
|
8063
|
+
const entryStat2 = lstatSync6(path);
|
|
8046
8064
|
const rows = tracked.get(name) ?? [];
|
|
8047
|
-
const candidate = { alias, name, path, label, kind:
|
|
8065
|
+
const candidate = { alias, name, path, label, kind: entryStat2.isSymbolicLink() ? "link" : entryStat2.isDirectory() ? "directory" : "file", tracked: rows.map((row) => row.path) };
|
|
8048
8066
|
if (name === ".git") {
|
|
8049
8067
|
candidate.staticBlock = `${label} is git metadata; refusing to relocate another repository's work tree`;
|
|
8050
|
-
} else if (
|
|
8068
|
+
} else if (entryStat2.isSymbolicLink()) {
|
|
8051
8069
|
candidate.text = readlinkSync5(path);
|
|
8052
8070
|
candidate.resolution = resolveIn(diskLayout, path);
|
|
8053
8071
|
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`;
|
|
8054
|
-
} else if (!
|
|
8072
|
+
} else if (!entryStat2.isDirectory() && !entryStat2.isFile()) {
|
|
8055
8073
|
candidate.staticBlock = `${label} is a special file; refusing to relocate it`;
|
|
8056
8074
|
} else {
|
|
8057
8075
|
candidate.staticBlock = entryHazard(path, label);
|
|
8058
|
-
if (!candidate.staticBlock && rootDevice !== void 0 &&
|
|
8076
|
+
if (!candidate.staticBlock && rootDevice !== void 0 && entryStat2.dev !== rootDevice) candidate.staticBlock = `${label} is on a different filesystem than .agents/skills; refusing a copying move`;
|
|
8059
8077
|
const linkRow = rows.find((row) => row.mode === "120000");
|
|
8060
8078
|
const gitlink = rows.find((row) => row.mode === "160000");
|
|
8061
8079
|
if (!candidate.staticBlock && gitlink) candidate.staticBlock = `${label} holds a tracked submodule (${gitlink.path}); refusing to relocate it`;
|
|
@@ -8812,7 +8830,7 @@ function readHandoffRecord(ctx) {
|
|
|
8812
8830
|
}
|
|
8813
8831
|
function writeHandoffRecord(ctx, names) {
|
|
8814
8832
|
const path = handoffRecordPath(ctx);
|
|
8815
|
-
mkdirSync5(dirname7(path), { recursive: true });
|
|
8833
|
+
mkdirSync5(dirname7(path), { recursive: true, mode: 448 });
|
|
8816
8834
|
const temporary = `${path}.${process.pid}.tmp`;
|
|
8817
8835
|
writeFileSync5(temporary, `${JSON.stringify({ project: realpathOf(ctx.repoRoot) ?? resolve5(ctx.repoRoot), excluded: [...new Set(names)].sort() }, null, 2)}
|
|
8818
8836
|
`);
|
|
@@ -10031,14 +10049,324 @@ function createG33CompanionCheck() {
|
|
|
10031
10049
|
}
|
|
10032
10050
|
|
|
10033
10051
|
// src/parity/rules.ts
|
|
10034
|
-
import { existsSync as existsSync8, lstatSync as
|
|
10035
|
-
|
|
10052
|
+
import { existsSync as existsSync8, lstatSync as lstatSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync12, 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";
|
|
10053
|
+
|
|
10054
|
+
// src/bmad/setup-scripts.ts
|
|
10055
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
10056
|
+
import { closeSync as closeSync2, fchmodSync, fstatSync, linkSync, lstatSync as lstatSync9, mkdirSync as mkdirSync6, mkdtempSync as mkdtempSync2, openSync as openSync2, readFileSync as readFileSync10, readdirSync as readdirSync7, rmdirSync as rmdirSync3, unlinkSync as unlinkSync5, writeFileSync as writeFileSync7, constants as constants2 } from "node:fs";
|
|
10057
|
+
import { basename as basename4, join as join10 } from "node:path";
|
|
10058
|
+
function entryStat(path) {
|
|
10059
|
+
try {
|
|
10060
|
+
return lstatSync9(path);
|
|
10061
|
+
} catch (error) {
|
|
10062
|
+
if (error.code === "ENOENT") return void 0;
|
|
10063
|
+
throw error;
|
|
10064
|
+
}
|
|
10065
|
+
}
|
|
10066
|
+
var RUNTIME_SCRIPTS = ["config_utils.py", "memlog.py", "render_skill.py", "resolve_config.py", "resolve_customization.py"];
|
|
10067
|
+
function manifestRows(text2) {
|
|
10068
|
+
const rows = [];
|
|
10069
|
+
let row = [], field2 = "", quoted = false, closed = false, inQuotes = false;
|
|
10070
|
+
for (let i = 0; i < text2.length; i++) {
|
|
10071
|
+
const c = text2[i];
|
|
10072
|
+
if (inQuotes) {
|
|
10073
|
+
if (c === '"' && text2[i + 1] === '"') {
|
|
10074
|
+
field2 += '"';
|
|
10075
|
+
i++;
|
|
10076
|
+
} else if (c === '"') {
|
|
10077
|
+
inQuotes = false;
|
|
10078
|
+
closed = true;
|
|
10079
|
+
} else field2 += c;
|
|
10080
|
+
} else if (c === '"' && !field2 && !closed) {
|
|
10081
|
+
inQuotes = true;
|
|
10082
|
+
quoted = true;
|
|
10083
|
+
} else if (c === ",") {
|
|
10084
|
+
row.push(field2);
|
|
10085
|
+
field2 = "";
|
|
10086
|
+
quoted = closed = false;
|
|
10087
|
+
} else if (c === "\n" || c === "\r") {
|
|
10088
|
+
if (c === "\r" && text2[i + 1] === "\n") i++;
|
|
10089
|
+
row.push(field2);
|
|
10090
|
+
if (row.length !== 1 || row[0]) rows.push(row);
|
|
10091
|
+
row = [];
|
|
10092
|
+
field2 = "";
|
|
10093
|
+
quoted = closed = false;
|
|
10094
|
+
} else if (closed || c === '"' || quoted && c === "\r") throw new Error("Malformed BMAD file manifest CSV");
|
|
10095
|
+
else field2 += c;
|
|
10096
|
+
}
|
|
10097
|
+
if (inQuotes) throw new Error("Unterminated BMAD file manifest CSV field");
|
|
10098
|
+
if (row.length || field2 || closed) {
|
|
10099
|
+
row.push(field2);
|
|
10100
|
+
rows.push(row);
|
|
10101
|
+
}
|
|
10102
|
+
if (rows.some((r) => r.length !== 5) || rows[0]?.join("\0") !== "type\0name\0module\0path\0hash") {
|
|
10103
|
+
throw new Error("Invalid BMAD file manifest columns");
|
|
10104
|
+
}
|
|
10105
|
+
return rows.slice(1);
|
|
10106
|
+
}
|
|
10107
|
+
function sameIdentity(a, b) {
|
|
10108
|
+
return !!a && a.dev === b.dev && a.ino === b.ino && a.isDirectory() && !a.isSymbolicLink();
|
|
10109
|
+
}
|
|
10110
|
+
var DeclinableError = class extends Error {
|
|
10111
|
+
constructor(message2, kind) {
|
|
10112
|
+
super(message2);
|
|
10113
|
+
this.kind = kind;
|
|
10114
|
+
}
|
|
10115
|
+
};
|
|
10116
|
+
var FIELD_SEP = String.fromCharCode(0);
|
|
10117
|
+
var MANIFEST_UNUSABLE = "Installed BMAD file manifest is missing or not a real file; cannot attest recovered scripts";
|
|
10118
|
+
function inspectBmadSetupScripts(repoRoot, version, installedVersion) {
|
|
10119
|
+
const bmadDir = join10(repoRoot, "_bmad");
|
|
10120
|
+
const dest = join10(bmadDir, "scripts");
|
|
10121
|
+
try {
|
|
10122
|
+
const bmad = entryStat(bmadDir);
|
|
10123
|
+
if (!bmad) return { state: "decline", why: "_bmad is absent", missing: [...RUNTIME_SCRIPTS] };
|
|
10124
|
+
if (!bmad.isDirectory() || bmad.isSymbolicLink()) return { state: "unsafe", error: "_bmad is not a real directory" };
|
|
10125
|
+
const destStat = entryStat(dest);
|
|
10126
|
+
if (destStat && (!destStat.isDirectory() || destStat.isSymbolicLink())) return { state: "unsafe", error: "_bmad/scripts is not a real directory" };
|
|
10127
|
+
const missing = [];
|
|
10128
|
+
for (const name of RUNTIME_SCRIPTS) {
|
|
10129
|
+
const existing = destStat ? entryStat(join10(dest, name)) : void 0;
|
|
10130
|
+
if (!existing) missing.push(name);
|
|
10131
|
+
else if (!existing.isFile() || existing.isSymbolicLink()) return { state: "unsafe", error: `Existing _bmad/scripts/${name} is not a regular file; refusing overwrite` };
|
|
10132
|
+
}
|
|
10133
|
+
if (!missing.length) return { state: "present" };
|
|
10134
|
+
const configDir = entryStat(join10(bmadDir, "_config"));
|
|
10135
|
+
if (configDir && (!configDir.isDirectory() || configDir.isSymbolicLink())) return { state: "unsafe", error: MANIFEST_UNUSABLE };
|
|
10136
|
+
const manifestPath = join10(bmadDir, "_config", "files-manifest.csv");
|
|
10137
|
+
const manifestFile = configDir ? entryStat(manifestPath) : void 0;
|
|
10138
|
+
if (!manifestFile) return { state: "decline", why: "_bmad/_config/files-manifest.csv is absent, so recovered scripts cannot be attested", missing };
|
|
10139
|
+
if (!manifestFile.isFile() || manifestFile.isSymbolicLink()) return { state: "unsafe", error: MANIFEST_UNUSABLE };
|
|
10140
|
+
const installed = installedVersion?.();
|
|
10141
|
+
if (installed && installed !== version) {
|
|
10142
|
+
return { state: "decline", why: `this install is bmad-method ${installed}, not the pinned ${version} that recovery restores from`, missing };
|
|
10143
|
+
}
|
|
10144
|
+
const rows = manifestRows(readFileSync10(manifestPath, "utf8"));
|
|
10145
|
+
const unattested = missing.filter((name) => !rows.some((row) => row[3] === `scripts/${name}`));
|
|
10146
|
+
if (unattested.length) {
|
|
10147
|
+
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 };
|
|
10148
|
+
}
|
|
10149
|
+
return { state: "missing", missing };
|
|
10150
|
+
} catch (error) {
|
|
10151
|
+
return { state: "unsafe", error: error instanceof Error ? error.message : String(error) };
|
|
10152
|
+
}
|
|
10153
|
+
}
|
|
10154
|
+
function planBmadSetupScripts(repoRoot, pkg, version) {
|
|
10155
|
+
const dest = join10(repoRoot, "_bmad", "scripts");
|
|
10156
|
+
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"}` };
|
|
10157
|
+
let fingerprint2;
|
|
10158
|
+
try {
|
|
10159
|
+
fingerprint2 = installerFingerprint(pkg.root);
|
|
10160
|
+
} catch (error) {
|
|
10161
|
+
return { files: [], declinable: "package", error: `Could not fingerprint pinned BMAD installer: ${String(error)}` };
|
|
10162
|
+
}
|
|
10163
|
+
if (VERIFIED_BMAD_INSTALLERS[fingerprint2] !== version) return { files: [], declinable: "package", error: `Unverified BMAD installer fingerprint ${fingerprint2}; refusing script recovery` };
|
|
10164
|
+
const source = join10(pkg.root, "src", "scripts");
|
|
10165
|
+
try {
|
|
10166
|
+
const bmad = lstatSync9(join10(repoRoot, "_bmad"));
|
|
10167
|
+
if (!bmad.isDirectory() || bmad.isSymbolicLink()) throw new Error("_bmad is not a real directory");
|
|
10168
|
+
const destStat = entryStat(dest);
|
|
10169
|
+
if (destStat && (!destStat.isDirectory() || destStat.isSymbolicLink())) throw new Error("_bmad/scripts is not a real directory");
|
|
10170
|
+
const names = readdirSync7(source).filter((name) => !name.startsWith(".") && !name.endsWith(".pyc") && name !== "tests" && name !== "__pycache__" && name !== ".pytest_cache").sort();
|
|
10171
|
+
if (names.join(FIELD_SEP) !== [...RUNTIME_SCRIPTS].sort().join(FIELD_SEP)) throw new DeclinableError("Pinned installer script inventory changed; refusing recovery", "package");
|
|
10172
|
+
const missing = [];
|
|
10173
|
+
for (const name of names) {
|
|
10174
|
+
const existing = entryStat(join10(dest, name));
|
|
10175
|
+
if (!existing) missing.push(name);
|
|
10176
|
+
else if (!existing.isFile() || existing.isSymbolicLink()) throw new Error(`Existing _bmad/scripts/${name} is not a regular file; refusing overwrite`);
|
|
10177
|
+
}
|
|
10178
|
+
const files = [];
|
|
10179
|
+
if (!missing.length) return { files, source };
|
|
10180
|
+
const manifestPath = join10(repoRoot, "_bmad", "_config", "files-manifest.csv");
|
|
10181
|
+
const manifestDir = entryStat(join10(repoRoot, "_bmad", "_config"));
|
|
10182
|
+
const manifestFile = entryStat(manifestPath);
|
|
10183
|
+
if (!manifestDir?.isDirectory() || manifestDir.isSymbolicLink() || !manifestFile?.isFile() || manifestFile.isSymbolicLink()) {
|
|
10184
|
+
throw new Error(MANIFEST_UNUSABLE);
|
|
10185
|
+
}
|
|
10186
|
+
const rows = manifestRows(readFileSync10(manifestPath, "utf8"));
|
|
10187
|
+
for (const name of missing) {
|
|
10188
|
+
const src = join10(source, name);
|
|
10189
|
+
if (!lstatSync9(src).isFile() || lstatSync9(src).isSymbolicLink()) throw new DeclinableError(`Installer script is not a regular file: ${name}`, "package");
|
|
10190
|
+
const matching = rows.filter((row) => row[3] === `scripts/${name}`);
|
|
10191
|
+
const expectedHash = createHash8("sha256").update(readFileSync10(src)).digest("hex");
|
|
10192
|
+
if (matching.length !== 1 || matching[0].join(FIELD_SEP) !== ["py", name.slice(0, -3), "scripts", `scripts/${name}`, expectedHash].join(FIELD_SEP)) {
|
|
10193
|
+
throw new DeclinableError(`Installed file manifest does not attest pinned script ${name}`, "attest");
|
|
10194
|
+
}
|
|
10195
|
+
files.push(join10(dest, name));
|
|
10196
|
+
}
|
|
10197
|
+
return { files, source };
|
|
10198
|
+
} catch (error) {
|
|
10199
|
+
return { files: [], ...error instanceof DeclinableError ? { declinable: error.kind } : {}, error: `BMAD script recovery refused: ${error instanceof Error ? error.message : String(error)}` };
|
|
10200
|
+
}
|
|
10201
|
+
}
|
|
10202
|
+
function applyBmadSetupScripts(repoRoot, pkg, version, afterPublish) {
|
|
10203
|
+
const plan = planBmadSetupScripts(repoRoot, pkg, version);
|
|
10204
|
+
if (plan.error || !plan.files.length) return { files: [], ...plan.error ? { error: plan.error } : {} };
|
|
10205
|
+
const parent = join10(repoRoot, "_bmad");
|
|
10206
|
+
const dest = join10(parent, "scripts");
|
|
10207
|
+
const expectedParent = entryStat(parent);
|
|
10208
|
+
const expectedDir = entryStat(dest);
|
|
10209
|
+
let stage;
|
|
10210
|
+
let stageFd;
|
|
10211
|
+
let parentFd;
|
|
10212
|
+
const created = [];
|
|
10213
|
+
const stagedIdentities = /* @__PURE__ */ new Map();
|
|
10214
|
+
const published = /* @__PURE__ */ new Map();
|
|
10215
|
+
let ambiguousPublication;
|
|
10216
|
+
const matchesPayload = (path, owned) => {
|
|
10217
|
+
const stat = entryStat(path);
|
|
10218
|
+
return !!stat?.isFile() && !stat.isSymbolicLink() && stat.dev === owned.dev && stat.ino === owned.ino && (stat.mode & 4095) === owned.mode && readFileSync10(path).equals(owned.bytes);
|
|
10219
|
+
};
|
|
10220
|
+
let madeDir = false;
|
|
10221
|
+
let dirFd;
|
|
10222
|
+
let result2 = { files: [] };
|
|
10223
|
+
try {
|
|
10224
|
+
parentFd = openSync2(parent, constants2.O_RDONLY | constants2.O_DIRECTORY | constants2.O_NOFOLLOW);
|
|
10225
|
+
const pinnedParent = fstatSync(parentFd);
|
|
10226
|
+
if (!sameIdentity(expectedParent, pinnedParent) || !sameIdentity(entryStat(parent), pinnedParent)) throw new Error("BMAD parent directory changed before staging");
|
|
10227
|
+
const anchoredParent = `/proc/self/fd/${parentFd}`;
|
|
10228
|
+
stage = mkdtempSync2(join10(anchoredParent, ".scripts-stage-"));
|
|
10229
|
+
stageFd = openSync2(stage, constants2.O_RDONLY | constants2.O_DIRECTORY | constants2.O_NOFOLLOW);
|
|
10230
|
+
const pinnedStage = fstatSync(stageFd);
|
|
10231
|
+
const stageName = basename4(stage);
|
|
10232
|
+
const anchoredStage = `/proc/self/fd/${stageFd}`;
|
|
10233
|
+
if (!sameIdentity(entryStat(join10(anchoredParent, stageName)), pinnedStage)) throw new Error("Staging directory changed during creation");
|
|
10234
|
+
for (const path of plan.files) {
|
|
10235
|
+
const name = basename4(path);
|
|
10236
|
+
const src = join10(plan.source, name);
|
|
10237
|
+
const staged = join10(anchoredStage, name);
|
|
10238
|
+
const fileFd = openSync2(staged, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | constants2.O_NOFOLLOW, 384);
|
|
10239
|
+
try {
|
|
10240
|
+
const stat = fstatSync(fileFd);
|
|
10241
|
+
const bytes = readFileSync10(src);
|
|
10242
|
+
const mode = lstatSync9(src).mode & 4095;
|
|
10243
|
+
stagedIdentities.set(name, { dev: stat.dev, ino: stat.ino, bytes, mode });
|
|
10244
|
+
writeFileSync7(fileFd, bytes);
|
|
10245
|
+
fchmodSync(fileFd, mode);
|
|
10246
|
+
} finally {
|
|
10247
|
+
closeSync2(fileFd);
|
|
10248
|
+
}
|
|
10249
|
+
}
|
|
10250
|
+
if (!sameIdentity(entryStat(parent), pinnedParent)) throw new Error("BMAD parent directory changed while staging");
|
|
10251
|
+
const current = planBmadSetupScripts(repoRoot, pkg, version);
|
|
10252
|
+
if (current.error || current.files.join("\0") !== plan.files.join("\0")) throw new Error(current.error ?? "Script destination changed while staging");
|
|
10253
|
+
const anchoredDest = join10(anchoredParent, "scripts");
|
|
10254
|
+
if (!entryStat(anchoredDest)) {
|
|
10255
|
+
mkdirSync6(anchoredDest);
|
|
10256
|
+
madeDir = true;
|
|
10257
|
+
}
|
|
10258
|
+
dirFd = openSync2(anchoredDest, constants2.O_RDONLY | constants2.O_DIRECTORY | constants2.O_NOFOLLOW);
|
|
10259
|
+
const pinned = fstatSync(dirFd);
|
|
10260
|
+
if (expectedDir && !sameIdentity(expectedDir, pinned)) throw new Error("Script directory changed while staging");
|
|
10261
|
+
if (!sameIdentity(entryStat(parent), pinnedParent) || !sameIdentity(entryStat(dest), pinned)) throw new Error("Script or parent directory changed during publication");
|
|
10262
|
+
const anchored = `/proc/self/fd/${dirFd}`;
|
|
10263
|
+
for (const path of plan.files) {
|
|
10264
|
+
const name = basename4(path);
|
|
10265
|
+
const staged = join10(anchoredStage, name);
|
|
10266
|
+
const output = join10(anchored, name);
|
|
10267
|
+
const owned = stagedIdentities.get(name);
|
|
10268
|
+
if (!sameIdentity(entryStat(parent), pinnedParent) || !sameIdentity(entryStat(dest), pinned)) throw new Error("Script or parent directory changed during publication");
|
|
10269
|
+
if (!matchesPayload(staged, owned)) throw new Error(`Staged script changed before publication: ${name}`);
|
|
10270
|
+
if (entryStat(output)) {
|
|
10271
|
+
ambiguousPublication = path;
|
|
10272
|
+
throw new Error(`Script destination appeared before publication: ${name}`);
|
|
10273
|
+
}
|
|
10274
|
+
try {
|
|
10275
|
+
linkSync(staged, output);
|
|
10276
|
+
} catch (error) {
|
|
10277
|
+
if (entryStat(output)) ambiguousPublication = path;
|
|
10278
|
+
throw error;
|
|
10279
|
+
}
|
|
10280
|
+
published.set(path, owned);
|
|
10281
|
+
created.push(path);
|
|
10282
|
+
if (!matchesPayload(output, owned)) throw new Error(`Published script changed during publication: ${name}`);
|
|
10283
|
+
afterPublish?.(created.length);
|
|
10284
|
+
}
|
|
10285
|
+
if (!sameIdentity(entryStat(parent), pinnedParent) || !sameIdentity(entryStat(dest), pinned)) throw new Error("Script or parent directory changed during publication");
|
|
10286
|
+
for (const path of created) {
|
|
10287
|
+
if (!matchesPayload(join10(anchored, basename4(path)), stagedIdentities.get(basename4(path)))) throw new Error(`Published script changed before completion: ${basename4(path)}`);
|
|
10288
|
+
}
|
|
10289
|
+
result2 = { files: created };
|
|
10290
|
+
} catch (error) {
|
|
10291
|
+
const recoveryErrors = [];
|
|
10292
|
+
for (const [path, owned] of [...published].reverse()) {
|
|
10293
|
+
try {
|
|
10294
|
+
const anchoredPath = join10(`/proc/self/fd/${dirFd}`, basename4(path));
|
|
10295
|
+
const stat = entryStat(anchoredPath);
|
|
10296
|
+
if (matchesPayload(anchoredPath, owned)) unlinkSync5(anchoredPath);
|
|
10297
|
+
else if (stat) recoveryErrors.push(`Concurrent replacement or mutation retained: ${path}`);
|
|
10298
|
+
else recoveryErrors.push(`Published file absent from pinned directory; ownership unresolved: ${path}`);
|
|
10299
|
+
} catch (recoveryError) {
|
|
10300
|
+
recoveryErrors.push(`Could not restore ${path}: ${String(recoveryError)}`);
|
|
10301
|
+
}
|
|
10302
|
+
}
|
|
10303
|
+
if (ambiguousPublication) recoveryErrors.push(`Publication not confirmed; destination retained or ownership unresolved: ${ambiguousPublication}`);
|
|
10304
|
+
if (madeDir && dirFd !== void 0 && parentFd !== void 0) {
|
|
10305
|
+
try {
|
|
10306
|
+
const pinned = fstatSync(dirFd);
|
|
10307
|
+
const anchoredDest = join10(`/proc/self/fd/${parentFd}`, "scripts");
|
|
10308
|
+
if (sameIdentity(entryStat(anchoredDest), pinned) && readdirSync7(anchoredDest).length === 0) rmdirSync3(anchoredDest);
|
|
10309
|
+
else recoveryErrors.push(`Created script directory moved or retained: ${dest}`);
|
|
10310
|
+
} catch (recoveryError) {
|
|
10311
|
+
recoveryErrors.push(`Could not remove empty script directory: ${String(recoveryError)}`);
|
|
10312
|
+
}
|
|
10313
|
+
}
|
|
10314
|
+
result2 = { files: [], error: `BMAD script recovery failed: ${error instanceof Error ? error.message : String(error)}${recoveryErrors.length ? `; recovery incomplete: ${recoveryErrors.join("; ")}` : ""}` };
|
|
10315
|
+
} finally {
|
|
10316
|
+
const cleanupErrors = [];
|
|
10317
|
+
if (stageFd !== void 0) {
|
|
10318
|
+
try {
|
|
10319
|
+
const pinnedStage = fstatSync(stageFd);
|
|
10320
|
+
const anchoredStage = `/proc/self/fd/${stageFd}`;
|
|
10321
|
+
for (const [name, owned] of stagedIdentities) {
|
|
10322
|
+
const staged = join10(anchoredStage, name);
|
|
10323
|
+
const stat = entryStat(staged);
|
|
10324
|
+
if (stat?.isFile() && !stat.isSymbolicLink() && stat.dev === owned.dev && stat.ino === owned.ino) unlinkSync5(staged);
|
|
10325
|
+
else cleanupErrors.push(`Staged file replaced, modified or absent: ${name}`);
|
|
10326
|
+
}
|
|
10327
|
+
const stagePath = join10(`/proc/self/fd/${parentFd}`, basename4(stage));
|
|
10328
|
+
if (sameIdentity(entryStat(stagePath), pinnedStage) && readdirSync7(anchoredStage).length === 0) rmdirSync3(stagePath);
|
|
10329
|
+
else cleanupErrors.push(`Staging directory moved or contains foreign entries: ${join10(parent, basename4(stage))}`);
|
|
10330
|
+
} catch (cleanupError) {
|
|
10331
|
+
cleanupErrors.push(`Could not clean owned staging directory: ${String(cleanupError)}`);
|
|
10332
|
+
}
|
|
10333
|
+
closeSync2(stageFd);
|
|
10334
|
+
} else if (stage) cleanupErrors.push(`Staging directory identity unavailable: ${join10(parent, basename4(stage))}`);
|
|
10335
|
+
if (cleanupErrors.length && result2.files.length) {
|
|
10336
|
+
for (const [path, owned] of [...published].reverse()) {
|
|
10337
|
+
try {
|
|
10338
|
+
const output = join10(`/proc/self/fd/${dirFd}`, basename4(path));
|
|
10339
|
+
if (matchesPayload(output, owned)) unlinkSync5(output);
|
|
10340
|
+
else cleanupErrors.push(`Concurrent replacement or mutation retained: ${path}`);
|
|
10341
|
+
} catch (error) {
|
|
10342
|
+
cleanupErrors.push(`Could not restore ${path}: ${String(error)}`);
|
|
10343
|
+
}
|
|
10344
|
+
}
|
|
10345
|
+
if (madeDir && dirFd !== void 0 && parentFd !== void 0) {
|
|
10346
|
+
try {
|
|
10347
|
+
const anchoredDest = join10(`/proc/self/fd/${parentFd}`, "scripts");
|
|
10348
|
+
if (sameIdentity(entryStat(anchoredDest), fstatSync(dirFd)) && readdirSync7(anchoredDest).length === 0) rmdirSync3(anchoredDest);
|
|
10349
|
+
else cleanupErrors.push(`Created script directory moved or retained: ${dest}`);
|
|
10350
|
+
} catch (error) {
|
|
10351
|
+
cleanupErrors.push(`Could not remove empty script directory: ${String(error)}`);
|
|
10352
|
+
}
|
|
10353
|
+
}
|
|
10354
|
+
}
|
|
10355
|
+
if (dirFd !== void 0) closeSync2(dirFd);
|
|
10356
|
+
if (parentFd !== void 0) closeSync2(parentFd);
|
|
10357
|
+
if (cleanupErrors.length) result2 = { files: [], error: `${result2.error ? `${result2.error}; ` : "BMAD script staging cleanup incomplete: "}recovery incomplete: ${cleanupErrors.join("; ")}` };
|
|
10358
|
+
}
|
|
10359
|
+
return result2;
|
|
10360
|
+
}
|
|
10361
|
+
|
|
10362
|
+
// src/parity/rules.ts
|
|
10363
|
+
import { basename as basename5, dirname as dirname9, join as join12, relative as relative9, resolve as resolve7 } from "node:path";
|
|
10036
10364
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
10037
10365
|
import YAML4 from "yaml";
|
|
10038
10366
|
|
|
10039
10367
|
// src/bmad/reserved-modules.ts
|
|
10040
|
-
import { existsSync as existsSync7, readFileSync as
|
|
10041
|
-
import { join as
|
|
10368
|
+
import { existsSync as existsSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync8 } from "node:fs";
|
|
10369
|
+
import { join as join11 } from "node:path";
|
|
10042
10370
|
import YAML3 from "yaml";
|
|
10043
10371
|
var BMAD_RESERVED_DIR_NAMES = ["custom", "scripts", "_config", "_memory", "memory", "render"];
|
|
10044
10372
|
var indentOf = (line) => line.length - line.trimStart().length;
|
|
@@ -10095,19 +10423,19 @@ function stripReservedModuleEntries(raw) {
|
|
|
10095
10423
|
return { text: lines.filter((_, index) => !drop.has(index)).join(""), evicted };
|
|
10096
10424
|
}
|
|
10097
10425
|
function evictReservedModuleEntries(repoRoot, dryRun) {
|
|
10098
|
-
const manifestPath =
|
|
10426
|
+
const manifestPath = join11(repoRoot, "_bmad", "_config", "manifest.yaml");
|
|
10099
10427
|
const none = { evicted: [], changedFiles: [], details: [] };
|
|
10100
10428
|
if (!existsSync7(manifestPath)) return none;
|
|
10101
10429
|
let original;
|
|
10102
10430
|
try {
|
|
10103
|
-
original =
|
|
10431
|
+
original = readFileSync11(manifestPath, "utf8");
|
|
10104
10432
|
} catch {
|
|
10105
10433
|
return none;
|
|
10106
10434
|
}
|
|
10107
10435
|
const stripped = stripReservedModuleEntries(original);
|
|
10108
10436
|
if (!stripped) return none;
|
|
10109
10437
|
const names = stripped.evicted.map((name) => `"${name}"`).join(", ");
|
|
10110
|
-
if (!dryRun)
|
|
10438
|
+
if (!dryRun) writeFileSync8(manifestPath, stripped.text, "utf8");
|
|
10111
10439
|
return {
|
|
10112
10440
|
evicted: stripped.evicted,
|
|
10113
10441
|
changedFiles: [manifestPath],
|
|
@@ -10821,17 +11149,17 @@ function normalizeNewlines(value) {
|
|
|
10821
11149
|
return value.replace(/\r\n/g, "\n");
|
|
10822
11150
|
}
|
|
10823
11151
|
function readText(path) {
|
|
10824
|
-
return normalizeNewlines(
|
|
11152
|
+
return normalizeNewlines(readFileSync12(path, "utf8"));
|
|
10825
11153
|
}
|
|
10826
11154
|
function safeReadText2(path) {
|
|
10827
11155
|
return existsSync8(path) ? readText(path) : null;
|
|
10828
11156
|
}
|
|
10829
11157
|
function ensureParent(path) {
|
|
10830
|
-
|
|
11158
|
+
mkdirSync7(dirname9(path), { recursive: true });
|
|
10831
11159
|
}
|
|
10832
11160
|
function writeText(path, content) {
|
|
10833
11161
|
ensureParent(path);
|
|
10834
|
-
|
|
11162
|
+
writeFileSync9(path, content);
|
|
10835
11163
|
}
|
|
10836
11164
|
function tryParseJson(text2) {
|
|
10837
11165
|
if (!text2) return null;
|
|
@@ -10857,12 +11185,12 @@ function readSymlinkTarget(path) {
|
|
|
10857
11185
|
}
|
|
10858
11186
|
function ensureSymlink(path, target, dryRun) {
|
|
10859
11187
|
if (existsSync8(path)) {
|
|
10860
|
-
const stat =
|
|
11188
|
+
const stat = lstatSync10(path);
|
|
10861
11189
|
if (stat.isSymbolicLink()) {
|
|
10862
11190
|
const current = readSymlinkTarget(path);
|
|
10863
11191
|
if (current === target) return { changed: false };
|
|
10864
11192
|
if (!dryRun) {
|
|
10865
|
-
|
|
11193
|
+
unlinkSync6(path);
|
|
10866
11194
|
symlinkSync3(target, path);
|
|
10867
11195
|
}
|
|
10868
11196
|
return { changed: true };
|
|
@@ -10873,12 +11201,12 @@ function ensureSymlink(path, target, dryRun) {
|
|
|
10873
11201
|
return { changed: true };
|
|
10874
11202
|
}
|
|
10875
11203
|
function bootstrapAgentsFile(repoRoot, dryRun) {
|
|
10876
|
-
const agentsPath =
|
|
11204
|
+
const agentsPath = join12(repoRoot, "AGENTS.md");
|
|
10877
11205
|
if (existsSync8(agentsPath)) return { changedFiles: [], details: [] };
|
|
10878
11206
|
for (const file of ["CLAUDE.md", "GEMINI.md"]) {
|
|
10879
|
-
const source =
|
|
11207
|
+
const source = join12(repoRoot, file);
|
|
10880
11208
|
if (!existsSync8(source)) continue;
|
|
10881
|
-
const stat =
|
|
11209
|
+
const stat = lstatSync10(source);
|
|
10882
11210
|
if (stat.isSymbolicLink()) continue;
|
|
10883
11211
|
if (stat.isFile()) {
|
|
10884
11212
|
if (!dryRun) renameSync4(source, agentsPath);
|
|
@@ -10886,9 +11214,9 @@ function bootstrapAgentsFile(repoRoot, dryRun) {
|
|
|
10886
11214
|
}
|
|
10887
11215
|
return { changedFiles: [], details: [], blocked: `${file} exists but is not a regular file; cannot promote to AGENTS.md` };
|
|
10888
11216
|
}
|
|
10889
|
-
const readmePath =
|
|
11217
|
+
const readmePath = join12(repoRoot, "README.md");
|
|
10890
11218
|
if (existsSync8(readmePath)) {
|
|
10891
|
-
const stat =
|
|
11219
|
+
const stat = lstatSync10(readmePath);
|
|
10892
11220
|
if (!stat.isFile()) return { changedFiles: [], details: [], blocked: "README.md exists but is not a regular file; cannot copy to AGENTS.md" };
|
|
10893
11221
|
if (!dryRun) copyFileSync(readmePath, agentsPath);
|
|
10894
11222
|
return { changedFiles: [agentsPath], details: ["Copied README.md to AGENTS.md before wiring agent-file symlinks"] };
|
|
@@ -10926,11 +11254,11 @@ function yamlGet(text2, keyPath) {
|
|
|
10926
11254
|
return "";
|
|
10927
11255
|
}
|
|
10928
11256
|
function discoverRoles(repoRoot) {
|
|
10929
|
-
const rolesDir =
|
|
11257
|
+
const rolesDir = join12(repoRoot, "agents", "hermes");
|
|
10930
11258
|
if (!existsSync8(rolesDir)) return [];
|
|
10931
|
-
return
|
|
10932
|
-
const roleDir =
|
|
10933
|
-
const roleYamlPath =
|
|
11259
|
+
return readdirSync8(rolesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => {
|
|
11260
|
+
const roleDir = join12(rolesDir, entry.name);
|
|
11261
|
+
const roleYamlPath = join12(roleDir, "role.yaml");
|
|
10934
11262
|
if (!existsSync8(roleYamlPath)) return null;
|
|
10935
11263
|
const text2 = readText(roleYamlPath);
|
|
10936
11264
|
const runtimeRepoRaw = yamlGet(text2, "runtime.github_repo");
|
|
@@ -10963,26 +11291,26 @@ function discoverRoles(repoRoot) {
|
|
|
10963
11291
|
}).filter((value) => Boolean(value));
|
|
10964
11292
|
}
|
|
10965
11293
|
function templateScript(ctx, name) {
|
|
10966
|
-
const source =
|
|
11294
|
+
const source = join12(ctx.pjanglerRoot, ".mise", "scripts", name);
|
|
10967
11295
|
return existsSync8(source) ? readText(source) : void 0;
|
|
10968
11296
|
}
|
|
10969
11297
|
function templateVersioningScript(ctx) {
|
|
10970
11298
|
return templateScript(ctx, "versioning.sh");
|
|
10971
11299
|
}
|
|
10972
11300
|
function templateLinkAgentfilesScript(ctx) {
|
|
10973
|
-
const source =
|
|
11301
|
+
const source = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", ".mise", "scripts", "link-agentfiles.sh");
|
|
10974
11302
|
return existsSync8(source) ? readText(source) : templateScript(ctx, "link-agentfiles.sh");
|
|
10975
11303
|
}
|
|
10976
11304
|
function templateMaterializeEnvScript(ctx) {
|
|
10977
|
-
const source =
|
|
11305
|
+
const source = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", MATERIALIZE_ENV_SCRIPT_REL);
|
|
10978
11306
|
return existsSync8(source) ? readText(source) : void 0;
|
|
10979
11307
|
}
|
|
10980
11308
|
function resolveAgentHooksLayer(ctx) {
|
|
10981
11309
|
const override = process.env.PJ_AGENT_HOOKS_LAYER;
|
|
10982
11310
|
if (override === "0" || override === "false") return false;
|
|
10983
11311
|
if (override === "1" || override === "true") return true;
|
|
10984
|
-
if (existsSync8(
|
|
10985
|
-
return !existsSync8(
|
|
11312
|
+
if (existsSync8(join12(ctx.repoRoot, ".agents", "hooks", "sync.py"))) return true;
|
|
11313
|
+
return !existsSync8(join12(ctx.homeDir, ".agents", "hooks"));
|
|
10986
11314
|
}
|
|
10987
11315
|
function evaluateMiseConditionals(template, agentHooksLayer) {
|
|
10988
11316
|
const out = [];
|
|
@@ -11008,13 +11336,13 @@ function evaluateMiseConditionals(template, agentHooksLayer) {
|
|
|
11008
11336
|
}
|
|
11009
11337
|
function renderGeneratedProjectMiseToml(ctx, template) {
|
|
11010
11338
|
const project = readProjectJson(ctx);
|
|
11011
|
-
const projectName = String(project?.project_name ??
|
|
11339
|
+
const projectName = String(project?.project_name ?? basename5(ctx.repoRoot) ?? "project");
|
|
11012
11340
|
return evaluateMiseConditionals(template, resolveAgentHooksLayer(ctx)).replace(/\{%\s*raw\s*%\}([\s\S]*?)\{%\s*endraw\s*%\}/g, "$1").replace(/\{\{\s*project_name\s*\}\}/g, projectName);
|
|
11013
11341
|
}
|
|
11014
11342
|
function ensureMiseTomlFromTemplate(ctx, changedFiles) {
|
|
11015
|
-
const targetPath =
|
|
11343
|
+
const targetPath = join12(ctx.repoRoot, "mise.toml");
|
|
11016
11344
|
if (existsSync8(targetPath)) return null;
|
|
11017
|
-
const sourcePath =
|
|
11345
|
+
const sourcePath = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", "mise.toml.jinja");
|
|
11018
11346
|
if (!existsSync8(sourcePath)) return null;
|
|
11019
11347
|
const rendered = renderGeneratedProjectMiseToml(ctx, readText(sourcePath));
|
|
11020
11348
|
changedFiles.push(targetPath);
|
|
@@ -11022,12 +11350,12 @@ function ensureMiseTomlFromTemplate(ctx, changedFiles) {
|
|
|
11022
11350
|
return rendered;
|
|
11023
11351
|
}
|
|
11024
11352
|
function templateCommonProjectText(ctx, rel) {
|
|
11025
|
-
const path =
|
|
11353
|
+
const path = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", rel);
|
|
11026
11354
|
return existsSync8(path) ? readText(path) : void 0;
|
|
11027
11355
|
}
|
|
11028
11356
|
function lstatIfPresent(path) {
|
|
11029
11357
|
try {
|
|
11030
|
-
return
|
|
11358
|
+
return lstatSync10(path);
|
|
11031
11359
|
} catch (error) {
|
|
11032
11360
|
if (error.code === "ENOENT") return void 0;
|
|
11033
11361
|
throw error;
|
|
@@ -11035,7 +11363,7 @@ function lstatIfPresent(path) {
|
|
|
11035
11363
|
}
|
|
11036
11364
|
function templateVersionFilesConf(ctx, repoRoot) {
|
|
11037
11365
|
if (isObsidianPluginRepo(ctx, repoRoot)) return OBSIDIAN_VERSION_FILES_CONF;
|
|
11038
|
-
const packageJson =
|
|
11366
|
+
const packageJson = join12(repoRoot, "package.json");
|
|
11039
11367
|
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";
|
|
11040
11368
|
}
|
|
11041
11369
|
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";
|
|
@@ -11045,7 +11373,7 @@ function recordedProjectType(ctx) {
|
|
|
11045
11373
|
function isObsidianPluginRepo(ctx, repoRoot) {
|
|
11046
11374
|
const recorded = recordedProjectType(ctx);
|
|
11047
11375
|
if (recorded !== void 0) return recorded === "obsidian-plugin";
|
|
11048
|
-
const manifest = record2(tryParseJson(safeReadText2(
|
|
11376
|
+
const manifest = record2(tryParseJson(safeReadText2(join12(repoRoot, "manifest.json"))));
|
|
11049
11377
|
return typeof manifest.id === "string" && typeof manifest.minAppVersion === "string";
|
|
11050
11378
|
}
|
|
11051
11379
|
function unsupportedVersionTypes(conf, script) {
|
|
@@ -11506,7 +11834,7 @@ function describeRewrite(before, after) {
|
|
|
11506
11834
|
return difference ? `migrate would still rewrite ${difference} in mise.toml` : "migrate would still re-lay out mise.toml (its meaning is unchanged)";
|
|
11507
11835
|
}
|
|
11508
11836
|
function readProjectJson(ctx) {
|
|
11509
|
-
return tryParseJson(safeReadText2(
|
|
11837
|
+
return tryParseJson(safeReadText2(join12(ctx.repoRoot, ".project.json")));
|
|
11510
11838
|
}
|
|
11511
11839
|
function readDeclaredAgents(ctx) {
|
|
11512
11840
|
const project = readProjectJson(ctx);
|
|
@@ -11523,7 +11851,7 @@ function readDeclaredAgents(ctx) {
|
|
|
11523
11851
|
});
|
|
11524
11852
|
}
|
|
11525
11853
|
function readRoleYamlAt(roleDir) {
|
|
11526
|
-
const roleYamlPath =
|
|
11854
|
+
const roleYamlPath = join12(roleDir, "role.yaml");
|
|
11527
11855
|
if (!existsSync8(roleYamlPath)) return null;
|
|
11528
11856
|
const text2 = readText(roleYamlPath);
|
|
11529
11857
|
return {
|
|
@@ -11535,7 +11863,7 @@ function readRoleYamlAt(roleDir) {
|
|
|
11535
11863
|
}
|
|
11536
11864
|
function declaredRoleIsUnprovisioned(repoRoot, roleDir) {
|
|
11537
11865
|
if (!roleDir) return true;
|
|
11538
|
-
return !existsSync8(
|
|
11866
|
+
return !existsSync8(join12(resolve7(repoRoot, roleDir), "role.yaml"));
|
|
11539
11867
|
}
|
|
11540
11868
|
function validateDeclaredAgent(ctx, declared) {
|
|
11541
11869
|
const details = [];
|
|
@@ -11560,11 +11888,11 @@ function validateDeclaredAgent(ctx, declared) {
|
|
|
11560
11888
|
details.push(`agents.${declared.agentId} should map to agent_id ${roleYaml.agentId}`);
|
|
11561
11889
|
}
|
|
11562
11890
|
if (roleYaml.providerName) {
|
|
11563
|
-
const dispatcher =
|
|
11891
|
+
const dispatcher = join12(roleDir, ".scripts", "lib", "ticket-provider.sh");
|
|
11564
11892
|
if (!existsSync8(dispatcher)) {
|
|
11565
11893
|
details.push(`agents.${declared.agentId} provider dispatcher ${relative9(ctx.repoRoot, dispatcher)} missing`);
|
|
11566
11894
|
}
|
|
11567
|
-
const provider =
|
|
11895
|
+
const provider = join12(roleDir, ".scripts", "providers", `${roleYaml.providerName}.sh`);
|
|
11568
11896
|
if (!existsSync8(provider)) {
|
|
11569
11897
|
details.push(`agents.${declared.agentId} provider script ${relative9(ctx.repoRoot, provider)} missing`);
|
|
11570
11898
|
}
|
|
@@ -11580,7 +11908,7 @@ function repoPathNamesRoot(value, repoRoot, homeDir) {
|
|
|
11580
11908
|
function canonicalProjectJson(ctx) {
|
|
11581
11909
|
const roles = discoverRoles(ctx.repoRoot);
|
|
11582
11910
|
const existing = readProjectJson(ctx) ?? {};
|
|
11583
|
-
const slug = normalizeProjectId(existing.project_id ?? existing.project_slug ?? slugifyRepoName(
|
|
11911
|
+
const slug = normalizeProjectId(existing.project_id ?? existing.project_slug ?? slugifyRepoName(basename5(ctx.repoRoot)));
|
|
11584
11912
|
const firstRole = roles[0];
|
|
11585
11913
|
const ticketProvider = {
|
|
11586
11914
|
...existing.ticket_provider,
|
|
@@ -11650,8 +11978,8 @@ function canonicalProjectJson(ctx) {
|
|
|
11650
11978
|
};
|
|
11651
11979
|
}
|
|
11652
11980
|
function projectJsonFinding(ctx) {
|
|
11653
|
-
const projectPath =
|
|
11654
|
-
const planeJsonPath =
|
|
11981
|
+
const projectPath = join12(ctx.repoRoot, ".project.json");
|
|
11982
|
+
const planeJsonPath = join12(ctx.repoRoot, ".plane.json");
|
|
11655
11983
|
const details = [];
|
|
11656
11984
|
const data = readProjectJson(ctx);
|
|
11657
11985
|
const roles = discoverRoles(ctx.repoRoot);
|
|
@@ -11722,7 +12050,7 @@ var BMAD_DIST_TAGS_TTL_MS = 60 * 60 * 1e3;
|
|
|
11722
12050
|
var DEFAULT_BMAD_MODULES = ["bmm", "bmb", "cis"];
|
|
11723
12051
|
var BMAD_INSTALL_TOOLS = SUPPORTED_BMAD_TOOLS;
|
|
11724
12052
|
function manifestBmadModules(repoRoot) {
|
|
11725
|
-
const manifestPath =
|
|
12053
|
+
const manifestPath = join12(repoRoot, "_bmad", "_config", "manifest.yaml");
|
|
11726
12054
|
const raw = safeReadText2(manifestPath);
|
|
11727
12055
|
if (raw === null) return { status: "absent" };
|
|
11728
12056
|
try {
|
|
@@ -11750,7 +12078,7 @@ function manifestBmadModules(repoRoot) {
|
|
|
11750
12078
|
}
|
|
11751
12079
|
}
|
|
11752
12080
|
function configuredBmadModuleSelection(repoRoot) {
|
|
11753
|
-
const raw = safeReadText2(
|
|
12081
|
+
const raw = safeReadText2(join12(repoRoot, "_bmad", "config.toml"));
|
|
11754
12082
|
if (raw === null) return void 0;
|
|
11755
12083
|
const modules = [...raw.matchAll(/^\[modules\.([A-Za-z0-9][A-Za-z0-9_-]*)\]\s*$/gm)].map((match) => match[1]);
|
|
11756
12084
|
return { modules: Array.from(new Set(modules)).filter((name) => name !== "g33"), hasG33: modules.includes("g33") };
|
|
@@ -11761,24 +12089,60 @@ function selectedBmadModules(repoRoot) {
|
|
|
11761
12089
|
if (manifest.status === "invalid") throw new Error(manifest.error);
|
|
11762
12090
|
return configuredBmadModuleSelection(repoRoot)?.modules ?? [...DEFAULT_BMAD_MODULES];
|
|
11763
12091
|
}
|
|
11764
|
-
|
|
11765
|
-
|
|
11766
|
-
|
|
11767
|
-
|
|
11768
|
-
|
|
11769
|
-
|
|
11770
|
-
|
|
12092
|
+
var BMAD_RUNTIME_SCRIPT_SENTINELS = ["resolve_config.py", "config_utils.py"];
|
|
12093
|
+
function consolidatedBmadConfig(repoRoot) {
|
|
12094
|
+
const path = join12(repoRoot, "_bmad", "config.yaml");
|
|
12095
|
+
if (!existsSync8(path)) return { status: "absent" };
|
|
12096
|
+
let parsed;
|
|
12097
|
+
try {
|
|
12098
|
+
parsed = YAML4.parse(readText(path));
|
|
12099
|
+
} catch (error) {
|
|
12100
|
+
return { status: "unusable", reason: `could not be parsed: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}` };
|
|
12101
|
+
}
|
|
12102
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
12103
|
+
return { status: "unusable", reason: "is not a YAML mapping" };
|
|
12104
|
+
}
|
|
12105
|
+
return { status: "mapping", sections: new Set(Object.keys(parsed)) };
|
|
12106
|
+
}
|
|
12107
|
+
function bmadSentinelGaps(repoRoot, modules = selectedBmadModules(repoRoot)) {
|
|
12108
|
+
const bmad = join12(repoRoot, "_bmad");
|
|
12109
|
+
const gaps = [];
|
|
12110
|
+
const absent = (...segments) => !existsSync8(join12(bmad, ...segments));
|
|
12111
|
+
if (absent("config.toml")) gaps.push({ path: "config.toml", detail: "_bmad/config.toml" });
|
|
12112
|
+
for (const script of BMAD_RUNTIME_SCRIPT_SENTINELS) {
|
|
12113
|
+
if (absent("scripts", script)) gaps.push({ path: join12("scripts", script), detail: `_bmad/scripts/${script}` });
|
|
12114
|
+
}
|
|
12115
|
+
if (absent("_config", "manifest.yaml")) {
|
|
12116
|
+
gaps.push({
|
|
12117
|
+
path: join12("_config", "manifest.yaml"),
|
|
12118
|
+
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"
|
|
12119
|
+
});
|
|
12120
|
+
}
|
|
12121
|
+
let consolidated;
|
|
12122
|
+
for (const module of modules) {
|
|
12123
|
+
if (!absent(module, "config.yaml")) continue;
|
|
12124
|
+
consolidated ??= consolidatedBmadConfig(repoRoot);
|
|
12125
|
+
if (consolidated.status === "mapping" && consolidated.sections.has(module)) continue;
|
|
12126
|
+
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)`;
|
|
12127
|
+
gaps.push({ path: join12(module, "config.yaml"), detail: `_bmad/${module}/config.yaml${why}` });
|
|
12128
|
+
}
|
|
12129
|
+
return gaps;
|
|
12130
|
+
}
|
|
12131
|
+
function bmadScaffoldIssues(repoRoot, modules) {
|
|
12132
|
+
const gaps = bmadSentinelGaps(repoRoot, modules);
|
|
12133
|
+
const nameIssues = bmadProjectNameIssues(repoRoot);
|
|
12134
|
+
return { gaps, nameIssues, details: [...gaps.map((gap) => gap.detail), ...nameIssues.details] };
|
|
11771
12135
|
}
|
|
11772
12136
|
function canonicalBmadProjectName(repoRoot) {
|
|
11773
12137
|
const project = readProjectJson({ repoRoot });
|
|
11774
12138
|
const declared = typeof project?.project_name === "string" ? project.project_name.trim() : "";
|
|
11775
|
-
return declared ||
|
|
12139
|
+
return declared || basename5(repoRoot);
|
|
11776
12140
|
}
|
|
11777
12141
|
function bmadProjectNameIssues(repoRoot) {
|
|
11778
12142
|
const expected = canonicalBmadProjectName(repoRoot);
|
|
11779
12143
|
const paths = [];
|
|
11780
12144
|
const details = [];
|
|
11781
|
-
const configToml =
|
|
12145
|
+
const configToml = join12(repoRoot, "_bmad", "config.toml");
|
|
11782
12146
|
const tomlText = safeReadText2(configToml);
|
|
11783
12147
|
const tomlMatch = tomlText?.match(/^project_name\s*=\s*"((?:\\.|[^"\\])*)"\s*$/m);
|
|
11784
12148
|
let tomlName;
|
|
@@ -11793,16 +12157,16 @@ function bmadProjectNameIssues(repoRoot) {
|
|
|
11793
12157
|
paths.push(configToml);
|
|
11794
12158
|
details.push(`_bmad/config.toml project_name must be ${JSON.stringify(expected)}`);
|
|
11795
12159
|
}
|
|
11796
|
-
const bmadRoot =
|
|
12160
|
+
const bmadRoot = join12(repoRoot, "_bmad");
|
|
11797
12161
|
if (existsSync8(bmadRoot)) {
|
|
11798
12162
|
let declared;
|
|
11799
12163
|
try {
|
|
11800
12164
|
declared = selectedBmadModules(repoRoot);
|
|
11801
12165
|
} catch {
|
|
11802
|
-
declared =
|
|
12166
|
+
declared = readdirSync8(bmadRoot);
|
|
11803
12167
|
}
|
|
11804
12168
|
for (const name of new Set(declared)) {
|
|
11805
|
-
const configPath =
|
|
12169
|
+
const configPath = join12(bmadRoot, name, "config.yaml");
|
|
11806
12170
|
const raw = safeReadText2(configPath);
|
|
11807
12171
|
if (raw === null) continue;
|
|
11808
12172
|
let actual;
|
|
@@ -11822,7 +12186,7 @@ function bmadProjectNameIssues(repoRoot) {
|
|
|
11822
12186
|
function isCurrentActivation(path, activations) {
|
|
11823
12187
|
if (activations.owned.has(path)) return true;
|
|
11824
12188
|
try {
|
|
11825
|
-
if (activations.owned.has(
|
|
12189
|
+
if (activations.owned.has(join12(realpathSync4(dirname9(path)), basename5(path)))) return true;
|
|
11826
12190
|
return activations.targets.has(realpathSync4(path));
|
|
11827
12191
|
} catch {
|
|
11828
12192
|
return false;
|
|
@@ -11835,26 +12199,26 @@ async function skillActivations(ctx) {
|
|
|
11835
12199
|
function evictLegacyBmadPackState(ctx, changedFiles, activations) {
|
|
11836
12200
|
const details = [];
|
|
11837
12201
|
const skillDirs = [
|
|
11838
|
-
|
|
11839
|
-
...SUPPORTED_CLI_ROOTS.map((root) =>
|
|
12202
|
+
join12(ctx.repoRoot, ".agents", "skills"),
|
|
12203
|
+
...SUPPORTED_CLI_ROOTS.map((root) => join12(ctx.repoRoot, root, "skills"))
|
|
11840
12204
|
];
|
|
11841
12205
|
for (const dir of skillDirs) {
|
|
11842
12206
|
const dirStat = lstatIfPresent(dir);
|
|
11843
12207
|
if (!dirStat || dirStat.isSymbolicLink() || !dirStat.isDirectory()) continue;
|
|
11844
12208
|
let names;
|
|
11845
12209
|
try {
|
|
11846
|
-
names =
|
|
12210
|
+
names = readdirSync8(dir);
|
|
11847
12211
|
} catch {
|
|
11848
12212
|
continue;
|
|
11849
12213
|
}
|
|
11850
12214
|
for (const name of names) {
|
|
11851
12215
|
if (!name.startsWith(BMAD_SKILL_NAME_PREFIX)) continue;
|
|
11852
|
-
const path =
|
|
12216
|
+
const path = join12(dir, name);
|
|
11853
12217
|
if (!lstatIfPresent(path)?.isSymbolicLink()) continue;
|
|
11854
12218
|
if (isCurrentActivation(path, activations)) continue;
|
|
11855
12219
|
changedFiles.push(path);
|
|
11856
12220
|
details.push(`removed retired BMAD pack symlink ${relative9(ctx.repoRoot, path)}`);
|
|
11857
|
-
if (!ctx.dryRun)
|
|
12221
|
+
if (!ctx.dryRun) unlinkSync6(path);
|
|
11858
12222
|
}
|
|
11859
12223
|
}
|
|
11860
12224
|
return details;
|
|
@@ -11867,10 +12231,11 @@ function bmadInstallerInvocation(version = BMAD_INSTALLER_VERSION) {
|
|
|
11867
12231
|
prefixArgs: ["-y", `${BMAD_NPM_PACKAGE}@${version}`]
|
|
11868
12232
|
};
|
|
11869
12233
|
}
|
|
11870
|
-
function resolveBmadInstallerPackage(version = BMAD_INSTALLER_VERSION) {
|
|
12234
|
+
function resolveBmadInstallerPackage(version = BMAD_INSTALLER_VERSION, options = {}) {
|
|
12235
|
+
const timeoutMs = options.timeoutMs ?? 18e4;
|
|
11871
12236
|
const packageAt = (dir) => {
|
|
11872
12237
|
try {
|
|
11873
|
-
const parsed = JSON.parse(
|
|
12238
|
+
const parsed = JSON.parse(readFileSync12(join12(dir, "package.json"), "utf8"));
|
|
11874
12239
|
return parsed.name === BMAD_NPM_PACKAGE ? typeof parsed.version === "string" ? parsed.version : "unknown" : void 0;
|
|
11875
12240
|
} catch {
|
|
11876
12241
|
return void 0;
|
|
@@ -11902,12 +12267,13 @@ function resolveBmadInstallerPackage(version = BMAD_INSTALLER_VERSION) {
|
|
|
11902
12267
|
`node -e "process.stdout.write(require('fs').realpathSync(process.argv[1]))" "$(command -v ${BMAD_NPM_PACKAGE})"`
|
|
11903
12268
|
], {
|
|
11904
12269
|
encoding: "utf8",
|
|
11905
|
-
timeout:
|
|
12270
|
+
timeout: timeoutMs,
|
|
11906
12271
|
env: { ...process.env, npm_config_update_notifier: "false", npm_config_fund: "false" }
|
|
11907
12272
|
});
|
|
11908
12273
|
const entry = probe.stdout?.trim().split("\n").pop()?.trim();
|
|
11909
12274
|
if (probe.status !== 0 || !entry) {
|
|
11910
|
-
const
|
|
12275
|
+
const timedOut = probe.error?.code === "ETIMEDOUT";
|
|
12276
|
+
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(" ");
|
|
11911
12277
|
return { reason: `could not resolve ${BMAD_NPM_PACKAGE}@${version}: ${detail || "no installer path reported"}` };
|
|
11912
12278
|
}
|
|
11913
12279
|
const root = dirname9(dirname9(dirname9(entry)));
|
|
@@ -11915,6 +12281,34 @@ function resolveBmadInstallerPackage(version = BMAD_INSTALLER_VERSION) {
|
|
|
11915
12281
|
if (!found) return { reason: `${entry} is not inside a ${BMAD_NPM_PACKAGE} package` };
|
|
11916
12282
|
return { root, entry, version: found };
|
|
11917
12283
|
}
|
|
12284
|
+
var BMAD_SETUP_SCRIPTS_PROBE_TIMEOUT_MS = 1e4;
|
|
12285
|
+
var BMAD_SETUP_SCRIPTS_REMEDY = "re-run the BMAD installer (npx bmad-method install) or upgrade via `pj migrate bmad.version`";
|
|
12286
|
+
function bmadSetupScriptsState(repoRoot) {
|
|
12287
|
+
const inspection = inspectBmadSetupScripts(repoRoot, BMAD_INSTALLER_VERSION, () => readInstalledBmadVersion(repoRoot));
|
|
12288
|
+
const decline = (why, missing, remedy2) => ({
|
|
12289
|
+
kind: "decline",
|
|
12290
|
+
summary: `Setup script recovery unavailable: ${why}${missing.length ? ` (missing from _bmad/scripts: ${missing.join(", ")})` : ""}; ${remedy2}`
|
|
12291
|
+
});
|
|
12292
|
+
switch (inspection.state) {
|
|
12293
|
+
case "present":
|
|
12294
|
+
return { kind: "present" };
|
|
12295
|
+
case "unsafe":
|
|
12296
|
+
return { kind: "refuse", error: `BMAD script recovery refused: ${inspection.error}` };
|
|
12297
|
+
case "decline":
|
|
12298
|
+
return decline(inspection.why, inspection.missing, BMAD_SETUP_SCRIPTS_REMEDY);
|
|
12299
|
+
case "missing":
|
|
12300
|
+
break;
|
|
12301
|
+
}
|
|
12302
|
+
const override = Boolean(process.env.PJ_BMAD_INSTALLER?.trim());
|
|
12303
|
+
const remedy = override ? "unset PJ_BMAD_INSTALLER to enable recovery from the pinned package" : `retry when npm is reachable, or ${BMAD_SETUP_SCRIPTS_REMEDY}`;
|
|
12304
|
+
const pkg = resolveBmadInstallerPackage(BMAD_INSTALLER_VERSION, { timeoutMs: BMAD_SETUP_SCRIPTS_PROBE_TIMEOUT_MS });
|
|
12305
|
+
if (!pkg.root) return decline(pkg.reason ?? "installer is not a bmad-method package", inspection.missing, remedy);
|
|
12306
|
+
const plan = planBmadSetupScripts(repoRoot, pkg, BMAD_INSTALLER_VERSION);
|
|
12307
|
+
if (plan.error) {
|
|
12308
|
+
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 };
|
|
12309
|
+
}
|
|
12310
|
+
return plan.files.length ? { kind: "missing", pkg, files: plan.files } : { kind: "present" };
|
|
12311
|
+
}
|
|
11918
12312
|
function bmadInstallerArgs(repoRoot, modules = selectedBmadModules(repoRoot), extraArgs = []) {
|
|
11919
12313
|
const installerModules = modules.length ? modules.join(",") : "core";
|
|
11920
12314
|
return [
|
|
@@ -11933,7 +12327,7 @@ function bmadInstallerArgs(repoRoot, modules = selectedBmadModules(repoRoot), ex
|
|
|
11933
12327
|
}
|
|
11934
12328
|
function bmadUpgradeChannelArgs(repoRoot, version) {
|
|
11935
12329
|
if (!/^\d+\.\d+\.\d+$/.test(version)) return [];
|
|
11936
|
-
const raw = safeReadText2(
|
|
12330
|
+
const raw = safeReadText2(join12(repoRoot, "_bmad", "_config", "manifest.yaml"));
|
|
11937
12331
|
if (!raw) return [];
|
|
11938
12332
|
let modules;
|
|
11939
12333
|
try {
|
|
@@ -12003,7 +12397,7 @@ function bmadChannelTarget(distTags) {
|
|
|
12003
12397
|
return next ? { version: next, channel: BMAD_TARGET_CHANNEL } : void 0;
|
|
12004
12398
|
}
|
|
12005
12399
|
function readInstalledBmadVersion(repoRoot) {
|
|
12006
|
-
const raw = safeReadText2(
|
|
12400
|
+
const raw = safeReadText2(join12(repoRoot, "_bmad", "_config", "manifest.yaml"));
|
|
12007
12401
|
if (!raw) return void 0;
|
|
12008
12402
|
try {
|
|
12009
12403
|
const parsed = YAML4.parse(raw);
|
|
@@ -12014,8 +12408,8 @@ function readInstalledBmadVersion(repoRoot) {
|
|
|
12014
12408
|
}
|
|
12015
12409
|
}
|
|
12016
12410
|
function bmadCachePath(homeDir) {
|
|
12017
|
-
const cacheRoot = process.env.XDG_CACHE_HOME?.trim() ||
|
|
12018
|
-
return
|
|
12411
|
+
const cacheRoot = process.env.XDG_CACHE_HOME?.trim() || join12(homeDir, ".cache");
|
|
12412
|
+
return join12(cacheRoot, "pjangler", "bmad-dist-tags.json");
|
|
12019
12413
|
}
|
|
12020
12414
|
function readBmadDistTagsCache(homeDir) {
|
|
12021
12415
|
const raw = safeReadText2(bmadCachePath(homeDir));
|
|
@@ -12057,8 +12451,8 @@ function resolveBmadDistTags(homeDir) {
|
|
|
12057
12451
|
if (fetched) {
|
|
12058
12452
|
try {
|
|
12059
12453
|
const path = bmadCachePath(homeDir);
|
|
12060
|
-
|
|
12061
|
-
|
|
12454
|
+
mkdirSync7(dirname9(path), { recursive: true });
|
|
12455
|
+
writeFileSync9(path, JSON.stringify({ fetchedAt: Date.now(), distTags: fetched }, null, 2));
|
|
12062
12456
|
} catch {
|
|
12063
12457
|
}
|
|
12064
12458
|
return { distTags: fetched, stale: false };
|
|
@@ -12101,27 +12495,27 @@ function compareBmadVersions(a, b) {
|
|
|
12101
12495
|
function discoverMomoProviderCandidates(repoRoot) {
|
|
12102
12496
|
const candidates = [];
|
|
12103
12497
|
const roleDirs = [];
|
|
12104
|
-
const hermesDir =
|
|
12498
|
+
const hermesDir = join12(repoRoot, "agents", "hermes");
|
|
12105
12499
|
if (existsSync8(hermesDir)) {
|
|
12106
|
-
for (const entry of
|
|
12107
|
-
if (entry.isDirectory()) roleDirs.push(
|
|
12500
|
+
for (const entry of readdirSync8(hermesDir, { withFileTypes: true })) {
|
|
12501
|
+
if (entry.isDirectory()) roleDirs.push(join12(hermesDir, entry.name));
|
|
12108
12502
|
}
|
|
12109
12503
|
}
|
|
12110
12504
|
for (const roleDir of roleDirs) {
|
|
12111
12505
|
for (const name of ["momo", "provider", "momo-provider"]) {
|
|
12112
|
-
const path =
|
|
12506
|
+
const path = join12(roleDir, name);
|
|
12113
12507
|
if (existsSync8(path)) {
|
|
12114
12508
|
const kind = path.endsWith(".py") ? "python" : "shell";
|
|
12115
12509
|
candidates.push({ path, kind });
|
|
12116
12510
|
}
|
|
12117
12511
|
}
|
|
12118
12512
|
if (existsSync8(roleDir)) {
|
|
12119
|
-
for (const entry of
|
|
12513
|
+
for (const entry of readdirSync8(roleDir, { withFileTypes: true })) {
|
|
12120
12514
|
if (!entry.isFile()) continue;
|
|
12121
12515
|
if (!entry.name.startsWith("momo")) continue;
|
|
12122
|
-
const path =
|
|
12516
|
+
const path = join12(roleDir, entry.name);
|
|
12123
12517
|
try {
|
|
12124
|
-
if (
|
|
12518
|
+
if (lstatSync10(path).mode & 73) {
|
|
12125
12519
|
const kind = entry.name.endsWith(".py") ? "python" : "shell";
|
|
12126
12520
|
if (!candidates.some((c) => c.path === path)) candidates.push({ path, kind });
|
|
12127
12521
|
}
|
|
@@ -12131,7 +12525,7 @@ function discoverMomoProviderCandidates(repoRoot) {
|
|
|
12131
12525
|
}
|
|
12132
12526
|
}
|
|
12133
12527
|
for (const rel of [".mise/scripts/momo-provider.sh", ".scripts/momo-provider.sh", "momo"]) {
|
|
12134
|
-
const path =
|
|
12528
|
+
const path = join12(repoRoot, rel);
|
|
12135
12529
|
if (existsSync8(path)) {
|
|
12136
12530
|
const kind = path.endsWith(".py") ? "python" : "shell";
|
|
12137
12531
|
if (!candidates.some((c) => c.path === path)) candidates.push({ path, kind });
|
|
@@ -12167,7 +12561,7 @@ function runProviderLocalSmoke(repoRoot, candidate) {
|
|
|
12167
12561
|
return { ok: true };
|
|
12168
12562
|
}
|
|
12169
12563
|
function attemptPlaneStateMapping(repoRoot) {
|
|
12170
|
-
const project = tryParseJson(safeReadText2(
|
|
12564
|
+
const project = tryParseJson(safeReadText2(join12(repoRoot, ".project.json")));
|
|
12171
12565
|
const tp = project?.ticket_provider ?? {};
|
|
12172
12566
|
if (!tp.board_id) return { ok: false, detail: "ticket_provider.board_id missing; cannot map Plane states" };
|
|
12173
12567
|
return { ok: false, detail: `Plane state mapping attempted for board ${tp.board_id} (credentials required for full mapping)` };
|
|
@@ -12184,7 +12578,7 @@ function momoLifecycleFinding(section2, status, summary, details = []) {
|
|
|
12184
12578
|
}
|
|
12185
12579
|
function auditManifestRoleConsistency(repoRoot) {
|
|
12186
12580
|
const details = [];
|
|
12187
|
-
const projectPath =
|
|
12581
|
+
const projectPath = join12(repoRoot, ".project.json");
|
|
12188
12582
|
if (!existsSync8(projectPath)) {
|
|
12189
12583
|
return momoLifecycleFinding("manifest-role-consistency", "fail", ".project.json missing", [".project.json missing"]);
|
|
12190
12584
|
}
|
|
@@ -12206,7 +12600,7 @@ function auditManifestRoleConsistency(repoRoot) {
|
|
|
12206
12600
|
details.push(`agents.${agentId}.role_dir does not exist: ${agent.role_dir}`);
|
|
12207
12601
|
continue;
|
|
12208
12602
|
}
|
|
12209
|
-
const roleYaml =
|
|
12603
|
+
const roleYaml = join12(roleDir, "role.yaml");
|
|
12210
12604
|
if (!existsSync8(roleYaml)) {
|
|
12211
12605
|
details.push(`agents.${agentId} role.yaml missing at ${agent.role_dir}/role.yaml`);
|
|
12212
12606
|
continue;
|
|
@@ -12249,26 +12643,26 @@ function hasAnyLifecycleScript(repoRoot) {
|
|
|
12249
12643
|
for (const pattern of patterns) {
|
|
12250
12644
|
if (pattern.includes("*")) {
|
|
12251
12645
|
const [prefix, suffix] = pattern.split("*");
|
|
12252
|
-
const base =
|
|
12646
|
+
const base = join12(repoRoot, prefix);
|
|
12253
12647
|
if (!existsSync8(base)) continue;
|
|
12254
|
-
for (const entry of
|
|
12648
|
+
for (const entry of readdirSync8(base, { withFileTypes: true })) {
|
|
12255
12649
|
if (!entry.isDirectory()) continue;
|
|
12256
|
-
const candidate =
|
|
12650
|
+
const candidate = join12(base, entry.name, suffix);
|
|
12257
12651
|
if (existsSync8(candidate)) return true;
|
|
12258
12652
|
const parent = dirname9(candidate);
|
|
12259
|
-
const stem =
|
|
12653
|
+
const stem = basename5(candidate);
|
|
12260
12654
|
if (!existsSync8(parent)) continue;
|
|
12261
|
-
for (const sibling of
|
|
12655
|
+
for (const sibling of readdirSync8(parent, { withFileTypes: true })) {
|
|
12262
12656
|
if (sibling.isFile() && sibling.name.startsWith(stem)) return true;
|
|
12263
12657
|
}
|
|
12264
12658
|
}
|
|
12265
12659
|
} else {
|
|
12266
|
-
const base =
|
|
12660
|
+
const base = join12(repoRoot, pattern);
|
|
12267
12661
|
if (existsSync8(base)) return true;
|
|
12268
12662
|
const parent = dirname9(base);
|
|
12269
|
-
const prefix =
|
|
12663
|
+
const prefix = basename5(base);
|
|
12270
12664
|
if (existsSync8(parent)) {
|
|
12271
|
-
for (const entry of
|
|
12665
|
+
for (const entry of readdirSync8(parent, { withFileTypes: true })) {
|
|
12272
12666
|
if (entry.name.startsWith(prefix)) return true;
|
|
12273
12667
|
}
|
|
12274
12668
|
}
|
|
@@ -12298,26 +12692,26 @@ function hasAnySentinelScript(repoRoot) {
|
|
|
12298
12692
|
for (const pattern of patterns) {
|
|
12299
12693
|
if (pattern.includes("*")) {
|
|
12300
12694
|
const [prefix, suffix] = pattern.split("*");
|
|
12301
|
-
const base =
|
|
12695
|
+
const base = join12(repoRoot, prefix);
|
|
12302
12696
|
if (!existsSync8(base)) continue;
|
|
12303
|
-
for (const entry of
|
|
12697
|
+
for (const entry of readdirSync8(base, { withFileTypes: true })) {
|
|
12304
12698
|
if (!entry.isDirectory()) continue;
|
|
12305
|
-
const candidate =
|
|
12699
|
+
const candidate = join12(base, entry.name, suffix);
|
|
12306
12700
|
if (existsSync8(candidate)) return true;
|
|
12307
12701
|
const parent = dirname9(candidate);
|
|
12308
|
-
const stem =
|
|
12702
|
+
const stem = basename5(candidate);
|
|
12309
12703
|
if (!existsSync8(parent)) continue;
|
|
12310
|
-
for (const sibling of
|
|
12704
|
+
for (const sibling of readdirSync8(parent, { withFileTypes: true })) {
|
|
12311
12705
|
if (sibling.isFile() && sibling.name.startsWith(stem)) return true;
|
|
12312
12706
|
}
|
|
12313
12707
|
}
|
|
12314
12708
|
} else {
|
|
12315
|
-
const base =
|
|
12709
|
+
const base = join12(repoRoot, pattern);
|
|
12316
12710
|
if (existsSync8(base)) return true;
|
|
12317
12711
|
const parent = dirname9(base);
|
|
12318
|
-
const prefix =
|
|
12712
|
+
const prefix = basename5(base);
|
|
12319
12713
|
if (existsSync8(parent)) {
|
|
12320
|
-
for (const entry of
|
|
12714
|
+
for (const entry of readdirSync8(parent, { withFileTypes: true })) {
|
|
12321
12715
|
if (entry.name.startsWith(prefix)) return true;
|
|
12322
12716
|
}
|
|
12323
12717
|
}
|
|
@@ -12362,7 +12756,7 @@ function auditProviderSyntax(repoRoot) {
|
|
|
12362
12756
|
}
|
|
12363
12757
|
function auditPlaneBinding(repoRoot) {
|
|
12364
12758
|
const details = [];
|
|
12365
|
-
const project = tryParseJson(safeReadText2(
|
|
12759
|
+
const project = tryParseJson(safeReadText2(join12(repoRoot, ".project.json")));
|
|
12366
12760
|
const tp = project?.ticket_provider ?? {};
|
|
12367
12761
|
for (const key of ["type", "workspace", "identifier", "board_id"]) {
|
|
12368
12762
|
if (!tp[key]) details.push(`ticket_provider.${key} missing`);
|
|
@@ -12534,8 +12928,8 @@ var UNSUPPORTED_BMAD_ROOTS = {
|
|
|
12534
12928
|
".zencoder": "zencoder"
|
|
12535
12929
|
};
|
|
12536
12930
|
function unsupportedRootAttestation(repoRoot, rootName) {
|
|
12537
|
-
const root =
|
|
12538
|
-
const stat =
|
|
12931
|
+
const root = join12(repoRoot, rootName);
|
|
12932
|
+
const stat = lstatSync10(root);
|
|
12539
12933
|
if (!stat.isDirectory() || stat.isSymbolicLink()) return { safe: false, reason: `${rootName} is not a regular generated directory` };
|
|
12540
12934
|
const installerTool = UNSUPPORTED_BMAD_ROOTS[rootName];
|
|
12541
12935
|
if (!installedBmadTools(repoRoot).has(installerTool)) {
|
|
@@ -12554,7 +12948,7 @@ var OPTIONAL_TEMPLATE_SCRIPTS = [
|
|
|
12554
12948
|
function optionalTemplateScriptIssues(ctx) {
|
|
12555
12949
|
const issues = [];
|
|
12556
12950
|
for (const rel of OPTIONAL_TEMPLATE_SCRIPTS) {
|
|
12557
|
-
const path =
|
|
12951
|
+
const path = join12(ctx.repoRoot, rel);
|
|
12558
12952
|
if (!existsSync8(path)) continue;
|
|
12559
12953
|
const expected = templateCommonProjectText(ctx, rel);
|
|
12560
12954
|
if (expected === void 0) continue;
|
|
@@ -12565,7 +12959,7 @@ function optionalTemplateScriptIssues(ctx) {
|
|
|
12565
12959
|
function refreshOptionalTemplateScripts(ctx) {
|
|
12566
12960
|
const changed = [];
|
|
12567
12961
|
for (const rel of OPTIONAL_TEMPLATE_SCRIPTS) {
|
|
12568
|
-
const path =
|
|
12962
|
+
const path = join12(ctx.repoRoot, rel);
|
|
12569
12963
|
if (!existsSync8(path)) continue;
|
|
12570
12964
|
const expected = templateCommonProjectText(ctx, rel);
|
|
12571
12965
|
if (expected === void 0 || safeReadText2(path) === expected) continue;
|
|
@@ -12583,13 +12977,13 @@ function createMiseChecks() {
|
|
|
12583
12977
|
id: "mise.config-root",
|
|
12584
12978
|
title: "mise config_root + AGENTS link hooks",
|
|
12585
12979
|
audit: (ctx) => {
|
|
12586
|
-
const misePath =
|
|
12980
|
+
const misePath = join12(ctx.repoRoot, "mise.toml");
|
|
12587
12981
|
if (!existsSync8(misePath)) {
|
|
12588
12982
|
return { id: "mise.config-root", title: "mise config_root + AGENTS link hooks", status: "fail", summary: "mise.toml missing", details: [], fixable: true };
|
|
12589
12983
|
}
|
|
12590
12984
|
const text2 = readText(misePath);
|
|
12591
12985
|
const details = [];
|
|
12592
|
-
const linkAgentfilesPath =
|
|
12986
|
+
const linkAgentfilesPath = join12(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
|
|
12593
12987
|
if (!existsSync8(linkAgentfilesPath)) details.push(".mise/scripts/link-agentfiles.sh missing");
|
|
12594
12988
|
const toml = [];
|
|
12595
12989
|
const manual = [];
|
|
@@ -12637,7 +13031,7 @@ function createMiseChecks() {
|
|
|
12637
13031
|
};
|
|
12638
13032
|
},
|
|
12639
13033
|
migrate: (ctx, finding2) => {
|
|
12640
|
-
const path =
|
|
13034
|
+
const path = join12(ctx.repoRoot, "mise.toml");
|
|
12641
13035
|
const changedFiles = [];
|
|
12642
13036
|
const details = [];
|
|
12643
13037
|
if (!existsSync8(path)) {
|
|
@@ -12656,7 +13050,7 @@ function createMiseChecks() {
|
|
|
12656
13050
|
if (!ctx.dryRun) writeText(path, plan.text);
|
|
12657
13051
|
text2 = plan.text;
|
|
12658
13052
|
}
|
|
12659
|
-
const linkAgentfilesPath =
|
|
13053
|
+
const linkAgentfilesPath = join12(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
|
|
12660
13054
|
const expectedScript = templateLinkAgentfilesScript(ctx);
|
|
12661
13055
|
if (expectedScript === void 0) {
|
|
12662
13056
|
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: [] };
|
|
@@ -12694,9 +13088,9 @@ function createMiseChecks() {
|
|
|
12694
13088
|
title: "managed mise versioning block",
|
|
12695
13089
|
audit: (ctx) => {
|
|
12696
13090
|
const details = [];
|
|
12697
|
-
const misePath =
|
|
12698
|
-
const versioningPath =
|
|
12699
|
-
const manifestPath =
|
|
13091
|
+
const misePath = join12(ctx.repoRoot, "mise.toml");
|
|
13092
|
+
const versioningPath = join12(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
|
|
13093
|
+
const manifestPath = join12(ctx.repoRoot, ".mise", "version-files.conf");
|
|
12700
13094
|
const text2 = safeReadText2(misePath);
|
|
12701
13095
|
if (!text2?.includes("# >>> mise-versioning >>>")) details.push("mise versioning managed block missing");
|
|
12702
13096
|
if (!existsSync8(versioningPath)) details.push(".mise/scripts/versioning.sh missing");
|
|
@@ -12722,7 +13116,7 @@ function createMiseChecks() {
|
|
|
12722
13116
|
migrate: (ctx, finding2) => {
|
|
12723
13117
|
const changedFiles = [];
|
|
12724
13118
|
const details = [];
|
|
12725
|
-
const misePath =
|
|
13119
|
+
const misePath = join12(ctx.repoRoot, "mise.toml");
|
|
12726
13120
|
if (!existsSync8(misePath)) {
|
|
12727
13121
|
if (ensureMiseTomlFromTemplate(ctx, changedFiles) === null) {
|
|
12728
13122
|
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: [] };
|
|
@@ -12740,7 +13134,7 @@ function createMiseChecks() {
|
|
|
12740
13134
|
if (!changedFiles.includes(misePath)) changedFiles.push(misePath);
|
|
12741
13135
|
if (!ctx.dryRun) writeText(misePath, versioningPlan.text);
|
|
12742
13136
|
}
|
|
12743
|
-
const versioningPath =
|
|
13137
|
+
const versioningPath = join12(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
|
|
12744
13138
|
const expectedScript = templateVersioningScript(ctx);
|
|
12745
13139
|
if (expectedScript === void 0) {
|
|
12746
13140
|
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: [] };
|
|
@@ -12752,7 +13146,7 @@ function createMiseChecks() {
|
|
|
12752
13146
|
chmodSync3(versioningPath, 493);
|
|
12753
13147
|
}
|
|
12754
13148
|
}
|
|
12755
|
-
const manifestPath =
|
|
13149
|
+
const manifestPath = join12(ctx.repoRoot, ".mise", "version-files.conf");
|
|
12756
13150
|
const expectedManifest = templateVersionFilesConf(ctx, ctx.repoRoot);
|
|
12757
13151
|
if (safeReadText2(manifestPath) !== expectedManifest) {
|
|
12758
13152
|
changedFiles.push(manifestPath);
|
|
@@ -12814,7 +13208,7 @@ var RETIRED_SKILL_SCRIPTS = [
|
|
|
12814
13208
|
".mise/scripts/unlink-project-skills-from-clis.sh"
|
|
12815
13209
|
];
|
|
12816
13210
|
function retiredSkillsScripts(ctx) {
|
|
12817
|
-
return RETIRED_SKILL_SCRIPTS.map((path) =>
|
|
13211
|
+
return RETIRED_SKILL_SCRIPTS.map((path) => join12(ctx.repoRoot, path)).filter((path) => Boolean(lstatIfPresent(path)));
|
|
12818
13212
|
}
|
|
12819
13213
|
function skillsWiringFindings(text2) {
|
|
12820
13214
|
if (text2 === null) return { fixable: [], manual: [] };
|
|
@@ -12865,7 +13259,7 @@ function createAgentHooksChecks() {
|
|
|
12865
13259
|
title: "Skillex project skills",
|
|
12866
13260
|
audit: async (ctx) => {
|
|
12867
13261
|
const finding2 = await auditProjectSkills(ctx);
|
|
12868
|
-
const mise = safeReadText2(
|
|
13262
|
+
const mise = safeReadText2(join12(ctx.repoRoot, "mise.toml"));
|
|
12869
13263
|
const registryField = await legacyRegistryField(ctx);
|
|
12870
13264
|
const registryDrop = registryField ? await dropLegacyRegistryField({ ...ctx, dryRun: true }) : void 0;
|
|
12871
13265
|
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`] : []];
|
|
@@ -12886,7 +13280,7 @@ function createAgentHooksChecks() {
|
|
|
12886
13280
|
const changedFiles = [];
|
|
12887
13281
|
const details = [];
|
|
12888
13282
|
const result2 = (status, summary) => ({ id: finding2.id, title: finding2.title, status, summary, changedFiles: [...new Set(changedFiles)], details });
|
|
12889
|
-
let currentMise = safeReadText2(
|
|
13283
|
+
let currentMise = safeReadText2(join12(ctx.repoRoot, "mise.toml"));
|
|
12890
13284
|
if (currentMise === null) {
|
|
12891
13285
|
const template = templateCommonProjectText(ctx, "mise.toml.jinja");
|
|
12892
13286
|
if (template === void 0) return result2("blocked", "Generated-project mise template is unavailable");
|
|
@@ -12901,9 +13295,9 @@ function createAgentHooksChecks() {
|
|
|
12901
13295
|
const stat = lstatIfPresent(path);
|
|
12902
13296
|
if (!stat || stat.isDirectory()) continue;
|
|
12903
13297
|
changedFiles.push(path);
|
|
12904
|
-
if (!ctx.dryRun)
|
|
13298
|
+
if (!ctx.dryRun) unlinkSync6(path);
|
|
12905
13299
|
}
|
|
12906
|
-
const misePath =
|
|
13300
|
+
const misePath = join12(ctx.repoRoot, "mise.toml");
|
|
12907
13301
|
const nextMise = upsertLinkAgentfilesBlock(currentMise, ctx);
|
|
12908
13302
|
if (safeReadText2(misePath) !== nextMise) {
|
|
12909
13303
|
changedFiles.push(misePath);
|
|
@@ -12934,9 +13328,9 @@ function createAgentHooksChecks() {
|
|
|
12934
13328
|
id: "sot.agent-symlinks",
|
|
12935
13329
|
title: "AGENTS/CLAUDE/GEMINI symlink contract",
|
|
12936
13330
|
audit: (ctx) => {
|
|
12937
|
-
const agentsPath =
|
|
13331
|
+
const agentsPath = join12(ctx.repoRoot, "AGENTS.md");
|
|
12938
13332
|
if (!existsSync8(agentsPath)) {
|
|
12939
|
-
const fallbackSources = ["CLAUDE.md", "GEMINI.md", "README.md"].filter((file) => existsSync8(
|
|
13333
|
+
const fallbackSources = ["CLAUDE.md", "GEMINI.md", "README.md"].filter((file) => existsSync8(join12(ctx.repoRoot, file)));
|
|
12940
13334
|
if (fallbackSources.length === 0) {
|
|
12941
13335
|
return { id: "sot.agent-symlinks", title: "AGENTS/CLAUDE/GEMINI symlink contract", status: "skip", summary: "AGENTS.md missing; symlink contract not applicable", details: [], fixable: false };
|
|
12942
13336
|
}
|
|
@@ -12951,7 +13345,7 @@ function createAgentHooksChecks() {
|
|
|
12951
13345
|
}
|
|
12952
13346
|
const details = [];
|
|
12953
13347
|
for (const file of ["CLAUDE.md", "GEMINI.md"]) {
|
|
12954
|
-
const full =
|
|
13348
|
+
const full = join12(ctx.repoRoot, file);
|
|
12955
13349
|
const target = readSymlinkTarget(full);
|
|
12956
13350
|
if (target !== "AGENTS.md") details.push(`${file} should be a symlink to AGENTS.md`);
|
|
12957
13351
|
}
|
|
@@ -12975,7 +13369,7 @@ function createAgentHooksChecks() {
|
|
|
12975
13369
|
return { id: finding2.id, title: finding2.title, status: "blocked", summary: "AGENTS.md missing; cannot derive canonical agent file", changedFiles, details: [bootstrap.blocked] };
|
|
12976
13370
|
}
|
|
12977
13371
|
for (const file of ["CLAUDE.md", "GEMINI.md"]) {
|
|
12978
|
-
const full =
|
|
13372
|
+
const full = join12(ctx.repoRoot, file);
|
|
12979
13373
|
const result2 = ensureSymlink(full, "AGENTS.md", ctx.dryRun);
|
|
12980
13374
|
if (result2.blocked) blockedDetails.push(result2.blocked);
|
|
12981
13375
|
if (result2.changed) changedFiles.push(full);
|
|
@@ -13002,7 +13396,7 @@ function createProjectJsonChecks() {
|
|
|
13002
13396
|
const changedFiles = [];
|
|
13003
13397
|
const blockedDetails = [];
|
|
13004
13398
|
const droppedDetails = [];
|
|
13005
|
-
const path =
|
|
13399
|
+
const path = join12(ctx.repoRoot, ".project.json");
|
|
13006
13400
|
const existing = readProjectJson(ctx) ?? {};
|
|
13007
13401
|
const canonical = canonicalProjectJson(ctx);
|
|
13008
13402
|
const preservedDetails = [];
|
|
@@ -13034,7 +13428,7 @@ function createProjectJsonChecks() {
|
|
|
13034
13428
|
changedFiles.push(path);
|
|
13035
13429
|
if (!ctx.dryRun) writeText(path, expected);
|
|
13036
13430
|
}
|
|
13037
|
-
const planeJson =
|
|
13431
|
+
const planeJson = join12(ctx.repoRoot, ".plane.json");
|
|
13038
13432
|
if (existsSync8(planeJson)) {
|
|
13039
13433
|
const backup = `${planeJson}.migrated-backup`;
|
|
13040
13434
|
if (existsSync8(backup)) {
|
|
@@ -13066,10 +13460,10 @@ function createMiseOpInjectChecks() {
|
|
|
13066
13460
|
const details = [];
|
|
13067
13461
|
let envOpNeedsHands = false;
|
|
13068
13462
|
const optedOut = envMaterializationOptedOut(ctx);
|
|
13069
|
-
const envOpPath =
|
|
13463
|
+
const envOpPath = join12(ctx.repoRoot, ".env.op");
|
|
13070
13464
|
const envOpExists = existsSync8(envOpPath);
|
|
13071
13465
|
const envOp = envOpExists ? readText(envOpPath) : void 0;
|
|
13072
|
-
const gitignore = safeReadText2(
|
|
13466
|
+
const gitignore = safeReadText2(join12(ctx.repoRoot, ".gitignore"));
|
|
13073
13467
|
if (!envOpExists) {
|
|
13074
13468
|
details.push(".env.op missing");
|
|
13075
13469
|
} else if (!envOp?.trim()) {
|
|
@@ -13089,7 +13483,7 @@ function createMiseOpInjectChecks() {
|
|
|
13089
13483
|
if (!gitignore?.includes(".env\n") && !gitignore?.includes(".env\r\n")) details.push(".gitignore should ignore .env");
|
|
13090
13484
|
if (!gitignore?.includes(".env.*")) details.push(".gitignore should ignore .env.*");
|
|
13091
13485
|
if (!gitignore?.includes("!.env.op")) details.push(".gitignore should unignore .env.op");
|
|
13092
|
-
const misePath =
|
|
13486
|
+
const misePath = join12(ctx.repoRoot, "mise.toml");
|
|
13093
13487
|
const miseText = safeReadText2(misePath);
|
|
13094
13488
|
if (!miseText) {
|
|
13095
13489
|
details.push("mise.toml missing for .env materialization hook");
|
|
@@ -13112,7 +13506,7 @@ function createMiseOpInjectChecks() {
|
|
|
13112
13506
|
else if (plan.text !== miseText && !mise.length) mise.push(describeRewrite(miseText, plan.text));
|
|
13113
13507
|
details.push(...mise);
|
|
13114
13508
|
}
|
|
13115
|
-
const materializePath =
|
|
13509
|
+
const materializePath = join12(ctx.repoRoot, MATERIALIZE_ENV_SCRIPT_REL);
|
|
13116
13510
|
const expectedMaterializer = templateMaterializeEnvScript(ctx);
|
|
13117
13511
|
if (optedOut) {
|
|
13118
13512
|
if (existsSync8(materializePath)) details.push(`${MATERIALIZE_ENV_SCRIPT_REL} is present, but .project.json declares secrets.materialize_env: false`);
|
|
@@ -13120,7 +13514,7 @@ function createMiseOpInjectChecks() {
|
|
|
13120
13514
|
details.push("pjangler package is missing the managed materialize-env.sh source");
|
|
13121
13515
|
} else if (safeReadText2(materializePath) !== expectedMaterializer) {
|
|
13122
13516
|
details.push(`${MATERIALIZE_ENV_SCRIPT_REL} missing or drifted`);
|
|
13123
|
-
} else if ((
|
|
13517
|
+
} else if ((lstatSync10(materializePath).mode & 73) === 0) {
|
|
13124
13518
|
details.push(`${MATERIALIZE_ENV_SCRIPT_REL} is not executable`);
|
|
13125
13519
|
}
|
|
13126
13520
|
return {
|
|
@@ -13136,8 +13530,8 @@ function createMiseOpInjectChecks() {
|
|
|
13136
13530
|
const changedFiles = [];
|
|
13137
13531
|
const details = [];
|
|
13138
13532
|
let envOpNeedsHands = false;
|
|
13139
|
-
const envOpPath =
|
|
13140
|
-
const canonicalEnvOpPath =
|
|
13533
|
+
const envOpPath = join12(ctx.repoRoot, ".env.op");
|
|
13534
|
+
const canonicalEnvOpPath = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", ".env.op");
|
|
13141
13535
|
if (!existsSync8(canonicalEnvOpPath)) {
|
|
13142
13536
|
return { id: finding2.id, title: finding2.title, status: "blocked", summary: "pjangler package is missing the neutral .env.op template", changedFiles: [], details: [] };
|
|
13143
13537
|
}
|
|
@@ -13166,7 +13560,7 @@ function createMiseOpInjectChecks() {
|
|
|
13166
13560
|
}
|
|
13167
13561
|
}
|
|
13168
13562
|
}
|
|
13169
|
-
const gitignorePath =
|
|
13563
|
+
const gitignorePath = join12(ctx.repoRoot, ".gitignore");
|
|
13170
13564
|
const gitignore = safeReadText2(gitignorePath) ?? "";
|
|
13171
13565
|
const requiredBlock = `# Secrets \u2014 .env is materialized from .env.op by \`op inject\` on mise enter,
|
|
13172
13566
|
# staged through a mktemp file and moved into place only on success.
|
|
@@ -13179,7 +13573,7 @@ function createMiseOpInjectChecks() {
|
|
|
13179
13573
|
changedFiles.push(gitignorePath);
|
|
13180
13574
|
if (!ctx.dryRun) writeText(gitignorePath, `${gitignore.replace(/\s*$/, "")}${gitignore.trim() ? "\n\n" : ""}${requiredBlock}`);
|
|
13181
13575
|
}
|
|
13182
|
-
const misePath =
|
|
13576
|
+
const misePath = join12(ctx.repoRoot, "mise.toml");
|
|
13183
13577
|
let currentMise = safeReadText2(misePath);
|
|
13184
13578
|
if (currentMise === null) {
|
|
13185
13579
|
const initialized = ensureMiseTomlFromTemplate(ctx, changedFiles);
|
|
@@ -13196,7 +13590,7 @@ function createMiseOpInjectChecks() {
|
|
|
13196
13590
|
if (!changedFiles.includes(misePath)) changedFiles.push(misePath);
|
|
13197
13591
|
if (!ctx.dryRun) writeText(misePath, opPlan.text);
|
|
13198
13592
|
}
|
|
13199
|
-
const materializePath =
|
|
13593
|
+
const materializePath = join12(ctx.repoRoot, MATERIALIZE_ENV_SCRIPT_REL);
|
|
13200
13594
|
const expectedMaterializer = templateMaterializeEnvScript(ctx);
|
|
13201
13595
|
if (optedOut) {
|
|
13202
13596
|
if (existsSync8(materializePath)) {
|
|
@@ -13205,7 +13599,7 @@ function createMiseOpInjectChecks() {
|
|
|
13205
13599
|
}
|
|
13206
13600
|
} else if (!expectedMaterializer) {
|
|
13207
13601
|
return { id: finding2.id, title: finding2.title, status: "blocked", summary: "pjangler package is missing materialize-env.sh", changedFiles: [], details: [] };
|
|
13208
|
-
} else if (safeReadText2(materializePath) !== expectedMaterializer || existsSync8(materializePath) && (
|
|
13602
|
+
} else if (safeReadText2(materializePath) !== expectedMaterializer || existsSync8(materializePath) && (lstatSync10(materializePath).mode & 73) === 0) {
|
|
13209
13603
|
changedFiles.push(materializePath);
|
|
13210
13604
|
if (!ctx.dryRun) {
|
|
13211
13605
|
writeText(materializePath, expectedMaterializer);
|
|
@@ -13267,7 +13661,7 @@ function createProjectProvenanceChecks() {
|
|
|
13267
13661
|
title: ".copier-answers.yml provenance + drift report",
|
|
13268
13662
|
audit: (ctx) => {
|
|
13269
13663
|
const details = [];
|
|
13270
|
-
const path =
|
|
13664
|
+
const path = join12(ctx.repoRoot, ".copier-answers.yml");
|
|
13271
13665
|
const text2 = safeReadText2(path);
|
|
13272
13666
|
const project = readProjectJson(ctx);
|
|
13273
13667
|
if (!text2) {
|
|
@@ -13295,11 +13689,11 @@ function createProjectProvenanceChecks() {
|
|
|
13295
13689
|
migrate: (ctx, finding2) => {
|
|
13296
13690
|
const changedFiles = [];
|
|
13297
13691
|
const project = canonicalProjectJson(ctx);
|
|
13298
|
-
const path =
|
|
13692
|
+
const path = join12(ctx.repoRoot, ".copier-answers.yml");
|
|
13299
13693
|
const current = safeReadText2(path);
|
|
13300
13694
|
const existing = current ? YAML4.parseDocument(current) : void 0;
|
|
13301
13695
|
const document = existing && !existing.errors.length && YAML4.isMap(existing.contents) ? existing : new YAML4.Document({});
|
|
13302
|
-
document.set("_src_path",
|
|
13696
|
+
document.set("_src_path", join12(ctx.pjanglerRoot, "templates", "commonproject"));
|
|
13303
13697
|
document.set("project_description", String(project.project_description));
|
|
13304
13698
|
document.set("project_name", String(project.project_name));
|
|
13305
13699
|
document.set("ticket_provider", String(project.ticket_provider?.type ?? "plane"));
|
|
@@ -13324,9 +13718,9 @@ ${body}`;
|
|
|
13324
13718
|
}
|
|
13325
13719
|
function supportedCliProjectionIssues(repoRoot, plan) {
|
|
13326
13720
|
const issues = [];
|
|
13327
|
-
const managedSkills =
|
|
13721
|
+
const managedSkills = join12(repoRoot, ".agents", "skills");
|
|
13328
13722
|
for (const rootName of SUPPORTED_CLI_ROOTS) {
|
|
13329
|
-
const root =
|
|
13723
|
+
const root = join12(repoRoot, rootName);
|
|
13330
13724
|
const rootStat = lstatIfPresent(root);
|
|
13331
13725
|
if (!rootStat) {
|
|
13332
13726
|
issues.push(`${rootName} missing`);
|
|
@@ -13336,7 +13730,7 @@ function supportedCliProjectionIssues(repoRoot, plan) {
|
|
|
13336
13730
|
issues.push(`${rootName} must be a real configuration directory`);
|
|
13337
13731
|
continue;
|
|
13338
13732
|
}
|
|
13339
|
-
const skills =
|
|
13733
|
+
const skills = join12(root, "skills");
|
|
13340
13734
|
const skillsStat = lstatIfPresent(skills);
|
|
13341
13735
|
if (!skillsStat) {
|
|
13342
13736
|
issues.push(`${rootName}/skills missing`);
|
|
@@ -13373,9 +13767,9 @@ function supportedCliProjectionIssues(repoRoot, plan) {
|
|
|
13373
13767
|
issues.push(`${rootName}/skills is not a directory`);
|
|
13374
13768
|
continue;
|
|
13375
13769
|
}
|
|
13376
|
-
const hasGeneratedSkill = existsSync8(projectedSkills) &&
|
|
13377
|
-
const skillFile =
|
|
13378
|
-
return existsSync8(skillFile) &&
|
|
13770
|
+
const hasGeneratedSkill = existsSync8(projectedSkills) && readdirSync8(projectedSkills).some((name) => {
|
|
13771
|
+
const skillFile = join12(projectedSkills, name, "SKILL.md");
|
|
13772
|
+
return existsSync8(skillFile) && lstatSync10(skillFile).isFile();
|
|
13379
13773
|
});
|
|
13380
13774
|
if (!hasGeneratedSkill) issues.push(`${rootName}/skills contains no BMAD skill configuration`);
|
|
13381
13775
|
}
|
|
@@ -13401,7 +13795,7 @@ var LEGACY_CLI_GITIGNORE_LINES = /* @__PURE__ */ new Set([
|
|
|
13401
13795
|
"# some CLIs get a real directory here and some get a symlink."
|
|
13402
13796
|
]);
|
|
13403
13797
|
function supportedCliGitignoreIssues(repoRoot) {
|
|
13404
|
-
const lines = (safeReadText2(
|
|
13798
|
+
const lines = (safeReadText2(join12(repoRoot, ".gitignore")) ?? "").split(/\r?\n/);
|
|
13405
13799
|
const legacy = lines.filter((line) => LEGACY_CLI_GITIGNORE_LINES.has(line.trim()));
|
|
13406
13800
|
return [
|
|
13407
13801
|
...legacy.length ? [`.gitignore contains ${legacy.length} legacy client-root override line(s); only .agents is canonical`] : [],
|
|
@@ -13409,7 +13803,7 @@ function supportedCliGitignoreIssues(repoRoot) {
|
|
|
13409
13803
|
];
|
|
13410
13804
|
}
|
|
13411
13805
|
function ensureSupportedCliGitignore(ctx) {
|
|
13412
|
-
const path =
|
|
13806
|
+
const path = join12(ctx.repoRoot, ".gitignore");
|
|
13413
13807
|
const current = safeReadText2(path) ?? "";
|
|
13414
13808
|
const kept = current.split(/\r?\n/).filter((line) => {
|
|
13415
13809
|
const trimmed2 = line.trim();
|
|
@@ -13451,6 +13845,24 @@ function withInstallerSkillHandoff(check) {
|
|
|
13451
13845
|
}
|
|
13452
13846
|
};
|
|
13453
13847
|
}
|
|
13848
|
+
function withScaffoldPostcondition(check) {
|
|
13849
|
+
if (check.id !== "bmad.scaffold") return check;
|
|
13850
|
+
return {
|
|
13851
|
+
...check,
|
|
13852
|
+
migrate: async (ctx, finding2) => {
|
|
13853
|
+
const result2 = await check.migrate(ctx, finding2);
|
|
13854
|
+
if (ctx.dryRun || result2.status !== "applied" && result2.status !== "noop") return result2;
|
|
13855
|
+
const after = await check.audit(ctx);
|
|
13856
|
+
if (after.status === "pass" || after.status === "skip") return result2;
|
|
13857
|
+
return {
|
|
13858
|
+
...result2,
|
|
13859
|
+
status: "partial",
|
|
13860
|
+
summary: after.summary,
|
|
13861
|
+
details: [...result2.details, `still failing after the install: ${after.summary}`, ...after.details]
|
|
13862
|
+
};
|
|
13863
|
+
}
|
|
13864
|
+
};
|
|
13865
|
+
}
|
|
13454
13866
|
function createBmadChecks() {
|
|
13455
13867
|
return [
|
|
13456
13868
|
{
|
|
@@ -13468,15 +13880,8 @@ function createBmadChecks() {
|
|
|
13468
13880
|
fixable: false
|
|
13469
13881
|
};
|
|
13470
13882
|
}
|
|
13471
|
-
const targetRoot = join11(ctx.repoRoot, "_bmad");
|
|
13472
13883
|
const selectedModules = manifestSelection.status === "valid" ? manifestSelection.modules : configuredBmadModuleSelection(ctx.repoRoot)?.modules ?? [...DEFAULT_BMAD_MODULES];
|
|
13473
|
-
const
|
|
13474
|
-
const missing = sentinels.filter((file) => !existsSync8(join11(targetRoot, file)));
|
|
13475
|
-
const projectNameIssues = bmadProjectNameIssues(ctx.repoRoot);
|
|
13476
|
-
const details = [
|
|
13477
|
-
...missing.map((file) => `_bmad/${file}`),
|
|
13478
|
-
...projectNameIssues.details
|
|
13479
|
-
];
|
|
13884
|
+
const { details } = bmadScaffoldIssues(ctx.repoRoot, selectedModules);
|
|
13480
13885
|
return {
|
|
13481
13886
|
id: "bmad.scaffold",
|
|
13482
13887
|
title: "BMAD modules/docs scaffold",
|
|
@@ -13515,7 +13920,7 @@ function createBmadChecks() {
|
|
|
13515
13920
|
bmadCompanion: preservation
|
|
13516
13921
|
};
|
|
13517
13922
|
if (ctx.dryRun) {
|
|
13518
|
-
const
|
|
13923
|
+
const issues = bmadScaffoldIssues(ctx.repoRoot, selectedModules);
|
|
13519
13924
|
const companion = installedBmad ? await runG33PreUpdateCompanion(ctx.repoRoot) : await runG33Companion(ctx.repoRoot, true, "bmad-install");
|
|
13520
13925
|
if (installedBmad && ["unavailable", "conflict", "error"].includes(companion.status)) {
|
|
13521
13926
|
return {
|
|
@@ -13528,8 +13933,8 @@ function createBmadChecks() {
|
|
|
13528
13933
|
bmadCompanion: companion
|
|
13529
13934
|
};
|
|
13530
13935
|
}
|
|
13531
|
-
changedFiles.push(...
|
|
13532
|
-
changedFiles.push(...
|
|
13936
|
+
changedFiles.push(...issues.gaps.map((gap) => join12(ctx.repoRoot, "_bmad", gap.path)));
|
|
13937
|
+
changedFiles.push(...issues.nameIssues.paths);
|
|
13533
13938
|
const reservedPreview = installedBmad ? evictReservedModuleEntries(ctx.repoRoot, true) : void 0;
|
|
13534
13939
|
changedFiles.push(...reservedPreview?.changedFiles ?? []);
|
|
13535
13940
|
return {
|
|
@@ -13547,9 +13952,10 @@ function createBmadChecks() {
|
|
|
13547
13952
|
};
|
|
13548
13953
|
}
|
|
13549
13954
|
if (installedBmad && g33CompanionEnabled(ctx.repoRoot)) {
|
|
13955
|
+
const before2 = bmadScaffoldIssues(ctx.repoRoot, selectedModules);
|
|
13550
13956
|
const expectedChangedPaths2 = [
|
|
13551
|
-
...
|
|
13552
|
-
...
|
|
13957
|
+
...before2.gaps.map((gap) => join12(ctx.repoRoot, "_bmad", gap.path)),
|
|
13958
|
+
...before2.nameIssues.paths
|
|
13553
13959
|
];
|
|
13554
13960
|
const pkg = resolveBmadInstallerPackage(BMAD_INSTALLER_VERSION);
|
|
13555
13961
|
const scope = deriveBmadUpdateScope(ctx.repoRoot, { tools: BMAD_INSTALL_TOOLS, installer: pkg, companionSurfaces: G33_OWNED_SURFACES });
|
|
@@ -13590,16 +13996,17 @@ function createBmadChecks() {
|
|
|
13590
13996
|
bmadCompanion: update.companion
|
|
13591
13997
|
};
|
|
13592
13998
|
}
|
|
13999
|
+
const before = bmadScaffoldIssues(ctx.repoRoot, selectedModules);
|
|
13593
14000
|
const expectedChangedPaths = [
|
|
13594
|
-
...
|
|
13595
|
-
...
|
|
14001
|
+
...before.gaps.map((gap) => join12(ctx.repoRoot, "_bmad", gap.path)),
|
|
14002
|
+
...before.nameIssues.paths
|
|
13596
14003
|
];
|
|
13597
14004
|
const evicted = evictLegacyBmadPackState(ctx, changedFiles, await skillActivations(ctx));
|
|
13598
14005
|
const reserved = installedBmad ? evictReservedModuleEntries(ctx.repoRoot, false) : void 0;
|
|
13599
14006
|
if (reserved) evicted.unshift(...reserved.details);
|
|
13600
14007
|
const install = await runBmadInstall(ctx.repoRoot, selectedModules, BMAD_INSTALLER_VERSION, channelArgs);
|
|
13601
14008
|
if (!install.ok && reserved?.original !== void 0) {
|
|
13602
|
-
|
|
14009
|
+
writeFileSync9(reserved.changedFiles[0], reserved.original, "utf8");
|
|
13603
14010
|
evicted.push("restored _bmad/_config/manifest.yaml after the failed install");
|
|
13604
14011
|
}
|
|
13605
14012
|
if (!install.ok) {
|
|
@@ -13624,6 +14031,58 @@ function createBmadChecks() {
|
|
|
13624
14031
|
};
|
|
13625
14032
|
}
|
|
13626
14033
|
},
|
|
14034
|
+
{
|
|
14035
|
+
id: "bmad.setup-scripts",
|
|
14036
|
+
title: "BMAD ignored installer setup scripts",
|
|
14037
|
+
audit: (ctx) => {
|
|
14038
|
+
if (!isInstalledBmadProject(ctx.repoRoot)) return {
|
|
14039
|
+
id: "bmad.setup-scripts",
|
|
14040
|
+
title: "BMAD ignored installer setup scripts",
|
|
14041
|
+
status: "skip",
|
|
14042
|
+
summary: "No installed BMAD project",
|
|
14043
|
+
details: [],
|
|
14044
|
+
fixable: false
|
|
14045
|
+
};
|
|
14046
|
+
const state = bmadSetupScriptsState(ctx.repoRoot);
|
|
14047
|
+
const id = "bmad.setup-scripts", title = "BMAD ignored installer setup scripts";
|
|
14048
|
+
switch (state.kind) {
|
|
14049
|
+
case "present":
|
|
14050
|
+
return { id, title, status: "pass", summary: "BMAD installer scripts are present", details: [], fixable: false };
|
|
14051
|
+
case "decline":
|
|
14052
|
+
return { id, title, status: "skip", summary: state.summary, details: [], fixable: false };
|
|
14053
|
+
case "refuse":
|
|
14054
|
+
return { id, title, status: "fail", summary: state.error, details: [state.error], fixable: false };
|
|
14055
|
+
case "missing":
|
|
14056
|
+
return {
|
|
14057
|
+
id,
|
|
14058
|
+
title,
|
|
14059
|
+
status: "fail",
|
|
14060
|
+
summary: "Ignored BMAD installer scripts are missing",
|
|
14061
|
+
details: state.files.map((path) => relative9(ctx.repoRoot, path)),
|
|
14062
|
+
fixable: true
|
|
14063
|
+
};
|
|
14064
|
+
}
|
|
14065
|
+
},
|
|
14066
|
+
migrate: (ctx, finding2) => {
|
|
14067
|
+
const title = finding2.title, id = finding2.id;
|
|
14068
|
+
if (finding2.status === "skip") return { id, title, status: "skipped", summary: finding2.summary, details: [], changedFiles: [] };
|
|
14069
|
+
const state = bmadSetupScriptsState(ctx.repoRoot);
|
|
14070
|
+
if (state.kind === "decline") return { id, title, status: "skipped", summary: state.summary, details: [], changedFiles: [] };
|
|
14071
|
+
if (state.kind === "refuse") return { id, title, status: "blocked", summary: state.error, details: [state.error], changedFiles: [] };
|
|
14072
|
+
if (state.kind === "present") return { id, title, status: "noop", summary: "BMAD installer scripts already present", details: [], changedFiles: [] };
|
|
14073
|
+
const { pkg, files } = state;
|
|
14074
|
+
if (ctx.dryRun) return {
|
|
14075
|
+
id,
|
|
14076
|
+
title,
|
|
14077
|
+
status: "applied",
|
|
14078
|
+
summary: "Would restore ignored scripts from verified pinned bmad-method package",
|
|
14079
|
+
details: [`bmad-method@${BMAD_INSTALLER_VERSION} (${pkg.root})`],
|
|
14080
|
+
changedFiles: files
|
|
14081
|
+
};
|
|
14082
|
+
const applied = applyBmadSetupScripts(ctx.repoRoot, pkg, BMAD_INSTALLER_VERSION);
|
|
14083
|
+
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 };
|
|
14084
|
+
}
|
|
14085
|
+
},
|
|
13627
14086
|
{
|
|
13628
14087
|
id: "bmad.version",
|
|
13629
14088
|
title: "BMAD version currency",
|
|
@@ -13634,7 +14093,7 @@ function createBmadChecks() {
|
|
|
13634
14093
|
id: "bmad.version",
|
|
13635
14094
|
title: "BMAD version currency",
|
|
13636
14095
|
status: "skip",
|
|
13637
|
-
summary: existsSync8(
|
|
14096
|
+
summary: existsSync8(join12(ctx.repoRoot, "_bmad")) ? "BMAD installed but version manifest unreadable" : "No BMAD install present",
|
|
13638
14097
|
details: [],
|
|
13639
14098
|
fixable: false
|
|
13640
14099
|
};
|
|
@@ -13722,7 +14181,7 @@ function createBmadChecks() {
|
|
|
13722
14181
|
}
|
|
13723
14182
|
const installed = readInstalledBmadVersion(ctx.repoRoot);
|
|
13724
14183
|
const available = ctx.bmadVersionPin?.trim() ?? bmadChannelTarget(resolveBmadDistTags(ctx.homeDir)?.distTags)?.version;
|
|
13725
|
-
const manifestPath =
|
|
14184
|
+
const manifestPath = join12(ctx.repoRoot, "_bmad", "_config", "manifest.yaml");
|
|
13726
14185
|
const manifestSelection = manifestBmadModules(ctx.repoRoot);
|
|
13727
14186
|
if (manifestSelection.status === "invalid") {
|
|
13728
14187
|
return {
|
|
@@ -13820,7 +14279,7 @@ function createBmadChecks() {
|
|
|
13820
14279
|
const reserved = evictReservedModuleEntries(ctx.repoRoot, false);
|
|
13821
14280
|
const install = await runBmadInstall(ctx.repoRoot, selectedModules, available ?? BMAD_TARGET_CHANNEL, channelArgs);
|
|
13822
14281
|
if (!install.ok) {
|
|
13823
|
-
if (reserved.original !== void 0)
|
|
14282
|
+
if (reserved.original !== void 0) writeFileSync9(reserved.changedFiles[0], reserved.original, "utf8");
|
|
13824
14283
|
return {
|
|
13825
14284
|
id: finding2.id,
|
|
13826
14285
|
title: finding2.title,
|
|
@@ -13855,7 +14314,7 @@ function createBmadChecks() {
|
|
|
13855
14314
|
title: "Supported BMAD CLI projection roots",
|
|
13856
14315
|
audit: (ctx) => {
|
|
13857
14316
|
const unsupportedNames = Object.keys(UNSUPPORTED_BMAD_ROOTS);
|
|
13858
|
-
const present = unsupportedNames.filter((name) => existsSync8(
|
|
14317
|
+
const present = unsupportedNames.filter((name) => existsSync8(join12(ctx.repoRoot, name)));
|
|
13859
14318
|
const attestations = present.map((name) => ({ name, ...unsupportedRootAttestation(ctx.repoRoot, name) }));
|
|
13860
14319
|
const plan = planSkillRoots(ctx.repoRoot);
|
|
13861
14320
|
const supportedIssues = supportedCliProjectionIssues(ctx.repoRoot, plan);
|
|
@@ -13877,7 +14336,7 @@ function createBmadChecks() {
|
|
|
13877
14336
|
},
|
|
13878
14337
|
migrate: (ctx, finding2) => {
|
|
13879
14338
|
const unsupportedNames = Object.keys(UNSUPPORTED_BMAD_ROOTS);
|
|
13880
|
-
const present = unsupportedNames.filter((name) => existsSync8(
|
|
14339
|
+
const present = unsupportedNames.filter((name) => existsSync8(join12(ctx.repoRoot, name)));
|
|
13881
14340
|
const attestations = present.map((name) => ({ name, ...unsupportedRootAttestation(ctx.repoRoot, name) }));
|
|
13882
14341
|
const blocked = attestations.filter((entry) => !entry.safe);
|
|
13883
14342
|
if (blocked.length) {
|
|
@@ -13902,7 +14361,7 @@ function createBmadChecks() {
|
|
|
13902
14361
|
};
|
|
13903
14362
|
}
|
|
13904
14363
|
const gitignoreChanges = ensureSupportedCliGitignore(ctx);
|
|
13905
|
-
const removedRoots = attestations.map((entry) =>
|
|
14364
|
+
const removedRoots = attestations.map((entry) => join12(ctx.repoRoot, entry.name));
|
|
13906
14365
|
if (!ctx.dryRun) for (const path of removedRoots) rmSync5(path, { recursive: true, force: true });
|
|
13907
14366
|
const changedFiles = [.../* @__PURE__ */ new Set([...projectionResult.changedFiles, ...gitignoreChanges, ...removedRoots])].sort();
|
|
13908
14367
|
return {
|
|
@@ -13915,7 +14374,7 @@ function createBmadChecks() {
|
|
|
13915
14374
|
};
|
|
13916
14375
|
}
|
|
13917
14376
|
}
|
|
13918
|
-
].map(withInstallerSkillHandoff);
|
|
14377
|
+
].map(withInstallerSkillHandoff).map(withScaffoldPostcondition);
|
|
13919
14378
|
}
|
|
13920
14379
|
function createProjectMomoChecks() {
|
|
13921
14380
|
return [
|
|
@@ -14604,8 +15063,8 @@ var NodeRecipe = class extends Recipe {
|
|
|
14604
15063
|
// src/recipes/ProjectRecipe.ts
|
|
14605
15064
|
init_project();
|
|
14606
15065
|
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
14607
|
-
import { existsSync as existsSync18, lstatSync as
|
|
14608
|
-
import { dirname as dirname18, isAbsolute as isAbsolute10, join as
|
|
15066
|
+
import { existsSync as existsSync18, lstatSync as lstatSync17, readFileSync as readFileSync23, rmSync as rmSync8 } from "node:fs";
|
|
15067
|
+
import { dirname as dirname18, isAbsolute as isAbsolute10, join as join25, relative as relativePath, resolve as resolve19 } from "node:path";
|
|
14609
15068
|
|
|
14610
15069
|
// src/project/ticketScope.ts
|
|
14611
15070
|
function ticketScope(ticket) {
|
|
@@ -14622,8 +15081,8 @@ init_tree_diff();
|
|
|
14622
15081
|
|
|
14623
15082
|
// src/recipes/NotebookRecipe.ts
|
|
14624
15083
|
init_project();
|
|
14625
|
-
import { existsSync as existsSync17, readFileSync as
|
|
14626
|
-
import { join as
|
|
15084
|
+
import { existsSync as existsSync17, readFileSync as readFileSync22 } from "node:fs";
|
|
15085
|
+
import { join as join24 } from "node:path";
|
|
14627
15086
|
|
|
14628
15087
|
// src/notebook/checks.ts
|
|
14629
15088
|
init_config();
|
|
@@ -14631,8 +15090,8 @@ init_notes();
|
|
|
14631
15090
|
init_output();
|
|
14632
15091
|
init_state();
|
|
14633
15092
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
14634
|
-
import { existsSync as existsSync14, readFileSync as
|
|
14635
|
-
import { join as
|
|
15093
|
+
import { existsSync as existsSync14, readFileSync as readFileSync20 } from "node:fs";
|
|
15094
|
+
import { join as join20 } from "node:path";
|
|
14636
15095
|
var NOTEBOOK_RULE_IDS = [
|
|
14637
15096
|
"notebook.configuration",
|
|
14638
15097
|
"notebook.binding",
|
|
@@ -14649,10 +15108,10 @@ function result(check, status, summary, changedFiles = [], details = []) {
|
|
|
14649
15108
|
return { id: check.id, title: check.title, status, summary, changedFiles, details };
|
|
14650
15109
|
}
|
|
14651
15110
|
function manifestNotebook(repo) {
|
|
14652
|
-
const path =
|
|
15111
|
+
const path = join20(repo, ".project.json");
|
|
14653
15112
|
if (!existsSync14(path)) return null;
|
|
14654
15113
|
try {
|
|
14655
|
-
const parsed = JSON.parse(
|
|
15114
|
+
const parsed = JSON.parse(readFileSync20(path, "utf8"));
|
|
14656
15115
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
14657
15116
|
const notebook = parsed.notebook;
|
|
14658
15117
|
return notebook && typeof notebook === "object" && !Array.isArray(notebook) ? notebook : null;
|
|
@@ -14881,7 +15340,7 @@ function captureAudit(ctx) {
|
|
|
14881
15340
|
function overviewMigrate(ctx, current) {
|
|
14882
15341
|
if (ctx.dryRun) return result(current, "applied", "Would reconcile drifted Project Overview note");
|
|
14883
15342
|
if (!ctx.live) return result(current, "blocked", "Reconciling the remote Overview note requires --live");
|
|
14884
|
-
const pjanglerEntry = ctx.pjanglerRoot ?
|
|
15343
|
+
const pjanglerEntry = ctx.pjanglerRoot ? join20(ctx.pjanglerRoot, "dist", "index.js") : null;
|
|
14885
15344
|
const entry = pjanglerEntry && existsSync14(pjanglerEntry) ? pjanglerEntry : "pj";
|
|
14886
15345
|
const cmd = entry === "pj" ? ["pj"] : [process.execPath, entry];
|
|
14887
15346
|
const spawned = spawnSync8(cmd[0], [...cmd.slice(1), "notebook", "overview", ctx.repoRoot], {
|
|
@@ -14933,7 +15392,7 @@ import { resolve as resolve18 } from "node:path";
|
|
|
14933
15392
|
|
|
14934
15393
|
// src/notebook/journal-key.ts
|
|
14935
15394
|
init_types();
|
|
14936
|
-
import { createHash as
|
|
15395
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
14937
15396
|
function validateJournalPeriodKey(period, key) {
|
|
14938
15397
|
if (period === "monthly") {
|
|
14939
15398
|
if (!/^\d{4}-(?:0[1-9]|1[0-2])$/u.test(key)) throw new NotebookError("INVALID_INPUT", "Monthly period key must be YYYY-MM");
|
|
@@ -14952,7 +15411,7 @@ function validateJournalPeriodKey(period, key) {
|
|
|
14952
15411
|
}
|
|
14953
15412
|
function journalNoteOperationId(period, key) {
|
|
14954
15413
|
validateJournalPeriodKey(period, key);
|
|
14955
|
-
const bytes =
|
|
15414
|
+
const bytes = createHash12("sha256").update(`pjangler-dev-journal-v1\0infra\0${period}\0${key}`).digest().subarray(0, 16);
|
|
14956
15415
|
bytes[6] = bytes[6] & 15 | 80;
|
|
14957
15416
|
bytes[8] = bytes[8] & 63 | 128;
|
|
14958
15417
|
const hex = bytes.toString("hex");
|
|
@@ -15108,9 +15567,9 @@ init_notes();
|
|
|
15108
15567
|
init_state();
|
|
15109
15568
|
init_types();
|
|
15110
15569
|
import { spawn as spawn2, spawnSync as spawnSync11 } from "node:child_process";
|
|
15111
|
-
import { createHash as
|
|
15112
|
-
import { chmodSync as chmodSync6, closeSync as
|
|
15113
|
-
import { basename as
|
|
15570
|
+
import { createHash as createHash13, randomUUID as randomUUID5 } from "node:crypto";
|
|
15571
|
+
import { chmodSync as chmodSync6, closeSync as closeSync7, constants as constants6, copyFileSync as copyFileSync3, existsSync as existsSync15, fstatSync as fstatSync4, fsyncSync as fsyncSync4, lstatSync as lstatSync16, mkdirSync as mkdirSync10, openSync as openSync7, readFileSync as readFileSync21, 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";
|
|
15572
|
+
import { basename as basename11, dirname as dirname17, isAbsolute as isAbsolute8, join as join23, parse as parse3, relative as relative15, resolve as resolve16, sep as sep7 } from "node:path";
|
|
15114
15573
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
15115
15574
|
function captureWorkerEnvironment(source, projectSlug) {
|
|
15116
15575
|
const result2 = {
|
|
@@ -15158,7 +15617,7 @@ function bundledSkillCandidates() {
|
|
|
15158
15617
|
const candidates = [];
|
|
15159
15618
|
let cursor = dirname17(fileURLToPath3(import.meta.url));
|
|
15160
15619
|
for (let depth = 0; depth < 8; depth++) {
|
|
15161
|
-
candidates.push(
|
|
15620
|
+
candidates.push(join23(cursor, "dist", "assets", "project-notebook-skill"), join23(cursor, "assets", "project-notebook-skill"));
|
|
15162
15621
|
const parent = dirname17(cursor);
|
|
15163
15622
|
if (parent === cursor) break;
|
|
15164
15623
|
cursor = parent;
|
|
@@ -15176,14 +15635,14 @@ function assertOwnedSkillTree(source) {
|
|
|
15176
15635
|
assertNoSymlinkComponents(source);
|
|
15177
15636
|
const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
15178
15637
|
const walk = (directory) => {
|
|
15179
|
-
const directoryStat =
|
|
15638
|
+
const directoryStat = lstatSync16(directory);
|
|
15180
15639
|
if (!directoryStat.isDirectory() || directoryStat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill source contains a non-directory component");
|
|
15181
15640
|
if (uid !== void 0 && directoryStat.uid !== uid) throw new NotebookError("CONFLICT", "Project Notebook skill source is not owned by the current user");
|
|
15182
15641
|
if (directoryStat.mode & 3586) throw new NotebookError("CONFLICT", "Project Notebook skill source has unsafe directory mode bits");
|
|
15183
|
-
for (const entry of
|
|
15642
|
+
for (const entry of readdirSync11(directory, { withFileTypes: true })) {
|
|
15184
15643
|
if (entry.isDirectory() && (entry.name === "__pycache__" || entry.name === ".git" || entry.name === "node_modules")) continue;
|
|
15185
|
-
const path =
|
|
15186
|
-
const stat =
|
|
15644
|
+
const path = join23(directory, entry.name);
|
|
15645
|
+
const stat = lstatSync16(path);
|
|
15187
15646
|
if (stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill source contains a symlink");
|
|
15188
15647
|
if (entry.isDirectory()) walk(path);
|
|
15189
15648
|
else if (!entry.isFile()) throw new NotebookError("CONFLICT", "Project Notebook skill source contains a non-regular entry");
|
|
@@ -15198,18 +15657,18 @@ function assertOwnedSkillTree(source) {
|
|
|
15198
15657
|
function enumerateSkillPayload(source) {
|
|
15199
15658
|
const result2 = [];
|
|
15200
15659
|
const walk = (directory) => {
|
|
15201
|
-
for (const entry of
|
|
15660
|
+
for (const entry of readdirSync11(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name, "en"))) {
|
|
15202
15661
|
if (entry.isDirectory() && (entry.name === "__pycache__" || entry.name === ".git" || entry.name === "node_modules")) continue;
|
|
15203
|
-
const path =
|
|
15662
|
+
const path = join23(directory, entry.name);
|
|
15204
15663
|
const rel = relative15(source, path).split(sep7).join("/");
|
|
15205
15664
|
if (!rel.includes("/") && (rel === "export-manifest.json" || rel === "SHA256SUMS" || rel === ".source.yaml")) continue;
|
|
15206
15665
|
if (!safeSkillRelativePath(rel)) throw new NotebookError("CONFLICT", `Project Notebook skill export path is unsafe: ${rel}`);
|
|
15207
|
-
const stat =
|
|
15666
|
+
const stat = lstatSync16(path);
|
|
15208
15667
|
if (stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill export contains a symlink");
|
|
15209
15668
|
if (entry.isDirectory()) walk(path);
|
|
15210
15669
|
else if (entry.isFile()) {
|
|
15211
15670
|
const expectedMode = rel.endsWith(".sh") || rel.startsWith("scripts/") ? "0755" : "0644";
|
|
15212
|
-
result2.push({ path: rel, sha256:
|
|
15671
|
+
result2.push({ path: rel, sha256: createHash13("sha256").update(readFileSync21(path)).digest("hex"), mode: expectedMode });
|
|
15213
15672
|
} else throw new NotebookError("CONFLICT", "Project Notebook skill export contains a non-regular entry");
|
|
15214
15673
|
}
|
|
15215
15674
|
};
|
|
@@ -15217,9 +15676,9 @@ function enumerateSkillPayload(source) {
|
|
|
15217
15676
|
return result2.sort((a, b) => a.path.localeCompare(b.path, "en"));
|
|
15218
15677
|
}
|
|
15219
15678
|
function parsePackedManifest(source) {
|
|
15220
|
-
const manifestPath =
|
|
15679
|
+
const manifestPath = join23(source, "export-manifest.json");
|
|
15221
15680
|
if (!existsSync15(manifestPath)) return null;
|
|
15222
|
-
const value = JSON.parse(
|
|
15681
|
+
const value = JSON.parse(readFileSync21(manifestPath, "utf8"));
|
|
15223
15682
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new NotebookError("CONFLICT", "Project Notebook skill export manifest is invalid");
|
|
15224
15683
|
const manifest = value;
|
|
15225
15684
|
if (manifest.schema_version !== 1 || manifest.skill !== "project-notebook" || !Array.isArray(manifest.files)) throw new NotebookError("CONFLICT", "Project Notebook skill export manifest is incompatible");
|
|
@@ -15228,10 +15687,10 @@ function parsePackedManifest(source) {
|
|
|
15228
15687
|
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");
|
|
15229
15688
|
if (!safeSkillRelativePath(entry.path) || paths.has(entry.path)) throw new NotebookError("CONFLICT", "Project Notebook skill export path is unsafe or duplicated");
|
|
15230
15689
|
paths.add(entry.path);
|
|
15231
|
-
const path =
|
|
15232
|
-
const stat =
|
|
15690
|
+
const path = join23(source, ...entry.path.split("/"));
|
|
15691
|
+
const stat = lstatSync16(path);
|
|
15233
15692
|
if (!stat.isFile() || stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill export contains a non-regular entry");
|
|
15234
|
-
const actual =
|
|
15693
|
+
const actual = createHash13("sha256").update(readFileSync21(path)).digest("hex");
|
|
15235
15694
|
if (actual !== entry.sha256) throw new NotebookError("CONFLICT", `Project Notebook skill digest mismatch: ${entry.path}`);
|
|
15236
15695
|
const actualMode = stat.mode & 511;
|
|
15237
15696
|
const executable = entry.mode === "0755";
|
|
@@ -15243,16 +15702,16 @@ function parsePackedManifest(source) {
|
|
|
15243
15702
|
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");
|
|
15244
15703
|
const sums = `${manifest.files.map((entry) => `${entry.sha256} ${entry.path}`).join("\n")}
|
|
15245
15704
|
`;
|
|
15246
|
-
if (!existsSync15(
|
|
15705
|
+
if (!existsSync15(join23(source, "SHA256SUMS")) || readFileSync21(join23(source, "SHA256SUMS"), "utf8") !== sums) throw new NotebookError("CONFLICT", "Project Notebook skill SHA256SUMS is missing or stale");
|
|
15247
15706
|
return manifest;
|
|
15248
15707
|
}
|
|
15249
15708
|
function expectedPackedSkill() {
|
|
15250
15709
|
for (const candidate of bundledSkillCandidates()) {
|
|
15251
|
-
if (!existsSync15(
|
|
15710
|
+
if (!existsSync15(join23(candidate, "export-manifest.json"))) continue;
|
|
15252
15711
|
assertOwnedSkillTree(candidate);
|
|
15253
15712
|
const manifest = parsePackedManifest(candidate);
|
|
15254
15713
|
if (!manifest) continue;
|
|
15255
|
-
const digest =
|
|
15714
|
+
const digest = createHash13("sha256").update(JSON.stringify(manifest)).digest("hex");
|
|
15256
15715
|
return { source: candidate, manifest, digest };
|
|
15257
15716
|
}
|
|
15258
15717
|
throw new NotebookError("NOT_CONFIGURED", "PJ\xE1ngler package has no verified Project Notebook skill export");
|
|
@@ -15315,13 +15774,13 @@ function verifyProjectNotebookSkillExport(source) {
|
|
|
15315
15774
|
function resolveProjectNotebookSkillSource(env2 = process.env) {
|
|
15316
15775
|
if (env2.PJ_PROJECT_NOTEBOOK_SKILL_ROOT) {
|
|
15317
15776
|
const explicit = resolve16(env2.PJ_PROJECT_NOTEBOOK_SKILL_ROOT);
|
|
15318
|
-
if (!existsSync15(
|
|
15777
|
+
if (!existsSync15(join23(explicit, "SKILL.md"))) throw new NotebookError("NOT_CONFIGURED", "Configured Project Notebook skill source is unavailable");
|
|
15319
15778
|
verifyProjectNotebookSkillExport(explicit);
|
|
15320
15779
|
return explicit;
|
|
15321
15780
|
}
|
|
15322
15781
|
if (env2.PJ_SKILLS_REGISTRY_ROOT) {
|
|
15323
15782
|
const canonical = resolve16(env2.PJ_SKILLS_REGISTRY_ROOT, "all-skills", "project-notebook");
|
|
15324
|
-
if (existsSync15(
|
|
15783
|
+
if (existsSync15(join23(canonical, "SKILL.md"))) {
|
|
15325
15784
|
verifyProjectNotebookSkillExport(canonical);
|
|
15326
15785
|
return canonical;
|
|
15327
15786
|
}
|
|
@@ -15336,7 +15795,7 @@ var REPAIR_COMMAND = "pj notebook skill --apply";
|
|
|
15336
15795
|
function probeCanonicalSkillexRootProjection(skillsRoot, link) {
|
|
15337
15796
|
let rootLink;
|
|
15338
15797
|
try {
|
|
15339
|
-
rootLink =
|
|
15798
|
+
rootLink = lstatSync16(skillsRoot);
|
|
15340
15799
|
} catch (error) {
|
|
15341
15800
|
if (error.code === "ENOENT") return { state: "absent" };
|
|
15342
15801
|
throw error;
|
|
@@ -15349,15 +15808,15 @@ function probeCanonicalSkillexRootProjection(skillsRoot, link) {
|
|
|
15349
15808
|
if (uid !== void 0 && rootLink.uid !== uid) throw new Error("owner");
|
|
15350
15809
|
const globalRoot = realpathSync10(skillsRoot);
|
|
15351
15810
|
assertNoSymlinkComponents(globalRoot);
|
|
15352
|
-
const globalStat =
|
|
15811
|
+
const globalStat = lstatSync16(globalRoot);
|
|
15353
15812
|
if (!globalStat.isDirectory() || globalStat.isSymbolicLink()) throw new Error("root-type");
|
|
15354
15813
|
if (uid !== void 0 && globalStat.uid !== uid) throw new Error("root-owner");
|
|
15355
15814
|
if (globalStat.mode & 3586) throw new Error("root-mode");
|
|
15356
15815
|
const skillSetsRoot = dirname17(globalRoot);
|
|
15357
|
-
if (
|
|
15816
|
+
if (basename11(globalRoot) !== "global" || basename11(skillSetsRoot) !== "skill-sets") throw new Error("layout");
|
|
15358
15817
|
const checkoutRoot = dirname17(skillSetsRoot);
|
|
15359
|
-
const expectedSource =
|
|
15360
|
-
const linkStat =
|
|
15818
|
+
const expectedSource = join23(checkoutRoot, "all-skills", "project-notebook");
|
|
15819
|
+
const linkStat = lstatSync16(link);
|
|
15361
15820
|
if (!linkStat.isSymbolicLink()) throw new Error("projection-type");
|
|
15362
15821
|
if (uid !== void 0 && linkStat.uid !== uid) throw new Error("projection-owner");
|
|
15363
15822
|
const projectedSource = realpathSync10(link);
|
|
@@ -15391,44 +15850,44 @@ function installPackagedProjectNotebookSkill(input = {}) {
|
|
|
15391
15850
|
const source = input.source ?? resolveProjectNotebookSkillSource(env2);
|
|
15392
15851
|
if (!source) throw new NotebookError("NOT_CONFIGURED", "Project Notebook skill source is unavailable");
|
|
15393
15852
|
const manifest = verifyProjectNotebookSkillExport(source);
|
|
15394
|
-
const digest =
|
|
15853
|
+
const digest = createHash13("sha256").update(JSON.stringify(manifest)).digest("hex");
|
|
15395
15854
|
const home = env2.HOME;
|
|
15396
15855
|
if (!home || !resolve16(home).startsWith("/")) throw new NotebookError("NOT_CONFIGURED", "A trusted HOME is required to install the Project Notebook skill");
|
|
15397
|
-
const skillsRoot =
|
|
15398
|
-
const link =
|
|
15856
|
+
const skillsRoot = join23(home, ".agents", "skills");
|
|
15857
|
+
const link = join23(skillsRoot, "project-notebook");
|
|
15399
15858
|
const probe = probeCanonicalSkillexRootProjection(skillsRoot, link);
|
|
15400
15859
|
if (probe.state === "adopted") return { installed: false, path: link, digest };
|
|
15401
15860
|
if (probe.state === "declined") return { installed: false, path: link, digest, blocked: probe.block };
|
|
15402
|
-
const dataRoot = resolve16(env2.XDG_DATA_HOME ||
|
|
15403
|
-
const payload =
|
|
15861
|
+
const dataRoot = resolve16(env2.XDG_DATA_HOME || join23(home, ".local", "share"), "pjangler", "skills", "project-notebook");
|
|
15862
|
+
const payload = join23(dataRoot, `${PJANGLER_VERSION}-${digest}`);
|
|
15404
15863
|
assertNoSymlinkComponents(dirname17(dataRoot), true);
|
|
15405
|
-
|
|
15864
|
+
mkdirSync10(dataRoot, { recursive: true, mode: 448 });
|
|
15406
15865
|
assertNoSymlinkComponents(dataRoot);
|
|
15407
|
-
const dataStat =
|
|
15866
|
+
const dataStat = lstatSync16(dataRoot);
|
|
15408
15867
|
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");
|
|
15409
15868
|
chmodSync6(dataRoot, 448);
|
|
15410
15869
|
if (!existsSync15(payload)) {
|
|
15411
|
-
const staging =
|
|
15412
|
-
|
|
15870
|
+
const staging = join23(dataRoot, `.staging-${randomUUID5()}`);
|
|
15871
|
+
mkdirSync10(staging, { recursive: false, mode: 448 });
|
|
15413
15872
|
try {
|
|
15414
15873
|
for (const entry of manifest.files) {
|
|
15415
|
-
const destination =
|
|
15416
|
-
|
|
15417
|
-
copyFileSync3(
|
|
15874
|
+
const destination = join23(staging, ...entry.path.split("/"));
|
|
15875
|
+
mkdirSync10(dirname17(destination), { recursive: true, mode: 493 });
|
|
15876
|
+
copyFileSync3(join23(source, ...entry.path.split("/")), destination);
|
|
15418
15877
|
const mode = entry.mode === "0755" ? 493 : 420;
|
|
15419
15878
|
chmodSync6(destination, mode);
|
|
15420
|
-
const fd =
|
|
15879
|
+
const fd = openSync7(destination, constants6.O_RDONLY | (constants6.O_NOFOLLOW ?? 0));
|
|
15421
15880
|
try {
|
|
15422
|
-
const current =
|
|
15881
|
+
const current = fstatSync4(fd);
|
|
15423
15882
|
if (!current.isFile()) throw new NotebookError("CONFLICT", `Installed skill entry is not regular: ${entry.path}`);
|
|
15424
15883
|
fsyncSync4(fd);
|
|
15425
15884
|
} finally {
|
|
15426
|
-
|
|
15885
|
+
closeSync7(fd);
|
|
15427
15886
|
}
|
|
15428
15887
|
}
|
|
15429
|
-
|
|
15888
|
+
writeFileSync13(join23(staging, "export-manifest.json"), `${JSON.stringify(manifest, null, 2)}
|
|
15430
15889
|
`, { mode: 420, flag: "wx" });
|
|
15431
|
-
|
|
15890
|
+
writeFileSync13(join23(staging, "SHA256SUMS"), `${manifest.files.map((entry) => `${entry.sha256} ${entry.path}`).join("\n")}
|
|
15432
15891
|
`, { mode: 420, flag: "wx" });
|
|
15433
15892
|
verifyProjectNotebookSkillExport(staging);
|
|
15434
15893
|
renameSync8(staging, payload);
|
|
@@ -15436,17 +15895,17 @@ function installPackagedProjectNotebookSkill(input = {}) {
|
|
|
15436
15895
|
if (existsSync15(staging)) rmSync7(staging, { recursive: true, force: true });
|
|
15437
15896
|
}
|
|
15438
15897
|
} else {
|
|
15439
|
-
const stat =
|
|
15898
|
+
const stat = lstatSync16(payload);
|
|
15440
15899
|
if (!stat.isDirectory() || stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Installed Project Notebook payload is not a real directory");
|
|
15441
15900
|
verifyProjectNotebookSkillExport(payload);
|
|
15442
15901
|
}
|
|
15443
15902
|
assertNoSymlinkComponents(skillsRoot, true);
|
|
15444
|
-
|
|
15903
|
+
mkdirSync10(skillsRoot, { recursive: true, mode: 448 });
|
|
15445
15904
|
assertNoSymlinkComponents(skillsRoot);
|
|
15446
15905
|
let linkExists = existsSync15(link);
|
|
15447
15906
|
if (!linkExists) {
|
|
15448
15907
|
try {
|
|
15449
|
-
|
|
15908
|
+
lstatSync16(link);
|
|
15450
15909
|
linkExists = true;
|
|
15451
15910
|
} catch {
|
|
15452
15911
|
}
|
|
@@ -15463,7 +15922,7 @@ function installPackagedProjectNotebookSkill(input = {}) {
|
|
|
15463
15922
|
repair: REPAIR_COMMAND
|
|
15464
15923
|
}
|
|
15465
15924
|
});
|
|
15466
|
-
const stat =
|
|
15925
|
+
const stat = lstatSync16(link);
|
|
15467
15926
|
if (!stat.isSymbolicLink()) return foreign(["the path is a real file or directory, not a PJ\xE1ngler-owned link"]);
|
|
15468
15927
|
const target = realpathSync10(link);
|
|
15469
15928
|
if (target !== realpathSync10(payload) && !isVerifiedCanonicalSkillexProjection(target)) {
|
|
@@ -15491,15 +15950,15 @@ function collectSupersededPayloads(dataRoot, keep) {
|
|
|
15491
15950
|
const removed = [];
|
|
15492
15951
|
let entries;
|
|
15493
15952
|
try {
|
|
15494
|
-
entries =
|
|
15953
|
+
entries = readdirSync11(dataRoot);
|
|
15495
15954
|
} catch {
|
|
15496
15955
|
return removed;
|
|
15497
15956
|
}
|
|
15498
15957
|
for (const name of entries) {
|
|
15499
15958
|
if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?-[0-9a-f]{64}$/u.test(name)) continue;
|
|
15500
|
-
const candidate =
|
|
15959
|
+
const candidate = join23(dataRoot, name);
|
|
15501
15960
|
try {
|
|
15502
|
-
const stat =
|
|
15961
|
+
const stat = lstatSync16(candidate);
|
|
15503
15962
|
if (!stat.isDirectory() || stat.isSymbolicLink()) continue;
|
|
15504
15963
|
if (kept.has(realpathSync10(candidate))) continue;
|
|
15505
15964
|
rmSync7(candidate, { recursive: true, force: true });
|
|
@@ -15513,8 +15972,8 @@ function repairProjectNotebookSkillProjection(input = {}) {
|
|
|
15513
15972
|
const env2 = input.env ?? process.env;
|
|
15514
15973
|
const home = env2.HOME;
|
|
15515
15974
|
if (!home || !resolve16(home).startsWith("/")) throw new NotebookError("NOT_CONFIGURED", "A trusted HOME is required to repair the Project Notebook skill projection");
|
|
15516
|
-
const skillsRoot =
|
|
15517
|
-
const link =
|
|
15975
|
+
const skillsRoot = join23(home, ".agents", "skills");
|
|
15976
|
+
const link = join23(skillsRoot, "project-notebook");
|
|
15518
15977
|
const probe = probeCanonicalSkillexRootProjection(skillsRoot, link);
|
|
15519
15978
|
if (probe.state === "adopted") return { status: "clean", summary: "Canonical Skillex projection already matches the version-pinned export", source: probe.source, drift: [], changed_files: [] };
|
|
15520
15979
|
if (probe.state !== "declined") {
|
|
@@ -15527,8 +15986,8 @@ function repairProjectNotebookSkillProjection(input = {}) {
|
|
|
15527
15986
|
assertOwnedSkillTree(source);
|
|
15528
15987
|
const present = [];
|
|
15529
15988
|
const walk = (directory) => {
|
|
15530
|
-
for (const entry of
|
|
15531
|
-
const path =
|
|
15989
|
+
for (const entry of readdirSync11(directory, { withFileTypes: true })) {
|
|
15990
|
+
const path = join23(directory, entry.name);
|
|
15532
15991
|
const rel = relative15(source, path).split(sep7).join("/");
|
|
15533
15992
|
if (entry.isDirectory()) {
|
|
15534
15993
|
walk(path);
|
|
@@ -15542,11 +16001,11 @@ function repairProjectNotebookSkillProjection(input = {}) {
|
|
|
15542
16001
|
walk(source);
|
|
15543
16002
|
const stale = present.filter((rel) => !wanted.has(rel)).sort();
|
|
15544
16003
|
const changed = expected.manifest.files.filter((entry) => {
|
|
15545
|
-
const path =
|
|
16004
|
+
const path = join23(source, ...entry.path.split("/"));
|
|
15546
16005
|
if (!existsSync15(path)) return true;
|
|
15547
|
-
const stat =
|
|
16006
|
+
const stat = lstatSync16(path);
|
|
15548
16007
|
if (!stat.isFile()) return true;
|
|
15549
|
-
if (
|
|
16008
|
+
if (createHash13("sha256").update(readFileSync21(path)).digest("hex") !== entry.sha256) return true;
|
|
15550
16009
|
const mode = stat.mode & 511;
|
|
15551
16010
|
const executable = entry.mode === "0755";
|
|
15552
16011
|
return executable && (mode & 64) === 0 || !executable && (mode & 73) !== 0;
|
|
@@ -15561,15 +16020,15 @@ function repairProjectNotebookSkillProjection(input = {}) {
|
|
|
15561
16020
|
summary: `Would restore ${affected.length} file(s) in ${source} from the version-pinned export`,
|
|
15562
16021
|
source,
|
|
15563
16022
|
drift: probe.block.details,
|
|
15564
|
-
changed_files: affected.map((rel) =>
|
|
16023
|
+
changed_files: affected.map((rel) => join23(source, ...rel.split("/")))
|
|
15565
16024
|
};
|
|
15566
16025
|
}
|
|
15567
|
-
for (const rel of stale) rmSync7(
|
|
16026
|
+
for (const rel of stale) rmSync7(join23(source, ...rel.split("/")), { force: true });
|
|
15568
16027
|
for (const rel of changed) {
|
|
15569
|
-
const destination =
|
|
16028
|
+
const destination = join23(source, ...rel.split("/"));
|
|
15570
16029
|
const entry = expected.manifest.files.find((item) => item.path === rel);
|
|
15571
|
-
|
|
15572
|
-
copyFileSync3(
|
|
16030
|
+
mkdirSync10(dirname17(destination), { recursive: true, mode: 493 });
|
|
16031
|
+
copyFileSync3(join23(expected.source, ...rel.split("/")), destination);
|
|
15573
16032
|
chmodSync6(destination, entry.mode === "0755" ? 493 : 420);
|
|
15574
16033
|
}
|
|
15575
16034
|
verifyProjectNotebookSkillExport(source);
|
|
@@ -15578,21 +16037,21 @@ function repairProjectNotebookSkillProjection(input = {}) {
|
|
|
15578
16037
|
summary: `Restored ${affected.length} file(s) in ${source} from the version-pinned export; commit the change in the Skillex checkout`,
|
|
15579
16038
|
source,
|
|
15580
16039
|
drift: probe.block.details,
|
|
15581
|
-
changed_files: affected.map((rel) =>
|
|
16040
|
+
changed_files: affected.map((rel) => join23(source, ...rel.split("/")))
|
|
15582
16041
|
};
|
|
15583
16042
|
}
|
|
15584
16043
|
function inspectProjectNotebookIntegration(env2 = process.env) {
|
|
15585
16044
|
const home = env2.HOME;
|
|
15586
16045
|
if (!home) return { skill_installed: false, hooks_projected: false, details: ["HOME is unavailable"] };
|
|
15587
|
-
const skillsRoot =
|
|
15588
|
-
const link =
|
|
16046
|
+
const skillsRoot = join23(home, ".agents", "skills");
|
|
16047
|
+
const link = join23(skillsRoot, "project-notebook");
|
|
15589
16048
|
try {
|
|
15590
16049
|
const expected = expectedPackedSkill();
|
|
15591
|
-
const dataRoot = resolve16(env2.XDG_DATA_HOME ||
|
|
15592
|
-
const expectedPayload =
|
|
16050
|
+
const dataRoot = resolve16(env2.XDG_DATA_HOME || join23(home, ".local", "share"), "pjangler", "skills", "project-notebook");
|
|
16051
|
+
const expectedPayload = join23(dataRoot, `${PJANGLER_VERSION}-${expected.digest}`);
|
|
15593
16052
|
const probe = probeCanonicalSkillexRootProjection(skillsRoot, link);
|
|
15594
16053
|
if (probe.state === "declined") return { skill_installed: false, hooks_projected: false, details: probe.block.details, blocked: probe.block };
|
|
15595
|
-
const stat =
|
|
16054
|
+
const stat = lstatSync16(link);
|
|
15596
16055
|
if (!stat.isSymbolicLink()) {
|
|
15597
16056
|
return {
|
|
15598
16057
|
skill_installed: false,
|
|
@@ -15629,8 +16088,8 @@ function inspectProjectNotebookIntegration(env2 = process.env) {
|
|
|
15629
16088
|
}
|
|
15630
16089
|
}
|
|
15631
16090
|
function projectorArguments(source, command, input) {
|
|
15632
|
-
const script =
|
|
15633
|
-
const args = [script, command, "--master",
|
|
16091
|
+
const script = join23(source, "scripts", "project-hooks.py");
|
|
16092
|
+
const args = [script, command, "--master", join23(source, "hooks", "hooks.master.json"), "--fragment", join23(source, "hooks", "claude.settings.json"), "--target", input.target];
|
|
15634
16093
|
if (command === "check") args.push("--json");
|
|
15635
16094
|
else if (input.stateHome) args.push("--state-home", input.stateHome);
|
|
15636
16095
|
return args;
|
|
@@ -15652,7 +16111,7 @@ function checkProjectNotebookHooks(input = {}) {
|
|
|
15652
16111
|
verifyProjectNotebookSkillExport(source);
|
|
15653
16112
|
const home = env2.HOME;
|
|
15654
16113
|
if (!home) throw new NotebookError("NOT_CONFIGURED", "HOME is required to check Project Notebook hooks");
|
|
15655
|
-
const target = resolve16(input.target ?? env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ??
|
|
16114
|
+
const target = resolve16(input.target ?? env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ?? join23(home, ".claude", "settings.json"));
|
|
15656
16115
|
const result2 = spawnSync11("/usr/bin/python3", ["-I", ...projectorArguments(source, "check", { target })], { encoding: "utf8", env: projectorEnvironment(env2), timeout: 5e3, maxBuffer: 1048576 });
|
|
15657
16116
|
if (result2.status !== 0 && result2.status !== 1) throw new NotebookError("CONFLICT", (result2.stderr || "Project Notebook projector check failed").trim().slice(0, 512));
|
|
15658
16117
|
try {
|
|
@@ -15670,14 +16129,14 @@ function installProjectNotebookIntegration(input = {}) {
|
|
|
15670
16129
|
const source = realpathSync10(skill.path);
|
|
15671
16130
|
const home = env2.HOME;
|
|
15672
16131
|
if (!home) throw new NotebookError("NOT_CONFIGURED", "HOME is required to install Project Notebook hooks");
|
|
15673
|
-
const target = resolve16(input.target ?? env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ??
|
|
15674
|
-
const stateHome = resolve16(env2.XDG_STATE_HOME ||
|
|
16132
|
+
const target = resolve16(input.target ?? env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ?? join23(home, ".claude", "settings.json"));
|
|
16133
|
+
const stateHome = resolve16(env2.XDG_STATE_HOME || join23(home, ".local", "state"));
|
|
15675
16134
|
const result2 = spawnSync11("/usr/bin/python3", ["-I", ...projectorArguments(source, "install", { target, stateHome })], { encoding: "utf8", env: projectorEnvironment(env2), timeout: 5e3, maxBuffer: 1048576 });
|
|
15676
16135
|
if (result2.status !== 0) {
|
|
15677
16136
|
if (skill.installed) {
|
|
15678
16137
|
try {
|
|
15679
|
-
const stat =
|
|
15680
|
-
if (stat.isSymbolicLink() && realpathSync10(skill.path) === source)
|
|
16138
|
+
const stat = lstatSync16(skill.path);
|
|
16139
|
+
if (stat.isSymbolicLink() && realpathSync10(skill.path) === source) unlinkSync9(skill.path);
|
|
15681
16140
|
} catch {
|
|
15682
16141
|
}
|
|
15683
16142
|
}
|
|
@@ -15722,10 +16181,10 @@ function assertNoSymlinkComponents(path, allowMissing = false) {
|
|
|
15722
16181
|
const root = parse3(absolute).root;
|
|
15723
16182
|
let cursor = root;
|
|
15724
16183
|
for (const component of absolute.slice(root.length).split(sep7).filter(Boolean)) {
|
|
15725
|
-
cursor =
|
|
16184
|
+
cursor = join23(cursor, component);
|
|
15726
16185
|
let stat;
|
|
15727
16186
|
try {
|
|
15728
|
-
stat =
|
|
16187
|
+
stat = lstatSync16(cursor);
|
|
15729
16188
|
} catch (error) {
|
|
15730
16189
|
if (allowMissing && error.code === "ENOENT") return;
|
|
15731
16190
|
throw error;
|
|
@@ -15744,18 +16203,18 @@ function readHookPayload(input) {
|
|
|
15744
16203
|
if (rel === ".." || rel.startsWith(`..${sep7}`) || rel.startsWith(sep7)) throw new NotebookError("INVALID_INPUT", "Hook payload file is outside Notebook XDG state");
|
|
15745
16204
|
assertNoSymlinkComponents(root);
|
|
15746
16205
|
assertNoSymlinkComponents(path);
|
|
15747
|
-
const before =
|
|
16206
|
+
const before = lstatSync16(path);
|
|
15748
16207
|
if (!before.isFile() || before.isSymbolicLink() || (before.mode & 511) !== 384 || before.nlink !== 1) throw new NotebookError("INVALID_INPUT", "Hook payload file must be a contained mode-0600 regular file");
|
|
15749
16208
|
if (typeof process.getuid === "function" && before.uid !== process.getuid()) throw new NotebookError("INVALID_INPUT", "Hook payload file must be owned by the current user");
|
|
15750
16209
|
if (before.size > input.maxBytes) throw new NotebookError("INVALID_INPUT", "Hook payload file exceeds its configured ceiling");
|
|
15751
|
-
const fd =
|
|
16210
|
+
const fd = openSync7(path, constants6.O_RDONLY | (constants6.O_NOFOLLOW ?? 0));
|
|
15752
16211
|
try {
|
|
15753
|
-
const opened =
|
|
16212
|
+
const opened = fstatSync4(fd);
|
|
15754
16213
|
if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino) throw new NotebookError("INVALID_INPUT", "Hook payload file changed while opening");
|
|
15755
16214
|
const value = readBoundedHookStdin(input.maxBytes, fd);
|
|
15756
16215
|
return { payload: parsePayload(value, input.maxBytes), bytes: Buffer.byteLength(value, "utf8") };
|
|
15757
16216
|
} finally {
|
|
15758
|
-
|
|
16217
|
+
closeSync7(fd);
|
|
15759
16218
|
}
|
|
15760
16219
|
}
|
|
15761
16220
|
var ALLOWED_CLIENT_NAMES = /* @__PURE__ */ new Set([
|
|
@@ -16482,17 +16941,17 @@ init_remote_mutation_journal();
|
|
|
16482
16941
|
function integrationEnvironment(module, ctx) {
|
|
16483
16942
|
const env2 = { ...module.environment, HOME: ctx.homeDir };
|
|
16484
16943
|
if (module.environment.HOME !== ctx.homeDir) {
|
|
16485
|
-
env2.XDG_DATA_HOME =
|
|
16486
|
-
env2.XDG_STATE_HOME =
|
|
16487
|
-
env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS =
|
|
16944
|
+
env2.XDG_DATA_HOME = join24(ctx.homeDir, ".local", "share");
|
|
16945
|
+
env2.XDG_STATE_HOME = join24(ctx.homeDir, ".local", "state");
|
|
16946
|
+
env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS = join24(ctx.homeDir, ".claude", "settings.json");
|
|
16488
16947
|
}
|
|
16489
16948
|
return env2;
|
|
16490
16949
|
}
|
|
16491
16950
|
function resolvedForPlan(plan) {
|
|
16492
|
-
const manifestPath =
|
|
16951
|
+
const manifestPath = join24(plan.project.repo_path, ".project.json");
|
|
16493
16952
|
let manifest = plan.manifest;
|
|
16494
16953
|
if (existsSync17(manifestPath)) {
|
|
16495
|
-
const parsed = JSON.parse(
|
|
16954
|
+
const parsed = JSON.parse(readFileSync22(manifestPath, "utf8"));
|
|
16496
16955
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`${manifestPath} must contain a JSON object`);
|
|
16497
16956
|
manifest = parsed;
|
|
16498
16957
|
}
|
|
@@ -16669,7 +17128,7 @@ function unsafeToRemove(targetDir) {
|
|
|
16669
17128
|
while (!seen.has(cursor)) {
|
|
16670
17129
|
seen.add(cursor);
|
|
16671
17130
|
try {
|
|
16672
|
-
const stat =
|
|
17131
|
+
const stat = lstatSync17(cursor);
|
|
16673
17132
|
if (stat.isSymbolicLink()) {
|
|
16674
17133
|
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`;
|
|
16675
17134
|
}
|
|
@@ -16697,12 +17156,12 @@ function publicMigration(report) {
|
|
|
16697
17156
|
};
|
|
16698
17157
|
}
|
|
16699
17158
|
function hasGitRepository(runtime, targetDir) {
|
|
16700
|
-
if (!existsSync18(
|
|
17159
|
+
if (!existsSync18(join25(targetDir, ".git"))) return false;
|
|
16701
17160
|
return runtime.runGit(targetDir, ["rev-parse", "--is-inside-work-tree"]).status === 0;
|
|
16702
17161
|
}
|
|
16703
17162
|
function refreshPlanFromCanonicalManifest(plan) {
|
|
16704
|
-
const manifestPath =
|
|
16705
|
-
const manifest = JSON.parse(
|
|
17163
|
+
const manifestPath = join25(plan.project.repo_path, ".project.json");
|
|
17164
|
+
const manifest = JSON.parse(readFileSync23(manifestPath, "utf8"));
|
|
16706
17165
|
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
16707
17166
|
throw new Error(`${manifestPath} must contain a JSON object`);
|
|
16708
17167
|
}
|
|
@@ -16916,12 +17375,12 @@ var ProjectRecipe = class extends Recipe {
|
|
|
16916
17375
|
changedFiles: result2.status === "applied" || result2.status === "partial" ? result2.changedFiles : [],
|
|
16917
17376
|
message: result2.summary
|
|
16918
17377
|
})));
|
|
16919
|
-
errors.push(...migrationReport.results.filter((result2) => result2.status === "blocked" || result2.status === "partial").map((result2) =>
|
|
17378
|
+
errors.push(...migrationReport.results.filter((result2) => result2.status === "blocked" || result2.status === "partial").map((result2) => describeFinding(result2)));
|
|
16920
17379
|
}
|
|
16921
17380
|
const eligibilityAudit = errors.length === 0 ? publicAudit(await this.registry.auditRecipes({ ...transactionContext, dryRun: true })) : void 0;
|
|
16922
17381
|
audit = eligibilityAudit;
|
|
16923
17382
|
if (eligibilityAudit && !eligibilityAudit.ok) {
|
|
16924
|
-
errors.push(...eligibilityAudit.rules.filter((finding2) => gatesProject(finding2)).map((finding2) =>
|
|
17383
|
+
errors.push(...eligibilityAudit.rules.filter((finding2) => gatesProject(finding2)).map((finding2) => describeFinding(finding2)));
|
|
16925
17384
|
}
|
|
16926
17385
|
phases.push({
|
|
16927
17386
|
id: "project.audit:eligibility",
|
|
@@ -16933,7 +17392,7 @@ var ProjectRecipe = class extends Recipe {
|
|
|
16933
17392
|
if (hasGitRepository(this.runtime, targetDir)) {
|
|
16934
17393
|
phases.push({ id: "project.git", status: "unchanged", changedFiles: [], message: "Git repository already initialized" });
|
|
16935
17394
|
} else {
|
|
16936
|
-
const gitPath =
|
|
17395
|
+
const gitPath = join25(targetDir, ".git");
|
|
16937
17396
|
for (const { args, label, options } of [
|
|
16938
17397
|
{ args: ["init", "--initial-branch=main"], label: "git init" },
|
|
16939
17398
|
{ args: ["add", "-A"], label: "git add" },
|
|
@@ -17012,7 +17471,7 @@ var ProjectRecipe = class extends Recipe {
|
|
|
17012
17471
|
transactionContext.notebookManifestBinding = projectedNotebook?.binding ? Object.freeze({ ...projectedNotebook.binding }) : null;
|
|
17013
17472
|
transactionContext.notebookObservation = Object.freeze(await notebookRecipe.observeExternal(plan, notebookPlan));
|
|
17014
17473
|
const candidateAudit = publicAudit(await this.registry.auditRecipes(transactionContext, ["notebook"]));
|
|
17015
|
-
if (!candidateAudit.ok) errors.push(...candidateAudit.rules.filter((item) => item.status === "fail" || item.status === "warn").map((item) =>
|
|
17474
|
+
if (!candidateAudit.ok) errors.push(...candidateAudit.rules.filter((item) => item.status === "fail" || item.status === "warn").map((item) => describeFinding(item)));
|
|
17016
17475
|
}
|
|
17017
17476
|
phases.push({
|
|
17018
17477
|
id: "project.external:notebook",
|
|
@@ -17068,7 +17527,7 @@ var ProjectRecipe = class extends Recipe {
|
|
|
17068
17527
|
}
|
|
17069
17528
|
audit = errors.length === 0 ? publicAudit(await this.registry.auditRecipes({ ...transactionContext, dryRun: true })) : audit;
|
|
17070
17529
|
if (errors.length === 0 && audit && !audit.ok) {
|
|
17071
|
-
errors.push(...audit.rules.filter((finding2) => gatesProject(finding2)).map((finding2) =>
|
|
17530
|
+
errors.push(...audit.rules.filter((finding2) => gatesProject(finding2)).map((finding2) => describeFinding(finding2)));
|
|
17072
17531
|
}
|
|
17073
17532
|
phases.push({
|
|
17074
17533
|
id: "project.audit",
|
|
@@ -17144,12 +17603,12 @@ var ProjectRecipe = class extends Recipe {
|
|
|
17144
17603
|
};
|
|
17145
17604
|
|
|
17146
17605
|
// src/recipes/registry.ts
|
|
17147
|
-
import { readFileSync as
|
|
17148
|
-
import { join as
|
|
17606
|
+
import { readFileSync as readFileSync24 } from "node:fs";
|
|
17607
|
+
import { join as join26 } from "node:path";
|
|
17149
17608
|
function projectWaivers(repoRoot) {
|
|
17150
17609
|
let waive;
|
|
17151
17610
|
try {
|
|
17152
|
-
waive = JSON.parse(
|
|
17611
|
+
waive = JSON.parse(readFileSync24(join26(repoRoot, ".project.json"), "utf8"))?.parity?.waive;
|
|
17153
17612
|
} catch {
|
|
17154
17613
|
return /* @__PURE__ */ new Map();
|
|
17155
17614
|
}
|
|
@@ -17490,8 +17949,8 @@ var recipeRegistry = new RecipeRegistry([
|
|
|
17490
17949
|
|
|
17491
17950
|
// src/commands/AgentHooksCommands.ts
|
|
17492
17951
|
import { homedir as homedir10 } from "node:os";
|
|
17493
|
-
import { join as
|
|
17494
|
-
import { existsSync as existsSync19, cpSync, mkdirSync as
|
|
17952
|
+
import { join as join27, dirname as dirname19 } from "node:path";
|
|
17953
|
+
import { existsSync as existsSync19, cpSync, mkdirSync as mkdirSync11, readFileSync as readFileSync25, writeFileSync as writeFileSync14 } from "node:fs";
|
|
17495
17954
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
17496
17955
|
init_project();
|
|
17497
17956
|
import { parse as parseToml4 } from "smol-toml";
|
|
@@ -17504,16 +17963,16 @@ function resolveTemplateRoot() {
|
|
|
17504
17963
|
try {
|
|
17505
17964
|
let dir = dirname19(fileURLToPath4(import.meta.url));
|
|
17506
17965
|
for (let i = 0; i < 8; i++) {
|
|
17507
|
-
candidates.push(
|
|
17966
|
+
candidates.push(join27(dir, "templates", "commonproject", "template"));
|
|
17508
17967
|
const parent = dirname19(dir);
|
|
17509
17968
|
if (parent === dir) break;
|
|
17510
17969
|
dir = parent;
|
|
17511
17970
|
}
|
|
17512
17971
|
} catch {
|
|
17513
17972
|
}
|
|
17514
|
-
candidates.push(
|
|
17973
|
+
candidates.push(join27(homedir10(), "code", "pjangler", "templates", "commonproject", "template"));
|
|
17515
17974
|
for (const c of candidates) {
|
|
17516
|
-
if (existsSync19(
|
|
17975
|
+
if (existsSync19(join27(c, ".agents", "hooks", "hooks.master.json"))) return c;
|
|
17517
17976
|
}
|
|
17518
17977
|
throw new Error(
|
|
17519
17978
|
"Could not locate the CommonProject template. Set PJANGLER_COMMONPROJECT_TEMPLATE to <repo>/templates/commonproject/template."
|
|
@@ -17539,15 +17998,15 @@ var CopyAgentHooksTree = class extends Command {
|
|
|
17539
17998
|
const created = [];
|
|
17540
17999
|
const skipped = [];
|
|
17541
18000
|
for (const { rel, dir } of items) {
|
|
17542
|
-
const src =
|
|
17543
|
-
const dest =
|
|
18001
|
+
const src = join27(templateRoot, rel);
|
|
18002
|
+
const dest = join27(this.context.targetDir, rel);
|
|
17544
18003
|
if (!existsSync19(src)) continue;
|
|
17545
18004
|
if (existsSync19(dest) && !this.context.force) {
|
|
17546
18005
|
skipped.push(rel);
|
|
17547
18006
|
continue;
|
|
17548
18007
|
}
|
|
17549
18008
|
if (!this.context.dryRun) {
|
|
17550
|
-
|
|
18009
|
+
mkdirSync11(dirname19(dest), { recursive: true });
|
|
17551
18010
|
cpSync(src, dest, { recursive: dir, force: true });
|
|
17552
18011
|
}
|
|
17553
18012
|
created.push(rel);
|
|
@@ -17568,14 +18027,14 @@ var WireMiseAgentHooks = class _WireMiseAgentHooks extends Command {
|
|
|
17568
18027
|
if (!resolveAgentHooksLayer2()) {
|
|
17569
18028
|
return { success: true, message: this.formatMessage(AGENT_HOOKS_SKIP_MESSAGE) };
|
|
17570
18029
|
}
|
|
17571
|
-
const misePath =
|
|
18030
|
+
const misePath = join27(this.context.targetDir, "mise.toml");
|
|
17572
18031
|
if (!existsSync19(misePath)) {
|
|
17573
18032
|
return {
|
|
17574
18033
|
success: false,
|
|
17575
18034
|
message: "\u26A0\uFE0F No mise.toml found \u2014 run `pj add mise` first, then re-run."
|
|
17576
18035
|
};
|
|
17577
18036
|
}
|
|
17578
|
-
let content =
|
|
18037
|
+
let content = readFileSync25(misePath, "utf8");
|
|
17579
18038
|
if (content.includes(_WireMiseAgentHooks.MARKER)) {
|
|
17580
18039
|
return { success: true, message: this.formatMessage("\u2713 mise.toml already wired for agent-hooks") };
|
|
17581
18040
|
}
|
|
@@ -17659,7 +18118,7 @@ ${leaveBlock}`);
|
|
|
17659
18118
|
if (!verdict.ok) {
|
|
17660
18119
|
return { success: false, message: `\u26A0\uFE0F mise.toml was not rewritten: ${verdict.reason}. Add the agent-hooks tasks by hand.` };
|
|
17661
18120
|
}
|
|
17662
|
-
if (!this.context.dryRun)
|
|
18121
|
+
if (!this.context.dryRun) writeFileSync14(misePath, content);
|
|
17663
18122
|
if (wiredHooks) {
|
|
17664
18123
|
return { success: true, message: this.formatMessage("\u2705 Wired mise.toml ([hooks] enter/leave + tasks)") };
|
|
17665
18124
|
}
|
|
@@ -17790,7 +18249,7 @@ if __name__ == "__main__":
|
|
|
17790
18249
|
|
|
17791
18250
|
// src/commands/AddMiseCodegraphScript.ts
|
|
17792
18251
|
import { chmodSync as chmodSync7 } from "fs";
|
|
17793
|
-
import { join as
|
|
18252
|
+
import { join as join28 } from "path";
|
|
17794
18253
|
var AddMiseCodegraphScript = class extends Command {
|
|
17795
18254
|
async invoke() {
|
|
17796
18255
|
const filePath = ".mise/scripts/codegraph.sh";
|
|
@@ -17844,7 +18303,7 @@ fi
|
|
|
17844
18303
|
`;
|
|
17845
18304
|
this.writeFile(filePath, content);
|
|
17846
18305
|
if (!this.context.dryRun) {
|
|
17847
|
-
chmodSync7(
|
|
18306
|
+
chmodSync7(join28(this.context.targetDir, filePath), 493);
|
|
17848
18307
|
}
|
|
17849
18308
|
return {
|
|
17850
18309
|
success: true,
|
|
@@ -17882,12 +18341,12 @@ SECRET_KEY=""
|
|
|
17882
18341
|
// src/parity/index.ts
|
|
17883
18342
|
import { existsSync as existsSync20 } from "node:fs";
|
|
17884
18343
|
import { homedir as homedir11 } from "node:os";
|
|
17885
|
-
import { dirname as dirname20, join as
|
|
18344
|
+
import { dirname as dirname20, join as join29, resolve as resolve20 } from "node:path";
|
|
17886
18345
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
17887
18346
|
function resolvePjanglerRoot2() {
|
|
17888
18347
|
let dir = dirname20(fileURLToPath5(import.meta.url));
|
|
17889
18348
|
while (dir !== dirname20(dir)) {
|
|
17890
|
-
if (existsSync20(
|
|
18349
|
+
if (existsSync20(join29(dir, "package.json")) && existsSync20(join29(dir, "templates", "commonproject", "copier.yml"))) return dir;
|
|
17891
18350
|
dir = dirname20(dir);
|
|
17892
18351
|
}
|
|
17893
18352
|
return resolve20(process.cwd());
|
|
@@ -18079,13 +18538,13 @@ init_project();
|
|
|
18079
18538
|
// src/project/info.ts
|
|
18080
18539
|
init_boardUrl();
|
|
18081
18540
|
init_project();
|
|
18082
|
-
import { readFileSync as
|
|
18083
|
-
import { join as
|
|
18541
|
+
import { readFileSync as readFileSync26 } from "node:fs";
|
|
18542
|
+
import { join as join30, resolve as resolve21 } from "node:path";
|
|
18084
18543
|
function readManifest2(root) {
|
|
18085
|
-
const path =
|
|
18544
|
+
const path = join30(root, ".project.json");
|
|
18086
18545
|
let manifest;
|
|
18087
18546
|
try {
|
|
18088
|
-
const value = JSON.parse(
|
|
18547
|
+
const value = JSON.parse(readFileSync26(path, "utf8"));
|
|
18089
18548
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("expected a JSON object");
|
|
18090
18549
|
manifest = value;
|
|
18091
18550
|
} catch (error) {
|
|
@@ -18126,7 +18585,7 @@ function readProjectInfo(input = {}) {
|
|
|
18126
18585
|
ok: true,
|
|
18127
18586
|
project_id: projectId,
|
|
18128
18587
|
repo_path: resolve21(root),
|
|
18129
|
-
manifest_path:
|
|
18588
|
+
manifest_path: join30(resolve21(root), ".project.json"),
|
|
18130
18589
|
manifest,
|
|
18131
18590
|
registry: { location, status: "indexed" }
|
|
18132
18591
|
};
|
|
@@ -18175,20 +18634,20 @@ init_project();
|
|
|
18175
18634
|
init_version();
|
|
18176
18635
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
|
|
18177
18636
|
import {
|
|
18178
|
-
closeSync as
|
|
18637
|
+
closeSync as closeSync8,
|
|
18179
18638
|
existsSync as existsSync21,
|
|
18180
|
-
fchmodSync as
|
|
18639
|
+
fchmodSync as fchmodSync5,
|
|
18181
18640
|
fsyncSync as fsyncSync5,
|
|
18182
|
-
lstatSync as
|
|
18183
|
-
mkdirSync as
|
|
18184
|
-
openSync as
|
|
18185
|
-
readFileSync as
|
|
18641
|
+
lstatSync as lstatSync18,
|
|
18642
|
+
mkdirSync as mkdirSync12,
|
|
18643
|
+
openSync as openSync8,
|
|
18644
|
+
readFileSync as readFileSync27,
|
|
18186
18645
|
renameSync as renameSync9,
|
|
18187
|
-
unlinkSync as
|
|
18188
|
-
writeFileSync as
|
|
18646
|
+
unlinkSync as unlinkSync10,
|
|
18647
|
+
writeFileSync as writeFileSync15
|
|
18189
18648
|
} from "node:fs";
|
|
18190
18649
|
import { homedir as homedir12 } from "node:os";
|
|
18191
|
-
import { dirname as dirname21, join as
|
|
18650
|
+
import { dirname as dirname21, join as join31, resolve as resolve22 } from "node:path";
|
|
18192
18651
|
import YAML7 from "yaml";
|
|
18193
18652
|
var DEAD_AGENT_IDS = ["coachingagentframework-pm"];
|
|
18194
18653
|
function isRecord6(value) {
|
|
@@ -18196,7 +18655,7 @@ function isRecord6(value) {
|
|
|
18196
18655
|
}
|
|
18197
18656
|
function hermesAgentsRegistryPath(env2 = process.env, home = homedir12()) {
|
|
18198
18657
|
const override = env2.HERMES_AGENTS_REGISTRY?.trim();
|
|
18199
|
-
return override ||
|
|
18658
|
+
return override || join31(home, ".hermes", "agents-registry.yaml");
|
|
18200
18659
|
}
|
|
18201
18660
|
function planeApiKeyFor(workspace, env2, home) {
|
|
18202
18661
|
return resolvePlaneApiKey(workspace, env2, home) ?? resolvePlaneApiKey(DEFAULT_PLANE_WORKSPACE, env2, home);
|
|
@@ -18207,7 +18666,7 @@ async function fetchPlaneBoards(workspace, env2 = process.env, home = homedir12(
|
|
|
18207
18666
|
const apiKey = planeApiKeyFor(workspace, env2, home);
|
|
18208
18667
|
if (!apiKey) {
|
|
18209
18668
|
throw new BoardError(
|
|
18210
|
-
`no Plane API key for workspace "${workspace}": set PLANE_API_KEY, or add one to ${env2.HERMES_FLEET_ENV?.trim() ||
|
|
18669
|
+
`no Plane API key for workspace "${workspace}": set PLANE_API_KEY, or add one to ${env2.HERMES_FLEET_ENV?.trim() || join31(home, ".hermes", "fleet.env")}`
|
|
18211
18670
|
);
|
|
18212
18671
|
}
|
|
18213
18672
|
const base = planeBase(env2, home);
|
|
@@ -18256,7 +18715,7 @@ async function patchPlaneBoardIdentifier(workspace, boardId, identifier, env2 =
|
|
|
18256
18715
|
const apiKey = planeApiKeyFor(workspace, env2, home);
|
|
18257
18716
|
if (!apiKey) {
|
|
18258
18717
|
throw new BoardError(
|
|
18259
|
-
`no Plane API key for workspace "${workspace}": set PLANE_API_KEY, or add one to ${env2.HERMES_FLEET_ENV?.trim() ||
|
|
18718
|
+
`no Plane API key for workspace "${workspace}": set PLANE_API_KEY, or add one to ${env2.HERMES_FLEET_ENV?.trim() || join31(home, ".hermes", "fleet.env")}`
|
|
18260
18719
|
);
|
|
18261
18720
|
}
|
|
18262
18721
|
const base = planeBase(env2, home);
|
|
@@ -18290,7 +18749,7 @@ async function patchPlaneBoardIdentifier(workspace, boardId, identifier, env2 =
|
|
|
18290
18749
|
}
|
|
18291
18750
|
}
|
|
18292
18751
|
function trelloCredential(name, env2, home) {
|
|
18293
|
-
const fleetEnv = env2.HERMES_FLEET_ENV?.trim() ||
|
|
18752
|
+
const fleetEnv = env2.HERMES_FLEET_ENV?.trim() || join31(home, ".hermes", "fleet.env");
|
|
18294
18753
|
const raw = env2[name]?.trim() || dotenvValue(fleetEnv, name)?.trim();
|
|
18295
18754
|
return raw ? resolveSecretValue(raw) : void 0;
|
|
18296
18755
|
}
|
|
@@ -18328,7 +18787,7 @@ async function fetchTrelloBoard(boardId, env2 = process.env, home = homedir12())
|
|
|
18328
18787
|
};
|
|
18329
18788
|
}
|
|
18330
18789
|
function readHermesAgentBoards(path) {
|
|
18331
|
-
const document = YAML7.parseDocument(
|
|
18790
|
+
const document = YAML7.parseDocument(readFileSync27(path, "utf8"));
|
|
18332
18791
|
if (document.errors.length) throw new Error(`Hermes agents registry YAML is invalid: ${path}`);
|
|
18333
18792
|
const parsed = document.toJS();
|
|
18334
18793
|
const agents = isRecord6(parsed) && isRecord6(parsed.agents) ? parsed.agents : {};
|
|
@@ -18359,11 +18818,11 @@ function matchRegistrySlug(agent, registry) {
|
|
|
18359
18818
|
}
|
|
18360
18819
|
function readManifestBoard(repoPath) {
|
|
18361
18820
|
if (!repoPath) return void 0;
|
|
18362
|
-
const path =
|
|
18821
|
+
const path = join31(repoPath, ".project.json");
|
|
18363
18822
|
if (!existsSync21(path)) return void 0;
|
|
18364
18823
|
let parsed;
|
|
18365
18824
|
try {
|
|
18366
|
-
parsed = JSON.parse(
|
|
18825
|
+
parsed = JSON.parse(readFileSync27(path, "utf8"));
|
|
18367
18826
|
} catch {
|
|
18368
18827
|
return void 0;
|
|
18369
18828
|
}
|
|
@@ -18382,32 +18841,32 @@ function yamlStyleOf(original) {
|
|
|
18382
18841
|
return { indentSeq: !/\n([ \t]*)[^\s#][^\n]*:[ \t]*\n\1- /.test(original), lineWidth: 0 };
|
|
18383
18842
|
}
|
|
18384
18843
|
function atomicWrite(path, text2) {
|
|
18385
|
-
const mode = existsSync21(path) ?
|
|
18386
|
-
|
|
18844
|
+
const mode = existsSync21(path) ? lstatSync18(path).mode & 511 : 384;
|
|
18845
|
+
mkdirSync12(dirname21(path), { recursive: true });
|
|
18387
18846
|
const temp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
18388
18847
|
let fd;
|
|
18389
18848
|
try {
|
|
18390
|
-
fd =
|
|
18391
|
-
|
|
18392
|
-
|
|
18849
|
+
fd = openSync8(temp, "wx", mode);
|
|
18850
|
+
writeFileSync15(fd, text2, "utf8");
|
|
18851
|
+
fchmodSync5(fd, mode);
|
|
18393
18852
|
fsyncSync5(fd);
|
|
18394
|
-
|
|
18853
|
+
closeSync8(fd);
|
|
18395
18854
|
fd = void 0;
|
|
18396
18855
|
renameSync9(temp, path);
|
|
18397
18856
|
} catch (error) {
|
|
18398
18857
|
if (fd !== void 0) try {
|
|
18399
|
-
|
|
18858
|
+
closeSync8(fd);
|
|
18400
18859
|
} catch {
|
|
18401
18860
|
}
|
|
18402
18861
|
try {
|
|
18403
|
-
|
|
18862
|
+
unlinkSync10(temp);
|
|
18404
18863
|
} catch {
|
|
18405
18864
|
}
|
|
18406
18865
|
throw error;
|
|
18407
18866
|
}
|
|
18408
18867
|
}
|
|
18409
18868
|
function applyHermesIdentifiers(path, identifiers, deletions) {
|
|
18410
|
-
const original =
|
|
18869
|
+
const original = readFileSync27(path, "utf8");
|
|
18411
18870
|
const document = YAML7.parseDocument(original);
|
|
18412
18871
|
if (document.errors.length) throw new Error(`Hermes agents registry YAML is invalid: ${path}`);
|
|
18413
18872
|
const before = document.toJS();
|
|
@@ -18863,11 +19322,11 @@ function formatIdentityReport(report) {
|
|
|
18863
19322
|
return lines.join("\n");
|
|
18864
19323
|
}
|
|
18865
19324
|
function writeManifestTicketProvider(repoPath, provider, expectedProvider) {
|
|
18866
|
-
const path =
|
|
19325
|
+
const path = join31(repoPath, ".project.json");
|
|
18867
19326
|
if (!existsSync21(path)) return void 0;
|
|
18868
19327
|
let existing = {};
|
|
18869
19328
|
try {
|
|
18870
|
-
const parsed = JSON.parse(
|
|
19329
|
+
const parsed = JSON.parse(readFileSync27(path, "utf8"));
|
|
18871
19330
|
if (isRecord6(parsed)) existing = parsed;
|
|
18872
19331
|
} catch {
|
|
18873
19332
|
throw new BoardError(`${path} is not readable JSON; fix it before linking a board`);
|
|
@@ -18892,7 +19351,7 @@ function writeManifestTicketProvider(repoPath, provider, expectedProvider) {
|
|
|
18892
19351
|
};
|
|
18893
19352
|
const text2 = `${JSON.stringify(next, null, 2)}
|
|
18894
19353
|
`;
|
|
18895
|
-
if (
|
|
19354
|
+
if (readFileSync27(path, "utf8") === text2) return void 0;
|
|
18896
19355
|
atomicWrite(path, text2);
|
|
18897
19356
|
return path;
|
|
18898
19357
|
}
|
|
@@ -18904,8 +19363,8 @@ async function linkProjectBoard(options) {
|
|
|
18904
19363
|
const project = getProject(registry, options.slug);
|
|
18905
19364
|
const boardId = options.boardId.trim();
|
|
18906
19365
|
if (!boardId) throw new BoardError("a board id is required");
|
|
18907
|
-
const manifestFile =
|
|
18908
|
-
const expectedProvider = !isRegistryServiceLocation(registryPath) && existsSync21(manifestFile) ? JSON.parse(
|
|
19366
|
+
const manifestFile = join31(project.repo_path, ".project.json");
|
|
19367
|
+
const expectedProvider = !isRegistryServiceLocation(registryPath) && existsSync21(manifestFile) ? JSON.parse(readFileSync27(manifestFile, "utf8")).ticket_provider : void 0;
|
|
18909
19368
|
const current = project.ticket_provider;
|
|
18910
19369
|
const type = (current.type || "plane").trim().toLowerCase();
|
|
18911
19370
|
const workspace = (options.workspace ?? current.workspace ?? DEFAULT_PLANE_WORKSPACE).trim();
|
|
@@ -18956,10 +19415,10 @@ async function linkProjectBoard(options) {
|
|
|
18956
19415
|
if (apply) {
|
|
18957
19416
|
registry.projects[project.slug] = { ...project, ticket_provider: { ...current, ...block }, updated_at: now };
|
|
18958
19417
|
if (isRegistryServiceLocation(registryPath)) {
|
|
18959
|
-
const beforeManifest = existsSync21(manifestFile) ?
|
|
19418
|
+
const beforeManifest = existsSync21(manifestFile) ? readFileSync27(manifestFile, "utf8") : null;
|
|
18960
19419
|
saveProjectRegistry(registry, registryPath);
|
|
18961
19420
|
manifestPath = manifestFile;
|
|
18962
|
-
const afterManifest = existsSync21(manifestFile) ?
|
|
19421
|
+
const afterManifest = existsSync21(manifestFile) ? readFileSync27(manifestFile, "utf8") : null;
|
|
18963
19422
|
if (beforeManifest !== afterManifest) changedFiles.push(manifestFile);
|
|
18964
19423
|
} else {
|
|
18965
19424
|
manifestPath = writeManifestTicketProvider(project.repo_path, block, expectedProvider);
|
|
@@ -18968,7 +19427,7 @@ async function linkProjectBoard(options) {
|
|
|
18968
19427
|
changedFiles.push(registryPath);
|
|
18969
19428
|
}
|
|
18970
19429
|
} else {
|
|
18971
|
-
manifestPath = existsSync21(
|
|
19430
|
+
manifestPath = existsSync21(join31(project.repo_path, ".project.json")) ? join31(project.repo_path, ".project.json") : void 0;
|
|
18972
19431
|
}
|
|
18973
19432
|
return {
|
|
18974
19433
|
ok: true,
|
|
@@ -18992,9 +19451,9 @@ async function linkProjectBoard(options) {
|
|
|
18992
19451
|
// src/project/rename.ts
|
|
18993
19452
|
init_boardUrl();
|
|
18994
19453
|
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
18995
|
-
import { existsSync as existsSync22, readdirSync as
|
|
19454
|
+
import { existsSync as existsSync22, readdirSync as readdirSync12, readFileSync as readFileSync28, realpathSync as realpathSync11, statSync as statSync6 } from "node:fs";
|
|
18996
19455
|
import { homedir as homedir13 } from "node:os";
|
|
18997
|
-
import { isAbsolute as isAbsolute11, join as
|
|
19456
|
+
import { isAbsolute as isAbsolute11, join as join32, relative as relative16 } from "node:path";
|
|
18998
19457
|
import YAML8 from "yaml";
|
|
18999
19458
|
init_project();
|
|
19000
19459
|
init_registryClient();
|
|
@@ -19015,7 +19474,7 @@ function message(error) {
|
|
|
19015
19474
|
function readJsonObject(path) {
|
|
19016
19475
|
let parsed;
|
|
19017
19476
|
try {
|
|
19018
|
-
parsed = JSON.parse(
|
|
19477
|
+
parsed = JSON.parse(readFileSync28(path, "utf8"));
|
|
19019
19478
|
} catch {
|
|
19020
19479
|
throw new RenameError("manifest_invalid", `${path} is not readable JSON; fix it before renaming`);
|
|
19021
19480
|
}
|
|
@@ -19068,7 +19527,7 @@ async function renameProject(options) {
|
|
|
19068
19527
|
let from;
|
|
19069
19528
|
if (existing && oldInput) {
|
|
19070
19529
|
try {
|
|
19071
|
-
const candidate = readJsonObject(
|
|
19530
|
+
const candidate = readJsonObject(join32(existing.repo_path, ".project.json")).renamed_from;
|
|
19072
19531
|
if (isRecord7(candidate)) from = candidate;
|
|
19073
19532
|
} catch {
|
|
19074
19533
|
}
|
|
@@ -19087,7 +19546,7 @@ async function renameProject(options) {
|
|
|
19087
19546
|
const isPlane = type === "plane";
|
|
19088
19547
|
const workspace = isPlane ? String(provider.workspace ?? "").trim() || DEFAULT_PLANE_WORKSPACE : String(provider.workspace ?? "").trim();
|
|
19089
19548
|
const repoPath = record3.repo_path;
|
|
19090
|
-
const manifestPath =
|
|
19549
|
+
const manifestPath = join32(repoPath, ".project.json");
|
|
19091
19550
|
const manifestExists = existsSync22(manifestPath);
|
|
19092
19551
|
if (service && !manifestExists) {
|
|
19093
19552
|
throw new RenameError("manifest_missing", `${manifestPath} is missing; the registry service indexes manifests, so there is nothing to rename`);
|
|
@@ -19232,7 +19691,7 @@ async function renameProject(options) {
|
|
|
19232
19691
|
return { status: "done", detail, files: service ? [] : [registryPath] };
|
|
19233
19692
|
};
|
|
19234
19693
|
const roleStep = async (doApply) => {
|
|
19235
|
-
const hermesDir =
|
|
19694
|
+
const hermesDir = join32(repoPath, "agents", "hermes");
|
|
19236
19695
|
if (!existsSync22(hermesDir)) return { status: "skipped", detail: "no agents/hermes directory" };
|
|
19237
19696
|
if (!isPlane) return { status: "skipped", detail: "role.yaml plane.identifier is a Plane field" };
|
|
19238
19697
|
const files = [];
|
|
@@ -19240,16 +19699,16 @@ async function renameProject(options) {
|
|
|
19240
19699
|
let settled = 0;
|
|
19241
19700
|
let entries;
|
|
19242
19701
|
try {
|
|
19243
|
-
entries =
|
|
19702
|
+
entries = readdirSync12(hermesDir, { withFileTypes: true });
|
|
19244
19703
|
} catch (error) {
|
|
19245
19704
|
return { status: "failed", detail: `${hermesDir}: ${message(error)}` };
|
|
19246
19705
|
}
|
|
19247
19706
|
for (const entry of entries) {
|
|
19248
19707
|
if (!entry.isDirectory()) continue;
|
|
19249
|
-
const path =
|
|
19708
|
+
const path = join32(hermesDir, entry.name, "role.yaml");
|
|
19250
19709
|
if (!existsSync22(path)) continue;
|
|
19251
19710
|
try {
|
|
19252
|
-
const original =
|
|
19711
|
+
const original = readFileSync28(path, "utf8");
|
|
19253
19712
|
const document = YAML8.parseDocument(original);
|
|
19254
19713
|
if (document.errors.length) throw new Error("YAML is invalid");
|
|
19255
19714
|
const node = document.getIn(["plane", "identifier"], true);
|
|
@@ -19307,7 +19766,7 @@ async function renameProject(options) {
|
|
|
19307
19766
|
}
|
|
19308
19767
|
};
|
|
19309
19768
|
const agentsMdStep = async (doApply) => {
|
|
19310
|
-
const link =
|
|
19769
|
+
const link = join32(repoPath, "AGENTS.md");
|
|
19311
19770
|
if (!existsSync22(link)) return { status: "skipped", detail: "no AGENTS.md" };
|
|
19312
19771
|
try {
|
|
19313
19772
|
const path = realpathSync11(link);
|
|
@@ -19315,7 +19774,7 @@ async function renameProject(options) {
|
|
|
19315
19774
|
if (inside.startsWith("..") || isAbsolute11(inside)) {
|
|
19316
19775
|
return { status: "skipped", detail: `AGENTS.md resolves outside the repo (${path}); left untouched` };
|
|
19317
19776
|
}
|
|
19318
|
-
const original =
|
|
19777
|
+
const original = readFileSync28(path, "utf8");
|
|
19319
19778
|
const lines = original.split("\n");
|
|
19320
19779
|
let boardLines = 0;
|
|
19321
19780
|
let replaced = 0;
|
|
@@ -19345,8 +19804,8 @@ async function renameProject(options) {
|
|
|
19345
19804
|
}
|
|
19346
19805
|
};
|
|
19347
19806
|
const hintsStep = async (doApply) => {
|
|
19348
|
-
const configDir = env2.PILOT_CONFIG_DIR?.trim() ||
|
|
19349
|
-
const path =
|
|
19807
|
+
const configDir = env2.PILOT_CONFIG_DIR?.trim() || join32(env2.XDG_CONFIG_HOME?.trim() || join32(home, ".config"), "pilot");
|
|
19808
|
+
const path = join32(configDir, "harvest", "project-hints.json");
|
|
19350
19809
|
if (!existsSync22(path)) return { status: "skipped", detail: `no harvest hints at ${path}` };
|
|
19351
19810
|
try {
|
|
19352
19811
|
const hints = readJsonObject(path);
|
|
@@ -19380,7 +19839,7 @@ async function renameProject(options) {
|
|
|
19380
19839
|
if (oldInput === newId && steps.every((step) => step.status === "already" || step.status === "skipped")) {
|
|
19381
19840
|
return { status: "already", detail: "nothing changed; projection not re-synced" };
|
|
19382
19841
|
}
|
|
19383
|
-
const cwd =
|
|
19842
|
+
const cwd = join32(home, "code", "33GOD", "candystore");
|
|
19384
19843
|
try {
|
|
19385
19844
|
if (!statSync6(cwd).isDirectory()) throw new Error("not a directory");
|
|
19386
19845
|
} catch {
|
|
@@ -19475,7 +19934,7 @@ init_boardQuery();
|
|
|
19475
19934
|
// src/describe/activity.ts
|
|
19476
19935
|
import { spawn as spawn3, spawnSync as spawnSync16 } from "node:child_process";
|
|
19477
19936
|
import { statSync as statSync7 } from "node:fs";
|
|
19478
|
-
import { join as
|
|
19937
|
+
import { join as join33 } from "node:path";
|
|
19479
19938
|
var ACTIVE_WINDOW_SECONDS = 24 * 60 * 60;
|
|
19480
19939
|
var MAX_DIRTY_STATS = 500;
|
|
19481
19940
|
var GIT_TIMEOUT_MS = 5e3;
|
|
@@ -19640,7 +20099,7 @@ function uncommittedSource(repo, paths) {
|
|
|
19640
20099
|
let newest = 0;
|
|
19641
20100
|
for (const path of paths.slice(0, MAX_DIRTY_STATS)) {
|
|
19642
20101
|
try {
|
|
19643
|
-
const mtime = Math.floor(statSync7(
|
|
20102
|
+
const mtime = Math.floor(statSync7(join33(repo, path)).mtimeMs / 1e3);
|
|
19644
20103
|
if (mtime > newest) newest = mtime;
|
|
19645
20104
|
} catch {
|
|
19646
20105
|
}
|
|
@@ -19838,8 +20297,8 @@ function openUrl(url, env2 = process.env, platform = process.platform) {
|
|
|
19838
20297
|
}
|
|
19839
20298
|
|
|
19840
20299
|
// src/describe/index.ts
|
|
19841
|
-
import { existsSync as existsSync23, readFileSync as
|
|
19842
|
-
import { join as
|
|
20300
|
+
import { existsSync as existsSync23, readFileSync as readFileSync29, readdirSync as readdirSync13, statSync as statSync8 } from "node:fs";
|
|
20301
|
+
import { join as join34, resolve as resolve23 } from "node:path";
|
|
19843
20302
|
init_project();
|
|
19844
20303
|
init_config();
|
|
19845
20304
|
init_state();
|
|
@@ -19895,7 +20354,7 @@ var CONFIG_FILES = [
|
|
|
19895
20354
|
];
|
|
19896
20355
|
function readJson(path) {
|
|
19897
20356
|
try {
|
|
19898
|
-
const parsed = JSON.parse(
|
|
20357
|
+
const parsed = JSON.parse(readFileSync29(path, "utf8"));
|
|
19899
20358
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
19900
20359
|
} catch {
|
|
19901
20360
|
return void 0;
|
|
@@ -19917,23 +20376,23 @@ function describeType(repo) {
|
|
|
19917
20376
|
const evidence = [];
|
|
19918
20377
|
const note = (signal, file) => evidence.push(`${signal} (${file})`);
|
|
19919
20378
|
for (const marker of LANGUAGE_MARKERS) {
|
|
19920
|
-
if (!existsSync23(
|
|
20379
|
+
if (!existsSync23(join34(repo, marker.file))) continue;
|
|
19921
20380
|
if (!languages.includes(marker.language)) {
|
|
19922
20381
|
languages.push(marker.language);
|
|
19923
20382
|
note(marker.language, marker.file);
|
|
19924
20383
|
}
|
|
19925
20384
|
}
|
|
19926
20385
|
try {
|
|
19927
|
-
const dotnet =
|
|
20386
|
+
const dotnet = readdirSync13(repo).find((entry) => entry.endsWith(".csproj") || entry.endsWith(".sln"));
|
|
19928
20387
|
if (dotnet && !languages.includes("dotnet")) {
|
|
19929
20388
|
languages.push("dotnet");
|
|
19930
20389
|
note("dotnet", dotnet);
|
|
19931
20390
|
}
|
|
19932
20391
|
} catch {
|
|
19933
20392
|
}
|
|
19934
|
-
const pkg = readJson(
|
|
20393
|
+
const pkg = readJson(join34(repo, "package.json"));
|
|
19935
20394
|
if (pkg) {
|
|
19936
|
-
if (existsSync23(
|
|
20395
|
+
if (existsSync23(join34(repo, "tsconfig.json"))) {
|
|
19937
20396
|
const index = languages.indexOf("javascript");
|
|
19938
20397
|
if (index >= 0) languages.splice(index, 1);
|
|
19939
20398
|
if (!languages.includes("typescript")) {
|
|
@@ -19967,14 +20426,14 @@ function describeType(repo) {
|
|
|
19967
20426
|
["hermes-fleet-host", "agents/hermes"]
|
|
19968
20427
|
];
|
|
19969
20428
|
for (const [role, marker] of roleMarkers) {
|
|
19970
|
-
if (!existsSync23(
|
|
20429
|
+
if (!existsSync23(join34(repo, marker))) continue;
|
|
19971
20430
|
roles.push(role);
|
|
19972
20431
|
note(role, marker);
|
|
19973
20432
|
}
|
|
19974
20433
|
return { primaryLanguage: languages[0], languages, roles, evidence };
|
|
19975
20434
|
}
|
|
19976
20435
|
function describeIdentity(repo, registryPath) {
|
|
19977
|
-
const manifestPath =
|
|
20436
|
+
const manifestPath = join34(repo, ".project.json");
|
|
19978
20437
|
const manifest = readJson(manifestPath);
|
|
19979
20438
|
const drift = [];
|
|
19980
20439
|
let record3;
|
|
@@ -20067,12 +20526,12 @@ function describeSubsystems(repo, findings) {
|
|
|
20067
20526
|
const markers = SUBSYSTEM_MARKERS[metadata.id] ?? [];
|
|
20068
20527
|
const evidence = metadata.id === "notebook" ? (() => {
|
|
20069
20528
|
try {
|
|
20070
|
-
const manifest = JSON.parse(
|
|
20529
|
+
const manifest = JSON.parse(readFileSync29(join34(repo, ".project.json"), "utf8"));
|
|
20071
20530
|
return manifest.notebook && typeof manifest.notebook === "object" ? [".project.json#notebook"] : [];
|
|
20072
20531
|
} catch {
|
|
20073
20532
|
return [];
|
|
20074
20533
|
}
|
|
20075
|
-
})() : markers.filter((marker) => existsSync23(
|
|
20534
|
+
})() : markers.filter((marker) => existsSync23(join34(repo, marker)));
|
|
20076
20535
|
const rules = (byRecipe.get(metadata.id) ?? []).map((finding2) => ({
|
|
20077
20536
|
id: finding2.id,
|
|
20078
20537
|
title: finding2.title,
|
|
@@ -20091,7 +20550,7 @@ function describeNotebook(repo, registryPath) {
|
|
|
20091
20550
|
try {
|
|
20092
20551
|
const registry = loadProjectRegistry(registryPath);
|
|
20093
20552
|
const project = Object.values(registry.projects).find((entry) => resolve23(entry.repo_path) === resolve23(repo));
|
|
20094
|
-
const manifest = existsSync23(
|
|
20553
|
+
const manifest = existsSync23(join34(repo, ".project.json")) ? JSON.parse(readFileSync29(join34(repo, ".project.json"), "utf8")) : void 0;
|
|
20095
20554
|
const declared = Boolean(project?.notebook || manifest?.notebook && typeof manifest.notebook === "object");
|
|
20096
20555
|
if (!declared) return { declared: false, bindingState: null, notebookId: null, overviewNoteId: null, health: null, remoteCheck: "skip", captureAdmission: null };
|
|
20097
20556
|
const config = loadEffectiveNotebookConfig(repo, registryPath);
|
|
@@ -20109,7 +20568,7 @@ function describeNotebook(repo, registryPath) {
|
|
|
20109
20568
|
}
|
|
20110
20569
|
}
|
|
20111
20570
|
function describeConfigFiles(repo) {
|
|
20112
|
-
return CONFIG_FILES.filter((spec) => existsSync23(
|
|
20571
|
+
return CONFIG_FILES.filter((spec) => existsSync23(join34(repo, spec.path))).map((spec) => ({ path: spec.path, purpose: spec.purpose, subsystem: spec.subsystem }));
|
|
20113
20572
|
}
|
|
20114
20573
|
function describeNextSteps(description, findings) {
|
|
20115
20574
|
const steps = [];
|
|
@@ -20533,7 +20992,7 @@ init_style();
|
|
|
20533
20992
|
// src/notebook/cli.ts
|
|
20534
20993
|
init_project();
|
|
20535
20994
|
init_capture();
|
|
20536
|
-
import { closeSync as
|
|
20995
|
+
import { closeSync as closeSync9, constants as constants7, existsSync as existsSync24, fstatSync as fstatSync5, lstatSync as lstatSync19, openSync as openSync9, readSync as readSync4 } from "node:fs";
|
|
20537
20996
|
import { resolve as resolve24 } from "node:path";
|
|
20538
20997
|
import { createInterface } from "node:readline/promises";
|
|
20539
20998
|
|
|
@@ -20676,12 +21135,12 @@ async function execute(input) {
|
|
|
20676
21135
|
function readBoundedRegularFile(path, maxBytes) {
|
|
20677
21136
|
const absolute = resolve24(path);
|
|
20678
21137
|
if (!existsSync24(absolute)) throw new NotebookError("INVALID_INPUT", `File not found: ${absolute}`);
|
|
20679
|
-
const before =
|
|
21138
|
+
const before = lstatSync19(absolute);
|
|
20680
21139
|
if (!before.isFile() || before.isSymbolicLink()) throw new NotebookError("INVALID_INPUT", `File must be a regular non-symlink: ${absolute}`);
|
|
20681
21140
|
if (before.size > maxBytes) throw new NotebookError("INVALID_INPUT", `File exceeds the configured ceiling: ${absolute}`);
|
|
20682
|
-
const fd =
|
|
21141
|
+
const fd = openSync9(absolute, constants7.O_RDONLY | (constants7.O_NOFOLLOW ?? 0));
|
|
20683
21142
|
try {
|
|
20684
|
-
const opened =
|
|
21143
|
+
const opened = fstatSync5(fd);
|
|
20685
21144
|
if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino) throw new NotebookError("INVALID_INPUT", `File changed while opening: ${absolute}`);
|
|
20686
21145
|
const chunks = [];
|
|
20687
21146
|
let total = 0;
|
|
@@ -20699,7 +21158,7 @@ function readBoundedRegularFile(path, maxBytes) {
|
|
|
20699
21158
|
throw new NotebookError("INVALID_INPUT", `File must be valid UTF-8: ${absolute}`);
|
|
20700
21159
|
}
|
|
20701
21160
|
} finally {
|
|
20702
|
-
|
|
21161
|
+
closeSync9(fd);
|
|
20703
21162
|
}
|
|
20704
21163
|
}
|
|
20705
21164
|
function textInput(options, maxBytes) {
|
|
@@ -20951,14 +21410,14 @@ async function loadScreen(relativeToDist, base) {
|
|
|
20951
21410
|
init_boardQuery();
|
|
20952
21411
|
init_style();
|
|
20953
21412
|
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
20954
|
-
import { mkdirSync as
|
|
21413
|
+
import { mkdirSync as mkdirSync13, readFileSync as readFileSync31, realpathSync as realpathSync12, renameSync as renameSync10, writeFileSync as writeFileSync16 } from "node:fs";
|
|
20955
21414
|
import { homedir as homedir15 } from "node:os";
|
|
20956
|
-
import { dirname as dirname22, join as
|
|
21415
|
+
import { dirname as dirname22, join as join36 } from "node:path";
|
|
20957
21416
|
|
|
20958
21417
|
// src/crank/engine.ts
|
|
20959
|
-
import { closeSync as
|
|
21418
|
+
import { closeSync as closeSync10, constants as constants8, fstatSync as fstatSync6, openSync as openSync10, readFileSync as readFileSync30, readSync as readSync5 } from "node:fs";
|
|
20960
21419
|
import { homedir as homedir14 } from "node:os";
|
|
20961
|
-
import { join as
|
|
21420
|
+
import { join as join35 } from "node:path";
|
|
20962
21421
|
var ENGINE_DEFAULT_JOB_ID = "777c511a009c";
|
|
20963
21422
|
var WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
20964
21423
|
var TICK_MS = 5 * 60 * 1e3;
|
|
@@ -21058,9 +21517,9 @@ function parseEngineStatus(auditText, input) {
|
|
|
21058
21517
|
}
|
|
21059
21518
|
}
|
|
21060
21519
|
function readTail(path, maxBytes) {
|
|
21061
|
-
const fd =
|
|
21520
|
+
const fd = openSync10(path, constants8.O_RDONLY | constants8.O_NONBLOCK);
|
|
21062
21521
|
try {
|
|
21063
|
-
const stat =
|
|
21522
|
+
const stat = fstatSync6(fd);
|
|
21064
21523
|
if (!stat.isFile()) throw new Error(`${path} is not a regular file`);
|
|
21065
21524
|
const size = stat.size;
|
|
21066
21525
|
const length = Math.min(size, maxBytes);
|
|
@@ -21076,20 +21535,20 @@ function readTail(path, maxBytes) {
|
|
|
21076
21535
|
const newline = text2.indexOf("\n");
|
|
21077
21536
|
return newline === -1 ? "" : text2.slice(newline + 1);
|
|
21078
21537
|
} finally {
|
|
21079
|
-
|
|
21538
|
+
closeSync10(fd);
|
|
21080
21539
|
}
|
|
21081
21540
|
}
|
|
21082
21541
|
function engineSources(env2 = process.env) {
|
|
21083
|
-
const cronDir =
|
|
21542
|
+
const cronDir = join35(homedir14(), ".hermes", "profiles", "33god-pm", "cron");
|
|
21084
21543
|
return {
|
|
21085
|
-
auditPath: env2.PJ_CRANK_USAGE_AUDIT?.trim() ||
|
|
21086
|
-
jobsPath:
|
|
21544
|
+
auditPath: env2.PJ_CRANK_USAGE_AUDIT?.trim() || join35(cronDir, "usage_audit.jsonl"),
|
|
21545
|
+
jobsPath: join35(cronDir, "jobs.json"),
|
|
21087
21546
|
jobId: env2.PJ_CRANK_JOB_ID?.trim() || ENGINE_DEFAULT_JOB_ID
|
|
21088
21547
|
};
|
|
21089
21548
|
}
|
|
21090
21549
|
function readCap(jobsPath, jobId) {
|
|
21091
21550
|
try {
|
|
21092
|
-
const parsed = JSON.parse(
|
|
21551
|
+
const parsed = JSON.parse(readFileSync30(jobsPath, "utf8"));
|
|
21093
21552
|
const jobs = Array.isArray(parsed.jobs) ? parsed.jobs : [];
|
|
21094
21553
|
const job = jobs.find((j) => typeof j === "object" && j !== null && String(j.id ?? "") === jobId);
|
|
21095
21554
|
const economics = job?.economics;
|
|
@@ -21475,11 +21934,11 @@ function withTabOwners(tabs, panes, identify, claimed = []) {
|
|
|
21475
21934
|
}
|
|
21476
21935
|
var PANE_CWD_TTL_SECONDS = 6 * 3600;
|
|
21477
21936
|
function paneCwdCachePath(env2 = process.env) {
|
|
21478
|
-
return env2.ZCRANK_CWD_CACHE?.trim() ||
|
|
21937
|
+
return env2.ZCRANK_CWD_CACHE?.trim() || join36(homedir15(), ".local/state/zcrank/pane-cwds.json");
|
|
21479
21938
|
}
|
|
21480
21939
|
function loadCwdCache(path) {
|
|
21481
21940
|
try {
|
|
21482
|
-
const parsed = JSON.parse(
|
|
21941
|
+
const parsed = JSON.parse(readFileSync31(path, "utf8"));
|
|
21483
21942
|
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
|
|
21484
21943
|
} catch {
|
|
21485
21944
|
return {};
|
|
@@ -21487,9 +21946,9 @@ function loadCwdCache(path) {
|
|
|
21487
21946
|
}
|
|
21488
21947
|
function saveCwdCache(path, cache) {
|
|
21489
21948
|
try {
|
|
21490
|
-
|
|
21949
|
+
mkdirSync13(dirname22(path), { recursive: true });
|
|
21491
21950
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
21492
|
-
|
|
21951
|
+
writeFileSync16(tmp, JSON.stringify(cache));
|
|
21493
21952
|
renameSync10(tmp, path);
|
|
21494
21953
|
} catch {
|
|
21495
21954
|
}
|
|
@@ -21642,7 +22101,7 @@ function resolveByName(project, live, storedTabs) {
|
|
|
21642
22101
|
function projectNameFor(handle, registryName) {
|
|
21643
22102
|
if (registryName && registryName.trim()) return registryName.trim();
|
|
21644
22103
|
try {
|
|
21645
|
-
const manifest = JSON.parse(
|
|
22104
|
+
const manifest = JSON.parse(readFileSync31(join36(handle.root, ".project.json"), "utf8"));
|
|
21646
22105
|
const name = manifest.project_name;
|
|
21647
22106
|
if (typeof name === "string" && name.trim()) return name.trim();
|
|
21648
22107
|
} catch {
|
|
@@ -21653,7 +22112,7 @@ function projectAgentsFor(handle, registryAgents) {
|
|
|
21653
22112
|
const fromRecord = Object.entries(registryAgents ?? {}).map(([agentHandle, value]) => ({ handle: agentHandle, role: String(value?.role ?? "") }));
|
|
21654
22113
|
if (fromRecord.length) return fromRecord;
|
|
21655
22114
|
try {
|
|
21656
|
-
const manifest = JSON.parse(
|
|
22115
|
+
const manifest = JSON.parse(readFileSync31(join36(handle.root, ".project.json"), "utf8"));
|
|
21657
22116
|
const agents = manifest.agents;
|
|
21658
22117
|
if (agents && typeof agents === "object" && !Array.isArray(agents)) {
|
|
21659
22118
|
return Object.entries(agents).map(([agentHandle, value]) => ({ handle: agentHandle, role: typeof value?.role === "string" ? value.role : "" }));
|
|
@@ -21971,9 +22430,9 @@ function createCrankController(deps) {
|
|
|
21971
22430
|
}
|
|
21972
22431
|
|
|
21973
22432
|
// src/crank/panes.ts
|
|
21974
|
-
import { readFileSync as
|
|
22433
|
+
import { readFileSync as readFileSync32, readdirSync as readdirSync14, readlinkSync as readlinkSync9 } from "node:fs";
|
|
21975
22434
|
import { homedir as homedir16 } from "node:os";
|
|
21976
|
-
import { basename as
|
|
22435
|
+
import { basename as basename12, join as join37 } from "node:path";
|
|
21977
22436
|
var AGENT_CLIS = [
|
|
21978
22437
|
"claude",
|
|
21979
22438
|
"codex",
|
|
@@ -22055,7 +22514,7 @@ function parseProcStat(text2) {
|
|
|
22055
22514
|
}
|
|
22056
22515
|
function readProcStat(pid) {
|
|
22057
22516
|
try {
|
|
22058
|
-
return parseProcStat(
|
|
22517
|
+
return parseProcStat(readFileSync32(`/proc/${pid}/stat`, "latin1"));
|
|
22059
22518
|
} catch {
|
|
22060
22519
|
return null;
|
|
22061
22520
|
}
|
|
@@ -22063,7 +22522,7 @@ function readProcStat(pid) {
|
|
|
22063
22522
|
function readTag(pid) {
|
|
22064
22523
|
let raw;
|
|
22065
22524
|
try {
|
|
22066
|
-
raw =
|
|
22525
|
+
raw = readFileSync32(`/proc/${pid}/environ`);
|
|
22067
22526
|
} catch {
|
|
22068
22527
|
return null;
|
|
22069
22528
|
}
|
|
@@ -22089,8 +22548,8 @@ function readTag(pid) {
|
|
|
22089
22548
|
}
|
|
22090
22549
|
function readArgv(pid) {
|
|
22091
22550
|
try {
|
|
22092
|
-
const parts =
|
|
22093
|
-
return { argv: parts.join(" "), exe: parts[0] ?
|
|
22551
|
+
const parts = readFileSync32(`/proc/${pid}/cmdline`, "utf8").split("\0").filter(Boolean);
|
|
22552
|
+
return { argv: parts.join(" "), exe: parts[0] ? basename12(parts[0]) : "" };
|
|
22094
22553
|
} catch {
|
|
22095
22554
|
return { argv: "", exe: "" };
|
|
22096
22555
|
}
|
|
@@ -22130,7 +22589,7 @@ var ProcScanner = class {
|
|
|
22130
22589
|
pids = [...this.enumerate()];
|
|
22131
22590
|
} else {
|
|
22132
22591
|
try {
|
|
22133
|
-
pids =
|
|
22592
|
+
pids = readdirSync14("/proc").filter((name) => /^\d+$/.test(name)).map(Number);
|
|
22134
22593
|
} catch {
|
|
22135
22594
|
return [];
|
|
22136
22595
|
}
|
|
@@ -22166,13 +22625,13 @@ var ProcScanner = class {
|
|
|
22166
22625
|
var PANE_STATUSES = ["busy", "idle", "shell"];
|
|
22167
22626
|
function claudeSessionsDir(env2 = process.env) {
|
|
22168
22627
|
const override = env2.PJ_CLAUDE_SESSIONS_DIR?.trim();
|
|
22169
|
-
return override ||
|
|
22628
|
+
return override || join37(homedir16(), ".claude", "sessions");
|
|
22170
22629
|
}
|
|
22171
22630
|
function readClaudeSession(pid, stat, dir = claudeSessionsDir()) {
|
|
22172
22631
|
const none = (reason) => ({ kind: null, sessionId: null, reason, status: "unknown", statusUpdatedAt: null });
|
|
22173
22632
|
let raw;
|
|
22174
22633
|
try {
|
|
22175
|
-
raw =
|
|
22634
|
+
raw = readFileSync32(join37(dir, `${pid}.json`), "utf8");
|
|
22176
22635
|
} catch {
|
|
22177
22636
|
return none("no-session-file");
|
|
22178
22637
|
}
|
|
@@ -22633,7 +23092,7 @@ async function promptForRuleIds(rules) {
|
|
|
22633
23092
|
function readJson2(path) {
|
|
22634
23093
|
if (!existsSync26(path)) return void 0;
|
|
22635
23094
|
try {
|
|
22636
|
-
const parsed = JSON.parse(
|
|
23095
|
+
const parsed = JSON.parse(readFileSync33(path, "utf8"));
|
|
22637
23096
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
22638
23097
|
} catch {
|
|
22639
23098
|
return void 0;
|
|
@@ -22650,9 +23109,9 @@ function packageNameToProjectName(value) {
|
|
|
22650
23109
|
return name.replace(/[-_]+/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase()).trim();
|
|
22651
23110
|
}
|
|
22652
23111
|
function deriveProjectDefaults(targetDir) {
|
|
22653
|
-
const manifest = readJson2(
|
|
22654
|
-
const pkg = readJson2(
|
|
22655
|
-
const name = String(manifest?.project_name ?? "").trim() || packageNameToProjectName(typeof pkg?.name === "string" ? pkg.name : void 0) || packageNameToProjectName(
|
|
23112
|
+
const manifest = readJson2(join38(targetDir, ".project.json"));
|
|
23113
|
+
const pkg = readJson2(join38(targetDir, "package.json"));
|
|
23114
|
+
const name = String(manifest?.project_name ?? "").trim() || packageNameToProjectName(typeof pkg?.name === "string" ? pkg.name : void 0) || packageNameToProjectName(basename13(targetDir)) || "Project";
|
|
22656
23115
|
const ticketProvider = manifest?.ticket_provider && typeof manifest.ticket_provider === "object" ? manifest.ticket_provider : {};
|
|
22657
23116
|
return {
|
|
22658
23117
|
name,
|
|
@@ -22679,11 +23138,19 @@ async function promptTextValue(message2, initialValue) {
|
|
|
22679
23138
|
}
|
|
22680
23139
|
return value.trim();
|
|
22681
23140
|
}
|
|
22682
|
-
function reportBoardDelivery(board, slug, skipRequested) {
|
|
23141
|
+
function reportBoardDelivery(board, slug, skipRequested, reached = true) {
|
|
22683
23142
|
if (board.confirmed) {
|
|
22684
23143
|
console.log(` ${green(glyph.pass)} ${bold("Board linked")} ${dim(glyph.dot)} ${cyan(`${board.provider}/${board.workspace}/${board.identifier}`)} ${dim(board.boardId)}`);
|
|
22685
23144
|
return true;
|
|
22686
23145
|
}
|
|
23146
|
+
if (!reached && !skipRequested) {
|
|
23147
|
+
console.error("");
|
|
23148
|
+
console.error(` ${xmark2} ${bold("Board step not reached")} ${dim(glyph.dot)} ${cyan(slug)}`);
|
|
23149
|
+
console.error(` ${dim("project init failed before the ticket-provider phase; no board was created or looked up")}`);
|
|
23150
|
+
console.error(` ${dim("fix the error(s) above, then re-run the same command. The provider credential is not the cause.")}`);
|
|
23151
|
+
console.error("");
|
|
23152
|
+
return false;
|
|
23153
|
+
}
|
|
22687
23154
|
const mark = skipRequested ? `${yellow(glyph.warn)}` : xmark2;
|
|
22688
23155
|
const title = skipRequested ? "No ticket board (--skip-board)" : "No ticket board";
|
|
22689
23156
|
const log = skipRequested ? console.log : console.error;
|
|
@@ -22726,7 +23193,7 @@ function actionNeedsRun(plan, kind, syncMode) {
|
|
|
22726
23193
|
if (!action || action.kind !== "project.write-manifest") return false;
|
|
22727
23194
|
const next = `${JSON.stringify(action.manifest, null, 2)}
|
|
22728
23195
|
`;
|
|
22729
|
-
return !existsSync26(action.path) ||
|
|
23196
|
+
return !existsSync26(action.path) || readFileSync33(action.path, "utf8") !== next;
|
|
22730
23197
|
}
|
|
22731
23198
|
if (kind === "copier.copy.commonproject") return true;
|
|
22732
23199
|
if (kind === "ticket-provider.create-or-link") return plan.actions.some((action) => action.kind === kind && action.enabled);
|
|
@@ -22783,11 +23250,11 @@ async function resolveProjectInitTarget(name, options) {
|
|
|
22783
23250
|
if (!targetDir && cwdGitRoot) {
|
|
22784
23251
|
targetDir = cwdGitRoot;
|
|
22785
23252
|
}
|
|
22786
|
-
if (!targetDir && !name && existsSync26(
|
|
23253
|
+
if (!targetDir && !name && existsSync26(join38(cwd, ".project.json"))) {
|
|
22787
23254
|
targetDir = cwd;
|
|
22788
23255
|
}
|
|
22789
23256
|
if (!targetDir && interactive) {
|
|
22790
|
-
const promptedName = await promptTextValue("Project name", packageNameToProjectName(
|
|
23257
|
+
const promptedName = await promptTextValue("Project name", packageNameToProjectName(basename13(cwd)));
|
|
22791
23258
|
targetDir = resolve25(await promptTextValue("Project directory", projectTargetDirUnder(promptedName, cwd)));
|
|
22792
23259
|
name = promptedName;
|
|
22793
23260
|
}
|
|
@@ -22797,14 +23264,14 @@ async function resolveProjectInitTarget(name, options) {
|
|
|
22797
23264
|
const targetExists = existsSync26(targetDir);
|
|
22798
23265
|
if (targetExists && !statSync9(targetDir).isDirectory()) throw new Error(`Target path is not a directory: ${targetDir}`);
|
|
22799
23266
|
const targetGitRoot = targetExists ? findGitRoot(targetDir) : void 0;
|
|
22800
|
-
const alreadyScaffolded = targetExists && (existsSync26(
|
|
23267
|
+
const alreadyScaffolded = targetExists && (existsSync26(join38(targetDir, ".project.json")) || existsSync26(join38(targetDir, ".copier-answers.yml")));
|
|
22801
23268
|
const syncMode = Boolean(
|
|
22802
23269
|
targetGitRoot && resolve25(targetGitRoot) === resolve25(targetDir) || alreadyScaffolded
|
|
22803
23270
|
);
|
|
22804
|
-
if (targetExists && !syncMode && !options.force &&
|
|
23271
|
+
if (targetExists && !syncMode && !options.force && readdirSync15(targetDir).length > 0) {
|
|
22805
23272
|
throw new Error(`Target already exists and is not empty: ${targetDir} (pass --force to render over it)`);
|
|
22806
23273
|
}
|
|
22807
|
-
const defaults = targetExists ? deriveProjectDefaults(targetDir) : { name: packageNameToProjectName(
|
|
23274
|
+
const defaults = targetExists ? deriveProjectDefaults(targetDir) : { name: packageNameToProjectName(basename13(targetDir)) ?? "Project", description: "" };
|
|
22808
23275
|
if (!name && interactive && !syncMode) {
|
|
22809
23276
|
name = await promptTextValue("Project name", defaults.name);
|
|
22810
23277
|
}
|
|
@@ -23075,8 +23542,9 @@ async function runProjectInit(name, options) {
|
|
|
23075
23542
|
const board = boardDelivery({ ...result2.plan, actions: plan.actions });
|
|
23076
23543
|
const skipRequested = Boolean(options.skipBoard);
|
|
23077
23544
|
const boardDelivered = board.confirmed || skipRequested;
|
|
23545
|
+
const boardStepReached = result2.phases.some((phase) => phase.id === "project.external:ticket-provider");
|
|
23078
23546
|
if (options.json) {
|
|
23079
|
-
console.log(JSON.stringify({ ...result2, ok: result2.ok && boardDelivered, board }, null, 2));
|
|
23547
|
+
console.log(JSON.stringify({ ...result2, ok: result2.ok && boardDelivered, board: { ...board, reached: boardStepReached } }, null, 2));
|
|
23080
23548
|
} else {
|
|
23081
23549
|
console.log(formatProjectInitPlan(selectedPlan));
|
|
23082
23550
|
for (const line of result2.logs) console.log(line);
|
|
@@ -23084,7 +23552,7 @@ async function runProjectInit(name, options) {
|
|
|
23084
23552
|
if (result2.migrationReport) console.log(formatMigrationReport(result2.migrationReport));
|
|
23085
23553
|
if (result2.ok && result2.changedFiles.length) console.log(` ${green(glyph.pass)} ${bold("Project synchronized")} ${dim(glyph.dot)} ${cyan(plan.project.slug)}`);
|
|
23086
23554
|
if (result2.ok && result2.changedFiles.length === 0) console.log(` ${green(glyph.pass)} ${dim("Already in parity")} ${dim(glyph.dot)} ${cyan(plan.project.slug)}`);
|
|
23087
|
-
reportBoardDelivery(board, plan.project.slug, skipRequested);
|
|
23555
|
+
reportBoardDelivery(board, plan.project.slug, skipRequested, boardStepReached || result2.ok);
|
|
23088
23556
|
const rendered = selectedPlan.actions.find((action) => action.kind === "copier.copy.commonproject");
|
|
23089
23557
|
if (result2.ok && rendered?.kind === "copier.copy.commonproject" && rendered.data.project_type === "obsidian-plugin") {
|
|
23090
23558
|
console.log(` ${bold("Next")} ${dim(`cd ${target.targetDir}`)}`);
|
|
@@ -23319,7 +23787,7 @@ program.command("reindex").argument("[project-id]", "Canonical project ID (defau
|
|
|
23319
23787
|
else if (options.all) result2 = registryRequest(location, "POST", "/v1/reindex", {});
|
|
23320
23788
|
else {
|
|
23321
23789
|
const repo = projectId ? getProject(loadProjectRegistry(location), projectId).repo_path : resolveProject(process.cwd()).root;
|
|
23322
|
-
result2 = registryRequest(location, "POST", "/v1/index", { manifest_path:
|
|
23790
|
+
result2 = registryRequest(location, "POST", "/v1/index", { manifest_path: join38(repo, ".project.json") });
|
|
23323
23791
|
}
|
|
23324
23792
|
const statuses = result2.__registry_status;
|
|
23325
23793
|
const stale = Object.entries(statuses ?? {}).filter(([, item]) => item.status !== "ok");
|